mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 21:48:12 +08:00
Change responses to use classes instead of interfaces (#394)
* ability to mark an open document as unsaved * unsaved detection now being triggered based on layer tree height * Changed responses to use classes instead of interfaces * - rust implementation of unsaved markers - upgraded eslint * updated eslint in package.json * - Renamed GetOpenDocumentsList -> UpdateOpenDocumentsList - is not -> was not * changed hash to current identifier to better reflect its meaning * resolve some merge conflicts * removed console.log statement leftover from debuging * - changed Response to jsMessage - split files - Array<> -> [] * -remove path from UpdateLayer * - remove unused if statements * - comment for reflect-metadata - registerJsMessageHandler -> subscribeJsMessage - readonly message properties - fixed binding filename and comment - toRgb -> toRgba * - newOpacity -> transformer - added comments * MessageMaker -> messageMaker
This commit is contained in:
@@ -153,7 +153,7 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
self.layer_data(path).selected = true;
|
self.layer_data(path).selected = true;
|
||||||
let data = self.layer_panel_entry(path.to_vec()).ok()?;
|
let data = self.layer_panel_entry(path.to_vec()).ok()?;
|
||||||
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { path: path.to_vec().into(), data }.into())
|
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { data }.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||||
@@ -621,7 +621,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
LayerChanged(path) => {
|
LayerChanged(path) => {
|
||||||
responses.extend(self.layer_panel_entry(path.clone()).ok().and_then(|entry| {
|
responses.extend(self.layer_panel_entry(path.clone()).ok().and_then(|entry| {
|
||||||
let overlay = self.graphene_document.layer(&path).unwrap().overlay;
|
let overlay = self.graphene_document.layer(&path).unwrap().overlay;
|
||||||
(!overlay).then(|| FrontendMessage::UpdateLayer { path: path.into(), data: entry }.into())
|
(!overlay).then(|| FrontendMessage::UpdateLayer { data: entry }.into())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::document::layer_panel::{LayerPanelEntry, Path, RawBuffer};
|
use crate::document::layer_panel::{LayerPanelEntry, RawBuffer};
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::tool_options::ToolOptions;
|
use crate::tool::tool_options::ToolOptions;
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
@@ -15,7 +15,7 @@ pub enum FrontendMessage {
|
|||||||
DisplayPanic { panic_info: String, title: String, description: String },
|
DisplayPanic { panic_info: String, title: String, description: String },
|
||||||
DisplayConfirmationToCloseDocument { document_index: usize },
|
DisplayConfirmationToCloseDocument { document_index: usize },
|
||||||
DisplayConfirmationToCloseAllDocuments,
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
UpdateLayer { path: Path, data: LayerPanelEntry },
|
UpdateLayer { data: LayerPanelEntry },
|
||||||
UpdateCanvas { document: String },
|
UpdateCanvas { document: String },
|
||||||
UpdateScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
|
UpdateScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
|
||||||
UpdateRulers { origin: (f64, f64), spacing: f64, interval: f64 },
|
UpdateRulers { origin: (f64, f64), spacing: f64, interval: f64 },
|
||||||
|
|||||||
Generated
+1796
-6071
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,8 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"homepage": "https://www.graphite.design",
|
"homepage": "https://www.graphite.design",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"class-transformer": "^0.5.0",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
"vue": "^3.2.23"
|
"vue": "^3.2.23"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -40,7 +42,9 @@
|
|||||||
"sass-loader": "^10.0.0",
|
"sass-loader": "^10.0.0",
|
||||||
"typescript": "^4.5.2",
|
"typescript": "^4.5.2",
|
||||||
"vue-loader": "^16.8.3",
|
"vue-loader": "^16.8.3",
|
||||||
"vue-template-compiler": "^2.6.14",
|
"vue-template-compiler": "^2.6.14"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
"wasm-pack": "^0.10.1"
|
"wasm-pack": "^0.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,7 +238,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { ResponseType, registerResponseHandler, Response, UpdateCanvas, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation } from "@/utilities/response-handler";
|
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
|
import { UpdateCanvas, UpdateScrollbars, UpdateRulers, SetActiveTool, SetCanvasZoom, SetCanvasRotation } from "@/utilities/js-messages";
|
||||||
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
import { SeparatorDirection, SeparatorType } from "@/components/widgets/widgets";
|
||||||
import { comingSoon } from "@/utilities/errors";
|
import { comingSoon } from "@/utilities/errors";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
@@ -332,50 +333,34 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
registerResponseHandler(ResponseType.UpdateCanvas, (responseData: Response) => {
|
subscribeJsMessage(UpdateCanvas, (updateCanvas) => {
|
||||||
const updateData = responseData as UpdateCanvas;
|
this.viewportSvg = updateCanvas.document;
|
||||||
if (updateData) this.viewportSvg = updateData.document;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.UpdateScrollbars, (responseData: Response) => {
|
subscribeJsMessage(UpdateScrollbars, (updateScrollbars) => {
|
||||||
const updateData = responseData as UpdateScrollbars;
|
this.scrollbarPos = updateScrollbars.position;
|
||||||
if (updateData) {
|
this.scrollbarSize = updateScrollbars.size;
|
||||||
this.scrollbarPos = updateData.position;
|
this.scrollbarMultiplier = updateScrollbars.multiplier;
|
||||||
this.scrollbarSize = updateData.size;
|
|
||||||
this.scrollbarMultiplier = updateData.multiplier;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.UpdateRulers, (responseData: Response) => {
|
subscribeJsMessage(UpdateRulers, (updateRulers) => {
|
||||||
const updateData = responseData as UpdateRulers;
|
this.rulerOrigin = updateRulers.origin;
|
||||||
if (updateData) {
|
this.rulerSpacing = updateRulers.spacing;
|
||||||
this.rulerOrigin = updateData.origin;
|
this.rulerInterval = updateRulers.interval;
|
||||||
this.rulerSpacing = updateData.spacing;
|
|
||||||
this.rulerInterval = updateData.interval;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SetActiveTool, (responseData: Response) => {
|
subscribeJsMessage(SetActiveTool, (setActiveTool) => {
|
||||||
const toolData = responseData as SetActiveTool;
|
this.activeTool = setActiveTool.tool_name;
|
||||||
if (toolData) {
|
this.activeToolOptions = setActiveTool.tool_options;
|
||||||
this.activeTool = toolData.tool_name;
|
|
||||||
this.activeToolOptions = toolData.tool_options;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SetCanvasZoom, (responseData: Response) => {
|
subscribeJsMessage(SetCanvasZoom, (setCanvasZoom) => {
|
||||||
const updateData = responseData as SetCanvasZoom;
|
this.documentZoom = setCanvasZoom.new_zoom * 100;
|
||||||
if (updateData) {
|
|
||||||
this.documentZoom = updateData.new_zoom * 100;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SetCanvasRotation, (responseData: Response) => {
|
subscribeJsMessage(SetCanvasRotation, (setCanvasRotation) => {
|
||||||
const updateData = responseData as SetCanvasRotation;
|
const newRotation = setCanvasRotation.new_radians * (180 / Math.PI);
|
||||||
if (updateData) {
|
this.documentRotation = (360 + (newRotation % 360)) % 360;
|
||||||
const newRotation = updateData.new_radians * (180 / Math.PI);
|
|
||||||
this.documentRotation = (360 + (newRotation % 360)) % 360;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("resize", this.viewportResize);
|
window.addEventListener("resize", this.viewportResize);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-if="layer.layer_type === LayerType.Folder"
|
v-if="layer.layer_type === LayerTypeOptions.Folder"
|
||||||
class="node-connector"
|
class="node-connector"
|
||||||
:class="{ expanded: layer.layer_data.expanded }"
|
:class="{ expanded: layer.layer_data.expanded }"
|
||||||
@click.stop="handleNodeConnectorClick(layer.path)"
|
@click.stop="handleNodeConnectorClick(layer.path)"
|
||||||
@@ -43,7 +43,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 === LayerType.Folder" :icon="'NodeTypeFolder'" title="Folder" />
|
<IconLabel v-if="layer.layer_type === LayerTypeOptions.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">
|
||||||
@@ -197,7 +197,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { ResponseType, registerResponseHandler, Response, BlendMode, DisplayFolderTreeStructure, UpdateLayer, LayerPanelEntry, LayerType } from "@/utilities/response-handler";
|
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
|
import { BlendMode, DisplayFolderTreeStructure, UpdateLayer, LayerPanelEntry, LayerTypeOptions } from "@/utilities/js-messages";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
import { SeparatorType } from "@/components/widgets/widgets";
|
import { SeparatorType } from "@/components/widgets/widgets";
|
||||||
|
|
||||||
@@ -214,42 +215,42 @@ import { SectionsOfMenuListEntries } from "@/components/widgets/floating-menus/M
|
|||||||
|
|
||||||
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
const wasm = import("@/../wasm/pkg").then(panicProxy);
|
||||||
|
|
||||||
const blendModeEntries: SectionsOfMenuListEntries = [
|
const blendModeEntries: SectionsOfMenuListEntries<BlendMode> = [
|
||||||
[{ label: "Normal", value: BlendMode.Normal }],
|
[{ label: "Normal", value: "normal" }],
|
||||||
[
|
[
|
||||||
{ label: "Multiply", value: BlendMode.Multiply },
|
{ label: "Multiply", value: "multiply" },
|
||||||
{ label: "Darken", value: BlendMode.Darken },
|
{ label: "Darken", value: "darken" },
|
||||||
{ label: "Color Burn", value: BlendMode.ColorBurn },
|
{ label: "Color Burn", value: "color-burn" },
|
||||||
// { label: "Linear Burn", value: "" }, // Not supported by SVG
|
// { label: "Linear Burn", value: "" }, // Not supported by SVG
|
||||||
// { label: "Darker Color", value: "" }, // Not supported by SVG
|
// { label: "Darker Color", value: "" }, // Not supported by SVG
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Screen", value: BlendMode.Screen },
|
{ label: "Screen", value: "screen" },
|
||||||
{ label: "Lighten", value: BlendMode.Lighten },
|
{ label: "Lighten", value: "lighten" },
|
||||||
{ label: "Color Dodge", value: BlendMode.ColorDodge },
|
{ label: "Color Dodge", value: "color-dodge" },
|
||||||
// { label: "Linear Dodge (Add)", value: "" }, // Not supported by SVG
|
// { label: "Linear Dodge (Add)", value: "" }, // Not supported by SVG
|
||||||
// { label: "Lighter Color", value: "" }, // Not supported by SVG
|
// { label: "Lighter Color", value: "" }, // Not supported by SVG
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Overlay", value: BlendMode.Overlay },
|
{ label: "Overlay", value: "overlay" },
|
||||||
{ label: "Soft Light", value: BlendMode.SoftLight },
|
{ label: "Soft Light", value: "soft-light" },
|
||||||
{ label: "Hard Light", value: BlendMode.HardLight },
|
{ label: "Hard Light", value: "hard-light" },
|
||||||
// { label: "Vivid Light", value: "" }, // Not supported by SVG
|
// { label: "Vivid Light", value: "" }, // Not supported by SVG
|
||||||
// { label: "Linear Light", value: "" }, // Not supported by SVG
|
// { label: "Linear Light", value: "" }, // Not supported by SVG
|
||||||
// { label: "Pin Light", value: "" }, // Not supported by SVG
|
// { label: "Pin Light", value: "" }, // Not supported by SVG
|
||||||
// { label: "Hard Mix", value: "" }, // Not supported by SVG
|
// { label: "Hard Mix", value: "" }, // Not supported by SVG
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Difference", value: BlendMode.Difference },
|
{ label: "Difference", value: "difference" },
|
||||||
{ label: "Exclusion", value: BlendMode.Exclusion },
|
{ label: "Exclusion", value: "exclusion" },
|
||||||
// { label: "Subtract", value: "" }, // Not supported by SVG
|
// { label: "Subtract", value: "" }, // Not supported by SVG
|
||||||
// { label: "Divide", value: "" }, // Not supported by SVG
|
// { label: "Divide", value: "" }, // Not supported by SVG
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ label: "Hue", value: BlendMode.Hue },
|
{ label: "Hue", value: "hue" },
|
||||||
{ label: "Saturation", value: BlendMode.Saturation },
|
{ label: "Saturation", value: "saturation" },
|
||||||
{ label: "Color", value: BlendMode.Color },
|
{ label: "Color", value: "color" },
|
||||||
{ label: "Luminosity", value: BlendMode.Luminosity },
|
{ label: "Luminosity", value: "luminosity" },
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -262,14 +263,14 @@ export default defineComponent({
|
|||||||
opacityNumberInputDisabled: true,
|
opacityNumberInputDisabled: true,
|
||||||
// TODO: replace with BigUint64Array as index
|
// TODO: replace with BigUint64Array as index
|
||||||
layerCache: new Map() as Map<string, LayerPanelEntry>,
|
layerCache: new Map() as Map<string, LayerPanelEntry>,
|
||||||
layers: [] as Array<LayerPanelEntry>,
|
layers: [] as LayerPanelEntry[],
|
||||||
layerDepths: [] as Array<number>,
|
layerDepths: [] as number[],
|
||||||
selectionRangeStartLayer: undefined as undefined | LayerPanelEntry,
|
selectionRangeStartLayer: undefined as undefined | LayerPanelEntry,
|
||||||
selectionRangeEndLayer: undefined as undefined | LayerPanelEntry,
|
selectionRangeEndLayer: undefined as undefined | LayerPanelEntry,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
MenuDirection,
|
MenuDirection,
|
||||||
SeparatorType,
|
SeparatorType,
|
||||||
LayerType,
|
LayerTypeOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -406,13 +407,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
registerResponseHandler(ResponseType.DisplayFolderTreeStructure, (responseData: Response) => {
|
subscribeJsMessage(DisplayFolderTreeStructure, (displayFolderTreeStructure) => {
|
||||||
const expandData = responseData as DisplayFolderTreeStructure;
|
const path = [] as bigint[];
|
||||||
if (!expandData) return;
|
this.layers = [] as LayerPanelEntry[];
|
||||||
|
function recurse(folder: DisplayFolderTreeStructure, layers: LayerPanelEntry[], cache: Map<string, LayerPanelEntry>) {
|
||||||
const path = [] as Array<bigint>;
|
|
||||||
this.layers = [] as Array<LayerPanelEntry>;
|
|
||||||
function recurse(folder: DisplayFolderTreeStructure, layers: Array<LayerPanelEntry>, cache: Map<string, LayerPanelEntry>) {
|
|
||||||
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()));
|
||||||
@@ -422,21 +420,21 @@ export default defineComponent({
|
|||||||
path.pop();
|
path.pop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
recurse(expandData, this.layers, this.layerCache);
|
recurse(displayFolderTreeStructure, this.layers, this.layerCache);
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.UpdateLayer, (responseData) => {
|
subscribeJsMessage(UpdateLayer, (updateLayer) => {
|
||||||
const updateData = responseData as UpdateLayer;
|
const responsePath = updateLayer.data.path;
|
||||||
if (updateData) {
|
const responseLayer = updateLayer.data;
|
||||||
const responsePath = updateData.path;
|
|
||||||
const responseLayer = updateData.data;
|
|
||||||
|
|
||||||
const layer = this.layerCache.get(responsePath.toString());
|
const layer = this.layerCache.get(responsePath.toString());
|
||||||
if (layer) Object.assign(this.layerCache.get(responsePath.toString()), responseLayer);
|
if (layer) {
|
||||||
else this.layerCache.set(responsePath.toString(), responseLayer);
|
Object.assign(this.layerCache.get(responsePath.toString()), responseLayer);
|
||||||
this.setBlendModeForSelectedLayers();
|
} else {
|
||||||
this.setOpacityForSelectedLayers();
|
this.layerCache.set(responsePath.toString(), responseLayer);
|
||||||
}
|
}
|
||||||
|
this.setBlendModeForSelectedLayers();
|
||||||
|
this.setOpacityForSelectedLayers();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -143,21 +143,21 @@ 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 UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
|
|
||||||
export type MenuListEntries = Array<MenuListEntry>;
|
export type MenuListEntries<Value = string> = MenuListEntry<Value>[];
|
||||||
export type SectionsOfMenuListEntries = Array<MenuListEntries>;
|
export type SectionsOfMenuListEntries<Value = string> = MenuListEntries<Value>[];
|
||||||
|
|
||||||
interface MenuListEntryData {
|
interface MenuListEntryData<Value = string> {
|
||||||
value?: string;
|
value?: Value;
|
||||||
label?: string;
|
label?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
checkbox?: boolean;
|
checkbox?: boolean;
|
||||||
shortcut?: Array<string>;
|
shortcut?: string[];
|
||||||
shortcutRequiresLock?: boolean;
|
shortcutRequiresLock?: boolean;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
children?: SectionsOfMenuListEntries;
|
children?: SectionsOfMenuListEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MenuListEntry = MenuListEntryData & { ref?: typeof FloatingMenu | typeof MenuList; checked?: boolean };
|
export type MenuListEntry<Value = string> = MenuListEntryData<Value> & { ref?: typeof FloatingMenu | typeof MenuList; checked?: boolean };
|
||||||
|
|
||||||
const KEYBOARD_LOCK_USE_FULLSCREEN = "This hotkey is reserved by the browser, but becomes available in fullscreen mode";
|
const KEYBOARD_LOCK_USE_FULLSCREEN = "This hotkey is reserved by the browser, but becomes available in fullscreen mode";
|
||||||
const KEYBOARD_LOCK_SWITCH_BROWSER = "This hotkey is reserved by the browser, but becomes available in Chrome, Edge, and Opera which support the Keyboard.lock() API";
|
const KEYBOARD_LOCK_SWITCH_BROWSER = "This hotkey is reserved by the browser, but becomes available in Chrome, Edge, and Opera which support the Keyboard.lock() API";
|
||||||
@@ -240,7 +240,7 @@ const MenuList = defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
menuEntriesWithoutRefs(): Array<Array<MenuListEntryData>> {
|
menuEntriesWithoutRefs(): MenuListEntryData[][] {
|
||||||
const { menuEntries } = this;
|
const { menuEntries } = this;
|
||||||
return menuEntries.map((entries) =>
|
return menuEntries.map((entries) =>
|
||||||
entries.map((entry) => {
|
entries.map((entry) => {
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export interface RadioEntryData {
|
|||||||
action?: () => void;
|
action?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RadioEntries = Array<RadioEntryData>;
|
export type RadioEntries = RadioEntryData[];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ import { defineComponent } from "vue";
|
|||||||
|
|
||||||
import { rgbToDecimalRgb, RGB } from "@/utilities/color";
|
import { rgbToDecimalRgb, RGB } from "@/utilities/color";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
import { ResponseType, registerResponseHandler, Response, UpdateWorkingColors } from "@/utilities/response-handler";
|
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
|
import { UpdateWorkingColors } from "@/utilities/js-messages";
|
||||||
|
|
||||||
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, { MenuDirection, MenuType } from "@/components/widgets/floating-menus/FloatingMenu.vue";
|
||||||
@@ -135,20 +136,17 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
registerResponseHandler(ResponseType.UpdateWorkingColors, (responseData: Response) => {
|
subscribeJsMessage(UpdateWorkingColors, (updateWorkingColors) => {
|
||||||
const colorData = responseData as UpdateWorkingColors;
|
const { primary, secondary } = updateWorkingColors;
|
||||||
if (!colorData) return;
|
|
||||||
const { primary, secondary } = colorData;
|
|
||||||
|
|
||||||
this.primaryColor = { r: primary.red, g: primary.green, b: primary.blue, a: primary.alpha };
|
this.primaryColor = primary.toRgba();
|
||||||
let color = this.primaryColor;
|
this.secondaryColor = secondary.toRgba();
|
||||||
let button = this.getRef<HTMLButtonElement>("primaryButton");
|
|
||||||
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
|
||||||
|
|
||||||
this.secondaryColor = { r: secondary.red, g: secondary.green, b: secondary.blue, a: secondary.alpha };
|
const primaryButton = this.getRef<HTMLButtonElement>("primaryButton");
|
||||||
color = this.secondaryColor;
|
primaryButton.style.setProperty("--swatch-color", primary.toRgbaCSS());
|
||||||
button = this.getRef<HTMLButtonElement>("secondaryButton");
|
|
||||||
button.style.setProperty("--swatch-color", `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`);
|
const secondaryButton = this.getRef<HTMLButtonElement>("secondaryButton");
|
||||||
|
secondaryButton.style.setProperty("--swatch-color", secondary.toRgbaCSS());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updatePrimaryColor();
|
this.updatePrimaryColor();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export type Widgets = TextButtonWidget | IconButtonWidget | SeparatorWidget | PopoverButtonWidget | NumberInputWidget;
|
export type Widgets = TextButtonWidget | IconButtonWidget | SeparatorWidget | PopoverButtonWidget | NumberInputWidget;
|
||||||
export type WidgetRow = Array<Widgets>;
|
export type WidgetRow = Widgets[];
|
||||||
export type WidgetLayout = Array<WidgetRow>;
|
export type WidgetLayout = WidgetRow[];
|
||||||
|
|
||||||
// Text Button
|
// Text Button
|
||||||
export interface TextButtonWidget {
|
export interface TextButtonWidget {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
// Allows for runtime reflection of types in javascript.
|
||||||
|
// It is needed for class-transformer to work and is imported as a side effect.
|
||||||
|
// The library replaces the Reflect Api on the window to support more features.
|
||||||
|
import "reflect-metadata";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
|
||||||
import { fullscreenModeChanged } from "@/utilities/fullscreen";
|
import { fullscreenModeChanged } from "@/utilities/fullscreen";
|
||||||
import { onKeyUp, onKeyDown, onMouseMove, onMouseDown, onMouseUp, onMouseScroll, onWindowResize, onBeforeUnload } from "@/utilities/input";
|
import { onKeyUp, onKeyDown, onMouseMove, onMouseDown, onMouseUp, onMouseScroll, onWindowResize, onBeforeUnload } from "@/utilities/input";
|
||||||
import "@/utilities/errors";
|
import "@/utilities/errors";
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { reactive, readonly } from "vue";
|
import { reactive, readonly } from "vue";
|
||||||
|
|
||||||
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
||||||
|
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
import {
|
import {
|
||||||
ResponseType,
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
registerResponseHandler,
|
|
||||||
Response,
|
|
||||||
SetActiveDocument,
|
SetActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
DisplayConfirmationToCloseDocument,
|
DisplayConfirmationToCloseDocument,
|
||||||
ExportDocument,
|
ExportDocument,
|
||||||
SaveDocument,
|
SaveDocument,
|
||||||
} from "@/utilities/response-handler";
|
OpenDocumentBrowse,
|
||||||
|
} from "@/utilities/js-messages";
|
||||||
import { download, upload } from "@/utilities/files";
|
import { download, upload } from "@/utilities/files";
|
||||||
import { panicProxy } from "@/utilities/panic-proxy";
|
import { panicProxy } from "@/utilities/panic-proxy";
|
||||||
|
|
||||||
@@ -94,41 +94,34 @@ export async function closeAllDocumentsWithConfirmation() {
|
|||||||
|
|
||||||
export default readonly(state);
|
export default readonly(state);
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => {
|
subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
|
||||||
const documentListData = responseData as UpdateOpenDocumentsList;
|
state.documents = updateOpenDocumentList.open_documents.map(({ name, isSaved }) => new DocumentState(name, isSaved));
|
||||||
state.documents = documentListData.open_documents.map(({ name, isSaved }) => new DocumentState(name, isSaved));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => {
|
subscribeJsMessage(SetActiveDocument, (setActiveDocument) => {
|
||||||
const documentData = responseData as SetActiveDocument;
|
state.activeDocumentIndex = setActiveDocument.document_index;
|
||||||
if (documentData) {
|
|
||||||
state.activeDocumentIndex = documentData.document_index;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.DisplayConfirmationToCloseDocument, (responseData: Response) => {
|
subscribeJsMessage(DisplayConfirmationToCloseDocument, (displayConfirmationToCloseDocument) => {
|
||||||
const data = responseData as DisplayConfirmationToCloseDocument;
|
closeDocumentWithConfirmation(displayConfirmationToCloseDocument.document_index);
|
||||||
closeDocumentWithConfirmation(data.document_index);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.DisplayConfirmationToCloseAllDocuments, (_: Response) => {
|
subscribeJsMessage(DisplayConfirmationToCloseAllDocuments, () => {
|
||||||
closeAllDocumentsWithConfirmation();
|
closeAllDocumentsWithConfirmation();
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.OpenDocumentBrowse, async (_: Response) => {
|
subscribeJsMessage(OpenDocumentBrowse, async () => {
|
||||||
const extension = (await wasm).file_save_suffix();
|
const extension = (await wasm).file_save_suffix();
|
||||||
const data = await upload(extension);
|
const data = await upload(extension);
|
||||||
(await wasm).open_document_file(data.filename, data.content);
|
(await wasm).open_document_file(data.filename, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.ExportDocument, (responseData: Response) => {
|
subscribeJsMessage(ExportDocument, (exportDocument) => {
|
||||||
const updateData = responseData as ExportDocument;
|
download(exportDocument.name, exportDocument.document);
|
||||||
if (updateData) download(updateData.name, updateData.document);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
registerResponseHandler(ResponseType.SaveDocument, (responseData: Response) => {
|
subscribeJsMessage(SaveDocument, (saveDocument) => {
|
||||||
const saveData = responseData as SaveDocument;
|
download(saveDocument.name, saveDocument.document);
|
||||||
if (saveData) download(saveData.name, saveData.document);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
(async () => (await wasm).get_open_documents_list())();
|
(async () => (await wasm).get_open_documents_list())();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
||||||
import { TextButtonWidget } from "@/components/widgets/widgets";
|
import { TextButtonWidget } from "@/components/widgets/widgets";
|
||||||
import { ResponseType, registerResponseHandler, Response, DisplayError, DisplayPanic } from "@/utilities/response-handler";
|
import { subscribeJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
|
import { DisplayError, DisplayPanic } from "./js-messages";
|
||||||
|
|
||||||
// Coming soon dialog
|
// Coming soon dialog
|
||||||
export function comingSoon(issueNumber?: number) {
|
export function comingSoon(issueNumber?: number) {
|
||||||
@@ -24,9 +25,7 @@ export function comingSoon(issueNumber?: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Graphite error dialog
|
// Graphite error dialog
|
||||||
registerResponseHandler(ResponseType.DisplayError, (responseData: Response) => {
|
subscribeJsMessage(DisplayError, (displayError) => {
|
||||||
const data = responseData as DisplayError;
|
|
||||||
|
|
||||||
const okButton: TextButtonWidget = {
|
const okButton: TextButtonWidget = {
|
||||||
kind: "TextButton",
|
kind: "TextButton",
|
||||||
callback: async () => dismissDialog(),
|
callback: async () => dismissDialog(),
|
||||||
@@ -34,17 +33,15 @@ registerResponseHandler(ResponseType.DisplayError, (responseData: Response) => {
|
|||||||
};
|
};
|
||||||
const buttons = [okButton];
|
const buttons = [okButton];
|
||||||
|
|
||||||
createDialog("Warning", data.title, data.description, buttons);
|
createDialog("Warning", displayError.title, displayError.description, buttons);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Code panic dialog and console error
|
// Code panic dialog and console error
|
||||||
registerResponseHandler(ResponseType.DisplayPanic, (responseData: Response) => {
|
subscribeJsMessage(DisplayPanic, (displayPanic) => {
|
||||||
const data = responseData as DisplayPanic;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(Error as any).stackTraceLimit = Infinity;
|
(Error as any).stackTraceLimit = Infinity;
|
||||||
const stackTrace = new Error().stack || "";
|
const stackTrace = new Error().stack || "";
|
||||||
const panicDetails = `${data.panic_info}\n\n${stackTrace}`;
|
const panicDetails = `${displayPanic.panic_info}\n\n${stackTrace}`;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error(panicDetails);
|
console.error(panicDetails);
|
||||||
@@ -66,7 +63,7 @@ registerResponseHandler(ResponseType.DisplayPanic, (responseData: Response) => {
|
|||||||
};
|
};
|
||||||
const buttons = [reloadButton, copyErrorLogButton, reportOnGithubButton];
|
const buttons = [reloadButton, copyErrorLogButton, reportOnGithubButton];
|
||||||
|
|
||||||
createDialog("Warning", data.title, data.description, buttons);
|
createDialog("Warning", displayPanic.title, displayPanic.description, buttons);
|
||||||
});
|
});
|
||||||
|
|
||||||
function githubUrl(panicDetails: string) {
|
function githubUrl(panicDetails: string) {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// This file is instantiated by wasm-bindgen in `/frontend/wasm/src/lib.rs` and re-exports the `handleJsMessage` function to
|
||||||
|
// provide access to the global copy of `js-message-dispatcher.ts` with its shared state, not an isolated duplicate with empty state
|
||||||
|
|
||||||
|
export { handleJsMessage } from "@/utilities/js-message-dispatcher";
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import { plainToInstance } from "class-transformer";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
|
DisplayConfirmationToCloseDocument,
|
||||||
|
DisplayError,
|
||||||
|
DisplayPanic,
|
||||||
|
ExportDocument,
|
||||||
|
newDisplayFolderTreeStructure,
|
||||||
|
OpenDocumentBrowse,
|
||||||
|
SaveDocument,
|
||||||
|
SetActiveDocument,
|
||||||
|
SetActiveTool,
|
||||||
|
SetCanvasRotation,
|
||||||
|
SetCanvasZoom,
|
||||||
|
UpdateCanvas,
|
||||||
|
UpdateOpenDocumentsList,
|
||||||
|
UpdateRulers,
|
||||||
|
UpdateScrollbars,
|
||||||
|
UpdateWorkingColors,
|
||||||
|
UpdateLayer,
|
||||||
|
JsMessage,
|
||||||
|
} from "./js-messages";
|
||||||
|
|
||||||
|
type JsMessageCallback<T extends JsMessage> = (responseData: T) => void;
|
||||||
|
type JsMessageCallbackMap = {
|
||||||
|
[response: string]: JsMessageCallback<any> | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
responseMap: {} as JsMessageCallbackMap,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Constructs<T> = new (...args: any[]) => T;
|
||||||
|
type ConstructsJsMessage = Constructs<JsMessage> & typeof JsMessage;
|
||||||
|
|
||||||
|
const responseMap = {
|
||||||
|
UpdateCanvas,
|
||||||
|
UpdateScrollbars,
|
||||||
|
UpdateRulers,
|
||||||
|
ExportDocument,
|
||||||
|
SaveDocument,
|
||||||
|
OpenDocumentBrowse,
|
||||||
|
DisplayFolderTreeStructure: newDisplayFolderTreeStructure,
|
||||||
|
UpdateLayer,
|
||||||
|
SetActiveTool,
|
||||||
|
SetActiveDocument,
|
||||||
|
UpdateOpenDocumentsList,
|
||||||
|
UpdateWorkingColors,
|
||||||
|
SetCanvasZoom,
|
||||||
|
SetCanvasRotation,
|
||||||
|
DisplayError,
|
||||||
|
DisplayPanic,
|
||||||
|
DisplayConfirmationToCloseDocument,
|
||||||
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type JsMessageType = keyof typeof responseMap;
|
||||||
|
|
||||||
|
function isJsMessageConstructor(fn: ConstructsJsMessage | ((data: any) => JsMessage)): fn is ConstructsJsMessage {
|
||||||
|
return (fn as ConstructsJsMessage).jsMessageMarker !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleJsMessage(responseType: JsMessageType, responseData: any) {
|
||||||
|
const messageMaker = responseMap[responseType];
|
||||||
|
let message: JsMessage;
|
||||||
|
|
||||||
|
if (!messageMaker) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Received a Response of type "${responseType}" but but was not able to parse the data.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJsMessageConstructor(messageMaker)) {
|
||||||
|
message = plainToInstance(messageMaker, responseData[responseType]);
|
||||||
|
} else {
|
||||||
|
message = messageMaker(responseData[responseType]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is ok to use constructor.name even with minification since it is used consistently with registerHandler
|
||||||
|
const callback = state.responseMap[message.constructor.name];
|
||||||
|
|
||||||
|
if (callback && message) {
|
||||||
|
callback(message);
|
||||||
|
} else if (message) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscribeJsMessage<T extends JsMessage>(responseType: Constructs<T>, callback: JsMessageCallback<T>) {
|
||||||
|
state.responseMap[responseType.name] = callback;
|
||||||
|
}
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
|
import { Transform, Type } from "class-transformer";
|
||||||
|
|
||||||
|
export class JsMessage {
|
||||||
|
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
||||||
|
static readonly jsMessageMarker = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateOpenDocumentsList extends JsMessage {
|
||||||
|
@Transform(({ value }) => value.map((tuple: [string, boolean]) => ({ name: tuple[0], isSaved: tuple[1] })))
|
||||||
|
readonly open_documents!: { name: string; isSaved: boolean }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const To255Scale = Transform(({ value }) => value * 255);
|
||||||
|
export class Color {
|
||||||
|
@To255Scale
|
||||||
|
readonly red!: number;
|
||||||
|
|
||||||
|
@To255Scale
|
||||||
|
readonly green!: number;
|
||||||
|
|
||||||
|
@To255Scale
|
||||||
|
readonly blue!: number;
|
||||||
|
|
||||||
|
readonly alpha!: number;
|
||||||
|
|
||||||
|
toRgba() {
|
||||||
|
return { r: this.red, g: this.green, b: this.blue, a: this.alpha };
|
||||||
|
}
|
||||||
|
|
||||||
|
toRgbaCSS() {
|
||||||
|
const { r, g, b, a } = this.toRgba();
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateWorkingColors extends JsMessage {
|
||||||
|
@Type(() => Color)
|
||||||
|
readonly primary!: Color;
|
||||||
|
|
||||||
|
@Type(() => Color)
|
||||||
|
readonly secondary!: Color;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetActiveTool extends JsMessage {
|
||||||
|
readonly tool_name!: string;
|
||||||
|
|
||||||
|
readonly tool_options!: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetActiveDocument extends JsMessage {
|
||||||
|
readonly document_index!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayError extends JsMessage {
|
||||||
|
readonly title!: string;
|
||||||
|
|
||||||
|
readonly description!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayPanic extends JsMessage {
|
||||||
|
readonly panic_info!: string;
|
||||||
|
|
||||||
|
readonly title!: string;
|
||||||
|
|
||||||
|
readonly description!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayConfirmationToCloseDocument extends JsMessage {
|
||||||
|
readonly document_index!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayConfirmationToCloseAllDocuments extends JsMessage {}
|
||||||
|
|
||||||
|
export class UpdateCanvas extends JsMessage {
|
||||||
|
readonly document!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TupleToVec2 = Transform(({ value }) => ({ x: value[0], y: value[1] }));
|
||||||
|
|
||||||
|
export class UpdateScrollbars extends JsMessage {
|
||||||
|
@TupleToVec2
|
||||||
|
readonly position!: { x: number; y: number };
|
||||||
|
|
||||||
|
@TupleToVec2
|
||||||
|
readonly size!: { x: number; y: number };
|
||||||
|
|
||||||
|
@TupleToVec2
|
||||||
|
readonly multiplier!: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateRulers extends JsMessage {
|
||||||
|
@TupleToVec2
|
||||||
|
readonly origin!: { x: number; y: number };
|
||||||
|
|
||||||
|
readonly spacing!: number;
|
||||||
|
|
||||||
|
readonly interval!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExportDocument extends JsMessage {
|
||||||
|
readonly document!: string;
|
||||||
|
|
||||||
|
readonly name!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SaveDocument extends JsMessage {
|
||||||
|
readonly document!: string;
|
||||||
|
|
||||||
|
readonly name!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenDocumentBrowse extends JsMessage {}
|
||||||
|
|
||||||
|
export class DocumentChanged extends JsMessage {}
|
||||||
|
|
||||||
|
export class DisplayFolderTreeStructure extends JsMessage {
|
||||||
|
constructor(readonly layerId: BigInt, readonly children: DisplayFolderTreeStructure[]) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function newDisplayFolderTreeStructure(input: any): DisplayFolderTreeStructure {
|
||||||
|
const { ptr, len } = input.data_buffer;
|
||||||
|
const wasmMemoryBuffer = (window as any).wasmMemory().buffer;
|
||||||
|
|
||||||
|
// Decode the folder structure encoding
|
||||||
|
const encoding = new DataView(wasmMemoryBuffer, ptr, len);
|
||||||
|
|
||||||
|
// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
|
||||||
|
const structureSectionLength = Number(encoding.getBigUint64(0, true));
|
||||||
|
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, ptr + 8, structureSectionLength * 8);
|
||||||
|
|
||||||
|
// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
|
||||||
|
const layerIdsSection = new DataView(wasmMemoryBuffer, ptr + 8 + structureSectionLength * 8);
|
||||||
|
|
||||||
|
let layersEncountered = 0;
|
||||||
|
let currentFolder = new DisplayFolderTreeStructure(BigInt(-1), []);
|
||||||
|
const currentFolderStack = [currentFolder];
|
||||||
|
|
||||||
|
for (let i = 0; i < structureSectionLength; i += 1) {
|
||||||
|
const msbSigned = structureSectionMsbSigned.getBigUint64(i * 8, true);
|
||||||
|
const msbMask = BigInt(1) << BigInt(63);
|
||||||
|
|
||||||
|
// Set the MSB to 0 to clear the sign and then read the number as usual
|
||||||
|
const numberOfLayersAtThisDepth = msbSigned & ~msbMask;
|
||||||
|
|
||||||
|
// Store child folders in the current folder (until we are interrupted by an indent)
|
||||||
|
for (let j = 0; j < numberOfLayersAtThisDepth; j += 1) {
|
||||||
|
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
|
||||||
|
layersEncountered += 1;
|
||||||
|
|
||||||
|
const childLayer = new DisplayFolderTreeStructure(layerId, []);
|
||||||
|
currentFolder.children.push(childLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the sign of the MSB, where a 1 is a negative (outward) indent
|
||||||
|
const subsequentDirectionOfDepthChange = (msbSigned & msbMask) === BigInt(0);
|
||||||
|
// Inward
|
||||||
|
if (subsequentDirectionOfDepthChange) {
|
||||||
|
currentFolderStack.push(currentFolder);
|
||||||
|
currentFolder = currentFolder.children[currentFolder.children.length - 1];
|
||||||
|
}
|
||||||
|
// Outward
|
||||||
|
else {
|
||||||
|
const popped = currentFolderStack.pop();
|
||||||
|
if (!popped) throw Error("Too many negative indents in the folder structure");
|
||||||
|
if (popped) currentFolder = popped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateLayer extends JsMessage {
|
||||||
|
@Type(() => LayerPanelEntry)
|
||||||
|
readonly data!: LayerPanelEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetCanvasZoom extends JsMessage {
|
||||||
|
readonly new_zoom!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetCanvasRotation extends JsMessage {
|
||||||
|
readonly new_radians!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPath(input: any): BigUint64Array {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const u32CombinedPairs = input.map((n: number[]) => BigInt((BigInt(n[0]) << BigInt(32)) | BigInt(n[1])));
|
||||||
|
return new BigUint64Array(u32CombinedPairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlendMode =
|
||||||
|
| "normal"
|
||||||
|
| "multiply"
|
||||||
|
| "darken"
|
||||||
|
| "color-burn"
|
||||||
|
| "screen"
|
||||||
|
| "lighten"
|
||||||
|
| "color-dodge"
|
||||||
|
| "overlay"
|
||||||
|
| "soft-light"
|
||||||
|
| "hard-light"
|
||||||
|
| "difference"
|
||||||
|
| "exclusion"
|
||||||
|
| "hue"
|
||||||
|
| "saturation"
|
||||||
|
| "color"
|
||||||
|
| "luminosity";
|
||||||
|
|
||||||
|
export class LayerPanelEntry {
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
visible!: boolean;
|
||||||
|
|
||||||
|
blend_mode!: BlendMode;
|
||||||
|
|
||||||
|
// On the rust side opacity is out of 1 rather than 100
|
||||||
|
@Transform(({ value }) => value * 100)
|
||||||
|
opacity!: number;
|
||||||
|
|
||||||
|
layer_type!: LayerType;
|
||||||
|
|
||||||
|
@Transform(({ value }) => newPath(value))
|
||||||
|
path!: BigUint64Array;
|
||||||
|
|
||||||
|
@Type(() => LayerData)
|
||||||
|
layer_data!: LayerData;
|
||||||
|
|
||||||
|
thumbnail!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LayerData {
|
||||||
|
expanded!: boolean;
|
||||||
|
|
||||||
|
selected!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LayerTypeOptions = {
|
||||||
|
Folder: "Folder",
|
||||||
|
Shape: "Shape",
|
||||||
|
Circle: "Circle",
|
||||||
|
Rect: "Rect",
|
||||||
|
Line: "Line",
|
||||||
|
PolyLine: "PolyLine",
|
||||||
|
Ellipse: "Ellipse",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type LayerType = typeof LayerTypeOptions[keyof typeof LayerTypeOptions];
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file is instantiated by wasm-bindgen in `/frontend/wasm/src/lib.rs` and re-exports the `handleResponse` function to
|
|
||||||
// provide access to the global copy of `response-handler.ts` with its shared state, not an isolated duplicate with empty state
|
|
||||||
|
|
||||||
export { handleResponse } from "@/utilities/response-handler";
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
/* eslint-disable camelcase */
|
|
||||||
|
|
||||||
import { reactive } from "vue";
|
|
||||||
|
|
||||||
type ResponseCallback = (responseData: Response) => void;
|
|
||||||
type ResponseMap = {
|
|
||||||
[response: string]: ResponseCallback | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
responseMap: {} as ResponseMap,
|
|
||||||
});
|
|
||||||
|
|
||||||
export enum ResponseType {
|
|
||||||
UpdateCanvas = "UpdateCanvas",
|
|
||||||
UpdateScrollbars = "UpdateScrollbars",
|
|
||||||
UpdateRulers = "UpdateRulers",
|
|
||||||
ExportDocument = "ExportDocument",
|
|
||||||
SaveDocument = "SaveDocument",
|
|
||||||
OpenDocumentBrowse = "OpenDocumentBrowse",
|
|
||||||
DisplayFolderTreeStructure = "DisplayFolderTreeStructure",
|
|
||||||
UpdateLayer = "UpdateLayer",
|
|
||||||
SetActiveTool = "SetActiveTool",
|
|
||||||
SetActiveDocument = "SetActiveDocument",
|
|
||||||
UpdateOpenDocumentsList = "UpdateOpenDocumentsList",
|
|
||||||
UpdateWorkingColors = "UpdateWorkingColors",
|
|
||||||
SetCanvasZoom = "SetCanvasZoom",
|
|
||||||
SetCanvasRotation = "SetCanvasRotation",
|
|
||||||
DisplayError = "DisplayError",
|
|
||||||
DisplayPanic = "DisplayPanic",
|
|
||||||
DisplayConfirmationToCloseDocument = "DisplayConfirmationToCloseDocument",
|
|
||||||
DisplayConfirmationToCloseAllDocuments = "DisplayConfirmationToCloseAllDocuments",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerResponseHandler(responseType: ResponseType, callback: ResponseCallback) {
|
|
||||||
state.responseMap[responseType] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleResponse(responseType: string, responseData: any) {
|
|
||||||
const callback = state.responseMap[responseType];
|
|
||||||
const data = parseResponse(responseType, responseData);
|
|
||||||
|
|
||||||
if (callback && data) {
|
|
||||||
callback(data);
|
|
||||||
} else if (data) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(`Received a Response of type "${responseType}" but but was not able to parse the data.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseResponse(responseType: string, data: any): Response {
|
|
||||||
switch (responseType) {
|
|
||||||
case "DocumentChanged":
|
|
||||||
return newDocumentChanged(data.DocumentChanged);
|
|
||||||
case "DisplayFolderTreeStructure":
|
|
||||||
return newDisplayFolderTreeStructure(data.DisplayFolderTreeStructure);
|
|
||||||
case "SetActiveTool":
|
|
||||||
return newSetActiveTool(data.SetActiveTool);
|
|
||||||
case "SetActiveDocument":
|
|
||||||
return newSetActiveDocument(data.SetActiveDocument);
|
|
||||||
case "UpdateOpenDocumentsList":
|
|
||||||
return newUpdateOpenDocumentsList(data.UpdateOpenDocumentsList);
|
|
||||||
case "UpdateCanvas":
|
|
||||||
return newUpdateCanvas(data.UpdateCanvas);
|
|
||||||
case "UpdateScrollbars":
|
|
||||||
return newUpdateScrollbars(data.UpdateScrollbars);
|
|
||||||
case "UpdateRulers":
|
|
||||||
return newUpdateRulers(data.UpdateRulers);
|
|
||||||
case "UpdateLayer":
|
|
||||||
return newUpdateLayer(data.UpdateLayer);
|
|
||||||
case "SetCanvasZoom":
|
|
||||||
return newSetCanvasZoom(data.SetCanvasZoom);
|
|
||||||
case "SetCanvasRotation":
|
|
||||||
return newSetCanvasRotation(data.SetCanvasRotation);
|
|
||||||
case "ExportDocument":
|
|
||||||
return newExportDocument(data.ExportDocument);
|
|
||||||
case "SaveDocument":
|
|
||||||
return newSaveDocument(data.SaveDocument);
|
|
||||||
case "OpenDocumentBrowse":
|
|
||||||
return newOpenDocumentBrowse(data.OpenDocumentBrowse);
|
|
||||||
case "UpdateWorkingColors":
|
|
||||||
return newUpdateWorkingColors(data.UpdateWorkingColors);
|
|
||||||
case "DisplayError":
|
|
||||||
return newDisplayError(data.DisplayError);
|
|
||||||
case "DisplayPanic":
|
|
||||||
return newDisplayPanic(data.DisplayPanic);
|
|
||||||
case "DisplayConfirmationToCloseDocument":
|
|
||||||
return newDisplayConfirmationToCloseDocument(data.DisplayConfirmationToCloseDocument);
|
|
||||||
case "DisplayConfirmationToCloseAllDocuments":
|
|
||||||
return newDisplayConfirmationToCloseAllDocuments(data.DisplayConfirmationToCloseAllDocuments);
|
|
||||||
default:
|
|
||||||
throw new Error(`Unrecognized origin/responseType pair: ${origin}, '${responseType}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Response =
|
|
||||||
| DocumentChanged
|
|
||||||
| DisplayFolderTreeStructure
|
|
||||||
| SetActiveTool
|
|
||||||
| SetActiveDocument
|
|
||||||
| UpdateOpenDocumentsList
|
|
||||||
| UpdateCanvas
|
|
||||||
| UpdateScrollbars
|
|
||||||
| UpdateRulers
|
|
||||||
| UpdateLayer
|
|
||||||
| SetCanvasZoom
|
|
||||||
| SetCanvasRotation
|
|
||||||
| ExportDocument
|
|
||||||
| SaveDocument
|
|
||||||
| OpenDocumentBrowse
|
|
||||||
| UpdateWorkingColors
|
|
||||||
| DisplayError
|
|
||||||
| DisplayPanic
|
|
||||||
| DisplayConfirmationToCloseDocument
|
|
||||||
| DisplayConfirmationToCloseAllDocuments;
|
|
||||||
|
|
||||||
export interface UpdateOpenDocumentsList {
|
|
||||||
open_documents: { name: string; isSaved: boolean }[];
|
|
||||||
}
|
|
||||||
function newUpdateOpenDocumentsList(input: any): UpdateOpenDocumentsList {
|
|
||||||
const openDocuments = input.open_documents.map((docData: [string, boolean]) => ({ name: docData[0], isSaved: docData[1] }));
|
|
||||||
return { open_documents: openDocuments };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Color {
|
|
||||||
red: number;
|
|
||||||
green: number;
|
|
||||||
blue: number;
|
|
||||||
alpha: number;
|
|
||||||
}
|
|
||||||
function newColor(input: any): Color {
|
|
||||||
// TODO: Possibly change this in the Rust side to avoid any pitfalls
|
|
||||||
return { red: input.red * 255, green: input.green * 255, blue: input.blue * 255, alpha: input.alpha };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateWorkingColors {
|
|
||||||
primary: Color;
|
|
||||||
secondary: Color;
|
|
||||||
}
|
|
||||||
function newUpdateWorkingColors(input: any): UpdateWorkingColors {
|
|
||||||
return {
|
|
||||||
primary: newColor(input.primary),
|
|
||||||
secondary: newColor(input.secondary),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetActiveTool {
|
|
||||||
tool_name: string;
|
|
||||||
tool_options: object;
|
|
||||||
}
|
|
||||||
function newSetActiveTool(input: any): SetActiveTool {
|
|
||||||
return {
|
|
||||||
tool_name: input.tool_name,
|
|
||||||
tool_options: input.tool_options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetActiveDocument {
|
|
||||||
document_index: number;
|
|
||||||
}
|
|
||||||
function newSetActiveDocument(input: any): SetActiveDocument {
|
|
||||||
return {
|
|
||||||
document_index: input.document_index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DisplayError {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
function newDisplayError(input: any): DisplayError {
|
|
||||||
return {
|
|
||||||
title: input.title,
|
|
||||||
description: input.description,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DisplayPanic {
|
|
||||||
panic_info: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
function newDisplayPanic(input: any): DisplayPanic {
|
|
||||||
return {
|
|
||||||
panic_info: input.panic_info,
|
|
||||||
title: input.title,
|
|
||||||
description: input.description,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DisplayConfirmationToCloseDocument {
|
|
||||||
document_index: number;
|
|
||||||
}
|
|
||||||
function newDisplayConfirmationToCloseDocument(input: any): DisplayConfirmationToCloseDocument {
|
|
||||||
return {
|
|
||||||
document_index: input.document_index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DisplayConfirmationToCloseAllDocuments = Record<string, never>;
|
|
||||||
|
|
||||||
function newDisplayConfirmationToCloseAllDocuments(_input: any): DisplayConfirmationToCloseAllDocuments {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateCanvas {
|
|
||||||
document: string;
|
|
||||||
}
|
|
||||||
function newUpdateCanvas(input: any): UpdateCanvas {
|
|
||||||
return {
|
|
||||||
document: input.document,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateScrollbars {
|
|
||||||
position: { x: number; y: number };
|
|
||||||
size: { x: number; y: number };
|
|
||||||
multiplier: { x: number; y: number };
|
|
||||||
}
|
|
||||||
function newUpdateScrollbars(input: any): UpdateScrollbars {
|
|
||||||
return {
|
|
||||||
position: { x: input.position[0], y: input.position[1] },
|
|
||||||
size: { x: input.size[0], y: input.size[1] },
|
|
||||||
multiplier: { x: input.multiplier[0], y: input.multiplier[1] },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateRulers {
|
|
||||||
origin: { x: number; y: number };
|
|
||||||
spacing: number;
|
|
||||||
interval: number;
|
|
||||||
}
|
|
||||||
function newUpdateRulers(input: any): UpdateRulers {
|
|
||||||
return {
|
|
||||||
origin: { x: input.origin[0], y: input.origin[1] },
|
|
||||||
spacing: input.spacing,
|
|
||||||
interval: input.interval,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExportDocument {
|
|
||||||
document: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
function newExportDocument(input: any): ExportDocument {
|
|
||||||
return {
|
|
||||||
document: input.document,
|
|
||||||
name: input.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SaveDocument {
|
|
||||||
document: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
function newSaveDocument(input: any): SaveDocument {
|
|
||||||
return {
|
|
||||||
document: input.document,
|
|
||||||
name: input.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OpenDocumentBrowse = Record<string, never>;
|
|
||||||
function newOpenDocumentBrowse(_: any): OpenDocumentBrowse {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DocumentChanged = Record<string, never>;
|
|
||||||
function newDocumentChanged(_: any): DocumentChanged {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DisplayFolderTreeStructure {
|
|
||||||
layerId: BigInt;
|
|
||||||
children: DisplayFolderTreeStructure[];
|
|
||||||
}
|
|
||||||
function newDisplayFolderTreeStructure(input: any): DisplayFolderTreeStructure {
|
|
||||||
const { ptr, len } = input.data_buffer;
|
|
||||||
const wasmMemoryBuffer = (window as any).wasmMemory().buffer;
|
|
||||||
|
|
||||||
// Decode the folder structure encoding
|
|
||||||
const encoding = new DataView(wasmMemoryBuffer, ptr, len);
|
|
||||||
|
|
||||||
// The structure section indicates how to read through the upcoming layer list and assign depths to each layer
|
|
||||||
const structureSectionLength = Number(encoding.getBigUint64(0, true));
|
|
||||||
const structureSectionMsbSigned = new DataView(wasmMemoryBuffer, ptr + 8, structureSectionLength * 8);
|
|
||||||
|
|
||||||
// The layer IDs section lists each layer ID sequentially in the tree, as it will show up in the panel
|
|
||||||
const layerIdsSection = new DataView(wasmMemoryBuffer, ptr + 8 + structureSectionLength * 8);
|
|
||||||
|
|
||||||
let layersEncountered = 0;
|
|
||||||
let currentFolder: DisplayFolderTreeStructure = { layerId: BigInt(-1), children: [] };
|
|
||||||
const currentFolderStack = [currentFolder];
|
|
||||||
|
|
||||||
for (let i = 0; i < structureSectionLength; i += 1) {
|
|
||||||
const msbSigned = structureSectionMsbSigned.getBigUint64(i * 8, true);
|
|
||||||
const msbMask = BigInt(1) << BigInt(63);
|
|
||||||
|
|
||||||
// Set the MSB to 0 to clear the sign and then read the number as usual
|
|
||||||
const numberOfLayersAtThisDepth = msbSigned & ~msbMask;
|
|
||||||
|
|
||||||
// Store child folders in the current folder (until we are interrupted by an indent)
|
|
||||||
for (let j = 0; j < numberOfLayersAtThisDepth; j += 1) {
|
|
||||||
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
|
|
||||||
layersEncountered += 1;
|
|
||||||
|
|
||||||
const childLayer = { layerId, children: [] };
|
|
||||||
currentFolder.children.push(childLayer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the sign of the MSB, where a 1 is a negative (outward) indent
|
|
||||||
const subsequentDirectionOfDepthChange = (msbSigned & msbMask) === BigInt(0);
|
|
||||||
// debugger;
|
|
||||||
// Inward
|
|
||||||
if (subsequentDirectionOfDepthChange) {
|
|
||||||
currentFolderStack.push(currentFolder);
|
|
||||||
currentFolder = currentFolder.children[currentFolder.children.length - 1];
|
|
||||||
}
|
|
||||||
// Outward
|
|
||||||
else {
|
|
||||||
const popped = currentFolderStack.pop();
|
|
||||||
if (!popped) throw Error("Too many negative indents in the folder structure");
|
|
||||||
if (popped) currentFolder = popped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateLayer {
|
|
||||||
path: BigUint64Array;
|
|
||||||
data: LayerPanelEntry;
|
|
||||||
}
|
|
||||||
function newUpdateLayer(input: any): UpdateLayer {
|
|
||||||
return {
|
|
||||||
path: newPath(input.data.path),
|
|
||||||
data: newLayerPanelEntry(input.data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetCanvasZoom {
|
|
||||||
new_zoom: number;
|
|
||||||
}
|
|
||||||
function newSetCanvasZoom(input: any): SetCanvasZoom {
|
|
||||||
return {
|
|
||||||
new_zoom: input.new_zoom,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetCanvasRotation {
|
|
||||||
new_radians: number;
|
|
||||||
}
|
|
||||||
function newSetCanvasRotation(input: any): SetCanvasRotation {
|
|
||||||
return {
|
|
||||||
new_radians: input.new_radians,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function newPath(input: any): BigUint64Array {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const u32CombinedPairs = input.map((n: Array<number>) => BigInt((BigInt(n[0]) << BigInt(32)) | BigInt(n[1])));
|
|
||||||
return new BigUint64Array(u32CombinedPairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BlendMode {
|
|
||||||
Normal = "normal",
|
|
||||||
Multiply = "multiply",
|
|
||||||
Darken = "darken",
|
|
||||||
ColorBurn = "color-burn",
|
|
||||||
Screen = "screen",
|
|
||||||
Lighten = "lighten",
|
|
||||||
ColorDodge = "color-dodge",
|
|
||||||
Overlay = "overlay",
|
|
||||||
SoftLight = "soft-light",
|
|
||||||
HardLight = "hard-light",
|
|
||||||
Difference = "difference",
|
|
||||||
Exclusion = "exclusion",
|
|
||||||
Hue = "hue",
|
|
||||||
Saturation = "saturation",
|
|
||||||
Color = "color",
|
|
||||||
Luminosity = "luminosity",
|
|
||||||
}
|
|
||||||
function newBlendMode(input: string): BlendMode {
|
|
||||||
const blendMode = {
|
|
||||||
Normal: BlendMode.Normal,
|
|
||||||
Multiply: BlendMode.Multiply,
|
|
||||||
Darken: BlendMode.Darken,
|
|
||||||
ColorBurn: BlendMode.ColorBurn,
|
|
||||||
Screen: BlendMode.Screen,
|
|
||||||
Lighten: BlendMode.Lighten,
|
|
||||||
ColorDodge: BlendMode.ColorDodge,
|
|
||||||
Overlay: BlendMode.Overlay,
|
|
||||||
SoftLight: BlendMode.SoftLight,
|
|
||||||
HardLight: BlendMode.HardLight,
|
|
||||||
Difference: BlendMode.Difference,
|
|
||||||
Exclusion: BlendMode.Exclusion,
|
|
||||||
Hue: BlendMode.Hue,
|
|
||||||
Saturation: BlendMode.Saturation,
|
|
||||||
Color: BlendMode.Color,
|
|
||||||
Luminosity: BlendMode.Luminosity,
|
|
||||||
}[input];
|
|
||||||
|
|
||||||
if (!blendMode) throw new Error(`Invalid blend mode "${blendMode}"`);
|
|
||||||
|
|
||||||
return blendMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newOpacity(input: number): number {
|
|
||||||
return input * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LayerPanelEntry {
|
|
||||||
name: string;
|
|
||||||
visible: boolean;
|
|
||||||
blend_mode: BlendMode;
|
|
||||||
opacity: number;
|
|
||||||
layer_type: LayerType;
|
|
||||||
path: BigUint64Array;
|
|
||||||
layer_data: LayerData;
|
|
||||||
thumbnail: string;
|
|
||||||
}
|
|
||||||
function newLayerPanelEntry(input: any): LayerPanelEntry {
|
|
||||||
return {
|
|
||||||
name: input.name,
|
|
||||||
visible: input.visible,
|
|
||||||
blend_mode: newBlendMode(input.blend_mode),
|
|
||||||
opacity: newOpacity(input.opacity),
|
|
||||||
layer_type: newLayerType(input.layer_type),
|
|
||||||
layer_data: newLayerData(input.layer_data),
|
|
||||||
path: newPath(input.path),
|
|
||||||
thumbnail: input.thumbnail,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LayerData {
|
|
||||||
expanded: boolean;
|
|
||||||
selected: boolean;
|
|
||||||
}
|
|
||||||
function newLayerData(input: any): LayerData {
|
|
||||||
return {
|
|
||||||
expanded: input.expanded,
|
|
||||||
selected: input.selected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum LayerType {
|
|
||||||
Folder = "Folder",
|
|
||||||
Shape = "Shape",
|
|
||||||
Circle = "Circle",
|
|
||||||
Rect = "Rect",
|
|
||||||
Line = "Line",
|
|
||||||
PolyLine = "PolyLine",
|
|
||||||
Ellipse = "Ellipse",
|
|
||||||
}
|
|
||||||
function newLayerType(input: any): LayerType {
|
|
||||||
switch (input) {
|
|
||||||
case "Folder":
|
|
||||||
return LayerType.Folder;
|
|
||||||
case "Shape":
|
|
||||||
return LayerType.Shape;
|
|
||||||
case "Circle":
|
|
||||||
return LayerType.Circle;
|
|
||||||
case "Rect":
|
|
||||||
return LayerType.Rect;
|
|
||||||
case "Line":
|
|
||||||
return LayerType.Line;
|
|
||||||
case "PolyLine":
|
|
||||||
return LayerType.PolyLine;
|
|
||||||
case "Ellipse":
|
|
||||||
return LayerType.Ellipse;
|
|
||||||
default:
|
|
||||||
throw Error(`Received invalid input as an enum variant for LayerType: ${input}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -71,7 +71,7 @@ module.exports = {
|
|||||||
.init(
|
.init(
|
||||||
(Plugin) =>
|
(Plugin) =>
|
||||||
new Plugin({
|
new Plugin({
|
||||||
allow: "(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT)",
|
allow: "(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR MIT OR 0BSD)",
|
||||||
emitError: true,
|
emitError: true,
|
||||||
outputFilename: "third-party-licenses.txt",
|
outputFilename: "third-party-licenses.txt",
|
||||||
outputWriter: formatThirdPartyLicenses,
|
outputWriter: formatThirdPartyLicenses,
|
||||||
@@ -150,7 +150,7 @@ License information is required on production builds. Aborting.`);
|
|||||||
// Augment the imported Rust license list with the provided JS license list
|
// Augment the imported Rust license list with the provided JS license list
|
||||||
jsLicenses.dependencies.forEach((jsLicense) => {
|
jsLicenses.dependencies.forEach((jsLicense) => {
|
||||||
const { name, version, author, repository, licenseName } = jsLicense;
|
const { name, version, author, repository, licenseName } = jsLicense;
|
||||||
const licenseText = trimBlankLines(jsLicense.licenseText);
|
const licenseText = trimBlankLines(jsLicense.licenseText ?? "");
|
||||||
|
|
||||||
// Remove the `git+` or `git://` prefix and `.git` suffix
|
// Remove the `git+` or `git://` prefix and `.git` suffix
|
||||||
const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository;
|
const repo = repository ? repository.replace(/^.*(github.com\/.*?\/.*?)(?:.git)/, "https://$1") : repository;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fn handle_response(message: FrontendMessage) {
|
|||||||
let message_type = message.to_discriminant().local_name();
|
let message_type = message.to_discriminant().local_name();
|
||||||
let message_data = JsValue::from_serde(&message).expect("Failed to serialize FrontendMessage");
|
let message_data = JsValue::from_serde(&message).expect("Failed to serialize FrontendMessage");
|
||||||
|
|
||||||
let js_return_value = handleResponse(message_type, message_data);
|
let js_return_value = handleJsMessage(message_type, message_data);
|
||||||
if let Err(error) = js_return_value {
|
if let Err(error) = js_return_value {
|
||||||
log::error!(
|
log::error!(
|
||||||
"While handling FrontendMessage \"{:?}\", JavaScript threw an error: {:?}",
|
"While handling FrontendMessage \"{:?}\", JavaScript threw an error: {:?}",
|
||||||
@@ -66,8 +66,8 @@ fn handle_response(message: FrontendMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The JavaScript function to call into with each FrontendMessage
|
// The JavaScript function to call into with each FrontendMessage
|
||||||
#[wasm_bindgen(module = "/../src/utilities/response-handler-binding.ts")]
|
#[wasm_bindgen(module = "/../src/utilities/js-message-dispatcher-binding.ts")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[wasm_bindgen(catch)]
|
#[wasm_bindgen(catch)]
|
||||||
fn handleResponse(responseType: String, responseData: JsValue) -> Result<(), JsValue>;
|
fn handleJsMessage(responseType: String, responseData: JsValue) -> Result<(), JsValue>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user