mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 04:18:11 +08:00
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:
committed by
Keavon Chambers
parent
3b5cc535ee
commit
a0c1d7f898
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user