mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 04:58:12 +08:00
Restructure project directories (#333)
`/client/web` -> `/frontend` `/client/cli` -> *delete for now* `/client/native` -> *delete for now* `/core/editor` -> `/editor` `/core/document` -> `/graphene` `/core/renderer` -> `/charcoal` `/core/proc-macro` -> `/proc-macros` *(now plural)*
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
export interface RGB {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
}
|
||||
|
||||
export interface HSV {
|
||||
h: number;
|
||||
s: number;
|
||||
v: number;
|
||||
a: number;
|
||||
}
|
||||
|
||||
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 q = v * (1 - f * s);
|
||||
const t = v * (1 - (1 - f) * s);
|
||||
const mod = i % 6;
|
||||
const r = Math.round([v, q, p, p, t, v][mod]);
|
||||
const g = Math.round([t, v, v, q, p, p][mod]);
|
||||
const b = Math.round([p, p, t, v, v, q][mod]);
|
||||
return { r, g, b, a: hsv.a };
|
||||
}
|
||||
|
||||
export function rgbToHsv(rgb: RGB) {
|
||||
const { r, g, b } = rgb;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const d = max - min;
|
||||
const s = max === 0 ? 0 : d / max;
|
||||
const v = max;
|
||||
let h = 0;
|
||||
if (max === min) {
|
||||
h = 0;
|
||||
} else {
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
return { h, s, v, a: rgb.a };
|
||||
}
|
||||
|
||||
export function rgbToDecimalRgb(rgb: RGB) {
|
||||
const r = rgb.r / 255;
|
||||
const g = rgb.g / 255;
|
||||
const b = rgb.b / 255;
|
||||
return { r, g, b, a: rgb.a };
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
||||
import { TextButtonWidget } from "@/components/widgets/widgets";
|
||||
|
||||
export default function comingSoon(issueNumber?: number) {
|
||||
const bugMessage = `— but you can help add it!\nSee issue #${issueNumber} on GitHub.`;
|
||||
const details = `This feature is not implemented yet${issueNumber ? bugMessage : ""}`;
|
||||
|
||||
const okButton: TextButtonWidget = {
|
||||
kind: "TextButton",
|
||||
callback: async () => dismissDialog(),
|
||||
props: { label: "OK", emphasized: true, minWidth: 96 },
|
||||
};
|
||||
const issueButton: TextButtonWidget = {
|
||||
kind: "TextButton",
|
||||
callback: async () => window.open(`https://github.com/GraphiteEditor/Graphite/issues/${issueNumber}`, "_blank"),
|
||||
props: { label: `Issue #${issueNumber}`, minWidth: 96 },
|
||||
};
|
||||
const buttons = [okButton];
|
||||
if (issueNumber) buttons.push(issueButton);
|
||||
|
||||
createDialog("Warning", "Coming soon", details, buttons);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { reactive, readonly } from "vue";
|
||||
|
||||
import { TextButtonWidget } from "@/components/widgets/widgets";
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
icon: "",
|
||||
heading: "",
|
||||
details: "",
|
||||
buttons: [] as TextButtonWidget[],
|
||||
});
|
||||
|
||||
export function createDialog(icon: string, heading: string, details: string, buttons: TextButtonWidget[]) {
|
||||
state.visible = true;
|
||||
state.icon = icon;
|
||||
state.heading = heading;
|
||||
state.details = details;
|
||||
state.buttons = buttons;
|
||||
}
|
||||
|
||||
export function dismissDialog() {
|
||||
state.visible = false;
|
||||
}
|
||||
|
||||
export function submitDialog() {
|
||||
const firstEmphasizedButton = state.buttons.find((button) => button.props.emphasized && button.callback);
|
||||
if (firstEmphasizedButton) {
|
||||
// If statement satisfies TypeScript
|
||||
if (firstEmphasizedButton.callback) firstEmphasizedButton.callback();
|
||||
}
|
||||
}
|
||||
|
||||
export function dialogIsVisible(): boolean {
|
||||
return state.visible;
|
||||
}
|
||||
|
||||
export default readonly(state);
|
||||
@@ -0,0 +1,97 @@
|
||||
import { reactive, readonly } from "vue";
|
||||
|
||||
import { createDialog, dismissDialog } from "@/utilities/dialog";
|
||||
import { ResponseType, registerResponseHandler, Response, SetActiveDocument, UpdateOpenDocumentsList, DisplayConfirmationToCloseDocument } from "@/utilities/response-handler";
|
||||
|
||||
const wasm = import("@/../wasm/pkg");
|
||||
|
||||
const state = reactive({
|
||||
title: "",
|
||||
unsaved: false,
|
||||
documents: [] as Array<string>,
|
||||
activeDocumentIndex: 0,
|
||||
});
|
||||
|
||||
export async function selectDocument(tabIndex: number) {
|
||||
const { select_document } = await wasm;
|
||||
select_document(tabIndex);
|
||||
}
|
||||
|
||||
export async function closeDocumentWithConfirmation(tabIndex: number) {
|
||||
selectDocument(tabIndex);
|
||||
|
||||
const tabLabel = state.documents[tabIndex];
|
||||
|
||||
// TODO: Rename to "Save changes before closing?" when we can actually save documents somewhere, not just export SVGs
|
||||
createDialog("File", "Close without exporting SVG?", tabLabel, [
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: async () => {
|
||||
(await wasm).export_document();
|
||||
dismissDialog();
|
||||
},
|
||||
props: { label: "Export", emphasized: true, minWidth: 96 },
|
||||
},
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: async () => {
|
||||
(await wasm).close_document(tabIndex);
|
||||
dismissDialog();
|
||||
},
|
||||
props: { label: "Discard", minWidth: 96 },
|
||||
},
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: async () => {
|
||||
dismissDialog();
|
||||
},
|
||||
props: { label: "Cancel", minWidth: 96 },
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export async function closeAllDocumentsWithConfirmation() {
|
||||
createDialog("Copy", "Close all documents?", "Unsaved work will be lost!", [
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: async () => {
|
||||
(await wasm).close_all_documents();
|
||||
dismissDialog();
|
||||
},
|
||||
props: { label: "Discard All", minWidth: 96 },
|
||||
},
|
||||
{
|
||||
kind: "TextButton",
|
||||
callback: async () => {
|
||||
dismissDialog();
|
||||
},
|
||||
props: { label: "Cancel", minWidth: 96 },
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
export default readonly(state);
|
||||
|
||||
registerResponseHandler(ResponseType.UpdateOpenDocumentsList, (responseData: Response) => {
|
||||
const documentListData = responseData as UpdateOpenDocumentsList;
|
||||
if (documentListData) {
|
||||
state.documents = documentListData.open_documents;
|
||||
state.title = state.documents[state.activeDocumentIndex];
|
||||
}
|
||||
});
|
||||
registerResponseHandler(ResponseType.SetActiveDocument, (responseData: Response) => {
|
||||
const documentData = responseData as SetActiveDocument;
|
||||
if (documentData) {
|
||||
state.activeDocumentIndex = documentData.document_index;
|
||||
state.title = state.documents[state.activeDocumentIndex];
|
||||
}
|
||||
});
|
||||
registerResponseHandler(ResponseType.DisplayConfirmationToCloseDocument, (responseData: Response) => {
|
||||
const data = responseData as DisplayConfirmationToCloseDocument;
|
||||
closeDocumentWithConfirmation(data.document_index);
|
||||
});
|
||||
registerResponseHandler(ResponseType.DisplayConfirmationToCloseAllDocuments, (_responseData: Response) => {
|
||||
closeAllDocumentsWithConfirmation();
|
||||
});
|
||||
|
||||
(async () => (await wasm).get_open_documents_list())();
|
||||
@@ -0,0 +1,37 @@
|
||||
import { reactive, readonly } from "vue";
|
||||
|
||||
const state = reactive({
|
||||
windowFullscreen: false,
|
||||
keyboardLocked: false,
|
||||
});
|
||||
|
||||
export function fullscreenModeChanged() {
|
||||
state.windowFullscreen = Boolean(document.fullscreenElement);
|
||||
if (!state.windowFullscreen) state.keyboardLocked = false;
|
||||
}
|
||||
|
||||
export function keyboardLockApiSupported(): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return "keyboard" in navigator && "lock" in (navigator as any).keyboard;
|
||||
}
|
||||
|
||||
export async function enterFullscreen() {
|
||||
await document.documentElement.requestFullscreen();
|
||||
|
||||
if (keyboardLockApiSupported()) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (navigator as any).keyboard.lock(["ControlLeft", "ControlRight"]);
|
||||
state.keyboardLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
export async function exitFullscreen() {
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
|
||||
export async function toggleFullscreen() {
|
||||
if (state.windowFullscreen) await exitFullscreen();
|
||||
else await enterFullscreen();
|
||||
}
|
||||
|
||||
export default readonly(state);
|
||||
@@ -0,0 +1,76 @@
|
||||
import { toggleFullscreen } from "@/utilities/fullscreen";
|
||||
import { dialogIsVisible, dismissDialog, submitDialog } from "@/utilities/dialog";
|
||||
|
||||
const wasm = import("@/../wasm/pkg");
|
||||
|
||||
export function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): boolean {
|
||||
// Don't redirect user input from text entry into HTML elements
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.nodeName === "INPUT" || target.nodeName === "TEXTAREA" || target.isContentEditable) return false;
|
||||
|
||||
// Don't redirect when a modal is covering the workspace
|
||||
if (dialogIsVisible()) return false;
|
||||
|
||||
// Don't redirect a fullscreen request
|
||||
if (e.key.toLowerCase() === "f11" && e.type === "keydown" && !e.repeat) {
|
||||
e.preventDefault();
|
||||
toggleFullscreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't redirect a reload request
|
||||
if (e.key.toLowerCase() === "f5") return false;
|
||||
|
||||
// Don't redirect debugging tools
|
||||
if (e.key.toLowerCase() === "f12") return false;
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "c") return false;
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "i") return false;
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === "j") return false;
|
||||
|
||||
// Redirect to the backend
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function handleKeyDown(e: KeyboardEvent) {
|
||||
if (shouldRedirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const { on_key_down } = await wasm;
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_key_down(e.key, modifiers);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogIsVisible()) {
|
||||
if (e.key === "Escape") dismissDialog();
|
||||
if (e.key === "Enter") submitDialog();
|
||||
|
||||
// Prevent the Enter key from acting like a click on the last clicked button, which might reopen the dialog
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleKeyUp(e: KeyboardEvent) {
|
||||
if (shouldRedirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const { on_key_up } = await wasm;
|
||||
const modifiers = makeModifiersBitfield(e.ctrlKey, e.shiftKey, e.altKey);
|
||||
on_key_up(e.key, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMouseDown(e: MouseEvent) {
|
||||
const target = e.target && (e.target as HTMLElement);
|
||||
const clickedInsideDialog = target && target.closest(".dialog-modal .floating-menu-content");
|
||||
|
||||
if (dialogIsVisible() && !clickedInsideDialog) {
|
||||
dismissDialog();
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
export function makeModifiersBitfield(control: boolean, shift: boolean, alt: boolean): number {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return Number(control) | (Number(shift) << 1) | (Number(alt) << 2);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function clamp(value: number, min = 0, max = 1) {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// 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";
|
||||
@@ -0,0 +1,333 @@
|
||||
import { reactive } from "vue";
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type ResponseCallback = (responseData: Response) => void;
|
||||
type ResponseMap = {
|
||||
[response: string]: ResponseCallback | undefined;
|
||||
};
|
||||
|
||||
const state = reactive({
|
||||
responseMap: {} as ResponseMap,
|
||||
});
|
||||
|
||||
export enum ResponseType {
|
||||
UpdateCanvas = "UpdateCanvas",
|
||||
ExportDocument = "ExportDocument",
|
||||
ExpandFolder = "ExpandFolder",
|
||||
CollapseFolder = "CollapseFolder",
|
||||
SetActiveTool = "SetActiveTool",
|
||||
SetActiveDocument = "SetActiveDocument",
|
||||
UpdateOpenDocumentsList = "UpdateOpenDocumentsList",
|
||||
UpdateWorkingColors = "UpdateWorkingColors",
|
||||
UpdateLayer = "UpdateLayer",
|
||||
SetCanvasZoom = "SetCanvasZoom",
|
||||
SetCanvasRotation = "SetCanvasRotation",
|
||||
DisplayConfirmationToCloseDocument = "DisplayConfirmationToCloseDocument",
|
||||
DisplayConfirmationToCloseAllDocuments = "DisplayConfirmationToCloseAllDocuments",
|
||||
}
|
||||
|
||||
export function registerResponseHandler(responseType: ResponseType, callback: ResponseCallback) {
|
||||
state.responseMap[responseType] = callback;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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) {
|
||||
console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`);
|
||||
} else {
|
||||
console.error(`Received a Response of type "${responseType}" but but was not able to parse the data.`);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function parseResponse(responseType: string, data: any): Response {
|
||||
switch (responseType) {
|
||||
case "DocumentChanged":
|
||||
return newDocumentChanged(data.DocumentChanged);
|
||||
case "CollapseFolder":
|
||||
return newCollapseFolder(data.CollapseFolder);
|
||||
case "ExpandFolder":
|
||||
return newExpandFolder(data.ExpandFolder);
|
||||
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 "UpdateLayer":
|
||||
return newUpdateLayer(data.UpdateLayer);
|
||||
case "SetCanvasZoom":
|
||||
return newSetCanvasZoom(data.SetCanvasZoom);
|
||||
case "SetCanvasRotation":
|
||||
return newSetCanvasRotation(data.SetCanvasRotation);
|
||||
case "ExportDocument":
|
||||
return newExportDocument(data.ExportDocument);
|
||||
case "UpdateWorkingColors":
|
||||
return newUpdateWorkingColors(data.UpdateWorkingColors);
|
||||
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 = SetActiveTool | UpdateCanvas | DocumentChanged | CollapseFolder | ExpandFolder | UpdateWorkingColors | SetCanvasZoom | SetCanvasRotation;
|
||||
|
||||
export interface UpdateOpenDocumentsList {
|
||||
open_documents: Array<string>;
|
||||
}
|
||||
function newUpdateOpenDocumentsList(input: any): UpdateOpenDocumentsList {
|
||||
return { open_documents: input.open_documents };
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
function newSetActiveTool(input: any): SetActiveTool {
|
||||
return {
|
||||
tool_name: input.tool_name,
|
||||
};
|
||||
}
|
||||
|
||||
export interface SetActiveDocument {
|
||||
document_index: number;
|
||||
}
|
||||
function newSetActiveDocument(input: any): SetActiveDocument {
|
||||
return {
|
||||
document_index: input.document_index,
|
||||
};
|
||||
}
|
||||
|
||||
export interface DisplayConfirmationToCloseDocument {
|
||||
document_index: number;
|
||||
}
|
||||
function newDisplayConfirmationToCloseDocument(input: any): DisplayConfirmationToCloseDocument {
|
||||
return {
|
||||
document_index: input.document_index,
|
||||
};
|
||||
}
|
||||
|
||||
function newDisplayConfirmationToCloseAllDocuments(_input: any): {} {
|
||||
return {};
|
||||
}
|
||||
|
||||
export interface UpdateCanvas {
|
||||
document: string;
|
||||
}
|
||||
function newUpdateCanvas(input: any): UpdateCanvas {
|
||||
return {
|
||||
document: input.document,
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExportDocument {
|
||||
document: string;
|
||||
}
|
||||
function newExportDocument(input: any): UpdateCanvas {
|
||||
return {
|
||||
document: input.document,
|
||||
};
|
||||
}
|
||||
|
||||
export type DocumentChanged = {};
|
||||
function newDocumentChanged(_: any): DocumentChanged {
|
||||
return {};
|
||||
}
|
||||
|
||||
export interface CollapseFolder {
|
||||
path: BigUint64Array;
|
||||
}
|
||||
function newCollapseFolder(input: any): CollapseFolder {
|
||||
return {
|
||||
path: newPath(input.path),
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateLayer {
|
||||
path: BigUint64Array;
|
||||
data: LayerPanelEntry;
|
||||
}
|
||||
function newUpdateLayer(input: any): UpdateLayer {
|
||||
return {
|
||||
path: newPath(input.data.path),
|
||||
data: newLayerPanelEntry(input.data),
|
||||
};
|
||||
}
|
||||
|
||||
export interface ExpandFolder {
|
||||
path: BigUint64Array;
|
||||
children: Array<LayerPanelEntry>;
|
||||
}
|
||||
function newExpandFolder(input: any): ExpandFolder {
|
||||
return {
|
||||
path: newPath(input.path),
|
||||
children: input.children.map((child: any) => newLayerPanelEntry(child)),
|
||||
};
|
||||
}
|
||||
|
||||
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<bigint>) => 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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user