Fix Bezier-rs interactive demos and migrate its Webpack bundler to Vite

This commit is contained in:
Keavon Chambers 2024-01-04 02:03:50 -08:00
parent bbece3fb65
commit d268afb7fb
27 changed files with 3580 additions and 3013 deletions

View file

@ -3,20 +3,16 @@
"useTabs": true, "useTabs": true,
"tabWidth": 4, "tabWidth": 4,
"printWidth": 200, "printWidth": 200,
"plugins": ["prettier-plugin-svelte"],
"overrides": [ "overrides": [
{ {
"files": ["*.yml", "*.yaml"], "files": [
"*.yml",
"*.yaml"
],
"options": { "options": {
"useTabs": false, "useTabs": false,
"tabWidth": 2 "tabWidth": 2
} }
},
{
"files": ["*.svelte"],
"options": {
"parser": "svelte"
}
} }
] ]
} }

View file

@ -21,7 +21,7 @@
// Configured in `.prettierrc` // Configured in `.prettierrc`
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[yaml][github-actions-workflow]": { "[json][jsonc][yaml][github-actions-workflow]": {
"editor.formatOnSave": true, "editor.formatOnSave": true,
// Configured in `.prettierrc` // Configured in `.prettierrc`
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"

View file

@ -12,7 +12,7 @@ module.exports = {
], ],
plugins: ["import", "@typescript-eslint", "prettier"], plugins: ["import", "@typescript-eslint", "prettier"],
settings: { settings: {
"import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] }, "import/parsers": { "@typescript-eslint/parser": [".ts"] },
"import/resolver": { typescript: true, node: true }, "import/resolver": { typescript: true, node: true },
}, },
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
@ -32,16 +32,16 @@ module.exports = {
"!.*.ts", "!.*.ts",
], ],
overrides: [ overrides: [
{
files: ["*.js"],
rules: { "@typescript-eslint/explicit-function-return-type": ["off"] },
},
{ {
files: ["*.svelte"], files: ["*.svelte"],
parser: "svelte-eslint-parser", parser: "svelte-eslint-parser",
// Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration. // Parse the `<script>` in `.svelte` as TypeScript by adding the following configuration.
parserOptions: { parser: "@typescript-eslint/parser" }, parserOptions: { parser: "@typescript-eslint/parser" },
}, },
{
extends: ["plugin:@typescript-eslint/disable-type-checked"],
files: [".eslintrc.cjs"],
},
], ],
rules: { rules: {
// Standard ESLint config (for ordinary JS syntax linting) // Standard ESLint config (for ordinary JS syntax linting)

19
frontend/.prettierrc Normal file
View file

@ -0,0 +1,19 @@
{
"singleQuote": false,
"useTabs": true,
"tabWidth": 4,
"printWidth": 200,
"plugins": [
"prettier-plugin-svelte"
],
"overrides": [
{
"files": [
"*.svelte"
],
"options": {
"parser": "svelte"
}
}
]
}

View file

@ -53,12 +53,11 @@
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.4.5", "vite": "^4.4.5",
"vite-multiple-assets": "^1.2.6" "vite-multiple-assets": "1.2.6"
}, },
"optionalDependencies": { "optionalDependencies": {
"wasm-pack": "0.12.1" "wasm-pack": "0.12.1"
}, },
"//": "Notes about dependency issues and incompatibilities should be added here when needed.",
"homepage": "https://graphite.rs", "homepage": "https://graphite.rs",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {

View file

@ -3,7 +3,6 @@
"target": "ES2020", "target": "ES2020",
"module": "esnext", "module": "esnext",
"strict": true, "strict": true,
"jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"moduleResolution": "node", "moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,

View file

@ -0,0 +1,76 @@
module.exports = {
root: true,
env: { browser: true, node: true },
extends: ["eslint:recommended", "plugin:import/recommended", "plugin:@typescript-eslint/recommended", "plugin:import/typescript", "prettier"],
plugins: ["import", "@typescript-eslint", "prettier"],
settings: {
"import/parsers": { "@typescript-eslint/parser": [".ts"] },
"import/resolver": { typescript: true, node: true },
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
project: "./tsconfig.json",
},
overrides: [
{
extends: ["plugin:@typescript-eslint/disable-type-checked"],
files: [".eslintrc.cjs"],
},
],
ignorePatterns: [
// Ignore generated directories
"node_modules/",
"dist/",
"pkg/",
"wasm/pkg/",
// Don't ignore JS and TS dotfiles in this folder
"!.*.js",
"!.*.ts",
],
rules: {
// Standard ESLint config (for ordinary JS syntax linting)
indent: "off",
quotes: ["error", "double", { allowTemplateLiterals: true }],
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }],
"prefer-destructuring": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-param-reassign": ["error", { props: false }],
"no-bitwise": "off",
"no-shadow": "off",
"no-use-before-define": "off",
"no-restricted-imports": ["error", { patterns: [".*", "!@/*"] }],
// 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/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as", objectLiteralTypeAssertions: "never" }],
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
"@typescript-eslint/consistent-generic-constructors": ["error", "constructor"],
"@typescript-eslint/ban-types": ["error", { types: { null: "Use `undefined` instead." } }],
// Prettier plugin config (for validating and fixing formatting)
"prettier/prettier": "error",
// 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 },
"newlines-between": "always-and-inside-groups",
warnOnUnassignedImports: true,
},
],
},
};

