mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Add the Dropdown Input widget (#168)
Fixes #135. * Add the Dropdown Input widget * Fix font loading race condition
This commit is contained in:
parent
eae2d70e93
commit
91303459a9
10 changed files with 339 additions and 46 deletions
6
client/web/assets/16px-solid/viewport-design-mode.svg
Normal file
6
client/web/assets/16px-solid/viewport-design-mode.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="M15,6.06V15H9.69c-0.22,0.36-0.49,0.69-0.79,1H16V4.46C15.71,4.96,15.37,5.49,15,6.06z" />
|
||||
<path d="M5.57,9.4c-1.22,0.07-2.27,1.09-2.94,3.05C1.94,14.47,0,15.36,0,15.36c2.08,0.71,4.3,0.95,6.26,0.08c1.75-0.78,2.38-2.35,2.22-3.76C8.35,10.58,7.27,9.3,5.57,9.4z" />
|
||||
<path d="M15.42,0.08c-0.69-0.55-3.27,1.97-6.11,5.14c-1,1.12-1.99,2.3-2.6,3.34C7.17,8.6,7.66,8.78,8.11,9.12C8.67,9.55,9,10.05,9.14,10.49c0.94-0.86,1.75-1.96,2.68-3.3C14.29,3.65,16.11,0.62,15.42,0.08z" />
|
||||
<path d="M0.74,11.81c0.08-0.24,0.17-0.43,0.26-0.65V1h9.52c0.38-0.38,0.72-0.71,1.05-1H0v12.93C0.29,12.65,0.58,12.28,0.74,11.81z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 673 B |
6
client/web/assets/16px-solid/viewport-select-mode.svg
Normal file
6
client/web/assets/16px-solid/viewport-select-mode.svg
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="M15,1v1.85c0.37,0.32,0.7,0.66,1,1.02V0H0v3.19c0.24-0.28,0.49-0.55,0.78-0.81C0.85,2.32,0.93,2.27,1,2.21V1H15z" />
|
||||
<path d="M0,11.17v4.37l1-0.66v-2.15c-0.15-0.28-0.27-0.58-0.34-0.89C0.42,11.62,0.2,11.39,0,11.17z" />
|
||||
<path d="M15,12.74V15H8.54c-0.23,0.24-0.41,0.52-0.55,0.77L7.86,16H16v-4.11C15.7,12.2,15.37,12.49,15,12.74z" />
|
||||
<path d="M2.54,10.76c-0.01,0.49,0.12,0.96,0.41,1.33c0.35,0.45,1.02,0.93,2.29,0.93c0.03,0,0.07-0.01,0.1-0.01C5.19,13.78,4.62,14.86,2.91,16h2.38c0.4-0.41,0.71-0.81,0.94-1.19l0,0c0.87-1.57,2.19-2.48,3.72-2.54c3.31-0.14,5.61-1.86,5.61-4.2c0-2.98-3.07-5.42-7.47-5.94C5.86,1.87,3.63,2.52,2.11,3.88C1.03,4.84,0.44,6.02,0.44,7.22C0.44,8.57,1.2,9.83,2.54,10.76z M6.91,11.81c-0.03,0.02-0.06,0.04-0.09,0.06c-0.04-0.23-0.1-0.38-0.12-0.45c-0.33-0.86-1.11-1.01-1.62-1.11c-0.15-0.03-0.31-0.06-0.45-0.11c-0.11-0.05-0.22-0.1-0.33-0.15C4.51,9.75,4.85,9.49,5.3,9.43c0.85-0.1,1.48,0.1,1.77,0.56C7.34,10.45,7.27,11.14,6.91,11.81z M3.1,5c1.03-0.92,2.49-1.42,4.01-1.42c0.27,0,0.53,0.02,0.8,0.05c4.04,0.47,6.15,2.6,6.15,4.45c0,1.49-1.72,2.6-4.18,2.7c-0.4,0.02-0.78,0.09-1.16,0.19c0.05-0.63-0.08-1.24-0.39-1.75c-0.6-0.97-1.78-1.43-3.22-1.26c-0.84,0.1-1.6,0.6-2.08,1.32c-0.71-0.6-1.1-1.31-1.1-2.04C1.94,6.47,2.36,5.66,3.1,5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -2,7 +2,7 @@
|
|||
<LayoutCol :class="'document'">
|
||||
<LayoutRow :class="'options-bar'">
|
||||
<div class="left side">
|
||||
<span class="label">Select</span>
|
||||
<DropdownInput :menuEntries="modeMenuEntries" :default="modeMenuEntries[0][0]" :drawIcon="true" />
|
||||
|
||||
<Separator :type="SeparatorType.Section" />
|
||||
|
||||
|
|
@ -132,11 +132,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 4px;
|
||||
|
||||
.label {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,21 +172,19 @@ import IconButton from "../widgets/buttons/IconButton.vue";
|
|||
import PopoverButton from "../widgets/buttons/PopoverButton.vue";
|
||||
import RadioInput from "../widgets/inputs/RadioInput.vue";
|
||||
import NumberInput from "../widgets/inputs/NumberInput.vue";
|
||||
import DropdownInput from "../widgets/inputs/DropdownInput.vue";
|
||||
import { SectionsOfMenuListEntries } from "../widgets/floating-menus/MenuList.vue";
|
||||
|
||||
const modeMenuEntries: SectionsOfMenuListEntries = [
|
||||
[
|
||||
{ label: "Design Mode", icon: "ViewportDesignMode" },
|
||||
{ label: "Select Mode", icon: "ViewportSelectMode" },
|
||||
],
|
||||
];
|
||||
|
||||
const wasm = import("../../../wasm/pkg");
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
LayoutRow,
|
||||
LayoutCol,
|
||||
WorkingColors,
|
||||
ShelfItem,
|
||||
Separator,
|
||||
IconButton,
|
||||
PopoverButton,
|
||||
RadioInput,
|
||||
NumberInput,
|
||||
},
|
||||
methods: {
|
||||
async canvasMouseDown(e: MouseEvent) {
|
||||
const { on_mouse_down } = await wasm;
|
||||
|
|
@ -259,7 +252,20 @@ export default defineComponent({
|
|||
MenuDirection,
|
||||
SeparatorDirection,
|
||||
SeparatorType,
|
||||
modeMenuEntries,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
LayoutRow,
|
||||
LayoutCol,
|
||||
WorkingColors,
|
||||
ShelfItem,
|
||||
Separator,
|
||||
IconButton,
|
||||
PopoverButton,
|
||||
RadioInput,
|
||||
NumberInput,
|
||||
DropdownInput,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<LayoutCol :class="'layer-tree-panel'">
|
||||
<LayoutRow :class="'options-bar'">
|
||||
<DropdownInput :menuEntries="blendModeMenuEntries" :default="blendModeMenuEntries[0][0]" />
|
||||
|
||||
<Separator :type="SeparatorType.Related" />
|
||||
|
||||
<NumberInput :value="100" :unit="`%`" />
|
||||
|
||||
<Separator :type="SeparatorType.Related" />
|
||||
|
|
@ -38,6 +42,10 @@
|
|||
margin: 0 4px;
|
||||
align-items: center;
|
||||
|
||||
.dropdown-input {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.number-input {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
|
@ -88,19 +96,21 @@ import PopoverButton from "../widgets/buttons/PopoverButton.vue";
|
|||
import { MenuDirection } from "../widgets/floating-menus/FloatingMenu.vue";
|
||||
import IconButton from "../widgets/buttons/IconButton.vue";
|
||||
import Icon from "../widgets/labels/Icon.vue";
|
||||
import DropdownInput from "../widgets/inputs/DropdownInput.vue";
|
||||
import { SectionsOfMenuListEntries } from "../widgets/floating-menus/MenuList.vue";
|
||||
|
||||
const wasm = import("../../../wasm/pkg");
|
||||
|
||||
const blendModeMenuEntries: SectionsOfMenuListEntries = [
|
||||
[{ label: "Normal" }],
|
||||
[{ label: "Multiply" }, { label: "Darken" }, { label: "Color Burn" }, { label: "Linear Burn" }, { label: "Darker Color" }],
|
||||
[{ label: "Screen" }, { label: "Lighten" }, { label: "Color Dodge" }, { label: "Linear Dodge (Add)" }, { label: "Lighter Color" }],
|
||||
[{ label: "Overlay" }, { label: "Soft Light" }, { label: "Hard Light" }, { label: "Vivid Light" }, { label: "Linear Light" }, { label: "Pin Light" }, { label: "Hard Mix" }],
|
||||
[{ label: "Difference" }, { label: "Exclusion" }, { label: "Subtract" }, { label: "Divide" }],
|
||||
[{ label: "Hue" }, { label: "Saturation" }, { label: "Color" }, { label: "Luminosity" }],
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
LayoutRow,
|
||||
LayoutCol,
|
||||
Separator,
|
||||
PopoverButton,
|
||||
NumberInput,
|
||||
IconButton,
|
||||
Icon,
|
||||
},
|
||||
props: {},
|
||||
methods: {
|
||||
async toggleLayerVisibility(path: BigUint64Array) {
|
||||
|
|
@ -125,10 +135,21 @@ export default defineComponent({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
blendModeMenuEntries,
|
||||
MenuDirection,
|
||||
SeparatorType,
|
||||
layers: [] as Array<LayerPanelEntry>,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
LayoutRow,
|
||||
LayoutCol,
|
||||
Separator,
|
||||
PopoverButton,
|
||||
NumberInput,
|
||||
IconButton,
|
||||
Icon,
|
||||
DropdownInput,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open" ref="floatingMenu">
|
||||
<div class="tail" v-if="type === MenuType.Popover"></div>
|
||||
<div class="floating-menu-container" ref="floatingMenuContainer">
|
||||
<div class="floating-menu-content" ref="floatingMenuContent">
|
||||
<div class="floating-menu-content" ref="floatingMenuContent" :style="{ minWidth: minWidth > 0 ? `${minWidth}px` : undefined }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -192,6 +192,7 @@ export default defineComponent({
|
|||
direction: { type: String, default: MenuDirection.Bottom },
|
||||
type: { type: String, required: true },
|
||||
windowEdgeMargin: { type: Number, default: 8 },
|
||||
minWidth: { type: Number, default: 0 },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -237,6 +238,27 @@ export default defineComponent({
|
|||
isOpen(): boolean {
|
||||
return this.open;
|
||||
},
|
||||
getWidth(callback: (width: number) => void) {
|
||||
this.$nextTick(() => {
|
||||
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
|
||||
const width = floatingMenuContent.clientWidth;
|
||||
|
||||
callback(width);
|
||||
});
|
||||
},
|
||||
disableMinWidth(callback: (minWidth: string) => void) {
|
||||
this.$nextTick(() => {
|
||||
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
|
||||
const initialMinWidth = floatingMenuContent.style.minWidth;
|
||||
floatingMenuContent.style.minWidth = "0";
|
||||
|
||||
callback(initialMinWidth);
|
||||
});
|
||||
},
|
||||
enableMinWidth(minWidth: string) {
|
||||
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
|
||||
floatingMenuContent.style.minWidth = minWidth;
|
||||
},
|
||||
mouseMoveHandler(e: MouseEvent) {
|
||||
const MOUSE_STRAY_DISTANCE = 100;
|
||||
const target = e.target as HTMLElement;
|
||||
|
|
@ -299,6 +321,7 @@ export default defineComponent({
|
|||
},
|
||||
isMouseEventOutsideFloatingMenu(e: MouseEvent, extraDistanceAllowed = 0): boolean {
|
||||
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
|
||||
if (!floatingMenuContent) return true;
|
||||
const floatingMenuBounds = floatingMenuContent.getBoundingClientRect();
|
||||
|
||||
if (floatingMenuBounds.left - e.clientX >= extraDistanceAllowed) return true;
|
||||
|
|
|
|||
|
|
@ -6,19 +6,28 @@
|
|||
v-for="(entry, entryIndex) in section"
|
||||
:key="entryIndex"
|
||||
class="row"
|
||||
:class="{ open: isMenuEntryOpen(entry) }"
|
||||
:class="{ open: isMenuEntryOpen(entry), active: entry === activeEntry }"
|
||||
@click="handleEntryClick(entry)"
|
||||
@mouseenter="handleEntryMouseEnter(entry)"
|
||||
@mouseleave="handleEntryMouseLeave(entry)"
|
||||
:data-hover-menu-spawner-extend="entry.children && []"
|
||||
>
|
||||
<Icon :icon="entry.icon" v-if="entry.icon" />
|
||||
<div class="no-icon" v-else />
|
||||
<span class="label">{{ entry.label }}</span>
|
||||
<Icon :icon="entry.icon" v-if="entry.icon && drawIcon" />
|
||||
<div class="no-icon" v-else-if="drawIcon" />
|
||||
<span class="entry-label">{{ entry.label }}</span>
|
||||
<UserInputLabel v-if="entry.shortcut && entry.shortcut.length" :inputKeys="[entry.shortcut]" />
|
||||
<div class="submenu-arrow" v-if="entry.children && entry.children.length"></div>
|
||||
<div class="no-submenu-arrow" v-else></div>
|
||||
<MenuList v-if="entry.children" :menuEntries="entry.children" :direction="MenuDirection.TopRight" :ref="(ref) => setEntryRefs(entry, ref)" />
|
||||
<MenuList
|
||||
v-if="entry.children"
|
||||
:direction="MenuDirection.TopRight"
|
||||
:menuEntries="entry.children"
|
||||
:activeEntry="activeEntry"
|
||||
:minWidth="minWidth"
|
||||
:defaultAction="defaultAction"
|
||||
:drawIcon="drawIcon"
|
||||
:ref="(ref) => setEntryRefs(entry, ref)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FloatingMenu>
|
||||
|
|
@ -27,8 +36,9 @@
|
|||
<style lang="scss">
|
||||
.menu-list {
|
||||
.floating-menu-container .floating-menu-content {
|
||||
min-width: 240px;
|
||||
padding: 4px 0;
|
||||
position: absolute;
|
||||
min-width: 100%;
|
||||
|
||||
.row {
|
||||
height: 20px;
|
||||
|
|
@ -36,6 +46,7 @@
|
|||
align-items: center;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
|
||||
& > * {
|
||||
flex: 0 0 auto;
|
||||
|
|
@ -49,18 +60,23 @@
|
|||
width: 16px;
|
||||
}
|
||||
|
||||
.label {
|
||||
.entry-label {
|
||||
flex: 1 1 100%;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.icon,
|
||||
.no-icon,
|
||||
.label {
|
||||
.no-icon {
|
||||
margin: 0 4px;
|
||||
|
||||
& + .entry-label {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-input-label {
|
||||
margin: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.submenu-arrow {
|
||||
|
|
@ -77,14 +93,19 @@
|
|||
|
||||
.submenu-arrow,
|
||||
.no-submenu-arrow {
|
||||
margin-left: 4px;
|
||||
margin-right: 2px;
|
||||
margin-left: 6px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.open {
|
||||
&.open,
|
||||
&.active {
|
||||
background: var(--color-6-lowergray);
|
||||
|
||||
&.active {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
|
|
@ -106,28 +127,38 @@ import Icon from "../labels/Icon.vue";
|
|||
import UserInputLabel from "../labels/UserInputLabel.vue";
|
||||
|
||||
export type MenuListEntries = Array<MenuListEntry>;
|
||||
export type SectionsOfMenuListEntries = Array<MenuListEntries>;
|
||||
|
||||
export interface MenuListEntry {
|
||||
interface MenuListEntryData {
|
||||
label?: string;
|
||||
icon?: string;
|
||||
// TODO: Add `checkbox` (which overrides any `icon`)
|
||||
shortcut?: Array<string>;
|
||||
action?: Function;
|
||||
children?: Array<Array<MenuListEntry>>;
|
||||
ref?: typeof FloatingMenu | typeof MenuList;
|
||||
children?: SectionsOfMenuListEntries;
|
||||
}
|
||||
|
||||
export type MenuListEntry = MenuListEntryData & { ref?: typeof FloatingMenu | typeof MenuList };
|
||||
|
||||
const MenuList = defineComponent({
|
||||
props: {
|
||||
direction: { type: String as PropType<MenuDirection>, value: MenuDirection.Bottom },
|
||||
menuEntries: { type: Array as PropType<MenuListEntries>, required: true },
|
||||
direction: { type: String as PropType<MenuDirection>, default: MenuDirection.Bottom },
|
||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
activeEntry: { type: Object as PropType<MenuListEntry>, required: false },
|
||||
minWidth: { type: Number, default: 0 },
|
||||
defaultAction: { type: Function, required: false },
|
||||
widthChanged: { type: Function, required: false },
|
||||
drawIcon: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu) {
|
||||
if (ref) menuEntry.ref = ref;
|
||||
},
|
||||
handleEntryClick(menuEntry: MenuListEntry) {
|
||||
(this.$refs.floatingMenu as typeof FloatingMenu).setClosed();
|
||||
|
||||
if (menuEntry.action) menuEntry.action();
|
||||
else alert("This action is not yet implemented");
|
||||
else if (this.defaultAction) this.defaultAction(menuEntry);
|
||||
},
|
||||
handleEntryMouseEnter(menuEntry: MenuListEntry) {
|
||||
if (!menuEntry.children || !menuEntry.children.length) return;
|
||||
|
|
@ -161,6 +192,57 @@ const MenuList = defineComponent({
|
|||
const floatingMenu = this.$refs.floatingMenu as typeof FloatingMenu;
|
||||
return Boolean(floatingMenu && floatingMenu.isOpen());
|
||||
},
|
||||
measureAndReportWidth() {
|
||||
const { widthChanged } = this;
|
||||
if (!widthChanged) return;
|
||||
|
||||
// API is experimental but supported in all browsers - https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(document as any).fonts.ready.then(() => {
|
||||
const floatingMenu = this.$refs.floatingMenu as typeof FloatingMenu;
|
||||
|
||||
// Save open/closed state before forcing open, if necessary, for measurement
|
||||
const initiallyOpen = floatingMenu.isOpen();
|
||||
if (!initiallyOpen) floatingMenu.setOpen();
|
||||
|
||||
floatingMenu.disableMinWidth((initialMinWidth: string) => {
|
||||
floatingMenu.getWidth((width: number) => {
|
||||
floatingMenu.enableMinWidth(initialMinWidth);
|
||||
|
||||
// Restore open/closed state if it was forced open for measurement
|
||||
if (!initiallyOpen) floatingMenu.setClosed();
|
||||
|
||||
widthChanged(width);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
menuEntriesWithoutRefs(): Array<Array<MenuListEntryData>> {
|
||||
const { menuEntries } = this;
|
||||
return menuEntries.map((entries) =>
|
||||
entries.map((entry) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { ref, ...entryWithoutRef } = entry;
|
||||
return entryWithoutRef;
|
||||
})
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.measureAndReportWidth();
|
||||
},
|
||||
updated() {
|
||||
this.measureAndReportWidth();
|
||||
},
|
||||
watch: {
|
||||
menuEntriesWithoutRefs: {
|
||||
handler() {
|
||||
this.measureAndReportWidth();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
113
client/web/src/components/widgets/inputs/DropdownInput.vue
Normal file
113
client/web/src/components/widgets/inputs/DropdownInput.vue
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<div class="dropdown-input">
|
||||
<div class="dropdown-box" :style="{ minWidth: `${minWidth}px` }" @click="clickDropdownBox" data-hover-menu-spawner>
|
||||
<Icon :class="'dropdown-icon'" :icon="activeEntry.icon" v-if="activeEntry.icon" />
|
||||
<span>{{ activeEntry.label }}</span>
|
||||
<Icon :class="'dropdown-arrow'" :icon="'DropdownArrow'" />
|
||||
</div>
|
||||
<MenuList
|
||||
:menuEntries="menuEntries"
|
||||
:activeEntry="activeEntry"
|
||||
:defaultAction="setActiveEntry"
|
||||
:direction="MenuDirection.Bottom"
|
||||
:widthChanged="widthChanged"
|
||||
:drawIcon="drawIcon"
|
||||
ref="menuList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown-input {
|
||||
position: relative;
|
||||
|
||||
.dropdown-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
background: var(--color-1-nearblack);
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
|
||||
.dropdown-icon {
|
||||
margin: 4px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-left: 8px;
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
.dropdown-icon + span {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
margin: 6px 2px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.open {
|
||||
background: var(--color-6-lowergray);
|
||||
|
||||
span {
|
||||
color: var(--color-f-white);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-list .floating-menu-container .floating-menu-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import Icon from "../labels/Icon.vue";
|
||||
import MenuList, { MenuListEntry, SectionsOfMenuListEntries } from "../floating-menus/MenuList.vue";
|
||||
import { MenuDirection } from "../floating-menus/FloatingMenu.vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
default: { type: Object as PropType<MenuListEntry>, required: true },
|
||||
drawIcon: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeEntry: this.default,
|
||||
MenuDirection,
|
||||
minWidth: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
clickDropdownBox() {
|
||||
(this.$refs.menuList as typeof MenuList).setOpen();
|
||||
},
|
||||
setActiveEntry(newActiveEntry: MenuListEntry) {
|
||||
this.activeEntry = newActiveEntry;
|
||||
},
|
||||
widthChanged(newWidth: number) {
|
||||
this.minWidth = newWidth;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
MenuList,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -5,7 +5,15 @@
|
|||
<Icon :icon="entry.icon" v-if="entry.icon" />
|
||||
<span v-if="entry.label">{{ entry.label }}</span>
|
||||
</div>
|
||||
<MenuList :menuEntries="entry.children" :direction="MenuDirection.Bottom" :ref="(ref) => setEntryRefs(entry, ref)" />
|
||||
<MenuList
|
||||
:ourEntry="entry"
|
||||
:menuEntries="entry.children"
|
||||
:direction="MenuDirection.Bottom"
|
||||
:minWidth="240"
|
||||
:drawIcon="true"
|
||||
:defaultAction="actionNotImplemented"
|
||||
:ref="(ref) => setEntryRefs(entry, ref)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -140,6 +148,9 @@ export default defineComponent({
|
|||
if (menuEntry.ref) menuEntry.ref.setOpen();
|
||||
else throw new Error("The menu bar floating menu has no associated ref");
|
||||
},
|
||||
actionNotImplemented() {
|
||||
alert("This action is not yet implemented");
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ import GraphiteLogo from "../../../../assets/16px-solid/graphite-logo.svg";
|
|||
import File from "../../../../assets/16px-solid/file.svg";
|
||||
import Copy from "../../../../assets/16px-solid/copy.svg";
|
||||
import Paste from "../../../../assets/16px-solid/paste.svg";
|
||||
import ViewportDesignMode from "../../../../assets/16px-solid/viewport-design-mode.svg";
|
||||
import ViewportSelectMode from "../../../../assets/16px-solid/viewport-select-mode.svg";
|
||||
|
||||
import SwapButton from "../../../../assets/12px-solid/swap.svg";
|
||||
import ResetColorsButton from "../../../../assets/12px-solid/reset-colors.svg";
|
||||
|
|
@ -148,6 +150,8 @@ const icons = {
|
|||
File: { component: File, size: 16 },
|
||||
Copy: { component: Copy, size: 16 },
|
||||
Paste: { component: Paste, size: 16 },
|
||||
ViewportDesignMode: { component: ViewportDesignMode, size: 16 },
|
||||
ViewportSelectMode: { component: ViewportSelectMode, size: 16 },
|
||||
SwapButton: { component: SwapButton, size: 12 },
|
||||
ResetColorsButton: { component: ResetColorsButton, size: 12 },
|
||||
DropdownArrow: { component: DropdownArrow, size: 12 },
|
||||
|
|
|
|||
21
client/web/src/components/widgets/labels/TextLabel.vue
Normal file
21
client/web/src/components/widgets/labels/TextLabel.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<span class="text-label">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.text-label {
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {},
|
||||
props: {},
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue