Restore ESLint and Prettier auto-formatting and CI linting (#1457)

* Restore ESLint and Prettier autoformatting

* Fix formatting and lints in web files

* Hacky fix to eslint crash

* Fix remaining lints

* Add lint-fix script

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2023-11-16 13:12:47 -08:00
committed by GitHub
co-authored by 0hypercube
parent 9784d31edb
commit a6ca43bb2d
63 changed files with 5869 additions and 351 deletions
+1 -2
View File
@@ -75,8 +75,7 @@ jobs:
NODE_ENV: production NODE_ENV: production
run: | run: |
cd frontend cd frontend
# npm run lint npm run lint
echo "💥 Frontend linting is temporarily disabled until it can be set up again with Svelte 💥"
- name: 🔬 Check Rust formatting - name: 🔬 Check Rust formatting
run: | run: |
+7
View File
@@ -3,6 +3,7 @@
"useTabs": true, "useTabs": true,
"tabWidth": 4, "tabWidth": 4,
"printWidth": 200, "printWidth": 200,
"plugins": ["prettier-plugin-svelte"],
"overrides": [ "overrides": [
{ {
"files": ["*.yml", "*.yaml"], "files": ["*.yml", "*.yaml"],
@@ -10,6 +11,12 @@
"useTabs": false, "useTabs": false,
"tabWidth": 2 "tabWidth": 2
} }
},
{
"files": ["*.svelte"],
"options": {
"parser": "svelte"
}
} }
] ]
} }
+2 -2
View File
@@ -6,7 +6,7 @@
"editor.defaultFormatter": "rust-lang.rust-analyzer" "editor.defaultFormatter": "rust-lang.rust-analyzer"
}, },
// Web: save on format // Web: save on format
"[typescript][javascript]": { "[javascript][typescript][svelte]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
@@ -39,7 +39,7 @@
// ESLint config // ESLint config
"eslint.format.enable": true, "eslint.format.enable": true,
"eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"], "eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"],
"eslint.validate": ["javascript", "typescript"], "eslint.validate": ["javascript", "typescript", "svelte"],
// VS Code config // VS Code config
"html.format.wrapLineLength": 200, "html.format.wrapLineLength": 200,
"files.eol": "\n", "files.eol": "\n",
@@ -1,24 +1,25 @@
module.exports = { module.exports = {
root: true, root: true,
env: { env: { browser: true, node: true },
browser: true,
node: true,
es2020: true,
},
parserOptions: {
ecmaVersion: 2020,
},
extends: [ extends: [
// General Prettier defaults "eslint:recommended",
"plugin:import/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:svelte/recommended",
"plugin:svelte/prettier",
"prettier", "prettier",
], ],
plugins: ["import", "@typescript-eslint", "prettier"],
settings: { settings: {
// https://github.com/import-js/eslint-plugin-import#resolvers "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] },
"import/resolver": { "import/resolver": { typescript: true, node: true },
// `node` must be listed first! },
node: {}, parser: "@typescript-eslint/parser",
}, parserOptions: {
ecmaVersion: "latest",
project: "./tsconfig.json",
extraFileExtensions: [".svelte"],
}, },
ignorePatterns: [ ignorePatterns: [
// Ignore generated directories // Ignore generated directories
@@ -26,13 +27,24 @@ module.exports = {
"dist/", "dist/",
"pkg/", "pkg/",
"wasm/pkg/", "wasm/pkg/",
// Don't ignore JS and TS dotfiles in this folder // Don't ignore JS and TS dotfiles in this folder
"!.*.js", "!.*.js",
"!.*.ts", "!.*.ts",
], ],
overrides: [
{
files: ["*.js"],
rules: { "@typescript-eslint/explicit-function-return-type": ["off"] },
},
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
// Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration.
parserOptions: { parser: "@typescript-eslint/parser" },
},
],
rules: { rules: {
// Standard ESLint config // Standard ESLint config (for ordinary JS syntax linting)
indent: "off", indent: "off",
quotes: ["error", "double", { allowTemplateLiterals: true }], quotes: ["error", "double", { allowTemplateLiterals: true }],
camelcase: ["error", { properties: "always" }], camelcase: ["error", { properties: "always" }],
@@ -48,12 +60,11 @@ module.exports = {
"no-use-before-define": "off", "no-use-before-define": "off",
"no-restricted-imports": ["error", { patterns: [".*", "!@graphite/*"] }], "no-restricted-imports": ["error", { patterns: [".*", "!@graphite/*"] }],
// TypeScript plugin config // TypeScript plugin config (for TS-specific linting)
"@typescript-eslint/indent": "off", "@typescript-eslint/indent": "off",
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }], "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }],
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-definitions": ["error", "type"], "@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as", objectLiteralTypeAssertions: "never" }], "@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as", objectLiteralTypeAssertions: "never" }],
@@ -61,34 +72,25 @@ module.exports = {
"@typescript-eslint/consistent-generic-constructors": ["error", "constructor"], "@typescript-eslint/consistent-generic-constructors": ["error", "constructor"],
"@typescript-eslint/ban-types": ["error", { types: { null: "Use `undefined` instead." } }], "@typescript-eslint/ban-types": ["error", { types: { null: "Use `undefined` instead." } }],
// Import plugin config (used to intelligently validate module import statements) // Prettier plugin config (for validating and fixing formatting)
"prettier/prettier": "error",
// Svelte plugin config (for validating Svelte-specific syntax)
"svelte/no-at-html-tags": "off",
"svelte/valid-compile": ["error", { ignoreWarnings: true }],
// Import plugin config (for intelligently validating module import statements)
"import/no-unresolved": "error",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"import/no-relative-packages": "error", "import/no-relative-packages": "error",
"import/order": [ "import/order": [
"error", "error",
{ {
alphabetize: { alphabetize: { order: "asc", caseInsensitive: true },
order: "asc", pathGroups: [{ pattern: "**/*.svelte", group: "unknown", position: "after" }],
caseInsensitive: true,
},
warnOnUnassignedImports: true,
"newlines-between": "always-and-inside-groups", "newlines-between": "always-and-inside-groups",
pathGroups: [ warnOnUnassignedImports: true,
{
pattern: "**/*.svelte",
group: "unknown",
position: "after",
},
],
}, },
], ],
}, },
overrides: [
{
files: ["*.js"],
rules: {
"@typescript-eslint/explicit-function-return-type": ["off"],
},
},
],
}; };
+5458 -15
View File
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -8,6 +8,8 @@
"scripts": { "scripts": {
"start": "npm run build-wasm && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm\" || (npm run print-building-help && exit 1)", "start": "npm run build-wasm && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm\" || (npm run print-building-help && exit 1)",
"profiling": "npm run build-wasm-profiling && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm-profiling\" || (npm run print-building-help && exit 1)", "profiling": "npm run build-wasm-profiling && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm-profiling\" || (npm run print-building-help && exit 1)",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"build": "npm run build-wasm-prod && vite build || (npm run print-building-help && exit 1)", "build": "npm run build-wasm-prod && vite build || (npm run print-building-help && exit 1)",
"build-wasm": "wasm-pack build ./wasm --dev --target=web", "build-wasm": "wasm-pack build ./wasm --dev --target=web",
"build-wasm-profiling": "wasm-pack build ./wasm --profiling --target=web", "build-wasm-profiling": "wasm-pack build ./wasm --profiling --target=web",
@@ -29,14 +31,23 @@
"devDependencies": { "devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.4.2", "@sveltejs/vite-plugin-svelte": "^2.4.2",
"@types/node": "^18.16.2", "@types/node": "^18.16.2",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"buffer": "^5.7.1", "buffer": "^5.7.1",
"concurrently": "^8.0.1", "concurrently": "^8.0.1",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-svelte": "^2.35.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.1.0",
"process": "^0.11.10", "process": "^0.11.10",
"rollup-plugin-license": "^3.2.0", "rollup-plugin-license": "^3.2.0",
"sass": "^1.62.1", "sass": "^1.62.1",
"svelte-preprocess": "^5.0.3",
"svelte": "^3.58.0", "svelte": "^3.58.0",
"svelte-preprocess": "^5.0.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.4.5" "vite": "^4.4.5"
+17 -7
View File
@@ -1,8 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount, onDestroy, setContext } from "svelte"; import { onMount, onDestroy, setContext } from "svelte";
import type { createEditor } from "@graphite/wasm-communication/editor";
import { operatingSystem } from "@graphite/utility-functions/platform";
import { createClipboardManager } from "@graphite/io-managers/clipboard"; import { createClipboardManager } from "@graphite/io-managers/clipboard";
import { createDragManager } from "@graphite/io-managers/drag"; import { createDragManager } from "@graphite/io-managers/drag";
import { createHyperlinkManager } from "@graphite/io-managers/hyperlinks"; import { createHyperlinkManager } from "@graphite/io-managers/hyperlinks";
@@ -16,6 +14,8 @@
import { createFullscreenState } from "@graphite/state-providers/fullscreen"; import { createFullscreenState } from "@graphite/state-providers/fullscreen";
import { createNodeGraphState } from "@graphite/state-providers/node-graph"; import { createNodeGraphState } from "@graphite/state-providers/node-graph";
import { createPortfolioState } from "@graphite/state-providers/portfolio"; import { createPortfolioState } from "@graphite/state-providers/portfolio";
import { operatingSystem } from "@graphite/utility-functions/platform";
import type { createEditor } from "@graphite/wasm-communication/editor";
import MainWindow from "@graphite/components/window/MainWindow.svelte"; import MainWindow from "@graphite/components/window/MainWindow.svelte";
@@ -125,19 +125,29 @@
--color-none-position: center center; --color-none-position: center center;
// 24px tall, 48px wide // 24px tall, 48px wide
--color-none-size-24px: 60px 24px; --color-none-size-24px: 60px 24px;
--color-none-image-24px: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 24"><line stroke="red" stroke-width="4px" x1="0" y1="27" x2="60" y2="-3" /></svg>'); --color-none-image-24px:
// Red diagonal slash (24px tall)
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 24"><line stroke="red" stroke-width="4px" x1="0" y1="27" x2="60" y2="-3" /></svg>');
// 32px tall, 64px wide // 32px tall, 64px wide
--color-none-size-32px: 80px 32px; --color-none-size-32px: 80px 32px;
--color-none-image-32px: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 32"><line stroke="red" stroke-width="4px" x1="0" y1="36" x2="80" y2="-4" /></svg>'); --color-none-image-32px:
// Red diagonal slash (32px tall)
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 32"><line stroke="red" stroke-width="4px" x1="0" y1="36" x2="80" y2="-4" /></svg>');
--color-transparent-checkered-background: linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%), --color-transparent-checkered-background: linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%),
linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%), linear-gradient(#ffffff, #ffffff); linear-gradient(45deg, #cccccc 25%, transparent 25%, transparent 75%, #cccccc 75%), linear-gradient(#ffffff, #ffffff);
--color-transparent-checkered-background-size: 16px 16px; --color-transparent-checkered-background-size: 16px 16px;
--color-transparent-checkered-background-position: 0 0, 8px 8px; --color-transparent-checkered-background-position: 0 0, 8px 8px;
--icon-expand-collapse-arrow: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23eee" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>'); --icon-expand-collapse-arrow:
--icon-expand-collapse-arrow-hover: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23fff" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>'); // Arrow triangle (#eee fill)
--icon-expand-collapse-arrow-disabled: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23888" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>'); url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23eee" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>');
--icon-expand-collapse-arrow-hover:
// Arrow triangle (#fff fill)
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23fff" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>');
--icon-expand-collapse-arrow-disabled:
// Arrow triangle (#888 fill)
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><polygon fill="%23888" points="3,0 1,0 5,4 1,8 3,8 7,4" /></svg>');
} }
html, html,
@@ -2,6 +2,7 @@
import { onDestroy, createEventDispatcher, getContext } from "svelte"; import { onDestroy, createEventDispatcher, getContext } from "svelte";
import { clamp } from "@graphite/utility-functions/math"; import { clamp } from "@graphite/utility-functions/math";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type HSV, type RGB } from "@graphite/wasm-communication/messages"; import { type HSV, type RGB } from "@graphite/wasm-communication/messages";
import { Color } from "@graphite/wasm-communication/messages"; import { Color } from "@graphite/wasm-communication/messages";
@@ -14,7 +15,6 @@
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
type PresetColors = "none" | "black" | "white" | "red" | "yellow" | "green" | "cyan" | "blue" | "magenta"; type PresetColors = "none" | "black" | "white" | "red" | "yellow" | "green" | "cyan" | "blue" | "magenta";
@@ -71,7 +71,7 @@
$: hsvChannels = Object.entries(!isNone ? { h: hue * 360, s: saturation * 100, v: value * 100 } : { h: undefined, s: undefined, v: undefined }) as [keyof HSV, number | undefined][]; $: hsvChannels = Object.entries(!isNone ? { h: hue * 360, s: saturation * 100, v: value * 100 } : { h: undefined, s: undefined, v: undefined }) as [keyof HSV, number | undefined][];
$: opaqueHueColor = new Color({ h: hue, s: 1, v: 1, a: 1 }); $: opaqueHueColor = new Color({ h: hue, s: 1, v: 1, a: 1 });
function generateColor(h: number, s: number, v: number, a: number, none: boolean, ..._: any[]) { function generateColor(h: number, s: number, v: number, a: number, none: boolean) {
if (none) return new Color("none"); if (none) return new Color("none");
return new Color({ h, s, v, a }); return new Color({ h, s, v, a });
} }
@@ -432,8 +432,13 @@
.hue-picker { .hue-picker {
background-blend-mode: screen; background-blend-mode: screen;
background: linear-gradient(to top, #ff0000ff 16.666%, #ff000000 33.333%, #ff000000 66.666%, #ff0000ff 83.333%), background:
linear-gradient(to top, #00ff0000 0%, #00ff00ff 16.666%, #00ff00ff 50%, #00ff0000 66.666%), linear-gradient(to top, #0000ff00 33.333%, #0000ffff 50%, #0000ffff 83.333%, #0000ff00 100%); // Reds
linear-gradient(to top, #ff0000ff 16.666%, #ff000000 33.333%, #ff000000 66.666%, #ff0000ff 83.333%),
// Greens
linear-gradient(to top, #00ff0000 0%, #00ff00ff 16.666%, #00ff00ff 50%, #00ff0000 66.666%),
// Blues
linear-gradient(to top, #0000ff00 33.333%, #0000ffff 50%, #0000ffff 83.333%, #0000ff00 100%);
--selection-needle-color: var(--hue-color-contrasting); --selection-needle-color: var(--hue-color-contrasting);
} }
@@ -567,7 +572,17 @@
.text-label { .text-label {
// Many stacked white shadows helps to increase the opacity and approximate shadow spread which does not exist for text shadows // Many stacked white shadows helps to increase the opacity and approximate shadow spread which does not exist for text shadows
text-shadow: 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white, 0 0 4px white; text-shadow:
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white,
0 0 4px white;
} }
} }
} }
@@ -1,8 +1,11 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import { githubUrl } from "@graphite/io-managers/panic"; import { githubUrl } from "@graphite/io-managers/panic";
import { wipeDocuments } from "@graphite/io-managers/persistence"; import { wipeDocuments } from "@graphite/io-managers/persistence";
import type { DialogState } from "@graphite/state-providers/dialog";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte"; import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -10,7 +13,6 @@
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import type { DialogState } from "@graphite/state-providers/dialog";
const dialog = getContext<DialogState>("dialog"); const dialog = getContext<DialogState>("dialog");
@@ -87,7 +87,9 @@
left: -8px; left: -8px;
padding: 8px; padding: 8px;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5), 0 0 8px rgba(0, 0, 0, 0.25); box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.5),
0 0 8px rgba(0, 0, 0, 0.25);
} }
.canvas-container { .canvas-container {
@@ -109,7 +111,9 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; border-radius: 50%;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5), inset 0 0 8px rgba(0, 0, 0, 0.25); box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.5),
inset 0 0 8px rgba(0, 0, 0, 0.25);
} }
.pixel-outline { .pixel-outline {
@@ -40,6 +40,7 @@
$: virtualScrollingTotalHeight = entries.length === 0 ? 0 : entries[0].length * virtualScrollingEntryHeight; $: virtualScrollingTotalHeight = entries.length === 0 ? 0 : entries[0].length * virtualScrollingEntryHeight;
$: virtualScrollingStartIndex = Math.floor(virtualScrollingEntriesStart / virtualScrollingEntryHeight) || 0; $: virtualScrollingStartIndex = Math.floor(virtualScrollingEntriesStart / virtualScrollingEntryHeight) || 0;
$: virtualScrollingEndIndex = entries.length === 0 ? 0 : Math.min(entries[0].length, virtualScrollingStartIndex + 1 + 400 / virtualScrollingEntryHeight); $: virtualScrollingEndIndex = entries.length === 0 ? 0 : Math.min(entries[0].length, virtualScrollingStartIndex + 1 + 400 / virtualScrollingEntryHeight);
$: startIndex = virtualScrollingEntryHeight ? virtualScrollingStartIndex : 0;
function watchOpen(open: boolean) { function watchOpen(open: boolean) {
highlighted = activeEntry; highlighted = activeEntry;
@@ -55,7 +56,7 @@
virtualScrollingEntriesStart = (e.target as HTMLElement)?.scrollTop || 0; virtualScrollingEntriesStart = (e.target as HTMLElement)?.scrollTop || 0;
} }
function onEntryClick(menuListEntry: MenuListEntry): void { function onEntryClick(menuListEntry: MenuListEntry) {
// Call the action if available // Call the action if available
if (menuListEntry.action) menuListEntry.action(); if (menuListEntry.action) menuListEntry.action();
@@ -71,7 +72,7 @@
open = false; open = false;
} }
function onEntryPointerEnter(menuListEntry: MenuListEntry): void { function onEntryPointerEnter(menuListEntry: MenuListEntry) {
if (!menuListEntry.children?.length) return; if (!menuListEntry.children?.length) return;
if (menuListEntry.ref) { if (menuListEntry.ref) {
@@ -80,7 +81,7 @@
} else dispatch("open", true); } else dispatch("open", true);
} }
function onEntryPointerLeave(menuListEntry: MenuListEntry): void { function onEntryPointerLeave(menuListEntry: MenuListEntry) {
if (!menuListEntry.children?.length) return; if (!menuListEntry.children?.length) return;
if (menuListEntry.ref) { if (menuListEntry.ref) {
@@ -104,7 +105,7 @@
const flatEntries = entries.flat().filter((entry) => !entry.disabled); const flatEntries = entries.flat().filter((entry) => !entry.disabled);
const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.open); const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.open);
const openSubmenu = (highlightedEntry: MenuListEntry): void => { const openSubmenu = (highlightedEntry: MenuListEntry) => {
if (highlightedEntry.ref && highlightedEntry.children?.length) { if (highlightedEntry.ref && highlightedEntry.children?.length) {
highlightedEntry.ref.open = true; highlightedEntry.ref.open = true;
// The reason we bother taking `highlightdEntry` as an argument is because, when this function is called, it can ensure `highlightedEntry` is not undefined. // The reason we bother taking `highlightdEntry` as an argument is because, when this function is called, it can ensure `highlightedEntry` is not undefined.
@@ -181,7 +182,7 @@
if (interactive && newHighlight?.value !== activeEntry?.value && newHighlight) dispatch("activeEntry", newHighlight); if (interactive && newHighlight?.value !== activeEntry?.value && newHighlight) dispatch("activeEntry", newHighlight);
} }
export function scrollViewTo(distanceDown: number): void { export function scrollViewTo(distanceDown: number) {
scroller?.div()?.scrollTo(0, distanceDown); scroller?.div()?.scrollTo(0, distanceDown);
} }
</script> </script>
@@ -214,7 +215,7 @@
{#if sectionIndex > 0} {#if sectionIndex > 0}
<Separator type="List" direction="Vertical" /> <Separator type="List" direction="Vertical" />
{/if} {/if}
{#each virtualScrollingEntryHeight ? section.slice(virtualScrollingStartIndex, virtualScrollingEndIndex) : section as entry, entryIndex (entryIndex + (virtualScrollingEntryHeight ? virtualScrollingStartIndex : 0))} {#each virtualScrollingEntryHeight ? section.slice(virtualScrollingStartIndex, virtualScrollingEndIndex) : section as entry, entryIndex (entryIndex + startIndex)}
<LayoutRow <LayoutRow
class="row" class="row"
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }} classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
@@ -359,4 +360,5 @@
} }
} }
} }
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
</style> </style>
@@ -137,9 +137,9 @@
floatingMenuContentDiv.style.setProperty("min-height", "unset"); floatingMenuContentDiv.style.setProperty("min-height", "unset");
} }
// Gets the client bounds of the elements and apply relevant styles to them // Gets the client bounds of the elements and apply relevant styles to them.
// TODO: Use DOM attribute bindings more whilst not causing recursive updates // TODO: Use DOM attribute bindings more whilst not causing recursive updates. Turning measuring on and off both causes the component to change,
// Turning measuring on and off both causes the component to change, which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop) // TODO: which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop).
if (!measuringOngoingGuard) positionAndStyleFloatingMenu(); if (!measuringOngoingGuard) positionAndStyleFloatingMenu();
}); });
@@ -237,7 +237,7 @@
} }
// To be called by the parent component. Measures the actual width of the floating menu content element and returns it in a promise. // To be called by the parent component. Measures the actual width of the floating menu content element and returns it in a promise.
export async function measureAndEmitNaturalWidth(): Promise<void> { export async function measureAndEmitNaturalWidth() {
if (!measuringOngoingGuard) return; if (!measuringOngoingGuard) return;
// Wait for the changed content which fired the `afterUpdate()` Svelte event to be put into the DOM // Wait for the changed content which fired the `afterUpdate()` Svelte event to be put into the DOM
@@ -297,7 +297,7 @@
} }
} }
function hoverTransfer(self: HTMLDivElement | undefined, ownSpawner: HTMLElement | undefined, targetSpawner: HTMLElement | undefined): void { function hoverTransfer(self: HTMLDivElement | undefined, ownSpawner: HTMLElement | undefined, targetSpawner: HTMLElement | undefined) {
// Algorithm pseudo-code to detect and transfer to hover-transferrable floating menu spawners // Algorithm pseudo-code to detect and transfer to hover-transferrable floating menu spawners
// Accompanying diagram: <https://files.keavon.com/-/SpringgreenKnownXantus/capture.png> // Accompanying diagram: <https://files.keavon.com/-/SpringgreenKnownXantus/capture.png>
// //
@@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount, tick } from "svelte"; import { getContext, onMount, tick } from "svelte";
import type { DocumentState } from "@graphite/state-providers/document";
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry"; import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization"; import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
import type { Editor } from "@graphite/wasm-communication/editor";
import { import {
type MouseCursorIcon, type MouseCursorIcon,
type XY, type XY,
@@ -28,8 +30,6 @@
import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte"; import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte"; import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { DocumentState } from "@graphite/state-providers/document";
let rulerHorizontal: CanvasRuler | undefined; let rulerHorizontal: CanvasRuler | undefined;
let rulerVertical: CanvasRuler | undefined; let rulerVertical: CanvasRuler | undefined;
@@ -136,6 +136,7 @@
const canvasName = placeholder.getAttribute("data-canvas-placeholder"); const canvasName = placeholder.getAttribute("data-canvas-placeholder");
if (!canvasName) return; if (!canvasName) return;
// Get the canvas element from the global storage // Get the canvas element from the global storage
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const canvas = (window as any).imageCanvases[canvasName]; const canvas = (window as any).imageCanvases[canvasName];
placeholder.replaceWith(canvas); placeholder.replaceWith(canvas);
}); });
@@ -198,7 +199,7 @@
mousePosition.x * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2, mousePosition.x * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,
mousePosition.y * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2, mousePosition.y * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,
ZOOM_WINDOW_DIMENSIONS, ZOOM_WINDOW_DIMENSIONS,
ZOOM_WINDOW_DIMENSIONS ZOOM_WINDOW_DIMENSIONS,
); );
cursorEyedropperPreviewImageData = previewRegion; cursorEyedropperPreviewImageData = previewRegion;
@@ -272,13 +273,13 @@
textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`; textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`;
textInput.style.color = displayEditableTextbox.color.toHexOptionalAlpha() || "transparent"; textInput.style.color = displayEditableTextbox.color.toHexOptionalAlpha() || "transparent";
textInput.oninput = (): void => { textInput.oninput = () => {
if (!textInput) return; if (!textInput) return;
editor.instance.updateBounds(textInputCleanup(textInput.innerText)); editor.instance.updateBounds(textInputCleanup(textInput.innerText));
}; };
textInputMatrix = displayEditableTextbox.transform; textInputMatrix = displayEditableTextbox.transform;
const new_font = new FontFace("text-font", `url(${displayEditableTextbox.url})`); const newFont = new FontFace("text-font", `url(${displayEditableTextbox.url})`);
window.document.fonts.add(new_font); window.document.fonts.add(newFont);
textInput.style.fontFamily = "text-font"; textInput.style.fontFamily = "text-font";
// Necessary to select contenteditable: https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element/6150060#6150060 // Necessary to select contenteditable: https://stackoverflow.com/questions/6139107/programmatically-select-text-in-a-contenteditable-html-element/6150060#6150060
@@ -3,6 +3,7 @@
import { beginDraggingElement } from "@graphite/io-managers/drag"; import { beginDraggingElement } from "@graphite/io-managers/drag";
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { import {
type LayerPanelEntry, type LayerPanelEntry,
defaultWidgetLayout, defaultWidgetLayout,
@@ -17,7 +18,6 @@
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
type LayerListingInfo = { type LayerListingInfo = {
folderIndex: number; folderIndex: number;
@@ -228,7 +228,7 @@
if (!layer.layerMetadata.selected) { if (!layer.layerMetadata.selected) {
fakeHighlight = [layer.path]; fakeHighlight = [layer.path];
} }
const select = (): void => { const select = () => {
if (!layer.layerMetadata.selected) selectLayer(false, false, listing); if (!layer.layerMetadata.selected) selectLayer(false, false, listing);
}; };
@@ -275,7 +275,7 @@
layers = []; layers = [];
// Build the new layer tree // Build the new layer tree
const recurse = (folder: UpdateDocumentLayerTreeStructureJs): void => { const recurse = (folder: UpdateDocumentLayerTreeStructureJs) => {
folder.children.forEach((item, index) => { folder.children.forEach((item, index) => {
// TODO: fix toString // TODO: fix toString
const layerId = BigInt(item.layerId.toString()); const layerId = BigInt(item.layerId.toString());
@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/wasm-communication/messages"; import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
+15 -12
View File
@@ -1,16 +1,17 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount, tick } from "svelte"; import { getContext, onMount, tick } from "svelte";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor";
import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages"; import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode } from "@graphite/wasm-communication/messages"; import type { FrontendNodeLink, FrontendNodeType, FrontendNode } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
const WHEEL_RATE = (1 / 600) * 3; const WHEEL_RATE = (1 / 600) * 3;
const GRID_COLLAPSE_SPACING = 10; const GRID_COLLAPSE_SPACING = 10;
@@ -142,7 +143,7 @@
return { nodeOutput, nodeInput }; return { nodeOutput, nodeInput };
} }
async function refreshLinks(): Promise<void> { async function refreshLinks() {
await tick(); await tick();
if (!nodesContainer) return; if (!nodesContainer) return;
@@ -263,7 +264,7 @@
} }
} }
function keydown(e: KeyboardEvent): void { function keydown(e: KeyboardEvent) {
if (e.key.toLowerCase() === "escape") { if (e.key.toLowerCase() === "escape") {
nodeListLocation = undefined; nodeListLocation = undefined;
document.removeEventListener("keydown", keydown); document.removeEventListener("keydown", keydown);
@@ -380,7 +381,7 @@
panning = true; panning = true;
} }
function doubleClick(e: MouseEvent) { function doubleClick(_e: MouseEvent) {
// const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; // const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
// const nodeId = node?.getAttribute("data-node") || undefined; // const nodeId = node?.getAttribute("data-node") || undefined;
// if (nodeId) { // if (nodeId) {
@@ -416,9 +417,12 @@
refresh(); refresh();
// const DRAG_SMOOTHING_TIME = 0.1; // const DRAG_SMOOTHING_TIME = 0.1;
const DRAG_SMOOTHING_TIME = 0; // TODO: Reenable this after fixing the bugs with the wires, see the CSS `transition` attribute todo for other info const DRAG_SMOOTHING_TIME = 0; // TODO: Reenable this after fixing the bugs with the wires, see the CSS `transition` attribute todo for other info
setTimeout(() => { setTimeout(
stop = true; () => {
}, DRAG_SMOOTHING_TIME * 1000 + 10); stop = true;
},
DRAG_SMOOTHING_TIME * 1000 + 10,
);
} }
} }
} }
@@ -473,7 +477,7 @@
selectedNodeBounds.top - containerBoundsBounds.y, selectedNodeBounds.top - containerBoundsBounds.y,
selectedNodeBounds.left - containerBoundsBounds.x, selectedNodeBounds.left - containerBoundsBounds.x,
selectedNodeBounds.bottom - containerBoundsBounds.y, selectedNodeBounds.bottom - containerBoundsBounds.y,
selectedNodeBounds.right - containerBoundsBounds.x selectedNodeBounds.right - containerBoundsBounds.x,
); );
}); });
@@ -516,7 +520,7 @@
linkInProgressToConnector = new DOMRect( linkInProgressToConnector = new DOMRect(
(nodeListLocation2.x * GRID_SIZE + transform.x) * transform.scale + graphBounds.x, (nodeListLocation2.x * GRID_SIZE + transform.x) * transform.scale + graphBounds.x,
(nodeListLocation2.y * GRID_SIZE + transform.y) * transform.scale + graphBounds.y (nodeListLocation2.y * GRID_SIZE + transform.y) * transform.scale + graphBounds.y,
); );
return; return;
@@ -540,7 +544,7 @@
linkInProgressToConnector = undefined; linkInProgressToConnector = undefined;
} }
function createNode(nodeType: string): void { function createNode(nodeType: string) {
if (!nodeListLocation) return; if (!nodeListLocation) return;
const inputNodeConnectionIndex = 0; const inputNodeConnectionIndex = 0;
@@ -658,7 +662,6 @@
<div class="layers-and-nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}> <div class="layers-and-nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}>
<!-- Layers --> <!-- Layers -->
{#each $nodeGraph.nodes.filter((node) => node.displayName === "Layer") as node (String(node.id))} {#each $nodeGraph.nodes.filter((node) => node.displayName === "Layer") as node (String(node.id))}
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]}
{@const clipPathId = `${Math.random()}`.substring(2)} {@const clipPathId = `${Math.random()}`.substring(2)}
{@const stackDatainput = node.exposedInputs[0]} {@const stackDatainput = node.exposedInputs[0]}
<div <div
@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte";
import { debouncer } from "@graphite/utility-functions/debounce"; import { debouncer } from "@graphite/utility-functions/debounce";
import type { Widget } from "@graphite/wasm-communication/messages"; import type { Editor } from "@graphite/wasm-communication/editor";
import { narrowWidgetProps } from "@graphite/wasm-communication/messages"; import type { Widget, WidgetColumn, WidgetRow } from "@graphite/wasm-communication/messages";
import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@graphite/wasm-communication/messages"; import { narrowWidgetProps, isWidgetColumn, isWidgetRow } from "@graphite/wasm-communication/messages";
import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte"; import PivotAssist from "@graphite/components/widgets/assists/PivotAssist.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte"; import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte"; import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
@@ -27,14 +28,14 @@
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte"; import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import { getContext } from "svelte"; import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const SUFFIX_WIDGETS = ["PopoverButton"]; const SUFFIX_WIDGETS = ["PopoverButton"];
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
export let widgetData: WidgetColumn | WidgetRow; export let widgetData: WidgetColumn | WidgetRow;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let layoutTarget: any; export let layoutTarget: any;
$: direction = watchDirection(widgetData); $: direction = watchDirection(widgetData);
@@ -73,6 +74,7 @@
function exclude<T extends object>(props: T, additional?: (keyof T)[]): Omit<T, typeof additional extends Array<infer K> ? K : never> { function exclude<T extends object>(props: T, additional?: (keyof T)[]): Omit<T, typeof additional extends Array<infer K> ? K : never> {
const exclusions = ["kind", ...(additional || [])]; const exclusions = ["kind", ...(additional || [])];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Object.fromEntries(Object.entries(props).filter((entry) => !exclusions.includes(entry[0]))) as any; return Object.fromEntries(Object.entries(props).filter((entry) => !exclusions.includes(entry[0]))) as any;
} }
</script> </script>
@@ -241,4 +243,5 @@
} }
} }
} }
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
</style> </style>
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte"; import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte"; import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
@@ -1,15 +1,12 @@
<script lang="ts"> <script lang="ts">
import { isWidgetRow, isWidgetSection, type LayoutGroup, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages"; import { isWidgetRow, isWidgetSection, type WidgetSection as WidgetSectionFromJsMessages } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte"; import WidgetRow from "@graphite/components/widgets/WidgetRow.svelte";
import { getContext } from "svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const editor = getContext<Editor>("editor");
export let widgetData: WidgetSectionFromJsMessages; export let widgetData: WidgetSectionFromJsMessages;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let layoutTarget: any; // TODO: Give type export let layoutTarget: any; // TODO: Give type
let expanded = true; let expanded = true;
@@ -36,7 +36,7 @@
</script> </script>
<LayoutRow class="checkbox-input"> <LayoutRow class="checkbox-input">
<input type="checkbox" id={`checkbox-input-${id}`} {checked} on:change={(e) => dispatch("checked", inputElement?.checked)} {disabled} tabindex={disabled ? -1 : 0} bind:this={inputElement} /> <input type="checkbox" id={`checkbox-input-${id}`} {checked} on:change={(_) => dispatch("checked", inputElement?.checked)} {disabled} tabindex={disabled ? -1 : 0} bind:this={inputElement} />
<label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}> <label class:disabled class:checked for={`checkbox-input-${id}`} on:keydown={(e) => e.key === "Enter" && toggleCheckboxFromLabel(e)} title={tooltip}>
<LayoutRow class="checkbox-box"> <LayoutRow class="checkbox-box">
<IconLabel icon={displayIcon} /> <IconLabel icon={displayIcon} />
@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { clamp } from "@graphite/utility-functions/math";
import type { Curve, CurveManipulatorGroup } from "@graphite/wasm-communication/messages"; import type { Curve, CurveManipulatorGroup } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import { clamp } from "@graphite/utility-functions/math";
// emits: ["update:value"], // emits: ["update:value"],
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
@@ -145,7 +145,7 @@
updateCurve(); updateCurve();
} }
function setHandlePosition(anchorIndex: number, handleIndex: number, position: [number, number]): void { function setHandlePosition(anchorIndex: number, handleIndex: number, position: [number, number]) {
const { anchor, handles } = groups[anchorIndex]; const { anchor, handles } = groups[anchorIndex];
const otherHandle = handles[1 - handleIndex]; const otherHandle = handles[1 - handleIndex];
@@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, getContext, onMount, tick } from "svelte"; import { createEventDispatcher, getContext, onMount, tick } from "svelte";
import type { FontsState } from "@graphite/state-providers/fonts";
import type { MenuListEntry } from "@graphite/wasm-communication/messages"; import type { MenuListEntry } from "@graphite/wasm-communication/messages";
import MenuList from "@graphite/components/floating-menus/MenuList.svelte"; import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import type { FontsState } from "@graphite/state-providers/fonts";
const fonts = getContext<FontsState>("fonts"); const fonts = getContext<FontsState>("fonts");
@@ -34,14 +34,14 @@
$: watchFont(fontFamily, fontStyle); $: watchFont(fontFamily, fontStyle);
async function watchFont(..._: string[]): Promise<void> { async function watchFont(..._: string[]) {
// We set this function's result to a local variable to avoid reading from `entries` which causes Svelte to trigger an update that results in an infinite loop // We set this function's result to a local variable to avoid reading from `entries` which causes Svelte to trigger an update that results in an infinite loop
const newEntries = await getEntries(); const newEntries = await getEntries();
entries = newEntries; entries = newEntries;
activeEntry = getActiveEntry(newEntries); activeEntry = getActiveEntry(newEntries);
} }
async function setOpen(): Promise<void> { async function setOpen() {
open = true; open = true;
// Scroll to the active entry (the scroller div does not yet exist so we must wait for the component to render) // Scroll to the active entry (the scroller div does not yet exist so we must wait for the component to render)
@@ -53,7 +53,7 @@
} }
} }
function toggleOpen(): void { function toggleOpen() {
if (!disabled) { if (!disabled) {
open = !open; open = !open;
@@ -61,7 +61,7 @@
} }
} }
async function selectFont(newName: string): Promise<void> { async function selectFont(newName: string) {
let family; let family;
let style; let style;
@@ -24,13 +24,13 @@
$: droppable = hoveringDrop && Boolean(currentDraggingElement()); $: droppable = hoveringDrop && Boolean(currentDraggingElement());
function dragOver(e: DragEvent): void { function dragOver(e: DragEvent) {
hoveringDrop = true; hoveringDrop = true;
e.preventDefault(); e.preventDefault();
} }
function drop(e: DragEvent): void { function drop(e: DragEvent) {
hoveringDrop = false; hoveringDrop = false;
const element = currentDraggingElement(); const element = currentDraggingElement();
@@ -2,12 +2,12 @@
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages"; import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/wasm-communication/messages";
import MenuList from "@graphite/components/floating-menus/MenuList.svelte"; import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of // TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
const accelKey = platformIsMac() ? "Command" : "Control"; const accelKey = platformIsMac() ? "Command" : "Control";
@@ -57,7 +57,7 @@
...entry, ...entry,
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry` // Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
action: (): void => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined), action: () => editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined, children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
// New fields in `MenuListEntry` // New fields in `MenuListEntry`
@@ -174,8 +174,9 @@
function onCancelTextChange() { function onCancelTextChange() {
updateValue(undefined, min, max, displayDecimalPlaces, unit); updateValue(undefined, min, max, displayDecimalPlaces, unit);
rangeSliderValue = value; const valueOrZero = value !== undefined ? value : 0;
rangeSliderValueAsRendered = value; rangeSliderValue = valueOrZero;
rangeSliderValueAsRendered = valueOrZero;
editing = false; editing = false;
@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte"; import { getContext } from "svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { Color } from "@graphite/wasm-communication/messages"; import type { Color } from "@graphite/wasm-communication/messages";
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte"; import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
@@ -1,4 +1,7 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import { type KeyRaw, type LayoutKeysGroup, type Key, type MouseMotion } from "@graphite/wasm-communication/messages"; import { type KeyRaw, type LayoutKeysGroup, type Key, type MouseMotion } from "@graphite/wasm-communication/messages";
@@ -7,8 +10,6 @@
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import { getContext } from "svelte";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
type LabelData = { label?: string; icon?: IconName; width: string }; type LabelData = { label?: string; icon?: IconName; width: string };
@@ -2,12 +2,12 @@
import { getContext, onMount } from "svelte"; import { getContext, onMount } from "svelte";
import { platformIsMac } from "@graphite/utility-functions/platform"; import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@graphite/wasm-communication/messages"; import { type HintData, type HintInfo, type LayoutKeysGroup, UpdateInputHints } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte"; import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte"; import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
@@ -3,14 +3,16 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import MenuBarInput from "@graphite/components/widgets/inputs/MenuBarInput.svelte"; import MenuBarInput from "@graphite/components/widgets/inputs/MenuBarInput.svelte";
import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte"; import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte";
import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte"; import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte"; import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte"; import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import { getContext } from "svelte";
export let platform: Platform; export let platform: Platform;
export let maximized: boolean; export let maximized: boolean;
@@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte"; import { getContext } from "svelte";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
const fullscreen = getContext<FullscreenState>("fullscreen"); const fullscreen = getContext<FullscreenState>("fullscreen");
@@ -1,9 +1,9 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
import Document from "@graphite/components/panels/Document.svelte"; import Document from "@graphite/components/panels/Document.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import LayerTree from "@graphite/components/panels/LayerTree.svelte"; import LayerTree from "@graphite/components/panels/LayerTree.svelte";
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
import Properties from "@graphite/components/panels/Properties.svelte"; import Properties from "@graphite/components/panels/Properties.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
const PANEL_COMPONENTS = { const PANEL_COMPONENTS = {
@@ -19,6 +19,7 @@
import { platformIsMac, isEventSupported } from "@graphite/utility-functions/platform"; import { platformIsMac, isEventSupported } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { type LayoutKeysGroup, type Key } from "@graphite/wasm-communication/messages"; import { type LayoutKeysGroup, type Key } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@@ -26,7 +27,6 @@
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte"; import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
@@ -1,13 +1,16 @@
<script lang="ts"> <script lang="ts">
import { getContext } from "svelte";
import type { DialogState } from "@graphite/state-providers/dialog";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { FrontendDocumentDetails } from "@graphite/wasm-communication/messages";
import DialogModal from "@graphite/components/floating-menus/DialogModal.svelte"; import DialogModal from "@graphite/components/floating-menus/DialogModal.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Panel from "@graphite/components/window/workspace/Panel.svelte"; import Panel from "@graphite/components/window/workspace/Panel.svelte";
import { getContext } from "svelte";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import type { DialogState } from "@graphite/state-providers/dialog";
import type { FrontendDocumentDetails } from "@graphite/wasm-communication/messages";
const MIN_PANEL_SIZE = 100; const MIN_PANEL_SIZE = 100;
const PANEL_SIZES = { const PANEL_SIZES = {
@@ -66,7 +69,7 @@
const mouseStart = isHorizontal ? e.clientX : e.clientY; const mouseStart = isHorizontal ? e.clientX : e.clientY;
const updatePosition = (e: PointerEvent): void => { const updatePosition = (e: PointerEvent) => {
const mouseCurrent = isHorizontal ? e.clientX : e.clientY; const mouseCurrent = isHorizontal ? e.clientX : e.clientY;
let mouseDelta = mouseStart - mouseCurrent; let mouseDelta = mouseStart - mouseCurrent;
@@ -79,7 +82,7 @@
window.dispatchEvent(new CustomEvent("resize")); window.dispatchEvent(new CustomEvent("resize"));
}; };
const cleanup = (e: PointerEvent): void => { const cleanup = (e: PointerEvent) => {
gutter.releasePointerCapture(e.pointerId); gutter.releasePointerCapture(e.pointerId);
document.removeEventListener("pointermove", updatePosition); document.removeEventListener("pointermove", updatePosition);
+3 -3
View File
@@ -1,8 +1,8 @@
import { imageToPNG } from "@graphite/utility-functions/rasterization";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { TriggerTextCopy } from "@graphite/wasm-communication/messages"; import { TriggerTextCopy } from "@graphite/wasm-communication/messages";
import { imageToPNG } from "@graphite/utility-functions/rasterization";
export function createClipboardManager(editor: Editor): void { export function createClipboardManager(editor: Editor) {
// Subscribe to process backend event // Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => { editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
// If the Clipboard API is supported in the browser, copy text to the clipboard // If the Clipboard API is supported in the browser, copy text to the clipboard
@@ -10,7 +10,7 @@ export function createClipboardManager(editor: Editor): void {
}); });
} }
export async function copyToClipboardFileURL(url: string): Promise<void> { export async function copyToClipboardFileURL(url: string) {
const response = await fetch(url); const response = await fetch(url);
const blob = await response.blob(); const blob = await response.blob();
+2 -2
View File
@@ -1,7 +1,7 @@
let draggingElement: HTMLElement | undefined; let draggingElement: HTMLElement | undefined;
export function createDragManager(): () => void { export function createDragManager(): () => void {
const clearDraggingElement = (): void => { const clearDraggingElement = () => {
draggingElement = undefined; draggingElement = undefined;
}; };
@@ -17,7 +17,7 @@ export function createDragManager(): () => void {
}; };
} }
export function beginDraggingElement(element: HTMLElement): void { export function beginDraggingElement(element: HTMLElement) {
draggingElement = element; draggingElement = element;
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { TriggerVisitLink } from "@graphite/wasm-communication/messages"; import { TriggerVisitLink } from "@graphite/wasm-communication/messages";
export function createHyperlinkManager(editor: Editor): void { export function createHyperlinkManager(editor: Editor) {
// Subscribe to process backend event // Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerVisitLink, async (triggerOpenLink) => { editor.subscriptions.subscribeJsMessage(TriggerVisitLink, async (triggerOpenLink) => {
window.open(triggerOpenLink.url, "_blank"); window.open(triggerOpenLink.url, "_blank");
+21 -21
View File
@@ -41,18 +41,18 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
{ target: window, eventName: "wheel", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } }, { target: window, eventName: "wheel", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } },
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) }, { target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) },
{ target: window, eventName: "focusout", action: () => (canvasFocused = false) }, { target: window, eventName: "focusout", action: () => (canvasFocused = false) },
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) }, { target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) },
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreen.fullscreenModeChanged() }, { target: window.document, eventName: "fullscreenchange", action: () => fullscreen.fullscreenModeChanged() },
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => onPaste(e) }, { target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => onPaste(e) },
]; ];
// Event bindings // Event bindings
function bindListeners(): void { function bindListeners() {
// Add event bindings for the lifetime of the application // Add event bindings for the lifetime of the application
listeners.forEach(({ target, eventName, action, options }) => target.addEventListener(eventName, action, options)); listeners.forEach(({ target, eventName, action, options }) => target.addEventListener(eventName, action, options));
} }
function unbindListeners(): void { function unbindListeners() {
// Remove event bindings after the lifetime of the application (or on hot-module replacement during development) // Remove event bindings after the lifetime of the application (or on hot-module replacement during development)
listeners.forEach(({ target, eventName, action, options }) => target.removeEventListener(eventName, action, options)); listeners.forEach(({ target, eventName, action, options }) => target.removeEventListener(eventName, action, options));
} }
@@ -96,7 +96,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
return true; return true;
} }
async function onKeyDown(e: KeyboardEvent): Promise<void> { async function onKeyDown(e: KeyboardEvent) {
const key = await getLocalizedScanCode(e); const key = await getLocalizedScanCode(e);
const NO_KEY_REPEAT_MODIFIER_KEYS = ["ControlLeft", "ControlRight", "ShiftLeft", "ShiftRight", "MetaLeft", "MetaRight", "AltLeft", "AltRight", "AltGraph", "CapsLock", "Fn", "FnLock"]; const NO_KEY_REPEAT_MODIFIER_KEYS = ["ControlLeft", "ControlRight", "ShiftLeft", "ShiftRight", "MetaLeft", "MetaRight", "AltLeft", "AltRight", "AltGraph", "CapsLock", "Fn", "FnLock"];
@@ -114,7 +114,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
} }
} }
async function onKeyUp(e: KeyboardEvent): Promise<void> { async function onKeyUp(e: KeyboardEvent) {
const key = await getLocalizedScanCode(e); const key = await getLocalizedScanCode(e);
if (await shouldRedirectKeyboardEventToBackend(e)) { if (await shouldRedirectKeyboardEventToBackend(e)) {
@@ -127,7 +127,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
// Pointer events // Pointer events
// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend // While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend
function onPointerMove(e: PointerEvent): void { function onPointerMove(e: PointerEvent) {
if (!e.buttons) viewportPointerInteractionOngoing = false; if (!e.buttons) viewportPointerInteractionOngoing = false;
// Don't redirect pointer movement to the backend if there's no ongoing interaction and it's over a floating menu, or the graph overlay, on top of the canvas // Don't redirect pointer movement to the backend if there's no ongoing interaction and it's over a floating menu, or the graph overlay, on top of the canvas
@@ -149,12 +149,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
editor.instance.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
} }
function onMouseDown(e: MouseEvent): void { function onMouseDown(e: MouseEvent) {
// Block middle mouse button auto-scroll mode (the circlar gizmo that appears and allows quick scrolling by moving the cursor above or below it) // Block middle mouse button auto-scroll mode (the circlar gizmo that appears and allows quick scrolling by moving the cursor above or below it)
if (e.button === 1) e.preventDefault(); if (e.button === 1) e.preventDefault();
} }
function onPointerDown(e: PointerEvent): void { function onPointerDown(e: PointerEvent) {
const { target } = e; const { target } = e;
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]"); const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]"); const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
@@ -177,7 +177,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
} }
} }
function onPointerUp(e: PointerEvent): void { function onPointerUp(e: PointerEvent) {
if (!e.buttons) viewportPointerInteractionOngoing = false; if (!e.buttons) viewportPointerInteractionOngoing = false;
if (textToolInteractiveInputElement) return; if (textToolInteractiveInputElement) return;
@@ -186,12 +186,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers); editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
} }
function onPotentialDoubleClick(e: MouseEvent): void { function onPotentialDoubleClick(e: MouseEvent) {
if (textToolInteractiveInputElement) return; if (textToolInteractiveInputElement) return;
// Allow only double-clicks // Allow only double-clicks
if (e.detail !== 2) return; if (e.detail !== 2) return;
// `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead // `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead
let buttons = 1; let buttons = 1;
if (e.button === 0) buttons = 1; // LMB if (e.button === 0) buttons = 1; // LMB
@@ -204,7 +204,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
// Mouse events // Mouse events
function onWheelScroll(e: WheelEvent): void { function onWheelScroll(e: WheelEvent) {
const { target } = e; const { target } = e;
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]"); const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
@@ -223,7 +223,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
} }
} }
function onContextMenu(e: MouseEvent): void { function onContextMenu(e: MouseEvent) {
if (!targetIsTextField(e.target || undefined) && e.target !== textToolInteractiveInputElement) { if (!targetIsTextField(e.target || undefined) && e.target !== textToolInteractiveInputElement) {
e.preventDefault(); e.preventDefault();
} }
@@ -231,13 +231,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
// Receives a custom event dispatched when the user begins interactively editing with the text tool. // Receives a custom event dispatched when the user begins interactively editing with the text tool.
// We keep a copy of the text input element to check against when it's active for text entry. // We keep a copy of the text input element to check against when it's active for text entry.
function onModifyInputField(e: CustomEvent): void { function onModifyInputField(e: CustomEvent) {
textToolInteractiveInputElement = e.detail; textToolInteractiveInputElement = e.detail;
} }
// Window events // Window events
function onWindowResize(container: HTMLElement): void { function onWindowResize(container: HTMLElement) {
const viewports = Array.from(container.querySelectorAll("[data-viewport]")); const viewports = Array.from(container.querySelectorAll("[data-viewport]"));
const boundsOfViewports = viewports.map((canvas) => { const boundsOfViewports = viewports.map((canvas) => {
const bounds = canvas.getBoundingClientRect(); const bounds = canvas.getBoundingClientRect();
@@ -250,7 +250,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data); if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data);
} }
async function onBeforeUnload(e: BeforeUnloadEvent): Promise<void> { async function onBeforeUnload(e: BeforeUnloadEvent) {
const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex]; const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex];
if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id); if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id);
@@ -267,7 +267,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
} }
} }
function onPaste(e: ClipboardEvent): void { function onPaste(e: ClipboardEvent) {
const dataTransfer = e.clipboardData; const dataTransfer = e.clipboardData;
if (!dataTransfer || targetIsTextField(e.target || undefined)) return; if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
e.preventDefault(); e.preventDefault();
@@ -285,7 +285,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
const file = item.getAsFile(); const file = item.getAsFile();
if (file?.type.startsWith("image")) { if (file?.type.startsWith("image")) {
extractPixelData(file).then((imageData): void => { extractPixelData(file).then((imageData) => {
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height); editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
}); });
} }
@@ -315,7 +315,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
if (item.types.includes("text/plain")) { if (item.types.includes("text/plain")) {
const blob = await item.getType("text/plain"); const blob = await item.getType("text/plain");
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (): void => { reader.onload = () => {
const text = reader.result as string; const text = reader.result as string;
if (text.startsWith("graphite/layer: ")) { if (text.startsWith("graphite/layer: ")) {
@@ -330,7 +330,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
if (imageType) { if (imageType) {
const blob = await item.getType(imageType); const blob = await item.getType(imageType);
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async (): Promise<void> => { reader.onload = async () => {
if (reader.result instanceof ArrayBuffer) { if (reader.result instanceof ArrayBuffer) {
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType })); const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height); editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
+2 -2
View File
@@ -1,8 +1,8 @@
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { TriggerAboutGraphiteLocalizedCommitDate } from "@graphite/wasm-communication/messages"; import { TriggerAboutGraphiteLocalizedCommitDate } from "@graphite/wasm-communication/messages";
export function createLocalizationManager(editor: Editor): void { export function createLocalizationManager(editor: Editor) {
function localizeTimestamp(utc: string): { timestamp: string, year: string } { function localizeTimestamp(utc: string): { timestamp: string; year: string } {
// Timestamp // Timestamp
const date = new Date(utc); const date = new Date(utc);
if (Number.isNaN(date.getTime())) return { timestamp: utc, year: `${new Date().getFullYear()}` }; if (Number.isNaN(date.getTime())) return { timestamp: utc, year: `${new Date().getFullYear()}` };
+1 -1
View File
@@ -4,7 +4,7 @@ import { stripIndents } from "@graphite/utility-functions/strip-indents";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { DisplayDialogPanic } from "@graphite/wasm-communication/messages"; import { DisplayDialogPanic } from "@graphite/wasm-communication/messages";
export function createPanicManager(editor: Editor, dialogState: DialogState): void { export function createPanicManager(editor: Editor, dialogState: DialogState) {
// Code panic dialog and console error // Code panic dialog and console error
editor.subscriptions.subscribeJsMessage(DisplayDialogPanic, (displayDialogPanic) => { editor.subscriptions.subscribeJsMessage(DisplayDialogPanic, (displayDialogPanic) => {
// `Error.stackTraceLimit` is only available in V8/Chromium // `Error.stackTraceLimit` is only available in V8/Chromium
+10 -10
View File
@@ -7,16 +7,16 @@ import { TriggerIndexedDbWriteDocument, TriggerIndexedDbRemoveDocument, TriggerS
const graphiteStore = createStore("graphite", "store"); const graphiteStore = createStore("graphite", "store");
export function createPersistenceManager(editor: Editor, portfolio: PortfolioState): void { export function createPersistenceManager(editor: Editor, portfolio: PortfolioState) {
// DOCUMENTS // DOCUMENTS
async function storeDocumentOrder(): Promise<void> { async function storeDocumentOrder() {
const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id)); const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id));
await set("documents_tab_order", documentOrder, graphiteStore); await set("documents_tab_order", documentOrder, graphiteStore);
} }
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument): Promise<void> { async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
await update<Record<string, TriggerIndexedDbWriteDocument>>( await update<Record<string, TriggerIndexedDbWriteDocument>>(
"documents", "documents",
(old) => { (old) => {
@@ -24,13 +24,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
documents[autoSaveDocument.details.id] = autoSaveDocument; documents[autoSaveDocument.details.id] = autoSaveDocument;
return documents; return documents;
}, },
graphiteStore graphiteStore,
); );
await storeDocumentOrder(); await storeDocumentOrder();
} }
async function removeDocument(id: string): Promise<void> { async function removeDocument(id: string) {
await update<Record<string, TriggerIndexedDbWriteDocument>>( await update<Record<string, TriggerIndexedDbWriteDocument>>(
"documents", "documents",
(old) => { (old) => {
@@ -38,13 +38,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
delete documents[id]; delete documents[id];
return documents; return documents;
}, },
graphiteStore graphiteStore,
); );
await storeDocumentOrder(); await storeDocumentOrder();
} }
async function loadDocuments(): Promise<void> { async function loadDocuments() {
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore); const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore); const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
if (!previouslySavedDocuments || !documentOrder) return; if (!previouslySavedDocuments || !documentOrder) return;
@@ -64,11 +64,11 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
// PREFERENCES // PREFERENCES
async function savePreferences(preferences: TriggerSavePreferences["preferences"]): Promise<void> { async function savePreferences(preferences: TriggerSavePreferences["preferences"]) {
await set("preferences", preferences, graphiteStore); await set("preferences", preferences, graphiteStore);
} }
async function loadPreferences(): Promise<void> { async function loadPreferences() {
const preferences = await get<Record<string, unknown>>("preferences", graphiteStore); const preferences = await get<Record<string, unknown>>("preferences", graphiteStore);
if (!preferences) return; if (!preferences) return;
@@ -95,7 +95,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
}); });
} }
export async function wipeDocuments(): Promise<void> { export async function wipeDocuments() {
await del("documents_tab_order", graphiteStore); await del("documents_tab_order", graphiteStore);
await del("documents", graphiteStore); await del("documents", graphiteStore);
} }
+5 -5
View File
@@ -1,4 +1,4 @@
import {writable} from "svelte/store"; import { writable } from "svelte/store";
import { type IconName } from "@graphite/utility-functions/icons"; import { type IconName } from "@graphite/utility-functions/icons";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
@@ -17,18 +17,18 @@ export function createDialogState(editor: Editor) {
panicDetails: "", panicDetails: "",
}); });
function dismissDialog(): void { function dismissDialog() {
update((state) => { update((state) => {
// Disallow dismissing the crash dialog since it can confuse users why the app stopped responding if they dismiss it without realizing what it means // Disallow dismissing the crash dialog since it can confuse users why the app stopped responding if they dismiss it without realizing what it means
if (state.panicDetails === "") state.visible = false; if (state.panicDetails === "") state.visible = false;
return state; return state;
}); });
} }
// Creates a crash dialog from JS once the editor has panicked. // Creates a crash dialog from JS once the editor has panicked.
// Normal dialogs are created in the Rust backend, but for the crash dialog, the editor instance has panicked so it cannot respond to widget callbacks. // Normal dialogs are created in the Rust backend, but for the crash dialog, the editor instance has panicked so it cannot respond to widget callbacks.
function createCrashDialog(panicDetails: string): void { function createCrashDialog(panicDetails: string) {
update((state) => { update((state) => {
state.visible = true; state.visible = true;
@@ -51,7 +51,7 @@ export function createDialogState(editor: Editor) {
state.title = displayDialog.title; state.title = displayDialog.title;
state.icon = displayDialog.icon; state.icon = displayDialog.icon;
return state; return state;
}); });
}); });
+7 -7
View File
@@ -1,5 +1,5 @@
import {tick} from "svelte"; import { tick } from "svelte";
import {writable} from "svelte/store"; import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { import {
@@ -35,7 +35,7 @@ export function createDocumentState(editor: Editor) {
// Update layouts // Update layouts
editor.subscriptions.subscribeJsMessage(UpdateDocumentModeLayout, async (updateDocumentModeLayout) => { editor.subscriptions.subscribeJsMessage(UpdateDocumentModeLayout, async (updateDocumentModeLayout) => {
await tick(); await tick();
update((state) => { update((state) => {
// `state.documentModeLayout` is mutated in the function // `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout); patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout);
@@ -44,7 +44,7 @@ export function createDocumentState(editor: Editor) {
}); });
editor.subscriptions.subscribeJsMessage(UpdateToolOptionsLayout, async (updateToolOptionsLayout) => { editor.subscriptions.subscribeJsMessage(UpdateToolOptionsLayout, async (updateToolOptionsLayout) => {
await tick(); await tick();
update((state) => { update((state) => {
// `state.documentModeLayout` is mutated in the function // `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout); patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout);
@@ -53,7 +53,7 @@ export function createDocumentState(editor: Editor) {
}); });
editor.subscriptions.subscribeJsMessage(UpdateDocumentBarLayout, async (updateDocumentBarLayout) => { editor.subscriptions.subscribeJsMessage(UpdateDocumentBarLayout, async (updateDocumentBarLayout) => {
await tick(); await tick();
update((state) => { update((state) => {
// `state.documentModeLayout` is mutated in the function // `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout); patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout);
@@ -62,7 +62,7 @@ export function createDocumentState(editor: Editor) {
}); });
editor.subscriptions.subscribeJsMessage(UpdateToolShelfLayout, async (updateToolShelfLayout) => { editor.subscriptions.subscribeJsMessage(UpdateToolShelfLayout, async (updateToolShelfLayout) => {
await tick(); await tick();
update((state) => { update((state) => {
// `state.documentModeLayout` is mutated in the function // `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout); patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout);
@@ -79,7 +79,7 @@ export function createDocumentState(editor: Editor) {
}); });
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => { editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => {
await tick(); await tick();
update((state) => { update((state) => {
// `state.documentModeLayout` is mutated in the function // `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.workingColorsLayout, updateWorkingColorsLayout); patchWidgetLayout(state.workingColorsLayout, updateWorkingColorsLayout);
+5 -5
View File
@@ -1,4 +1,4 @@
import {writable} from "svelte/store"; import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
@@ -9,7 +9,7 @@ export function createFullscreenState(_: Editor) {
keyboardLocked: false, keyboardLocked: false,
}); });
function fullscreenModeChanged(): void { function fullscreenModeChanged() {
update((state) => { update((state) => {
state.windowFullscreen = Boolean(document.fullscreenElement); state.windowFullscreen = Boolean(document.fullscreenElement);
if (!state.windowFullscreen) state.keyboardLocked = false; if (!state.windowFullscreen) state.keyboardLocked = false;
@@ -17,7 +17,7 @@ export function createFullscreenState(_: Editor) {
}); });
} }
async function enterFullscreen(): Promise<void> { async function enterFullscreen() {
await document.documentElement.requestFullscreen(); await document.documentElement.requestFullscreen();
if (keyboardLockApiSupported) { if (keyboardLockApiSupported) {
@@ -31,11 +31,11 @@ export function createFullscreenState(_: Editor) {
} }
} }
async function exitFullscreen(): Promise<void> { async function exitFullscreen() {
await document.exitFullscreen(); await document.exitFullscreen();
} }
async function toggleFullscreen(): Promise<void> { async function toggleFullscreen() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
update((state) => { update((state) => {
if (state.windowFullscreen) exitFullscreen().then(resolve).catch(reject); if (state.windowFullscreen) exitFullscreen().then(resolve).catch(reject);
+4 -13
View File
@@ -1,15 +1,7 @@
import {writable} from "svelte/store"; import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { import { type FrontendNode, type FrontendNodeLink, type FrontendNodeType, UpdateNodeGraph, UpdateNodeTypes, UpdateNodeThumbnail, UpdateZoomWithScroll } from "@graphite/wasm-communication/messages";
type FrontendNode,
type FrontendNodeLink,
type FrontendNodeType,
UpdateNodeGraph,
UpdateNodeTypes,
UpdateNodeThumbnail,
UpdateZoomWithScroll,
} from "@graphite/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createNodeGraphState(editor: Editor) { export function createNodeGraphState(editor: Editor) {
@@ -26,11 +18,10 @@ export function createNodeGraphState(editor: Editor) {
update((state) => { update((state) => {
state.nodes = updateNodeGraph.nodes; state.nodes = updateNodeGraph.nodes;
state.links = updateNodeGraph.links; state.links = updateNodeGraph.links;
let newThumbnails = new Map<bigint, string>(); const newThumbnails = new Map<bigint, string>();
state.nodes.forEach((node) => { state.nodes.forEach((node) => {
const thumbnail = state.thumbnails.get(node.id); const thumbnail = state.thumbnails.get(node.id);
if (thumbnail) if (thumbnail) newThumbnails.set(node.id, thumbnail);
newThumbnails.set(node.id, thumbnail);
}); });
state.thumbnails = newThumbnails; state.thumbnails = newThumbnails;
return state; return state;
+7 -9
View File
@@ -2,7 +2,8 @@
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { downloadFileText, downloadFileBlob, upload, downloadFileURL } from "@graphite/utility-functions/files"; import { copyToClipboardFileURL } from "@graphite/io-managers/clipboard";
import { downloadFileText, downloadFileBlob, upload } from "@graphite/utility-functions/files";
import { extractPixelData, imageToPNG, rasterizeSVG, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization"; import { extractPixelData, imageToPNG, rasterizeSVG, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
import { type Editor } from "@graphite/wasm-communication/editor"; import { type Editor } from "@graphite/wasm-communication/editor";
import { import {
@@ -20,7 +21,6 @@ import {
UpdateImageData, UpdateImageData,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
} from "@graphite/wasm-communication/messages"; } from "@graphite/wasm-communication/messages";
import { copyToClipboardFileURL } from "@graphite/io-managers/clipboard";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createPortfolioState(editor: Editor) { export function createPortfolioState(editor: Editor) {
@@ -35,7 +35,7 @@ export function createPortfolioState(editor: Editor) {
update((state) => { update((state) => {
state.documents = updateOpenDocumentList.openDocuments; state.documents = updateOpenDocumentList.openDocuments;
return state; return state;
}) });
}); });
editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => { editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => {
update((state) => { update((state) => {
@@ -43,7 +43,7 @@ export function createPortfolioState(editor: Editor) {
const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId); const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId);
state.activeDocumentIndex = activeId; state.activeDocumentIndex = activeId;
return state; return state;
}) });
}); });
editor.subscriptions.subscribeJsMessage(TriggerFetchAndOpenDocument, async (triggerFetchAndOpenDocument) => { editor.subscriptions.subscribeJsMessage(TriggerFetchAndOpenDocument, async (triggerFetchAndOpenDocument) => {
try { try {
@@ -98,7 +98,6 @@ export function createPortfolioState(editor: Editor) {
} catch { } catch {
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image // Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
} }
}); });
editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => { editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => {
updateImageData.imageData.forEach(async (element) => { updateImageData.imageData.forEach(async (element) => {
@@ -126,12 +125,11 @@ export function createPortfolioState(editor: Editor) {
editor.instance.renderGraphUsingRasterizedRegionBelowLayer(documentId, layerPath, new Uint8Array(imageData.data), imageData.width, imageData.height); editor.instance.renderGraphUsingRasterizedRegionBelowLayer(documentId, layerPath, new Uint8Array(imageData.data), imageData.width, imageData.height);
} }
} } catch (e) {
// getImageData may throw an exception if the resolution is too high // getImageData may throw an exception if the resolution is too high
catch (e) { // eslint-disable-next-line no-console
console.error("Failed to rasterize the SVG canvas in JS to be sent back to Rust:", e); console.error("Failed to rasterize the SVG canvas in JS to be sent back to Rust:", e);
} }
}); });
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => { editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
URL.revokeObjectURL(triggerRevokeBlobUrl.url); URL.revokeObjectURL(triggerRevokeBlobUrl.url);
+2 -2
View File
@@ -9,13 +9,13 @@ export function debouncer<T>(callFn: (value: T) => unknown, { debounceTime = 60
let currentValue: T | undefined; let currentValue: T | undefined;
let recentlyUpdated: boolean = false; let recentlyUpdated: boolean = false;
const emitValue = (): void => { const emitValue = () => {
recentlyUpdated = false; recentlyUpdated = false;
if (currentValue === undefined) return; if (currentValue === undefined) return;
updateValue(currentValue); updateValue(currentValue);
}; };
const updateValue = (newValue: T): void => { const updateValue = (newValue: T) => {
if (recentlyUpdated) { if (recentlyUpdated) {
currentValue = newValue; currentValue = newValue;
return; return;
+5 -5
View File
@@ -1,4 +1,4 @@
export function downloadFileURL(filename: string, url: string): void { export function downloadFileURL(filename: string, url: string) {
const element = document.createElement("a"); const element = document.createElement("a");
element.href = url; element.href = url;
@@ -7,7 +7,7 @@ export function downloadFileURL(filename: string, url: string): void {
element.click(); element.click();
} }
export function downloadFileBlob(filename: string, blob: Blob): void { export function downloadFileBlob(filename: string, blob: Blob) {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
downloadFileURL(filename, url); downloadFileURL(filename, url);
@@ -15,7 +15,7 @@ export function downloadFileBlob(filename: string, blob: Blob): void {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
export function downloadFileText(filename: string, text: string): void { export function downloadFileText(filename: string, text: string) {
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([text], { type }); const blob = new Blob([text], { type });
@@ -41,7 +41,7 @@ export async function upload<T extends "text" | "data">(acceptedExtensions: stri
resolve({ filename, type, content }); resolve({ filename, type, content });
} }
}, },
{ capture: false, once: true } { capture: false, once: true },
); );
element.click(); element.click();
@@ -55,7 +55,7 @@ type UploadResultType<T> = T extends "text" ? string : T extends "data" ? Uint8A
export function blobToBase64(blob: Blob): Promise<string> { export function blobToBase64(blob: Blob): Promise<string> {
return new Promise((resolve) => { return new Promise((resolve) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = (): void => resolve(typeof reader.result === "string" ? reader.result : ""); reader.onloadend = () => resolve(typeof reader.result === "string" ? reader.result : "");
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
}); });
} }
+3 -3
View File
@@ -104,16 +104,16 @@ import Copy from "@graphite-frontend/assets/icon-16px-solid/copy.svg";
import Credits from "@graphite-frontend/assets/icon-16px-solid/credits.svg"; import Credits from "@graphite-frontend/assets/icon-16px-solid/credits.svg";
import CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.svg"; import CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.svg";
import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg"; import Edit from "@graphite-frontend/assets/icon-16px-solid/edit.svg";
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg"; import EyeHidden from "@graphite-frontend/assets/icon-16px-solid/eye-hidden.svg";
import EyeVisible from "@graphite-frontend/assets/icon-16px-solid/eye-visible.svg"; import EyeVisible from "@graphite-frontend/assets/icon-16px-solid/eye-visible.svg";
import Eyedropper from "@graphite-frontend/assets/icon-16px-solid/eyedropper.svg";
import File from "@graphite-frontend/assets/icon-16px-solid/file.svg"; import File from "@graphite-frontend/assets/icon-16px-solid/file.svg";
import FlipHorizontal from "@graphite-frontend/assets/icon-16px-solid/flip-horizontal.svg"; import FlipHorizontal from "@graphite-frontend/assets/icon-16px-solid/flip-horizontal.svg";
import FlipVertical from "@graphite-frontend/assets/icon-16px-solid/flip-vertical.svg"; import FlipVertical from "@graphite-frontend/assets/icon-16px-solid/flip-vertical.svg";
import Folder from "@graphite-frontend/assets/icon-16px-solid/folder.svg"; import Folder from "@graphite-frontend/assets/icon-16px-solid/folder.svg";
import GraphiteLogo from "@graphite-frontend/assets/icon-16px-solid/graphite-logo.svg";
import GraphViewClosed from "@graphite-frontend/assets/icon-16px-solid/graph-view-closed.svg"; import GraphViewClosed from "@graphite-frontend/assets/icon-16px-solid/graph-view-closed.svg";
import GraphViewOpen from "@graphite-frontend/assets/icon-16px-solid/graph-view-open.svg"; import GraphViewOpen from "@graphite-frontend/assets/icon-16px-solid/graph-view-open.svg";
import GraphiteLogo from "@graphite-frontend/assets/icon-16px-solid/graphite-logo.svg";
import IconsGrid from "@graphite-frontend/assets/icon-16px-solid/icons-grid.svg"; import IconsGrid from "@graphite-frontend/assets/icon-16px-solid/icons-grid.svg";
import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg"; import Image from "@graphite-frontend/assets/icon-16px-solid/image.svg";
import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg"; import Layer from "@graphite-frontend/assets/icon-16px-solid/layer.svg";
@@ -269,8 +269,8 @@ import VectorFreehandTool from "@graphite-frontend/assets/icon-24px-two-tone/vec
import VectorLineTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-line-tool.svg"; import VectorLineTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-line-tool.svg";
import VectorPathTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-path-tool.svg"; import VectorPathTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-path-tool.svg";
import VectorPenTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-pen-tool.svg"; import VectorPenTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-pen-tool.svg";
import VectorRectangleTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-rectangle-tool.svg";
import VectorPolygonTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-polygon-tool.svg"; import VectorPolygonTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-polygon-tool.svg";
import VectorRectangleTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-rectangle-tool.svg";
import VectorSplineTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-spline-tool.svg"; import VectorSplineTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-spline-tool.svg";
import VectorTextTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-text-tool.svg"; import VectorTextTool from "@graphite-frontend/assets/icon-24px-two-tone/vector-text-tool.svg";
+1 -1
View File
@@ -9,7 +9,7 @@ export function requestWithUploadDownloadProgress(
method: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH", method: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH",
body: string, body: string,
uploadProgress: (progress: number) => void, uploadProgress: (progress: number) => void,
downloadOccurring: () => void downloadOccurring: () => void,
): [Promise<RequestResult>, XMLHttpRequest | undefined] { ): [Promise<RequestResult>, XMLHttpRequest | undefined] {
let xhrValue: XMLHttpRequest | undefined; let xhrValue: XMLHttpRequest | undefined;
const promise = new Promise<RequestResult>((resolve, reject) => { const promise = new Promise<RequestResult>((resolve, reject) => {
+2 -1
View File
@@ -61,10 +61,11 @@ export function isEventSupported(eventName: string) {
if (["submit", "reset"].includes(eventName)) tag = "form"; if (["submit", "reset"].includes(eventName)) tag = "form";
if (["error", "load", "abort"].includes(eventName)) tag = "img"; if (["error", "load", "abort"].includes(eventName)) tag = "img";
const element = document.createElement(tag); const element = document.createElement(tag);
if (onEventName in element) return true; if (onEventName in element) return true;
// Check if "return;" gets converted into a function, meaning the event is supported // Check if "return;" gets converted into a function, meaning the event is supported
element.setAttribute(eventName, "return;"); element.setAttribute(eventName, "return;");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return typeof (element as Record<string, any>)[onEventName] === "function"; return typeof (element as Record<string, any>)[onEventName] === "function";
} }
@@ -26,7 +26,7 @@ export async function rasterizeSVGCanvas(svg: string, width: number, height: num
const image = new Image(); const image = new Image();
image.src = url; image.src = url;
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
image.onload = (): void => resolve(); image.onload = () => resolve();
}); });
// Draw our SVG to the canvas // Draw our SVG to the canvas
@@ -41,7 +41,7 @@ export async function rasterizeSVGCanvas(svg: string, width: number, height: num
// Rasterize the string of an SVG document at a given width and height and turn it into the blob data of an image file matching the given MIME type // Rasterize the string of an SVG document at a given width and height and turn it into the blob data of an image file matching the given MIME type
export async function rasterizeSVG(svg: string, width: number, height: number, mime: string, backgroundColor?: string): Promise<Blob> { export async function rasterizeSVG(svg: string, width: number, height: number, mime: string, backgroundColor?: string): Promise<Blob> {
if (!width || !height) throw new Error("Width and height must be nonzero when given to rasterizeSVG()"); if (!width || !height) throw new Error("Width and height must be nonzero when given to rasterizeSVG()");
const canvas = await rasterizeSVGCanvas(svg, width, height, backgroundColor); const canvas = await rasterizeSVGCanvas(svg, width, height, backgroundColor);
// Convert the canvas to an image of the correct MIME type // Convert the canvas to an image of the correct MIME type
@@ -109,7 +109,7 @@ export async function imageToCanvasContext(imageData: ImageBitmapSource): Promis
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
const context = canvas.getContext("2d"); const context = canvas.getContext("2d");
if (!context) throw new Error("Could not create canvas context"); if (!context) throw new Error("Could not create canvas context");
context.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height); context.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height);
+23 -17
View File
@@ -1,8 +1,7 @@
import type WasmBindgenPackage from "@graphite-frontend/wasm/pkg"; // import { panicProxy } from "@graphite/utility-functions/panic-proxy";
import init, { setRandomSeed, wasmMemory, JsEditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
import { panicProxy } from "@graphite/utility-functions/panic-proxy";
import { type JsMessageType } from "@graphite/wasm-communication/messages"; import { type JsMessageType } from "@graphite/wasm-communication/messages";
import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/wasm-communication/subscription-router"; import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/wasm-communication/subscription-router";
import init, { setRandomSeed, wasmMemory, JsEditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
export type WasmRawInstance = WebAssembly.Memory; export type WasmRawInstance = WebAssembly.Memory;
export type WasmEditorInstance = JsEditorHandle; export type WasmEditorInstance = JsEditorHandle;
@@ -11,7 +10,7 @@ export type Editor = Readonly<ReturnType<typeof createEditor>>;
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()` // `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
let wasmImport: WebAssembly.Memory | undefined; let wasmImport: WebAssembly.Memory | undefined;
export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint): Promise<void> { export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, transform: Float64Array, documentId: bigint) {
const blob = new Blob([imageData], { type: mime }); const blob = new Blob([imageData], { type: mime });
const blobURL = URL.createObjectURL(blob); const blobURL = URL.createObjectURL(blob);
@@ -21,10 +20,11 @@ export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: st
image.src = blobURL; image.src = blobURL;
await image.decode(); await image.decode();
window["editorInstance"]?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform); // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform);
} }
export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: string, documentId: bigint, url: string): Promise<void> { export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: string, documentId: bigint, url: string) {
const data = await fetch(url); const data = await fetch(url);
const blob = await data.blob(); const blob = await data.blob();
@@ -35,16 +35,18 @@ export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: str
image.src = blobURL; image.src = blobURL;
await image.decode(); await image.decode();
window["editorInstance"]?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined); // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined);
} }
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api"); const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
export async function dispatchTauri(message: unknown): Promise<void> { export async function dispatchTauri(message: unknown) {
if (!tauri) return; if (!tauri) return;
try { try {
const response = await (await tauri).invoke("handle_message", { message }); const response = await (await tauri).invoke("handle_message", { message });
window["editorInstance"]?.tauriResponse(response); // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorInstance?.tauriResponse(response);
} catch { } catch {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error("Failed to dispatch Tauri message"); console.error("Failed to dispatch Tauri message");
@@ -52,7 +54,7 @@ export async function dispatchTauri(message: unknown): Promise<void> {
} }
// Should be called asynchronously before `createEditor()` // Should be called asynchronously before `createEditor()`
export async function initWasm(): Promise<void> { export async function initWasm() {
// Skip if the WASM module is already initialized // Skip if the WASM module is already initialized
if (wasmImport !== undefined) return; if (wasmImport !== undefined) return;
@@ -60,8 +62,8 @@ export async function initWasm(): Promise<void> {
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
await init(); await init();
wasmImport = await wasmMemory(); wasmImport = await wasmMemory();
window["imageCanvases"] = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).imageCanvases = {};
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers // Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
@@ -79,12 +81,13 @@ export function createEditor() {
const raw: WasmRawInstance = wasmImport; const raw: WasmRawInstance = wasmImport;
// Instance: Object containing many functions from `editor_api.rs` that are part of the editor instance (generated by wasm-bindgen) // Instance: Object containing many functions from `editor_api.rs` that are part of the editor instance (generated by wasm-bindgen)
const instance: WasmEditorInstance = new JsEditorHandle((messageType: JsMessageType, messageData: Record<string, unknown>): void => { const instance: WasmEditorInstance = new JsEditorHandle((messageType: JsMessageType, messageData: Record<string, unknown>) => {
// This callback is called by WASM when a FrontendMessage is received from the WASM wrapper editor instance // This callback is called by WASM when a FrontendMessage is received from the WASM wrapper editor instance
// We pass along the first two arguments then add our own `raw` and `instance` context for the last two arguments // We pass along the first two arguments then add our own `raw` and `instance` context for the last two arguments
subscriptions.handleJsMessage(messageType, messageData, raw, instance); subscriptions.handleJsMessage(messageType, messageData, raw, instance);
}); });
window["editorInstance"] = instance; // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorInstance = instance;
// Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend // Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend
const subscriptions: SubscriptionRouter = createSubscriptionRouter(); const subscriptions: SubscriptionRouter = createSubscriptionRouter();
@@ -97,14 +100,16 @@ export function createEditor() {
try { try {
const url = new URL(`https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/${demoArtwork}.graphite`); const url = new URL(`https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/${demoArtwork}.graphite`);
const data = await fetch(url); const data = await fetch(url);
const filename = url.pathname.split("/").pop() || "Untitled"; const filename = url.pathname.split("/").pop() || "Untitled";
const content = await data.text(); const content = await data.text();
instance.openDocumentFile(filename, content); instance.openDocumentFile(filename, content);
// Remove the hash fragment from the URL // Remove the hash fragment from the URL
history.replaceState("", "", `${window.location.pathname}${window.location.search}`); history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
} catch {} } catch {
// Do nothing
}
})(); })();
return { return {
@@ -115,5 +120,6 @@ export function createEditor() {
} }
export function injectImaginatePollServerStatus() { export function injectImaginatePollServerStatus() {
window["editorInstance"]?.injectImaginatePollServerStatus() // eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).editorInstance?.injectImaginatePollServerStatus();
} }
+35 -31
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { Transform, Type, plainToClass } from "class-transformer"; import { Transform, Type, plainToClass } from "class-transformer";
@@ -11,7 +12,7 @@ export class JsMessage {
} }
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] })); const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) })); // const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
export type XY = { x: number; y: number }; export type XY = { x: number; y: number };
@@ -506,19 +507,19 @@ export class UpdateMouseCursor extends JsMessage {
readonly cursor!: MouseCursorIcon; readonly cursor!: MouseCursorIcon;
} }
export class TriggerLoadAutoSaveDocuments extends JsMessage { } export class TriggerLoadAutoSaveDocuments extends JsMessage {}
export class TriggerLoadPreferences extends JsMessage { } export class TriggerLoadPreferences extends JsMessage {}
export class TriggerFetchAndOpenDocument extends JsMessage { export class TriggerFetchAndOpenDocument extends JsMessage {
readonly url!: string; readonly url!: string;
} }
export class TriggerOpenDocument extends JsMessage { } export class TriggerOpenDocument extends JsMessage {}
export class TriggerImport extends JsMessage { } export class TriggerImport extends JsMessage {}
export class TriggerPaste extends JsMessage { } export class TriggerPaste extends JsMessage {}
export class TriggerCopyToClipboardBlobUrl extends JsMessage { export class TriggerCopyToClipboardBlobUrl extends JsMessage {
readonly blobUrl!: string; readonly blobUrl!: string;
@@ -557,7 +558,7 @@ export class TriggerRasterizeRegionBelowLayer extends JsMessage {
readonly size!: [number, number]; readonly size!: [number, number];
} }
export class TriggerRefreshBoundsOfViewports extends JsMessage { } export class TriggerRefreshBoundsOfViewports extends JsMessage {}
export class TriggerRevokeBlobUrl extends JsMessage { export class TriggerRevokeBlobUrl extends JsMessage {
readonly url!: string; readonly url!: string;
@@ -567,10 +568,13 @@ export class TriggerSavePreferences extends JsMessage {
readonly preferences!: Record<string, unknown>; readonly preferences!: Record<string, unknown>;
} }
export class DocumentChanged extends JsMessage { } export class DocumentChanged extends JsMessage {}
export class UpdateDocumentLayerTreeStructureJs extends JsMessage { export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) { constructor(
readonly layerId: bigint,
readonly children: UpdateDocumentLayerTreeStructureJs[],
) {
super(); super();
} }
} }
@@ -660,7 +664,7 @@ export class UpdateImageData extends JsMessage {
readonly imageData!: ImaginateImageData[]; readonly imageData!: ImaginateImageData[];
} }
export class DisplayRemoveEditableTextbox extends JsMessage { } export class DisplayRemoveEditableTextbox extends JsMessage {}
export class UpdateDocumentLayerDetails extends JsMessage { export class UpdateDocumentLayerDetails extends JsMessage {
@Type(() => LayerPanelEntry) @Type(() => LayerPanelEntry)
@@ -706,7 +710,7 @@ export class ImaginateImageData {
readonly transform!: Float64Array; readonly transform!: Float64Array;
} }
export class DisplayDialogDismiss extends JsMessage { } export class DisplayDialogDismiss extends JsMessage {}
export class Font { export class Font {
fontFamily!: string; fontFamily!: string;
@@ -729,7 +733,7 @@ export class TriggerVisitLink extends JsMessage {
url!: string; url!: string;
} }
export class TriggerTextCommit extends JsMessage { } export class TriggerTextCommit extends JsMessage {}
export class TriggerTextCopy extends JsMessage { export class TriggerTextCopy extends JsMessage {
readonly copyText!: string; readonly copyText!: string;
@@ -739,7 +743,7 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
readonly commitDate!: string; readonly commitDate!: string;
} }
export class TriggerViewportResize extends JsMessage { } export class TriggerViewportResize extends JsMessage {}
// WIDGET PROPS // WIDGET PROPS
@@ -760,7 +764,7 @@ export class CheckboxInput extends WidgetProps {
export class ColorInput extends WidgetProps { export class ColorInput extends WidgetProps {
@Transform(({ value }: { value: { red: number; green: number; blue: number; alpha: number } | undefined }) => @Transform(({ value }: { value: { red: number; green: number; blue: number; alpha: number } | undefined }) =>
value === undefined ? new Color("none") : new Color(value.red, value.green, value.blue, value.alpha) value === undefined ? new Color("none") : new Color(value.red, value.green, value.blue, value.alpha),
) )
value!: Color; value!: Color;
@@ -784,7 +788,7 @@ type MenuEntryCommon = {
export type MenuBarEntry = MenuEntryCommon & { export type MenuBarEntry = MenuEntryCommon & {
action: Widget; action: Widget;
children?: MenuBarEntry[][]; children?: MenuBarEntry[][];
disabled?: boolean, disabled?: boolean;
}; };
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets // An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
@@ -1042,7 +1046,7 @@ export class TextButton extends WidgetProps {
emphasized!: boolean; emphasized!: boolean;
noBackground!: boolean; noBackground!: boolean;
minWidth!: number; minWidth!: number;
disabled!: boolean; disabled!: boolean;
@@ -1217,7 +1221,7 @@ export function defaultWidgetLayout(): WidgetLayout {
} }
// Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument // Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument
export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: WidgetDiffUpdate): void { export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: WidgetDiffUpdate) {
layout.layoutTarget = updates.layoutTarget; layout.layoutTarget = updates.layoutTarget;
updates.diff.forEach((update) => { updates.diff.forEach((update) => {
@@ -1315,19 +1319,19 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
} }
// WIDGET LAYOUTS // WIDGET LAYOUTS
export class UpdateDialogButtons extends WidgetDiffUpdate { } export class UpdateDialogButtons extends WidgetDiffUpdate {}
export class UpdateDialogColumn1 extends WidgetDiffUpdate { } export class UpdateDialogColumn1 extends WidgetDiffUpdate {}
export class UpdateDialogColumn2 extends WidgetDiffUpdate { } export class UpdateDialogColumn2 extends WidgetDiffUpdate {}
export class UpdateDocumentBarLayout extends WidgetDiffUpdate { } export class UpdateDocumentBarLayout extends WidgetDiffUpdate {}
export class UpdateDocumentModeLayout extends WidgetDiffUpdate { } export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate { } export class UpdateGraphViewOverlayButtonLayout extends WidgetDiffUpdate {}
export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate { } export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate {}
// Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed // Extends JsMessage instead of WidgetDiffUpdate because the menu bar isn't diffed
export class UpdateMenuBarLayout extends JsMessage { export class UpdateMenuBarLayout extends JsMessage {
@@ -1339,17 +1343,17 @@ export class UpdateMenuBarLayout extends JsMessage {
layout!: MenuBarEntry[]; layout!: MenuBarEntry[];
} }
export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate { } export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate {}
export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate { } export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate {}
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate { } export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {}
export class UpdateToolOptionsLayout extends WidgetDiffUpdate { } export class UpdateToolOptionsLayout extends WidgetDiffUpdate {}
export class UpdateToolShelfLayout extends WidgetDiffUpdate { } export class UpdateToolShelfLayout extends WidgetDiffUpdate {}
export class UpdateWorkingColorsLayout extends WidgetDiffUpdate { } export class UpdateWorkingColorsLayout extends WidgetDiffUpdate {}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] { function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
@@ -1366,7 +1370,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
action: hoistWidgetHolders([entry.action])[0], action: hoistWidgetHolders([entry.action])[0],
children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined, children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined,
disabled: entry.disabled ?? false, disabled: entry.disabled ?? false,
})) })),
); );
} }
@@ -13,18 +13,18 @@ type JsMessageCallbackMap = Record<string, JsMessageCallback<any> | undefined>;
export function createSubscriptionRouter() { export function createSubscriptionRouter() {
const subscriptions: JsMessageCallbackMap = {}; const subscriptions: JsMessageCallbackMap = {};
const subscribeJsMessage = <T extends JsMessage, Args extends unknown[]>(messageType: new (...args: Args) => T, callback: JsMessageCallback<T>): void => { const subscribeJsMessage = <T extends JsMessage, Args extends unknown[]>(messageType: new (...args: Args) => T, callback: JsMessageCallback<T>) => {
subscriptions[messageType.name] = callback; subscriptions[messageType.name] = callback;
}; };
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmRawInstance, instance: WasmEditorInstance): void => { const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmRawInstance, instance: WasmEditorInstance) => {
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class // Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
const messageMaker = messageMakers[messageType]; const messageMaker = messageMakers[messageType];
if (!messageMaker) { if (!messageMaker) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error( console.error(
`Received a frontend message of type "${messageType}" but was not able to parse the data. ` + `Received a frontend message of type "${messageType}" but was not able to parse the data. ` +
"(Perhaps this message parser isn't exported in `messageMakers` at the bottom of `messages.ts`.)" "(Perhaps this message parser isn't exported in `messageMakers` at the bottom of `messages.ts`.)",
); );
return; return;
} }
+1 -1
View File
@@ -20,7 +20,7 @@
}, },
"lib": ["esnext", "dom", "dom.iterable", "scripthost"] "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte", "tests/**/*.ts"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte", "*.ts", "*.js", "*.cjs"],
"exclude": ["node_modules"], "exclude": ["node_modules"],
"ts-node": { "ts-node": {
"compilerOptions": { "compilerOptions": {
+37 -26
View File
@@ -1,9 +1,13 @@
import { defineConfig } from "vite"; /* eslint-disable no-console */
import { spawnSync } from "child_process"; import { spawnSync } from "child_process";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import { sveltePreprocess } from "svelte-preprocess/dist/autoProcess";
import path from "path"; import path from "path";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import rollupPluginLicense, { type Dependency } from "rollup-plugin-license"; import rollupPluginLicense, { type Dependency } from "rollup-plugin-license";
import { sveltePreprocess } from "svelte-preprocess/dist/autoProcess";
import { defineConfig } from "vite";
const projectRootDir = path.resolve(__dirname); const projectRootDir = path.resolve(__dirname);
@@ -30,11 +34,11 @@ export default defineConfig({
svelte({ svelte({
preprocess: [sveltePreprocess()], preprocess: [sveltePreprocess()],
onwarn(warning, defaultHandler) { onwarn(warning, defaultHandler) {
const suppressed = ["vite-plugin-svelte-css-no-scopable-elements"]; const suppressed = ["css-unused-selector", "vite-plugin-svelte-css-no-scopable-elements"];
if (suppressed.includes(warning.code)) return; if (suppressed.includes(warning.code)) return;
defaultHandler?.(warning); defaultHandler?.(warning);
} },
}), }),
], ],
resolve: { resolve: {
@@ -44,7 +48,7 @@ export default defineConfig({
{ find: "@graphite/../assets", replacement: path.resolve(projectRootDir, "assets") }, { find: "@graphite/../assets", replacement: path.resolve(projectRootDir, "assets") },
{ find: "@graphite/../public", replacement: path.resolve(projectRootDir, "public") }, { find: "@graphite/../public", replacement: path.resolve(projectRootDir, "public") },
{ find: "@graphite", replacement: path.resolve(projectRootDir, "src") }, { find: "@graphite", replacement: path.resolve(projectRootDir, "src") },
] ],
}, },
server: { server: {
port: 8080, port: 8080,
@@ -70,18 +74,18 @@ export default defineConfig({
}, },
}); });
interface LicenseInfo { type LicenseInfo = {
licenseName: string; licenseName: string;
licenseText: string; licenseText: string;
packages: PackageInfo[]; packages: PackageInfo[];
} };
interface PackageInfo { type PackageInfo = {
name: string; name: string;
version: string; version: string;
author: string; author: string;
repository: string; repository: string;
} };
function formatThirdPartyLicenses(jsLicenses: Dependency[]): string { function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
// Generate the Rust license information. // Generate the Rust license information.
@@ -90,8 +94,8 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
// Ensure we have license information to work with before proceeding. // Ensure we have license information to work with before proceeding.
if (licenses.length === 0) { if (licenses.length === 0) {
// This is probably caused by `cargo about` not being installed. // This is probably caused by `cargo about` not being installed.
console.error("Could not run \`cargo about\`, which is required to generate license information."); console.error("Could not run `cargo about`, which is required to generate license information.");
console.error("To install cargo-about on your system, you can run \`cargo install cargo-about\`."); console.error("To install cargo-about on your system, you can run `cargo install cargo-about`.");
console.error("License information is required in production builds. Aborting."); console.error("License information is required in production builds. Aborting.");
process.exit(1); process.exit(1);
@@ -136,9 +140,10 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
// Filter out the internal Graphite crates, which are not third-party. // Filter out the internal Graphite crates, which are not third-party.
licenses = licenses.filter((license) => { licenses = licenses.filter((license) => {
license.packages = license.packages.filter((packageInfo) => license.packages = license.packages.filter(
!(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) && (packageInfo) =>
!(packageInfo.author && packageInfo.author.toLowerCase().includes("contact@graphite.rs")) !(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) &&
!(packageInfo.author && packageInfo.author.toLowerCase().includes("contact@graphite.rs")),
); );
return license.packages.length > 0; return license.packages.length > 0;
}); });
@@ -207,18 +212,24 @@ function generateRustLicenses(): LicenseInfo[] | undefined {
// We call eval indirectly to avoid a warning as explained here: <https://esbuild.github.io/content-types/#direct-eval>. // We call eval indirectly to avoid a warning as explained here: <https://esbuild.github.io/content-types/#direct-eval>.
const indirectEval = eval; const indirectEval = eval;
const licensesArray = indirectEval(stdout) as LicenseInfo[]; const licensesArray = indirectEval(stdout) as LicenseInfo[];
// Remove the HTML character encoding caused by Handlebars. // Remove the HTML character encoding caused by Handlebars.
let rustLicenses = (licensesArray || []).map((rustLicense): LicenseInfo => ({ const rustLicenses = (licensesArray || []).map(
licenseName: htmlDecode(rustLicense.licenseName), (rustLicense): LicenseInfo => ({
licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)), licenseName: htmlDecode(rustLicense.licenseName),
packages: rustLicense.packages.map((packageInfo): PackageInfo => ({ licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)),
name: htmlDecode(packageInfo.name), packages: rustLicense.packages.map(
version: htmlDecode(packageInfo.version), (packageInfo): PackageInfo => ({
author: htmlDecode(packageInfo.author).replace(/\[(.*), \]/, "$1").replace("[]", ""), name: htmlDecode(packageInfo.name),
repository: htmlDecode(packageInfo.repository), version: htmlDecode(packageInfo.version),
})), author: htmlDecode(packageInfo.author)
})); .replace(/\[(.*), \]/, "$1")
.replace("[]", ""),
repository: htmlDecode(packageInfo.repository),
}),
),
}),
);
return rustLicenses; return rustLicenses;
} catch (_) { } catch (_) {
@@ -52,7 +52,6 @@ module.exports = {
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }], "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }],
"@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",
@@ -37,7 +37,7 @@ class BezierDemo extends HTMLElement implements Demo {
sliderUnits!: Record<string, string | string[]>; sliderUnits!: Record<string, string | string[]>;
async connectedCallback(): Promise<void> { async connectedCallback() {
this.title = this.getAttribute("title") || ""; this.title = this.getAttribute("title") || "";
this.points = JSON.parse(this.getAttribute("points") || "[]"); this.points = JSON.parse(this.getAttribute("points") || "[]");
this.key = this.getAttribute("key") as BezierFeatureKey; this.key = this.getAttribute("key") as BezierFeatureKey;
@@ -59,15 +59,15 @@ class BezierDemo extends HTMLElement implements Demo {
this.drawDemo(figure); this.drawDemo(figure);
} }
render(): void { render() {
renderDemo(this); renderDemo(this);
} }
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void { drawDemo(figure: HTMLElement, mouseLocation?: [number, number]) {
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation); figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation);
} }
onMouseDown(event: MouseEvent): void { onMouseDown(event: MouseEvent) {
const mx = event.offsetX; const mx = event.offsetX;
const my = event.offsetY; const my = event.offsetY;
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) { for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
@@ -79,11 +79,11 @@ class BezierDemo extends HTMLElement implements Demo {
} }
} }
onMouseUp(): void { onMouseUp() {
this.activeIndex = undefined; this.activeIndex = undefined;
} }
onMouseMove(event: MouseEvent): void { onMouseMove(event: MouseEvent) {
const mx = event.offsetX; const mx = event.offsetX;
const my = event.offsetY; const my = event.offsetY;
const figure = event.currentTarget as HTMLElement; const figure = event.currentTarget as HTMLElement;
@@ -41,7 +41,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
id!: string; id!: string;
connectedCallback(): void { connectedCallback() {
this.key = (this.getAttribute("name") || "") as BezierFeatureKey; this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
this.id = `bezier/${this.key}`; this.id = `bezier/${this.key}`;
this.name = bezierFeatures[this.key].name; this.name = bezierFeatures[this.key].name;
@@ -62,7 +62,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
this.render(); this.render();
} }
render(): void { render() {
renderDemoPane(this); renderDemoPane(this);
} }
@@ -34,7 +34,7 @@ class SubpathDemo extends HTMLElement {
sliderUnits!: Record<string, string | string[]>; sliderUnits!: Record<string, string | string[]>;
async connectedCallback(): Promise<void> { async connectedCallback() {
this.title = this.getAttribute("title") || ""; this.title = this.getAttribute("title") || "";
this.triples = JSON.parse(this.getAttribute("triples") || "[]"); this.triples = JSON.parse(this.getAttribute("triples") || "[]");
this.key = this.getAttribute("key") as SubpathFeatureKey; this.key = this.getAttribute("key") as SubpathFeatureKey;
@@ -53,15 +53,15 @@ class SubpathDemo extends HTMLElement {
this.drawDemo(figure); this.drawDemo(figure);
} }
render(): void { render() {
renderDemo(this); renderDemo(this);
} }
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void { drawDemo(figure: HTMLElement, mouseLocation?: [number, number]) {
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation); figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation);
} }
onMouseDown(event: MouseEvent): void { onMouseDown(event: MouseEvent) {
const mx = event.offsetX; const mx = event.offsetX;
const my = event.offsetY; const my = event.offsetY;
for (let controllerIndex = 0; controllerIndex < this.triples.length; controllerIndex += 1) { for (let controllerIndex = 0; controllerIndex < this.triples.length; controllerIndex += 1) {
@@ -75,11 +75,11 @@ class SubpathDemo extends HTMLElement {
} }
} }
onMouseUp(): void { onMouseUp() {
this.activeIndex = undefined; this.activeIndex = undefined;
} }
onMouseMove(event: MouseEvent): void { onMouseMove(event: MouseEvent) {
const mx = event.offsetX; const mx = event.offsetX;
const my = event.offsetY; const my = event.offsetY;
const figure = event.currentTarget as HTMLElement; const figure = event.currentTarget as HTMLElement;
@@ -17,7 +17,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
id!: string; id!: string;
connectedCallback(): void { connectedCallback() {
this.demos = [ this.demos = [
{ {
title: "Open Subpath", title: "Open Subpath",
@@ -52,7 +52,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
this.render(); this.render();
} }
render(): void { render() {
renderDemoPane(this); renderDemoPane(this);
} }
+4 -4
View File
@@ -20,7 +20,7 @@ window.customElements.define("bezier-demo-pane", BezierDemoPane);
window.customElements.define("subpath-demo", SubpathDemo); window.customElements.define("subpath-demo", SubpathDemo);
window.customElements.define("subpath-demo-pane", SubpathDemoPane); window.customElements.define("subpath-demo-pane", SubpathDemoPane);
function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement | null): void { function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement | null) {
const feature = bezierFeatures[featureName]; const feature = bezierFeatures[featureName];
const demo = document.createElement("bezier-demo-pane"); const demo = document.createElement("bezier-demo-pane");
@@ -30,7 +30,7 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement
container?.append(demo); container?.append(demo);
} }
function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null): void { function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null) {
const feature = subpathFeatures[featureName]; const feature = subpathFeatures[featureName];
const demo = document.createElement("subpath-demo-pane"); const demo = document.createElement("subpath-demo-pane");
@@ -46,7 +46,7 @@ function isUrlSolo(url: string): boolean {
return splitHash?.length === 3 && splitHash?.[2] === "solo"; return splitHash?.length === 3 && splitHash?.[2] === "solo";
} }
window.addEventListener("hashchange", (e: Event): void => { window.addEventListener("hashchange", (e: Event) => {
const hashChangeEvent = e as HashChangeEvent; const hashChangeEvent = e as HashChangeEvent;
const isOldHashSolo = isUrlSolo(hashChangeEvent.oldURL); const isOldHashSolo = isUrlSolo(hashChangeEvent.oldURL);
const isNewHashSolo = isUrlSolo(hashChangeEvent.newURL); const isNewHashSolo = isUrlSolo(hashChangeEvent.newURL);
@@ -57,7 +57,7 @@ window.addEventListener("hashchange", (e: Event): void => {
} }
}); });
function renderExamples(): void { function renderExamples() {
const hash = window.location.hash; const hash = window.location.hash;
const splitHash = hash.split("/"); const splitHash = hash.split("/");
@@ -1,6 +1,6 @@
import { Demo, DemoPane, InputOption } from "@/utils/types"; import { Demo, DemoPane, InputOption } from "@/utils/types";
export function renderDemo(demo: Demo): void { export function renderDemo(demo: Demo) {
const header = document.createElement("h4"); const header = document.createElement("h4");
header.className = "demo-header"; header.className = "demo-header";
header.innerText = demo.title; header.innerText = demo.title;
@@ -47,7 +47,7 @@ export function renderDemo(demo: Demo): void {
selectInput.disabled = true; selectInput.disabled = true;
} }
selectInput.addEventListener("change", (event: Event): void => { selectInput.addEventListener("change", (event: Event) => {
demo.sliderData[inputOption.variable] = Number((event.target as HTMLInputElement).value); demo.sliderData[inputOption.variable] = Number((event.target as HTMLInputElement).value);
demo.drawDemo(figure); demo.drawDemo(figure);
}); });
@@ -65,7 +65,7 @@ export function renderDemo(demo: Demo): void {
const ratio = (Number(inputOption.default) - Number(inputOption.min)) / range; const ratio = (Number(inputOption.default) - Number(inputOption.min)) / range;
sliderInput.style.setProperty("--range-ratio", String(ratio)); sliderInput.style.setProperty("--range-ratio", String(ratio));
sliderInput.addEventListener("input", (event: Event): void => { sliderInput.addEventListener("input", (event: Event) => {
const target = event.target as HTMLInputElement; const target = event.target as HTMLInputElement;
demo.sliderData[inputOption.variable] = Number(target.value); demo.sliderData[inputOption.variable] = Number(target.value);
const data = demo.sliderData[inputOption.variable]; const data = demo.sliderData[inputOption.variable];
@@ -86,7 +86,7 @@ export function renderDemo(demo: Demo): void {
demo.append(parentSliderContainer); demo.append(parentSliderContainer);
} }
export function renderDemoPane(demoPane: DemoPane): void { export function renderDemoPane(demoPane: DemoPane) {
const container = document.createElement("div"); const container = document.createElement("div");
container.className = "demo-pane-container"; container.className = "demo-pane-container";
@@ -79,10 +79,10 @@ export interface Demo extends HTMLElement {
sliderData: Record<string, number>; sliderData: Record<string, number>;
sliderUnits: Record<string, string | string[]>; sliderUnits: Record<string, string | string[]>;
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void; drawDemo(figure: HTMLElement, mouseLocation?: [number, number]);
onMouseDown(event: MouseEvent): void; onMouseDown(event: MouseEvent);
onMouseUp(): void; onMouseUp();
onMouseMove(event: MouseEvent): void; onMouseMove(event: MouseEvent);
getSliderUnit(sliderValue: number, variable: string): string; getSliderUnit(sliderValue: number, variable: string): string;
} }