View file

@ -1,79 +0,0 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2020: true,
},
parserOptions: {
ecmaVersion: 2020,
// parser: '@typescript-eslint/parser'
},
extends: [
// General Prettier defaults
"prettier",
],
settings: {
// https://github.com/import-js/eslint-plugin-import#resolvers
"import/resolver": {
// `node` must be listed first!
node: {},
},
},
ignorePatterns: [
// Ignore generated directories
"node_modules/",
"dist/",
"pkg/",
"wasm/pkg/",
// Don't ignore JS and TS dotfiles in this folder
"!.*.js",
"!.*.ts",
],
rules: {
// Standard ESLint config
indent: "off",
quotes: ["error", "double", { allowTemplateLiterals: true }],
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }],
"prefer-destructuring": "off",
"no-console": "warn",
"no-debugger": "warn",
"no-param-reassign": ["error", { props: false }],
"no-bitwise": "off",
"no-shadow": "off",
"no-use-before-define": "off",
"no-restricted-imports": ["error", { patterns: [".*", "!@/*"] }],
// TypeScript plugin config
"@typescript-eslint/indent": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", ignoreRestSiblings: true }],
// Import plugin config (used to intelligently validate module import statements)
"import/prefer-default-export": "off",
"import/no-relative-packages": "error",
"import/order": [
"error",
{
alphabetize: {
order: "asc",
caseInsensitive: true,
},
warnOnUnassignedImports: true,
"newlines-between": "always-and-inside-groups"
},
],
},
overrides: [
{
files: ["*.js"],
rules: {
"@typescript-eslint/explicit-function-return-type": ["off"],
},
},
],
};

View file

@ -0,0 +1,6 @@
{
"singleQuote": false,
"useTabs": true,
"tabWidth": 4,
"printWidth": 200
}

View file

@ -12,18 +12,7 @@ From this directory, first execute `npm install` to install the required Node de
``` ```
npm start npm start
``` ```
- To compile an unoptimized development build (like above, but it writes the files instead of serving them): - To compile an optimized production build:
``` ```
npm run build npm run build
``` ```
- To compile an optimized production build:
```
# WSL/Mac/Linux terminals:
npm run build-prod-unix
# Windows terminals:
npm run build-prod-windows
```
When a build is compiled, the entire `./public` folder is the output containing both the static `index.html`, etc., plus the generated `build/` folder.

View file

@ -12,6 +12,6 @@
</head> </head>
<body> <body>
<noscript>JavaScript is required</noscript> <noscript>JavaScript is required</noscript>
<script>import("./build/bundle.js");</script> <script type="module" src="src/main.ts"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

View file

@ -2,31 +2,39 @@
"name": "bezier-rs-demos", "name": "bezier-rs-demos",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"start": "webpack serve || (npm run print-building-help && exit 1)", "start": "npm run build-wasm && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm\" || (npm run print-building-help && exit 1)",
"serve": "webpack serve || (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)",
"build": "webpack build || (npm run print-building-help && exit 1)", "production": "npm run build-wasm-prod && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run watch:wasm\" || (npm run print-building-help && exit 1)",
"build-prod-unix": "NODE_ENV=production webpack build || (npm run print-building-help && exit 1)", "build": "npm run build-wasm-prod && vite build || (npm run print-building-help && exit 1)",
"build-prod-windows": "set NODE_ENV=production && webpack build || (npm run print-building-help && exit 1)", "build-profiling": "npm run build-wasm-profiling && vite build || (npm run print-building-help && exit 1)",
"print-building-help": "echo 'bezier-rs interactive demos project failed to build. Did you remember to `npm install` the node dependencies?'" "lint": "eslint .",
}, "lint-fix": "eslint . --fix",
"dependencies": { "--------------------": "",
"core-js": "^3.30.2" "build-wasm": "wasm-pack build ./wasm --dev --target=web",
"build-wasm-profiling": "wasm-pack build ./wasm --profiling --target=web",
"build-wasm-prod": "wasm-pack build ./wasm --release --target=web",
"watch:wasm": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --dev --target=web -- --color=always\"",
"watch:wasm-profiling": "cargo watch --postpone --watch-when-idle --workdir=wasm --shell \"wasm-pack build . --profiling --target=web -- --color=always\"",
"print-building-help": "echo 'Graphite project failed to build. Did you remember to `npm install` the dependencies in `/frontend`?'",
"print-linting-help": "echo 'Graphite project had lint errors, or may have otherwise failed. In the latter case, did you remember to `npm install` the dependencies in `/frontend`?'"
}, },
"devDependencies": { "devDependencies": {
"@types/webpack": "^5.28.1", "@types/node": "^18.16.2",
"@types/webpack-dev-server": "^4.7.2", "@typescript-eslint/eslint-plugin": "^6.11.0",
"@wasm-tool/wasm-pack-plugin": "^1.7.0", "@typescript-eslint/parser": "^6.11.0",
"css-loader": "^6.7.4", "concurrently": "^8.0.1",
"sass": "^1.62.1", "eslint-config-prettier": "^9.0.0",
"sass-loader": "^13.3.0", "eslint-import-resolver-typescript": "^3.6.1",
"ts-loader": "^9.4.3", "eslint-plugin-import": "^2.29.0",
"eslint-plugin-prettier": "^5.0.1",
"prettier": "^3.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"webpack": "^5.84.1", "vite": "^4.4.5"
"webpack-cli": "^5.1.1",
"webpack-dev-server": "^4.15.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"wasm-pack": "0.10.3" "wasm-pack": "0.12.1"
} }
} }

View file

@ -1,7 +1,9 @@
import { WasmBezier } from "@/../wasm/pkg"; import { WasmBezier } from "@/../wasm/pkg";
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features"; import type { BezierFeatureKey } from "@/features/bezier-features";
import bezierFeatures from "@/features/bezier-features";
import { renderDemo } from "@/utils/render"; import { renderDemo } from "@/utils/render";
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, InputOption, WasmBezierManipulatorKey, Demo } from "@/utils/types"; import type { BezierCallback, BezierCurveType, InputOption, WasmBezierManipulatorKey, Demo } from "@/utils/types";
import { getConstructorKey, getCurveType } from "@/utils/types";
const SELECTABLE_RANGE = 10; const SELECTABLE_RANGE = 10;
@ -54,8 +56,7 @@ class BezierDemo extends HTMLElement implements Demo {
this.render(); this.render();
const figure = this.querySelector("figure") as HTMLElement; const figure = this.querySelector("figure") as HTMLElement;
const wasm = await import("@/../wasm/pkg"); this.bezier = WasmBezier[getConstructorKey(curveType)](this.points);
this.bezier = wasm.WasmBezier[getConstructorKey(curveType)](this.points);
this.drawDemo(figure); this.drawDemo(figure);
} }

