mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-08 00:05:00 +00: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:
parent
9784d31edb
commit
a6ca43bb2d
63 changed files with 5869 additions and 351 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -75,8 +75,7 @@ jobs:
|
|||
NODE_ENV: production
|
||||
run: |
|
||||
cd frontend
|
||||
# npm run lint
|
||||
echo "💥 Frontend linting is temporarily disabled until it can be set up again with Svelte 💥"
|
||||
npm run lint
|
||||
|
||||
- name: 🔬 Check Rust formatting
|
||||
run: |
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"useTabs": true,
|
||||
"tabWidth": 4,
|
||||
"printWidth": 200,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.yml", "*.yaml"],
|
||||
|
@ -10,6 +11,12 @@
|
|||
"useTabs": false,
|
||||
"tabWidth": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.svelte"],
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -6,7 +6,7 @@
|
|||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
// Web: save on format
|
||||
"[typescript][javascript]": {
|
||||
"[javascript][typescript][svelte]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
|
@ -39,7 +39,7 @@
|
|||
// ESLint config
|
||||
"eslint.format.enable": true,
|
||||
"eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"],
|
||||
"eslint.validate": ["javascript", "typescript"],
|
||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||
// VS Code config
|
||||
"html.format.wrapLineLength": 200,
|
||||
"files.eol": "\n",
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es2020: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
env: { browser: true, node: true },
|
||||
extends: [
|
||||
// General Prettier defaults
|
||||
"eslint:recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:svelte/recommended",
|
||||
"plugin:svelte/prettier",
|
||||
"prettier",
|
||||
],
|
||||
plugins: ["import", "@typescript-eslint", "prettier"],
|
||||
settings: {
|
||||
// https://github.com/import-js/eslint-plugin-import#resolvers
|
||||
"import/resolver": {
|
||||
// `node` must be listed first!
|
||||
node: {},
|
||||
},
|
||||
"import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] },
|
||||
"import/resolver": { typescript: true, node: true },
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
project: "./tsconfig.json",
|
||||
extraFileExtensions: [".svelte"],
|
||||
},
|
||||
ignorePatterns: [
|
||||
// Ignore generated directories
|
||||
|
@ -26,13 +27,24 @@ module.exports = {
|
|||
"dist/",
|
||||
"pkg/",
|
||||
"wasm/pkg/",
|
||||
|
||||
// Don't ignore JS and TS dotfiles in this folder
|
||||
"!.*.js",
|
||||
"!.*.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: {
|
||||
// Standard ESLint config
|
||||
// Standard ESLint config (for ordinary JS syntax linting)
|
||||
indent: "off",
|
||||
quotes: ["error", "double", { allowTemplateLiterals: true }],
|
||||
camelcase: ["error", { properties: "always" }],
|
||||
|
@ -48,12 +60,11 @@ module.exports = {
|
|||
"no-use-before-define": "off",
|
||||
"no-restricted-imports": ["error", { patterns: [".*", "!@graphite/*"] }],
|
||||
|
||||
// TypeScript plugin config
|
||||
// TypeScript plugin config (for TS-specific linting)
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@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-definitions": ["error", "type"],
|
||||
"@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/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/no-relative-packages": "error",
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
alphabetize: {
|
||||
order: "asc",
|
||||
caseInsensitive: true,
|
||||
},
|
||||
warnOnUnassignedImports: true,
|
||||
alphabetize: { order: "asc", caseInsensitive: true },
|
||||
pathGroups: [{ pattern: "**/*.svelte", group: "unknown", position: "after" }],
|
||||
"newlines-between": "always-and-inside-groups",
|
||||
pathGroups: [
|
||||
{
|
||||
pattern: "**/*.svelte",
|
||||
group: "unknown",
|
||||
position: "after",
|
||||
},
|
||||
],
|
||||
warnOnUnassignedImports: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js"],
|
||||
rules: {
|
||||
"@typescript-eslint/explicit-function-return-type": ["off"],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
5473
frontend/package-lock.json
generated
5473
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,6 +8,8 @@
|
|||
"scripts": {
|
||||
"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)",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"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-profiling": "wasm-pack build ./wasm --profiling --target=web",
|
||||
|
@ -29,14 +31,23 @@
|
|||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||
"@types/node": "^18.16.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"buffer": "^5.7.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",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.1.0",
|
||||
"process": "^0.11.10",
|
||||
"rollup-plugin-license": "^3.2.0",
|
||||
"sass": "^1.62.1",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"svelte": "^3.58.0",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.4.5"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<script lang="ts">
|
||||
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 { createDragManager } from "@graphite/io-managers/drag";
|
||||
import { createHyperlinkManager } from "@graphite/io-managers/hyperlinks";
|
||||
|
@ -16,6 +14,8 @@
|
|||
import { createFullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
import { createNodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
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";
|
||||
|
||||
|
@ -125,19 +125,29 @@
|
|||
--color-none-position: center center;
|
||||
// 24px tall, 48px wide
|
||||
--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
|
||||
--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%),
|
||||
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-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-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>');
|
||||
--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>');
|
||||
--icon-expand-collapse-arrow:
|
||||
// Arrow triangle (#eee fill)
|
||||
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,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { onDestroy, createEventDispatcher, getContext } from "svelte";
|
||||
|
||||
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 { Color } from "@graphite/wasm-communication/messages";
|
||||
|
||||
|
@ -14,7 +15,6 @@
|
|||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.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";
|
||||
|
||||
|
@ -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][];
|
||||
$: 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");
|
||||
return new Color({ h, s, v, a });
|
||||
}
|
||||
|
@ -432,8 +432,13 @@
|
|||
|
||||
.hue-picker {
|
||||
background-blend-mode: screen;
|
||||
background: linear-gradient(to top, #ff0000ff 16.666%, #ff000000 33.333%, #ff000000 66.666%, #ff0000ff 83.333%),
|
||||
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%);
|
||||
background:
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -567,7 +572,17 @@
|
|||
|
||||
.text-label {
|
||||
// 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">
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
import { githubUrl } from "@graphite/io-managers/panic";
|
||||
import { wipeDocuments } from "@graphite/io-managers/persistence";
|
||||
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
@ -10,7 +13,6 @@
|
|||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import type { DialogState } from "@graphite/state-providers/dialog";
|
||||
|
||||
const dialog = getContext<DialogState>("dialog");
|
||||
|
||||
|
|
|
@ -87,7 +87,9 @@
|
|||
left: -8px;
|
||||
padding: 8px;
|
||||
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 {
|
||||
|
@ -109,7 +111,9 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
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 {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
$: virtualScrollingTotalHeight = entries.length === 0 ? 0 : entries[0].length * virtualScrollingEntryHeight;
|
||||
$: virtualScrollingStartIndex = Math.floor(virtualScrollingEntriesStart / virtualScrollingEntryHeight) || 0;
|
||||
$: virtualScrollingEndIndex = entries.length === 0 ? 0 : Math.min(entries[0].length, virtualScrollingStartIndex + 1 + 400 / virtualScrollingEntryHeight);
|
||||
$: startIndex = virtualScrollingEntryHeight ? virtualScrollingStartIndex : 0;
|
||||
|
||||
function watchOpen(open: boolean) {
|
||||
highlighted = activeEntry;
|
||||
|
@ -55,7 +56,7 @@
|
|||
virtualScrollingEntriesStart = (e.target as HTMLElement)?.scrollTop || 0;
|
||||
}
|
||||
|
||||
function onEntryClick(menuListEntry: MenuListEntry): void {
|
||||
function onEntryClick(menuListEntry: MenuListEntry) {
|
||||
// Call the action if available
|
||||
if (menuListEntry.action) menuListEntry.action();
|
||||
|
||||
|
@ -71,7 +72,7 @@
|
|||
open = false;
|
||||
}
|
||||
|
||||
function onEntryPointerEnter(menuListEntry: MenuListEntry): void {
|
||||
function onEntryPointerEnter(menuListEntry: MenuListEntry) {
|
||||
if (!menuListEntry.children?.length) return;
|
||||
|
||||
if (menuListEntry.ref) {
|
||||
|
@ -80,7 +81,7 @@
|
|||
} else dispatch("open", true);
|
||||
}
|
||||
|
||||
function onEntryPointerLeave(menuListEntry: MenuListEntry): void {
|
||||
function onEntryPointerLeave(menuListEntry: MenuListEntry) {
|
||||
if (!menuListEntry.children?.length) return;
|
||||
|
||||
if (menuListEntry.ref) {
|
||||
|
@ -104,7 +105,7 @@
|
|||
const flatEntries = entries.flat().filter((entry) => !entry.disabled);
|
||||
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) {
|
||||
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.
|
||||
|
@ -181,7 +182,7 @@
|
|||
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);
|
||||
}
|
||||
</script>
|
||||
|
@ -214,7 +215,7 @@
|
|||
{#if sectionIndex > 0}
|
||||
<Separator type="List" direction="Vertical" />
|
||||
{/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
|
||||
class="row"
|
||||
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
|
||||
|
@ -359,4 +360,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
|
||||
</style>
|
||||
|
|
|
@ -137,9 +137,9 @@
|
|||
floatingMenuContentDiv.style.setProperty("min-height", "unset");
|
||||
}
|
||||
|
||||
// Gets the client bounds of the elements and apply relevant styles to them
|
||||
// TODO: Use DOM attribute bindings more whilst not causing recursive updates
|
||||
// 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)
|
||||
// Gets the client bounds of the elements and apply relevant styles to them.
|
||||
// TODO: Use DOM attribute bindings more whilst not causing recursive updates. Turning measuring on and off both causes the component to change,
|
||||
// TODO: which causes the `afterUpdate()` Svelte event to fire extraneous times (hurting performance and sometimes causing an infinite loop).
|
||||
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.
|
||||
export async function measureAndEmitNaturalWidth(): Promise<void> {
|
||||
export async function measureAndEmitNaturalWidth() {
|
||||
if (!measuringOngoingGuard) return;
|
||||
|
||||
// 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
|
||||
// Accompanying diagram: <https://files.keavon.com/-/SpringgreenKnownXantus/capture.png>
|
||||
//
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount, tick } from "svelte";
|
||||
|
||||
import type { DocumentState } from "@graphite/state-providers/document";
|
||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import {
|
||||
type MouseCursorIcon,
|
||||
type XY,
|
||||
|
@ -28,8 +30,6 @@
|
|||
import CanvasRuler from "@graphite/components/widgets/metrics/CanvasRuler.svelte";
|
||||
import PersistentScrollbar from "@graphite/components/widgets/metrics/PersistentScrollbar.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 rulerVertical: CanvasRuler | undefined;
|
||||
|
@ -136,6 +136,7 @@
|
|||
const canvasName = placeholder.getAttribute("data-canvas-placeholder");
|
||||
if (!canvasName) return;
|
||||
// Get the canvas element from the global storage
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const canvas = (window as any).imageCanvases[canvasName];
|
||||
placeholder.replaceWith(canvas);
|
||||
});
|
||||
|
@ -198,7 +199,7 @@
|
|||
mousePosition.x * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,
|
||||
mousePosition.y * dpiFactor - (ZOOM_WINDOW_DIMENSIONS - 1) / 2,
|
||||
ZOOM_WINDOW_DIMENSIONS,
|
||||
ZOOM_WINDOW_DIMENSIONS
|
||||
ZOOM_WINDOW_DIMENSIONS,
|
||||
);
|
||||
cursorEyedropperPreviewImageData = previewRegion;
|
||||
|
||||
|
@ -272,13 +273,13 @@
|
|||
textInput.style.fontSize = `${displayEditableTextbox.fontSize}px`;
|
||||
textInput.style.color = displayEditableTextbox.color.toHexOptionalAlpha() || "transparent";
|
||||
|
||||
textInput.oninput = (): void => {
|
||||
textInput.oninput = () => {
|
||||
if (!textInput) return;
|
||||
editor.instance.updateBounds(textInputCleanup(textInput.innerText));
|
||||
};
|
||||
textInputMatrix = displayEditableTextbox.transform;
|
||||
const new_font = new FontFace("text-font", `url(${displayEditableTextbox.url})`);
|
||||
window.document.fonts.add(new_font);
|
||||
const newFont = new FontFace("text-font", `url(${displayEditableTextbox.url})`);
|
||||
window.document.fonts.add(newFont);
|
||||
textInput.style.fontFamily = "text-font";
|
||||
|
||||
// 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 { platformIsMac } from "@graphite/utility-functions/platform";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import {
|
||||
type LayerPanelEntry,
|
||||
defaultWidgetLayout,
|
||||
|
@ -17,7 +18,6 @@
|
|||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
type LayerListingInfo = {
|
||||
folderIndex: number;
|
||||
|
@ -228,7 +228,7 @@
|
|||
if (!layer.layerMetadata.selected) {
|
||||
fakeHighlight = [layer.path];
|
||||
}
|
||||
const select = (): void => {
|
||||
const select = () => {
|
||||
if (!layer.layerMetadata.selected) selectLayer(false, false, listing);
|
||||
};
|
||||
|
||||
|
@ -275,7 +275,7 @@
|
|||
layers = [];
|
||||
|
||||
// Build the new layer tree
|
||||
const recurse = (folder: UpdateDocumentLayerTreeStructureJs): void => {
|
||||
const recurse = (folder: UpdateDocumentLayerTreeStructureJs) => {
|
||||
folder.children.forEach((item, index) => {
|
||||
// TODO: fix toString
|
||||
const layerId = BigInt(item.layerId.toString());
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<script lang="ts">
|
||||
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 { Editor } from "@graphite/wasm-communication/editor";
|
||||
import { UpdateNodeGraphSelection } from "@graphite/wasm-communication/messages";
|
||||
import type { FrontendNodeLink, FrontendNodeType, FrontendNode } from "@graphite/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
|
||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.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 GRID_COLLAPSE_SPACING = 10;
|
||||
|
@ -142,7 +143,7 @@
|
|||
return { nodeOutput, nodeInput };
|
||||
}
|
||||
|
||||
async function refreshLinks(): Promise<void> {
|
||||
async function refreshLinks() {
|
||||
await tick();
|
||||
|
||||
if (!nodesContainer) return;
|
||||
|
@ -263,7 +264,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function keydown(e: KeyboardEvent): void {
|
||||
function keydown(e: KeyboardEvent) {
|
||||
if (e.key.toLowerCase() === "escape") {
|
||||
nodeListLocation = undefined;
|
||||
document.removeEventListener("keydown", keydown);
|
||||
|
@ -380,7 +381,7 @@
|
|||
panning = true;
|
||||
}
|
||||
|
||||
function doubleClick(e: MouseEvent) {
|
||||
function doubleClick(_e: MouseEvent) {
|
||||
// const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
||||
// const nodeId = node?.getAttribute("data-node") || undefined;
|
||||
// if (nodeId) {
|
||||
|
@ -416,9 +417,12 @@
|
|||
refresh();
|
||||
// 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
|
||||
setTimeout(() => {
|
||||
stop = true;
|
||||
}, DRAG_SMOOTHING_TIME * 1000 + 10);
|
||||
setTimeout(
|
||||
() => {
|
||||
stop = true;
|
||||
},
|
||||
DRAG_SMOOTHING_TIME * 1000 + 10,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -473,7 +477,7 @@
|
|||
selectedNodeBounds.top - containerBoundsBounds.y,
|
||||
selectedNodeBounds.left - containerBoundsBounds.x,
|
||||
selectedNodeBounds.bottom - containerBoundsBounds.y,
|
||||
selectedNodeBounds.right - containerBoundsBounds.x
|
||||
selectedNodeBounds.right - containerBoundsBounds.x,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -516,7 +520,7 @@
|
|||
|
||||
linkInProgressToConnector = new DOMRect(
|
||||
(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;
|
||||
|
@ -540,7 +544,7 @@
|
|||
linkInProgressToConnector = undefined;
|
||||
}
|
||||
|
||||
function createNode(nodeType: string): void {
|
||||
function createNode(nodeType: string) {
|
||||
if (!nodeListLocation) return;
|
||||
|
||||
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}>
|
||||
<!-- Layers -->
|
||||
{#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 stackDatainput = node.exposedInputs[0]}
|
||||
<div
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import { debouncer } from "@graphite/utility-functions/debounce";
|
||||
import type { Widget } from "@graphite/wasm-communication/messages";
|
||||
import { narrowWidgetProps } from "@graphite/wasm-communication/messages";
|
||||
import { isWidgetColumn, isWidgetRow, type WidgetColumn, type WidgetRow } from "@graphite/wasm-communication/messages";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import type { Widget, WidgetColumn, 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 WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
|
||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||
import ParameterExposeButton from "@graphite/components/widgets/buttons/ParameterExposeButton.svelte";
|
||||
|
@ -27,14 +28,14 @@
|
|||
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import { getContext } from "svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const SUFFIX_WIDGETS = ["PopoverButton"];
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
export let widgetData: WidgetColumn | WidgetRow;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any;
|
||||
|
||||
$: 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> {
|
||||
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;
|
||||
}
|
||||
</script>
|
||||
|
@ -241,4 +243,5 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import type { IconName } from "@graphite/utility-functions/icons";
|
||||
|
||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
<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 TextLabel from "@graphite/components/widgets/labels/TextLabel.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;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export let layoutTarget: any; // TODO: Give type
|
||||
|
||||
let expanded = true;
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</script>
|
||||
|
||||
<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}>
|
||||
<LayoutRow class="checkbox-box">
|
||||
<IconLabel icon={displayIcon} />
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import { clamp } from "@graphite/utility-functions/math";
|
||||
import type { Curve, CurveManipulatorGroup } from "@graphite/wasm-communication/messages";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import { clamp } from "@graphite/utility-functions/math";
|
||||
|
||||
// emits: ["update:value"],
|
||||
const dispatch = createEventDispatcher<{
|
||||
|
@ -145,7 +145,7 @@
|
|||
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 otherHandle = handles[1 - handleIndex];
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, getContext, onMount, tick } from "svelte";
|
||||
|
||||
import type { FontsState } from "@graphite/state-providers/fonts";
|
||||
import type { MenuListEntry } from "@graphite/wasm-communication/messages";
|
||||
|
||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import type { FontsState } from "@graphite/state-providers/fonts";
|
||||
|
||||
const fonts = getContext<FontsState>("fonts");
|
||||
|
||||
|
@ -34,14 +34,14 @@
|
|||
|
||||
$: 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
|
||||
const newEntries = await getEntries();
|
||||
entries = newEntries;
|
||||
activeEntry = getActiveEntry(newEntries);
|
||||
}
|
||||
|
||||
async function setOpen(): Promise<void> {
|
||||
async function setOpen() {
|
||||
open = true;
|
||||
|
||||
// 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) {
|
||||
open = !open;
|
||||
|
||||
|
@ -61,7 +61,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function selectFont(newName: string): Promise<void> {
|
||||
async function selectFont(newName: string) {
|
||||
let family;
|
||||
let style;
|
||||
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
|
||||
$: droppable = hoveringDrop && Boolean(currentDraggingElement());
|
||||
|
||||
function dragOver(e: DragEvent): void {
|
||||
function dragOver(e: DragEvent) {
|
||||
hoveringDrop = true;
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function drop(e: DragEvent): void {
|
||||
function drop(e: DragEvent) {
|
||||
hoveringDrop = false;
|
||||
|
||||
const element = currentDraggingElement();
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import { getContext, onMount } from "svelte";
|
||||
|
||||
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 MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.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
|
||||
const accelKey = platformIsMac() ? "Command" : "Control";
|
||||
|
@ -57,7 +57,7 @@
|
|||
...entry,
|
||||
|
||||
// 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,
|
||||
|
||||
// New fields in `MenuListEntry`
|
||||
|
|
|
@ -174,8 +174,9 @@
|
|||
function onCancelTextChange() {
|
||||
updateValue(undefined, min, max, displayDecimalPlaces, unit);
|
||||
|
||||
rangeSliderValue = value;
|
||||
rangeSliderValueAsRendered = value;
|
||||
const valueOrZero = value !== undefined ? value : 0;
|
||||
rangeSliderValue = valueOrZero;
|
||||
rangeSliderValueAsRendered = valueOrZero;
|
||||
|
||||
editing = false;
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
import type { Color } from "@graphite/wasm-communication/messages";
|
||||
|
||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
import type { IconName } from "@graphite/utility-functions/icons";
|
||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||
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 Separator from "@graphite/components/widgets/labels/Separator.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 };
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import { getContext, onMount } from "svelte";
|
||||
|
||||
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 LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import MenuBarInput from "@graphite/components/widgets/inputs/MenuBarInput.svelte";
|
||||
import WindowButtonsMac from "@graphite/components/window/title-bar/WindowButtonsMac.svelte";
|
||||
import WindowButtonsWeb from "@graphite/components/window/title-bar/WindowButtonsWeb.svelte";
|
||||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.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 maximized: boolean;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script lang="ts" context="module">
|
||||
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 PopoverButton from "@graphite/components/widgets/buttons/PopoverButton.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";
|
||||
|
||||
const PANEL_COMPONENTS = {
|
||||
|
@ -19,6 +19,7 @@
|
|||
|
||||
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 LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
|
@ -26,7 +27,6 @@
|
|||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import UserInputLabel from "@graphite/components/widgets/labels/UserInputLabel.svelte";
|
||||
import type { Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
<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 LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.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 PANEL_SIZES = {
|
||||
|
@ -66,7 +69,7 @@
|
|||
|
||||
const mouseStart = isHorizontal ? e.clientX : e.clientY;
|
||||
|
||||
const updatePosition = (e: PointerEvent): void => {
|
||||
const updatePosition = (e: PointerEvent) => {
|
||||
const mouseCurrent = isHorizontal ? e.clientX : e.clientY;
|
||||
let mouseDelta = mouseStart - mouseCurrent;
|
||||
|
||||
|
@ -79,7 +82,7 @@
|
|||
window.dispatchEvent(new CustomEvent("resize"));
|
||||
};
|
||||
|
||||
const cleanup = (e: PointerEvent): void => {
|
||||
const cleanup = (e: PointerEvent) => {
|
||||
gutter.releasePointerCapture(e.pointerId);
|
||||
|
||||
document.removeEventListener("pointermove", updatePosition);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { imageToPNG } from "@graphite/utility-functions/rasterization";
|
||||
import { type Editor } from "@graphite/wasm-communication/editor";
|
||||
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
|
||||
editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
|
||||
// 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 blob = await response.blob();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
let draggingElement: HTMLElement | undefined;
|
||||
|
||||
export function createDragManager(): () => void {
|
||||
const clearDraggingElement = (): void => {
|
||||
const clearDraggingElement = () => {
|
||||
draggingElement = undefined;
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ export function createDragManager(): () => void {
|
|||
};
|
||||
}
|
||||
|
||||
export function beginDraggingElement(element: HTMLElement): void {
|
||||
export function beginDraggingElement(element: HTMLElement) {
|
||||
draggingElement = element;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { type Editor } from "@graphite/wasm-communication/editor";
|
||||
import { TriggerVisitLink } from "@graphite/wasm-communication/messages";
|
||||
|
||||
export function createHyperlinkManager(editor: Editor): void {
|
||||
export function createHyperlinkManager(editor: Editor) {
|
||||
// Subscribe to process backend event
|
||||
editor.subscriptions.subscribeJsMessage(TriggerVisitLink, async (triggerOpenLink) => {
|
||||
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: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) },
|
||||
{ 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.body, eventName: "paste", action: (e: ClipboardEvent) => onPaste(e) },
|
||||
];
|
||||
|
||||
// Event bindings
|
||||
|
||||
function bindListeners(): void {
|
||||
function bindListeners() {
|
||||
// Add event bindings for the lifetime of the application
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
async function onKeyDown(e: KeyboardEvent): Promise<void> {
|
||||
async function onKeyDown(e: KeyboardEvent) {
|
||||
const key = await getLocalizedScanCode(e);
|
||||
|
||||
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);
|
||||
|
||||
if (await shouldRedirectKeyboardEventToBackend(e)) {
|
||||
|
@ -127,7 +127,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
// 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
|
||||
function onPointerMove(e: PointerEvent): void {
|
||||
function onPointerMove(e: PointerEvent) {
|
||||
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
|
||||
|
@ -149,12 +149,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
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)
|
||||
if (e.button === 1) e.preventDefault();
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent): void {
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport]");
|
||||
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 (textToolInteractiveInputElement) return;
|
||||
|
@ -186,12 +186,12 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
}
|
||||
|
||||
function onPotentialDoubleClick(e: MouseEvent): void {
|
||||
function onPotentialDoubleClick(e: MouseEvent) {
|
||||
if (textToolInteractiveInputElement) return;
|
||||
|
||||
|
||||
// Allow only double-clicks
|
||||
if (e.detail !== 2) return;
|
||||
|
||||
|
||||
// `e.buttons` is always 0 in the `mouseup` event, so we have to convert from `e.button` instead
|
||||
let buttons = 1;
|
||||
if (e.button === 0) buttons = 1; // LMB
|
||||
|
@ -204,7 +204,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
|
||||
// Mouse events
|
||||
|
||||
function onWheelScroll(e: WheelEvent): void {
|
||||
function onWheelScroll(e: WheelEvent) {
|
||||
const { target } = e;
|
||||
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) {
|
||||
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.
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Window events
|
||||
|
||||
function onWindowResize(container: HTMLElement): void {
|
||||
function onWindowResize(container: HTMLElement) {
|
||||
const viewports = Array.from(container.querySelectorAll("[data-viewport]"));
|
||||
const boundsOfViewports = viewports.map((canvas) => {
|
||||
const bounds = canvas.getBoundingClientRect();
|
||||
|
@ -250,7 +250,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
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];
|
||||
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;
|
||||
if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
|
||||
e.preventDefault();
|
||||
|
@ -285,7 +285,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
|
||||
const file = item.getAsFile();
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (item.types.includes("text/plain")) {
|
||||
const blob = await item.getType("text/plain");
|
||||
const reader = new FileReader();
|
||||
reader.onload = (): void => {
|
||||
reader.onload = () => {
|
||||
const text = reader.result as string;
|
||||
|
||||
if (text.startsWith("graphite/layer: ")) {
|
||||
|
@ -330,7 +330,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (imageType) {
|
||||
const blob = await item.getType(imageType);
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (): Promise<void> => {
|
||||
reader.onload = async () => {
|
||||
if (reader.result instanceof ArrayBuffer) {
|
||||
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
|
||||
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { type Editor } from "@graphite/wasm-communication/editor";
|
||||
import { TriggerAboutGraphiteLocalizedCommitDate } from "@graphite/wasm-communication/messages";
|
||||
|
||||
export function createLocalizationManager(editor: Editor): void {
|
||||
function localizeTimestamp(utc: string): { timestamp: string, year: string } {
|
||||
export function createLocalizationManager(editor: Editor) {
|
||||
function localizeTimestamp(utc: string): { timestamp: string; year: string } {
|
||||
// Timestamp
|
||||
const date = new Date(utc);
|
||||
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 { 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
|
||||
editor.subscriptions.subscribeJsMessage(DisplayDialogPanic, (displayDialogPanic) => {
|
||||
// `Error.stackTraceLimit` is only available in V8/Chromium
|
||||
|
|
|
@ -7,16 +7,16 @@ import { TriggerIndexedDbWriteDocument, TriggerIndexedDbRemoveDocument, TriggerS
|
|||
|
||||
const graphiteStore = createStore("graphite", "store");
|
||||
|
||||
export function createPersistenceManager(editor: Editor, portfolio: PortfolioState): void {
|
||||
export function createPersistenceManager(editor: Editor, portfolio: PortfolioState) {
|
||||
// DOCUMENTS
|
||||
|
||||
async function storeDocumentOrder(): Promise<void> {
|
||||
async function storeDocumentOrder() {
|
||||
const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id));
|
||||
|
||||
await set("documents_tab_order", documentOrder, graphiteStore);
|
||||
}
|
||||
|
||||
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument): Promise<void> {
|
||||
async function storeDocument(autoSaveDocument: TriggerIndexedDbWriteDocument) {
|
||||
await update<Record<string, TriggerIndexedDbWriteDocument>>(
|
||||
"documents",
|
||||
(old) => {
|
||||
|
@ -24,13 +24,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
|||
documents[autoSaveDocument.details.id] = autoSaveDocument;
|
||||
return documents;
|
||||
},
|
||||
graphiteStore
|
||||
graphiteStore,
|
||||
);
|
||||
|
||||
await storeDocumentOrder();
|
||||
}
|
||||
|
||||
async function removeDocument(id: string): Promise<void> {
|
||||
async function removeDocument(id: string) {
|
||||
await update<Record<string, TriggerIndexedDbWriteDocument>>(
|
||||
"documents",
|
||||
(old) => {
|
||||
|
@ -38,13 +38,13 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
|||
delete documents[id];
|
||||
return documents;
|
||||
},
|
||||
graphiteStore
|
||||
graphiteStore,
|
||||
);
|
||||
|
||||
await storeDocumentOrder();
|
||||
}
|
||||
|
||||
async function loadDocuments(): Promise<void> {
|
||||
async function loadDocuments() {
|
||||
const previouslySavedDocuments = await get<Record<string, TriggerIndexedDbWriteDocument>>("documents", graphiteStore);
|
||||
const documentOrder = await get<string[]>("documents_tab_order", graphiteStore);
|
||||
if (!previouslySavedDocuments || !documentOrder) return;
|
||||
|
@ -64,11 +64,11 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
|||
|
||||
// PREFERENCES
|
||||
|
||||
async function savePreferences(preferences: TriggerSavePreferences["preferences"]): Promise<void> {
|
||||
async function savePreferences(preferences: TriggerSavePreferences["preferences"]) {
|
||||
await set("preferences", preferences, graphiteStore);
|
||||
}
|
||||
|
||||
async function loadPreferences(): Promise<void> {
|
||||
async function loadPreferences() {
|
||||
const preferences = await get<Record<string, unknown>>("preferences", graphiteStore);
|
||||
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", 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 Editor } from "@graphite/wasm-communication/editor";
|
||||
|
@ -17,18 +17,18 @@ export function createDialogState(editor: Editor) {
|
|||
panicDetails: "",
|
||||
});
|
||||
|
||||
function dismissDialog(): void {
|
||||
function dismissDialog() {
|
||||
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
|
||||
if (state.panicDetails === "") state.visible = false;
|
||||
|
||||
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
// 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.
|
||||
function createCrashDialog(panicDetails: string): void {
|
||||
function createCrashDialog(panicDetails: string) {
|
||||
update((state) => {
|
||||
state.visible = true;
|
||||
|
||||
|
@ -51,7 +51,7 @@ export function createDialogState(editor: Editor) {
|
|||
|
||||
state.title = displayDialog.title;
|
||||
state.icon = displayDialog.icon;
|
||||
|
||||
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {tick} from "svelte";
|
||||
import {writable} from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/wasm-communication/editor";
|
||||
import {
|
||||
|
@ -35,7 +35,7 @@ export function createDocumentState(editor: Editor) {
|
|||
// Update layouts
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentModeLayout, async (updateDocumentModeLayout) => {
|
||||
await tick();
|
||||
|
||||
|
||||
update((state) => {
|
||||
// `state.documentModeLayout` is mutated in the function
|
||||
patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout);
|
||||
|
@ -44,7 +44,7 @@ export function createDocumentState(editor: Editor) {
|
|||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateToolOptionsLayout, async (updateToolOptionsLayout) => {
|
||||
await tick();
|
||||
|
||||
|
||||
update((state) => {
|
||||
// `state.documentModeLayout` is mutated in the function
|
||||
patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout);
|
||||
|
@ -53,7 +53,7 @@ export function createDocumentState(editor: Editor) {
|
|||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentBarLayout, async (updateDocumentBarLayout) => {
|
||||
await tick();
|
||||
|
||||
|
||||
update((state) => {
|
||||
// `state.documentModeLayout` is mutated in the function
|
||||
patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout);
|
||||
|
@ -62,7 +62,7 @@ export function createDocumentState(editor: Editor) {
|
|||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateToolShelfLayout, async (updateToolShelfLayout) => {
|
||||
await tick();
|
||||
|
||||
|
||||
update((state) => {
|
||||
// `state.documentModeLayout` is mutated in the function
|
||||
patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout);
|
||||
|
@ -79,7 +79,7 @@ export function createDocumentState(editor: Editor) {
|
|||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => {
|
||||
await tick();
|
||||
|
||||
|
||||
update((state) => {
|
||||
// `state.documentModeLayout` is mutated in the function
|
||||
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";
|
||||
|
||||
|
@ -9,7 +9,7 @@ export function createFullscreenState(_: Editor) {
|
|||
keyboardLocked: false,
|
||||
});
|
||||
|
||||
function fullscreenModeChanged(): void {
|
||||
function fullscreenModeChanged() {
|
||||
update((state) => {
|
||||
state.windowFullscreen = Boolean(document.fullscreenElement);
|
||||
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();
|
||||
|
||||
if (keyboardLockApiSupported) {
|
||||
|
@ -31,11 +31,11 @@ export function createFullscreenState(_: Editor) {
|
|||
}
|
||||
}
|
||||
|
||||
async function exitFullscreen(): Promise<void> {
|
||||
async function exitFullscreen() {
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
|
||||
async function toggleFullscreen(): Promise<void> {
|
||||
async function toggleFullscreen() {
|
||||
return new Promise((resolve, reject) => {
|
||||
update((state) => {
|
||||
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 FrontendNode,
|
||||
type FrontendNodeLink,
|
||||
type FrontendNodeType,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeTypes,
|
||||
UpdateNodeThumbnail,
|
||||
UpdateZoomWithScroll,
|
||||
} from "@graphite/wasm-communication/messages";
|
||||
import { 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
|
||||
export function createNodeGraphState(editor: Editor) {
|
||||
|
@ -26,11 +18,10 @@ export function createNodeGraphState(editor: Editor) {
|
|||
update((state) => {
|
||||
state.nodes = updateNodeGraph.nodes;
|
||||
state.links = updateNodeGraph.links;
|
||||
let newThumbnails = new Map<bigint, string>();
|
||||
const newThumbnails = new Map<bigint, string>();
|
||||
state.nodes.forEach((node) => {
|
||||
const thumbnail = state.thumbnails.get(node.id);
|
||||
if (thumbnail)
|
||||
newThumbnails.set(node.id, thumbnail);
|
||||
if (thumbnail) newThumbnails.set(node.id, thumbnail);
|
||||
});
|
||||
state.thumbnails = newThumbnails;
|
||||
return state;
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
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 { type Editor } from "@graphite/wasm-communication/editor";
|
||||
import {
|
||||
|
@ -20,7 +21,6 @@ import {
|
|||
UpdateImageData,
|
||||
UpdateOpenDocumentsList,
|
||||
} from "@graphite/wasm-communication/messages";
|
||||
import { copyToClipboardFileURL } from "@graphite/io-managers/clipboard";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function createPortfolioState(editor: Editor) {
|
||||
|
@ -35,7 +35,7 @@ export function createPortfolioState(editor: Editor) {
|
|||
update((state) => {
|
||||
state.documents = updateOpenDocumentList.openDocuments;
|
||||
return state;
|
||||
})
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => {
|
||||
update((state) => {
|
||||
|
@ -43,7 +43,7 @@ export function createPortfolioState(editor: Editor) {
|
|||
const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId);
|
||||
state.activeDocumentIndex = activeId;
|
||||
return state;
|
||||
})
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerFetchAndOpenDocument, async (triggerFetchAndOpenDocument) => {
|
||||
try {
|
||||
|
@ -98,7 +98,6 @@ export function createPortfolioState(editor: Editor) {
|
|||
} catch {
|
||||
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
||||
}
|
||||
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateImageData, (updateImageData) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
// getImageData may throw an exception if the resolution is too high
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
// getImageData may throw an exception if the resolution is too high
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to rasterize the SVG canvas in JS to be sent back to Rust:", e);
|
||||
}
|
||||
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
|
||||
URL.revokeObjectURL(triggerRevokeBlobUrl.url);
|
||||
|
|
|
@ -9,13 +9,13 @@ export function debouncer<T>(callFn: (value: T) => unknown, { debounceTime = 60
|
|||
let currentValue: T | undefined;
|
||||
let recentlyUpdated: boolean = false;
|
||||
|
||||
const emitValue = (): void => {
|
||||
const emitValue = () => {
|
||||
recentlyUpdated = false;
|
||||
if (currentValue === undefined) return;
|
||||
updateValue(currentValue);
|
||||
};
|
||||
|
||||
const updateValue = (newValue: T): void => {
|
||||
const updateValue = (newValue: T) => {
|
||||
if (recentlyUpdated) {
|
||||
currentValue = newValue;
|
||||
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");
|
||||
|
||||
element.href = url;
|
||||
|
@ -7,7 +7,7 @@ export function downloadFileURL(filename: string, url: string): void {
|
|||
element.click();
|
||||
}
|
||||
|
||||
export function downloadFileBlob(filename: string, blob: Blob): void {
|
||||
export function downloadFileBlob(filename: string, blob: Blob) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
downloadFileURL(filename, url);
|
||||
|
@ -15,7 +15,7 @@ export function downloadFileBlob(filename: string, blob: Blob): void {
|
|||
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 blob = new Blob([text], { type });
|
||||
|
@ -41,7 +41,7 @@ export async function upload<T extends "text" | "data">(acceptedExtensions: stri
|
|||
resolve({ filename, type, content });
|
||||
}
|
||||
},
|
||||
{ capture: false, once: true }
|
||||
{ capture: false, once: true },
|
||||
);
|
||||
|
||||
element.click();
|
||||
|
@ -55,7 +55,7 @@ type UploadResultType<T> = T extends "text" ? string : T extends "data" ? Uint8A
|
|||
export function blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 CustomColor from "@graphite-frontend/assets/icon-16px-solid/custom-color.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 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 FlipHorizontal from "@graphite-frontend/assets/icon-16px-solid/flip-horizontal.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 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 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 Image from "@graphite-frontend/assets/icon-16px-solid/image.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 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 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 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 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",
|
||||
body: string,
|
||||
uploadProgress: (progress: number) => void,
|
||||
downloadOccurring: () => void
|
||||
downloadOccurring: () => void,
|
||||
): [Promise<RequestResult>, XMLHttpRequest | undefined] {
|
||||
let xhrValue: XMLHttpRequest | undefined;
|
||||
const promise = new Promise<RequestResult>((resolve, reject) => {
|
||||
|
|
|
@ -61,10 +61,11 @@ export function isEventSupported(eventName: string) {
|
|||
if (["submit", "reset"].includes(eventName)) tag = "form";
|
||||
if (["error", "load", "abort"].includes(eventName)) tag = "img";
|
||||
const element = document.createElement(tag);
|
||||
|
||||
|
||||
if (onEventName in element) return true;
|
||||
|
||||
// Check if "return;" gets converted into a function, meaning the event is supported
|
||||
element.setAttribute(eventName, "return;");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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();
|
||||
image.src = url;
|
||||
await new Promise<void>((resolve) => {
|
||||
image.onload = (): void => resolve();
|
||||
image.onload = () => resolve();
|
||||
});
|
||||
|
||||
// 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
|
||||
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()");
|
||||
|
||||
|
||||
const canvas = await rasterizeSVGCanvas(svg, width, height, backgroundColor);
|
||||
|
||||
// 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");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if (!context) throw new Error("Could not create canvas context");
|
||||
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 init, { setRandomSeed, wasmMemory, JsEditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||
import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
||||
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
||||
import { type JsMessageType } from "@graphite/wasm-communication/messages";
|
||||
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 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()`
|
||||
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 blobURL = URL.createObjectURL(blob);
|
||||
|
@ -21,10 +20,11 @@ export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: st
|
|||
image.src = blobURL;
|
||||
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 blob = await data.blob();
|
||||
|
||||
|
@ -35,16 +35,18 @@ export async function fetchImage(path: BigUint64Array, nodeId: bigint, mime: str
|
|||
image.src = blobURL;
|
||||
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");
|
||||
export async function dispatchTauri(message: unknown): Promise<void> {
|
||||
export async function dispatchTauri(message: unknown) {
|
||||
if (!tauri) return;
|
||||
|
||||
try {
|
||||
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 {
|
||||
// eslint-disable-next-line no-console
|
||||
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()`
|
||||
export async function initWasm(): Promise<void> {
|
||||
export async function initWasm() {
|
||||
// Skip if the WASM module is already initialized
|
||||
if (wasmImport !== undefined) return;
|
||||
|
||||
|
@ -60,8 +62,8 @@ export async function initWasm(): Promise<void> {
|
|||
// eslint-disable-next-line import/no-cycle
|
||||
await init();
|
||||
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
|
||||
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
|
@ -79,12 +81,13 @@ export function createEditor() {
|
|||
const raw: WasmRawInstance = wasmImport;
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
});
|
||||
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
|
||||
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
|
||||
|
@ -97,14 +100,16 @@ export function createEditor() {
|
|||
try {
|
||||
const url = new URL(`https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/demo-artwork/${demoArtwork}.graphite`);
|
||||
const data = await fetch(url);
|
||||
|
||||
|
||||
const filename = url.pathname.split("/").pop() || "Untitled";
|
||||
const content = await data.text();
|
||||
instance.openDocumentFile(filename, content);
|
||||
|
||||
// Remove the hash fragment from the URL
|
||||
history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
|
||||
} catch {}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
|
@ -115,5 +120,6 @@ export function createEditor() {
|
|||
}
|
||||
|
||||
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 */
|
||||
|
||||
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 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 };
|
||||
|
||||
|
@ -506,19 +507,19 @@ export class UpdateMouseCursor extends JsMessage {
|
|||
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 {
|
||||
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 {
|
||||
readonly blobUrl!: string;
|
||||
|
@ -557,7 +558,7 @@ export class TriggerRasterizeRegionBelowLayer extends JsMessage {
|
|||
readonly size!: [number, number];
|
||||
}
|
||||
|
||||
export class TriggerRefreshBoundsOfViewports extends JsMessage { }
|
||||
export class TriggerRefreshBoundsOfViewports extends JsMessage {}
|
||||
|
||||
export class TriggerRevokeBlobUrl extends JsMessage {
|
||||
readonly url!: string;
|
||||
|
@ -567,10 +568,13 @@ export class TriggerSavePreferences extends JsMessage {
|
|||
readonly preferences!: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class DocumentChanged extends JsMessage { }
|
||||
export class DocumentChanged extends JsMessage {}
|
||||
|
||||
export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
|
||||
constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) {
|
||||
constructor(
|
||||
readonly layerId: bigint,
|
||||
readonly children: UpdateDocumentLayerTreeStructureJs[],
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
@ -660,7 +664,7 @@ export class UpdateImageData extends JsMessage {
|
|||
readonly imageData!: ImaginateImageData[];
|
||||
}
|
||||
|
||||
export class DisplayRemoveEditableTextbox extends JsMessage { }
|
||||
export class DisplayRemoveEditableTextbox extends JsMessage {}
|
||||
|
||||
export class UpdateDocumentLayerDetails extends JsMessage {
|
||||
@Type(() => LayerPanelEntry)
|
||||
|
@ -706,7 +710,7 @@ export class ImaginateImageData {
|
|||
readonly transform!: Float64Array;
|
||||
}
|
||||
|
||||
export class DisplayDialogDismiss extends JsMessage { }
|
||||
export class DisplayDialogDismiss extends JsMessage {}
|
||||
|
||||
export class Font {
|
||||
fontFamily!: string;
|
||||
|
@ -729,7 +733,7 @@ export class TriggerVisitLink extends JsMessage {
|
|||
url!: string;
|
||||
}
|
||||
|
||||
export class TriggerTextCommit extends JsMessage { }
|
||||
export class TriggerTextCommit extends JsMessage {}
|
||||
|
||||
export class TriggerTextCopy extends JsMessage {
|
||||
readonly copyText!: string;
|
||||
|
@ -739,7 +743,7 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
|
|||
readonly commitDate!: string;
|
||||
}
|
||||
|
||||
export class TriggerViewportResize extends JsMessage { }
|
||||
export class TriggerViewportResize extends JsMessage {}
|
||||
|
||||
// WIDGET PROPS
|
||||
|
||||
|
@ -760,7 +764,7 @@ export class CheckboxInput extends WidgetProps {
|
|||
|
||||
export class ColorInput extends WidgetProps {
|
||||
@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;
|
||||
|
||||
|
@ -784,7 +788,7 @@ type MenuEntryCommon = {
|
|||
export type MenuBarEntry = MenuEntryCommon & {
|
||||
action: Widget;
|
||||
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
|
||||
|
@ -1042,7 +1046,7 @@ export class TextButton extends WidgetProps {
|
|||
emphasized!: boolean;
|
||||
|
||||
noBackground!: boolean;
|
||||
|
||||
|
||||
minWidth!: number;
|
||||
|
||||
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
|
||||
export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: WidgetDiffUpdate): void {
|
||||
export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: WidgetDiffUpdate) {
|
||||
layout.layoutTarget = updates.layoutTarget;
|
||||
|
||||
updates.diff.forEach((update) => {
|
||||
|
@ -1315,19 +1319,19 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
|
|||
}
|
||||
|
||||
// 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
|
||||
export class UpdateMenuBarLayout extends JsMessage {
|
||||
|
@ -1339,17 +1343,17 @@ export class UpdateMenuBarLayout extends JsMessage {
|
|||
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
|
||||
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
|
||||
|
@ -1366,7 +1370,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
|
|||
action: hoistWidgetHolders([entry.action])[0],
|
||||
children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined,
|
||||
disabled: entry.disabled ?? false,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,18 +13,18 @@ type JsMessageCallbackMap = Record<string, JsMessageCallback<any> | undefined>;
|
|||
export function createSubscriptionRouter() {
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
const messageMaker = messageMakers[messageType];
|
||||
if (!messageMaker) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`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;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
},
|
||||
"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"],
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { defineConfig } from "vite";
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { spawnSync } from "child_process";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { sveltePreprocess } from "svelte-preprocess/dist/autoProcess";
|
||||
|
||||
import path from "path";
|
||||
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import rollupPluginLicense, { type Dependency } from "rollup-plugin-license";
|
||||
import { sveltePreprocess } from "svelte-preprocess/dist/autoProcess";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
const projectRootDir = path.resolve(__dirname);
|
||||
|
||||
|
@ -30,11 +34,11 @@ export default defineConfig({
|
|||
svelte({
|
||||
preprocess: [sveltePreprocess()],
|
||||
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;
|
||||
|
||||
defaultHandler?.(warning);
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
|
@ -44,7 +48,7 @@ export default defineConfig({
|
|||
{ find: "@graphite/../assets", replacement: path.resolve(projectRootDir, "assets") },
|
||||
{ find: "@graphite/../public", replacement: path.resolve(projectRootDir, "public") },
|
||||
{ find: "@graphite", replacement: path.resolve(projectRootDir, "src") },
|
||||
]
|
||||
],
|
||||
},
|
||||
server: {
|
||||
port: 8080,
|
||||
|
@ -70,18 +74,18 @@ export default defineConfig({
|
|||
},
|
||||
});
|
||||
|
||||
interface LicenseInfo {
|
||||
type LicenseInfo = {
|
||||
licenseName: string;
|
||||
licenseText: string;
|
||||
packages: PackageInfo[];
|
||||
}
|
||||
};
|
||||
|
||||
interface PackageInfo {
|
||||
type PackageInfo = {
|
||||
name: string;
|
||||
version: string;
|
||||
author: string;
|
||||
repository: string;
|
||||
}
|
||||
};
|
||||
|
||||
function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
||||
// Generate the Rust license information.
|
||||
|
@ -90,8 +94,8 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||
// Ensure we have license information to work with before proceeding.
|
||||
if (licenses.length === 0) {
|
||||
// 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("To install cargo-about on your system, you can run \`cargo install cargo-about\`.");
|
||||
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("License information is required in production builds. Aborting.");
|
||||
|
||||
process.exit(1);
|
||||
|
@ -136,9 +140,10 @@ function formatThirdPartyLicenses(jsLicenses: Dependency[]): string {
|
|||
|
||||
// Filter out the internal Graphite crates, which are not third-party.
|
||||
licenses = licenses.filter((license) => {
|
||||
license.packages = license.packages.filter((packageInfo) =>
|
||||
!(packageInfo.repository && packageInfo.repository.toLowerCase().includes("github.com/GraphiteEditor/Graphite".toLowerCase())) &&
|
||||
!(packageInfo.author && packageInfo.author.toLowerCase().includes("contact@graphite.rs"))
|
||||
license.packages = license.packages.filter(
|
||||
(packageInfo) =>
|
||||
!(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;
|
||||
});
|
||||
|
@ -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>.
|
||||
const indirectEval = eval;
|
||||
const licensesArray = indirectEval(stdout) as LicenseInfo[];
|
||||
|
||||
|
||||
// Remove the HTML character encoding caused by Handlebars.
|
||||
let rustLicenses = (licensesArray || []).map((rustLicense): LicenseInfo => ({
|
||||
licenseName: htmlDecode(rustLicense.licenseName),
|
||||
licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)),
|
||||
packages: rustLicense.packages.map((packageInfo): PackageInfo => ({
|
||||
name: htmlDecode(packageInfo.name),
|
||||
version: htmlDecode(packageInfo.version),
|
||||
author: htmlDecode(packageInfo.author).replace(/\[(.*), \]/, "$1").replace("[]", ""),
|
||||
repository: htmlDecode(packageInfo.repository),
|
||||
})),
|
||||
}));
|
||||
const rustLicenses = (licensesArray || []).map(
|
||||
(rustLicense): LicenseInfo => ({
|
||||
licenseName: htmlDecode(rustLicense.licenseName),
|
||||
licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)),
|
||||
packages: rustLicense.packages.map(
|
||||
(packageInfo): PackageInfo => ({
|
||||
name: htmlDecode(packageInfo.name),
|
||||
version: htmlDecode(packageInfo.version),
|
||||
author: htmlDecode(packageInfo.author)
|
||||
.replace(/\[(.*), \]/, "$1")
|
||||
.replace("[]", ""),
|
||||
repository: htmlDecode(packageInfo.repository),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
return rustLicenses;
|
||||
} catch (_) {
|
||||
|
|
|
@ -52,7 +52,6 @@ module.exports = {
|
|||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@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/prefer-default-export": "off",
|
||||
|
|
|
@ -37,7 +37,7 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
|
||||
sliderUnits!: Record<string, string | string[]>;
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
async connectedCallback() {
|
||||
this.title = this.getAttribute("title") || "";
|
||||
this.points = JSON.parse(this.getAttribute("points") || "[]");
|
||||
this.key = this.getAttribute("key") as BezierFeatureKey;
|
||||
|
@ -59,15 +59,15 @@ class BezierDemo extends HTMLElement implements Demo {
|
|||
this.drawDemo(figure);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
render() {
|
||||
renderDemo(this);
|
||||
}
|
||||
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]) {
|
||||
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation);
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent): void {
|
||||
onMouseDown(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
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;
|
||||
}
|
||||
|
||||
onMouseMove(event: MouseEvent): void {
|
||||
onMouseMove(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
const figure = event.currentTarget as HTMLElement;
|
||||
|
|
|
@ -41,7 +41,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
|||
|
||||
id!: string;
|
||||
|
||||
connectedCallback(): void {
|
||||
connectedCallback() {
|
||||
this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
|
||||
this.id = `bezier/${this.key}`;
|
||||
this.name = bezierFeatures[this.key].name;
|
||||
|
@ -62,7 +62,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
|
|||
this.render();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
render() {
|
||||
renderDemoPane(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class SubpathDemo extends HTMLElement {
|
|||
|
||||
sliderUnits!: Record<string, string | string[]>;
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
async connectedCallback() {
|
||||
this.title = this.getAttribute("title") || "";
|
||||
this.triples = JSON.parse(this.getAttribute("triples") || "[]");
|
||||
this.key = this.getAttribute("key") as SubpathFeatureKey;
|
||||
|
@ -53,15 +53,15 @@ class SubpathDemo extends HTMLElement {
|
|||
this.drawDemo(figure);
|
||||
}
|
||||
|
||||
render(): void {
|
||||
render() {
|
||||
renderDemo(this);
|
||||
}
|
||||
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]) {
|
||||
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation);
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent): void {
|
||||
onMouseDown(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
for (let controllerIndex = 0; controllerIndex < this.triples.length; controllerIndex += 1) {
|
||||
|
@ -75,11 +75,11 @@ class SubpathDemo extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
onMouseUp(): void {
|
||||
onMouseUp() {
|
||||
this.activeIndex = undefined;
|
||||
}
|
||||
|
||||
onMouseMove(event: MouseEvent): void {
|
||||
onMouseMove(event: MouseEvent) {
|
||||
const mx = event.offsetX;
|
||||
const my = event.offsetY;
|
||||
const figure = event.currentTarget as HTMLElement;
|
||||
|
|
|
@ -17,7 +17,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
|||
|
||||
id!: string;
|
||||
|
||||
connectedCallback(): void {
|
||||
connectedCallback() {
|
||||
this.demos = [
|
||||
{
|
||||
title: "Open Subpath",
|
||||
|
@ -52,7 +52,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
|
|||
this.render();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
render() {
|
||||
renderDemoPane(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ window.customElements.define("bezier-demo-pane", BezierDemoPane);
|
|||
window.customElements.define("subpath-demo", SubpathDemo);
|
||||
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 demo = document.createElement("bezier-demo-pane");
|
||||
|
||||
|
@ -30,7 +30,7 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement
|
|||
container?.append(demo);
|
||||
}
|
||||
|
||||
function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null): void {
|
||||
function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null) {
|
||||
const feature = subpathFeatures[featureName];
|
||||
const demo = document.createElement("subpath-demo-pane");
|
||||
|
||||
|
@ -46,7 +46,7 @@ function isUrlSolo(url: string): boolean {
|
|||
return splitHash?.length === 3 && splitHash?.[2] === "solo";
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", (e: Event): void => {
|
||||
window.addEventListener("hashchange", (e: Event) => {
|
||||
const hashChangeEvent = e as HashChangeEvent;
|
||||
const isOldHashSolo = isUrlSolo(hashChangeEvent.oldURL);
|
||||
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 splitHash = hash.split("/");
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Demo, DemoPane, InputOption } from "@/utils/types";
|
||||
|
||||
export function renderDemo(demo: Demo): void {
|
||||
export function renderDemo(demo: Demo) {
|
||||
const header = document.createElement("h4");
|
||||
header.className = "demo-header";
|
||||
header.innerText = demo.title;
|
||||
|
@ -47,7 +47,7 @@ export function renderDemo(demo: Demo): void {
|
|||
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.drawDemo(figure);
|
||||
});
|
||||
|
@ -65,7 +65,7 @@ export function renderDemo(demo: Demo): void {
|
|||
const ratio = (Number(inputOption.default) - Number(inputOption.min)) / range;
|
||||
sliderInput.style.setProperty("--range-ratio", String(ratio));
|
||||
|
||||
sliderInput.addEventListener("input", (event: Event): void => {
|
||||
sliderInput.addEventListener("input", (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
demo.sliderData[inputOption.variable] = Number(target.value);
|
||||
const data = demo.sliderData[inputOption.variable];
|
||||
|
@ -86,7 +86,7 @@ export function renderDemo(demo: Demo): void {
|
|||
demo.append(parentSliderContainer);
|
||||
}
|
||||
|
||||
export function renderDemoPane(demoPane: DemoPane): void {
|
||||
export function renderDemoPane(demoPane: DemoPane) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "demo-pane-container";
|
||||
|
||||
|
|
|
@ -79,10 +79,10 @@ export interface Demo extends HTMLElement {
|
|||
sliderData: Record<string, number>;
|
||||
sliderUnits: Record<string, string | string[]>;
|
||||
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void;
|
||||
onMouseDown(event: MouseEvent): void;
|
||||
onMouseUp(): void;
|
||||
onMouseMove(event: MouseEvent): void;
|
||||
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]);
|
||||
onMouseDown(event: MouseEvent);
|
||||
onMouseUp();
|
||||
onMouseMove(event: MouseEvent);
|
||||
getSliderUnit(sliderValue: number, variable: string): string;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue