Refactor frontend input components to use the v-model Vue pattern (#224)

* Use v-model for inputs

* Add opacity to LayerTree

* Fix FloatingMenu typing
This commit is contained in:
Chrs Msln
2021-06-26 23:18:47 -07:00
committed by Keavon Chambers
parent 3b5cc535ee
commit a0c1d7f898
7 changed files with 65 additions and 33 deletions
@@ -52,7 +52,7 @@
</div> </div>
<div class="spacer"></div> <div class="spacer"></div>
<div class="right side"> <div class="right side">
<RadioInput :initialIndex="0" @changed="viewModeChanged"> <RadioInput v-model:index="viewModeIndex">
<IconButton :icon="'ViewModeNormal'" :size="24" title="View Mode: Normal" /> <IconButton :icon="'ViewModeNormal'" :size="24" title="View Mode: Normal" />
<IconButton :icon="'ViewModeOutline'" :size="24" title="View Mode: Outline" /> <IconButton :icon="'ViewModeOutline'" :size="24" title="View Mode: Outline" />
<IconButton :icon="'ViewModePixels'" :size="24" title="View Mode: Pixels" /> <IconButton :icon="'ViewModePixels'" :size="24" title="View Mode: Pixels" />
@@ -236,6 +236,7 @@ export default defineComponent({
select_tool(toolName); select_tool(toolName);
}, },
async viewModeChanged(toolIndex: number) { async viewModeChanged(toolIndex: number) {
console.log(toolIndex);
function todo(_: number) { function todo(_: number) {
return _; return _;
} }
@@ -269,6 +270,8 @@ export default defineComponent({
window.addEventListener("keyup", (e: KeyboardEvent) => this.keyUp(e)); window.addEventListener("keyup", (e: KeyboardEvent) => this.keyUp(e));
window.addEventListener("keydown", (e: KeyboardEvent) => this.keyDown(e)); window.addEventListener("keydown", (e: KeyboardEvent) => this.keyDown(e));
this.$watch("viewModeIndex", this.viewModeChanged);
}, },
data() { data() {
return { return {
@@ -278,6 +281,7 @@ export default defineComponent({
SeparatorDirection, SeparatorDirection,
SeparatorType, SeparatorType,
modeMenuEntries, modeMenuEntries,
viewModeIndex: 0,
}; };
}, },
components: { components: {
@@ -5,7 +5,7 @@
<Separator :type="SeparatorType.Related" /> <Separator :type="SeparatorType.Related" />
<NumberInput :value="100" :unit="`%`" /> <NumberInput v-model:value="opacity" :min="0" :max="100" :step="1" :unit="`%`" />
<Separator :type="SeparatorType.Related" /> <Separator :type="SeparatorType.Related" />
@@ -222,6 +222,7 @@ export default defineComponent({
layers: [] as Array<LayerPanelEntry>, layers: [] as Array<LayerPanelEntry>,
selectionRangeStartLayer: undefined as LayerPanelEntry | undefined, selectionRangeStartLayer: undefined as LayerPanelEntry | undefined,
selectionRangeEndLayer: undefined as LayerPanelEntry | undefined, selectionRangeEndLayer: undefined as LayerPanelEntry | undefined,
opacity: 100,
}; };
}, },
components: { components: {
@@ -2,7 +2,7 @@
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open" ref="floatingMenu"> <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="tail" v-if="type === MenuType.Popover"></div>
<div class="floating-menu-container" ref="floatingMenuContainer"> <div class="floating-menu-container" ref="floatingMenuContainer">
<div class="floating-menu-content" ref="floatingMenuContent" :style="{ minWidth: minWidth > 0 ? `${minWidth}px` : undefined }"> <div class="floating-menu-content" ref="floatingMenuContent" :style="floatingMenuContentStyle">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
@@ -350,5 +350,12 @@ export default defineComponent({
} }
}, },
}, },
computed: {
floatingMenuContentStyle(): Partial<CSSStyleDeclaration> {
return {
minWidth: this.minWidth > 0 ? `${this.minWidth}px` : "",
};
},
},
}); });
</script> </script>
@@ -6,7 +6,7 @@
v-for="(entry, entryIndex) in section" v-for="(entry, entryIndex) in section"
:key="entryIndex" :key="entryIndex"
class="row" class="row"
:class="{ open: isMenuEntryOpen(entry), active: entry === activeEntry }" :class="{ open: isMenuEntryOpen(entry), active: entry === currentEntry }"
@click="handleEntryClick(entry)" @click="handleEntryClick(entry)"
@mouseenter="handleEntryMouseEnter(entry)" @mouseenter="handleEntryMouseEnter(entry)"
@mouseleave="handleEntryMouseLeave(entry)" @mouseleave="handleEntryMouseLeave(entry)"
@@ -22,9 +22,8 @@
v-if="entry.children" v-if="entry.children"
:direction="MenuDirection.TopRight" :direction="MenuDirection.TopRight"
:menuEntries="entry.children" :menuEntries="entry.children"
:activeEntry="activeEntry" v-model:active-entry="currentEntry"
:minWidth="minWidth" :minWidth="minWidth"
:defaultAction="defaultAction"
:drawIcon="drawIcon" :drawIcon="drawIcon"
:ref="(ref) => setEntryRefs(entry, ref)" :ref="(ref) => setEntryRefs(entry, ref)"
/> />
@@ -146,8 +145,6 @@ const MenuList = defineComponent({
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true }, menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
activeEntry: { type: Object as PropType<MenuListEntry>, required: false }, activeEntry: { type: Object as PropType<MenuListEntry>, required: false },
minWidth: { type: Number, default: 0 }, minWidth: { type: Number, default: 0 },
defaultAction: { type: Function, required: false },
widthChanged: { type: Function, required: false },
drawIcon: { type: Boolean, default: false }, drawIcon: { type: Boolean, default: false },
}, },
methods: { methods: {
@@ -157,8 +154,11 @@ const MenuList = defineComponent({
handleEntryClick(menuEntry: MenuListEntry) { handleEntryClick(menuEntry: MenuListEntry) {
(this.$refs.floatingMenu as typeof FloatingMenu).setClosed(); (this.$refs.floatingMenu as typeof FloatingMenu).setClosed();
if (menuEntry.action) menuEntry.action(); if (menuEntry.action) {
else if (this.defaultAction) this.defaultAction(menuEntry); menuEntry.action();
} else {
this.$emit("update:activeEntry", menuEntry);
}
}, },
handleEntryMouseEnter(menuEntry: MenuListEntry) { handleEntryMouseEnter(menuEntry: MenuListEntry) {
if (!menuEntry.children || !menuEntry.children.length) return; if (!menuEntry.children || !menuEntry.children.length) return;
@@ -193,9 +193,6 @@ const MenuList = defineComponent({
return Boolean(floatingMenu && floatingMenu.isOpen()); return Boolean(floatingMenu && floatingMenu.isOpen());
}, },
measureAndReportWidth() { 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 // 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
(document as any).fonts.ready.then(() => { (document as any).fonts.ready.then(() => {
@@ -212,7 +209,7 @@ const MenuList = defineComponent({
// Restore open/closed state if it was forced open for measurement // Restore open/closed state if it was forced open for measurement
if (!initiallyOpen) floatingMenu.setClosed(); if (!initiallyOpen) floatingMenu.setClosed();
widthChanged(width); this.$emit("width-changed", width);
}); });
}); });
}); });
@@ -246,6 +243,7 @@ const MenuList = defineComponent({
}, },
data() { data() {
return { return {
currentEntry: this.activeEntry,
SeparatorDirection, SeparatorDirection,
SeparatorType, SeparatorType,
MenuDirection, MenuDirection,
@@ -5,15 +5,7 @@
<span>{{ activeEntry.label }}</span> <span>{{ activeEntry.label }}</span>
<Icon :class="'dropdown-arrow'" :icon="'DropdownArrow'" /> <Icon :class="'dropdown-arrow'" :icon="'DropdownArrow'" />
</div> </div>
<MenuList <MenuList :menuEntries="menuEntries" v-model:active-entry="activeEntry" :direction="MenuDirection.Bottom" @width-changed="onWidthChanged" :drawIcon="drawIcon" ref="menuList" />
:menuEntries="menuEntries"
:activeEntry="activeEntry"
:defaultAction="setActiveEntry"
:direction="MenuDirection.Bottom"
:widthChanged="widthChanged"
:drawIcon="drawIcon"
ref="menuList"
/>
</div> </div>
</template> </template>
@@ -101,7 +93,7 @@ export default defineComponent({
setActiveEntry(newActiveEntry: MenuListEntry) { setActiveEntry(newActiveEntry: MenuListEntry) {
this.activeEntry = newActiveEntry; this.activeEntry = newActiveEntry;
}, },
widthChanged(newWidth: number) { onWidthChanged(newWidth: number) {
this.minWidth = newWidth; this.minWidth = newWidth;
}, },
}, },
@@ -1,8 +1,8 @@
<template> <template>
<div class="number-input"> <div class="number-input">
<button class="arrow left"></button> <button class="arrow left" @click="onIncrement(-1)"></button>
<button class="arrow right"></button> <button class="arrow right" @click="onIncrement(1)"></button>
<input type="text" spellcheck="false" :value="`${value}${unit}`" /> <input type="text" spellcheck="false" :value="displayValue" />
</div> </div>
</template> </template>
@@ -99,7 +99,37 @@ export default defineComponent({
components: {}, components: {},
props: { props: {
value: { type: Number, required: true }, value: { type: Number, required: true },
unit: { type: String, default: "" }, unit: { type: String, default: "", required: false },
step: { type: Number, default: 1, required: false },
min: { type: Number, required: false },
max: { type: Number, required: false },
},
computed: {
displayValue(): string {
if (!this.unit) return this.value.toString();
return `${this.value}${this.unit}`;
},
},
methods: {
onIncrement(direction: number) {
const step = this.step * direction;
const newValue = this.value + step;
this.updateValue(newValue);
},
updateValue(newValue: number) {
let value = newValue;
if (Number.isFinite(this.min) && typeof this.min === "number") {
value = Math.max(value, this.min);
}
if (Number.isFinite(this.max) && typeof this.max === "number") {
value = Math.min(value, this.max);
}
this.$emit("update:value", value);
},
}, },
}); });
</script> </script>
@@ -51,12 +51,11 @@ import { defineComponent } from "vue";
export default defineComponent({ export default defineComponent({
components: {}, components: {},
props: { props: {
initialIndex: { type: Number, required: true }, index: { type: Number, required: true },
setIndex: { type: Function, required: false },
}, },
data() { data() {
return { return {
activeIndex: this.initialIndex, activeIndex: this.index,
}; };
}, },
mounted() { mounted() {
@@ -64,8 +63,7 @@ export default defineComponent({
(this.$refs.radioInput as Element).querySelectorAll(".icon-button").forEach((iconButton, index) => { (this.$refs.radioInput as Element).querySelectorAll(".icon-button").forEach((iconButton, index) => {
iconButton.addEventListener("click", () => { iconButton.addEventListener("click", () => {
this.activeIndex = index; this.setActive(index);
this.$emit("changed", index);
}); });
}); });
}, },
@@ -78,6 +76,8 @@ export default defineComponent({
// This method may be called by the user of this component by setting a `ref="radioInput"` attribute and calling `(this.$refs.viewModePicker as typeof RadioInput).setActive(...)` // This method may be called by the user of this component by setting a `ref="radioInput"` attribute and calling `(this.$refs.viewModePicker as typeof RadioInput).setActive(...)`
setActive(index: number) { setActive(index: number) {
this.activeIndex = index; this.activeIndex = index;
this.$emit("update:index", index);
this.$emit("changed", index);
}, },
updateActiveIconButton() { updateActiveIconButton() {
const iconButtons = (this.$refs.radioInput as Element).querySelectorAll(".icon-button"); const iconButtons = (this.$refs.radioInput as Element).querySelectorAll(".icon-button");