View file

@ -1,6 +1,8 @@
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features"; import type { BezierFeatureKey } from "@/features/bezier-features";
import bezierFeatures from "@/features/bezier-features";
import { renderDemoPane } from "@/utils/render"; import { renderDemoPane } from "@/utils/render";
import { BezierCurveType, BEZIER_CURVE_TYPE, BezierDemoOptions, InputOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types"; import type { BezierCurveType, BezierDemoOptions, InputOption, DemoPane, BezierDemoArgs } from "@/utils/types";
import { BEZIER_CURVE_TYPE } from "@/utils/types";
const demoDefaults = { const demoDefaults = {
Linear: { Linear: {
@ -66,7 +68,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
renderDemoPane(this); renderDemoPane(this);
} }
buildDemo(demo: BezierDemoArgs): Demo { buildDemo(demo: BezierDemoArgs): HTMLElement {
const bezierDemo = document.createElement("bezier-demo"); const bezierDemo = document.createElement("bezier-demo");
bezierDemo.setAttribute("title", demo.title); bezierDemo.setAttribute("title", demo.title);
bezierDemo.setAttribute("points", JSON.stringify(demo.points)); bezierDemo.setAttribute("points", JSON.stringify(demo.points));

View file

@ -1,8 +1,8 @@
import { WasmSubpath } from "@/../wasm/pkg"; import { WasmSubpath } from "@/../wasm/pkg";
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features"; import type { SubpathFeatureKey } from "@/features/subpath-features";
import subpathFeatures from "@/features/subpath-features";
import { renderDemo } from "@/utils/render"; import { renderDemo } from "@/utils/render";
import type { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, InputOption } from "@/utils/types";
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, InputOption } from "@/utils/types";
const SELECTABLE_RANGE = 10; const SELECTABLE_RANGE = 10;
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"]; const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
@ -48,8 +48,7 @@ class SubpathDemo extends HTMLElement {
this.render(); this.render();
const figure = this.querySelector("figure") as HTMLElement; const figure = this.querySelector("figure") as HTMLElement;
const wasm = await import("@/../wasm/pkg"); this.subpath = WasmSubpath.from_triples(this.triples, this.closed) as WasmSubpathInstance;
this.subpath = wasm.WasmSubpath.from_triples(this.triples, this.closed) as WasmSubpathInstance;
this.drawDemo(figure); this.drawDemo(figure);
} }

View file

@ -1,6 +1,7 @@
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features"; import type { SubpathFeatureKey } from "@/features/subpath-features";
import subpathFeatures from "@/features/subpath-features";
import { renderDemoPane } from "@/utils/render"; import { renderDemoPane } from "@/utils/render";
import { Demo, DemoPane, SubpathDemoArgs, SubpathInputOption } from "@/utils/types"; import type { DemoPane, SubpathDemoArgs, SubpathInputOption } from "@/utils/types";
class SubpathDemoPane extends HTMLElement implements DemoPane { class SubpathDemoPane extends HTMLElement implements DemoPane {
// Props // Props
@ -56,7 +57,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
renderDemoPane(this); renderDemoPane(this);
} }
buildDemo(demo: SubpathDemoArgs): Demo { buildDemo(demo: SubpathDemoArgs): HTMLElement {
const subpathDemo = document.createElement("subpath-demo"); const subpathDemo = document.createElement("subpath-demo");
subpathDemo.setAttribute("title", demo.title); subpathDemo.setAttribute("title", demo.title);
subpathDemo.setAttribute("triples", JSON.stringify(demo.triples)); subpathDemo.setAttribute("triples", JSON.stringify(demo.triples));

View file

@ -1,6 +1,7 @@
import { WasmBezier } from "@/../wasm/pkg"; import { WasmBezier } from "@/../wasm/pkg";
import { capOptions, tSliderOptions, bezierTValueVariantOptions, errorOptions, minimumSeparationOptions } from "@/utils/options"; import { capOptions, tSliderOptions, bezierTValueVariantOptions, errorOptions, minimumSeparationOptions } from "@/utils/options";
import { BezierDemoOptions, WasmBezierInstance, BezierCallback, InputOption, BEZIER_T_VALUE_VARIANTS } from "@/utils/types"; import type { BezierDemoOptions, WasmBezierInstance, BezierCallback, InputOption } from "@/utils/types";
import { BEZIER_T_VALUE_VARIANTS } from "@/utils/types";
const bezierFeatures = { const bezierFeatures = {
constructor: { constructor: {

View file

@ -1,5 +1,6 @@
import { capOptions, joinOptions, tSliderOptions, subpathTValueVariantOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options"; import { capOptions, joinOptions, tSliderOptions, subpathTValueVariantOptions, intersectionErrorOptions, minimumSeparationOptions } from "@/utils/options";
import { SubpathCallback, SubpathInputOption, WasmSubpathInstance, SUBPATH_T_VALUE_VARIANTS } from "@/utils/types"; import type { SubpathCallback, SubpathInputOption, WasmSubpathInstance } from "@/utils/types";
import { SUBPATH_T_VALUE_VARIANTS } from "@/utils/types";
const subpathFeatures = { const subpathFeatures = {
constructor: { constructor: {
@ -71,7 +72,7 @@ const subpathFeatures = {
[210, 150], [210, 150],
], ],
options.error, options.error,
options.minimum_separation options.minimum_separation,
), ),
inputOptions: [intersectionErrorOptions, minimumSeparationOptions], inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
}, },
@ -85,7 +86,7 @@ const subpathFeatures = {
[135, 180], [135, 180],
], ],
options.error, options.error,
options.minimum_separation options.minimum_separation,
), ),
inputOptions: [intersectionErrorOptions, minimumSeparationOptions], inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
}, },
@ -100,7 +101,7 @@ const subpathFeatures = {
[200, 140], [200, 140],
], ],
options.error, options.error,
options.minimum_separation options.minimum_separation,
), ),
inputOptions: [intersectionErrorOptions, minimumSeparationOptions], inputOptions: [intersectionErrorOptions, minimumSeparationOptions],
}, },

