mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 13:38:11 +08:00
Major frontend code cleanup (#452)
Many large changes, including:
- TypeScript enums are now string unions throughout
- Strong type-checking throughout the TS and Vue codebase
- Vue component props now all specify `as PropType<...>`
- Usage of annotated return types on all functions
- Sorting of JS import statements
- Explicit usage of Vue bind attribute function call arguments (`@click="foo"` is now `@click=(e) => foo(e)`)
- Much improved code quality related to the color picker
- Consistent camelCase Vue bind and v-model attributes
- Consistent Vue HTML attribute strings with single quotes
- Bug fix and clarity improvement with incorrect hint class parameters
- Empty Vue component objects like `props: {}` and `components: {}` removed
This commit is contained in:
@@ -73,9 +73,54 @@ module.exports = {
|
|||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
"@typescript-eslint/no-loss-of-precision": "off", // TODO: Remove this line after upgrading to eslint 7.1 or greater
|
"@typescript-eslint/no-loss-of-precision": "off", // TODO: Remove this line after upgrading to eslint 7.1 or greater
|
||||||
|
"@typescript-eslint/explicit-function-return-type": ["error"],
|
||||||
|
|
||||||
// Import plugin config (used to intelligently validate module import statements)
|
// Import plugin config (used to intelligently validate module import statements)
|
||||||
"import/prefer-default-export": "off",
|
"import/prefer-default-export": "off",
|
||||||
|
"import/no-relative-packages": "error",
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
alphabetize: {
|
||||||
|
order: "asc",
|
||||||
|
caseInsensitive: true,
|
||||||
|
},
|
||||||
|
warnOnUnassignedImports: true,
|
||||||
|
"newlines-between": "always-and-inside-groups",
|
||||||
|
pathGroups: [
|
||||||
|
{
|
||||||
|
pattern: "**/*.vue",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "**/assets/12px-solid/*.svg",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "**/assets/16px-solid/*.svg",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "**/assets/16px-two-tone/*.svg",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "**/assets/24px-full-color/*.svg",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: "**/assets/24px-two-tone/*.svg",
|
||||||
|
group: "unknown",
|
||||||
|
position: "after",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
// Prettier plugin config (used to enforce HTML, CSS, and JS formatting styles as an ESLint plugin, where fixes are reported to ESLint to be applied when linting)
|
// Prettier plugin config (used to enforce HTML, CSS, and JS formatting styles as an ESLint plugin, where fixes are reported to ESLint to be applied when linting)
|
||||||
"prettier-vue/prettier": [
|
"prettier-vue/prettier": [
|
||||||
@@ -90,4 +135,12 @@ module.exports = {
|
|||||||
// Vue plugin config (used to validate Vue single-file components)
|
// Vue plugin config (used to validate Vue single-file components)
|
||||||
"vue/multi-word-component-names": "off",
|
"vue/multi-word-component-names": "off",
|
||||||
},
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ["*.js"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": ["off"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -221,16 +221,16 @@ img {
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { DialogState, createDialogState } from "@/state/dialog";
|
import { createAutoSaveManager } from "@/lifetime/auto-save";
|
||||||
|
import { initErrorHandling } from "@/lifetime/errors";
|
||||||
|
import { createInputManager, InputManager } from "@/lifetime/input";
|
||||||
|
import { createDialogState, DialogState } from "@/state/dialog";
|
||||||
import { createDocumentsState, DocumentsState } from "@/state/documents";
|
import { createDocumentsState, DocumentsState } from "@/state/documents";
|
||||||
import { createFullscreenState, FullscreenState } from "@/state/fullscreen";
|
import { createFullscreenState, FullscreenState } from "@/state/fullscreen";
|
||||||
|
|
||||||
import MainWindow from "@/components/window/MainWindow.vue";
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import { createEditorState, EditorState } from "@/state/wasm-loader";
|
import { createEditorState, EditorState } from "@/state/wasm-loader";
|
||||||
import { createInputManager, InputManager } from "@/lifetime/input";
|
|
||||||
import { initErrorHandling } from "@/lifetime/errors";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import { createAutoSaveManager } from "@/lifetime/auto-save";
|
import MainWindow from "@/components/window/MainWindow.vue";
|
||||||
|
|
||||||
// Vue injects don't play well with TypeScript, and all injects will show up as `any`. As a workaround, we can define these types.
|
// Vue injects don't play well with TypeScript, and all injects will show up as `any`. As a workaround, we can define these types.
|
||||||
declare module "@vue/runtime-core" {
|
declare module "@vue/runtime-core" {
|
||||||
|
|||||||
@@ -4,35 +4,35 @@
|
|||||||
<div class="left side">
|
<div class="left side">
|
||||||
<DropdownInput :menuEntries="documentModeEntries" v-model:selectedIndex="documentModeSelectionIndex" :drawIcon="true" />
|
<DropdownInput :menuEntries="documentModeEntries" v-model:selectedIndex="documentModeSelectionIndex" :drawIcon="true" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" />
|
<Separator :type="'Section'" />
|
||||||
|
|
||||||
<ToolOptions :activeTool="activeTool" :activeToolOptions="activeToolOptions" />
|
<ToolOptions :activeTool="activeTool" :activeToolOptions="activeToolOptions" />
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<div class="right side">
|
<div class="right side">
|
||||||
<OptionalInput v-model:checked="snappingEnabled" @update:checked="setSnap" :icon="'Snapping'" title="Snapping" />
|
<OptionalInput v-model:checked="snappingEnabled" @update:checked="(newStatus) => setSnap(newStatus)" :icon="'Snapping'" title="Snapping" />
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
<h3>Snapping</h3>
|
<h3>Snapping</h3>
|
||||||
<p>The contents of this popover menu are coming soon</p>
|
<p>The contents of this popover menu are coming soon</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Unrelated" />
|
<Separator :type="'Unrelated'" />
|
||||||
|
|
||||||
<OptionalInput v-model:checked="gridEnabled" @update:checked="dialog.comingSoon(318)" :icon="'Grid'" title="Grid" />
|
<OptionalInput v-model:checked="gridEnabled" @update:checked="() => dialog.comingSoon(318)" :icon="'Grid'" title="Grid" />
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
<h3>Grid</h3>
|
<h3>Grid</h3>
|
||||||
<p>The contents of this popover menu are coming soon</p>
|
<p>The contents of this popover menu are coming soon</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Unrelated" />
|
<Separator :type="'Unrelated'" />
|
||||||
|
|
||||||
<OptionalInput v-model:checked="overlaysEnabled" @update:checked="dialog.comingSoon(99)" :icon="'Overlays'" title="Overlays" />
|
<OptionalInput v-model:checked="overlaysEnabled" @update:checked="() => dialog.comingSoon(99)" :icon="'Overlays'" title="Overlays" />
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
<h3>Overlays</h3>
|
<h3>Overlays</h3>
|
||||||
<p>The contents of this popover menu are coming soon</p>
|
<p>The contents of this popover menu are coming soon</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Unrelated" />
|
<Separator :type="'Unrelated'" />
|
||||||
|
|
||||||
<RadioInput :entries="viewModeEntries" v-model:selectedIndex="viewModeIndex" class="combined-after" />
|
<RadioInput :entries="viewModeEntries" v-model:selectedIndex="viewModeIndex" class="combined-after" />
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
@@ -40,27 +40,27 @@
|
|||||||
<p>The contents of this popover menu are coming soon</p>
|
<p>The contents of this popover menu are coming soon</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" />
|
<Separator :type="'Section'" />
|
||||||
|
|
||||||
<NumberInput @update:value="setRotation" v-model:value="documentRotation" :incrementFactor="15" :unit="`°`" />
|
<NumberInput @update:value="(newRotation) => setRotation(newRotation)" v-model:value="documentRotation" :incrementFactor="15" :unit="'°'" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" />
|
<Separator :type="'Section'" />
|
||||||
|
|
||||||
<IconButton :action="() => this.$refs.zoom.onIncrement(IncrementDirection.Increase)" :icon="'ZoomIn'" :size="24" title="Zoom In" />
|
<IconButton :action="() => this.$refs.zoom.onIncrement('Increase')" :icon="'ZoomIn'" :size="24" title="Zoom In" />
|
||||||
<IconButton :action="() => this.$refs.zoom.onIncrement(IncrementDirection.Decrease)" :icon="'ZoomOut'" :size="24" title="Zoom Out" />
|
<IconButton :action="() => this.$refs.zoom.onIncrement('Decrease')" :icon="'ZoomOut'" :size="24" title="Zoom Out" />
|
||||||
<IconButton :action="() => this.$refs.zoom.updateValue(100)" :icon="'ZoomReset'" :size="24" title="Zoom to 100%" />
|
<IconButton :action="() => this.$refs.zoom.updateValue(100)" :icon="'ZoomReset'" :size="24" title="Zoom to 100%" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="'Related'" />
|
||||||
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
v-model:value="documentZoom"
|
v-model:value="documentZoom"
|
||||||
@update:value="setCanvasZoom"
|
@update:value="(newZoom) => setCanvasZoom(newZoom)"
|
||||||
:min="0.000001"
|
:min="0.000001"
|
||||||
:max="1000000"
|
:max="1000000"
|
||||||
:incrementBehavior="IncrementBehavior.Callback"
|
:incrementBehavior="'Callback'"
|
||||||
:incrementCallbackIncrease="increaseCanvasZoom"
|
:incrementCallbackIncrease="increaseCanvasZoom"
|
||||||
:incrementCallbackDecrease="decreaseCanvasZoom"
|
:incrementCallbackDecrease="decreaseCanvasZoom"
|
||||||
:unit="`%`"
|
:unit="'%'"
|
||||||
:displayDecimalPlaces="4"
|
:displayDecimalPlaces="4"
|
||||||
ref="zoom"
|
ref="zoom"
|
||||||
/>
|
/>
|
||||||
@@ -74,13 +74,13 @@
|
|||||||
<ShelfItemInput icon="LayoutNavigateTool" title="Navigate Tool (Z)" :active="activeTool === 'Navigate'" :action="() => selectTool('Navigate')" />
|
<ShelfItemInput icon="LayoutNavigateTool" title="Navigate Tool (Z)" :active="activeTool === 'Navigate'" :action="() => selectTool('Navigate')" />
|
||||||
<ShelfItemInput icon="LayoutEyedropperTool" title="Eyedropper Tool (I)" :active="activeTool === 'Eyedropper'" :action="() => selectTool('Eyedropper')" />
|
<ShelfItemInput icon="LayoutEyedropperTool" title="Eyedropper Tool (I)" :active="activeTool === 'Eyedropper'" :action="() => selectTool('Eyedropper')" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="'Section'" :direction="'Vertical'" />
|
||||||
|
|
||||||
<ShelfItemInput icon="ParametricTextTool" title="Text Tool (T)" :active="activeTool === 'Text'" :action="() => dialog.comingSoon(153) && selectTool('Text')" />
|
<ShelfItemInput icon="ParametricTextTool" title="Text Tool (T)" :active="activeTool === 'Text'" :action="() => dialog.comingSoon(153) && selectTool('Text')" />
|
||||||
<ShelfItemInput icon="ParametricFillTool" title="Fill Tool (F)" :active="activeTool === 'Fill'" :action="() => selectTool('Fill')" />
|
<ShelfItemInput icon="ParametricFillTool" title="Fill Tool (F)" :active="activeTool === 'Fill'" :action="() => selectTool('Fill')" />
|
||||||
<ShelfItemInput icon="ParametricGradientTool" title="Gradient Tool (H)" :active="activeTool === 'Gradient'" :action="() => dialog.comingSoon() && selectTool('Gradient')" />
|
<ShelfItemInput icon="ParametricGradientTool" title="Gradient Tool (H)" :active="activeTool === 'Gradient'" :action="() => dialog.comingSoon() && selectTool('Gradient')" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="'Section'" :direction="'Vertical'" />
|
||||||
|
|
||||||
<ShelfItemInput icon="RasterBrushTool" title="Brush Tool (B)" :active="activeTool === 'Brush'" :action="() => dialog.comingSoon() && selectTool('Brush')" />
|
<ShelfItemInput icon="RasterBrushTool" title="Brush Tool (B)" :active="activeTool === 'Brush'" :action="() => dialog.comingSoon() && selectTool('Brush')" />
|
||||||
<ShelfItemInput icon="RasterHealTool" title="Heal Tool (J)" :active="activeTool === 'Heal'" :action="() => dialog.comingSoon() && selectTool('Heal')" />
|
<ShelfItemInput icon="RasterHealTool" title="Heal Tool (J)" :active="activeTool === 'Heal'" :action="() => dialog.comingSoon() && selectTool('Heal')" />
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<ShelfItemInput icon="RasterBlurSharpenTool" title="Detail Tool (D)" :active="activeTool === 'BlurSharpen'" :action="() => dialog.comingSoon() && selectTool('BlurSharpen')" />
|
<ShelfItemInput icon="RasterBlurSharpenTool" title="Detail Tool (D)" :active="activeTool === 'BlurSharpen'" :action="() => dialog.comingSoon() && selectTool('BlurSharpen')" />
|
||||||
<ShelfItemInput icon="RasterRelightTool" title="Relight Tool (O)" :active="activeTool === 'Relight'" :action="() => dialog.comingSoon() && selectTool('Relight')" />
|
<ShelfItemInput icon="RasterRelightTool" title="Relight Tool (O)" :active="activeTool === 'Relight'" :action="() => dialog.comingSoon() && selectTool('Relight')" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="'Section'" :direction="'Vertical'" />
|
||||||
|
|
||||||
<ShelfItemInput icon="VectorPathTool" title="Path Tool (A)" :active="activeTool === 'Path'" :action="() => selectTool('Path')" />
|
<ShelfItemInput icon="VectorPathTool" title="Path Tool (A)" :active="activeTool === 'Path'" :action="() => selectTool('Path')" />
|
||||||
<ShelfItemInput icon="VectorPenTool" title="Pen Tool (P)" :active="activeTool === 'Pen'" :action="() => selectTool('Pen')" />
|
<ShelfItemInput icon="VectorPenTool" title="Pen Tool (P)" :active="activeTool === 'Pen'" :action="() => selectTool('Pen')" />
|
||||||
@@ -111,11 +111,11 @@
|
|||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol :class="'viewport'">
|
<LayoutCol :class="'viewport'">
|
||||||
<LayoutRow :class="'bar-area'">
|
<LayoutRow :class="'bar-area'">
|
||||||
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="RulerDirection.Horizontal" :class="'top-ruler'" />
|
<CanvasRuler :origin="rulerOrigin.x" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Horizontal'" :class="'top-ruler'" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow :class="'canvas-area'">
|
<LayoutRow :class="'canvas-area'">
|
||||||
<LayoutCol :class="'bar-area'">
|
<LayoutCol :class="'bar-area'">
|
||||||
<CanvasRuler :origin="rulerOrigin.y" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="RulerDirection.Vertical" />
|
<CanvasRuler :origin="rulerOrigin.y" :majorMarkSpacing="rulerSpacing" :numberInterval="rulerInterval" :direction="'Vertical'" />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol :class="'canvas-area'">
|
<LayoutCol :class="'canvas-area'">
|
||||||
<div class="canvas" ref="canvas">
|
<div class="canvas" ref="canvas">
|
||||||
@@ -125,22 +125,22 @@
|
|||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol :class="'bar-area'">
|
<LayoutCol :class="'bar-area'">
|
||||||
<PersistentScrollbar
|
<PersistentScrollbar
|
||||||
:direction="ScrollbarDirection.Vertical"
|
:direction="'Vertical'"
|
||||||
:handlePosition="scrollbarPos.y"
|
:handlePosition="scrollbarPos.y"
|
||||||
@update:handlePosition="translateCanvasY"
|
@update:handlePosition="(newValue) => translateCanvasY(newValue)"
|
||||||
v-model:handleLength="scrollbarSize.y"
|
v-model:handleLength="scrollbarSize.y"
|
||||||
@pressTrack="pageY"
|
@pressTrack="(delta) => pageY(delta)"
|
||||||
:class="'right-scrollbar'"
|
:class="'right-scrollbar'"
|
||||||
/>
|
/>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow :class="'bar-area'">
|
<LayoutRow :class="'bar-area'">
|
||||||
<PersistentScrollbar
|
<PersistentScrollbar
|
||||||
:direction="ScrollbarDirection.Horizontal"
|
:direction="'Horizontal'"
|
||||||
:handlePosition="scrollbarPos.x"
|
:handlePosition="scrollbarPos.x"
|
||||||
@update:handlePosition="translateCanvasX"
|
@update:handlePosition="(newValue) => translateCanvasX(newValue)"
|
||||||
v-model:handleLength="scrollbarSize.x"
|
v-model:handleLength="scrollbarSize.x"
|
||||||
@pressTrack="pageX"
|
@pressTrack="(delta) => pageX(delta)"
|
||||||
:class="'bottom-scrollbar'"
|
:class="'bottom-scrollbar'"
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
@@ -247,24 +247,22 @@
|
|||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation } from "@/dispatcher/js-messages";
|
import { UpdateArtwork, UpdateOverlays, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation } from "@/dispatcher/js-messages";
|
||||||
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import SwatchPairInput from "@/components/widgets/inputs/SwatchPairInput.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
|
||||||
import ShelfItemInput from "@/components/widgets/inputs/ShelfItemInput.vue";
|
|
||||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
|
||||||
import PersistentScrollbar, { ScrollbarDirection } from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
|
|
||||||
import CanvasRuler, { RulerDirection } from "@/components/widgets/rulers/CanvasRuler.vue";
|
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import RadioInput, { RadioEntries } from "@/components/widgets/inputs/RadioInput.vue";
|
|
||||||
import NumberInput, { IncrementDirection, IncrementBehavior } from "@/components/widgets/inputs/NumberInput.vue";
|
|
||||||
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
|
||||||
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
|
||||||
import ToolOptions from "@/components/widgets/options/ToolOptions.vue";
|
|
||||||
import { SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
import { SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
||||||
|
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
||||||
|
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
|
import OptionalInput from "@/components/widgets/inputs/OptionalInput.vue";
|
||||||
|
import RadioInput, { RadioEntries } from "@/components/widgets/inputs/RadioInput.vue";
|
||||||
|
import ShelfItemInput from "@/components/widgets/inputs/ShelfItemInput.vue";
|
||||||
|
import SwatchPairInput from "@/components/widgets/inputs/SwatchPairInput.vue";
|
||||||
|
import ToolOptions from "@/components/widgets/options/ToolOptions.vue";
|
||||||
|
import CanvasRuler from "@/components/widgets/rulers/CanvasRuler.vue";
|
||||||
|
import PersistentScrollbar from "@/components/widgets/scrollbars/PersistentScrollbar.vue";
|
||||||
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["editor", "dialog"],
|
inject: ["editor", "dialog"],
|
||||||
@@ -368,14 +366,14 @@ export default defineComponent({
|
|||||||
const documentModeEntries: SectionsOfMenuListEntries = [
|
const documentModeEntries: SectionsOfMenuListEntries = [
|
||||||
[
|
[
|
||||||
{ label: "Design Mode", icon: "ViewportDesignMode" },
|
{ label: "Design Mode", icon: "ViewportDesignMode" },
|
||||||
{ label: "Select Mode", icon: "ViewportSelectMode", action: () => this.dialog.comingSoon(330) },
|
{ label: "Select Mode", icon: "ViewportSelectMode", action: (): void => this.dialog.comingSoon(330) },
|
||||||
{ label: "Guide Mode", icon: "ViewportGuideMode", action: () => this.dialog.comingSoon(331) },
|
{ label: "Guide Mode", icon: "ViewportGuideMode", action: (): void => this.dialog.comingSoon(331) },
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
const viewModeEntries: RadioEntries = [
|
const viewModeEntries: RadioEntries = [
|
||||||
{ value: "normal", icon: "ViewModeNormal", tooltip: "View Mode: Normal", action: () => this.setViewMode("Normal") },
|
{ value: "normal", icon: "ViewModeNormal", tooltip: "View Mode: Normal", action: (): void => this.setViewMode("Normal") },
|
||||||
{ value: "outline", icon: "ViewModeOutline", tooltip: "View Mode: Outline", action: () => this.setViewMode("Outline") },
|
{ value: "outline", icon: "ViewModeOutline", tooltip: "View Mode: Outline", action: (): void => this.setViewMode("Outline") },
|
||||||
{ value: "pixels", icon: "ViewModePixels", tooltip: "View Mode: Pixels", action: () => this.dialog.comingSoon(320) },
|
{ value: "pixels", icon: "ViewModePixels", tooltip: "View Mode: Pixels", action: (): void => this.dialog.comingSoon(320) },
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -400,13 +398,6 @@ export default defineComponent({
|
|||||||
rulerOrigin: { x: 0, y: 0 },
|
rulerOrigin: { x: 0, y: 0 },
|
||||||
rulerSpacing: 100,
|
rulerSpacing: 100,
|
||||||
rulerInterval: 100,
|
rulerInterval: 100,
|
||||||
IncrementBehavior,
|
|
||||||
IncrementDirection,
|
|
||||||
MenuDirection,
|
|
||||||
SeparatorDirection,
|
|
||||||
ScrollbarDirection,
|
|
||||||
RulerDirection,
|
|
||||||
SeparatorType,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutCol :class="'layer-tree-panel'">
|
<LayoutCol :class="'layer-tree-panel'">
|
||||||
<LayoutRow :class="'options-bar'">
|
<LayoutRow :class="'options-bar'">
|
||||||
<DropdownInput v-model:selectedIndex="blendModeSelectedIndex" @update:selectedIndex="setLayerBlendMode" :menuEntries="blendModeEntries" :disabled="blendModeDropdownDisabled" />
|
<DropdownInput
|
||||||
|
v-model:selectedIndex="blendModeSelectedIndex"
|
||||||
|
@update:selectedIndex="(newSelectedIndex) => setLayerBlendMode(newSelectedIndex)"
|
||||||
|
:menuEntries="blendModeEntries"
|
||||||
|
:disabled="blendModeDropdownDisabled"
|
||||||
|
/>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="'Related'" />
|
||||||
|
|
||||||
<NumberInput v-model:value="opacity" @update:value="setLayerOpacity" :min="0" :max="100" :unit="`%`" :displayDecimalPlaces="2" :label="'Opacity'" :disabled="opacityNumberInputDisabled" />
|
<NumberInput
|
||||||
|
v-model:value="opacity"
|
||||||
|
@update:value="(newOpacity) => setLayerOpacity(newOpacity)"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:unit="'%'"
|
||||||
|
:displayDecimalPlaces="2"
|
||||||
|
:label="'Opacity'"
|
||||||
|
:disabled="opacityNumberInputDisabled"
|
||||||
|
/>
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Related" />
|
<Separator :type="'Related'" />
|
||||||
|
|
||||||
<PopoverButton>
|
<PopoverButton>
|
||||||
<h3>Compositing Options</h3>
|
<h3>Compositing Options</h3>
|
||||||
@@ -15,7 +29,7 @@
|
|||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow :class="'layer-tree scrollable-y'">
|
<LayoutRow :class="'layer-tree scrollable-y'">
|
||||||
<LayoutCol :class="'list'" ref="layerTreeList" @click="deselectAllLayers" @dragover="updateLine($event)" @dragend="drop()">
|
<LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateLine($event)" @dragend="drop()">
|
||||||
<div class="layer-row" v-for="(layer, index) in layers" :key="layer.path">
|
<div class="layer-row" v-for="(layer, index) in layers" :key="layer.path">
|
||||||
<div class="layer-visibility">
|
<div class="layer-visibility">
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -26,7 +40,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="layer.layer_type === LayerTypeOptions.Folder"
|
v-if="layer.layer_type === 'Folder'"
|
||||||
class="node-connector"
|
class="node-connector"
|
||||||
:class="{ expanded: layer.layer_metadata.expanded }"
|
:class="{ expanded: layer.layer_metadata.expanded }"
|
||||||
@click.stop="handleNodeConnectorClick(layer.path)"
|
@click.stop="handleNodeConnectorClick(layer.path)"
|
||||||
@@ -46,7 +60,7 @@
|
|||||||
>
|
>
|
||||||
<div class="layer-thumbnail" v-html="layer.thumbnail"></div>
|
<div class="layer-thumbnail" v-html="layer.thumbnail"></div>
|
||||||
<div class="layer-type-icon">
|
<div class="layer-type-icon">
|
||||||
<IconLabel v-if="layer.layer_type === LayerTypeOptions.Folder" :icon="'NodeTypeFolder'" title="Folder" />
|
<IconLabel v-if="layer.layer_type === 'Folder'" :icon="'NodeTypeFolder'" title="Folder" />
|
||||||
<IconLabel v-else :icon="'NodeTypePath'" title="Path" />
|
<IconLabel v-else :icon="'NodeTypePath'" title="Path" />
|
||||||
</div>
|
</div>
|
||||||
<div class="layer-name">
|
<div class="layer-name">
|
||||||
@@ -228,19 +242,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { BlendMode, DisplayFolderTreeStructure, UpdateLayer, LayerPanelEntry, LayerTypeOptions } from "@/dispatcher/js-messages";
|
import { BlendMode, DisplayFolderTreeStructure, UpdateLayer, LayerPanelEntry } from "@/dispatcher/js-messages";
|
||||||
import { SeparatorType } from "@/components/widgets/widgets";
|
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
|
||||||
import { SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
import { SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
||||||
|
import DropdownInput from "@/components/widgets/inputs/DropdownInput.vue";
|
||||||
|
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
|
|
||||||
const blendModeEntries: SectionsOfMenuListEntries<BlendMode> = [
|
const blendModeEntries: SectionsOfMenuListEntries<BlendMode> = [
|
||||||
[{ label: "Normal", value: "Normal" }],
|
[{ label: "Normal", value: "Normal" }],
|
||||||
@@ -301,13 +313,10 @@ export default defineComponent({
|
|||||||
selectionRangeEndLayer: undefined as undefined | LayerPanelEntry,
|
selectionRangeEndLayer: undefined as undefined | LayerPanelEntry,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
draggingData: undefined as undefined | { path: BigUint64Array; above: boolean; nearestPath: BigUint64Array; insertLine: HTMLDivElement },
|
draggingData: undefined as undefined | { path: BigUint64Array; above: boolean; nearestPath: BigUint64Array; insertLine: HTMLDivElement },
|
||||||
MenuDirection,
|
|
||||||
SeparatorType,
|
|
||||||
LayerTypeOptions,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
layerIndent(layer: LayerPanelEntry): string {
|
layerIndent(layer: LayerPanelEntry) {
|
||||||
return `${(layer.path.length - 1) * 16}px`;
|
return `${(layer.path.length - 1) * 16}px`;
|
||||||
},
|
},
|
||||||
async toggleLayerVisibility(path: BigUint64Array) {
|
async toggleLayerVisibility(path: BigUint64Array) {
|
||||||
@@ -316,14 +325,12 @@ export default defineComponent({
|
|||||||
async handleNodeConnectorClick(path: BigUint64Array) {
|
async handleNodeConnectorClick(path: BigUint64Array) {
|
||||||
this.editor.instance.toggle_layer_expansion(path);
|
this.editor.instance.toggle_layer_expansion(path);
|
||||||
},
|
},
|
||||||
async setLayerBlendMode() {
|
async setLayerBlendMode(newSelectedIndex: number) {
|
||||||
const blendMode = this.blendModeEntries.flat()[this.blendModeSelectedIndex].value;
|
const blendMode = this.blendModeEntries.flat()[newSelectedIndex].value;
|
||||||
if (blendMode) {
|
if (blendMode) this.editor.instance.set_blend_mode_for_selected_layers(blendMode);
|
||||||
this.editor.instance.set_blend_mode_for_selected_layers(blendMode);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async setLayerOpacity() {
|
async setLayerOpacity(newOpacity: number) {
|
||||||
this.editor.instance.set_opacity_for_selected_layers(this.opacity);
|
this.editor.instance.set_opacity_for_selected_layers(newOpacity);
|
||||||
},
|
},
|
||||||
async selectLayer(clickedLayer: LayerPanelEntry, ctrl: boolean, shift: boolean) {
|
async selectLayer(clickedLayer: LayerPanelEntry, ctrl: boolean, shift: boolean) {
|
||||||
this.editor.instance.select_layer(clickedLayer.path, ctrl, shift);
|
this.editor.instance.select_layer(clickedLayer.path, ctrl, shift);
|
||||||
@@ -377,7 +384,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Inserting below current row
|
// Inserting below current row
|
||||||
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0 && layer.layer_type !== LayerTypeOptions.Folder) {
|
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0 && layer.layer_type !== "Folder") {
|
||||||
closest = -distance;
|
closest = -distance;
|
||||||
nearestPath = layer.path;
|
nearestPath = layer.path;
|
||||||
if (child.parentNode && child.parentNode.nextSibling) {
|
if (child.parentNode && child.parentNode.nextSibling) {
|
||||||
@@ -491,7 +498,8 @@ export default defineComponent({
|
|||||||
this.editor.dispatcher.subscribeJsMessage(DisplayFolderTreeStructure, (displayFolderTreeStructure) => {
|
this.editor.dispatcher.subscribeJsMessage(DisplayFolderTreeStructure, (displayFolderTreeStructure) => {
|
||||||
const path = [] as bigint[];
|
const path = [] as bigint[];
|
||||||
this.layers = [] as LayerPanelEntry[];
|
this.layers = [] as LayerPanelEntry[];
|
||||||
function recurse(folder: DisplayFolderTreeStructure, layers: LayerPanelEntry[], cache: Map<string, LayerPanelEntry>) {
|
|
||||||
|
const recurse = (folder: DisplayFolderTreeStructure, layers: LayerPanelEntry[], cache: Map<string, LayerPanelEntry>): void => {
|
||||||
folder.children.forEach((item) => {
|
folder.children.forEach((item) => {
|
||||||
// TODO: fix toString
|
// TODO: fix toString
|
||||||
path.push(BigInt(item.layerId.toString()));
|
path.push(BigInt(item.layerId.toString()));
|
||||||
@@ -500,7 +508,8 @@ export default defineComponent({
|
|||||||
if (item.children.length >= 1) recurse(item, layers, cache);
|
if (item.children.length >= 1) recurse(item, layers, cache);
|
||||||
path.pop();
|
path.pop();
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
recurse(displayFolderTreeStructure, this.layers, this.layerCache);
|
recurse(displayFolderTreeStructure, this.layers, this.layerCache);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({});
|
||||||
components: {},
|
|
||||||
props: {},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,8 +7,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({});
|
||||||
components: {},
|
|
||||||
props: {},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="icon-button" :class="`size-${String(size)}`" @click="(e) => action(e)">
|
<button class="icon-button" :class="`size-${size}`" @click="(e) => action(e)">
|
||||||
<IconLabel :icon="icon" />
|
<IconLabel :icon="icon" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,16 +57,16 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel, { IconName, IconSize } from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
action: { type: Function, required: true },
|
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||||
icon: { type: String, required: true },
|
icon: { type: String as PropType<IconName>, required: true },
|
||||||
size: { type: Number, required: true },
|
size: { type: Number as PropType<IconSize>, required: true },
|
||||||
gapAfter: { type: Boolean, default: false },
|
gapAfter: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popover-button">
|
<div class="popover-button">
|
||||||
<IconButton :action="handleClick" :icon="icon" :size="16" data-hover-menu-spawner />
|
<IconButton :action="handleClick" :icon="icon" :size="16" data-hover-menu-spawner />
|
||||||
<FloatingMenu :type="MenuType.Popover" :direction="MenuDirection.Bottom" ref="floatingMenu">
|
<FloatingMenu :type="'Popover'" :direction="'Bottom'" ref="floatingMenu">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,15 +47,12 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import FloatingMenu from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
|
|
||||||
export enum PopoverButtonIcon {
|
export type PopoverButtonIcon = "DropdownArrow" | "VerticalEllipsis";
|
||||||
"DropdownArrow" = "DropdownArrow",
|
|
||||||
"VerticalEllipsis" = "VerticalEllipsis",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -63,8 +60,8 @@ export default defineComponent({
|
|||||||
IconButton,
|
IconButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
action: { type: Function, required: false },
|
action: { type: Function as PropType<() => void>, required: false },
|
||||||
icon: { type: String, default: PopoverButtonIcon.DropdownArrow },
|
icon: { type: String as PropType<PopoverButtonIcon>, default: "DropdownArrow" },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick() {
|
handleClick() {
|
||||||
@@ -73,11 +70,5 @@ export default defineComponent({
|
|||||||
if (this.action) this.action();
|
if (this.action) this.action();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
MenuDirection,
|
|
||||||
MenuType,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="text-button" :class="{ emphasized, disabled }" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" @click="action">
|
<button class="text-button" :class="{ emphasized, disabled }" :style="minWidth > 0 ? `min-width: ${minWidth}px` : ''" @click="(e) => action(e)">
|
||||||
<TextLabel>{{ label }}</TextLabel>
|
<TextLabel>{{ label }}</TextLabel>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -49,18 +49,18 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
action: { type: Function, required: true },
|
action: { type: Function as PropType<(e: MouseEvent) => void>, required: true },
|
||||||
label: { type: String, required: true },
|
label: { type: String as PropType<string>, required: true },
|
||||||
emphasized: { type: Boolean, default: false },
|
emphasized: { type: Boolean as PropType<boolean>, default: false },
|
||||||
disabled: { type: Boolean, default: false },
|
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||||
minWidth: { type: Number, default: 0 },
|
minWidth: { type: Number as PropType<number>, default: 0 },
|
||||||
gapAfter: { type: Boolean, default: false },
|
gapAfter: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
components: { TextLabel },
|
components: { TextLabel },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="color-picker">
|
<div class="color-picker">
|
||||||
<div class="saturation-picker" ref="saturationPicker" data-picker-action="MoveSaturation" @pointerdown="onPointerDown">
|
<div class="saturation-picker" ref="saturationPicker" @pointerdown="(e) => onPointerDown(e)">
|
||||||
<div ref="saturationCursor" class="selection-circle"></div>
|
<div ref="saturationCursor" class="selection-circle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hue-picker" ref="huePicker" data-picker-action="MoveHue" @pointerdown="onPointerDown">
|
<div class="hue-picker" ref="huePicker" @pointerdown="(e) => onPointerDown(e)">
|
||||||
<div ref="hueCursor" class="selection-pincers"></div>
|
<div ref="hueCursor" class="selection-pincers"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="opacity-picker" ref="opacityPicker" data-picker-action="MoveOpacity" @pointerdown="onPointerDown">
|
<div class="opacity-picker" ref="opacityPicker" @pointerdown="(e) => onPointerDown(e)">
|
||||||
<div ref="opacityCursor" class="selection-pincers"></div>
|
<div ref="opacityCursor" class="selection-pincers"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,43 +117,28 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { hsvToRgb, rgbToHsv, isRGB } from "@/utilities/color";
|
import { RGBA } from "@/dispatcher/js-messages";
|
||||||
|
import { hsvaToRgba, rgbaToHsva } from "@/utilities/color";
|
||||||
import { clamp } from "@/utilities/math";
|
import { clamp } from "@/utilities/math";
|
||||||
|
|
||||||
const enum ColorPickerState {
|
type ColorPickerState = "Idle" | "MoveHue" | "MoveOpacity" | "MoveSaturation";
|
||||||
Idle = "Idle",
|
|
||||||
MoveHue = "MoveHue",
|
// TODO: Clean up the fundamental code design in this file to simplify it and use better practices.
|
||||||
MoveOpacity = "MoveOpacity",
|
// TODO: Such as removing the `picker*` data variables and reducing the number of functions which call each other in weird, non-obvious ways.
|
||||||
MoveSaturation = "MoveSaturation",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
color: { type: Object, required: true },
|
color: { type: Object as PropType<RGBA>, required: true },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
state: ColorPickerState.Idle,
|
state: "Idle" as ColorPickerState,
|
||||||
// Disable proxy on this object
|
pickerHSVA: { h: 0, s: 0, v: 0, a: 1 },
|
||||||
// https://v3.vuejs.org/api/options-data.html#data-2
|
pickerHueRect: { width: 0, height: 0, top: 0, left: 0 },
|
||||||
// eslint-disable-next-line vue/no-reserved-keys
|
pickerOpacityRect: { width: 0, height: 0, top: 0, left: 0 },
|
||||||
_: {
|
pickerSaturationRect: { width: 0, height: 0, top: 0, left: 0 },
|
||||||
colorPicker: {
|
|
||||||
color: { h: 0, s: 0, v: 0, a: 1 },
|
|
||||||
hue: {
|
|
||||||
rect: { width: 0, height: 0, top: 0, left: 0 },
|
|
||||||
},
|
|
||||||
opacity: {
|
|
||||||
rect: { width: 0, height: 0, top: 0, left: 0 },
|
|
||||||
},
|
|
||||||
saturation: {
|
|
||||||
rect: { width: 0, height: 0, top: 0, left: 0 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -171,116 +156,122 @@ export default defineComponent({
|
|||||||
document.removeEventListener("pointermove", this.onPointerMove);
|
document.removeEventListener("pointermove", this.onPointerMove);
|
||||||
document.removeEventListener("pointerup", this.onPointerUp);
|
document.removeEventListener("pointerup", this.onPointerUp);
|
||||||
},
|
},
|
||||||
getRef<T>(name: string) {
|
|
||||||
return this.$refs[name] as T;
|
|
||||||
},
|
|
||||||
onPointerDown(e: PointerEvent) {
|
onPointerDown(e: PointerEvent) {
|
||||||
if (!(e.currentTarget instanceof Element)) return;
|
if (!(e.currentTarget instanceof HTMLElement)) return;
|
||||||
const picker = e.currentTarget.getAttribute("data-picker-action");
|
|
||||||
this.state = (() => {
|
|
||||||
switch (picker) {
|
|
||||||
case "MoveHue":
|
|
||||||
return ColorPickerState.MoveHue;
|
|
||||||
case "MoveOpacity":
|
|
||||||
return ColorPickerState.MoveOpacity;
|
|
||||||
case "MoveSaturation":
|
|
||||||
return ColorPickerState.MoveSaturation;
|
|
||||||
default:
|
|
||||||
return ColorPickerState.Idle;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (this.state !== ColorPickerState.Idle) {
|
if ((this.$refs.saturationPicker as HTMLElement).contains(e.currentTarget)) {
|
||||||
this.addEvents();
|
this.state = "MoveSaturation";
|
||||||
this.updateRects();
|
} else if ((this.$refs.huePicker as HTMLElement).contains(e.currentTarget)) {
|
||||||
this.onPointerMove(e);
|
this.state = "MoveHue";
|
||||||
|
} else if ((this.$refs.opacityPicker as HTMLElement).contains(e.currentTarget)) {
|
||||||
|
this.state = "MoveOpacity";
|
||||||
|
} else {
|
||||||
|
this.state = "Idle";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state === "Idle") return;
|
||||||
|
|
||||||
|
this.addEvents();
|
||||||
|
this.updateRects();
|
||||||
|
this.onPointerMove(e);
|
||||||
},
|
},
|
||||||
onPointerMove(e: PointerEvent) {
|
onPointerMove(e: PointerEvent) {
|
||||||
const { colorPicker } = this.$data._;
|
switch (this.state) {
|
||||||
|
case "MoveHue":
|
||||||
if (this.state === ColorPickerState.MoveHue) {
|
this.setHueCursorPosition(e.clientY - this.pickerHueRect.top);
|
||||||
this.setHuePosition(e.clientY - colorPicker.hue.rect.top);
|
break;
|
||||||
} else if (this.state === ColorPickerState.MoveOpacity) {
|
case "MoveOpacity":
|
||||||
this.setOpacityPosition(e.clientY - colorPicker.opacity.rect.top);
|
this.setOpacityCursorPosition(e.clientY - this.pickerOpacityRect.top);
|
||||||
} else if (this.state === ColorPickerState.MoveSaturation) {
|
break;
|
||||||
this.setSaturationPosition(e.clientX - colorPicker.saturation.rect.left, e.clientY - colorPicker.saturation.rect.top);
|
case "MoveSaturation":
|
||||||
|
this.setSaturationCursorPosition(e.clientX - this.pickerSaturationRect.left, e.clientY - this.pickerSaturationRect.top);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state !== ColorPickerState.Idle) {
|
this.updateHue();
|
||||||
this.updateHue();
|
// The `color` prop's watcher calls `this.updateColor()`
|
||||||
this.$emit("update:color", hsvToRgb(colorPicker.color));
|
this.$emit("update:color", hsvaToRgba(this.pickerHSVA));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPointerUp() {
|
onPointerUp() {
|
||||||
if (this.state !== ColorPickerState.Idle) {
|
if (this.state === "Idle") return;
|
||||||
this.state = ColorPickerState.Idle;
|
|
||||||
this.removeEvents();
|
this.state = "Idle";
|
||||||
}
|
this.removeEvents();
|
||||||
},
|
},
|
||||||
updateRects() {
|
updateRects() {
|
||||||
const { colorPicker } = this.$data._;
|
// Saturation
|
||||||
|
const saturationPicker = this.$refs.saturationPicker as HTMLElement;
|
||||||
const saturationPicker = this.getRef<HTMLElement>("saturationPicker");
|
|
||||||
const saturation = saturationPicker.getBoundingClientRect();
|
const saturation = saturationPicker.getBoundingClientRect();
|
||||||
colorPicker.saturation.rect.width = saturation.width;
|
|
||||||
colorPicker.saturation.rect.height = saturation.height;
|
|
||||||
colorPicker.saturation.rect.left = saturation.left;
|
|
||||||
colorPicker.saturation.rect.top = saturation.top;
|
|
||||||
|
|
||||||
const huePicker = this.getRef<HTMLElement>("huePicker");
|
this.pickerSaturationRect.width = saturation.width;
|
||||||
|
this.pickerSaturationRect.height = saturation.height;
|
||||||
|
this.pickerSaturationRect.left = saturation.left;
|
||||||
|
this.pickerSaturationRect.top = saturation.top;
|
||||||
|
|
||||||
|
// Hue
|
||||||
|
const huePicker = this.$refs.huePicker as HTMLElement;
|
||||||
const hue = huePicker.getBoundingClientRect();
|
const hue = huePicker.getBoundingClientRect();
|
||||||
colorPicker.hue.rect.width = hue.width;
|
|
||||||
colorPicker.hue.rect.height = hue.height;
|
|
||||||
colorPicker.hue.rect.left = hue.left;
|
|
||||||
colorPicker.hue.rect.top = hue.top;
|
|
||||||
|
|
||||||
const opacityPicker = this.getRef<HTMLElement>("opacityPicker");
|
this.pickerHueRect.width = hue.width;
|
||||||
|
this.pickerHueRect.height = hue.height;
|
||||||
|
this.pickerHueRect.left = hue.left;
|
||||||
|
this.pickerHueRect.top = hue.top;
|
||||||
|
|
||||||
|
// Opacity
|
||||||
|
const opacityPicker = this.$refs.opacityPicker as HTMLElement;
|
||||||
const opacity = opacityPicker.getBoundingClientRect();
|
const opacity = opacityPicker.getBoundingClientRect();
|
||||||
colorPicker.opacity.rect.width = opacity.width;
|
|
||||||
colorPicker.opacity.rect.height = opacity.height;
|
this.pickerOpacityRect.width = opacity.width;
|
||||||
colorPicker.opacity.rect.left = opacity.left;
|
this.pickerOpacityRect.height = opacity.height;
|
||||||
colorPicker.opacity.rect.top = opacity.top;
|
this.pickerOpacityRect.left = opacity.left;
|
||||||
|
this.pickerOpacityRect.top = opacity.top;
|
||||||
},
|
},
|
||||||
setSaturationPosition(x: number, y: number) {
|
setSaturationCursorPosition(x: number, y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const saturationPositionX = clamp(x, 0, this.pickerSaturationRect.width);
|
||||||
const saturationCursor = this.getRef<HTMLElement>("saturationCursor");
|
const saturationPositionY = clamp(y, 0, this.pickerSaturationRect.height);
|
||||||
const saturationPosition = [clamp(x, 0, colorPicker.saturation.rect.width), clamp(y, 0, colorPicker.saturation.rect.height)];
|
|
||||||
saturationCursor.style.transform = `translate(${saturationPosition[0]}px, ${saturationPosition[1]}px)`;
|
const saturationCursor = this.$refs.saturationCursor as HTMLElement;
|
||||||
colorPicker.color.s = saturationPosition[0] / colorPicker.saturation.rect.width;
|
saturationCursor.style.transform = `translate(${saturationPositionX}px, ${saturationPositionY}px)`;
|
||||||
colorPicker.color.v = (1 - saturationPosition[1] / colorPicker.saturation.rect.height) * 255;
|
|
||||||
|
this.pickerHSVA.s = saturationPositionX / this.pickerSaturationRect.width;
|
||||||
|
this.pickerHSVA.v = (1 - saturationPositionY / this.pickerSaturationRect.height) * 255;
|
||||||
},
|
},
|
||||||
setHuePosition(y: number) {
|
setHueCursorPosition(y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const huePosition = clamp(y, 0, this.pickerHueRect.height);
|
||||||
const hueCursor = this.getRef<HTMLElement>("hueCursor");
|
|
||||||
const huePosition = clamp(y, 0, colorPicker.hue.rect.height);
|
const hueCursor = this.$refs.hueCursor as HTMLElement;
|
||||||
hueCursor.style.transform = `translateY(${huePosition}px)`;
|
hueCursor.style.transform = `translateY(${huePosition}px)`;
|
||||||
colorPicker.color.h = clamp(1 - huePosition / colorPicker.hue.rect.height);
|
|
||||||
|
this.pickerHSVA.h = clamp(1 - huePosition / this.pickerHueRect.height);
|
||||||
},
|
},
|
||||||
setOpacityPosition(y: number) {
|
setOpacityCursorPosition(y: number) {
|
||||||
const { colorPicker } = this.$data._;
|
const opacityPosition = clamp(y, 0, this.pickerOpacityRect.height);
|
||||||
const opacityCursor = this.getRef<HTMLElement>("opacityCursor");
|
|
||||||
const opacityPosition = clamp(y, 0, colorPicker.opacity.rect.height);
|
const opacityCursor = this.$refs.opacityCursor as HTMLElement;
|
||||||
opacityCursor.style.transform = `translateY(${opacityPosition}px)`;
|
opacityCursor.style.transform = `translateY(${opacityPosition}px)`;
|
||||||
colorPicker.color.a = clamp(1 - opacityPosition / colorPicker.opacity.rect.height);
|
|
||||||
|
this.pickerHSVA.a = clamp(1 - opacityPosition / this.pickerOpacityRect.height);
|
||||||
},
|
},
|
||||||
updateHue() {
|
updateHue() {
|
||||||
const { colorPicker } = this.$data._;
|
const hsva = hsvaToRgba({ h: this.pickerHSVA.h, s: 1, v: 255, a: 1 });
|
||||||
let color = hsvToRgb({ h: colorPicker.color.h, s: 1, v: 255, a: 1 });
|
const rgba = hsvaToRgba(this.pickerHSVA);
|
||||||
this.$el.style.setProperty("--saturation-picker-hue", `rgb(${color.r}, ${color.g}, ${color.b})`);
|
|
||||||
color = hsvToRgb(colorPicker.color);
|
this.$el.style.setProperty("--saturation-picker-hue", `rgb(${hsva.r}, ${hsva.g}, ${hsva.b})`);
|
||||||
this.$el.style.setProperty("--opacity-picker-color", `rgb(${color.r}, ${color.g}, ${color.b})`);
|
this.$el.style.setProperty("--opacity-picker-color", `rgb(${rgba.r}, ${rgba.g}, ${rgba.b})`);
|
||||||
},
|
},
|
||||||
updateColor() {
|
updateColor() {
|
||||||
if (this.state !== ColorPickerState.Idle) return;
|
if (this.state !== "Idle") return;
|
||||||
const { color } = this;
|
|
||||||
if (!isRGB(color)) return;
|
this.pickerHSVA = rgbaToHsva(this.color);
|
||||||
const { colorPicker } = this.$data._;
|
|
||||||
colorPicker.color = rgbToHsv(color);
|
|
||||||
this.updateRects();
|
this.updateRects();
|
||||||
this.setSaturationPosition(colorPicker.color.s * colorPicker.saturation.rect.width, (1 - colorPicker.color.v / 255) * colorPicker.saturation.rect.height);
|
|
||||||
this.setOpacityPosition((1 - colorPicker.color.a) * colorPicker.opacity.rect.height);
|
this.setSaturationCursorPosition(this.pickerHSVA.s * this.pickerSaturationRect.width, (1 - this.pickerHSVA.v / 255) * this.pickerSaturationRect.height);
|
||||||
this.setHuePosition((1 - colorPicker.color.h) * colorPicker.hue.rect.height);
|
this.setOpacityCursorPosition((1 - this.pickerHSVA.a) * this.pickerOpacityRect.height);
|
||||||
|
this.setHueCursorPosition((1 - this.pickerHSVA.h) * this.pickerHueRect.height);
|
||||||
|
|
||||||
this.updateHue();
|
this.updateHue();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dialog-modal">
|
<div class="dialog-modal">
|
||||||
<FloatingMenu :type="MenuType.Dialog" :direction="MenuDirection.Center">
|
<FloatingMenu :type="'Dialog'" :direction="'Center'">
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
<LayoutCol :class="'icon-column'">
|
<LayoutCol :class="'icon-column'">
|
||||||
<!-- `dialog.state.icon` class exists to provide special sizing in CSS to specific icons -->
|
<!-- `dialog.state.icon` class exists to provide special sizing in CSS to specific icons -->
|
||||||
@@ -79,12 +79,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
|
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
||||||
|
import FloatingMenu from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||||
import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["dialog"],
|
inject: ["dialog"],
|
||||||
@@ -101,11 +101,5 @@ export default defineComponent({
|
|||||||
this.dialog.dismissDialog();
|
this.dialog.dismissDialog();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
MenuDirection,
|
|
||||||
MenuType,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open || type === MenuType.Dialog" ref="floatingMenu">
|
<div class="floating-menu" :class="[direction.toLowerCase(), type.toLowerCase()]" v-if="open || type === 'Dialog'" ref="floatingMenu">
|
||||||
<div class="tail" v-if="type === MenuType.Popover"></div>
|
<div class="tail" v-if="type === 'Popover'"></div>
|
||||||
<div class="floating-menu-container" ref="floatingMenuContainer">
|
<div class="floating-menu-container" ref="floatingMenuContainer">
|
||||||
<div class="floating-menu-content" :class="{ 'scrollable-y': scrollable }" ref="floatingMenuContent" :style="floatingMenuContentStyle">
|
<div class="floating-menu-content" :class="{ 'scrollable-y': scrollable }" ref="floatingMenuContent" :style="floatingMenuContentStyle">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -177,36 +177,20 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export enum MenuDirection {
|
export type MenuDirection = "Top" | "Bottom" | "Left" | "Right" | "TopLeft" | "TopRight" | "BottomLeft" | "BottomRight" | "Center";
|
||||||
Top = "Top",
|
export type MenuType = "Popover" | "Dropdown" | "Dialog";
|
||||||
Bottom = "Bottom",
|
|
||||||
Left = "Left",
|
|
||||||
Right = "Right",
|
|
||||||
TopLeft = "TopLeft",
|
|
||||||
TopRight = "TopRight",
|
|
||||||
BottomLeft = "BottomLeft",
|
|
||||||
BottomRight = "BottomRight",
|
|
||||||
Center = "Center",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum MenuType {
|
|
||||||
Popover = "Popover",
|
|
||||||
Dropdown = "Dropdown",
|
|
||||||
Dialog = "Dialog",
|
|
||||||
}
|
|
||||||
|
|
||||||
const POINTER_STRAY_DISTANCE = 100;
|
const POINTER_STRAY_DISTANCE = 100;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
direction: { type: String, default: MenuDirection.Bottom },
|
direction: { type: String as PropType<MenuDirection>, default: "Bottom" },
|
||||||
type: { type: String, required: true },
|
type: { type: String as PropType<MenuType>, required: true },
|
||||||
windowEdgeMargin: { type: Number, default: 6 },
|
windowEdgeMargin: { type: Number as PropType<number>, default: 6 },
|
||||||
minWidth: { type: Number, default: 0 },
|
minWidth: { type: Number as PropType<number>, default: 0 },
|
||||||
scrollable: { type: Boolean, default: false },
|
scrollable: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const containerResizeObserver = new ResizeObserver((entries) => {
|
const containerResizeObserver = new ResizeObserver((entries) => {
|
||||||
@@ -218,8 +202,6 @@ export default defineComponent({
|
|||||||
open: false,
|
open: false,
|
||||||
pointerStillDown: false,
|
pointerStillDown: false,
|
||||||
containerResizeObserver,
|
containerResizeObserver,
|
||||||
MenuDirection,
|
|
||||||
MenuType,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
@@ -235,8 +217,8 @@ export default defineComponent({
|
|||||||
let zeroedBorderDirection1: Edge | undefined;
|
let zeroedBorderDirection1: Edge | undefined;
|
||||||
let zeroedBorderDirection2: Edge | undefined;
|
let zeroedBorderDirection2: Edge | undefined;
|
||||||
|
|
||||||
if (this.direction === MenuDirection.Top || this.direction === MenuDirection.Bottom) {
|
if (this.direction === "Top" || this.direction === "Bottom") {
|
||||||
zeroedBorderDirection1 = this.direction === MenuDirection.Top ? "Bottom" : "Top";
|
zeroedBorderDirection1 = this.direction === "Top" ? "Bottom" : "Top";
|
||||||
|
|
||||||
if (floatingMenuBounds.left - this.windowEdgeMargin <= workspaceBounds.left) {
|
if (floatingMenuBounds.left - this.windowEdgeMargin <= workspaceBounds.left) {
|
||||||
floatingMenuContent.style.left = `${this.windowEdgeMargin}px`;
|
floatingMenuContent.style.left = `${this.windowEdgeMargin}px`;
|
||||||
@@ -249,8 +231,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.direction === MenuDirection.Left || this.direction === MenuDirection.Right) {
|
if (this.direction === "Left" || this.direction === "Right") {
|
||||||
zeroedBorderDirection2 = this.direction === MenuDirection.Left ? "Right" : "Left";
|
zeroedBorderDirection2 = this.direction === "Left" ? "Right" : "Left";
|
||||||
|
|
||||||
if (floatingMenuBounds.top - this.windowEdgeMargin <= workspaceBounds.top) {
|
if (floatingMenuBounds.top - this.windowEdgeMargin <= workspaceBounds.top) {
|
||||||
floatingMenuContent.style.top = `${this.windowEdgeMargin}px`;
|
floatingMenuContent.style.top = `${this.windowEdgeMargin}px`;
|
||||||
@@ -264,7 +246,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the rounded corner from where the tail perfectly meets the corner
|
// Remove the rounded corner from where the tail perfectly meets the corner
|
||||||
if (this.type === MenuType.Popover && this.windowEdgeMargin === 6 && zeroedBorderDirection1 && zeroedBorderDirection2) {
|
if (this.type === "Popover" && this.windowEdgeMargin === 6 && zeroedBorderDirection1 && zeroedBorderDirection2) {
|
||||||
switch (`${zeroedBorderDirection1}${zeroedBorderDirection2}`) {
|
switch (`${zeroedBorderDirection1}${zeroedBorderDirection2}`) {
|
||||||
case "TopLeft":
|
case "TopLeft":
|
||||||
floatingMenuContent.style.borderTopLeftRadius = "0";
|
floatingMenuContent.style.borderTopLeftRadius = "0";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<FloatingMenu :class="'menu-list'" :direction="direction" :type="MenuType.Dropdown" ref="floatingMenu" :windowEdgeMargin="0" :scrollable="scrollable" data-hover-menu-keep-open>
|
<FloatingMenu :class="'menu-list'" :direction="direction" :type="'Dropdown'" ref="floatingMenu" :windowEdgeMargin="0" :scrollable="scrollable" data-hover-menu-keep-open>
|
||||||
<template v-for="(section, sectionIndex) in menuEntries" :key="sectionIndex">
|
<template v-for="(section, sectionIndex) in menuEntries" :key="sectionIndex">
|
||||||
<Separator :type="SeparatorType.List" :direction="SeparatorDirection.Vertical" v-if="sectionIndex > 0" />
|
<Separator :type="'List'" :direction="'Vertical'" v-if="sectionIndex > 0" />
|
||||||
<div
|
<div
|
||||||
v-for="(entry, entryIndex) in section"
|
v-for="(entry, entryIndex) in section"
|
||||||
:key="entryIndex"
|
:key="entryIndex"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<MenuList
|
<MenuList
|
||||||
v-if="entry.children"
|
v-if="entry.children"
|
||||||
:direction="MenuDirection.TopRight"
|
:direction="'TopRight'"
|
||||||
:menuEntries="entry.children"
|
:menuEntries="entry.children"
|
||||||
v-bind="{ defaultAction, minWidth, drawIcon, scrollable }"
|
v-bind="{ defaultAction, minWidth, drawIcon, scrollable }"
|
||||||
:ref="(ref) => setEntryRefs(entry, ref)"
|
:ref="(ref) => setEntryRefs(entry, ref)"
|
||||||
@@ -132,13 +132,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
import FloatingMenu, { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
|
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
|
||||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|
||||||
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
|
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
|
||||||
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
|
|
||||||
export type MenuListEntries<Value = string> = MenuListEntry<Value>[];
|
export type MenuListEntries<Value = string> = MenuListEntry<Value>[];
|
||||||
export type SectionsOfMenuListEntries<Value = string> = MenuListEntries<Value>[];
|
export type SectionsOfMenuListEntries<Value = string> = MenuListEntries<Value>[];
|
||||||
@@ -162,13 +160,13 @@ const KEYBOARD_LOCK_SWITCH_BROWSER = "This hotkey is reserved by the browser, bu
|
|||||||
const MenuList = defineComponent({
|
const MenuList = defineComponent({
|
||||||
inject: ["fullscreen"],
|
inject: ["fullscreen"],
|
||||||
props: {
|
props: {
|
||||||
direction: { type: String as PropType<MenuDirection>, default: MenuDirection.Bottom },
|
direction: { type: String as PropType<MenuDirection>, default: "Bottom" },
|
||||||
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 },
|
||||||
defaultAction: { type: Function as PropType<() => void | undefined>, required: false },
|
defaultAction: { type: Function as PropType<() => void>, required: false },
|
||||||
minWidth: { type: Number, default: 0 },
|
minWidth: { type: Number as PropType<number>, default: 0 },
|
||||||
drawIcon: { type: Boolean, default: false },
|
drawIcon: { type: Boolean as PropType<boolean>, default: false },
|
||||||
scrollable: { type: Boolean, default: false },
|
scrollable: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu) {
|
setEntryRefs(menuEntry: MenuListEntry, ref: typeof FloatingMenu) {
|
||||||
@@ -231,7 +229,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();
|
||||||
|
|
||||||
this.$emit("width-changed", width);
|
this.$emit("widthChanged", width);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -265,10 +263,6 @@ const MenuList = defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
keyboardLockInfoMessage: this.fullscreen.keyboardLockApiSupported ? KEYBOARD_LOCK_USE_FULLSCREEN : KEYBOARD_LOCK_SWITCH_BROWSER,
|
keyboardLockInfoMessage: this.fullscreen.keyboardLockApiSupported ? KEYBOARD_LOCK_USE_FULLSCREEN : KEYBOARD_LOCK_SWITCH_BROWSER,
|
||||||
SeparatorDirection,
|
|
||||||
SeparatorType,
|
|
||||||
MenuDirection,
|
|
||||||
MenuType,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -80,9 +80,9 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel, { IconName } from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
@@ -96,9 +96,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
checked: { type: Boolean, required: true },
|
checked: { type: Boolean as PropType<boolean>, required: true },
|
||||||
icon: { type: String, default: "Checkmark" },
|
icon: { type: String as PropType<IconName>, default: "Checkmark" },
|
||||||
outlineStyle: { type: Boolean, default: false },
|
outlineStyle: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dropdown-input">
|
<div class="dropdown-input">
|
||||||
<div class="dropdown-box" :class="{ disabled }" :style="{ minWidth: `${minWidth}px`, disabled: 'disabled' }" @click="clickDropdownBox" data-hover-menu-spawner>
|
<div class="dropdown-box" :class="{ disabled }" :style="{ minWidth: `${minWidth}px`, disabled: 'disabled' }" @click="() => clickDropdownBox()" data-hover-menu-spawner>
|
||||||
<IconLabel :class="'dropdown-icon'" :icon="activeEntry.icon" v-if="activeEntry.icon" />
|
<IconLabel :class="'dropdown-icon'" :icon="activeEntry.icon" v-if="activeEntry.icon" />
|
||||||
<span>{{ activeEntry.label }}</span>
|
<span>{{ activeEntry.label }}</span>
|
||||||
<IconLabel :class="'dropdown-arrow'" :icon="'DropdownArrow'" />
|
<IconLabel :class="'dropdown-arrow'" :icon="'DropdownArrow'" />
|
||||||
</div>
|
</div>
|
||||||
<MenuList
|
<MenuList
|
||||||
v-model:active-entry="activeEntry"
|
v-model:activeEntry="activeEntry"
|
||||||
@update:activeEntry="activeEntryChanged"
|
@update:activeEntry="(newActiveEntry) => activeEntryChanged(newActiveEntry)"
|
||||||
@width-changed="onWidthChanged"
|
@widthChanged="(newWidth) => onWidthChanged(newWidth)"
|
||||||
:menuEntries="menuEntries"
|
:menuEntries="menuEntries"
|
||||||
:direction="MenuDirection.Bottom"
|
:direction="'Bottom'"
|
||||||
:drawIcon="drawIcon"
|
:drawIcon="drawIcon"
|
||||||
:scrollable="true"
|
:scrollable="true"
|
||||||
ref="menuList"
|
ref="menuList"
|
||||||
@@ -90,21 +90,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|
||||||
import MenuList, { MenuListEntry, SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
import MenuList, { MenuListEntry, SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
menuEntries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||||
selectedIndex: { type: Number, required: true },
|
selectedIndex: { type: Number as PropType<number>, required: true },
|
||||||
drawIcon: { type: Boolean, default: false },
|
drawIcon: { type: Boolean as PropType<boolean>, default: false },
|
||||||
disabled: { type: Boolean, default: false },
|
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeEntry: this.menuEntries.flat()[this.selectedIndex],
|
activeEntry: this.menuEntries.flat()[this.selectedIndex],
|
||||||
MenuDirection,
|
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<IconLabel :icon="entry.icon" v-if="entry.icon" />
|
<IconLabel :icon="entry.icon" v-if="entry.icon" />
|
||||||
<span v-if="entry.label">{{ entry.label }}</span>
|
<span v-if="entry.label">{{ entry.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
<MenuList :menuEntries="entry.children" :direction="MenuDirection.Bottom" :minWidth="240" :drawIcon="true" :defaultAction="comingSoon" :ref="(ref) => setEntryRefs(entry, ref)" />
|
<MenuList :menuEntries="entry.children" :direction="'Bottom'" :minWidth="240" :drawIcon="true" :defaultAction="comingSoon" :ref="(ref) => setEntryRefs(entry, ref)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -53,12 +53,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|
||||||
import { ApplicationPlatform } from "@/components/window/MainWindow.vue";
|
|
||||||
import MenuList, { MenuListEntry, MenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
|
||||||
import { EditorState } from "@/state/wasm-loader";
|
import { EditorState } from "@/state/wasm-loader";
|
||||||
|
|
||||||
|
import MenuList, { MenuListEntry, MenuListEntries } from "@/components/widgets/floating-menus/MenuList.vue";
|
||||||
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
function makeMenuEntries(editor: EditorState): MenuListEntries {
|
function makeMenuEntries(editor: EditorState): MenuListEntries {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -66,8 +65,8 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
ref: undefined,
|
ref: undefined,
|
||||||
children: [
|
children: [
|
||||||
[
|
[
|
||||||
{ label: "New", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: async () => editor.instance.new_document() },
|
{ label: "New", icon: "File", shortcut: ["KeyControl", "KeyN"], shortcutRequiresLock: true, action: async (): Promise<void> => editor.instance.new_document() },
|
||||||
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: async () => editor.instance.open_document() },
|
{ label: "Open…", shortcut: ["KeyControl", "KeyO"], action: async (): Promise<void> => editor.instance.open_document() },
|
||||||
{
|
{
|
||||||
label: "Open Recent",
|
label: "Open Recent",
|
||||||
shortcut: ["KeyControl", "KeyShift", "KeyO"],
|
shortcut: ["KeyControl", "KeyShift", "KeyO"],
|
||||||
@@ -84,18 +83,18 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Close", shortcut: ["KeyControl", "KeyW"], shortcutRequiresLock: true, action: async () => editor.instance.close_active_document_with_confirmation() },
|
{ label: "Close", shortcut: ["KeyControl", "KeyW"], shortcutRequiresLock: true, action: async (): Promise<void> => editor.instance.close_active_document_with_confirmation() },
|
||||||
{ label: "Close All", shortcut: ["KeyControl", "KeyAlt", "KeyW"], action: async () => editor.instance.close_all_documents_with_confirmation() },
|
{ label: "Close All", shortcut: ["KeyControl", "KeyAlt", "KeyW"], action: async (): Promise<void> => editor.instance.close_all_documents_with_confirmation() },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Save", shortcut: ["KeyControl", "KeyS"], action: async () => editor.instance.save_document() },
|
{ label: "Save", shortcut: ["KeyControl", "KeyS"], action: async (): Promise<void> => editor.instance.save_document() },
|
||||||
{ label: "Save As…", shortcut: ["KeyControl", "KeyShift", "KeyS"], action: async () => editor.instance.save_document() },
|
{ label: "Save As…", shortcut: ["KeyControl", "KeyShift", "KeyS"], action: async (): Promise<void> => editor.instance.save_document() },
|
||||||
{ label: "Save All", shortcut: ["KeyControl", "KeyAlt", "KeyS"] },
|
{ label: "Save All", shortcut: ["KeyControl", "KeyAlt", "KeyS"] },
|
||||||
{ label: "Auto-Save", checkbox: true, checked: true },
|
{ label: "Auto-Save", checkbox: true, checked: true },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Import…", shortcut: ["KeyControl", "KeyI"] },
|
{ label: "Import…", shortcut: ["KeyControl", "KeyI"] },
|
||||||
{ label: "Export…", shortcut: ["KeyControl", "KeyE"], action: async () => editor.instance.export_document() },
|
{ label: "Export…", shortcut: ["KeyControl", "KeyE"], action: async (): Promise<void> => editor.instance.export_document() },
|
||||||
],
|
],
|
||||||
[{ label: "Quit", shortcut: ["KeyControl", "KeyQ"] }],
|
[{ label: "Quit", shortcut: ["KeyControl", "KeyQ"] }],
|
||||||
],
|
],
|
||||||
@@ -105,13 +104,13 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
ref: undefined,
|
ref: undefined,
|
||||||
children: [
|
children: [
|
||||||
[
|
[
|
||||||
{ label: "Undo", shortcut: ["KeyControl", "KeyZ"], action: async () => editor.instance.undo() },
|
{ label: "Undo", shortcut: ["KeyControl", "KeyZ"], action: async (): Promise<void> => editor.instance.undo() },
|
||||||
{ label: "Redo", shortcut: ["KeyControl", "KeyShift", "KeyZ"], action: async () => editor.instance.redo() },
|
{ label: "Redo", shortcut: ["KeyControl", "KeyShift", "KeyZ"], action: async (): Promise<void> => editor.instance.redo() },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Cut", shortcut: ["KeyControl", "KeyX"], action: async () => editor.instance.cut() },
|
{ label: "Cut", shortcut: ["KeyControl", "KeyX"], action: async (): Promise<void> => editor.instance.cut() },
|
||||||
{ label: "Copy", icon: "Copy", shortcut: ["KeyControl", "KeyC"], action: async () => editor.instance.copy() },
|
{ label: "Copy", icon: "Copy", shortcut: ["KeyControl", "KeyC"], action: async (): Promise<void> => editor.instance.copy() },
|
||||||
{ label: "Paste", icon: "Paste", shortcut: ["KeyControl", "KeyV"], action: async () => editor.instance.paste() },
|
{ label: "Paste", icon: "Paste", shortcut: ["KeyControl", "KeyV"], action: async (): Promise<void> => editor.instance.paste() },
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -120,8 +119,8 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
ref: undefined,
|
ref: undefined,
|
||||||
children: [
|
children: [
|
||||||
[
|
[
|
||||||
{ label: "Select All", shortcut: ["KeyControl", "KeyA"], action: async () => editor.instance.select_all_layers() },
|
{ label: "Select All", shortcut: ["KeyControl", "KeyA"], action: async (): Promise<void> => editor.instance.select_all_layers() },
|
||||||
{ label: "Deselect All", shortcut: ["KeyControl", "KeyAlt", "KeyA"], action: async () => editor.instance.deselect_all_layers() },
|
{ label: "Deselect All", shortcut: ["KeyControl", "KeyAlt", "KeyA"], action: async (): Promise<void> => editor.instance.deselect_all_layers() },
|
||||||
{
|
{
|
||||||
label: "Order",
|
label: "Order",
|
||||||
children: [
|
children: [
|
||||||
@@ -129,14 +128,14 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
{
|
{
|
||||||
label: "Raise To Front",
|
label: "Raise To Front",
|
||||||
shortcut: ["KeyControl", "KeyShift", "KeyLeftBracket"],
|
shortcut: ["KeyControl", "KeyShift", "KeyLeftBracket"],
|
||||||
action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_max()),
|
action: async (): Promise<void> => editor.instance.reorder_selected_layers(editor.rawWasm.i32_max()),
|
||||||
},
|
},
|
||||||
{ label: "Raise", shortcut: ["KeyControl", "KeyRightBracket"], action: async () => editor.instance.reorder_selected_layers(1) },
|
{ label: "Raise", shortcut: ["KeyControl", "KeyRightBracket"], action: async (): Promise<void> => editor.instance.reorder_selected_layers(1) },
|
||||||
{ label: "Lower", shortcut: ["KeyControl", "KeyLeftBracket"], action: async () => editor.instance.reorder_selected_layers(-1) },
|
{ label: "Lower", shortcut: ["KeyControl", "KeyLeftBracket"], action: async (): Promise<void> => editor.instance.reorder_selected_layers(-1) },
|
||||||
{
|
{
|
||||||
label: "Lower to Back",
|
label: "Lower to Back",
|
||||||
shortcut: ["KeyControl", "KeyShift", "KeyRightBracket"],
|
shortcut: ["KeyControl", "KeyShift", "KeyRightBracket"],
|
||||||
action: async () => editor.instance.reorder_selected_layers(editor.rawWasm.i32_min()),
|
action: async (): Promise<void> => editor.instance.reorder_selected_layers(editor.rawWasm.i32_min()),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -158,12 +157,12 @@ function makeMenuEntries(editor: EditorState): MenuListEntries {
|
|||||||
label: "Help",
|
label: "Help",
|
||||||
ref: undefined,
|
ref: undefined,
|
||||||
children: [
|
children: [
|
||||||
[{ label: "About Graphite", action: async () => editor.instance.request_about_graphite_dialog() }],
|
[{ label: "About Graphite", action: async (): Promise<void> => editor.instance.request_about_graphite_dialog() }],
|
||||||
[
|
[
|
||||||
{ label: "Report a Bug", action: () => window.open("https://github.com/GraphiteEditor/Graphite/issues/new", "_blank") },
|
{ label: "Report a Bug", action: (): unknown => window.open("https://github.com/GraphiteEditor/Graphite/issues/new", "_blank") },
|
||||||
{ label: "Visit on GitHub", action: () => window.open("https://github.com/GraphiteEditor/Graphite", "_blank") },
|
{ label: "Visit on GitHub", action: (): unknown => window.open("https://github.com/GraphiteEditor/Graphite", "_blank") },
|
||||||
],
|
],
|
||||||
[{ label: "Debug: Panic (DANGER)", action: async () => editor.rawWasm.intentional_panic() }],
|
[{ label: "Debug: Panic (DANGER)", action: async (): Promise<void> => editor.rawWasm.intentional_panic() }],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -186,10 +185,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ApplicationPlatform,
|
|
||||||
menuEntries: makeMenuEntries(this.editor),
|
menuEntries: makeMenuEntries(this.editor),
|
||||||
MenuDirection,
|
comingSoon: (): void => this.dialog.comingSoon(),
|
||||||
comingSoon: () => this.dialog.comingSoon(),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
/>
|
/>
|
||||||
<label v-if="label" :for="`number-input-${id}`">{{ label }}</label>
|
<label v-if="label" :for="`number-input-${id}`">{{ label }}</label>
|
||||||
<button v-if="!Number.isNaN(value)" class="arrow left" @click="onIncrement(IncrementDirection.Decrease)"></button>
|
<button v-if="!Number.isNaN(value)" class="arrow left" @click="onIncrement('Decrease')"></button>
|
||||||
<button v-if="!Number.isNaN(value)" class="arrow right" @click="onIncrement(IncrementDirection.Increase)"></button>
|
<button v-if="!Number.isNaN(value)" class="arrow right" @click="onIncrement('Increase')"></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -152,40 +152,29 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export enum IncrementBehavior {
|
export type IncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
|
||||||
Add = "Add",
|
export type IncrementDirection = "Decrease" | "Increase";
|
||||||
Multiply = "Multiply",
|
|
||||||
Callback = "Callback",
|
|
||||||
None = "None",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum IncrementDirection {
|
|
||||||
Decrease = "Decrease",
|
|
||||||
Increase = "Increase",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
value: { type: Number, required: true },
|
value: { type: Number as PropType<number>, required: true },
|
||||||
min: { type: Number, required: false },
|
min: { type: Number as PropType<number>, required: false },
|
||||||
max: { type: Number, required: false },
|
max: { type: Number as PropType<number>, required: false },
|
||||||
incrementBehavior: { type: String as PropType<IncrementBehavior>, default: IncrementBehavior.Add },
|
incrementBehavior: { type: String as PropType<IncrementBehavior>, default: "Add" },
|
||||||
incrementFactor: { type: Number, default: 1 },
|
incrementFactor: { type: Number as PropType<number>, default: 1 },
|
||||||
incrementCallbackIncrease: { type: Function, required: false },
|
incrementCallbackIncrease: { type: Function as PropType<() => void>, required: false },
|
||||||
incrementCallbackDecrease: { type: Function, required: false },
|
incrementCallbackDecrease: { type: Function as PropType<() => void>, required: false },
|
||||||
isInteger: { type: Boolean, default: false },
|
isInteger: { type: Boolean as PropType<boolean>, default: false },
|
||||||
unit: { type: String, default: "" },
|
unit: { type: String as PropType<string>, default: "" },
|
||||||
unitIsHiddenWhenEditing: { type: Boolean, default: true },
|
unitIsHiddenWhenEditing: { type: Boolean as PropType<boolean>, default: true },
|
||||||
displayDecimalPlaces: { type: Number, default: 3 },
|
displayDecimalPlaces: { type: Number as PropType<number>, default: 3 },
|
||||||
label: { type: String, required: false },
|
label: { type: String as PropType<string>, required: false },
|
||||||
disabled: { type: Boolean, default: false },
|
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
text: `${this.value}${this.unit}`,
|
text: `${this.value}${this.unit}`,
|
||||||
editing: false,
|
editing: false,
|
||||||
IncrementDirection,
|
|
||||||
id: `${Math.random()}`.substring(2),
|
id: `${Math.random()}`.substring(2),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -225,19 +214,19 @@ export default defineComponent({
|
|||||||
if (Number.isNaN(this.value)) return;
|
if (Number.isNaN(this.value)) return;
|
||||||
|
|
||||||
switch (this.incrementBehavior) {
|
switch (this.incrementBehavior) {
|
||||||
case IncrementBehavior.Add: {
|
case "Add": {
|
||||||
const directionAddend = direction === IncrementDirection.Increase ? this.incrementFactor : -this.incrementFactor;
|
const directionAddend = direction === "Increase" ? this.incrementFactor : -this.incrementFactor;
|
||||||
this.updateValue(this.value + directionAddend);
|
this.updateValue(this.value + directionAddend);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IncrementBehavior.Multiply: {
|
case "Multiply": {
|
||||||
const directionMultiplier = direction === IncrementDirection.Increase ? this.incrementFactor : 1 / this.incrementFactor;
|
const directionMultiplier = direction === "Increase" ? this.incrementFactor : 1 / this.incrementFactor;
|
||||||
this.updateValue(this.value * directionMultiplier);
|
this.updateValue(this.value * directionMultiplier);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IncrementBehavior.Callback: {
|
case "Callback": {
|
||||||
if (direction === IncrementDirection.Increase && this.incrementCallbackIncrease) this.incrementCallbackIncrease();
|
if (direction === "Increase" && this.incrementCallbackIncrease) this.incrementCallbackIncrease();
|
||||||
if (direction === IncrementDirection.Decrease && this.incrementCallbackDecrease) this.incrementCallbackDecrease();
|
if (direction === "Decrease" && this.incrementCallbackDecrease) this.incrementCallbackDecrease();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -34,14 +34,15 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
|
import CheckboxInput from "@/components/widgets/inputs/CheckboxInput.vue";
|
||||||
|
import { IconName } from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
checked: { type: Boolean, required: true },
|
checked: { type: Boolean as PropType<boolean>, required: true },
|
||||||
icon: { type: String, default: "Checkmark" },
|
icon: { type: String as PropType<IconName>, default: "Checkmark" },
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
CheckboxInput,
|
CheckboxInput,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export type RadioEntries = RadioEntryData[];
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
entries: { type: Array as PropType<RadioEntries>, required: true },
|
entries: { type: Array as PropType<RadioEntries>, required: true },
|
||||||
selectedIndex: { type: Number, required: true },
|
selectedIndex: { type: Number as PropType<number>, required: true },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleEntryClick(menuEntry: RadioEntryData) {
|
handleEntryClick(menuEntry: RadioEntryData) {
|
||||||
|
|||||||
@@ -29,16 +29,17 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
|
import { IconName } from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { IconButton },
|
components: { IconButton },
|
||||||
props: {
|
props: {
|
||||||
icon: { type: String, required: true },
|
icon: { type: String as PropType<IconName>, required: true },
|
||||||
action: { type: Function, required: true },
|
action: { type: Function as PropType<(e?: MouseEvent) => void>, required: true },
|
||||||
active: { type: Boolean, default: false },
|
active: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="swatch-pair">
|
<div class="swatch-pair">
|
||||||
<div class="secondary swatch">
|
<div class="secondary swatch">
|
||||||
<button @click="clickSecondarySwatch" ref="secondaryButton" data-hover-menu-spawner></button>
|
<button @click="() => clickSecondarySwatch()" ref="secondaryButton" data-hover-menu-spawner></button>
|
||||||
<FloatingMenu :type="MenuType.Popover" :direction="MenuDirection.Right" horizontal ref="secondarySwatchFloatingMenu">
|
<FloatingMenu :type="'Popover'" :direction="'Right'" horizontal ref="secondarySwatchFloatingMenu">
|
||||||
<ColorPicker @update:color="secondaryColorChanged" :color="secondaryColor" />
|
<ColorPicker @update:color="(color) => secondaryColorChanged(color)" :color="secondaryColor" />
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
</div>
|
</div>
|
||||||
<div class="primary swatch">
|
<div class="primary swatch">
|
||||||
<button @click="clickPrimarySwatch" ref="primaryButton" data-hover-menu-spawner></button>
|
<button @click="() => clickPrimarySwatch()" ref="primaryButton" data-hover-menu-spawner></button>
|
||||||
<FloatingMenu :type="MenuType.Popover" :direction="MenuDirection.Right" horizontal ref="primarySwatchFloatingMenu">
|
<FloatingMenu :type="'Popover'" :direction="'Right'" horizontal ref="primarySwatchFloatingMenu">
|
||||||
<ColorPicker @update:color="primaryColorChanged" :color="primaryColor" />
|
<ColorPicker @update:color="(color) => primaryColorChanged(color)" :color="primaryColor" />
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,11 +68,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { rgbToDecimalRgb, RGB } from "@/utilities/color";
|
import { RGBA, UpdateWorkingColors } from "@/dispatcher/js-messages";
|
||||||
|
import { rgbaToDecimalRgba } from "@/utilities/color";
|
||||||
|
|
||||||
import ColorPicker from "@/components/widgets/floating-menus/ColorPicker.vue";
|
import ColorPicker from "@/components/widgets/floating-menus/ColorPicker.vue";
|
||||||
import FloatingMenu, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
import FloatingMenu from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
import { UpdateWorkingColors } from "@/dispatcher/js-messages";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["editor"],
|
inject: ["editor"],
|
||||||
@@ -80,70 +80,56 @@ export default defineComponent({
|
|||||||
FloatingMenu,
|
FloatingMenu,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
},
|
},
|
||||||
props: {},
|
|
||||||
methods: {
|
methods: {
|
||||||
clickPrimarySwatch() {
|
clickPrimarySwatch() {
|
||||||
this.getRef<typeof FloatingMenu>("primarySwatchFloatingMenu").setOpen();
|
(this.$refs.primarySwatchFloatingMenu as typeof FloatingMenu).setOpen();
|
||||||
this.getRef<typeof FloatingMenu>("secondarySwatchFloatingMenu").setClosed();
|
(this.$refs.secondarySwatchFloatingMenu as typeof FloatingMenu).setClosed();
|
||||||
},
|
},
|
||||||
|
|
||||||
clickSecondarySwatch() {
|
clickSecondarySwatch() {
|
||||||
this.getRef<typeof FloatingMenu>("secondarySwatchFloatingMenu").setOpen();
|
(this.$refs.secondarySwatchFloatingMenu as typeof FloatingMenu).setOpen();
|
||||||
this.getRef<typeof FloatingMenu>("primarySwatchFloatingMenu").setClosed();
|
(this.$refs.primarySwatchFloatingMenu as typeof FloatingMenu).setClosed();
|
||||||
},
|
},
|
||||||
|
primaryColorChanged(color: RGBA) {
|
||||||
getRef<T>(name: string) {
|
|
||||||
return this.$refs[name] as T;
|
|
||||||
},
|
|
||||||
|
|
||||||
primaryColorChanged(color: RGB) {
|
|
||||||
this.primaryColor = color;
|
this.primaryColor = color;
|
||||||
this.updatePrimaryColor();
|
this.updatePrimaryColor();
|
||||||
},
|
},
|
||||||
|
secondaryColorChanged(color: RGBA) {
|
||||||
secondaryColorChanged(color: RGB) {
|
|
||||||
this.secondaryColor = color;
|
this.secondaryColor = color;
|
||||||
this.updateSecondaryColor();
|
this.updateSecondaryColor();
|
||||||
},
|
},
|
||||||
|
|
||||||
async updatePrimaryColor() {
|
async updatePrimaryColor() {
|
||||||
let color = this.primaryColor;
|
let color = this.primaryColor;
|
||||||
const button = this.getRef<HTMLButtonElement>("primaryButton");
|
const button = this.$refs.primaryButton as HTMLButtonElement;
|
||||||
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
||||||
|
|
||||||
color = rgbToDecimalRgb(this.primaryColor);
|
color = rgbaToDecimalRgba(this.primaryColor);
|
||||||
this.editor.instance.update_primary_color(color.r, color.g, color.b, color.a);
|
this.editor.instance.update_primary_color(color.r, color.g, color.b, color.a);
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateSecondaryColor() {
|
async updateSecondaryColor() {
|
||||||
let color = this.secondaryColor;
|
let color = this.secondaryColor;
|
||||||
const button = this.getRef<HTMLButtonElement>("secondaryButton");
|
const button = this.$refs.secondaryButton as HTMLButtonElement;
|
||||||
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
||||||
|
|
||||||
color = rgbToDecimalRgb(this.secondaryColor);
|
color = rgbaToDecimalRgba(this.secondaryColor);
|
||||||
this.editor.instance.update_secondary_color(color.r, color.g, color.b, color.a);
|
this.editor.instance.update_secondary_color(color.r, color.g, color.b, color.a);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
MenuDirection,
|
primaryColor: { r: 0, g: 0, b: 0, a: 1 } as RGBA,
|
||||||
MenuType,
|
secondaryColor: { r: 255, g: 255, b: 255, a: 1 } as RGBA,
|
||||||
primaryColor: { r: 0, g: 0, b: 0, a: 1 },
|
|
||||||
secondaryColor: { r: 255, g: 255, b: 255, a: 1 },
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.editor.dispatcher.subscribeJsMessage(UpdateWorkingColors, (updateWorkingColors) => {
|
this.editor.dispatcher.subscribeJsMessage(UpdateWorkingColors, (updateWorkingColors) => {
|
||||||
const { primary, secondary } = updateWorkingColors;
|
this.primaryColor = updateWorkingColors.primary.toRgba();
|
||||||
|
this.secondaryColor = updateWorkingColors.secondary.toRgba();
|
||||||
|
|
||||||
this.primaryColor = primary.toRgba();
|
const primaryButton = this.$refs.primaryButton as HTMLButtonElement;
|
||||||
this.secondaryColor = secondary.toRgba();
|
primaryButton.style.setProperty("--swatch-color", updateWorkingColors.primary.toRgbaCSS());
|
||||||
|
|
||||||
const primaryButton = this.getRef<HTMLButtonElement>("primaryButton");
|
const secondaryButton = this.$refs.secondaryButton as HTMLButtonElement;
|
||||||
primaryButton.style.setProperty("--swatch-color", primary.toRgbaCSS());
|
secondaryButton.style.setProperty("--swatch-color", updateWorkingColors.secondary.toRgbaCSS());
|
||||||
|
|
||||||
const secondaryButton = this.getRef<HTMLButtonElement>("secondaryButton");
|
|
||||||
secondaryButton.style.setProperty("--swatch-color", secondary.toRgbaCSS());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updatePrimaryColor();
|
this.updatePrimaryColor();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="icon-label" :class="`size-${String(icons[icon].size)}`">
|
<div class="icon-label" :class="`size-${icons[icon].size}`">
|
||||||
<component :is="icon" />
|
<component :is="icon" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -28,81 +28,19 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { DefineComponent, defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import LayoutSelectTool from "@/../assets/24px-two-tone/layout-select-tool.svg";
|
|
||||||
import LayoutCropTool from "@/../assets/24px-two-tone/layout-crop-tool.svg";
|
|
||||||
import LayoutNavigateTool from "@/../assets/24px-two-tone/layout-navigate-tool.svg";
|
|
||||||
import LayoutEyedropperTool from "@/../assets/24px-two-tone/layout-eyedropper-tool.svg";
|
|
||||||
import ParametricTextTool from "@/../assets/24px-two-tone/parametric-text-tool.svg";
|
|
||||||
import ParametricFillTool from "@/../assets/24px-two-tone/parametric-fill-tool.svg";
|
|
||||||
import ParametricGradientTool from "@/../assets/24px-two-tone/parametric-gradient-tool.svg";
|
|
||||||
import RasterBrushTool from "@/../assets/24px-two-tone/raster-brush-tool.svg";
|
|
||||||
import RasterHealTool from "@/../assets/24px-two-tone/raster-heal-tool.svg";
|
|
||||||
import RasterCloneTool from "@/../assets/24px-two-tone/raster-clone-tool.svg";
|
|
||||||
import RasterPatchTool from "@/../assets/24px-two-tone/raster-patch-tool.svg";
|
|
||||||
import RasterBlurSharpenTool from "@/../assets/24px-two-tone/raster-detail-tool.svg";
|
|
||||||
import RasterRelightTool from "@/../assets/24px-two-tone/raster-relight-tool.svg";
|
|
||||||
import VectorPathTool from "@/../assets/24px-two-tone/vector-path-tool.svg";
|
|
||||||
import VectorPenTool from "@/../assets/24px-two-tone/vector-pen-tool.svg";
|
|
||||||
import VectorFreehandTool from "@/../assets/24px-two-tone/vector-freehand-tool.svg";
|
|
||||||
import VectorSplineTool from "@/../assets/24px-two-tone/vector-spline-tool.svg";
|
|
||||||
import VectorLineTool from "@/../assets/24px-two-tone/vector-line-tool.svg";
|
|
||||||
import VectorRectangleTool from "@/../assets/24px-two-tone/vector-rectangle-tool.svg";
|
|
||||||
import VectorEllipseTool from "@/../assets/24px-two-tone/vector-ellipse-tool.svg";
|
|
||||||
import VectorShapeTool from "@/../assets/24px-two-tone/vector-shape-tool.svg";
|
|
||||||
|
|
||||||
import AlignLeft from "@/../assets/16px-solid/align-left.svg";
|
|
||||||
import AlignHorizontalCenter from "@/../assets/16px-solid/align-horizontal-center.svg";
|
|
||||||
import AlignRight from "@/../assets/16px-solid/align-right.svg";
|
|
||||||
import AlignTop from "@/../assets/16px-solid/align-top.svg";
|
|
||||||
import AlignVerticalCenter from "@/../assets/16px-solid/align-vertical-center.svg";
|
|
||||||
import AlignBottom from "@/../assets/16px-solid/align-bottom.svg";
|
|
||||||
import FlipHorizontal from "@/../assets/16px-solid/flip-horizontal.svg";
|
|
||||||
import FlipVertical from "@/../assets/16px-solid/flip-vertical.svg";
|
|
||||||
import BooleanUnion from "@/../assets/16px-solid/boolean-union.svg";
|
|
||||||
import BooleanSubtractFront from "@/../assets/16px-solid/boolean-subtract-front.svg";
|
|
||||||
import BooleanSubtractBack from "@/../assets/16px-solid/boolean-subtract-back.svg";
|
|
||||||
import BooleanIntersect from "@/../assets/16px-solid/boolean-intersect.svg";
|
|
||||||
import BooleanDifference from "@/../assets/16px-solid/boolean-difference.svg";
|
|
||||||
import ZoomReset from "@/../assets/16px-solid/zoom-reset.svg";
|
|
||||||
import ZoomIn from "@/../assets/16px-solid/zoom-in.svg";
|
|
||||||
import ZoomOut from "@/../assets/16px-solid/zoom-out.svg";
|
|
||||||
import ViewModeNormal from "@/../assets/16px-solid/view-mode-normal.svg";
|
|
||||||
import ViewModeOutline from "@/../assets/16px-solid/view-mode-outline.svg";
|
|
||||||
import ViewModePixels from "@/../assets/16px-solid/view-mode-pixels.svg";
|
|
||||||
import EyeVisible from "@/../assets/16px-solid/eye-visible.svg";
|
|
||||||
import EyeHidden from "@/../assets/16px-solid/eye-hidden.svg";
|
|
||||||
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 ViewportGuideMode from "@/../assets/16px-solid/viewport-guide-mode.svg";
|
|
||||||
|
|
||||||
import Checkmark from "@/../assets/12px-solid/checkmark.svg";
|
import Checkmark from "@/../assets/12px-solid/checkmark.svg";
|
||||||
import Link from "@/../assets/12px-solid/link.svg";
|
|
||||||
import Grid from "@/../assets/12px-solid/grid.svg";
|
|
||||||
import Overlays from "@/../assets/12px-solid/overlays.svg";
|
|
||||||
import Snapping from "@/../assets/12px-solid/snapping.svg";
|
|
||||||
import Info from "@/../assets/12px-solid/info.svg";
|
|
||||||
import Warning from "@/../assets/12px-solid/warning.svg";
|
|
||||||
import Swap from "@/../assets/12px-solid/swap.svg";
|
|
||||||
import ResetColors from "@/../assets/12px-solid/reset-colors.svg";
|
|
||||||
import DropdownArrow from "@/../assets/12px-solid/dropdown-arrow.svg";
|
|
||||||
import VerticalEllipsis from "@/../assets/12px-solid/vertical-ellipsis.svg";
|
|
||||||
import CloseX from "@/../assets/12px-solid/close-x.svg";
|
import CloseX from "@/../assets/12px-solid/close-x.svg";
|
||||||
|
import DropdownArrow from "@/../assets/12px-solid/dropdown-arrow.svg";
|
||||||
import FullscreenEnter from "@/../assets/12px-solid/fullscreen-enter.svg";
|
import FullscreenEnter from "@/../assets/12px-solid/fullscreen-enter.svg";
|
||||||
import FullscreenExit from "@/../assets/12px-solid/fullscreen-exit.svg";
|
import FullscreenExit from "@/../assets/12px-solid/fullscreen-exit.svg";
|
||||||
import WindowButtonWinMinimize from "@/../assets/12px-solid/window-button-win-minimize.svg";
|
import Grid from "@/../assets/12px-solid/grid.svg";
|
||||||
import WindowButtonWinMaximize from "@/../assets/12px-solid/window-button-win-maximize.svg";
|
import Info from "@/../assets/12px-solid/info.svg";
|
||||||
import WindowButtonWinRestoreDown from "@/../assets/12px-solid/window-button-win-restore-down.svg";
|
|
||||||
import WindowButtonWinClose from "@/../assets/12px-solid/window-button-win-close.svg";
|
|
||||||
import KeyboardArrowUp from "@/../assets/12px-solid/keyboard-arrow-up.svg";
|
|
||||||
import KeyboardArrowRight from "@/../assets/12px-solid/keyboard-arrow-right.svg";
|
|
||||||
import KeyboardArrowDown from "@/../assets/12px-solid/keyboard-arrow-down.svg";
|
import KeyboardArrowDown from "@/../assets/12px-solid/keyboard-arrow-down.svg";
|
||||||
import KeyboardArrowLeft from "@/../assets/12px-solid/keyboard-arrow-left.svg";
|
import KeyboardArrowLeft from "@/../assets/12px-solid/keyboard-arrow-left.svg";
|
||||||
|
import KeyboardArrowRight from "@/../assets/12px-solid/keyboard-arrow-right.svg";
|
||||||
|
import KeyboardArrowUp from "@/../assets/12px-solid/keyboard-arrow-up.svg";
|
||||||
import KeyboardBackspace from "@/../assets/12px-solid/keyboard-backspace.svg";
|
import KeyboardBackspace from "@/../assets/12px-solid/keyboard-backspace.svg";
|
||||||
import KeyboardCommand from "@/../assets/12px-solid/keyboard-command.svg";
|
import KeyboardCommand from "@/../assets/12px-solid/keyboard-command.svg";
|
||||||
import KeyboardEnter from "@/../assets/12px-solid/keyboard-enter.svg";
|
import KeyboardEnter from "@/../assets/12px-solid/keyboard-enter.svg";
|
||||||
@@ -110,124 +48,199 @@ import KeyboardOption from "@/../assets/12px-solid/keyboard-option.svg";
|
|||||||
import KeyboardShift from "@/../assets/12px-solid/keyboard-shift.svg";
|
import KeyboardShift from "@/../assets/12px-solid/keyboard-shift.svg";
|
||||||
import KeyboardSpace from "@/../assets/12px-solid/keyboard-space.svg";
|
import KeyboardSpace from "@/../assets/12px-solid/keyboard-space.svg";
|
||||||
import KeyboardTab from "@/../assets/12px-solid/keyboard-tab.svg";
|
import KeyboardTab from "@/../assets/12px-solid/keyboard-tab.svg";
|
||||||
|
import Link from "@/../assets/12px-solid/link.svg";
|
||||||
|
import Overlays from "@/../assets/12px-solid/overlays.svg";
|
||||||
|
import ResetColors from "@/../assets/12px-solid/reset-colors.svg";
|
||||||
|
import Snapping from "@/../assets/12px-solid/snapping.svg";
|
||||||
|
import Swap from "@/../assets/12px-solid/swap.svg";
|
||||||
|
import VerticalEllipsis from "@/../assets/12px-solid/vertical-ellipsis.svg";
|
||||||
|
import Warning from "@/../assets/12px-solid/warning.svg";
|
||||||
|
import WindowButtonWinClose from "@/../assets/12px-solid/window-button-win-close.svg";
|
||||||
|
import WindowButtonWinMaximize from "@/../assets/12px-solid/window-button-win-maximize.svg";
|
||||||
|
import WindowButtonWinMinimize from "@/../assets/12px-solid/window-button-win-minimize.svg";
|
||||||
|
import WindowButtonWinRestoreDown from "@/../assets/12px-solid/window-button-win-restore-down.svg";
|
||||||
|
|
||||||
|
import AlignBottom from "@/../assets/16px-solid/align-bottom.svg";
|
||||||
|
import AlignHorizontalCenter from "@/../assets/16px-solid/align-horizontal-center.svg";
|
||||||
|
import AlignLeft from "@/../assets/16px-solid/align-left.svg";
|
||||||
|
import AlignRight from "@/../assets/16px-solid/align-right.svg";
|
||||||
|
import AlignTop from "@/../assets/16px-solid/align-top.svg";
|
||||||
|
import AlignVerticalCenter from "@/../assets/16px-solid/align-vertical-center.svg";
|
||||||
|
import BooleanDifference from "@/../assets/16px-solid/boolean-difference.svg";
|
||||||
|
import BooleanIntersect from "@/../assets/16px-solid/boolean-intersect.svg";
|
||||||
|
import BooleanSubtractBack from "@/../assets/16px-solid/boolean-subtract-back.svg";
|
||||||
|
import BooleanSubtractFront from "@/../assets/16px-solid/boolean-subtract-front.svg";
|
||||||
|
import BooleanUnion from "@/../assets/16px-solid/boolean-union.svg";
|
||||||
|
import Copy from "@/../assets/16px-solid/copy.svg";
|
||||||
|
import EyeHidden from "@/../assets/16px-solid/eye-hidden.svg";
|
||||||
|
import EyeVisible from "@/../assets/16px-solid/eye-visible.svg";
|
||||||
|
import File from "@/../assets/16px-solid/file.svg";
|
||||||
|
import FlipHorizontal from "@/../assets/16px-solid/flip-horizontal.svg";
|
||||||
|
import FlipVertical from "@/../assets/16px-solid/flip-vertical.svg";
|
||||||
|
import GraphiteLogo from "@/../assets/16px-solid/graphite-logo.svg";
|
||||||
|
import Paste from "@/../assets/16px-solid/paste.svg";
|
||||||
|
import ViewModeNormal from "@/../assets/16px-solid/view-mode-normal.svg";
|
||||||
|
import ViewModeOutline from "@/../assets/16px-solid/view-mode-outline.svg";
|
||||||
|
import ViewModePixels from "@/../assets/16px-solid/view-mode-pixels.svg";
|
||||||
|
import ViewportDesignMode from "@/../assets/16px-solid/viewport-design-mode.svg";
|
||||||
|
import ViewportGuideMode from "@/../assets/16px-solid/viewport-guide-mode.svg";
|
||||||
|
import ViewportSelectMode from "@/../assets/16px-solid/viewport-select-mode.svg";
|
||||||
|
import ZoomIn from "@/../assets/16px-solid/zoom-in.svg";
|
||||||
|
import ZoomOut from "@/../assets/16px-solid/zoom-out.svg";
|
||||||
|
import ZoomReset from "@/../assets/16px-solid/zoom-reset.svg";
|
||||||
|
|
||||||
import MouseHintNone from "@/../assets/16px-two-tone/mouse-hint-none.svg";
|
|
||||||
import MouseHintLmb from "@/../assets/16px-two-tone/mouse-hint-lmb.svg";
|
|
||||||
import MouseHintRmb from "@/../assets/16px-two-tone/mouse-hint-rmb.svg";
|
|
||||||
import MouseHintMmb from "@/../assets/16px-two-tone/mouse-hint-mmb.svg";
|
|
||||||
import MouseHintScrollUp from "@/../assets/16px-two-tone/mouse-hint-scroll-up.svg";
|
|
||||||
import MouseHintScrollDown from "@/../assets/16px-two-tone/mouse-hint-scroll-down.svg";
|
|
||||||
import MouseHintDrag from "@/../assets/16px-two-tone/mouse-hint-drag.svg";
|
import MouseHintDrag from "@/../assets/16px-two-tone/mouse-hint-drag.svg";
|
||||||
import MouseHintLmbDrag from "@/../assets/16px-two-tone/mouse-hint-lmb-drag.svg";
|
import MouseHintLmbDrag from "@/../assets/16px-two-tone/mouse-hint-lmb-drag.svg";
|
||||||
import MouseHintRmbDrag from "@/../assets/16px-two-tone/mouse-hint-rmb-drag.svg";
|
import MouseHintLmb from "@/../assets/16px-two-tone/mouse-hint-lmb.svg";
|
||||||
import MouseHintMmbDrag from "@/../assets/16px-two-tone/mouse-hint-mmb-drag.svg";
|
import MouseHintMmbDrag from "@/../assets/16px-two-tone/mouse-hint-mmb-drag.svg";
|
||||||
|
import MouseHintMmb from "@/../assets/16px-two-tone/mouse-hint-mmb.svg";
|
||||||
|
import MouseHintNone from "@/../assets/16px-two-tone/mouse-hint-none.svg";
|
||||||
|
import MouseHintRmbDrag from "@/../assets/16px-two-tone/mouse-hint-rmb-drag.svg";
|
||||||
|
import MouseHintRmb from "@/../assets/16px-two-tone/mouse-hint-rmb.svg";
|
||||||
|
import MouseHintScrollDown from "@/../assets/16px-two-tone/mouse-hint-scroll-down.svg";
|
||||||
|
import MouseHintScrollUp from "@/../assets/16px-two-tone/mouse-hint-scroll-up.svg";
|
||||||
|
|
||||||
import NodeTypePath from "@/../assets/24px-full-color/node-type-path.svg";
|
|
||||||
import NodeTypeFolder from "@/../assets/24px-full-color/node-type-folder.svg";
|
import NodeTypeFolder from "@/../assets/24px-full-color/node-type-folder.svg";
|
||||||
|
import NodeTypePath from "@/../assets/24px-full-color/node-type-path.svg";
|
||||||
|
|
||||||
const icons = {
|
import LayoutCropTool from "@/../assets/24px-two-tone/layout-crop-tool.svg";
|
||||||
LayoutSelectTool: { component: LayoutSelectTool, size: 24 },
|
import LayoutEyedropperTool from "@/../assets/24px-two-tone/layout-eyedropper-tool.svg";
|
||||||
LayoutCropTool: { component: LayoutCropTool, size: 24 },
|
import LayoutNavigateTool from "@/../assets/24px-two-tone/layout-navigate-tool.svg";
|
||||||
LayoutNavigateTool: { component: LayoutNavigateTool, size: 24 },
|
import LayoutSelectTool from "@/../assets/24px-two-tone/layout-select-tool.svg";
|
||||||
LayoutEyedropperTool: { component: LayoutEyedropperTool, size: 24 },
|
import ParametricFillTool from "@/../assets/24px-two-tone/parametric-fill-tool.svg";
|
||||||
ParametricTextTool: { component: ParametricTextTool, size: 24 },
|
import ParametricGradientTool from "@/../assets/24px-two-tone/parametric-gradient-tool.svg";
|
||||||
ParametricFillTool: { component: ParametricFillTool, size: 24 },
|
import ParametricTextTool from "@/../assets/24px-two-tone/parametric-text-tool.svg";
|
||||||
ParametricGradientTool: { component: ParametricGradientTool, size: 24 },
|
import RasterBrushTool from "@/../assets/24px-two-tone/raster-brush-tool.svg";
|
||||||
RasterBrushTool: { component: RasterBrushTool, size: 24 },
|
import RasterCloneTool from "@/../assets/24px-two-tone/raster-clone-tool.svg";
|
||||||
RasterHealTool: { component: RasterHealTool, size: 24 },
|
import RasterBlurSharpenTool from "@/../assets/24px-two-tone/raster-detail-tool.svg";
|
||||||
RasterCloneTool: { component: RasterCloneTool, size: 24 },
|
import RasterHealTool from "@/../assets/24px-two-tone/raster-heal-tool.svg";
|
||||||
RasterPatchTool: { component: RasterPatchTool, size: 24 },
|
import RasterPatchTool from "@/../assets/24px-two-tone/raster-patch-tool.svg";
|
||||||
RasterBlurSharpenTool: { component: RasterBlurSharpenTool, size: 24 },
|
import RasterRelightTool from "@/../assets/24px-two-tone/raster-relight-tool.svg";
|
||||||
RasterRelightTool: { component: RasterRelightTool, size: 24 },
|
import VectorEllipseTool from "@/../assets/24px-two-tone/vector-ellipse-tool.svg";
|
||||||
VectorPathTool: { component: VectorPathTool, size: 24 },
|
import VectorFreehandTool from "@/../assets/24px-two-tone/vector-freehand-tool.svg";
|
||||||
VectorPenTool: { component: VectorPenTool, size: 24 },
|
import VectorLineTool from "@/../assets/24px-two-tone/vector-line-tool.svg";
|
||||||
VectorFreehandTool: { component: VectorFreehandTool, size: 24 },
|
import VectorPathTool from "@/../assets/24px-two-tone/vector-path-tool.svg";
|
||||||
VectorSplineTool: { component: VectorSplineTool, size: 24 },
|
import VectorPenTool from "@/../assets/24px-two-tone/vector-pen-tool.svg";
|
||||||
VectorLineTool: { component: VectorLineTool, size: 24 },
|
import VectorRectangleTool from "@/../assets/24px-two-tone/vector-rectangle-tool.svg";
|
||||||
VectorRectangleTool: { component: VectorRectangleTool, size: 24 },
|
import VectorShapeTool from "@/../assets/24px-two-tone/vector-shape-tool.svg";
|
||||||
VectorEllipseTool: { component: VectorEllipseTool, size: 24 },
|
import VectorSplineTool from "@/../assets/24px-two-tone/vector-spline-tool.svg";
|
||||||
VectorShapeTool: { component: VectorShapeTool, size: 24 },
|
|
||||||
AlignLeft: { component: AlignLeft, size: 16 },
|
export type IconName = keyof typeof ICON_LIST;
|
||||||
AlignHorizontalCenter: { component: AlignHorizontalCenter, size: 16 },
|
export type IconSize = 12 | 16 | 24 | 32;
|
||||||
AlignRight: { component: AlignRight, size: 16 },
|
|
||||||
AlignTop: { component: AlignTop, size: 16 },
|
const size12: IconSize = 12;
|
||||||
AlignVerticalCenter: { component: AlignVerticalCenter, size: 16 },
|
const size16: IconSize = 16;
|
||||||
AlignBottom: { component: AlignBottom, size: 16 },
|
const size24: IconSize = 24;
|
||||||
FlipHorizontal: { component: FlipHorizontal, size: 16 },
|
// const size32: IconSize = 32;
|
||||||
FlipVertical: { component: FlipVertical, size: 16 },
|
|
||||||
BooleanUnion: { component: BooleanUnion, size: 16 },
|
const ICON_LIST = {
|
||||||
BooleanSubtractFront: { component: BooleanSubtractFront, size: 16 },
|
Checkmark: { component: Checkmark, size: size12 },
|
||||||
BooleanSubtractBack: { component: BooleanSubtractBack, size: 16 },
|
CloseX: { component: CloseX, size: size12 },
|
||||||
BooleanIntersect: { component: BooleanIntersect, size: 16 },
|
DropdownArrow: { component: DropdownArrow, size: size12 },
|
||||||
BooleanDifference: { component: BooleanDifference, size: 16 },
|
FullscreenEnter: { component: FullscreenEnter, size: size12 },
|
||||||
ZoomReset: { component: ZoomReset, size: 16 },
|
FullscreenExit: { component: FullscreenExit, size: size12 },
|
||||||
ZoomIn: { component: ZoomIn, size: 16 },
|
Grid: { component: Grid, size: size12 },
|
||||||
ZoomOut: { component: ZoomOut, size: 16 },
|
Info: { component: Info, size: size12 },
|
||||||
ViewModeNormal: { component: ViewModeNormal, size: 16 },
|
KeyboardArrowDown: { component: KeyboardArrowDown, size: size12 },
|
||||||
ViewModeOutline: { component: ViewModeOutline, size: 16 },
|
KeyboardArrowLeft: { component: KeyboardArrowLeft, size: size12 },
|
||||||
ViewModePixels: { component: ViewModePixels, size: 16 },
|
KeyboardArrowRight: { component: KeyboardArrowRight, size: size12 },
|
||||||
EyeVisible: { component: EyeVisible, size: 16 },
|
KeyboardArrowUp: { component: KeyboardArrowUp, size: size12 },
|
||||||
EyeHidden: { component: EyeHidden, size: 16 },
|
KeyboardBackspace: { component: KeyboardBackspace, size: size12 },
|
||||||
GraphiteLogo: { component: GraphiteLogo, size: 16 },
|
KeyboardCommand: { component: KeyboardCommand, size: size12 },
|
||||||
File: { component: File, size: 16 },
|
KeyboardEnter: { component: KeyboardEnter, size: size12 },
|
||||||
Copy: { component: Copy, size: 16 },
|
KeyboardOption: { component: KeyboardOption, size: size12 },
|
||||||
Paste: { component: Paste, size: 16 },
|
KeyboardShift: { component: KeyboardShift, size: size12 },
|
||||||
ViewportDesignMode: { component: ViewportDesignMode, size: 16 },
|
KeyboardSpace: { component: KeyboardSpace, size: size12 },
|
||||||
ViewportSelectMode: { component: ViewportSelectMode, size: 16 },
|
KeyboardTab: { component: KeyboardTab, size: size12 },
|
||||||
ViewportGuideMode: { component: ViewportGuideMode, size: 16 },
|
Link: { component: Link, size: size12 },
|
||||||
Checkmark: { component: Checkmark, size: 12 },
|
Overlays: { component: Overlays, size: size12 },
|
||||||
Link: { component: Link, size: 12 },
|
ResetColors: { component: ResetColors, size: size12 },
|
||||||
Grid: { component: Grid, size: 12 },
|
Snapping: { component: Snapping, size: size12 },
|
||||||
Overlays: { component: Overlays, size: 12 },
|
Swap: { component: Swap, size: size12 },
|
||||||
Snapping: { component: Snapping, size: 12 },
|
VerticalEllipsis: { component: VerticalEllipsis, size: size12 },
|
||||||
Info: { component: Info, size: 12 },
|
Warning: { component: Warning, size: size12 },
|
||||||
Warning: { component: Warning, size: 12 },
|
WindowButtonWinClose: { component: WindowButtonWinClose, size: size12 },
|
||||||
Swap: { component: Swap, size: 12 },
|
WindowButtonWinMaximize: { component: WindowButtonWinMaximize, size: size12 },
|
||||||
ResetColors: { component: ResetColors, size: 12 },
|
WindowButtonWinMinimize: { component: WindowButtonWinMinimize, size: size12 },
|
||||||
DropdownArrow: { component: DropdownArrow, size: 12 },
|
WindowButtonWinRestoreDown: { component: WindowButtonWinRestoreDown, size: size12 },
|
||||||
VerticalEllipsis: { component: VerticalEllipsis, size: 12 },
|
|
||||||
CloseX: { component: CloseX, size: 12 },
|
AlignBottom: { component: AlignBottom, size: size16 },
|
||||||
FullscreenEnter: { component: FullscreenEnter, size: 12 },
|
AlignHorizontalCenter: { component: AlignHorizontalCenter, size: size16 },
|
||||||
FullscreenExit: { component: FullscreenExit, size: 12 },
|
AlignLeft: { component: AlignLeft, size: size16 },
|
||||||
WindowButtonWinMinimize: { component: WindowButtonWinMinimize, size: 12 },
|
AlignRight: { component: AlignRight, size: size16 },
|
||||||
WindowButtonWinMaximize: { component: WindowButtonWinMaximize, size: 12 },
|
AlignTop: { component: AlignTop, size: size16 },
|
||||||
WindowButtonWinRestoreDown: { component: WindowButtonWinRestoreDown, size: 12 },
|
AlignVerticalCenter: { component: AlignVerticalCenter, size: size16 },
|
||||||
WindowButtonWinClose: { component: WindowButtonWinClose, size: 12 },
|
BooleanDifference: { component: BooleanDifference, size: size16 },
|
||||||
KeyboardArrowUp: { component: KeyboardArrowUp, size: 12 },
|
BooleanIntersect: { component: BooleanIntersect, size: size16 },
|
||||||
KeyboardArrowRight: { component: KeyboardArrowRight, size: 12 },
|
BooleanSubtractBack: { component: BooleanSubtractBack, size: size16 },
|
||||||
KeyboardArrowDown: { component: KeyboardArrowDown, size: 12 },
|
BooleanSubtractFront: { component: BooleanSubtractFront, size: size16 },
|
||||||
KeyboardArrowLeft: { component: KeyboardArrowLeft, size: 12 },
|
BooleanUnion: { component: BooleanUnion, size: size16 },
|
||||||
KeyboardBackspace: { component: KeyboardBackspace, size: 12 },
|
Copy: { component: Copy, size: size16 },
|
||||||
KeyboardCommand: { component: KeyboardCommand, size: 12 },
|
EyeHidden: { component: EyeHidden, size: size16 },
|
||||||
KeyboardEnter: { component: KeyboardEnter, size: 12 },
|
EyeVisible: { component: EyeVisible, size: size16 },
|
||||||
KeyboardOption: { component: KeyboardOption, size: 12 },
|
File: { component: File, size: size16 },
|
||||||
KeyboardShift: { component: KeyboardShift, size: 12 },
|
FlipHorizontal: { component: FlipHorizontal, size: size16 },
|
||||||
KeyboardSpace: { component: KeyboardSpace, size: 12 },
|
FlipVertical: { component: FlipVertical, size: size16 },
|
||||||
KeyboardTab: { component: KeyboardTab, size: 12 },
|
GraphiteLogo: { component: GraphiteLogo, size: size16 },
|
||||||
MouseHintNone: { component: MouseHintNone, size: 16 },
|
Paste: { component: Paste, size: size16 },
|
||||||
MouseHintLmb: { component: MouseHintLmb, size: 16 },
|
ViewModeNormal: { component: ViewModeNormal, size: size16 },
|
||||||
MouseHintRmb: { component: MouseHintRmb, size: 16 },
|
ViewModeOutline: { component: ViewModeOutline, size: size16 },
|
||||||
MouseHintMmb: { component: MouseHintMmb, size: 16 },
|
ViewModePixels: { component: ViewModePixels, size: size16 },
|
||||||
MouseHintScrollUp: { component: MouseHintScrollUp, size: 16 },
|
ViewportDesignMode: { component: ViewportDesignMode, size: size16 },
|
||||||
MouseHintScrollDown: { component: MouseHintScrollDown, size: 16 },
|
ViewportGuideMode: { component: ViewportGuideMode, size: size16 },
|
||||||
MouseHintDrag: { component: MouseHintDrag, size: 16 },
|
ViewportSelectMode: { component: ViewportSelectMode, size: size16 },
|
||||||
MouseHintLmbDrag: { component: MouseHintLmbDrag, size: 16 },
|
ZoomIn: { component: ZoomIn, size: size16 },
|
||||||
MouseHintRmbDrag: { component: MouseHintRmbDrag, size: 16 },
|
ZoomOut: { component: ZoomOut, size: size16 },
|
||||||
MouseHintMmbDrag: { component: MouseHintMmbDrag, size: 16 },
|
ZoomReset: { component: ZoomReset, size: size16 },
|
||||||
NodeTypePath: { component: NodeTypePath, size: 24 },
|
|
||||||
NodeTypeFolder: { component: NodeTypeFolder, size: 24 },
|
MouseHintDrag: { component: MouseHintDrag, size: size16 },
|
||||||
|
MouseHintLmbDrag: { component: MouseHintLmbDrag, size: size16 },
|
||||||
|
MouseHintLmb: { component: MouseHintLmb, size: size16 },
|
||||||
|
MouseHintMmbDrag: { component: MouseHintMmbDrag, size: size16 },
|
||||||
|
MouseHintMmb: { component: MouseHintMmb, size: size16 },
|
||||||
|
MouseHintNone: { component: MouseHintNone, size: size16 },
|
||||||
|
MouseHintRmbDrag: { component: MouseHintRmbDrag, size: size16 },
|
||||||
|
MouseHintRmb: { component: MouseHintRmb, size: size16 },
|
||||||
|
MouseHintScrollDown: { component: MouseHintScrollDown, size: size16 },
|
||||||
|
MouseHintScrollUp: { component: MouseHintScrollUp, size: size16 },
|
||||||
|
|
||||||
|
NodeTypeFolder: { component: NodeTypeFolder, size: size24 },
|
||||||
|
NodeTypePath: { component: NodeTypePath, size: size24 },
|
||||||
|
|
||||||
|
LayoutCropTool: { component: LayoutCropTool, size: size24 },
|
||||||
|
LayoutEyedropperTool: { component: LayoutEyedropperTool, size: size24 },
|
||||||
|
LayoutNavigateTool: { component: LayoutNavigateTool, size: size24 },
|
||||||
|
LayoutSelectTool: { component: LayoutSelectTool, size: size24 },
|
||||||
|
ParametricFillTool: { component: ParametricFillTool, size: size24 },
|
||||||
|
ParametricGradientTool: { component: ParametricGradientTool, size: size24 },
|
||||||
|
ParametricTextTool: { component: ParametricTextTool, size: size24 },
|
||||||
|
RasterBrushTool: { component: RasterBrushTool, size: size24 },
|
||||||
|
RasterCloneTool: { component: RasterCloneTool, size: size24 },
|
||||||
|
RasterBlurSharpenTool: { component: RasterBlurSharpenTool, size: size24 },
|
||||||
|
RasterHealTool: { component: RasterHealTool, size: size24 },
|
||||||
|
RasterPatchTool: { component: RasterPatchTool, size: size24 },
|
||||||
|
RasterRelightTool: { component: RasterRelightTool, size: size24 },
|
||||||
|
VectorEllipseTool: { component: VectorEllipseTool, size: size24 },
|
||||||
|
VectorFreehandTool: { component: VectorFreehandTool, size: size24 },
|
||||||
|
VectorLineTool: { component: VectorLineTool, size: size24 },
|
||||||
|
VectorPathTool: { component: VectorPathTool, size: size24 },
|
||||||
|
VectorPenTool: { component: VectorPenTool, size: size24 },
|
||||||
|
VectorRectangleTool: { component: VectorRectangleTool, size: size24 },
|
||||||
|
VectorShapeTool: { component: VectorShapeTool, size: size24 },
|
||||||
|
VectorSplineTool: { component: VectorSplineTool, size: size24 },
|
||||||
};
|
};
|
||||||
|
const icons: Record<IconName, { component: DefineComponent; size: IconSize }> = ICON_LIST;
|
||||||
const components = Object.fromEntries(Object.entries(icons).map(([name, data]) => [name, data.component]));
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: Object.fromEntries(Object.entries(icons).map(([name, data]) => [name, data.component])),
|
||||||
props: {
|
props: {
|
||||||
icon: { type: String, required: true },
|
icon: { type: String as PropType<IconName>, required: true },
|
||||||
gapAfter: { type: Boolean, default: false },
|
gapAfter: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
components,
|
|
||||||
data() {
|
data() {
|
||||||
return { icons };
|
return {
|
||||||
|
icons,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,13 +20,12 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
bold: { type: Boolean, default: false },
|
bold: { type: Boolean as PropType<boolean>, default: false },
|
||||||
italic: { type: Boolean, default: false },
|
italic: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<span class="input-mouse" v-if="inputMouse">
|
<span class="input-mouse" v-if="inputMouse">
|
||||||
<IconLabel :icon="mouseMovementIcon(inputMouse)" />
|
<IconLabel :icon="`MouseHint${inputMouse}`" />
|
||||||
</span>
|
</span>
|
||||||
<span class="hint-text" v-if="hasSlotContent">
|
<span class="hint-text" v-if="hasSlotContent">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
@@ -95,28 +95,17 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
import { HintInfo } from "@/dispatcher/js-messages";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export enum MouseInputInteraction {
|
|
||||||
"None" = "None",
|
|
||||||
"Lmb" = "Lmb",
|
|
||||||
"Rmb" = "Rmb",
|
|
||||||
"Mmb" = "Mmb",
|
|
||||||
"ScrollUp" = "ScrollUp",
|
|
||||||
"ScrollDown" = "ScrollDown",
|
|
||||||
"Drag" = "Drag",
|
|
||||||
"LmbDrag" = "LmbDrag",
|
|
||||||
"RmbDrag" = "RmbDrag",
|
|
||||||
"MmbDrag" = "MmbDrag",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
props: {
|
props: {
|
||||||
inputKeys: { type: Array, default: () => [] },
|
inputKeys: { type: Array as PropType<HintInfo["key_groups"]>, default: () => [] },
|
||||||
inputMouse: { type: String },
|
inputMouse: { type: String as PropType<HintInfo["mouse"]>, default: null },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasSlotContent(): boolean {
|
hasSlotContent(): boolean {
|
||||||
@@ -173,50 +162,14 @@ export default defineComponent({
|
|||||||
let result;
|
let result;
|
||||||
|
|
||||||
// Letters and numbers
|
// Letters and numbers
|
||||||
if (/^[A-Z0-9]$/.test(text)) {
|
if (/^[A-Z0-9]$/.test(text)) result = text;
|
||||||
result = text;
|
|
||||||
}
|
|
||||||
// Abbreviated names
|
// Abbreviated names
|
||||||
else if (text in textMap) {
|
else if (text in textMap) result = textMap[text];
|
||||||
result = textMap[text];
|
|
||||||
}
|
|
||||||
// Other
|
// Other
|
||||||
else {
|
else result = text;
|
||||||
result = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
||||||
},
|
},
|
||||||
mouseMovementIcon(mouseInputInteraction: MouseInputInteraction) {
|
|
||||||
switch (mouseInputInteraction) {
|
|
||||||
case MouseInputInteraction.Lmb:
|
|
||||||
return "MouseHintLmb";
|
|
||||||
case MouseInputInteraction.Rmb:
|
|
||||||
return "MouseHintRmb";
|
|
||||||
case MouseInputInteraction.Mmb:
|
|
||||||
return "MouseHintMmb";
|
|
||||||
case MouseInputInteraction.ScrollUp:
|
|
||||||
return "MouseHintScrollUp";
|
|
||||||
case MouseInputInteraction.ScrollDown:
|
|
||||||
return "MouseHintScrollDown";
|
|
||||||
case MouseInputInteraction.Drag:
|
|
||||||
return "MouseHintDrag";
|
|
||||||
case MouseInputInteraction.LmbDrag:
|
|
||||||
return "MouseHintLmbDrag";
|
|
||||||
case MouseInputInteraction.RmbDrag:
|
|
||||||
return "MouseHintRmbDrag";
|
|
||||||
case MouseInputInteraction.MmbDrag:
|
|
||||||
return "MouseHintMmbDrag";
|
|
||||||
default:
|
|
||||||
case MouseInputInteraction.None:
|
|
||||||
return "MouseHintNone";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
MouseInputInteraction,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,17 +31,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { WidgetRow, SeparatorType, IconButtonWidget } from "@/components/widgets/widgets";
|
import { WidgetRow, IconButtonWidget } from "@/utilities/widgets";
|
||||||
|
|
||||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
import NumberInput from "@/components/widgets/inputs/NumberInput.vue";
|
||||||
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
|
|
||||||
|
export type ToolName = "Select" | "Shape" | "Line" | "Pen";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["editor", "dialog"],
|
inject: ["editor", "dialog"],
|
||||||
props: {
|
props: {
|
||||||
activeTool: { type: String },
|
activeTool: { type: String as PropType<ToolName> },
|
||||||
activeToolOptions: { type: Object as PropType<Record<string, object>> },
|
activeToolOptions: { type: Object as PropType<Record<string, object>> },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -54,11 +56,15 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
// Traverses the given path and returns the direct parent of the option
|
// Traverses the given path and returns the direct parent of the option
|
||||||
getRecordContainingOption(optionPath: string[]): Record<string, number> {
|
getRecordContainingOption(optionPath: string[]): Record<string, number> {
|
||||||
const allButLast = optionPath.slice(0, -1);
|
// TODO: Formalize types and avoid casting with `as`
|
||||||
let currentRecord = this.activeToolOptions as Record<string, object | number>;
|
let currentRecord = this.activeToolOptions as Record<string, object | number>;
|
||||||
[this.activeTool || "", ...allButLast].forEach((attr) => {
|
|
||||||
|
const allButLastOptions = optionPath.slice(0, -1);
|
||||||
|
[this.activeTool || "", ...allButLastOptions].forEach((attr) => {
|
||||||
|
// Dig into the tree in each loop iteration
|
||||||
currentRecord = currentRecord[attr] as Record<string, object | number>;
|
currentRecord = currentRecord[attr] as Record<string, object | number>;
|
||||||
});
|
});
|
||||||
|
|
||||||
return currentRecord as Record<string, number>;
|
return currentRecord as Record<string, number>;
|
||||||
},
|
},
|
||||||
// Traverses the given path into the active tool's option struct, and sets the value at the path tail
|
// Traverses the given path into the active tool's option struct, and sets the value at the path tail
|
||||||
@@ -88,19 +94,19 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const toolOptionsWidgets: Record<string, WidgetRow> = {
|
const toolOptionsWidgets: Record<ToolName, WidgetRow> = {
|
||||||
Select: [
|
Select: [
|
||||||
{ kind: "IconButton", message: { Align: ["X", "Min"] }, tooltip: "Align Left", props: { icon: "AlignLeft", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["X", "Min"] }, tooltip: "Align Left", props: { icon: "AlignLeft", size: 24 } },
|
||||||
{ kind: "IconButton", message: { Align: ["X", "Center"] }, tooltip: "Align Horizontal Center", props: { icon: "AlignHorizontalCenter", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["X", "Center"] }, tooltip: "Align Horizontal Center", props: { icon: "AlignHorizontalCenter", size: 24 } },
|
||||||
{ kind: "IconButton", message: { Align: ["X", "Max"] }, tooltip: "Align Right", props: { icon: "AlignRight", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["X", "Max"] }, tooltip: "Align Right", props: { icon: "AlignRight", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Unrelated } },
|
{ kind: "Separator", props: { type: "Unrelated" } },
|
||||||
|
|
||||||
{ kind: "IconButton", message: { Align: ["Y", "Min"] }, tooltip: "Align Top", props: { icon: "AlignTop", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["Y", "Min"] }, tooltip: "Align Top", props: { icon: "AlignTop", size: 24 } },
|
||||||
{ kind: "IconButton", message: { Align: ["Y", "Center"] }, tooltip: "Align Vertical Center", props: { icon: "AlignVerticalCenter", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["Y", "Center"] }, tooltip: "Align Vertical Center", props: { icon: "AlignVerticalCenter", size: 24 } },
|
||||||
{ kind: "IconButton", message: { Align: ["Y", "Max"] }, tooltip: "Align Bottom", props: { icon: "AlignBottom", size: 24 } },
|
{ kind: "IconButton", message: { Align: ["Y", "Max"] }, tooltip: "Align Bottom", props: { icon: "AlignBottom", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
{ kind: "Separator", props: { type: "Related" } },
|
||||||
|
|
||||||
{
|
{
|
||||||
kind: "PopoverButton",
|
kind: "PopoverButton",
|
||||||
@@ -111,12 +117,12 @@ export default defineComponent({
|
|||||||
props: {},
|
props: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Section } },
|
{ kind: "Separator", props: { type: "Section" } },
|
||||||
|
|
||||||
{ kind: "IconButton", message: "FlipHorizontal", tooltip: "Flip Horizontal", props: { icon: "FlipHorizontal", size: 24 } },
|
{ kind: "IconButton", message: "FlipHorizontal", tooltip: "Flip Horizontal", props: { icon: "FlipHorizontal", size: 24 } },
|
||||||
{ kind: "IconButton", message: "FlipVertical", tooltip: "Flip Vertical", props: { icon: "FlipVertical", size: 24 } },
|
{ kind: "IconButton", message: "FlipVertical", tooltip: "Flip Vertical", props: { icon: "FlipVertical", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
{ kind: "Separator", props: { type: "Related" } },
|
||||||
|
|
||||||
{
|
{
|
||||||
kind: "PopoverButton",
|
kind: "PopoverButton",
|
||||||
@@ -127,15 +133,15 @@ export default defineComponent({
|
|||||||
props: {},
|
props: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Section } },
|
{ kind: "Separator", props: { type: "Section" } },
|
||||||
|
|
||||||
{ kind: "IconButton", tooltip: "Boolean Union", callback: () => this.dialog.comingSoon(197), props: { icon: "BooleanUnion", size: 24 } },
|
{ kind: "IconButton", tooltip: "Boolean Union", callback: (): void => this.dialog.comingSoon(197), props: { icon: "BooleanUnion", size: 24 } },
|
||||||
{ kind: "IconButton", tooltip: "Boolean Subtract Front", callback: () => this.dialog.comingSoon(197), props: { icon: "BooleanSubtractFront", size: 24 } },
|
{ kind: "IconButton", tooltip: "Boolean Subtract Front", callback: (): void => this.dialog.comingSoon(197), props: { icon: "BooleanSubtractFront", size: 24 } },
|
||||||
{ kind: "IconButton", tooltip: "Boolean Subtract Back", callback: () => this.dialog.comingSoon(197), props: { icon: "BooleanSubtractBack", size: 24 } },
|
{ kind: "IconButton", tooltip: "Boolean Subtract Back", callback: (): void => this.dialog.comingSoon(197), props: { icon: "BooleanSubtractBack", size: 24 } },
|
||||||
{ kind: "IconButton", tooltip: "Boolean Intersect", callback: () => this.dialog.comingSoon(197), props: { icon: "BooleanIntersect", size: 24 } },
|
{ kind: "IconButton", tooltip: "Boolean Intersect", callback: (): void => this.dialog.comingSoon(197), props: { icon: "BooleanIntersect", size: 24 } },
|
||||||
{ kind: "IconButton", tooltip: "Boolean Difference", callback: () => this.dialog.comingSoon(197), props: { icon: "BooleanDifference", size: 24 } },
|
{ kind: "IconButton", tooltip: "Boolean Difference", callback: (): void => this.dialog.comingSoon(197), props: { icon: "BooleanDifference", size: 24 } },
|
||||||
|
|
||||||
{ kind: "Separator", props: { type: SeparatorType.Related } },
|
{ kind: "Separator", props: { type: "Related" } },
|
||||||
|
|
||||||
{
|
{
|
||||||
kind: "PopoverButton",
|
kind: "PopoverButton",
|
||||||
@@ -153,7 +159,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
toolOptionsWidgets,
|
toolOptionsWidgets,
|
||||||
SeparatorType,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -50,29 +50,26 @@ const MAJOR_MARK_THICKNESS = 16;
|
|||||||
const MEDIUM_MARK_THICKNESS = 6;
|
const MEDIUM_MARK_THICKNESS = 6;
|
||||||
const MINOR_MARK_THICKNESS = 3;
|
const MINOR_MARK_THICKNESS = 3;
|
||||||
|
|
||||||
export enum RulerDirection {
|
export type RulerDirection = "Horizontal" | "Vertical";
|
||||||
"Horizontal" = "Horizontal",
|
|
||||||
"Vertical" = "Vertical",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apparently the modulo operator in js does not work properly.
|
// Apparently the modulo operator in js does not work properly.
|
||||||
const mod = (n: number, m: number) => {
|
const mod = (n: number, m: number): number => {
|
||||||
const remain = n % m;
|
const remainder = n % m;
|
||||||
return Math.floor(remain >= 0 ? remain : remain + m);
|
return Math.floor(remainder >= 0 ? remainder : remainder + m);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
direction: { type: String as PropType<RulerDirection>, default: RulerDirection.Vertical },
|
direction: { type: String as PropType<RulerDirection>, default: "Vertical" },
|
||||||
origin: { type: Number, required: true },
|
origin: { type: Number as PropType<number>, required: true },
|
||||||
numberInterval: { type: Number, required: true },
|
numberInterval: { type: Number as PropType<number>, required: true },
|
||||||
majorMarkSpacing: { type: Number, required: true },
|
majorMarkSpacing: { type: Number as PropType<number>, required: true },
|
||||||
mediumDivisions: { type: Number, default: 5 },
|
mediumDivisions: { type: Number as PropType<number>, default: 5 },
|
||||||
minorDivisions: { type: Number, default: 2 },
|
minorDivisions: { type: Number as PropType<number>, default: 2 },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
svgPath(): string {
|
svgPath(): string {
|
||||||
const isVertical = this.direction === RulerDirection.Vertical;
|
const isVertical = this.direction === "Vertical";
|
||||||
const lineDirection = isVertical ? "H" : "V";
|
const lineDirection = isVertical ? "H" : "V";
|
||||||
|
|
||||||
const offsetStart = mod(this.origin, this.majorMarkSpacing);
|
const offsetStart = mod(this.origin, this.majorMarkSpacing);
|
||||||
@@ -98,7 +95,7 @@ export default defineComponent({
|
|||||||
return dPathAttribute;
|
return dPathAttribute;
|
||||||
},
|
},
|
||||||
svgTexts(): { transform: string; text: number }[] {
|
svgTexts(): { transform: string; text: number }[] {
|
||||||
const isVertical = this.direction === RulerDirection.Vertical;
|
const isVertical = this.direction === "Vertical";
|
||||||
|
|
||||||
const offsetStart = mod(this.origin, this.majorMarkSpacing);
|
const offsetStart = mod(this.origin, this.majorMarkSpacing);
|
||||||
const shiftedOffsetStart = offsetStart - this.majorMarkSpacing;
|
const shiftedOffsetStart = offsetStart - this.majorMarkSpacing;
|
||||||
@@ -128,7 +125,7 @@ export default defineComponent({
|
|||||||
if (!this.$refs.rulerRef) return;
|
if (!this.$refs.rulerRef) return;
|
||||||
|
|
||||||
const rulerElement = this.$refs.rulerRef as HTMLElement;
|
const rulerElement = this.$refs.rulerRef as HTMLElement;
|
||||||
const isVertical = this.direction === RulerDirection.Vertical;
|
const isVertical = this.direction === "Vertical";
|
||||||
|
|
||||||
const newLength = isVertical ? rulerElement.clientHeight : rulerElement.clientWidth;
|
const newLength = isVertical ? rulerElement.clientHeight : rulerElement.clientWidth;
|
||||||
const roundedUp = (Math.floor(newLength / this.majorMarkSpacing) + 1) * this.majorMarkSpacing;
|
const roundedUp = (Math.floor(newLength / this.majorMarkSpacing) + 1) * this.majorMarkSpacing;
|
||||||
@@ -152,7 +149,6 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
rulerLength: 0,
|
rulerLength: 0,
|
||||||
svgBounds: { width: "0px", height: "0px" },
|
svgBounds: { width: "0px", height: "0px" },
|
||||||
RulerDirection,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="persistent-scrollbar" :class="direction.toLowerCase()">
|
<div class="persistent-scrollbar" :class="direction.toLowerCase()">
|
||||||
<button class="arrow decrease" @pointerdown="changePosition(-50)"></button>
|
<button class="arrow decrease" @pointerdown="changePosition(-50)"></button>
|
||||||
<div class="scroll-track" ref="scrollTrack" @pointerdown="grabArea">
|
<div class="scroll-track" ref="scrollTrack" @pointerdown="(e) => grabArea(e)">
|
||||||
<div class="scroll-thumb" @pointerdown="grabHandle" :class="{ dragging }" ref="handle" :style="[thumbStart, thumbEnd, sides]"></div>
|
<div class="scroll-thumb" @pointerdown="(e) => grabHandle(e)" :class="{ dragging }" ref="handle" :style="[thumbStart, thumbEnd, sides]"></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="arrow increase" @click="changePosition(50)"></button>
|
<button class="arrow increase" @click="changePosition(50)"></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,44 +110,40 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
export type ScrollbarDirection = "Horizontal" | "Vertical";
|
||||||
|
|
||||||
// Linear Interpolation
|
// Linear Interpolation
|
||||||
const lerp = (x: number, y: number, a: number) => x * (1 - a) + y * a;
|
const lerp = (x: number, y: number, a: number): number => x * (1 - a) + y * a;
|
||||||
|
|
||||||
// Convert the position of the handle (0-1) to the position on the track (0-1).
|
// Convert the position of the handle (0-1) to the position on the track (0-1).
|
||||||
// This includes the 1/2 handle length gap of the possible handle positionson each side so the end of the handle doesn't go off the track.
|
// This includes the 1/2 handle length gap of the possible handle positionson each side so the end of the handle doesn't go off the track.
|
||||||
const handleToTrack = (handleLen: number, handlePos: number) => lerp(handleLen / 2, 1 - handleLen / 2, handlePos);
|
const handleToTrack = (handleLen: number, handlePos: number): number => lerp(handleLen / 2, 1 - handleLen / 2, handlePos);
|
||||||
|
|
||||||
const pointerPosition = (direction: ScrollbarDirection, e: PointerEvent) => (direction === ScrollbarDirection.Vertical ? e.clientY : e.clientX);
|
const pointerPosition = (direction: ScrollbarDirection, e: PointerEvent): number => (direction === "Vertical" ? e.clientY : e.clientX);
|
||||||
|
|
||||||
export enum ScrollbarDirection {
|
|
||||||
"Horizontal" = "Horizontal",
|
|
||||||
"Vertical" = "Vertical",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
direction: { type: String as PropType<ScrollbarDirection>, default: ScrollbarDirection.Vertical },
|
direction: { type: String as PropType<ScrollbarDirection>, default: "Vertical" },
|
||||||
handlePosition: { type: Number, default: 0.5 },
|
handlePosition: { type: Number as PropType<number>, default: 0.5 },
|
||||||
handleLength: { type: Number, default: 0.5 },
|
handleLength: { type: Number as PropType<number>, default: 0.5 },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
thumbStart(): { left: string } | { top: string } {
|
thumbStart(): { left: string } | { top: string } {
|
||||||
const start = handleToTrack(this.handleLength, this.handlePosition) - this.handleLength / 2;
|
const start = handleToTrack(this.handleLength, this.handlePosition) - this.handleLength / 2;
|
||||||
|
|
||||||
return this.direction === ScrollbarDirection.Vertical ? { top: `${start * 100}%` } : { left: `${start * 100}%` };
|
return this.direction === "Vertical" ? { top: `${start * 100}%` } : { left: `${start * 100}%` };
|
||||||
},
|
},
|
||||||
thumbEnd(): { right: string } | { bottom: string } {
|
thumbEnd(): { right: string } | { bottom: string } {
|
||||||
const end = 1 - handleToTrack(this.handleLength, this.handlePosition) - this.handleLength / 2;
|
const end = 1 - handleToTrack(this.handleLength, this.handlePosition) - this.handleLength / 2;
|
||||||
|
|
||||||
return this.direction === ScrollbarDirection.Vertical ? { bottom: `${end * 100}%` } : { right: `${end * 100}%` };
|
return this.direction === "Vertical" ? { bottom: `${end * 100}%` } : { right: `${end * 100}%` };
|
||||||
},
|
},
|
||||||
sides(): { left: string; right: string } | { top: string; bottom: string } {
|
sides(): { left: string; right: string } | { top: string; bottom: string } {
|
||||||
return this.direction === ScrollbarDirection.Vertical ? { left: "0%", right: "0%" } : { top: "0%", bottom: "0%" };
|
return this.direction === "Vertical" ? { left: "0%", right: "0%" } : { top: "0%", bottom: "0%" };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ScrollbarDirection,
|
|
||||||
dragging: false,
|
dragging: false,
|
||||||
pointerPos: 0,
|
pointerPos: 0,
|
||||||
};
|
};
|
||||||
@@ -159,11 +155,11 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
trackLength(): number {
|
trackLength(): number {
|
||||||
const track = this.$refs.scrollTrack as HTMLElement;
|
const track = this.$refs.scrollTrack as HTMLElement;
|
||||||
return this.direction === ScrollbarDirection.Vertical ? track.clientHeight - this.handleLength : track.clientWidth;
|
return this.direction === "Vertical" ? track.clientHeight - this.handleLength : track.clientWidth;
|
||||||
},
|
},
|
||||||
trackOffset(): number {
|
trackOffset(): number {
|
||||||
const track = this.$refs.scrollTrack as HTMLElement;
|
const track = this.$refs.scrollTrack as HTMLElement;
|
||||||
return this.direction === ScrollbarDirection.Vertical ? track.getBoundingClientRect().top : track.getBoundingClientRect().left;
|
return this.direction === "Vertical" ? track.getBoundingClientRect().top : track.getBoundingClientRect().left;
|
||||||
},
|
},
|
||||||
clampHandlePosition(newPos: number) {
|
clampHandlePosition(newPos: number) {
|
||||||
const clampedPosition = Math.min(Math.max(newPos, 0), 1);
|
const clampedPosition = Math.min(Math.max(newPos, 0), 1);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="separator" :class="[direction.toLowerCase(), type.toLowerCase()]">
|
<div class="separator" :class="[direction.toLowerCase(), type.toLowerCase()]">
|
||||||
<div v-if="[SeparatorType.Section, SeparatorType.List].includes(type)"></div>
|
<div v-if="['Section', 'List'].includes(type)"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -69,21 +69,14 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
import { SeparatorDirection, SeparatorType } from "@/utilities/widgets";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {},
|
|
||||||
props: {
|
props: {
|
||||||
direction: { type: String, default: SeparatorDirection.Horizontal },
|
direction: { type: String as PropType<SeparatorDirection>, default: "Horizontal" },
|
||||||
type: { type: String, default: SeparatorType.Unrelated },
|
type: { type: String as PropType<SeparatorType>, default: "Unrelated" },
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
SeparatorDirection,
|
|
||||||
SeparatorType,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -39,18 +39,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import TitleBar from "@/components/window/title-bar/TitleBar.vue";
|
|
||||||
import StatusBar from "@/components/window/status-bar/StatusBar.vue";
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
|
import StatusBar from "@/components/window/status-bar/StatusBar.vue";
|
||||||
|
import TitleBar from "@/components/window/title-bar/TitleBar.vue";
|
||||||
import Workspace from "@/components/workspace/Workspace.vue";
|
import Workspace from "@/components/workspace/Workspace.vue";
|
||||||
|
|
||||||
export enum ApplicationPlatform {
|
export type ApplicationPlatform = "Windows" | "Mac" | "Linux" | "Web";
|
||||||
"Windows" = "Windows",
|
|
||||||
"Mac" = "Mac",
|
|
||||||
"Linux" = "Linux",
|
|
||||||
"Web" = "Web",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -62,7 +57,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
platform: ApplicationPlatform.Web,
|
platform: "Web" as ApplicationPlatform,
|
||||||
maximized: true,
|
maximized: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="status-bar">
|
<div class="status-bar">
|
||||||
<template v-for="(hintGroup, index) in hintData" :key="hintGroup">
|
<template v-for="(hintGroup, index) in hintData" :key="hintGroup">
|
||||||
<Separator :type="SeparatorType.Section" v-if="index !== 0" />
|
<Separator :type="'Section'" v-if="index !== 0" />
|
||||||
<template v-for="hint in hintGroup" :key="hint">
|
<template v-for="hint in hintGroup" :key="hint">
|
||||||
<span v-if="hint.plus" class="plus">+</span>
|
<span v-if="hint.plus" class="plus">+</span>
|
||||||
<UserInputLabel :inputMouse="hint.mouse" :inputKeys="hint.key_groups">{{ hint.label }}</UserInputLabel>
|
<UserInputLabel :inputMouse="hint.mouse" :inputKeys="hint.key_groups">{{ hint.label }}</UserInputLabel>
|
||||||
@@ -35,11 +35,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { SeparatorType } from "@/components/widgets/widgets";
|
import { HintData, UpdateInputHints } from "@/dispatcher/js-messages";
|
||||||
|
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
import Separator from "@/components/widgets/separators/Separator.vue";
|
import Separator from "@/components/widgets/separators/Separator.vue";
|
||||||
import { HintData, UpdateInputHints } from "@/dispatcher/js-messages";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["editor"],
|
inject: ["editor"],
|
||||||
@@ -49,7 +48,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
SeparatorType,
|
|
||||||
hintData: [] as HintData,
|
hintData: [] as HintData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="header-third">
|
<div class="header-third">
|
||||||
<WindowButtonsMac :maximized="maximized" v-if="platform === ApplicationPlatform.Mac" />
|
<WindowButtonsMac :maximized="maximized" v-if="platform === 'Mac'" />
|
||||||
<MenuBarInput v-if="platform !== ApplicationPlatform.Mac" />
|
<MenuBarInput v-if="platform !== 'Mac'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="header-third">
|
<div class="header-third">
|
||||||
<WindowTitle :title="`${activeDocumentDisplayName} - Graphite`" />
|
<WindowTitle :title="`${activeDocumentDisplayName} - Graphite`" />
|
||||||
</div>
|
</div>
|
||||||
<div class="header-third">
|
<div class="header-third">
|
||||||
<WindowButtonsWindows :maximized="maximized" v-if="platform === ApplicationPlatform.Windows || platform === ApplicationPlatform.Linux" />
|
<WindowButtonsWindows :maximized="maximized" v-if="platform === 'Windows' || platform === 'Linux'" />
|
||||||
<WindowButtonsWeb :maximized="maximized" v-if="platform === ApplicationPlatform.Web" />
|
<WindowButtonsWeb :maximized="maximized" v-if="platform === 'Web'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,25 +32,21 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import WindowTitle from "@/components/window/title-bar/WindowTitle.vue";
|
import MenuBarInput from "@/components/widgets/inputs/MenuBarInput.vue";
|
||||||
import WindowButtonsWindows from "@/components/window/title-bar/WindowButtonsWindows.vue";
|
|
||||||
import WindowButtonsMac from "@/components/window/title-bar/WindowButtonsMac.vue";
|
import WindowButtonsMac from "@/components/window/title-bar/WindowButtonsMac.vue";
|
||||||
import WindowButtonsWeb from "@/components/window/title-bar/WindowButtonsWeb.vue";
|
import WindowButtonsWeb from "@/components/window/title-bar/WindowButtonsWeb.vue";
|
||||||
import MenuBarInput from "@/components/widgets/inputs/MenuBarInput.vue";
|
import WindowButtonsWindows from "@/components/window/title-bar/WindowButtonsWindows.vue";
|
||||||
import { ApplicationPlatform } from "@/components/window/MainWindow.vue";
|
import WindowTitle from "@/components/window/title-bar/WindowTitle.vue";
|
||||||
|
|
||||||
|
export type Platform = "Windows" | "Mac" | "Linux" | "Web";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["documents"],
|
inject: ["documents"],
|
||||||
props: {
|
props: {
|
||||||
platform: { type: String, required: true },
|
platform: { type: String as PropType<Platform>, required: true },
|
||||||
maximized: { type: Boolean, required: true },
|
maximized: { type: Boolean as PropType<boolean>, required: true },
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ApplicationPlatform,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
activeDocumentDisplayName() {
|
activeDocumentDisplayName() {
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
maximized: { type: Boolean, default: false },
|
maximized: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="window-buttons-web" @click="handleClick" :title="fullscreen.state.windowFullscreen ? 'Exit Fullscreen (F11)' : 'Enter Fullscreen (F11)'">
|
<div class="window-buttons-web" @click="(e) => handleClick(e)" :title="fullscreen.state.windowFullscreen ? 'Exit Fullscreen (F11)' : 'Enter Fullscreen (F11)'">
|
||||||
<TextLabel v-if="requestFullscreenHotkeys" :italic="true">Go fullscreen to access all hotkeys</TextLabel>
|
<TextLabel v-if="requestFullscreenHotkeys" :italic="true">Go fullscreen to access all hotkeys</TextLabel>
|
||||||
<IconLabel :icon="fullscreen.state.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
<IconLabel :icon="fullscreen.state.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,14 +38,14 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { IconLabel },
|
components: { IconLabel },
|
||||||
props: {
|
props: {
|
||||||
maximized: { type: Boolean, default: false },
|
maximized: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
title: { type: String, required: true },
|
title: { type: String as PropType<string>, required: true },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<IconButton :action="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
<IconButton :action="(e) => e.stopPropagation() || (closeAction && closeAction(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PopoverButton :icon="PopoverButtonIcon.VerticalEllipsis">
|
<PopoverButton :icon="'VerticalEllipsis'">
|
||||||
<h3>Panel Options</h3>
|
<h3>Panel Options</h3>
|
||||||
<p>The contents of this popover menu are coming soon</p>
|
<p>The contents of this popover menu are coming soon</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
@@ -155,37 +155,32 @@
|
|||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import Document from "@/components/panels/Document.vue";
|
import Document from "@/components/panels/Document.vue";
|
||||||
import Properties from "@/components/panels/Properties.vue";
|
|
||||||
import LayerTree from "@/components/panels/LayerTree.vue";
|
import LayerTree from "@/components/panels/LayerTree.vue";
|
||||||
import Minimap from "@/components/panels/Minimap.vue";
|
import Minimap from "@/components/panels/Minimap.vue";
|
||||||
|
import Properties from "@/components/panels/Properties.vue";
|
||||||
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
import IconButton from "@/components/widgets/buttons/IconButton.vue";
|
||||||
import PopoverButton, { PopoverButtonIcon } from "@/components/widgets/buttons/PopoverButton.vue";
|
import PopoverButton from "@/components/widgets/buttons/PopoverButton.vue";
|
||||||
import { MenuDirection } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
|
||||||
|
const components = {
|
||||||
|
Document,
|
||||||
|
Properties,
|
||||||
|
LayerTree,
|
||||||
|
Minimap,
|
||||||
|
IconButton,
|
||||||
|
PopoverButton,
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["documents"],
|
inject: ["documents"],
|
||||||
components: {
|
components,
|
||||||
Document,
|
|
||||||
Properties,
|
|
||||||
LayerTree,
|
|
||||||
Minimap,
|
|
||||||
IconButton,
|
|
||||||
PopoverButton,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
tabMinWidths: { type: Boolean, default: false },
|
tabMinWidths: { type: Boolean as PropType<boolean>, default: false },
|
||||||
tabCloseButtons: { type: Boolean, default: false },
|
tabCloseButtons: { type: Boolean as PropType<boolean>, default: false },
|
||||||
tabLabels: { type: Array as PropType<string[]>, required: true },
|
tabLabels: { type: Array as PropType<string[]>, required: true },
|
||||||
tabActiveIndex: { type: Number, required: true },
|
tabActiveIndex: { type: Number as PropType<number>, required: true },
|
||||||
panelType: { type: String, required: true },
|
panelType: { type: String as PropType<keyof typeof components>, required: true },
|
||||||
clickAction: { type: Function as PropType<(index: number) => void>, required: false },
|
clickAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||||
closeAction: { type: Function as PropType<(index: number) => void>, required: false },
|
closeAction: { type: Function as PropType<(index: number) => void>, required: false },
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
PopoverButtonIcon,
|
|
||||||
MenuDirection,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -67,10 +67,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import Panel from "@/components/workspace/Panel.vue";
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import DialogModal from "@/components/widgets/floating-menus/DialogModal.vue";
|
import DialogModal from "@/components/widgets/floating-menus/DialogModal.vue";
|
||||||
|
import Panel from "@/components/workspace/Panel.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["documents", "dialog", "editor"],
|
inject: ["documents", "dialog", "editor"],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { plainToInstance } from "class-transformer";
|
import { plainToInstance } from "class-transformer";
|
||||||
|
|
||||||
import { JsMessageType, messageConstructors, JsMessage } from "@/dispatcher/js-messages";
|
import { JsMessageType, messageConstructors, JsMessage } from "@/dispatcher/js-messages";
|
||||||
import type { RustEditorInstance, WasmInstance } from "@/state/wasm-loader";
|
import type { RustEditorInstance, WasmInstance } from "@/state/wasm-loader";
|
||||||
|
|
||||||
@@ -10,14 +11,15 @@ type JsMessageCallbackMap = {
|
|||||||
[message: string]: JsMessageCallback<any> | undefined;
|
[message: string]: JsMessageCallback<any> | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createJsDispatcher() {
|
export function createJsDispatcher() {
|
||||||
const subscriptions: JsMessageCallbackMap = {};
|
const subscriptions: JsMessageCallbackMap = {};
|
||||||
|
|
||||||
const subscribeJsMessage = <T extends JsMessage, Args extends unknown[]>(messageType: new (...args: Args) => T, callback: JsMessageCallback<T>) => {
|
const subscribeJsMessage = <T extends JsMessage, Args extends unknown[]>(messageType: new (...args: Args) => T, callback: JsMessageCallback<T>): void => {
|
||||||
subscriptions[messageType.name] = callback;
|
subscriptions[messageType.name] = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmInstance, instance: RustEditorInstance) => {
|
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmInstance, instance: RustEditorInstance): void => {
|
||||||
const messageConstructor = messageConstructors[messageType];
|
const messageConstructor = messageConstructors[messageType];
|
||||||
if (!messageConstructor) {
|
if (!messageConstructor) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export abstract class DocumentDetails {
|
|||||||
|
|
||||||
readonly id!: BigInt | string;
|
readonly id!: BigInt | string;
|
||||||
|
|
||||||
get displayName() {
|
get displayName(): string {
|
||||||
return `${this.name}${this.is_saved ? "" : "*"}`;
|
return `${this.name}${this.is_saved ? "" : "*"}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,25 +44,43 @@ export class UpdateOpenDocumentsList extends JsMessage {
|
|||||||
readonly open_documents!: FrontendDocumentDetails[];
|
readonly open_documents!: FrontendDocumentDetails[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HintData = HintInfo[][];
|
|
||||||
|
|
||||||
export class UpdateInputHints extends JsMessage {
|
export class UpdateInputHints extends JsMessage {
|
||||||
@Type(() => HintInfo)
|
@Type(() => HintInfo)
|
||||||
readonly hint_data!: HintData;
|
readonly hint_data!: HintData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type KeysGroup = string[];
|
export type HintData = HintGroup[];
|
||||||
|
|
||||||
|
export type HintGroup = HintInfo[];
|
||||||
|
|
||||||
export class HintInfo {
|
export class HintInfo {
|
||||||
readonly keys!: string[];
|
readonly key_groups!: KeysGroup[];
|
||||||
|
|
||||||
readonly mouse!: KeysGroup | null;
|
readonly mouse!: MouseMotion | null;
|
||||||
|
|
||||||
readonly label!: string;
|
readonly label!: string;
|
||||||
|
|
||||||
readonly plus!: boolean;
|
readonly plus!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type KeysGroup = string[]; // Array of Rust enum `Key`
|
||||||
|
|
||||||
|
export type MouseMotion = string;
|
||||||
|
|
||||||
|
export type RGBA = {
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
a: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HSVA = {
|
||||||
|
h: number;
|
||||||
|
s: number;
|
||||||
|
v: number;
|
||||||
|
a: number;
|
||||||
|
};
|
||||||
|
|
||||||
const To255Scale = Transform(({ value }) => value * 255);
|
const To255Scale = Transform(({ value }) => value * 255);
|
||||||
export class Color {
|
export class Color {
|
||||||
@To255Scale
|
@To255Scale
|
||||||
@@ -76,11 +94,11 @@ export class Color {
|
|||||||
|
|
||||||
readonly alpha!: number;
|
readonly alpha!: number;
|
||||||
|
|
||||||
toRgba() {
|
toRgba(): RGBA {
|
||||||
return { r: this.red, g: this.green, b: this.blue, a: this.alpha };
|
return { r: this.red, g: this.green, b: this.blue, a: this.alpha };
|
||||||
}
|
}
|
||||||
|
|
||||||
toRgbaCSS() {
|
toRgbaCSS(): string {
|
||||||
const { r, g, b, a } = this.toRgba();
|
const { r, g, b, a } = this.toRgba();
|
||||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
}
|
}
|
||||||
@@ -296,17 +314,7 @@ export class LayerMetadata {
|
|||||||
selected!: boolean;
|
selected!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LayerTypeOptions = {
|
export type LayerType = "Folder" | "Shape" | "Circle" | "Rect" | "Line" | "PolyLine" | "Ellipse";
|
||||||
Folder: "Folder",
|
|
||||||
Shape: "Shape",
|
|
||||||
Circle: "Circle",
|
|
||||||
Rect: "Rect",
|
|
||||||
Line: "Line",
|
|
||||||
PolyLine: "PolyLine",
|
|
||||||
Ellipse: "Ellipse",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type LayerType = typeof LayerTypeOptions[keyof typeof LayerTypeOptions];
|
|
||||||
|
|
||||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||||
@Transform(({ value }: { value: BigInt }) => value.toString())
|
@Transform(({ value }: { value: BigInt }) => value.toString())
|
||||||
|
|||||||
@@ -10,23 +10,24 @@ const GRAPHITE_AUTO_SAVE_ORDER_KEY = "auto-save-documents-order";
|
|||||||
const databaseConnection: Promise<IDBDatabase> = new Promise((resolve) => {
|
const databaseConnection: Promise<IDBDatabase> = new Promise((resolve) => {
|
||||||
const dbOpenRequest = indexedDB.open(GRAPHITE_INDEXED_DB_NAME, GRAPHITE_INDEXED_DB_VERSION);
|
const dbOpenRequest = indexedDB.open(GRAPHITE_INDEXED_DB_NAME, GRAPHITE_INDEXED_DB_VERSION);
|
||||||
|
|
||||||
dbOpenRequest.onupgradeneeded = () => {
|
dbOpenRequest.onupgradeneeded = (): void => {
|
||||||
const db = dbOpenRequest.result;
|
const db = dbOpenRequest.result;
|
||||||
if (!db.objectStoreNames.contains(GRAPHITE_AUTO_SAVE_STORE)) {
|
if (!db.objectStoreNames.contains(GRAPHITE_AUTO_SAVE_STORE)) {
|
||||||
db.createObjectStore(GRAPHITE_AUTO_SAVE_STORE, { keyPath: "details.id" });
|
db.createObjectStore(GRAPHITE_AUTO_SAVE_STORE, { keyPath: "details.id" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
dbOpenRequest.onerror = () => {
|
dbOpenRequest.onerror = (): void => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error("Graphite IndexedDb error:", dbOpenRequest.error);
|
console.error("Graphite IndexedDb error:", dbOpenRequest.error);
|
||||||
};
|
};
|
||||||
|
|
||||||
dbOpenRequest.onsuccess = () => {
|
dbOpenRequest.onsuccess = (): void => {
|
||||||
resolve(dbOpenRequest.result);
|
resolve(dbOpenRequest.result);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createAutoSaveManager(editor: EditorState, documents: DocumentsState) {
|
export function createAutoSaveManager(editor: EditorState, documents: DocumentsState) {
|
||||||
const openAutoSavedDocuments = async (): Promise<void> => {
|
const openAutoSavedDocuments = async (): Promise<void> => {
|
||||||
const db = await databaseConnection;
|
const db = await databaseConnection;
|
||||||
@@ -34,7 +35,7 @@ export function createAutoSaveManager(editor: EditorState, documents: DocumentsS
|
|||||||
const request = transaction.objectStore(GRAPHITE_AUTO_SAVE_STORE).getAll();
|
const request = transaction.objectStore(GRAPHITE_AUTO_SAVE_STORE).getAll();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
request.onsuccess = () => {
|
request.onsuccess = (): void => {
|
||||||
const previouslySavedDocuments: AutoSaveDocument[] = request.result;
|
const previouslySavedDocuments: AutoSaveDocument[] = request.result;
|
||||||
|
|
||||||
const documentOrder: string[] = JSON.parse(window.localStorage.getItem(GRAPHITE_AUTO_SAVE_ORDER_KEY) || "[]");
|
const documentOrder: string[] = JSON.parse(window.localStorage.getItem(GRAPHITE_AUTO_SAVE_ORDER_KEY) || "[]");
|
||||||
@@ -48,7 +49,7 @@ export function createAutoSaveManager(editor: EditorState, documents: DocumentsS
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const storeDocumentOrder = () => {
|
const storeDocumentOrder = (): void => {
|
||||||
// Make sure to store as string since JSON does not play nice with BigInt
|
// Make sure to store as string since JSON does not play nice with BigInt
|
||||||
const documentOrder = documents.state.documents.map((doc) => doc.id.toString());
|
const documentOrder = documents.state.documents.map((doc) => doc.id.toString());
|
||||||
window.localStorage.setItem(GRAPHITE_AUTO_SAVE_ORDER_KEY, JSON.stringify(documentOrder));
|
window.localStorage.setItem(GRAPHITE_AUTO_SAVE_ORDER_KEY, JSON.stringify(documentOrder));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { DialogState } from "@/state/dialog";
|
|
||||||
import { TextButtonWidget } from "@/components/widgets/widgets";
|
|
||||||
import { DisplayError, DisplayPanic } from "@/dispatcher/js-messages";
|
import { DisplayError, DisplayPanic } from "@/dispatcher/js-messages";
|
||||||
|
import { DialogState } from "@/state/dialog";
|
||||||
import { EditorState } from "@/state/wasm-loader";
|
import { EditorState } from "@/state/wasm-loader";
|
||||||
import { stripIndents } from "@/utilities/strip-indents";
|
import { stripIndents } from "@/utilities/strip-indents";
|
||||||
|
import { TextButtonWidget } from "@/utilities/widgets";
|
||||||
|
|
||||||
export function initErrorHandling(editor: EditorState, dialogState: DialogState) {
|
export function initErrorHandling(editor: EditorState, dialogState: DialogState): void {
|
||||||
// Graphite error dialog
|
// Graphite error dialog
|
||||||
editor.dispatcher.subscribeJsMessage(DisplayError, (displayError) => {
|
editor.dispatcher.subscribeJsMessage(DisplayError, (displayError) => {
|
||||||
const okButton: TextButtonWidget = {
|
const okButton: TextButtonWidget = {
|
||||||
@@ -49,7 +49,7 @@ export function initErrorHandling(editor: EditorState, dialogState: DialogState)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function githubUrl(panicDetails: string) {
|
function githubUrl(panicDetails: string): string {
|
||||||
const url = new URL("https://github.com/GraphiteEditor/Graphite/issues/new");
|
const url = new URL("https://github.com/GraphiteEditor/Graphite/issues/new");
|
||||||
|
|
||||||
const body = stripIndents`
|
const body = stripIndents`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DialogState } from "@/state/dialog";
|
import { DialogState } from "@/state/dialog";
|
||||||
import { FullscreenState } from "@/state/fullscreen";
|
|
||||||
import { DocumentsState } from "@/state/documents";
|
import { DocumentsState } from "@/state/documents";
|
||||||
|
import { FullscreenState } from "@/state/fullscreen";
|
||||||
import { EditorState } from "@/state/wasm-loader";
|
import { EditorState } from "@/state/wasm-loader";
|
||||||
|
|
||||||
type EventName = keyof HTMLElementEventMap | keyof WindowEventHandlersEventMap;
|
type EventName = keyof HTMLElementEventMap | keyof WindowEventHandlersEventMap;
|
||||||
@@ -9,20 +9,21 @@ interface EventListenerTarget {
|
|||||||
removeEventListener: typeof window.removeEventListener;
|
removeEventListener: typeof window.removeEventListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createInputManager(editor: EditorState, container: HTMLElement, dialog: DialogState, document: DocumentsState, fullscreen: FullscreenState) {
|
export function createInputManager(editor: EditorState, container: HTMLElement, dialog: DialogState, document: DocumentsState, fullscreen: FullscreenState) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
|
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
|
||||||
{ target: window, eventName: "resize", action: () => onWindowResize(container) },
|
{ target: window, eventName: "resize", action: (): void => onWindowResize(container) },
|
||||||
{ target: window, eventName: "beforeunload", action: (e) => onBeforeUnload(e) },
|
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): void => onBeforeUnload(e) },
|
||||||
{ target: window.document, eventName: "contextmenu", action: (e) => e.preventDefault() },
|
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent): void => e.preventDefault() },
|
||||||
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreen.fullscreenModeChanged() },
|
{ target: window.document, eventName: "fullscreenchange", action: (): void => fullscreen.fullscreenModeChanged() },
|
||||||
{ target: window, eventName: "keyup", action: (e) => onKeyUp(e) },
|
{ target: window, eventName: "keyup", action: (e: KeyboardEvent): void => onKeyUp(e) },
|
||||||
{ target: window, eventName: "keydown", action: (e) => onKeyDown(e) },
|
{ target: window, eventName: "keydown", action: (e: KeyboardEvent): void => onKeyDown(e) },
|
||||||
{ target: window, eventName: "pointermove", action: (e) => onPointerMove(e) },
|
{ target: window, eventName: "pointermove", action: (e: PointerEvent): void => onPointerMove(e) },
|
||||||
{ target: window, eventName: "pointerdown", action: (e) => onPointerDown(e) },
|
{ target: window, eventName: "pointerdown", action: (e: PointerEvent): void => onPointerDown(e) },
|
||||||
{ target: window, eventName: "pointerup", action: (e) => onPointerUp(e) },
|
{ target: window, eventName: "pointerup", action: (e: PointerEvent): void => onPointerUp(e) },
|
||||||
{ target: window, eventName: "mousedown", action: (e) => onMouseDown(e) },
|
{ target: window, eventName: "mousedown", action: (e: MouseEvent): void => onMouseDown(e) },
|
||||||
{ target: window, eventName: "wheel", action: (e) => onMouseScroll(e), options: { passive: false } },
|
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onMouseScroll(e), options: { passive: false } },
|
||||||
];
|
];
|
||||||
|
|
||||||
let viewportPointerInteractionOngoing = false;
|
let viewportPointerInteractionOngoing = false;
|
||||||
@@ -60,7 +61,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent): void => {
|
||||||
const key = getLatinKey(e);
|
const key = getLatinKey(e);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyUp = (e: KeyboardEvent) => {
|
const onKeyUp = (e: KeyboardEvent): void => {
|
||||||
const key = getLatinKey(e);
|
const key = getLatinKey(e);
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
@@ -95,14 +96,14 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
|
|
||||||
// Pointer events
|
// Pointer events
|
||||||
|
|
||||||
const onPointerMove = (e: PointerEvent) => {
|
const onPointerMove = (e: PointerEvent): void => {
|
||||||
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
||||||
|
|
||||||
const modifiers = makeModifiersBitfield(e);
|
const modifiers = makeModifiersBitfield(e);
|
||||||
editor.instance.on_mouse_move(e.clientX, e.clientY, e.buttons, modifiers);
|
editor.instance.on_mouse_move(e.clientX, e.clientY, e.buttons, modifiers);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPointerDown = (e: PointerEvent) => {
|
const onPointerDown = (e: PointerEvent): void => {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const inCanvas = target instanceof Element && target.closest(".canvas");
|
const inCanvas = target instanceof Element && target.closest(".canvas");
|
||||||
const inDialog = target instanceof Element && target.closest(".dialog-modal .floating-menu-content");
|
const inDialog = target instanceof Element && target.closest(".dialog-modal .floating-menu-content");
|
||||||
@@ -121,7 +122,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPointerUp = (e: PointerEvent) => {
|
const onPointerUp = (e: PointerEvent): void => {
|
||||||
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
||||||
|
|
||||||
const modifiers = makeModifiersBitfield(e);
|
const modifiers = makeModifiersBitfield(e);
|
||||||
@@ -130,13 +131,13 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
|
|
||||||
// Mouse events
|
// Mouse events
|
||||||
|
|
||||||
const onMouseDown = (e: MouseEvent) => {
|
const onMouseDown = (e: MouseEvent): void => {
|
||||||
// Block middle mouse button auto-scroll mode (the circlar widget that appears and allows quick scrolling by moving the cursor above or below it)
|
// Block middle mouse button auto-scroll mode (the circlar widget that appears and allows quick scrolling by moving the cursor above or below it)
|
||||||
// This has to be in `mousedown`, not `pointerdown`, to avoid blocking Vue's middle click detection on HTML elements
|
// This has to be in `mousedown`, not `pointerdown`, to avoid blocking Vue's middle click detection on HTML elements
|
||||||
if (e.button === 1) e.preventDefault();
|
if (e.button === 1) e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseScroll = (e: WheelEvent) => {
|
const onMouseScroll = (e: WheelEvent): void => {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const inCanvas = target instanceof Element && target.closest(".canvas");
|
const inCanvas = target instanceof Element && target.closest(".canvas");
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
|
|
||||||
// Window events
|
// Window events
|
||||||
|
|
||||||
const onWindowResize = (container: HTMLElement) => {
|
const onWindowResize = (container: HTMLElement): void => {
|
||||||
const viewports = Array.from(container.querySelectorAll(".canvas"));
|
const viewports = Array.from(container.querySelectorAll(".canvas"));
|
||||||
const boundsOfViewports = viewports.map((canvas) => {
|
const boundsOfViewports = viewports.map((canvas) => {
|
||||||
const bounds = canvas.getBoundingClientRect();
|
const bounds = canvas.getBoundingClientRect();
|
||||||
@@ -168,7 +169,7 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
if (boundsOfViewports.length > 0) editor.instance.bounds_of_viewports(data);
|
if (boundsOfViewports.length > 0) editor.instance.bounds_of_viewports(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
const onBeforeUnload = (e: BeforeUnloadEvent): void => {
|
||||||
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
|
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
|
||||||
if (!activeDocument.is_saved) editor.instance.trigger_auto_save(activeDocument.id);
|
if (!activeDocument.is_saved) editor.instance.trigger_auto_save(activeDocument.id);
|
||||||
|
|
||||||
@@ -187,11 +188,11 @@ export function createInputManager(editor: EditorState, container: HTMLElement,
|
|||||||
|
|
||||||
// Event bindings
|
// Event bindings
|
||||||
|
|
||||||
const addListeners = () => {
|
const addListeners = (): void => {
|
||||||
listeners.forEach(({ target, eventName, action, options }) => target.addEventListener(eventName, action, options));
|
listeners.forEach(({ target, eventName, action, options }) => target.addEventListener(eventName, action, options));
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeListeners = () => {
|
const removeListeners = (): void => {
|
||||||
listeners.forEach(({ target, eventName, action }) => target.removeEventListener(eventName, action));
|
listeners.forEach(({ target, eventName, action }) => target.removeEventListener(eventName, action));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { initWasm } from "@/state/wasm-loader";
|
|||||||
|
|
||||||
import App from "@/App.vue";
|
import App from "@/App.vue";
|
||||||
|
|
||||||
(async () => {
|
(async (): Promise<void> => {
|
||||||
// Initialize the WASM editor backend
|
// Initialize the WASM editor backend
|
||||||
await initWasm();
|
await initWasm();
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { reactive, readonly } from "vue";
|
import { reactive, readonly } from "vue";
|
||||||
|
|
||||||
import { TextButtonWidget } from "@/components/widgets/widgets";
|
|
||||||
import { EditorState } from "@/state/wasm-loader";
|
|
||||||
import { DisplayAboutGraphiteDialog } from "@/dispatcher/js-messages";
|
import { DisplayAboutGraphiteDialog } from "@/dispatcher/js-messages";
|
||||||
|
import { EditorState } from "@/state/wasm-loader";
|
||||||
import { stripIndents } from "@/utilities/strip-indents";
|
import { stripIndents } from "@/utilities/strip-indents";
|
||||||
|
import { TextButtonWidget } from "@/utilities/widgets";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createDialogState(editor: EditorState) {
|
export function createDialogState(editor: EditorState) {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -14,7 +15,7 @@ export function createDialogState(editor: EditorState) {
|
|||||||
buttons: [] as TextButtonWidget[],
|
buttons: [] as TextButtonWidget[],
|
||||||
});
|
});
|
||||||
|
|
||||||
const createDialog = (icon: string, heading: string, details: string, buttons: TextButtonWidget[]) => {
|
const createDialog = (icon: string, heading: string, details: string, buttons: TextButtonWidget[]): void => {
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
state.icon = icon;
|
state.icon = icon;
|
||||||
state.heading = heading;
|
state.heading = heading;
|
||||||
@@ -22,11 +23,11 @@ export function createDialogState(editor: EditorState) {
|
|||||||
state.buttons = buttons;
|
state.buttons = buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dismissDialog = () => {
|
const dismissDialog = (): void => {
|
||||||
state.visible = false;
|
state.visible = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitDialog = () => {
|
const submitDialog = (): void => {
|
||||||
const firstEmphasizedButton = state.buttons.find((button) => button.props.emphasized && button.callback);
|
const firstEmphasizedButton = state.buttons.find((button) => button.props.emphasized && button.callback);
|
||||||
if (firstEmphasizedButton) {
|
if (firstEmphasizedButton) {
|
||||||
// If statement satisfies TypeScript
|
// If statement satisfies TypeScript
|
||||||
@@ -38,7 +39,7 @@ export function createDialogState(editor: EditorState) {
|
|||||||
return state.visible;
|
return state.visible;
|
||||||
};
|
};
|
||||||
|
|
||||||
const comingSoon = (issueNumber?: number) => {
|
const comingSoon = (issueNumber?: number): void => {
|
||||||
const bugMessage = `— but you can help add it!\nSee issue #${issueNumber} on GitHub.`;
|
const bugMessage = `— but you can help add it!\nSee issue #${issueNumber} on GitHub.`;
|
||||||
const details = `This feature is not implemented yet${issueNumber ? bugMessage : ""}`;
|
const details = `This feature is not implemented yet${issueNumber ? bugMessage : ""}`;
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ export function createDialogState(editor: EditorState) {
|
|||||||
createDialog("Warning", "Coming soon", details, buttons);
|
createDialog("Warning", "Coming soon", details, buttons);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAboutHandler = () => {
|
const onAboutHandler = (): void => {
|
||||||
const date = new Date(process.env.VUE_APP_COMMIT_DATE || "");
|
const date = new Date(process.env.VUE_APP_COMMIT_DATE || "");
|
||||||
const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
||||||
const timeString = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
const timeString = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
||||||
@@ -80,22 +81,22 @@ export function createDialogState(editor: EditorState) {
|
|||||||
const buttons: TextButtonWidget[] = [
|
const buttons: TextButtonWidget[] = [
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => window.open("https://www.graphite.design", "_blank"),
|
callback: (): unknown => window.open("https://www.graphite.design", "_blank"),
|
||||||
props: { label: "Website", emphasized: false, minWidth: 0 },
|
props: { label: "Website", emphasized: false, minWidth: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => window.open("https://github.com/GraphiteEditor/Graphite/graphs/contributors", "_blank"),
|
callback: (): unknown => window.open("https://github.com/GraphiteEditor/Graphite/graphs/contributors", "_blank"),
|
||||||
props: { label: "Credits", emphasized: false, minWidth: 0 },
|
props: { label: "Credits", emphasized: false, minWidth: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => window.open("https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/LICENSE.txt", "_blank"),
|
callback: (): unknown => window.open("https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/LICENSE.txt", "_blank"),
|
||||||
props: { label: "License", emphasized: false, minWidth: 0 },
|
props: { label: "License", emphasized: false, minWidth: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => window.open("/third-party-licenses.txt", "_blank"),
|
callback: (): unknown => window.open("/third-party-licenses.txt", "_blank"),
|
||||||
props: { label: "Third-Party Licenses", emphasized: false, minWidth: 0 },
|
props: { label: "Third-Party Licenses", emphasized: false, minWidth: 0 },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
import { reactive, readonly } from "vue";
|
import { reactive, readonly } from "vue";
|
||||||
|
|
||||||
import { DialogState } from "@/state/dialog";
|
|
||||||
import { download, upload } from "@/utilities/files";
|
|
||||||
import { EditorState } from "@/state/wasm-loader";
|
|
||||||
import {
|
import {
|
||||||
DisplayConfirmationToCloseAllDocuments,
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
DisplayConfirmationToCloseDocument,
|
DisplayConfirmationToCloseDocument,
|
||||||
@@ -14,7 +11,11 @@ import {
|
|||||||
SetActiveDocument,
|
SetActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
} from "@/dispatcher/js-messages";
|
} from "@/dispatcher/js-messages";
|
||||||
|
import { DialogState } from "@/state/dialog";
|
||||||
|
import { EditorState } from "@/state/wasm-loader";
|
||||||
|
import { download, upload } from "@/utilities/files";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createDocumentsState(editor: EditorState, dialogState: DialogState) {
|
export function createDocumentsState(editor: EditorState, dialogState: DialogState) {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
unsaved: false,
|
unsaved: false,
|
||||||
@@ -22,7 +23,7 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
activeDocumentIndex: 0,
|
activeDocumentIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeDocumentWithConfirmation = async (documentId: BigInt) => {
|
const closeDocumentWithConfirmation = async (documentId: BigInt): Promise<void> => {
|
||||||
// Assume we receive a correct document_id
|
// Assume we receive a correct document_id
|
||||||
const targetDocument = state.documents.find((doc) => doc.id === documentId) as FrontendDocumentDetails;
|
const targetDocument = state.documents.find((doc) => doc.id === documentId) as FrontendDocumentDetails;
|
||||||
const tabLabel = targetDocument.displayName;
|
const tabLabel = targetDocument.displayName;
|
||||||
@@ -31,7 +32,7 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
dialogState.createDialog("File", "Save changes before closing?", tabLabel, [
|
dialogState.createDialog("File", "Save changes before closing?", tabLabel, [
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: async () => {
|
callback: async (): Promise<void> => {
|
||||||
editor.instance.save_document();
|
editor.instance.save_document();
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
@@ -39,7 +40,7 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: async () => {
|
callback: async (): Promise<void> => {
|
||||||
editor.instance.close_document(targetDocument.id);
|
editor.instance.close_document(targetDocument.id);
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
@@ -47,7 +48,7 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: async () => {
|
callback: async (): Promise<void> => {
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
props: { label: "Cancel", minWidth: 96 },
|
props: { label: "Cancel", minWidth: 96 },
|
||||||
@@ -55,11 +56,11 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeAllDocumentsWithConfirmation = () => {
|
const closeAllDocumentsWithConfirmation = (): void => {
|
||||||
dialogState.createDialog("Copy", "Close all documents?", "Unsaved work will be lost!", [
|
dialogState.createDialog("Copy", "Close all documents?", "Unsaved work will be lost!", [
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => {
|
callback: (): void => {
|
||||||
editor.instance.close_all_documents();
|
editor.instance.close_all_documents();
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
@@ -67,7 +68,7 @@ export function createDocumentsState(editor: EditorState, dialogState: DialogSta
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: () => {
|
callback: (): void => {
|
||||||
dialogState.dismissDialog();
|
dialogState.dismissDialog();
|
||||||
},
|
},
|
||||||
props: { label: "Cancel", minWidth: 96 },
|
props: { label: "Cancel", minWidth: 96 },
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { reactive, readonly } from "vue";
|
import { reactive, readonly } from "vue";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createFullscreenState() {
|
export function createFullscreenState() {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
windowFullscreen: false,
|
windowFullscreen: false,
|
||||||
keyboardLocked: false,
|
keyboardLocked: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fullscreenModeChanged = () => {
|
const fullscreenModeChanged = (): void => {
|
||||||
state.windowFullscreen = Boolean(document.fullscreenElement);
|
state.windowFullscreen = Boolean(document.fullscreenElement);
|
||||||
if (!state.windowFullscreen) state.keyboardLocked = false;
|
if (!state.windowFullscreen) state.keyboardLocked = false;
|
||||||
};
|
};
|
||||||
@@ -15,7 +16,7 @@ export function createFullscreenState() {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const keyboardLockApiSupported: Readonly<boolean> = "keyboard" in navigator && "lock" in (navigator as any).keyboard;
|
const keyboardLockApiSupported: Readonly<boolean> = "keyboard" in navigator && "lock" in (navigator as any).keyboard;
|
||||||
|
|
||||||
const enterFullscreen = async () => {
|
const enterFullscreen = async (): Promise<void> => {
|
||||||
await document.documentElement.requestFullscreen();
|
await document.documentElement.requestFullscreen();
|
||||||
|
|
||||||
if (keyboardLockApiSupported) {
|
if (keyboardLockApiSupported) {
|
||||||
@@ -26,11 +27,11 @@ export function createFullscreenState() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
const exitFullscreen = async () => {
|
const exitFullscreen = async (): Promise<void> => {
|
||||||
await document.exitFullscreen();
|
await document.exitFullscreen();
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFullscreen = async () => {
|
const toggleFullscreen = async (): Promise<void> => {
|
||||||
if (state.windowFullscreen) await exitFullscreen();
|
if (state.windowFullscreen) await exitFullscreen();
|
||||||
else await enterFullscreen();
|
else await enterFullscreen();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export type WasmInstance = typeof import("@/../wasm/pkg");
|
|||||||
export type RustEditorInstance = InstanceType<WasmInstance["JsEditorHandle"]>;
|
export type RustEditorInstance = InstanceType<WasmInstance["JsEditorHandle"]>;
|
||||||
|
|
||||||
let wasmImport: WasmInstance | null = null;
|
let wasmImport: WasmInstance | null = null;
|
||||||
export async function initWasm() {
|
export async function initWasm(): Promise<void> {
|
||||||
if (wasmImport !== null) return;
|
if (wasmImport !== null) return;
|
||||||
|
|
||||||
// Separating in two lines satisfies typescript when used below
|
// Separating in two lines satisfies typescript when used below
|
||||||
@@ -32,7 +32,7 @@ function panicProxy<T extends object>(module: T): T {
|
|||||||
// Special handling to wrap the return of a constructor in the proxy
|
// Special handling to wrap the return of a constructor in the proxy
|
||||||
const isClass = isFunction && /^\s*class\s+/.test(targetValue.toString());
|
const isClass = isFunction && /^\s*class\s+/.test(targetValue.toString());
|
||||||
if (isClass) {
|
if (isClass) {
|
||||||
return function (...args: unknown[]) {
|
return function (...args: unknown[]): unknown {
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const result = new targetValue(...args);
|
const result = new targetValue(...args);
|
||||||
return panicProxy(result);
|
return panicProxy(result);
|
||||||
@@ -40,7 +40,7 @@ function panicProxy<T extends object>(module: T): T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the original function with a wrapper function that runs the original in a try-catch block
|
// Replace the original function with a wrapper function that runs the original in a try-catch block
|
||||||
return function (...args: unknown[]) {
|
return function (...args: unknown[]): unknown {
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error TypeScript does not know what `this` is, since it should be able to be anything
|
// @ts-expect-error TypeScript does not know what `this` is, since it should be able to be anything
|
||||||
@@ -57,16 +57,17 @@ function panicProxy<T extends object>(module: T): T {
|
|||||||
return new Proxy<T>(module, proxyHandler);
|
return new Proxy<T>(module, proxyHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWasmInstance() {
|
function getWasmInstance(): WasmInstance {
|
||||||
if (wasmImport) return wasmImport;
|
if (wasmImport) return wasmImport;
|
||||||
throw new Error("Editor WASM backend was not initialized at application startup");
|
throw new Error("Editor WASM backend was not initialized at application startup");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEditorState() {
|
type CreateEditorStateType = { dispatcher: ReturnType<typeof createJsDispatcher>; rawWasm: WasmInstance; instance: RustEditorInstance };
|
||||||
|
export function createEditorState(): CreateEditorStateType {
|
||||||
const dispatcher = createJsDispatcher();
|
const dispatcher = createJsDispatcher();
|
||||||
const rawWasm = getWasmInstance();
|
const rawWasm = getWasmInstance();
|
||||||
|
|
||||||
const rustCallback = (messageType: JsMessageType, data: Record<string, unknown>) => {
|
const rustCallback = (messageType: JsMessageType, data: Record<string, unknown>): void => {
|
||||||
dispatcher.handleJsMessage(messageType, data, rawWasm, instance);
|
dispatcher.handleJsMessage(messageType, data, rawWasm, instance);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +1,36 @@
|
|||||||
export interface RGB {
|
import { HSVA, RGBA } from "@/dispatcher/js-messages";
|
||||||
r: number;
|
|
||||||
g: number;
|
|
||||||
b: number;
|
|
||||||
a: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HSV {
|
export function hsvaToRgba(hsva: HSVA): RGBA {
|
||||||
h: number;
|
const { h, s, v, a } = hsva;
|
||||||
s: number;
|
|
||||||
v: number;
|
const hue = h * 6;
|
||||||
a: number;
|
const hueIntegerPart = Math.floor(hue);
|
||||||
}
|
const hueFractionalPart = hue - hueIntegerPart;
|
||||||
|
const hueIntegerMod6 = hueIntegerPart % 6;
|
||||||
|
|
||||||
export function hsvToRgb(hsv: HSV): RGB {
|
|
||||||
let { h } = hsv;
|
|
||||||
const { s, v } = hsv;
|
|
||||||
h *= 6;
|
|
||||||
const i = Math.floor(h);
|
|
||||||
const f = h - i;
|
|
||||||
const p = v * (1 - s);
|
const p = v * (1 - s);
|
||||||
const q = v * (1 - f * s);
|
const q = v * (1 - hueFractionalPart * s);
|
||||||
const t = v * (1 - (1 - f) * s);
|
const t = v * (1 - (1 - hueFractionalPart) * s);
|
||||||
const mod = i % 6;
|
|
||||||
const r = Math.round([v, q, p, p, t, v][mod]);
|
const r = Math.round([v, q, p, p, t, v][hueIntegerMod6]);
|
||||||
const g = Math.round([t, v, v, q, p, p][mod]);
|
const g = Math.round([t, v, v, q, p, p][hueIntegerMod6]);
|
||||||
const b = Math.round([p, p, t, v, v, q][mod]);
|
const b = Math.round([p, p, t, v, v, q][hueIntegerMod6]);
|
||||||
return { r, g, b, a: hsv.a };
|
|
||||||
|
return { r, g, b, a };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rgbToHsv(rgb: RGB) {
|
export function rgbaToHsva(rgba: RGBA): HSVA {
|
||||||
const { r, g, b } = rgb;
|
const { r, g, b, a } = rgba;
|
||||||
|
|
||||||
const max = Math.max(r, g, b);
|
const max = Math.max(r, g, b);
|
||||||
const min = Math.min(r, g, b);
|
const min = Math.min(r, g, b);
|
||||||
|
|
||||||
const d = max - min;
|
const d = max - min;
|
||||||
const s = max === 0 ? 0 : d / max;
|
const s = max === 0 ? 0 : d / max;
|
||||||
const v = max;
|
const v = max;
|
||||||
|
|
||||||
let h = 0;
|
let h = 0;
|
||||||
if (max === min) {
|
if (max !== min) {
|
||||||
h = 0;
|
|
||||||
} else {
|
|
||||||
switch (max) {
|
switch (max) {
|
||||||
case r:
|
case r:
|
||||||
h = (g - b) / d + (g < b ? 6 : 0);
|
h = (g - b) / d + (g < b ? 6 : 0);
|
||||||
@@ -53,27 +45,14 @@ export function rgbToHsv(rgb: RGB) {
|
|||||||
}
|
}
|
||||||
h /= 6;
|
h /= 6;
|
||||||
}
|
}
|
||||||
return { h, s, v, a: rgb.a };
|
|
||||||
|
return { h, s, v, a };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rgbToDecimalRgb(rgb: RGB) {
|
export function rgbaToDecimalRgba(rgba: RGBA): RGBA {
|
||||||
const r = rgb.r / 255;
|
const r = rgba.r / 255;
|
||||||
const g = rgb.g / 255;
|
const g = rgba.g / 255;
|
||||||
const b = rgb.b / 255;
|
const b = rgba.b / 255;
|
||||||
return { r, g, b, a: rgb.a };
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return { r, g, b, a: rgba.a };
|
||||||
export function isRGB(data: any): data is RGB {
|
|
||||||
if (typeof data !== "object" || data === null) return false;
|
|
||||||
return (
|
|
||||||
typeof data.r === "number" &&
|
|
||||||
!Number.isNaN(data.r) &&
|
|
||||||
typeof data.g === "number" &&
|
|
||||||
!Number.isNaN(data.g) &&
|
|
||||||
typeof data.b === "number" &&
|
|
||||||
!Number.isNaN(data.b) &&
|
|
||||||
typeof data.a === "number" &&
|
|
||||||
!Number.isNaN(data.a)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function download(filename: string, fileData: string) {
|
export function download(filename: string, fileData: string): void {
|
||||||
const type = filename.endsWith(".svg") ? "image/svg+xml;charset=utf-8" : "text/plain;charset=utf-8";
|
const type = filename.endsWith(".svg") ? "image/svg+xml;charset=utf-8" : "text/plain;charset=utf-8";
|
||||||
const blob = new Blob([fileData], { type });
|
const blob = new Blob([fileData], { type });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
@@ -11,7 +11,7 @@ export function download(filename: string, fileData: string) {
|
|||||||
element.click();
|
element.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upload(acceptedEextensions: string) {
|
export async function upload(acceptedEextensions: string): Promise<{ filename: string; content: string }> {
|
||||||
return new Promise<{ filename: string; content: string }>((resolve, _) => {
|
return new Promise<{ filename: string; content: string }>((resolve, _) => {
|
||||||
const element = document.createElement("input");
|
const element = document.createElement("input");
|
||||||
element.type = "file";
|
element.type = "file";
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export function clamp(value: number, min = 0, max = 1) {
|
export function clamp(value: number, min = 0, max = 1): number {
|
||||||
return Math.max(min, Math.min(value, max));
|
return Math.max(min, Math.min(value, max));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export function stripIndents(stringPieces: TemplateStringsArray, ...substitutions: unknown[]) {
|
export function stripIndents(stringPieces: TemplateStringsArray, ...substitutions: unknown[]): string {
|
||||||
const interleavedSubstitutions = stringPieces.flatMap((stringPiece, index) => [stringPiece, substitutions[index] !== undefined ? substitutions[index] : ""]);
|
const interleavedSubstitutions = stringPieces.flatMap((stringPiece, index) => [stringPiece, substitutions[index] !== undefined ? substitutions[index] : ""]);
|
||||||
const stringLines = interleavedSubstitutions.join("").split("\n");
|
const stringLines = interleavedSubstitutions.join("").split("\n");
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ export interface NumberInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
|
export type SeparatorDirection = "Horizontal" | "Vertical";
|
||||||
|
export type SeparatorType = "Related" | "Unrelated" | "Section" | "List";
|
||||||
|
|
||||||
export interface SeparatorWidget {
|
export interface SeparatorWidget {
|
||||||
kind: "Separator";
|
kind: "Separator";
|
||||||
props: SeparatorProps;
|
props: SeparatorProps;
|
||||||
@@ -83,15 +86,3 @@ export interface SeparatorProps {
|
|||||||
direction?: SeparatorDirection;
|
direction?: SeparatorDirection;
|
||||||
type?: SeparatorType;
|
type?: SeparatorType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SeparatorDirection {
|
|
||||||
"Horizontal" = "Horizontal",
|
|
||||||
"Vertical" = "Vertical",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum SeparatorType {
|
|
||||||
"Related" = "Related",
|
|
||||||
"Unrelated" = "Unrelated",
|
|
||||||
"Section" = "Section",
|
|
||||||
"List" = "List",
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
/* eslint-disable @typescript-eslint/no-var-requires, no-console */
|
||||||
const path = require("path");
|
|
||||||
const { execSync, spawnSync } = require("child_process");
|
const { execSync, spawnSync } = require("child_process");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
||||||
const LicenseCheckerWebpackPlugin = require("license-checker-webpack-plugin");
|
const LicenseCheckerWebpackPlugin = require("license-checker-webpack-plugin");
|
||||||
|
|||||||
Reference in New Issue
Block a user