mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 10:48:11 +08:00
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:
co-authored by
0hypercube
parent
9784d31edb
commit
a6ca43bb2d
@@ -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: |
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -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"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
Generated
+5458
-15
File diff suppressed because it is too large
Load Diff
+12
-1
@@ -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"
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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,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");
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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()}` };
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user