View file

@ -1,26 +1,36 @@
import { default as init } from "@/../wasm/pkg";
import BezierDemo from "@/components/BezierDemo"; import BezierDemo from "@/components/BezierDemo";
import BezierDemoPane from "@/components/BezierDemoPane"; import BezierDemoPane from "@/components/BezierDemoPane";
import SubpathDemo from "@/components/SubpathDemo"; import SubpathDemo from "@/components/SubpathDemo";
import SubpathDemoPane from "@/components/SubpathDemoPane"; import SubpathDemoPane from "@/components/SubpathDemoPane";
import type { BezierFeatureKey } from "@/features/bezier-features";
import bezierFeatures from "@/features/bezier-features";
import type { SubpathFeatureKey } from "@/features/subpath-features";
import subpathFeatures from "@/features/subpath-features";
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features"; (async () => {
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features"; await init();
declare global { window.customElements.define("bezier-demo", BezierDemo);
interface HTMLElementTagNameMap { window.customElements.define("bezier-demo-pane", BezierDemoPane);
"bezier-demo": BezierDemo; window.customElements.define("subpath-demo", SubpathDemo);
"bezier-demo-pane": BezierDemoPane; window.customElements.define("subpath-demo-pane", SubpathDemoPane);
"subpath-demo": SubpathDemo;
"subpath-demo-pane": SubpathDemoPane;
}
}
window.customElements.define("bezier-demo", BezierDemo); window.addEventListener("hashchange", (e: Event) => {
window.customElements.define("bezier-demo-pane", BezierDemoPane); const hashChangeEvent = e as HashChangeEvent;
window.customElements.define("subpath-demo", SubpathDemo); const isOldHashSolo = isUrlSolo(hashChangeEvent.oldURL);
window.customElements.define("subpath-demo-pane", SubpathDemoPane); const isNewHashSolo = isUrlSolo(hashChangeEvent.newURL);
const target = document.getElementById(window.location.hash.substring(1));
// Determine whether the page needs to recompute which examples to show
if (!target || isOldHashSolo !== isNewHashSolo) {
renderExamples();
}
});
function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement | null) { renderExamples();
})();
function renderBezierPane(featureName: BezierFeatureKey, container?: HTMLElement) {
const feature = bezierFeatures[featureName]; const feature = bezierFeatures[featureName];
const demo = document.createElement("bezier-demo-pane"); const demo = document.createElement("bezier-demo-pane");
@ -30,7 +40,7 @@ function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement
container?.append(demo); container?.append(demo);
} }
function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null) { function renderSubpathPane(featureName: SubpathFeatureKey, container?: HTMLElement) {
const feature = subpathFeatures[featureName]; const feature = subpathFeatures[featureName];
const demo = document.createElement("subpath-demo-pane"); const demo = document.createElement("subpath-demo-pane");
@ -46,17 +56,6 @@ function isUrlSolo(url: string): boolean {
return splitHash?.length === 3 && splitHash?.[2] === "solo"; return splitHash?.length === 3 && splitHash?.[2] === "solo";
} }
window.addEventListener("hashchange", (e: Event) => {
const hashChangeEvent = e as HashChangeEvent;
const isOldHashSolo = isUrlSolo(hashChangeEvent.oldURL);
const isNewHashSolo = isUrlSolo(hashChangeEvent.newURL);
const target = document.getElementById(window.location.hash.substring(1));
// Determine whether the page needs to recompute which examples to show
if (!target || isOldHashSolo !== isNewHashSolo) {
renderExamples();
}
});
function renderExamples() { function renderExamples() {
const hash = window.location.hash; const hash = window.location.hash;
const splitHash = hash.split("/"); const splitHash = hash.split("/");
@ -64,10 +63,10 @@ function renderExamples() {
// Determine which examples to render based on hash // Determine which examples to render based on hash
if (splitHash[0] === "#bezier" && splitHash[1] in bezierFeatures && splitHash[2] === "solo") { if (splitHash[0] === "#bezier" && splitHash[1] in bezierFeatures && splitHash[2] === "solo") {
window.document.body.innerHTML = `<div id="bezier-demos"></div>`; window.document.body.innerHTML = `<div id="bezier-demos"></div>`;
renderBezierPane(splitHash[1] as BezierFeatureKey, document.getElementById("bezier-demos")); renderBezierPane(splitHash[1] as BezierFeatureKey, document.getElementById("bezier-demos") || undefined);
} else if (splitHash[0] === "#subpath" && splitHash[1] in subpathFeatures && splitHash[2] === "solo") { } else if (splitHash[0] === "#subpath" && splitHash[1] in subpathFeatures && splitHash[2] === "solo") {
window.document.body.innerHTML = `<div id="subpath-demos"></div>`; window.document.body.innerHTML = `<div id="subpath-demos"></div>`;
renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos")); renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos") || undefined);
} else { } else {
window.document.body.innerHTML = ` window.document.body.innerHTML = `
<h1 class="website-header">Bezier-rs Interactive Documentation</h1> <h1 class="website-header">Bezier-rs Interactive Documentation</h1>
@ -83,8 +82,8 @@ function renderExamples() {
<div id="subpath-demos"></div> <div id="subpath-demos"></div>
`.trim(); `.trim();
const bezierDemos = document.getElementById("bezier-demos"); const bezierDemos = document.getElementById("bezier-demos") || undefined;
const subpathDemos = document.getElementById("subpath-demos"); const subpathDemos = document.getElementById("subpath-demos") || undefined;
(Object.keys(bezierFeatures) as BezierFeatureKey[]).forEach((feature) => renderBezierPane(feature, bezierDemos)); (Object.keys(bezierFeatures) as BezierFeatureKey[]).forEach((feature) => renderBezierPane(feature, bezierDemos));
(Object.keys(subpathFeatures) as SubpathFeatureKey[]).forEach((feature) => renderSubpathPane(feature, subpathDemos)); (Object.keys(subpathFeatures) as SubpathFeatureKey[]).forEach((feature) => renderSubpathPane(feature, subpathDemos));
@ -98,5 +97,3 @@ function renderExamples() {
} }
} }
} }
renderExamples();

