Move JS script to the top of each Vue file

This commit is contained in:
Keavon Chambers
2023-03-10 03:44:49 -08:00
parent 1c50f0030c
commit 2327524690
50 changed files with 3482 additions and 3517 deletions
@@ -1,3 +1,48 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type IconName } from "@/utility-functions/icons";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
export default defineComponent({
emits: ["update:checked"],
props: {
checked: { type: Boolean as PropType<boolean>, default: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
icon: { type: String as PropType<IconName>, default: "Checkmark" },
tooltip: { type: String as PropType<string | undefined>, required: false },
},
data() {
return {
id: `${Math.random()}`.substring(2),
};
},
computed: {
displayIcon(): IconName {
if (!this.checked && this.icon === "Checkmark") return "Empty12px";
return this.icon;
},
},
methods: {
isChecked() {
return this.checked;
},
toggleCheckboxFromLabel(e: KeyboardEvent) {
const target = (e.target || undefined) as HTMLLabelElement | undefined;
const previousSibling = (target?.previousSibling || undefined) as HTMLInputElement | undefined;
previousSibling?.click();
},
},
components: {
IconLabel,
LayoutRow,
},
});
</script>
<template>
<LayoutRow class="checkbox-input">
<input
@@ -80,48 +125,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type IconName } from "@/utility-functions/icons";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
export default defineComponent({
emits: ["update:checked"],
props: {
checked: { type: Boolean as PropType<boolean>, default: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
icon: { type: String as PropType<IconName>, default: "Checkmark" },
tooltip: { type: String as PropType<string | undefined>, required: false },
},
data() {
return {
id: `${Math.random()}`.substring(2),
};
},
computed: {
displayIcon(): IconName {
if (!this.checked && this.icon === "Checkmark") return "Empty12px";
return this.icon;
},
},
methods: {
isChecked() {
return this.checked;
},
toggleCheckboxFromLabel(e: KeyboardEvent) {
const target = (e.target || undefined) as HTMLLabelElement | undefined;
const previousSibling = (target?.previousSibling || undefined) as HTMLInputElement | undefined;
previousSibling?.click();
},
},
components: {
IconLabel,
LayoutRow,
},
});
</script>
@@ -1,3 +1,57 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { Color } from "@/wasm-communication/messages";
import ColorPicker from "@/components/floating-menus/ColorPicker.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:value", "update:open"],
props: {
value: { type: Color as PropType<Color>, required: true },
noTransparency: { type: Boolean as PropType<boolean>, default: false }, // TODO: Rename to allowTransparency, also implement allowNone
disabled: { type: Boolean as PropType<boolean>, default: false }, // TODO: Design and implement
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Bound through `v-model`
// TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed
open: { type: Boolean as PropType<boolean>, required: true },
},
data() {
return {
isOpen: false,
};
},
watch: {
// Called only when `open` is changed from outside this component (with v-model)
open(newOpen: boolean) {
this.isOpen = newOpen;
},
isOpen(newIsOpen: boolean) {
this.$emit("update:open", newIsOpen);
},
},
methods: {
colorPickerUpdated(color: Color) {
this.$emit("update:value", color);
},
},
computed: {
chip() {
return undefined;
},
},
components: {
ColorPicker,
LayoutRow,
TextLabel,
},
});
</script>
<template>
<LayoutRow class="color-input" :class="{ 'sharp-right-corners': sharpRightCorners }" :title="tooltip">
<button
@@ -77,57 +131,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { Color } from "@/wasm-communication/messages";
import ColorPicker from "@/components/floating-menus/ColorPicker.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:value", "update:open"],
props: {
value: { type: Color as PropType<Color>, required: true },
noTransparency: { type: Boolean as PropType<boolean>, default: false }, // TODO: Rename to allowTransparency, also implement allowNone
disabled: { type: Boolean as PropType<boolean>, default: false }, // TODO: Design and implement
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Bound through `v-model`
// TODO: See if this should be made to follow the pattern of DropdownInput.vue so this could be removed
open: { type: Boolean as PropType<boolean>, required: true },
},
data() {
return {
isOpen: false,
};
},
watch: {
// Called only when `open` is changed from outside this component (with v-model)
open(newOpen: boolean) {
this.isOpen = newOpen;
},
isOpen(newIsOpen: boolean) {
this.$emit("update:open", newIsOpen);
},
},
methods: {
colorPickerUpdated(color: Color) {
this.$emit("update:value", color);
},
},
computed: {
chip() {
return undefined;
},
},
components: {
ColorPicker,
LayoutRow,
TextLabel,
},
});
</script>
@@ -1,3 +1,80 @@
<script lang="ts">
import { defineComponent, type PropType, toRaw } from "vue";
import { type MenuListEntry } from "@/wasm-communication/messages";
import MenuList from "@/components/floating-menus/MenuList.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
const DASH_ENTRY = { label: "-" };
export default defineComponent({
emits: ["update:selectedIndex"],
props: {
entries: { type: Array as PropType<MenuListEntry[][]>, required: true },
selectedIndex: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
drawIcon: { type: Boolean as PropType<boolean>, default: false },
interactive: { type: Boolean as PropType<boolean>, default: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
data() {
return {
activeEntry: this.makeActiveEntry(this.selectedIndex),
activeEntrySkipWatcher: false,
open: false,
minWidth: 0,
};
},
watch: {
// Called only when `selectedIndex` is changed from outside this component (with v-model)
selectedIndex() {
this.activeEntrySkipWatcher = true;
this.activeEntry = this.makeActiveEntry();
},
// Called when `activeEntry` is changed by the `v-model` on this component's MenuList component, or by the `selectedIndex()` watcher above (but we want to skip that case)
activeEntry(newActiveEntry: MenuListEntry) {
if (this.activeEntrySkipWatcher) {
this.activeEntrySkipWatcher = false;
return;
}
// `toRaw()` pulls it out of the Vue proxy
if (toRaw(newActiveEntry) === DASH_ENTRY) return;
this.$emit("update:selectedIndex", this.entries.flat().indexOf(newActiveEntry));
},
},
methods: {
makeActiveEntry(): MenuListEntry {
const entries = this.entries.flat();
if (this.selectedIndex !== undefined && this.selectedIndex >= 0 && this.selectedIndex < entries.length) {
return entries[this.selectedIndex];
}
return DASH_ENTRY;
},
keydown(e: KeyboardEvent) {
(this.$refs.menuList as typeof MenuList | undefined)?.keydown(e, false);
},
unFocusDropdownBox(e: FocusEvent) {
const blurTarget = (e.target as HTMLDivElement | undefined)?.closest("[data-dropdown-input]");
const self: HTMLDivElement | undefined = this.$el;
if (blurTarget !== self) this.open = false;
},
},
components: {
IconLabel,
LayoutRow,
MenuList,
TextLabel,
},
});
</script>
<template>
<LayoutRow class="dropdown-input" data-dropdown-input>
<LayoutRow
@@ -95,80 +172,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType, toRaw } from "vue";
import { type MenuListEntry } from "@/wasm-communication/messages";
import MenuList from "@/components/floating-menus/MenuList.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
const DASH_ENTRY = { label: "-" };
export default defineComponent({
emits: ["update:selectedIndex"],
props: {
entries: { type: Array as PropType<MenuListEntry[][]>, required: true },
selectedIndex: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
drawIcon: { type: Boolean as PropType<boolean>, default: false },
interactive: { type: Boolean as PropType<boolean>, default: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
data() {
return {
activeEntry: this.makeActiveEntry(this.selectedIndex),
activeEntrySkipWatcher: false,
open: false,
minWidth: 0,
};
},
watch: {
// Called only when `selectedIndex` is changed from outside this component (with v-model)
selectedIndex() {
this.activeEntrySkipWatcher = true;
this.activeEntry = this.makeActiveEntry();
},
// Called when `activeEntry` is changed by the `v-model` on this component's MenuList component, or by the `selectedIndex()` watcher above (but we want to skip that case)
activeEntry(newActiveEntry: MenuListEntry) {
if (this.activeEntrySkipWatcher) {
this.activeEntrySkipWatcher = false;
return;
}
// `toRaw()` pulls it out of the Vue proxy
if (toRaw(newActiveEntry) === DASH_ENTRY) return;
this.$emit("update:selectedIndex", this.entries.flat().indexOf(newActiveEntry));
},
},
methods: {
makeActiveEntry(): MenuListEntry {
const entries = this.entries.flat();
if (this.selectedIndex !== undefined && this.selectedIndex >= 0 && this.selectedIndex < entries.length) {
return entries[this.selectedIndex];
}
return DASH_ENTRY;
},
keydown(e: KeyboardEvent) {
(this.$refs.menuList as typeof MenuList | undefined)?.keydown(e, false);
},
unFocusDropdownBox(e: FocusEvent) {
const blurTarget = (e.target as HTMLDivElement | undefined)?.closest("[data-dropdown-input]");
const self: HTMLDivElement | undefined = this.$el;
if (blurTarget !== self) this.open = false;
},
},
components: {
IconLabel,
LayoutRow,
MenuList,
TextLabel,
},
});
</script>
@@ -1,3 +1,64 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { platformIsMac } from "@/utility-functions/platform";
import LayoutRow from "@/components/layout/LayoutRow.vue";
export default defineComponent({
emits: ["update:value", "textFocused", "textChanged", "cancelTextChange"],
props: {
value: { type: String as PropType<string>, required: true },
label: { type: String as PropType<string>, required: false },
spellcheck: { type: Boolean as PropType<boolean>, default: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
textarea: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
placeholder: { type: String as PropType<string>, required: false },
},
data() {
return {
id: `${Math.random()}`.substring(2),
macKeyboardLayout: platformIsMac(),
};
},
methods: {
// Select (highlight) all the text. For technical reasons, it is necessary to pass the current text.
selectAllText(currentText: string) {
const inputElement = this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined;
if (!inputElement) return;
// Setting the value directly is required to make `inputElement.select()` work
inputElement.value = currentText;
inputElement.select();
},
unFocus() {
(this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined)?.blur();
},
getInputElementValue(): string | undefined {
return (this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined)?.value;
},
setInputElementValue(value: string) {
const inputElement = this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined;
if (inputElement) inputElement.value = value;
},
},
computed: {
inputValue: {
get() {
return this.value;
},
set(value: string) {
this.$emit("update:value", value);
},
},
},
components: { LayoutRow },
});
</script>
<!-- This is a base component, extended by others like NumberInput and TextInput. It should not be used directly. -->
<template>
<LayoutRow class="field-input" :class="{ disabled, 'sharp-right-corners': sharpRightCorners }" :title="tooltip">
@@ -131,64 +192,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { platformIsMac } from "@/utility-functions/platform";
import LayoutRow from "@/components/layout/LayoutRow.vue";
export default defineComponent({
emits: ["update:value", "textFocused", "textChanged", "cancelTextChange"],
props: {
value: { type: String as PropType<string>, required: true },
label: { type: String as PropType<string>, required: false },
spellcheck: { type: Boolean as PropType<boolean>, default: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
textarea: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
placeholder: { type: String as PropType<string>, required: false },
},
data() {
return {
id: `${Math.random()}`.substring(2),
macKeyboardLayout: platformIsMac(),
};
},
methods: {
// Select (highlight) all the text. For technical reasons, it is necessary to pass the current text.
selectAllText(currentText: string) {
const inputElement = this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined;
if (!inputElement) return;
// Setting the value directly is required to make `inputElement.select()` work
inputElement.value = currentText;
inputElement.select();
},
unFocus() {
(this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined)?.blur();
},
getInputElementValue(): string | undefined {
return (this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined)?.value;
},
setInputElementValue(value: string) {
const inputElement = this.$refs.input as HTMLInputElement | HTMLTextAreaElement | undefined;
if (inputElement) inputElement.value = value;
},
},
computed: {
inputValue: {
get() {
return this.value;
},
set(value: string) {
this.$emit("update:value", value);
},
},
},
components: { LayoutRow },
});
</script>
@@ -1,84 +1,3 @@
<!-- TODO: Combine this widget into the DropdownInput widget -->
<template>
<LayoutRow class="font-input">
<LayoutRow
class="dropdown-box"
:class="{ disabled, 'sharp-right-corners': sharpRightCorners }"
:style="{ minWidth: `${minWidth}px` }"
:title="tooltip"
:tabindex="disabled ? -1 : 0"
@click="toggleOpen"
@keydown="keydown"
data-floating-menu-spawner
>
<TextLabel class="dropdown-label">{{ activeEntry?.value || "" }}</TextLabel>
<IconLabel class="dropdown-arrow" :icon="'DropdownArrow'" />
</LayoutRow>
<MenuList
v-model:activeEntry="activeEntry"
v-model:open="open"
:entries="[entries]"
:minWidth="isStyle ? 0 : minWidth"
:virtualScrollingEntryHeight="isStyle ? 0 : 20"
:scrollableY="true"
@naturalWidth="(newNaturalWidth: number) => (isStyle && (minWidth = newNaturalWidth))"
ref="menuList"
></MenuList>
</LayoutRow>
</template>
<style lang="scss">
.font-input {
position: relative;
.dropdown-box {
align-items: center;
white-space: nowrap;
background: var(--color-1-nearblack);
height: 24px;
border-radius: 2px;
.dropdown-label {
margin: 0;
margin-left: 8px;
flex: 1 1 100%;
}
.dropdown-arrow {
margin: 6px 2px;
flex: 0 0 auto;
}
&:hover,
&.open {
background: var(--color-6-lowergray);
span {
color: var(--color-f-white);
}
}
&.open {
border-radius: 2px 2px 0 0;
}
&.disabled {
background: var(--color-2-mildblack);
span {
color: var(--color-8-uppergray);
}
}
}
.menu-list .floating-menu-container .floating-menu-content {
max-height: 400px;
padding: 4px 0;
}
}
</style>
<script lang="ts">
import { defineComponent, nextTick, type PropType } from "vue";
@@ -187,3 +106,84 @@ export default defineComponent({
},
});
</script>
<!-- TODO: Combine this widget into the DropdownInput widget -->
<template>
<LayoutRow class="font-input">
<LayoutRow
class="dropdown-box"
:class="{ disabled, 'sharp-right-corners': sharpRightCorners }"
:style="{ minWidth: `${minWidth}px` }"
:title="tooltip"
:tabindex="disabled ? -1 : 0"
@click="toggleOpen"
@keydown="keydown"
data-floating-menu-spawner
>
<TextLabel class="dropdown-label">{{ activeEntry?.value || "" }}</TextLabel>
<IconLabel class="dropdown-arrow" :icon="'DropdownArrow'" />
</LayoutRow>
<MenuList
v-model:activeEntry="activeEntry"
v-model:open="open"
:entries="[entries]"
:minWidth="isStyle ? 0 : minWidth"
:virtualScrollingEntryHeight="isStyle ? 0 : 20"
:scrollableY="true"
@naturalWidth="(newNaturalWidth: number) => (isStyle && (minWidth = newNaturalWidth))"
ref="menuList"
></MenuList>
</LayoutRow>
</template>
<style lang="scss">
.font-input {
position: relative;
.dropdown-box {
align-items: center;
white-space: nowrap;
background: var(--color-1-nearblack);
height: 24px;
border-radius: 2px;
.dropdown-label {
margin: 0;
margin-left: 8px;
flex: 1 1 100%;
}
.dropdown-arrow {
margin: 6px 2px;
flex: 0 0 auto;
}
&:hover,
&.open {
background: var(--color-6-lowergray);
span {
color: var(--color-f-white);
}
}
&.open {
border-radius: 2px 2px 0 0;
}
&.disabled {
background: var(--color-2-mildblack);
span {
color: var(--color-8-uppergray);
}
}
}
.menu-list .floating-menu-container .floating-menu-content {
max-height: 400px;
padding: 4px 0;
}
}
</style>
@@ -1,3 +1,73 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { currentDraggingElement } from "@/io-managers/drag";
import type { LayerType, LayerTypeData } from "@/wasm-communication/messages";
import { layerTypeData } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconButton from "@/components/widgets/buttons/IconButton.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:value"],
props: {
value: { type: String as PropType<string | undefined>, required: false },
layerName: { type: String as PropType<string | undefined>, required: false },
layerType: { type: String as PropType<LayerType | undefined>, required: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
data() {
return {
hoveringDrop: false,
};
},
computed: {
droppable() {
return this.hoveringDrop && currentDraggingElement();
},
},
methods: {
dragOver(e: DragEvent): void {
this.hoveringDrop = true;
e.preventDefault();
},
dragLeave(): void {
this.hoveringDrop = false;
},
drop(e: DragEvent): void {
this.hoveringDrop = false;
const element = currentDraggingElement();
const layerPath = element?.getAttribute("data-layer") || undefined;
if (layerPath) {
e.preventDefault();
this.$emit("update:value", layerPath);
}
},
clearLayer(): void {
this.$emit("update:value", undefined);
},
layerTypeData(layerType: LayerType): LayerTypeData {
return layerTypeData(layerType) || { name: "Error", icon: "Info" };
},
},
components: {
IconButton,
IconLabel,
LayoutRow,
TextLabel,
},
});
</script>
<template>
<LayoutRow
class="layer-reference-input"
@@ -89,73 +159,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { currentDraggingElement } from "@/io-managers/drag";
import type { LayerType, LayerTypeData } from "@/wasm-communication/messages";
import { layerTypeData } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconButton from "@/components/widgets/buttons/IconButton.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:value"],
props: {
value: { type: String as PropType<string | undefined>, required: false },
layerName: { type: String as PropType<string | undefined>, required: false },
layerType: { type: String as PropType<LayerType | undefined>, required: false },
disabled: { type: Boolean as PropType<boolean>, default: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
data() {
return {
hoveringDrop: false,
};
},
computed: {
droppable() {
return this.hoveringDrop && currentDraggingElement();
},
},
methods: {
dragOver(e: DragEvent): void {
this.hoveringDrop = true;
e.preventDefault();
},
dragLeave(): void {
this.hoveringDrop = false;
},
drop(e: DragEvent): void {
this.hoveringDrop = false;
const element = currentDraggingElement();
const layerPath = element?.getAttribute("data-layer") || undefined;
if (layerPath) {
e.preventDefault();
this.$emit("update:value", layerPath);
}
},
clearLayer(): void {
this.$emit("update:value", undefined);
},
layerTypeData(layerType: LayerType): LayerTypeData {
return layerTypeData(layerType) || { name: "Error", icon: "Info" };
},
},
components: {
IconButton,
IconLabel,
LayoutRow,
TextLabel,
},
});
</script>
@@ -1,69 +1,3 @@
<template>
<div class="menu-bar-input" data-menu-bar-input>
<div class="entry-container" v-for="(entry, index) in entries" :key="index">
<div
@click="(e: MouseEvent) => clickEntry(entry, e)"
@blur="(e: FocusEvent) => unFocusEntry(entry, e)"
@keydown="(e: KeyboardEvent) => entry.ref?.keydown(e, false)"
class="entry"
:class="{ open: entry.ref?.isOpen }"
tabindex="0"
:data-floating-menu-spawner="entry.children && entry.children.length > 0 ? '' : 'no-hover-transfer'"
>
<IconLabel v-if="entry.icon" :icon="entry.icon" />
<TextLabel v-if="entry.label">{{ entry.label }}</TextLabel>
</div>
<MenuList
v-if="entry.children && entry.children.length > 0"
:open="entry.ref?.open || false"
:entries="entry.children || []"
:direction="'Bottom'"
:minWidth="240"
:drawIcon="true"
:ref="(ref: MenuListInstance): void => (ref && (entry.ref = ref), undefined)"
/>
</div>
</div>
</template>
<style lang="scss">
.menu-bar-input {
display: flex;
.entry-container {
display: flex;
position: relative;
.entry {
display: flex;
align-items: center;
white-space: nowrap;
padding: 0 8px;
background: none;
border: 0;
margin: 0;
svg {
fill: var(--color-e-nearwhite);
}
&:hover,
&.open {
background: var(--color-6-lowergray);
svg {
fill: var(--color-f-white);
}
span {
color: var(--color-f-white);
}
}
}
}
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
@@ -152,3 +86,69 @@ export default defineComponent({
},
});
</script>
<template>
<div class="menu-bar-input" data-menu-bar-input>
<div class="entry-container" v-for="(entry, index) in entries" :key="index">
<div
@click="(e: MouseEvent) => clickEntry(entry, e)"
@blur="(e: FocusEvent) => unFocusEntry(entry, e)"
@keydown="(e: KeyboardEvent) => entry.ref?.keydown(e, false)"
class="entry"
:class="{ open: entry.ref?.isOpen }"
tabindex="0"
:data-floating-menu-spawner="entry.children && entry.children.length > 0 ? '' : 'no-hover-transfer'"
>
<IconLabel v-if="entry.icon" :icon="entry.icon" />
<TextLabel v-if="entry.label">{{ entry.label }}</TextLabel>
</div>
<MenuList
v-if="entry.children && entry.children.length > 0"
:open="entry.ref?.open || false"
:entries="entry.children || []"
:direction="'Bottom'"
:minWidth="240"
:drawIcon="true"
:ref="(ref: MenuListInstance): void => (ref && (entry.ref = ref), undefined)"
/>
</div>
</div>
</template>
<style lang="scss">
.menu-bar-input {
display: flex;
.entry-container {
display: flex;
position: relative;
.entry {
display: flex;
align-items: center;
white-space: nowrap;
padding: 0 8px;
background: none;
border: 0;
margin: 0;
svg {
fill: var(--color-e-nearwhite);
}
&:hover,
&.open {
background: var(--color-6-lowergray);
svg {
fill: var(--color-f-white);
}
span {
color: var(--color-f-white);
}
}
}
}
}
</style>
@@ -1,3 +1,240 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type NumberInputMode, type NumberInputIncrementBehavior } from "@/wasm-communication/messages";
import FieldInput from "@/components/widgets/inputs/FieldInput.vue";
export default defineComponent({
emits: ["update:value"],
props: {
// Label
label: { type: String as PropType<string>, required: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
// Disabled
disabled: { type: Boolean as PropType<boolean>, default: false },
// Value
value: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
min: { type: Number as PropType<number>, required: false },
max: { type: Number as PropType<number>, required: false },
isInteger: { type: Boolean as PropType<boolean>, default: false },
// Number presentation
displayDecimalPlaces: { type: Number as PropType<number>, default: 3 },
unit: { type: String as PropType<string>, default: "" },
unitIsHiddenWhenEditing: { type: Boolean as PropType<boolean>, default: true },
// Mode behavior
// "Increment" shows arrows and allows dragging left/right to change the value.
// "Range" shows a range slider between some minimum and maximum value.
mode: { type: String as PropType<NumberInputMode>, default: "Increment" },
// When `mode` is "Increment", `step` is the multiplier or addend used with `incrementBehavior`.
// When `mode` is "Range", `step` is the range slider's snapping increment if `isInteger` is `true`.
step: { type: Number as PropType<number>, default: 1 },
// `incrementBehavior` is only applicable with a `mode` of "Increment".
// "Add"/"Multiply": The value is added or multiplied by `step`.
// "None": the increment arrows are not shown.
// "Callback": the functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior.
incrementBehavior: { type: String as PropType<NumberInputIncrementBehavior>, default: "Add" },
// `rangeMin` and `rangeMax` are only applicable with a `mode` of "Range".
// They set the lower and upper values of the slider to drag between.
rangeMin: { type: Number as PropType<number>, default: 0 },
rangeMax: { type: Number as PropType<number>, default: 1 },
// Styling
minWidth: { type: Number as PropType<number>, default: 0 },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Callbacks
incrementCallbackIncrease: { type: Function as PropType<() => void>, required: false },
incrementCallbackDecrease: { type: Function as PropType<() => void>, required: false },
},
data() {
return {
text: this.displayText(this.value),
editing: false,
// Stays in sync with a binding to the actual input range slider element.
rangeSliderValue: this.value !== undefined ? this.value : 0,
// Value used to render the position of the fake slider when applicable, and length of the progress colored region to the slider's left.
// This is the same as `rangeSliderValue` except in the "mousedown" state, when it has the previous location before the user's mousedown.
rangeSliderValueAsRendered: this.value !== undefined ? this.value : 0,
// "default": no interaction is happening.
// "mousedown": the user has pressed down the mouse and might next decide to either drag left/right or release without dragging.
// "dragging": the user is dragging the slider left/right.
rangeSliderClickDragState: "default" as "default" | "mousedown" | "dragging",
};
},
computed: {
sliderStepValue() {
const step = this.step === undefined ? 1 : this.step;
return this.isInteger ? step : "any";
},
},
methods: {
sliderInput() {
// Keep only 4 digits after the decimal point
const ROUNDING_EXPONENT = 4;
const ROUNDING_MAGNITUDE = 10 ** ROUNDING_EXPONENT;
const roundedValue = Math.round(this.rangeSliderValue * ROUNDING_MAGNITUDE) / ROUNDING_MAGNITUDE;
// Exit if this is an extraneous event invocation that occurred after mouseup, which happens in Firefox
if (this.value !== undefined && Math.abs(this.value - roundedValue) < 1 / ROUNDING_MAGNITUDE) {
return;
}
// The first event upon mousedown means we transition to a "mousedown" state
if (this.rangeSliderClickDragState === "default") {
this.rangeSliderClickDragState = "mousedown";
// Exit early because we don't want to use the value set by where on the track the user pressed
return;
}
// The second event upon mousedown that occurs by moving left or right means the user has committed to dragging the slider
if (this.rangeSliderClickDragState === "mousedown") {
this.rangeSliderClickDragState = "dragging";
}
// If we're in a dragging state, we want to use the new slider value
this.rangeSliderValueAsRendered = roundedValue;
this.updateValue(roundedValue);
},
sliderPointerDown() {
// We want to render the fake slider thumb at the old position, which is still the number held by `value`
this.rangeSliderValueAsRendered = this.value || 0;
// Because an `input` event is fired right before or after this (depending on browser), that first
// invocation will transition the state machine to `mousedown`. That's why we don't do it here.
},
sliderPointerUp() {
// User clicked but didn't drag, so we focus the text input element
if (this.rangeSliderClickDragState === "mousedown") {
const fieldInput = this.$refs.fieldInput as typeof FieldInput | undefined;
const inputElement = fieldInput?.$el.querySelector("[data-input-element]") as HTMLInputElement | undefined;
if (!inputElement) return;
// Set the slider position back to the original position to undo the user moving it
this.rangeSliderValue = this.rangeSliderValueAsRendered;
// Begin editing the number text field
inputElement.focus();
}
// Releasing the mouse means we can reset the state machine
this.rangeSliderClickDragState = "default";
},
onTextFocused() {
if (this.value === undefined) this.text = "";
else if (this.unitIsHiddenWhenEditing) this.text = `${this.value}`;
else this.text = `${this.value}${unPluralize(this.unit, this.value)}`;
this.editing = true;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.selectAllText(this.text);
},
// Called only when `value` is changed from the <input> element via user input and committed, either with the
// enter key (via the `change` event) or when the <input> element is unfocused (with the `blur` event binding)
onTextChanged() {
// The `unFocus()` call at the bottom of this function and in `onCancelTextChange()` causes this function to be run again, so this check skips a second run
if (!this.editing) return;
const parsed = parseFloat(this.text);
const newValue = Number.isNaN(parsed) ? undefined : parsed;
this.updateValue(newValue);
this.editing = false;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.unFocus();
},
onCancelTextChange() {
this.updateValue(undefined);
this.editing = false;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.unFocus();
},
onIncrement(direction: "Decrease" | "Increase") {
if (this.value === undefined) return;
const actions = {
Add: (): void => {
const directionAddend = direction === "Increase" ? this.step : -this.step;
this.updateValue(this.value !== undefined ? this.value + directionAddend : undefined);
},
Multiply: (): void => {
const directionMultiplier = direction === "Increase" ? this.step : 1 / this.step;
this.updateValue(this.value !== undefined ? this.value * directionMultiplier : undefined);
},
Callback: (): void => {
if (direction === "Increase") this.incrementCallbackIncrease?.();
if (direction === "Decrease") this.incrementCallbackDecrease?.();
},
None: (): void => undefined,
};
const action = actions[this.incrementBehavior];
action();
},
updateValue(newValue: number | undefined) {
const nowValid = this.value !== undefined && this.isInteger ? Math.round(this.value) : this.value;
let cleaned = newValue !== undefined ? newValue : nowValid;
if (typeof this.min === "number" && !Number.isNaN(this.min) && cleaned !== undefined) cleaned = Math.max(cleaned, this.min);
if (typeof this.max === "number" && !Number.isNaN(this.max) && cleaned !== undefined) cleaned = Math.min(cleaned, this.max);
// Required as the call to update:value can, not change the value
this.text = this.displayText(this.value);
if (newValue !== undefined) this.$emit("update:value", cleaned);
},
displayText(value: number | undefined): string {
if (value === undefined) return "-";
// Find the amount of digits on the left side of the decimal
// 10.25 == 2
// 1.23 == 1
// 0.23 == 0 (Reason for the slightly more complicated code)
const absValueInt = Math.floor(Math.abs(value));
const leftSideDigits = absValueInt === 0 ? 0 : absValueInt.toString().length;
const roundingPower = 10 ** Math.max(this.displayDecimalPlaces - leftSideDigits, 0);
const displayValue = Math.round(value * roundingPower) / roundingPower;
return `${displayValue}${unPluralize(this.unit, value)}`;
},
},
watch: {
// Called only when `value` is changed from outside this component (with v-model)
value(newValue: number | undefined) {
// Draw a dash if the value is undefined
if (newValue === undefined) {
this.text = "-";
return;
}
// Update the range slider with the new value
this.rangeSliderValue = newValue;
this.rangeSliderValueAsRendered = newValue;
// The simple `clamp()` function can't be used here since `undefined` values need to be boundless
let sanitized = newValue;
if (typeof this.min === "number") sanitized = Math.max(sanitized, this.min);
if (typeof this.max === "number") sanitized = Math.min(sanitized, this.max);
this.text = this.displayText(sanitized);
},
},
components: { FieldInput },
});
function unPluralize(unit: string, value: number): string {
if (value === 1 && unit.endsWith("s")) return unit.slice(0, -1);
return unit;
}
</script>
<template>
<FieldInput
class="number-input"
@@ -240,240 +477,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type NumberInputMode, type NumberInputIncrementBehavior } from "@/wasm-communication/messages";
import FieldInput from "@/components/widgets/inputs/FieldInput.vue";
export default defineComponent({
emits: ["update:value"],
props: {
// Label
label: { type: String as PropType<string>, required: false },
tooltip: { type: String as PropType<string | undefined>, required: false },
// Disabled
disabled: { type: Boolean as PropType<boolean>, default: false },
// Value
value: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
min: { type: Number as PropType<number>, required: false },
max: { type: Number as PropType<number>, required: false },
isInteger: { type: Boolean as PropType<boolean>, default: false },
// Number presentation
displayDecimalPlaces: { type: Number as PropType<number>, default: 3 },
unit: { type: String as PropType<string>, default: "" },
unitIsHiddenWhenEditing: { type: Boolean as PropType<boolean>, default: true },
// Mode behavior
// "Increment" shows arrows and allows dragging left/right to change the value.
// "Range" shows a range slider between some minimum and maximum value.
mode: { type: String as PropType<NumberInputMode>, default: "Increment" },
// When `mode` is "Increment", `step` is the multiplier or addend used with `incrementBehavior`.
// When `mode` is "Range", `step` is the range slider's snapping increment if `isInteger` is `true`.
step: { type: Number as PropType<number>, default: 1 },
// `incrementBehavior` is only applicable with a `mode` of "Increment".
// "Add"/"Multiply": The value is added or multiplied by `step`.
// "None": the increment arrows are not shown.
// "Callback": the functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior.
incrementBehavior: { type: String as PropType<NumberInputIncrementBehavior>, default: "Add" },
// `rangeMin` and `rangeMax` are only applicable with a `mode` of "Range".
// They set the lower and upper values of the slider to drag between.
rangeMin: { type: Number as PropType<number>, default: 0 },
rangeMax: { type: Number as PropType<number>, default: 1 },
// Styling
minWidth: { type: Number as PropType<number>, default: 0 },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
// Callbacks
incrementCallbackIncrease: { type: Function as PropType<() => void>, required: false },
incrementCallbackDecrease: { type: Function as PropType<() => void>, required: false },
},
data() {
return {
text: this.displayText(this.value),
editing: false,
// Stays in sync with a binding to the actual input range slider element.
rangeSliderValue: this.value !== undefined ? this.value : 0,
// Value used to render the position of the fake slider when applicable, and length of the progress colored region to the slider's left.
// This is the same as `rangeSliderValue` except in the "mousedown" state, when it has the previous location before the user's mousedown.
rangeSliderValueAsRendered: this.value !== undefined ? this.value : 0,
// "default": no interaction is happening.
// "mousedown": the user has pressed down the mouse and might next decide to either drag left/right or release without dragging.
// "dragging": the user is dragging the slider left/right.
rangeSliderClickDragState: "default" as "default" | "mousedown" | "dragging",
};
},
computed: {
sliderStepValue() {
const step = this.step === undefined ? 1 : this.step;
return this.isInteger ? step : "any";
},
},
methods: {
sliderInput() {
// Keep only 4 digits after the decimal point
const ROUNDING_EXPONENT = 4;
const ROUNDING_MAGNITUDE = 10 ** ROUNDING_EXPONENT;
const roundedValue = Math.round(this.rangeSliderValue * ROUNDING_MAGNITUDE) / ROUNDING_MAGNITUDE;
// Exit if this is an extraneous event invocation that occurred after mouseup, which happens in Firefox
if (this.value !== undefined && Math.abs(this.value - roundedValue) < 1 / ROUNDING_MAGNITUDE) {
return;
}
// The first event upon mousedown means we transition to a "mousedown" state
if (this.rangeSliderClickDragState === "default") {
this.rangeSliderClickDragState = "mousedown";
// Exit early because we don't want to use the value set by where on the track the user pressed
return;
}
// The second event upon mousedown that occurs by moving left or right means the user has committed to dragging the slider
if (this.rangeSliderClickDragState === "mousedown") {
this.rangeSliderClickDragState = "dragging";
}
// If we're in a dragging state, we want to use the new slider value
this.rangeSliderValueAsRendered = roundedValue;
this.updateValue(roundedValue);
},
sliderPointerDown() {
// We want to render the fake slider thumb at the old position, which is still the number held by `value`
this.rangeSliderValueAsRendered = this.value || 0;
// Because an `input` event is fired right before or after this (depending on browser), that first
// invocation will transition the state machine to `mousedown`. That's why we don't do it here.
},
sliderPointerUp() {
// User clicked but didn't drag, so we focus the text input element
if (this.rangeSliderClickDragState === "mousedown") {
const fieldInput = this.$refs.fieldInput as typeof FieldInput | undefined;
const inputElement = fieldInput?.$el.querySelector("[data-input-element]") as HTMLInputElement | undefined;
if (!inputElement) return;
// Set the slider position back to the original position to undo the user moving it
this.rangeSliderValue = this.rangeSliderValueAsRendered;
// Begin editing the number text field
inputElement.focus();
}
// Releasing the mouse means we can reset the state machine
this.rangeSliderClickDragState = "default";
},
onTextFocused() {
if (this.value === undefined) this.text = "";
else if (this.unitIsHiddenWhenEditing) this.text = `${this.value}`;
else this.text = `${this.value}${unPluralize(this.unit, this.value)}`;
this.editing = true;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.selectAllText(this.text);
},
// Called only when `value` is changed from the <input> element via user input and committed, either with the
// enter key (via the `change` event) or when the <input> element is unfocused (with the `blur` event binding)
onTextChanged() {
// The `unFocus()` call at the bottom of this function and in `onCancelTextChange()` causes this function to be run again, so this check skips a second run
if (!this.editing) return;
const parsed = parseFloat(this.text);
const newValue = Number.isNaN(parsed) ? undefined : parsed;
this.updateValue(newValue);
this.editing = false;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.unFocus();
},
onCancelTextChange() {
this.updateValue(undefined);
this.editing = false;
(this.$refs.fieldInput as typeof FieldInput | undefined)?.unFocus();
},
onIncrement(direction: "Decrease" | "Increase") {
if (this.value === undefined) return;
const actions = {
Add: (): void => {
const directionAddend = direction === "Increase" ? this.step : -this.step;
this.updateValue(this.value !== undefined ? this.value + directionAddend : undefined);
},
Multiply: (): void => {
const directionMultiplier = direction === "Increase" ? this.step : 1 / this.step;
this.updateValue(this.value !== undefined ? this.value * directionMultiplier : undefined);
},
Callback: (): void => {
if (direction === "Increase") this.incrementCallbackIncrease?.();
if (direction === "Decrease") this.incrementCallbackDecrease?.();
},
None: (): void => undefined,
};
const action = actions[this.incrementBehavior];
action();
},
updateValue(newValue: number | undefined) {
const nowValid = this.value !== undefined && this.isInteger ? Math.round(this.value) : this.value;
let cleaned = newValue !== undefined ? newValue : nowValid;
if (typeof this.min === "number" && !Number.isNaN(this.min) && cleaned !== undefined) cleaned = Math.max(cleaned, this.min);
if (typeof this.max === "number" && !Number.isNaN(this.max) && cleaned !== undefined) cleaned = Math.min(cleaned, this.max);
// Required as the call to update:value can, not change the value
this.text = this.displayText(this.value);
if (newValue !== undefined) this.$emit("update:value", cleaned);
},
displayText(value: number | undefined): string {
if (value === undefined) return "-";
// Find the amount of digits on the left side of the decimal
// 10.25 == 2
// 1.23 == 1
// 0.23 == 0 (Reason for the slightly more complicated code)
const absValueInt = Math.floor(Math.abs(value));
const leftSideDigits = absValueInt === 0 ? 0 : absValueInt.toString().length;
const roundingPower = 10 ** Math.max(this.displayDecimalPlaces - leftSideDigits, 0);
const displayValue = Math.round(value * roundingPower) / roundingPower;
return `${displayValue}${unPluralize(this.unit, value)}`;
},
},
watch: {
// Called only when `value` is changed from outside this component (with v-model)
value(newValue: number | undefined) {
// Draw a dash if the value is undefined
if (newValue === undefined) {
this.text = "-";
return;
}
// Update the range slider with the new value
this.rangeSliderValue = newValue;
this.rangeSliderValueAsRendered = newValue;
// The simple `clamp()` function can't be used here since `undefined` values need to be boundless
let sanitized = newValue;
if (typeof this.min === "number") sanitized = Math.max(sanitized, this.min);
if (typeof this.max === "number") sanitized = Math.min(sanitized, this.max);
this.text = this.displayText(sanitized);
},
},
components: { FieldInput },
});
function unPluralize(unit: string, value: number): string {
if (value === 1 && unit.endsWith("s")) return unit.slice(0, -1);
return unit;
}
</script>
@@ -1,3 +1,26 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type IconName } from "@/utility-functions/icons";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
export default defineComponent({
emits: ["update:checked"],
props: {
checked: { type: Boolean as PropType<boolean>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
icon: { type: String as PropType<IconName>, default: "Checkmark" },
tooltip: { type: String as PropType<string | undefined>, required: false },
},
components: {
CheckboxInput,
LayoutRow,
},
});
</script>
<template>
<LayoutRow class="optional-input" :class="disabled">
<CheckboxInput :checked="checked" :disabled="disabled" @input="(e: Event) => $emit('update:checked', (e.target as HTMLInputElement).checked)" :icon="icon" :tooltip="tooltip" />
@@ -24,26 +47,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type IconName } from "@/utility-functions/icons";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
export default defineComponent({
emits: ["update:checked"],
props: {
checked: { type: Boolean as PropType<boolean>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
icon: { type: String as PropType<IconName>, default: "Checkmark" },
tooltip: { type: String as PropType<string | undefined>, required: false },
},
components: {
CheckboxInput,
LayoutRow,
},
});
</script>
@@ -1,3 +1,36 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type RadioEntries, type RadioEntryData } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:selectedIndex"],
props: {
entries: { type: Array as PropType<RadioEntries>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
selectedIndex: { type: Number as PropType<number>, required: true },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
methods: {
handleEntryClick(radioEntryData: RadioEntryData) {
const index = this.entries.indexOf(radioEntryData);
this.$emit("update:selectedIndex", index);
radioEntryData.action?.();
},
},
components: {
IconLabel,
LayoutRow,
TextLabel,
},
});
</script>
<template>
<LayoutRow class="radio-input" :class="{ disabled }">
<button
@@ -88,36 +121,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type RadioEntries, type RadioEntryData } from "@/wasm-communication/messages";
import LayoutRow from "@/components/layout/LayoutRow.vue";
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
export default defineComponent({
emits: ["update:selectedIndex"],
props: {
entries: { type: Array as PropType<RadioEntries>, required: true },
disabled: { type: Boolean as PropType<boolean>, default: false },
selectedIndex: { type: Number as PropType<number>, required: true },
sharpRightCorners: { type: Boolean as PropType<boolean>, default: false },
},
methods: {
handleEntryClick(radioEntryData: RadioEntryData) {
const index = this.entries.indexOf(radioEntryData);
this.$emit("update:selectedIndex", index);
radioEntryData.action?.();
},
},
components: {
IconLabel,
LayoutRow,
TextLabel,
},
});
</script>
@@ -1,3 +1,48 @@
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type Color } from "@/wasm-communication/messages";
import ColorPicker from "@/components/floating-menus/ColorPicker.vue";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
export default defineComponent({
inject: ["editor"],
props: {
primary: { type: Object as PropType<Color>, required: true },
secondary: { type: Object as PropType<Color>, required: true },
},
data() {
return {
primaryOpen: false,
secondaryOpen: false,
};
},
methods: {
clickPrimarySwatch() {
this.primaryOpen = true;
this.secondaryOpen = false;
},
clickSecondarySwatch() {
this.primaryOpen = false;
this.secondaryOpen = true;
},
primaryColorChanged(color: Color) {
this.editor.instance.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
},
secondaryColorChanged(color: Color) {
this.editor.instance.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
},
},
components: {
ColorPicker,
LayoutCol,
LayoutRow,
},
});
</script>
<template>
<LayoutCol class="swatch-pair">
<LayoutRow class="primary swatch">
@@ -49,48 +94,3 @@
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import { type Color } from "@/wasm-communication/messages";
import ColorPicker from "@/components/floating-menus/ColorPicker.vue";
import LayoutCol from "@/components/layout/LayoutCol.vue";
import LayoutRow from "@/components/layout/LayoutRow.vue";
export default defineComponent({
inject: ["editor"],
props: {
primary: { type: Object as PropType<Color>, required: true },
secondary: { type: Object as PropType<Color>, required: true },
},
data() {
return {
primaryOpen: false,
secondaryOpen: false,
};
},
methods: {
clickPrimarySwatch() {
this.primaryOpen = true;
this.secondaryOpen = false;
},
clickSecondarySwatch() {
this.primaryOpen = false;
this.secondaryOpen = true;
},
primaryColorChanged(color: Color) {
this.editor.instance.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
},
secondaryColorChanged(color: Color) {
this.editor.instance.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
},
},
components: {
ColorPicker,
LayoutCol,
LayoutRow,
},
});
</script>
@@ -1,22 +1,3 @@
<template>
<FieldInput
:textarea="true"
class="text-area-input"
:class="{ 'has-label': label }"
:label="label"
:spellcheck="true"
:disabled="disabled"
:tooltip="tooltip"
v-model:value="inputValue"
@textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()"
ref="fieldInput"
></FieldInput>
</template>
<style lang="scss"></style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
@@ -74,3 +55,22 @@ export default defineComponent({
components: { FieldInput },
});
</script>
<template>
<FieldInput
:textarea="true"
class="text-area-input"
:class="{ 'has-label': label }"
:label="label"
:spellcheck="true"
:disabled="disabled"
:tooltip="tooltip"
v-model:value="inputValue"
@textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()"
ref="fieldInput"
></FieldInput>
</template>
<style lang="scss"></style>
@@ -1,36 +1,3 @@
<template>
<FieldInput
class="text-input"
:class="{ centered }"
v-model:value="text"
:label="label"
:spellcheck="true"
:disabled="disabled"
:tooltip="tooltip"
:placeholder="placeholder"
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
:sharpRightCorners="sharpRightCorners"
@textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()"
ref="fieldInput"
></FieldInput>
</template>
<style lang="scss">
.text-input {
input {
text-align: left;
}
&.centered {
input:not(:focus) {
text-align: center;
}
}
}
</style>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
@@ -101,3 +68,36 @@ export default defineComponent({
components: { FieldInput },
});
</script>
<template>
<FieldInput
class="text-input"
:class="{ centered }"
v-model:value="text"
:label="label"
:spellcheck="true"
:disabled="disabled"
:tooltip="tooltip"
:placeholder="placeholder"
:style="{ 'min-width': minWidth > 0 ? `${minWidth}px` : undefined }"
:sharpRightCorners="sharpRightCorners"
@textFocused="() => onTextFocused()"
@textChanged="() => onTextChanged()"
@cancelTextChange="() => onCancelTextChange()"
ref="fieldInput"
></FieldInput>
</template>
<style lang="scss">
.text-input {
input {
text-align: left;
}
&.centered {
input:not(:focus) {
text-align: center;
}
}
}
</style>