Move scrollable behavior into LayoutRow/LayoutCol

This commit is contained in:
Keavon Chambers 2022-01-22 23:02:59 -08:00
parent 3bf5023ef8
commit 45d75bd13f
11 changed files with 153 additions and 147 deletions

View File

@ -1,5 +1,6 @@
<template>
<MainWindow />
<div class="unsupported-modal-backdrop" v-if="showUnsupportedModal">
<div class="unsupported-modal">
<h2>Your browser currently doesn't support Graphite</h2>
@ -91,72 +92,74 @@ img {
display: block;
}
.scrollable,
.scrollable-x,
.scrollable-y {
// Standard
scrollbar-width: thin;
scrollbar-width: 6px;
scrollbar-gutter: 6px;
scrollbar-color: var(--color-5-dullgray) transparent;
.layout-row,
.layout-col {
.scrollable-x,
.scrollable-y {
// Standard
scrollbar-width: thin;
scrollbar-width: 6px;
scrollbar-gutter: 6px;
scrollbar-color: var(--color-5-dullgray) transparent;
&:not(:hover) {
scrollbar-width: none;
}
&:not(:hover) {
scrollbar-width: none;
}
// WebKit
&::-webkit-scrollbar {
width: calc(2px + 6px + 2px);
height: calc(2px + 6px + 2px);
}
// WebKit
&::-webkit-scrollbar {
width: calc(2px + 6px + 2px);
height: calc(2px + 6px + 2px);
}
&:not(:hover)::-webkit-scrollbar {
width: 0;
height: 0;
}
&:not(:hover)::-webkit-scrollbar {
width: 0;
height: 0;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 0 1px var(--color-5-dullgray);
border: 2px solid transparent;
border-radius: 10px;
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 0 1px var(--color-5-dullgray);
border: 2px solid transparent;
border-radius: 10px;
&:hover {
box-shadow: inset 0 0 0 1px var(--color-6-lowergray);
&:hover {
box-shadow: inset 0 0 0 1px var(--color-6-lowergray);
}
}
&::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: var(--color-5-dullgray);
border: 2px solid transparent;
border-radius: 10px;
margin: 2px;
&:hover {
background-color: var(--color-6-lowergray);
}
}
}
&::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: var(--color-5-dullgray);
border: 2px solid transparent;
border-radius: 10px;
margin: 2px;
&:hover {
background-color: var(--color-6-lowergray);
}
.scrollable-x.scrollable-y {
// Standard
overflow: auto;
// WebKit
overflow: overlay;
}
}
.scrollable {
// Standard
overflow: auto;
// WebKit
overflow: overlay;
}
.scrollable-x:not(.scrollable-y) {
// Standard
overflow-x: auto;
// WebKit
overflow-x: overlay;
}
.scrollable-x {
// Standard
overflow-x: auto;
// WebKit
overflow-x: overlay;
}
.scrollable-y {
// Standard
overflow-y: auto;
// WebKit
overflow-y: overlay;
.scrollable-y:not(.scrollable-x) {
// Standard
overflow-y: auto;
// WebKit
overflow-y: overlay;
}
}
// For placeholder messages (remove eventually)

View File

@ -1,5 +1,5 @@
<template>
<div :class="['layout-col']">
<div class="layout-col" :class="{ 'scrollable-x': scrollableX, 'scrollable-y': scrollableY }">
<slot></slot>
</div>
</template>
@ -19,7 +19,12 @@
</style>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, PropType } from "vue";
export default defineComponent({});
export default defineComponent({
props: {
scrollableX: { type: Boolean as PropType<boolean>, default: false },
scrollableY: { type: Boolean as PropType<boolean>, default: false },
},
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<div :class="['layout-row']">
<div class="layout-row" :class="{ 'scrollable-x': scrollableX, 'scrollable-y': scrollableY }">
<slot></slot>
</div>
</template>
@ -19,7 +19,12 @@
</style>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, PropType } from "vue";
export default defineComponent({});
export default defineComponent({
props: {
scrollableX: { type: Boolean as PropType<boolean>, default: false },
scrollableY: { type: Boolean as PropType<boolean>, default: false },
},
});
</script>

View File

@ -1,6 +1,6 @@
<template>
<LayoutCol :class="'document'">
<LayoutRow :class="'options-bar scrollable-x'">
<LayoutCol class="document">
<LayoutRow class="options-bar" :scrollableX="true">
<div class="left side">
<DropdownInput :menuEntries="documentModeEntries" v-model:selectedIndex="documentModeSelectionIndex" :drawIcon="true" />
@ -66,9 +66,9 @@
/>
</div>
</LayoutRow>
<LayoutRow :class="'shelf-and-viewport'">
<LayoutCol :class="'shelf'">
<div class="tools scrollable-y">
<LayoutRow class="shelf-and-viewport">
<LayoutCol class="shelf">
<LayoutCol class="tools" :scrollableY="true">
<ShelfItemInput icon="LayoutSelectTool" title="Select Tool (V)" :active="activeTool === 'Select'" :action="() => selectTool('Select')" />
<ShelfItemInput icon="LayoutCropTool" title="Crop Tool" :active="activeTool === 'Crop'" :action="() => (dialog.comingSoon(289), false) && selectTool('Crop')" />
<ShelfItemInput icon="LayoutNavigateTool" title="Navigate Tool (Z)" :active="activeTool === 'Navigate'" :action="() => selectTool('Navigate')" />
@ -104,50 +104,52 @@
<ShelfItemInput icon="VectorRectangleTool" title="Rectangle Tool (M)" :active="activeTool === 'Rectangle'" :action="() => selectTool('Rectangle')" />
<ShelfItemInput icon="VectorEllipseTool" title="Ellipse Tool (E)" :active="activeTool === 'Ellipse'" :action="() => selectTool('Ellipse')" />
<ShelfItemInput icon="VectorShapeTool" title="Shape Tool (Y)" :active="activeTool === 'Shape'" :action="() => selectTool('Shape')" />
</div>
</LayoutCol>
<div class="spacer"></div>
<div class="working-colors">
<LayoutCol class="working-colors">
<SwatchPairInput />
<div class="swap-and-reset">
<IconButton :action="swapWorkingColors" :icon="'Swap'" title="Swap (Shift+X)" :size="16" />
<IconButton :action="resetWorkingColors" :icon="'ResetColors'" title="Reset (Ctrl+Shift+X)" :size="16" />
</div>
</div>
</LayoutCol>
</LayoutCol>
<LayoutCol :class="'viewport'">
<LayoutRow :class="'bar-area'">
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Horizontal'" :class="'top-ruler'" />
<LayoutCol class="viewport">
<LayoutRow class="bar-area">
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Horizontal'" class="top-ruler" />
</LayoutRow>
<LayoutRow :class="'canvas-area'">
<LayoutCol :class="'bar-area'">
<LayoutRow class="canvas-area">
<LayoutCol class="bar-area">
<CanvasRuler :origin="rulerOrigin.y" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Vertical'" />
</LayoutCol>
<LayoutCol :class="'canvas-area'">
<LayoutCol class="canvas-area">
<div class="canvas" ref="canvas" :style="{ cursor: canvasCursor }" @pointerdown="(e: PointerEvent) => canvasPointerDown(e)">
<svg class="artboards" v-html="artboardSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
<svg class="artwork" v-html="artworkSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
<svg class="overlays" v-html="overlaysSvg" :style="{ width: canvasSvgWidth, height: canvasSvgHeight }"></svg>
</div>
</LayoutCol>
<LayoutCol :class="'bar-area'">
<LayoutCol class="bar-area">
<PersistentScrollbar
:direction="'Vertical'"
:handlePosition="scrollbarPos.y"
@update:handlePosition="(newValue: number) => translateCanvasY(newValue)"
v-model:handleLength="scrollbarSize.y"
@pressTrack="(delta: number) => pageY(delta)"
:class="'right-scrollbar'"
class="right-scrollbar"
/>
</LayoutCol>
</LayoutRow>
<LayoutRow :class="'bar-area'">
<LayoutRow class="bar-area">
<PersistentScrollbar
:direction="'Horizontal'"
:handlePosition="scrollbarPos.x"
@update:handlePosition="(newValue: number) => translateCanvasX(newValue)"
v-model:handleLength="scrollbarSize.x"
@pressTrack="(delta: number) => pageX(delta)"
:class="'bottom-scrollbar'"
class="bottom-scrollbar"
/>
</LayoutRow>
</LayoutCol>
@ -191,9 +193,13 @@
min-height: 8px;
}
.working-colors .swap-and-reset {
.working-colors {
flex: 0 0 auto;
display: flex;
.swap-and-reset {
flex: 0 0 auto;
display: flex;
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<LayoutCol :class="'layer-tree-panel'">
<LayoutRow :class="'options-bar'">
<LayoutCol class="layer-tree-panel">
<LayoutRow class="options-bar">
<DropdownInput
v-model:selectedIndex="blendModeSelectedIndex"
@update:selectedIndex="(newSelectedIndex: number) => setLayerBlendMode(newSelectedIndex)"
@ -28,8 +28,8 @@
<p>The contents of this popover menu are coming soon</p>
</PopoverButton>
</LayoutRow>
<LayoutRow :class="'layer-tree scrollable-y'">
<LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateInsertLine($event)" @dragend="drop()">
<LayoutRow class="layer-tree" :scrollableY="true">
<LayoutCol class="list" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateInsertLine($event)" @dragend="drop()">
<div class="layer-row" v-for="({ entry: layer }, index) in layers" :key="String(layer.path.slice(-1))">
<div class="visibility">
<IconButton

View File

@ -2,14 +2,14 @@
<div class="dialog-modal">
<FloatingMenu :type="'Dialog'" :direction="'Center'">
<LayoutRow>
<LayoutCol :class="'icon-column'">
<LayoutCol class="icon-column">
<!-- `dialog.state.icon` class exists to provide special sizing in CSS to specific icons -->
<IconLabel :icon="dialog.state.icon" :class="dialog.state.icon.toLowerCase()" />
</LayoutCol>
<LayoutCol :class="'main-column'">
<TextLabel :bold="true" :class="'heading'">{{ dialog.state.heading }}</TextLabel>
<TextLabel :class="'details'">{{ dialog.state.details }}</TextLabel>
<LayoutRow :class="'buttons-row'" v-if="dialog.state.buttons.length > 0">
<LayoutCol class="main-column">
<TextLabel :bold="true" class="heading">{{ dialog.state.heading }}</TextLabel>
<TextLabel class="details">{{ dialog.state.details }}</TextLabel>
<LayoutRow class="buttons-row" v-if="dialog.state.buttons.length > 0">
<TextButton v-for="(button, index) in dialog.state.buttons" :key="index" :title="button.tooltip" :action="() => button.callback && button.callback()" v-bind="button.props" />
</LayoutRow>
</LayoutCol>

View File

@ -2,9 +2,9 @@
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open || type === 'Dialog'" ref="floatingMenu">
<div class="tail" v-if="type === 'Popover'"></div>
<div class="floating-menu-container" ref="floatingMenuContainer">
<div class="floating-menu-content" :class="{ 'scrollable-y': scrollable }" ref="floatingMenuContent" :style="floatingMenuContentStyle">
<LayoutCol class="floating-menu-content" :scrollableY="scrollableY" ref="floatingMenuContent" :style="floatingMenuContentStyle">
<slot></slot>
</div>
</LayoutCol>
</div>
</div>
</template>
@ -179,6 +179,8 @@
<script lang="ts">
import { defineComponent, PropType, StyleValue } from "vue";
import LayoutCol from "@/components/layout/LayoutCol.vue";
export type MenuDirection = "Top" | "Bottom" | "Left" | "Right" | "TopLeft" | "TopRight" | "BottomLeft" | "BottomRight" | "Center";
export type MenuType = "Popover" | "Dropdown" | "Dialog";
@ -190,14 +192,13 @@ export default defineComponent({
type: { type: String as PropType<MenuType>, required: true },
windowEdgeMargin: { type: Number as PropType<number>, default: 6 },
minWidth: { type: Number as PropType<number>, default: 0 },
scrollable: { type: Boolean as PropType<boolean>, default: false },
scrollableY: { type: Boolean as PropType<boolean>, default: false },
},
data() {
const containerResizeObserver = new ResizeObserver((entries) => {
const content = entries[0].target.querySelector(".floating-menu-content") as HTMLElement;
content.style.minWidth = `${entries[0].contentRect.width}px`;
});
return {
open: false,
pointerStillDown: false,
@ -206,9 +207,11 @@ export default defineComponent({
},
updated() {
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
const floatingMenuContentComponent = this.$refs.floatingMenuContent as typeof LayoutCol;
const floatingMenuContent = floatingMenuContentComponent && (floatingMenuContentComponent.$el as HTMLElement);
const workspace = document.querySelector(".workspace-row");
if (!floatingMenuContainer || !floatingMenuContent || !workspace) return;
if (!floatingMenuContainer || !floatingMenuContentComponent || !floatingMenuContent || !workspace) return;
const workspaceBounds = workspace.getBoundingClientRect();
const floatingMenuBounds = floatingMenuContent.getBoundingClientRect();
@ -224,13 +227,11 @@ export default defineComponent({
floatingMenuContent.style.left = `${this.windowEdgeMargin}px`;
if (workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderDirection2 = "Left";
}
if (floatingMenuBounds.right + this.windowEdgeMargin >= workspaceBounds.right) {
floatingMenuContent.style.right = `${this.windowEdgeMargin}px`;
if (workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderDirection2 = "Right";
}
}
if (this.direction === "Left" || this.direction === "Right") {
zeroedBorderDirection2 = this.direction === "Left" ? "Right" : "Left";
@ -238,7 +239,6 @@ export default defineComponent({
floatingMenuContent.style.top = `${this.windowEdgeMargin}px`;
if (workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderDirection1 = "Top";
}
if (floatingMenuBounds.bottom + this.windowEdgeMargin >= workspaceBounds.bottom) {
floatingMenuContent.style.bottom = `${this.windowEdgeMargin}px`;
if (workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderDirection1 = "Bottom";
@ -277,23 +277,21 @@ export default defineComponent({
},
getWidth(callback: (width: number) => void) {
this.$nextTick(() => {
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
const width = floatingMenuContent.clientWidth;
callback(width);
});
},
disableMinWidth(callback: (minWidth: string) => void) {
this.$nextTick(() => {
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
const initialMinWidth = floatingMenuContent.style.minWidth;
floatingMenuContent.style.minWidth = "0";
callback(initialMinWidth);
});
},
enableMinWidth(minWidth: string) {
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
const floatingMenuContent = (this.$refs.floatingMenuContent as typeof LayoutCol).$el as HTMLElement;
floatingMenuContent.style.minWidth = minWidth;
},
pointerMoveHandler(e: PointerEvent) {
@ -303,22 +301,18 @@ export default defineComponent({
// TODO: Simplify the following expression when optional chaining is supported by the build system
const pointerOverOwnFloatingMenuSpawner =
pointerOverFloatingMenuSpawner && pointerOverFloatingMenuSpawner.parentElement && pointerOverFloatingMenuSpawner.parentElement.contains(this.$refs.floatingMenu as HTMLElement);
// Swap this open floating menu with the one created by the floating menu spawner being hovered over
if (pointerOverFloatingMenuSpawner && !pointerOverOwnFloatingMenuSpawner) {
this.setClosed();
pointerOverFloatingMenuSpawner.click();
}
// Close the floating menu if the pointer has strayed far enough from its bounds
if (this.isPointerEventOutsideFloatingMenu(e, POINTER_STRAY_DISTANCE) && !pointerOverOwnFloatingMenuSpawner && !pointerOverFloatingMenuKeepOpen) {
// TODO: Extend this rectangle bounds check to all `data-hover-menu-keep-open` element bounds up the DOM tree since currently
// submenus disappear with zero stray distance if the cursor is further than the stray distance from only the top-level menu
this.setClosed();
}
const eventIncludesLmb = Boolean(e.buttons & 1);
// Clean up any messes from lost pointerup events
if (!this.open && !eventIncludesLmb) {
this.pointerStillDown = false;
@ -329,7 +323,6 @@ export default defineComponent({
// Close the floating menu if the pointer clicked outside the floating menu (but within stray distance)
if (this.isPointerEventOutsideFloatingMenu(e)) {
this.setClosed();
// Track if the left pointer button is now down so its later click event can be canceled
const eventIsForLmb = e.button === 0;
if (eventIsForLmb) this.pointerStillDown = true;
@ -337,12 +330,10 @@ export default defineComponent({
},
pointerUpHandler(e: PointerEvent) {
const eventIsForLmb = e.button === 0;
if (this.pointerStillDown && eventIsForLmb) {
// Clean up self
this.pointerStillDown = false;
window.removeEventListener("pointerup", this.pointerUpHandler);
// Prevent the click event from firing, which would normally occur right after this pointerup event
window.addEventListener("click", this.clickHandlerCapture, true);
}
@ -350,7 +341,6 @@ export default defineComponent({
clickHandlerCapture(e: MouseEvent) {
// Stop the click event from reopening this floating menu if the click event targets the floating menu's button
e.stopPropagation();
// Clean up self
window.removeEventListener("click", this.clickHandlerCapture, true);
},
@ -374,13 +364,10 @@ export default defineComponent({
if (newState && !oldState) {
// Close floating menu if pointer strays far enough away
window.addEventListener("pointermove", this.pointerMoveHandler);
// Close floating menu if pointer is outside (but within stray distance)
window.addEventListener("pointerdown", this.pointerDownHandler);
// Cancel the subsequent click event to prevent the floating menu from reopening if the floating menu's button is the click event target
window.addEventListener("pointerup", this.pointerUpHandler);
// Floating menu min-width resize observer
this.$nextTick(() => {
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
@ -390,12 +377,10 @@ export default defineComponent({
}
});
}
// Switching from open to closed
if (!newState && oldState) {
window.removeEventListener("pointermove", this.pointerMoveHandler);
window.removeEventListener("pointerdown", this.pointerDownHandler);
this.containerResizeObserver.disconnect();
}
},
@ -407,5 +392,6 @@ export default defineComponent({
};
},
},
components: { LayoutCol },
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<FloatingMenu :class="'menu-list'" :direction="direction" :type="'Dropdown'" ref="floatingMenu" :windowEdgeMargin="0" :scrollable="scrollable" data-hover-menu-keep-open>
<FloatingMenu class="menu-list" :direction="direction" :type="'Dropdown'" ref="floatingMenu" :windowEdgeMargin="0" :scrollableY="scrollableY" data-hover-menu-keep-open>
<template v-for="(section, sectionIndex) in menuEntries" :key="sectionIndex">
<Separator :type="'List'" :direction="'Vertical'" v-if="sectionIndex > 0" />
<div
@ -12,8 +12,8 @@
@pointerleave="handleEntryPointerLeave(entry)"
:data-hover-menu-spawner-extend="entry.children && []"
>
<CheckboxInput v-if="entry.checkbox" v-model:checked="entry.checked" :outlineStyle="true" :class="'entry-checkbox'" />
<IconLabel v-else-if="entry.icon && drawIcon" :icon="entry.icon" :class="'entry-icon'" />
<CheckboxInput v-if="entry.checkbox" v-model:checked="entry.checked" :outlineStyle="true" class="entry-checkbox" />
<IconLabel v-else-if="entry.icon && drawIcon" :icon="entry.icon" class="entry-icon" />
<div v-else-if="drawIcon" class="no-icon"></div>
<span class="entry-label">{{ entry.label }}</span>
@ -28,7 +28,7 @@
v-if="entry.children"
:direction="'TopRight'"
:menuEntries="entry.children"
v-bind="{ defaultAction, minWidth, drawIcon, scrollable }"
v-bind="{ defaultAction, minWidth, drawIcon, scrollableY }"
:ref="(ref: any) => setEntryRefs(entry, ref)"
/>
</div>
@ -168,7 +168,7 @@ const MenuList = defineComponent({
defaultAction: { type: Function as PropType<() => void>, required: false },
minWidth: { type: Number as PropType<number>, default: 0 },
drawIcon: { type: Boolean as PropType<boolean>, default: false },
scrollable: { type: Boolean as PropType<boolean>, default: false },
scrollableY: { type: Boolean as PropType<boolean>, default: false },
},
methods: {
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu) {

View File

@ -1,9 +1,9 @@
<template>
<div class="dropdown-input">
<div class="dropdown-box" :class="{ disabled }" :style="{ minWidth: `${minWidth}px` }" @click="() => clickDropdownBox()" data-hover-menu-spawner>
<IconLabel :class="'dropdown-icon'" :icon="activeEntry.icon" v-if="activeEntry.icon" />
<IconLabel class="dropdown-icon" :icon="activeEntry.icon" v-if="activeEntry.icon" />
<span>{{ activeEntry.label }}</span>
<IconLabel :class="'dropdown-arrow'" :icon="'DropdownArrow'" />
<IconLabel class="dropdown-arrow" :icon="'DropdownArrow'" />
</div>
<MenuList
v-model:activeEntry="activeEntry"
@ -12,7 +12,7 @@
:menuEntries="menuEntries"
:direction="'Bottom'"
:drawIcon="drawIcon"
:scrollable="true"
:scrollableY="true"
ref="menuList"
/>
</div>

View File

@ -1,12 +1,12 @@
<template>
<LayoutCol class="main-window">
<LayoutRow :class="'title-bar-row'">
<LayoutRow class="title-bar-row">
<TitleBar :platform="platform" :maximized="maximized" />
</LayoutRow>
<LayoutRow :class="'workspace-row'">
<LayoutRow class="workspace-row">
<Workspace />
</LayoutRow>
<LayoutRow :class="'status-bar-row'">
<LayoutRow class="status-bar-row">
<StatusBar />
</LayoutRow>
</LayoutCol>

View File

@ -1,7 +1,7 @@
<template>
<div class="panel">
<div class="tab-bar" :class="{ 'min-widths': tabMinWidths }">
<div class="tab-group scrollable-x">
<LayoutCol class="panel">
<LayoutRow class="tab-bar" :class="{ 'min-widths': tabMinWidths }">
<LayoutRow class="tab-group" :scrollableX="true">
<div
class="tab"
:class="{ active: tabIndex === tabActiveIndex }"
@ -13,41 +13,35 @@
<span>{{ tabLabel }}</span>
<IconButton :action="(e) => (e && e.stopPropagation(), closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
</div>
</div>
</LayoutRow>
<PopoverButton :icon="'VerticalEllipsis'">
<h3>Panel Options</h3>
<p>The contents of this popover menu are coming soon</p>
</PopoverButton>
</div>
<div class="panel-body">
</LayoutRow>
<LayoutCol class="panel-body">
<component :is="panelType" />
</div>
</div>
</LayoutCol>
</LayoutCol>
</template>
<style lang="scss">
.panel {
background: var(--color-1-nearblack);
border-radius: 8px;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
.tab-bar {
height: 28px;
display: flex;
flex-direction: row;
min-height: auto;
&.min-widths .tab-group .tab {
min-width: 124px;
min-width: 120px;
max-width: 360px;
}
.tab-group {
flex: 1 1 100%;
display: flex;
flex-direction: row;
position: relative;
// This always hangs out at the end of the last tab, providing 16px (15px plus the 1px reserved for the separator line) to the right of the tabs.
@ -154,6 +148,8 @@
<script lang="ts">
import { defineComponent, PropType } from "vue";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import Document from "@/components/panels/Document.vue";
import LayerTree from "@/components/panels/LayerTree.vue";
import Minimap from "@/components/panels/Minimap.vue";
@ -161,7 +157,7 @@ import Properties from "@/components/panels/Properties.vue";
import IconButton from "@/components/widgets/buttons/IconButton.vue";
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
const components = {
const panelComponents = {
Document,
Properties,
LayerTree,
@ -169,18 +165,23 @@ const components = {
IconButton,
PopoverButton,
};
type PanelTypes = keyof typeof panelComponents;
export default defineComponent({
inject: ["documents"],
components,
props: {
tabMinWidths: { type: Boolean as PropType<boolean>, default: false },
tabCloseButtons: { type: Boolean as PropType<boolean>, default: false },
tabLabels: { type: Array as PropType<string[]>, required: true },
tabActiveIndex: { type: Number as PropType<number>, required: true },
panelType: { type: String as PropType<keyof typeof components>, required: true },
panelType: { type: String as PropType<PanelTypes>, required: true },
clickAction: { type: Function as PropType<(index: number) => void>, required: false },
closeAction: { type: Function as PropType<(index: number) => void>, required: false },
},
components: {
LayoutCol,
LayoutRow,
...panelComponents,
},
});
</script>