View file

@ -1,4 +1,4 @@
import { Demo, DemoPane, InputOption } from "@/utils/types"; import type { Demo, DemoPane, InputOption } from "@/utils/types";
export function renderDemo(demo: Demo) { export function renderDemo(demo: Demo) {
const header = document.createElement("h4"); const header = document.createElement("h4");

View file

@ -1,4 +1,6 @@
export type WasmRawInstance = typeof import("@/../wasm/pkg"); import type * as WasmPkg from "@/../wasm/pkg";
export type WasmRawInstance = typeof WasmPkg;
export type WasmBezierInstance = InstanceType<WasmRawInstance["WasmBezier"]>; export type WasmBezierInstance = InstanceType<WasmRawInstance["WasmBezier"]>;
export type WasmBezierKey = keyof WasmBezierInstance; export type WasmBezierKey = keyof WasmBezierInstance;
@ -9,7 +11,7 @@ export type WasmSubpathInstance = InstanceType<WasmRawInstance["WasmSubpath"]>;
export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_out_handle"; export type WasmSubpathManipulatorKey = "set_anchor" | "set_in_handle" | "set_out_handle";
export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const; export const BEZIER_CURVE_TYPE = ["Linear", "Quadratic", "Cubic"] as const;
export type BezierCurveType = typeof BEZIER_CURVE_TYPE[number]; export type BezierCurveType = (typeof BEZIER_CURVE_TYPE)[number];
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string; export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string;
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string; export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number]) => string;
@ -59,22 +61,22 @@ export function getConstructorKey(bezierCurveType: BezierCurveType): WasmBezierC
return mapping[bezierCurveType]; return mapping[bezierCurveType];
} }
export interface DemoArgs { export type DemoArgs = {
title: string; title: string;
disabled?: boolean; disabled?: boolean;
} };
export interface BezierDemoArgs extends DemoArgs { export type BezierDemoArgs = {
points: number[][]; points: number[][];
inputOptions: InputOption[]; inputOptions: InputOption[];
} } & DemoArgs;
export interface SubpathDemoArgs extends DemoArgs { export type SubpathDemoArgs = {
triples: (number[] | undefined)[][]; triples: (number[] | undefined)[][];
closed: boolean; closed: boolean;
} } & DemoArgs;
export interface Demo extends HTMLElement { export type Demo = {
inputOptions: InputOption[]; inputOptions: InputOption[];
sliderData: Record<string, number>; sliderData: Record<string, number>;
sliderUnits: Record<string, string | string[]>; sliderUnits: Record<string, string | string[]>;
@ -84,14 +86,14 @@ export interface Demo extends HTMLElement {
onMouseUp(): void; onMouseUp(): void;
onMouseMove(event: MouseEvent): void; onMouseMove(event: MouseEvent): void;
getSliderUnit(sliderValue: number, variable: string): string; getSliderUnit(sliderValue: number, variable: string): string;
} } & HTMLElement;
export interface DemoPane extends HTMLElement { export type DemoPane = {
name: string; name: string;
demos: DemoArgs[]; demos: DemoArgs[];
id: string; id: string;
buildDemo(demo: DemoArgs): Demo; buildDemo(demo: DemoArgs): HTMLElement;
} } & HTMLElement;
export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const; export const BEZIER_T_VALUE_VARIANTS = ["Parametric", "Euclidean"] as const;
export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const; export const SUBPATH_T_VALUE_VARIANTS = ["GlobalParametric", "GlobalEuclidean"] as const;

View file

@ -3,7 +3,6 @@
"target": "ES2020", "target": "ES2020",
"module": "esnext", "module": "esnext",
"strict": true, "strict": true,
"jsx": "preserve",
"importHelpers": true, "importHelpers": true,
"moduleResolution": "node", "moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,
@ -12,14 +11,28 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"types": ["node"],
"paths": { "paths": {
"@/*": ["src/*"] "@/*": [
"src/*"
]
}, },
"lib": ["esnext", "dom", "dom.iterable", "scripthost"] "lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts"], "include": [
"exclude": ["node_modules"], "src/**/*.ts",
"src/**/*.d.ts",
"*.ts",
"*.js",
"*.cjs"
],
"exclude": [
"node_modules"
],
"ts-node": { "ts-node": {
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",

View file

@ -0,0 +1,19 @@
/* eslint-disable no-console */
import path from "path";
import { defineConfig } from "vite";
const projectRootDir = path.resolve(__dirname);
// https://vitejs.dev/config/
export default defineConfig({
base: "",
resolve: {
alias: [{ find: "@", replacement: path.resolve(projectRootDir, "src") }],
},
server: {
port: 8000,
host: "0.0.0.0",
},
});

View file

@ -8,7 +8,7 @@ pub static LOGGER: WasmLog = WasmLog;
/// Initialize the backend /// Initialize the backend
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn init_graphite() { pub fn init() {
// Set up the logger with a default level of debug // Set up the logger with a default level of debug
log::set_logger(&LOGGER).expect("Failed to set logger"); log::set_logger(&LOGGER).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Trace); log::set_max_level(log::LevelFilter::Trace);

View file

@ -1,66 +0,0 @@
// TODO: Eventually migrate the bundler from Webpack to Vite
const path = require("path");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
const mode = process.env.NODE_ENV === "production" ? "production" : "development";
module.exports = {
mode,
entry: {
bundle: ["./src/main.ts"],
},
resolve: {
alias: {
"@": path.resolve(__dirname, "src/"),
},
extensions: [".ts", ".js"],
mainFields: ["browser", "module", "main"],
},
output: {
path: path.resolve(__dirname, "public/build"),
publicPath: "./build/",
filename: "[name].js"
},
module: {
rules: [
// Rule: SASS
{
test: /\.(scss|sass)$/,
use: ["css-loader", "sass-loader"],
},
// Rule: CSS
{
test: /\.css$/,
use: ["css-loader"],
},
// Rule: TypeScript
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
devServer: {
hot: true,
},
plugins: [
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, "wasm"),
// Remove when this issue is resolved: https://github.com/wasm-tool/wasm-pack-plugin/issues/93
outDir: path.resolve(__dirname, "wasm/pkg"),
watchDirectories: ["../../../libraries/bezier-rs"].map((folder) => path.resolve(__dirname, folder)),
}),
],
devtool: mode === "development" ? "source-map" : false,
experiments: {
asyncWebAssembly: true,
},
stats: {
errorDetails: true,
}
};

View file

@ -22,6 +22,6 @@ mkdir dist/libraries
mkdir dist/libraries/bezier-rs mkdir dist/libraries/bezier-rs
cd bezier-rs-demos cd bezier-rs-demos
npm ci npm ci
NODE_ENV=production npm run build-prod-unix NODE_ENV=production npm run build
mv public/* ../dist/libraries/bezier-rs mv dist/* ../dist/libraries/bezier-rs
cd .. cd ..