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

* Restore ESLint and Prettier autoformatting

* Fix formatting and lints in web files

* Hacky fix to eslint crash

* Fix remaining lints

* Add lint-fix script

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers 2023-11-16 13:12:47 -08:00 committed by GitHub
parent 9784d31edb
commit a6ca43bb2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 5869 additions and 351 deletions

View file

@ -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: |

View file

@ -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"
}
}
]
}

View file

@ -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",

View file

@ -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"],
},
},
],
};

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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,

View file

@ -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;
}
}
}

View file

@ -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");

View file

@ -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 {

View file

@ -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>

View file

@ -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>
//

View file

@ -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

View file

@ -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());

View file

@ -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");

View file

@ -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

View file

@ -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>

View file

@ -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";

View file

@ -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;

View file

@ -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} />

View file

@ -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];

View file

@ -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;

View file

@ -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();

View file

@ -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`

View file

@ -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;

View file

@ -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");

View file

@ -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 };

View file

@ -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");

View file

@ -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;

View file

@ -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");

View file

@ -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");

View file

@ -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);

View file

@ -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();

View file

@ -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;
}

View file

@ -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");

View file

@ -41,18 +41,18 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
{ target: window, eventName: "wheel", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } },
{ target: window, eventName: "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);

View file

@ -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()}` };

View file

@ -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

View file

@ -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);
}

View file

@ -1,4 +1,4 @@
import {writable} from "svelte/store";
import { writable } from "svelte/store";
import { type IconName } from "@graphite/utility-functions/icons";
import { type 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;
});
});

View file

@ -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);

View file

@ -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);

View file

@ -1,15 +1,7 @@
import {writable} from "svelte/store";
import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor";
import {
type 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;

View file

@ -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);

View file

@ -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;

View file

@ -1,4 +1,4 @@
export function downloadFileURL(filename: string, url: string): void {
export function downloadFileURL(filename: string, url: string) {
const element = document.createElement("a");
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);
});
}

View file

@ -104,16 +104,16 @@ import Copy from "@graphite-frontend/assets/icon-16px-solid/copy.svg";
import Credits from "@graphite-frontend/assets/icon-16px-solid/credits.svg";
import 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";

View file

@ -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) => {

View file

@ -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";
}

View file

@ -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);

View file

@ -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();
}

View file

@ -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,
}))
})),
);
}

View file

@ -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;
}

View file

@ -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": {

View file

@ -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 (_) {

View file

@ -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",

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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("/");

View file

@ -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";

View file

@ -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;
}