Bezier-rs: Refactor interactive demo to remove Vue (#959)

* Converted bezier example to vanilla js component

* Indent with tabs

* Add sliders

* Converted bezier example pane to vanilla js

* Implement the radio buttons + fixes

* Converted SubpathExample to vanilla js

* Converted SubpathExamplePane to vanilla js

* Removed vue components

* Remove App.vue

* Remove vue and other dependencies

* Minor fix in main.ts

* Added insert to subpath features

* Entry point tweaks

* Rename "example" to "demo"

* Kebab-case file names (except classes)

Co-authored-by: Hannah Li <hannahli2010@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Rob Nadal 2023-01-13 04:04:39 -05:00 committed by Keavon Chambers
parent 758f757783
commit 01853fe4b7
19 changed files with 1272 additions and 1065 deletions

View File

@ -8,8 +8,7 @@
"name": "bezier-rs-demos", "name": "bezier-rs-demos",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"core-js": "^3.26.1", "core-js": "^3.26.1"
"vue": "^3.2.13"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
@ -17,7 +16,6 @@
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8", "@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-airbnb": "^6.0.0", "@vue/eslint-config-airbnb": "^6.0.0",
"@vue/eslint-config-typescript": "^11.0.2", "@vue/eslint-config-typescript": "^11.0.2",
"@wasm-tool/wasm-pack-plugin": "^1.6.0", "@wasm-tool/wasm-pack-plugin": "^1.6.0",
@ -26,8 +24,7 @@
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier-vue": "^4.2.0", "eslint-plugin-prettier-vue": "^4.2.0",
"eslint-plugin-vue": "^9.7.0", "eslint-plugin-vue": "^9.7.0",
"typescript": "^4.9.3", "typescript": "^4.9.3"
"vue-template-compiler": "^2.7.14"
}, },
"optionalDependencies": { "optionalDependencies": {
"wasm-pack": "^0.10.3" "wasm-pack": "^0.10.3"
@ -323,6 +320,7 @@
"version": "7.20.3", "version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -1395,6 +1393,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz",
"integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.45",
@ -1406,6 +1405,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz",
"integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==",
"dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -1415,6 +1415,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz",
"integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
@ -1432,6 +1433,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz",
"integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==",
"dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -1552,6 +1554,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz",
"integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
} }
@ -1560,6 +1564,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz",
"integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
@ -1572,6 +1577,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz",
"integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@vue/reactivity": "3.2.45", "@vue/reactivity": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -1581,6 +1588,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz",
"integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@vue/runtime-core": "3.2.45", "@vue/runtime-core": "3.2.45",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.45",
@ -1591,6 +1600,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz",
"integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.2.45", "@vue/compiler-ssr": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -1602,7 +1613,8 @@
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz",
"integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==",
"dev": true
}, },
"node_modules/@vue/vue-loader-v15": { "node_modules/@vue/vue-loader-v15": {
"name": "vue-loader", "name": "vue-loader",
@ -3290,13 +3302,17 @@
"node_modules/csstype": { "node_modules/csstype": {
"version": "2.6.21", "version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==",
"dev": true,
"peer": true
}, },
"node_modules/de-indent": { "node_modules/de-indent": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
@ -4563,7 +4579,8 @@
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
}, },
"node_modules/esutils": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
@ -6800,6 +6817,7 @@
"version": "0.25.9", "version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"dependencies": { "dependencies": {
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
} }
@ -7140,6 +7158,7 @@
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@ -7781,7 +7800,8 @@
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -7886,6 +7906,7 @@
"version": "8.4.19", "version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -9253,6 +9274,7 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -9261,6 +9283,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -9278,7 +9301,8 @@
"node_modules/sourcemap-codec": { "node_modules/sourcemap-codec": {
"version": "1.4.8", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
}, },
"node_modules/spdx-correct": { "node_modules/spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
@ -10140,6 +10164,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz",
"integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.45",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.45",
@ -10327,6 +10353,8 @@
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"de-indent": "^1.0.2", "de-indent": "^1.0.2",
"he": "^1.2.0" "he": "^1.2.0"
@ -11445,7 +11473,8 @@
"@babel/parser": { "@babel/parser": {
"version": "7.20.3", "version": "7.20.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==",
"dev": true
}, },
"@babel/template": { "@babel/template": {
"version": "7.18.10", "version": "7.18.10",
@ -12262,6 +12291,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz",
"integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==",
"dev": true,
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.45",
@ -12273,6 +12303,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz",
"integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==",
"dev": true,
"requires": { "requires": {
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -12282,6 +12313,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz",
"integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==",
"dev": true,
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
@ -12299,6 +12331,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz",
"integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==",
"dev": true,
"requires": { "requires": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -12388,6 +12421,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz",
"integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==",
"dev": true,
"peer": true,
"requires": { "requires": {
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
} }
@ -12396,6 +12431,7 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz",
"integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==",
"dev": true,
"requires": { "requires": {
"@babel/parser": "^7.16.4", "@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.45", "@vue/compiler-core": "3.2.45",
@ -12408,6 +12444,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz",
"integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==",
"dev": true,
"peer": true,
"requires": { "requires": {
"@vue/reactivity": "3.2.45", "@vue/reactivity": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -12417,6 +12455,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz",
"integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==",
"dev": true,
"peer": true,
"requires": { "requires": {
"@vue/runtime-core": "3.2.45", "@vue/runtime-core": "3.2.45",
"@vue/shared": "3.2.45", "@vue/shared": "3.2.45",
@ -12427,6 +12467,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz",
"integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==",
"dev": true,
"peer": true,
"requires": { "requires": {
"@vue/compiler-ssr": "3.2.45", "@vue/compiler-ssr": "3.2.45",
"@vue/shared": "3.2.45" "@vue/shared": "3.2.45"
@ -12435,7 +12477,8 @@
"@vue/shared": { "@vue/shared": {
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz",
"integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==",
"dev": true
}, },
"@vue/vue-loader-v15": { "@vue/vue-loader-v15": {
"version": "npm:vue-loader@15.10.1", "version": "npm:vue-loader@15.10.1",
@ -13702,13 +13745,17 @@
"csstype": { "csstype": {
"version": "2.6.21", "version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==",
"dev": true,
"peer": true
}, },
"de-indent": { "de-indent": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true "dev": true,
"optional": true,
"peer": true
}, },
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
@ -14671,7 +14718,8 @@
"estree-walker": { "estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
}, },
"esutils": { "esutils": {
"version": "2.0.3", "version": "2.0.3",
@ -16336,6 +16384,7 @@
"version": "0.25.9", "version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"dev": true,
"requires": { "requires": {
"sourcemap-codec": "^1.4.8" "sourcemap-codec": "^1.4.8"
} }
@ -16595,7 +16644,8 @@
"nanoid": { "nanoid": {
"version": "3.3.4", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
}, },
"natural-compare": { "natural-compare": {
"version": "1.4.0", "version": "1.4.0",
@ -17072,7 +17122,8 @@
"picocolors": { "picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
}, },
"picomatch": { "picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -17154,6 +17205,7 @@
"version": "8.4.19", "version": "8.4.19",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
"dev": true,
"requires": { "requires": {
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
@ -18103,12 +18155,14 @@
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}, },
"source-map-js": { "source-map-js": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
}, },
"source-map-support": { "source-map-support": {
"version": "0.5.21", "version": "0.5.21",
@ -18123,7 +18177,8 @@
"sourcemap-codec": { "sourcemap-codec": {
"version": "1.4.8", "version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
}, },
"spdx-correct": { "spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
@ -18765,6 +18820,8 @@
"version": "3.2.45", "version": "3.2.45",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz",
"integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==",
"dev": true,
"peer": true,
"requires": { "requires": {
"@vue/compiler-dom": "3.2.45", "@vue/compiler-dom": "3.2.45",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.45",
@ -18908,6 +18965,8 @@
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
"dev": true, "dev": true,
"optional": true,
"peer": true,
"requires": { "requires": {
"de-indent": "^1.0.2", "de-indent": "^1.0.2",
"he": "^1.2.0" "he": "^1.2.0"

View File

@ -9,8 +9,7 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.26.1", "core-js": "^3.26.1"
"vue": "^3.2.13"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
@ -18,7 +17,6 @@
"@vue/cli-plugin-eslint": "^5.0.8", "@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-typescript": "^5.0.8", "@vue/cli-plugin-typescript": "^5.0.8",
"@vue/cli-service": "^5.0.8", "@vue/cli-service": "^5.0.8",
"@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-airbnb": "^6.0.0", "@vue/eslint-config-airbnb": "^6.0.0",
"@vue/eslint-config-typescript": "^11.0.2", "@vue/eslint-config-typescript": "^11.0.2",
"@wasm-tool/wasm-pack-plugin": "^1.6.0", "@wasm-tool/wasm-pack-plugin": "^1.6.0",
@ -27,8 +25,7 @@
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier-vue": "^4.2.0", "eslint-plugin-prettier-vue": "^4.2.0",
"eslint-plugin-vue": "^9.7.0", "eslint-plugin-vue": "^9.7.0",
"typescript": "^4.9.3", "typescript": "^4.9.3"
"vue-template-compiler": "^2.7.14"
}, },
"optionalDependencies": { "optionalDependencies": {
"wasm-pack": "^0.10.3" "wasm-pack": "^0.10.3"

View File

@ -1,662 +0,0 @@
<template>
<h1>Bezier-rs Interactive Documentation</h1>
<p>
This is the interactive documentation for the <a href="https://crates.io/crates/bezier-rs"><b>Bezier-rs</b></a> library. View the
<a href="https://docs.rs/bezier-rs/latest/bezier_rs">crate documentation</a>
for detailed function descriptions and API usage. Click and drag on the endpoints of the example curves to visualize the various Bezier utilities and functions.
</p>
<h2>Beziers</h2>
<div v-for="(feature, index) in bezierFeatures" :key="index">
<BezierExamplePane
:name="feature.name"
:callback="feature.callback"
:exampleOptions="feature.exampleOptions"
:triggerOnMouseMove="feature.triggerOnMouseMove"
:chooseComputeType="feature.chooseComputeType"
/>
</div>
<h2>Subpaths</h2>
<div v-for="(feature, index) in subpathFeatures" :key="index">
<SubpathExamplePane
:name="feature.name"
:callback="feature.callback"
:sliderOptions="feature.sliderOptions"
:triggerOnMouseMove="feature.triggerOnMouseMove"
:chooseComputeType="feature.chooseComputeType"
/>
</div>
</template>
<style>
#app {
font-family: Arial, sans-serif;
text-align: center;
margin: 40px 0;
}
#app > h1 + p {
max-width: 768px;
line-height: 1.4;
margin: auto;
text-align: justify;
}
#app > h2 {
margin-top: 40px;
}
/* Example Pane styles */
.example-row {
display: flex;
flex-direction: row;
justify-content: center;
}
.compute-type-choice {
margin-top: 20px;
}
.example-pane-header {
margin-top: 2em;
margin-bottom: 0;
}
.example-pane-container {
position: relative;
width: fit-content;
margin: auto;
}
/* Example styles */
.example-header {
margin: 20px 0;
}
.example-figure {
width: 200px;
height: 200px;
margin-bottom: 20px;
border: solid 1px black;
}
</style>
<script lang="ts">
import { defineComponent } from "vue";
import { WasmBezier } from "@/../wasm/pkg";
import { ComputeType, ExampleOptions, WasmBezierInstance, WasmSubpathInstance } from "@/utils/types";
import BezierExamplePane from "@/components/BezierExamplePane.vue";
import SubpathExamplePane from "@/components/SubpathExamplePane.vue";
const tSliderOptions = {
min: 0,
max: 1,
step: 0.01,
default: 0.5,
variable: "t",
};
const tErrorOptions = {
variable: "error",
min: 0.1,
max: 2,
step: 0.1,
default: 0.5,
};
const tMinimumSeperationOptions = {
variable: "minimum_seperation",
min: 0.001,
max: 0.25,
step: 0.001,
default: 0.05,
};
export default defineComponent({
data() {
return {
bezierFeatures: [
{
name: "Constructor",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.to_svg(),
},
{
name: "Bezier Through Points",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const points = JSON.parse(bezier.get_points());
if (Object.values(options).length === 1) {
return WasmBezier.quadratic_through_points(points, options.t);
}
return WasmBezier.cubic_through_points(points, options.t, options["midpoint separation"]);
},
exampleOptions: {
Linear: {
disabled: true,
},
Quadratic: {
customPoints: [
[30, 50],
[120, 70],
[160, 170],
],
sliderOptions: [
{
min: 0.01,
max: 0.99,
step: 0.01,
default: 0.5,
variable: "t",
},
],
},
Cubic: {
customPoints: [
[30, 50],
[120, 70],
[160, 170],
],
sliderOptions: [
{
min: 0.01,
max: 0.99,
step: 0.01,
default: 0.5,
variable: "t",
},
{
min: 0,
max: 100,
step: 2,
default: 30,
variable: "midpoint separation",
},
],
},
},
},
{
name: "Length",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.length(),
},
{
name: "Evaluate",
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => bezier.evaluate(options.computeArgument, computeType),
exampleOptions: {
Quadratic: {
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
},
},
chooseComputeType: true,
},
{
name: "Lookup Table",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
min: 2,
max: 15,
step: 1,
default: 5,
variable: "steps",
},
],
},
},
},
{
name: "Derivative",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.derivative(),
exampleOptions: {
Linear: {
disabled: true,
},
Quadratic: {
customPoints: [
[30, 40],
[110, 50],
[120, 130],
],
},
Cubic: {
customPoints: [
[50, 50],
[60, 100],
[100, 140],
[140, 150],
],
},
},
},
{
name: "Tangent",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.tangent(options.t),
exampleOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
{
name: "Normal",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.normal(options.t),
exampleOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
{
name: "Curvature",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.curvature(options.t),
exampleOptions: {
Linear: {
disabled: true,
},
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
{
name: "Split",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.split(options.t),
exampleOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
{
name: "Trim",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.trim(options.t1, options.t2),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "t1",
min: 0,
max: 1,
step: 0.01,
default: 0.25,
},
{
variable: "t2",
min: 0,
max: 1,
step: 0.01,
default: 0.75,
},
],
},
},
},
{
name: "Project",
callback: (bezier: WasmBezierInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
mouseLocation ? bezier.project(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(),
triggerOnMouseMove: true,
},
{
name: "Local Extrema",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.local_extrema(),
exampleOptions: {
Quadratic: {
customPoints: [
[40, 40],
[160, 30],
[110, 150],
],
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 160],
],
},
},
},
{
name: "Bounding Box",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.bounding_box(),
},
{
name: "Inflections",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.inflections(),
exampleOptions: {
Linear: {
disabled: true,
},
Quadratic: {
disabled: true,
},
},
},
{
name: "Reduce",
callback: (bezier: WasmBezierInstance): string => bezier.reduce(),
},
{
name: "Offset",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.offset(options.distance),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance",
min: -50,
max: 50,
step: 1,
default: 20,
},
],
},
},
},
{
name: "Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.outline(options.distance),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance",
min: 0,
max: 50,
step: 1,
default: 20,
},
],
},
},
},
{
name: "Graduated Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.graduated_outline(options.start_distance, options.end_distance),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "start_distance",
min: 0,
max: 50,
step: 1,
default: 30,
},
{
variable: "end_distance",
min: 0,
max: 50,
step: 1,
default: 30,
},
],
},
},
customPoints: {
Cubic: [
[31, 94],
[40, 40],
[107, 107],
[106, 106],
],
},
},
{
name: "Skewed Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string =>
bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance1",
min: 0,
max: 50,
step: 1,
default: 20,
},
{
variable: "distance2",
min: 0,
max: 50,
step: 1,
default: 10,
},
{
variable: "distance3",
min: 0,
max: 50,
step: 1,
default: 30,
},
{
variable: "distance4",
min: 0,
max: 50,
step: 1,
default: 5,
},
],
},
},
},
{
name: "Arcs",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.arcs(options.error, options.max_iterations, options.strategy),
exampleOptions: ((): Omit<ExampleOptions, "Linear"> => {
const sliderOptions = [
{
variable: "strategy",
min: 0,
max: 2,
step: 1,
default: 0,
unit: [": Automatic", ": FavorLargerArcs", ": FavorCorrectness"],
},
{
variable: "error",
min: 0.05,
max: 1,
step: 0.05,
default: 0.5,
},
{
variable: "max_iterations",
min: 50,
max: 200,
step: 1,
default: 100,
},
];
return {
Quadratic: {
customPoints: [
[50, 50],
[85, 65],
[100, 100],
],
sliderOptions,
disabled: false,
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 160],
],
sliderOptions,
disabled: false,
},
};
})(),
},
{
name: "Intersect (Line Segment)",
callback: (bezier: WasmBezierInstance): string => {
const line = [
[150, 150],
[20, 20],
];
return bezier.intersect_line_segment(line);
},
},
{
name: "Intersect (Quadratic)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const quadratic = [
[20, 80],
[180, 10],
[90, 120],
];
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_seperation);
},
exampleOptions: {
Quadratic: {
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
},
},
},
{
name: "Intersect (Cubic)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const cubic = [
[40, 20],
[100, 40],
[40, 120],
[175, 140],
];
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_seperation);
},
exampleOptions: {
Quadratic: {
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
},
},
},
{
name: "Intersect (Self)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
exampleOptions: {
Quadratic: {
sliderOptions: [tErrorOptions],
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 140],
],
},
},
},
{
name: "Intersect (Rectangle)",
callback: (bezier: WasmBezierInstance): string =>
bezier.intersect_rectangle([
[50, 50],
[150, 150],
]),
},
{
name: "Rotate",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI, 100, 100),
exampleOptions: {
Quadratic: {
sliderOptions: [
{
variable: "angle",
min: 0,
max: 2,
step: 1 / 50,
default: 0.12,
unit: "π",
},
],
},
},
},
{
name: "De Casteljau Points",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.de_casteljau_points(options.t),
exampleOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
],
subpathFeatures: [
{
name: "Constructor",
callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
},
{
name: "Insert",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.insert(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
// TODO: Uncomment this after implementing the Euclidean version
// chooseComputeType: true,
},
{
name: "Length",
callback: (subpath: WasmSubpathInstance): string => subpath.length(),
},
{
name: "Evaluate",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.evaluate(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
chooseComputeType: true,
},
{
name: "Project",
callback: (subpath: WasmSubpathInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(),
triggerOnMouseMove: true,
},
{
name: "Intersect (Line Segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_line_segment([
[150, 150],
[20, 20],
]),
},
{
name: "Intersect (Quadratic segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_quadratic_segment([
[20, 80],
[180, 10],
[90, 120],
]),
},
{
name: "Intersect (Cubic segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_cubic_segment([
[40, 20],
[100, 40],
[40, 120],
[175, 140],
]),
},
],
};
},
components: {
BezierExamplePane,
SubpathExamplePane,
},
});
</script>

View File

@ -0,0 +1,120 @@
import { WasmBezier } from "@/../wasm/pkg";
import bezierFeatures, { BezierFeatureName } from "@/features/bezier-features";
import { renderDemo } from "@/utils/render";
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, ComputeType, Demo } from "@/utils/types";
const SELECTABLE_RANGE = 10;
// Given the number of points in the curve, map the index of a point to the correct manipulator key
const MANIPULATOR_KEYS_FROM_BEZIER_TYPE: { [key in BezierCurveType]: WasmBezierManipulatorKey[] } = {
Linear: ["set_start", "set_end"],
Quadratic: ["set_start", "set_handle_start", "set_end"],
Cubic: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
};
class BezierDemo extends HTMLElement implements Demo {
// Props
title!: string;
points!: number[][];
name!: BezierFeatureName;
sliderOptions!: SliderOption[];
triggerOnMouseMove!: boolean;
computeType!: ComputeType;
// Data
bezier!: WasmBezier;
callback!: BezierCallback;
manipulatorKeys!: WasmBezierManipulatorKey[];
activeIndex!: number | undefined;
sliderData!: Record<string, number>;
sliderUnits!: Record<string, string | string[]>;
static get observedAttributes(): string[] {
return ["computetype"];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
if (name === "computetype" && oldValue) {
this.computeType = (newValue || "Parametric") as ComputeType;
const figure = this.querySelector("figure") as HTMLElement;
this.drawDemo(figure);
}
}
connectedCallback(): void {
this.title = this.getAttribute("title") || "";
this.points = JSON.parse(this.getAttribute("points") || "[]");
this.name = this.getAttribute("name") as BezierFeatureName;
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.computeType = (this.getAttribute("computetype") || "Parametric") as ComputeType;
this.callback = bezierFeatures[this.name].callback as BezierCallback;
const curveType = getCurveType(this.points.length);
this.manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
this.bezier = WasmBezier[getConstructorKey(curveType)](this.points);
this.activeIndex = undefined as number | undefined;
this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit })));
this.render();
const figure = this.querySelector("figure") as HTMLElement;
this.drawDemo(figure);
}
render(): void {
renderDemo(this);
}
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
figure.innerHTML = this.callback(this.bezier, this.sliderData, mouseLocation, this.computeType);
}
onMouseDown(event: MouseEvent): void {
const mx = event.offsetX;
const my = event.offsetY;
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
const point = this.points[pointIndex];
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
this.activeIndex = pointIndex;
return;
}
}
}
onMouseUp(): void {
this.activeIndex = undefined;
}
onMouseMove(event: MouseEvent): void {
const mx = event.offsetX;
const my = event.offsetY;
const figure = event.currentTarget as HTMLElement;
if (this.activeIndex !== undefined) {
this.bezier[this.manipulatorKeys[this.activeIndex]](mx, my);
this.points[this.activeIndex] = [mx, my];
this.drawDemo(figure);
} else if (this.triggerOnMouseMove) {
this.drawDemo(figure, [mx, my]);
}
}
getSliderUnit(sliderValue: number, variable: string): string {
const sliderUnit = this.sliderUnits[variable];
return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || "";
}
}
export default BezierDemo;

View File

@ -0,0 +1,85 @@
import { BezierFeatureName } from "@/features/bezier-features";
import { renderDemoPane } from "@/utils/render";
import { BezierCurveType, BEZIER_CURVE_TYPE, ComputeType, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
const demoDefaults = {
Linear: {
points: [
[30, 60],
[140, 120],
],
},
Quadratic: {
points: [
[30, 50],
[140, 30],
[160, 170],
],
},
Cubic: {
points: [
[30, 30],
[60, 140],
[150, 30],
[160, 160],
],
},
};
class BezierDemoPane extends HTMLElement implements DemoPane {
// Props
name!: BezierFeatureName;
demoOptions!: BezierDemoOptions;
triggerOnMouseMove!: boolean;
chooseComputeType!: boolean;
// Data
demos!: BezierDemoArgs[];
id!: string;
computeType!: ComputeType;
connectedCallback(): void {
this.id = `${Math.random()}`.substring(2);
this.computeType = "Parametric";
this.name = (this.getAttribute("name") || "") as BezierFeatureName;
this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
// Use quadratic slider options as a default if sliders are not provided for the other curve types.
const defaultSliderOptions: SliderOption[] = this.demoOptions.Quadratic?.sliderOptions || [];
this.demos = BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => {
const givenData = this.demoOptions[curveType];
const defaultData = demoDefaults[curveType];
return {
title: curveType,
disabled: givenData?.disabled || false,
points: givenData?.customPoints || defaultData.points,
sliderOptions: givenData?.sliderOptions || defaultSliderOptions,
};
});
this.render();
}
render(): void {
renderDemoPane(this);
}
buildDemo(demo: BezierDemoArgs): Demo {
const bezierDemo = document.createElement("bezier-demo");
bezierDemo.setAttribute("title", demo.title);
bezierDemo.setAttribute("points", JSON.stringify(demo.points));
bezierDemo.setAttribute("name", this.name);
bezierDemo.setAttribute("sliderOptions", JSON.stringify(demo.sliderOptions));
bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
bezierDemo.setAttribute("computetype", this.computeType);
return bezierDemo;
}
}
export default BezierDemoPane;

View File

@ -1,97 +0,0 @@
<template>
<div>
<h4 class="example-header">{{ title }}</h4>
<figure @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove" class="example-figure" v-html="bezierSVG"></figure>
<div v-for="(slider, index) in sliderOptions" :key="index">
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
</div>
</div>
</template>
<style></style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { WasmBezier } from "@/../wasm/pkg";
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, WasmBezierManipulatorKey, SliderOption, ComputeType } from "@/utils/types";
const SELECTABLE_RANGE = 10;
// Given the number of points in the curve, map the index of a point to the correct manipulator key
const MANIPULATOR_KEYS_FROM_BEZIER_TYPE: { [key in BezierCurveType]: WasmBezierManipulatorKey[] } = {
Linear: ["set_start", "set_end"],
Quadratic: ["set_start", "set_handle_start", "set_end"],
Cubic: ["set_start", "set_handle_start", "set_handle_end", "set_end"],
};
export default defineComponent({
props: {
title: { type: String as PropType<string>, required: true },
points: { type: Array as PropType<Array<Array<number>>>, required: true, mutable: true },
callback: { type: Function as PropType<BezierCallback>, required: true },
sliderOptions: { type: Object as PropType<Array<SliderOption>>, default: () => ({}) },
triggerOnMouseMove: { type: Boolean as PropType<boolean>, default: false },
computeType: { type: String as PropType<ComputeType>, default: "Parametric" },
},
data() {
const curveType = getCurveType(this.points.length);
const manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];
const bezier = WasmBezier[getConstructorKey(curveType)](this.points);
const sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
return {
bezier,
bezierSVG: this.callback(bezier, sliderData, undefined, "Euclidean"),
manipulatorKeys,
activeIndex: undefined as number | undefined,
mutablePoints: JSON.parse(JSON.stringify(this.points)),
sliderData,
sliderUnits: Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit }))),
};
},
methods: {
onMouseDown(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
for (let pointIndex = 0; pointIndex < this.points.length; pointIndex += 1) {
const point = this.mutablePoints[pointIndex];
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
this.activeIndex = pointIndex;
return;
}
}
},
onMouseUp() {
this.activeIndex = undefined;
},
onMouseMove(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
if (this.activeIndex !== undefined) {
this.bezier[this.manipulatorKeys[this.activeIndex]](mx, my);
this.mutablePoints[this.activeIndex] = [mx, my];
this.bezierSVG = this.callback(this.bezier, this.sliderData, undefined, this.computeType);
} else if (this.triggerOnMouseMove) {
this.bezierSVG = this.callback(this.bezier, this.sliderData, [mx, my], this.computeType);
}
},
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
},
watch: {
sliderData: {
handler() {
this.bezierSVG = this.callback(this.bezier, this.sliderData, undefined, this.computeType);
},
deep: true,
},
computeType: {
handler() {
this.bezierSVG = this.callback(this.bezier, this.sliderData, undefined, this.computeType);
},
},
},
});
</script>

View File

@ -1,93 +0,0 @@
<template>
<div class="example-pane-container">
<h3 class="example-pane-header">{{ name }}</h3>
<div v-if="chooseComputeType" class="compute-type-choice">
<strong>ComputeType:</strong>
<input type="radio" :id="`${id}-parametric`" value="Parametric" v-model="computeTypeChoice" />
<label :for="`${id}-parametric`">Parametric</label>
<input type="radio" :id="`${id}-euclidean`" value="Euclidean" v-model="computeTypeChoice" />
<label :for="`${id}-euclidean`">Euclidean</label>
</div>
<div class="example-row">
<div v-for="(example, index) in examples" :key="index">
<BezierExample
v-if="!example.disabled"
:title="example.title"
:points="example.points"
:callback="callback"
:sliderOptions="example.sliderOptions"
:triggerOnMouseMove="triggerOnMouseMove"
:computeType="computeTypeChoice"
/>
</div>
</div>
</div>
</template>
<style></style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { BezierCallback, BezierCurveType, BEZIER_CURVE_TYPE, ComputeType, ExampleOptions, SliderOption } from "@/utils/types";
import BezierExample from "@/components/BezierExample.vue";
export default defineComponent({
props: {
name: { type: String as PropType<string>, required: true },
callback: { type: Function as PropType<BezierCallback>, required: true },
exampleOptions: { type: Object as PropType<ExampleOptions>, default: () => ({}) },
triggerOnMouseMove: { type: Boolean as PropType<boolean>, default: false },
chooseComputeType: { type: Boolean as PropType<boolean>, default: false },
},
data() {
const exampleDefaults = {
Linear: {
points: [
[30, 60],
[140, 120],
],
},
Quadratic: {
points: [
[30, 50],
[140, 30],
[160, 170],
],
},
Cubic: {
points: [
[30, 30],
[60, 140],
[150, 30],
[160, 160],
],
},
};
// Use quadratic slider options as a default if sliders are not provided for the other curve types.
const defaultSliderOptions: SliderOption[] = this.exampleOptions.Quadratic?.sliderOptions || [];
return {
examples: BEZIER_CURVE_TYPE.map((curveType: BezierCurveType) => {
const givenData = this.exampleOptions[curveType];
const defaultData = exampleDefaults[curveType];
return {
title: curveType,
disabled: givenData?.disabled || false,
points: givenData?.customPoints || defaultData.points,
sliderOptions: givenData?.sliderOptions || defaultSliderOptions,
};
}),
id: `${Math.random()}`.substring(2),
computeTypeChoice: "Parametric" as ComputeType,
};
},
components: {
BezierExample,
},
});
</script>

View File

@ -0,0 +1,116 @@
import { WasmSubpath } from "@/../wasm/pkg";
import subpathFeatures, { SubpathFeatureName } from "@/features/subpath-features";
import { renderDemo } from "@/utils/render";
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, ComputeType } from "@/utils/types";
const SELECTABLE_RANGE = 10;
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
class SubpathDemo extends HTMLElement {
// Props
title!: string;
triples!: (number[] | undefined)[][];
name!: SubpathFeatureName;
closed!: boolean;
sliderOptions!: SliderOption[];
triggerOnMouseMove!: boolean;
computeType!: ComputeType;
// Data
subpath!: WasmSubpath;
callback!: SubpathCallback;
manipulatorKeys!: WasmSubpathManipulatorKey[];
activeIndex!: number[] | undefined;
sliderData!: Record<string, number>;
sliderUnits!: Record<string, string | string[]>;
static get observedAttributes(): string[] {
return ["computetype"];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
if (name === "computetype" && oldValue) {
this.computeType = (newValue || "Parametric") as ComputeType;
const figure = this.querySelector("figure") as HTMLElement;
this.drawDemo(figure);
}
}
connectedCallback(): void {
this.title = this.getAttribute("title") || "";
this.triples = JSON.parse(this.getAttribute("triples") || "[]");
this.name = this.getAttribute("name") as SubpathFeatureName;
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.closed = this.getAttribute("closed") === "true";
this.computeType = (this.getAttribute("computetype") || "Parametric") as ComputeType;
this.callback = subpathFeatures[this.name].callback as SubpathCallback;
this.subpath = WasmSubpath.from_triples(this.triples, this.closed) as WasmSubpathInstance;
this.sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
this.sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit })));
this.render();
const figure = this.querySelector("figure") as HTMLElement;
this.drawDemo(figure);
}
render(): void {
renderDemo(this);
}
drawDemo(figure: HTMLElement, mouseLocation?: [number, number]): void {
figure.innerHTML = this.callback(this.subpath, this.sliderData, mouseLocation, this.computeType);
}
onMouseDown(event: MouseEvent): void {
const mx = event.offsetX;
const my = event.offsetY;
for (let controllerIndex = 0; controllerIndex < this.triples.length; controllerIndex += 1) {
for (let pointIndex = 0; pointIndex < 3; pointIndex += 1) {
const point = this.triples[controllerIndex][pointIndex];
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
this.activeIndex = [controllerIndex, pointIndex];
return;
}
}
}
}
onMouseUp(): void {
this.activeIndex = undefined;
}
onMouseMove(event: MouseEvent): void {
const mx = event.offsetX;
const my = event.offsetY;
const figure = event.currentTarget as HTMLElement;
if (this.activeIndex) {
this.subpath[POINT_INDEX_TO_MANIPULATOR[this.activeIndex[1]]](this.activeIndex[0], mx, my);
this.triples[this.activeIndex[0]][this.activeIndex[1]] = [mx, my];
this.drawDemo(figure);
} else if (this.triggerOnMouseMove) {
this.drawDemo(figure, [mx, my]);
}
}
getSliderUnit(sliderValue: number, variable: string): string {
const sliderUnit = this.sliderUnits[variable];
return (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit) || "";
}
}
export default SubpathDemo;

View File

@ -0,0 +1,76 @@
import { SubpathFeatureName } from "@/features/subpath-features";
import { renderDemoPane } from "@/utils/render";
import { ComputeType, Demo, DemoPane, SliderOption, SubpathDemoArgs } from "@/utils/types";
class SubpathDemoPane extends HTMLElement implements DemoPane {
// Props
name!: SubpathFeatureName;
sliderOptions!: SliderOption[];
triggerOnMouseMove!: boolean;
chooseComputeType!: boolean;
// Data
demos!: SubpathDemoArgs[];
id!: string;
computeType!: ComputeType;
connectedCallback(): void {
this.demos = [
{
title: "Open Subpath",
triples: [
[[20, 20], undefined, [10, 90]],
[[150, 40], [60, 40], undefined],
[[175, 175], undefined, undefined],
[[100, 100], [40, 120], undefined],
],
closed: false,
},
{
title: "Closed Subpath",
triples: [
[[35, 125], undefined, [40, 40]],
[[130, 30], [120, 120], undefined],
[
[145, 150],
[175, 90],
[70, 185],
],
],
closed: true,
},
];
this.id = `${Math.random()}`.substring(2);
this.computeType = "Parametric";
this.name = (this.getAttribute("name") || "") as SubpathFeatureName;
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
this.render();
}
render(): void {
renderDemoPane(this);
}
buildDemo(demo: SubpathDemoArgs): Demo {
const subpathDemo = document.createElement("subpath-demo");
subpathDemo.setAttribute("title", demo.title);
subpathDemo.setAttribute("triples", JSON.stringify(demo.triples));
subpathDemo.setAttribute("closed", String(demo.closed));
subpathDemo.setAttribute("name", this.name);
subpathDemo.setAttribute("sliderOptions", JSON.stringify(this.sliderOptions));
subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
subpathDemo.setAttribute("computetype", this.computeType);
return subpathDemo;
}
}
export default SubpathDemoPane;

View File

@ -1,92 +0,0 @@
<template>
<div>
<h4 class="example-header">{{ title }}</h4>
<figure @mousedown="onMouseDown" @mouseup="onMouseUp" @mousemove="onMouseMove" class="example-figure" v-html="subpathSVG"></figure>
<div v-for="(slider, index) in sliderOptions" :key="index">
<div class="slider-label">{{ slider.variable }} = {{ sliderData[slider.variable] }}{{ getSliderValue(sliderData[slider.variable], sliderUnits[slider.variable]) }}</div>
<input class="slider" v-model.number="sliderData[slider.variable]" type="range" :step="slider.step" :min="slider.min" :max="slider.max" />
</div>
</div>
</template>
<style></style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { WasmSubpath } from "@/../wasm/pkg";
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, ComputeType } from "@/utils/types";
const SELECTABLE_RANGE = 10;
const POINT_INDEX_TO_MANIPULATOR: WasmSubpathManipulatorKey[] = ["set_anchor", "set_in_handle", "set_out_handle"];
export default defineComponent({
props: {
title: { type: String as PropType<string>, required: true },
triples: { type: Array as PropType<Array<Array<number[] | undefined>>>, mutable: true, required: true },
closed: { type: Boolean as PropType<boolean>, default: false },
callback: { type: Function as PropType<SubpathCallback>, required: true },
sliderOptions: { type: Object as PropType<Array<SliderOption>>, default: () => ({}) },
triggerOnMouseMove: { type: Boolean as PropType<boolean>, default: false },
computeType: { type: String as PropType<ComputeType>, default: "Parametric" },
},
data() {
const subpath = WasmSubpath.from_triples(this.triples, this.closed) as WasmSubpathInstance;
const sliderData = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.default })));
const sliderUnits = Object.assign({}, ...this.sliderOptions.map((s) => ({ [s.variable]: s.unit })));
return {
subpath,
subpathSVG: this.callback(subpath, sliderData, undefined, "Euclidean"),
activeIndex: undefined as number[] | undefined,
mutableTriples: JSON.parse(JSON.stringify(this.triples)),
sliderData,
sliderUnits,
};
},
methods: {
onMouseDown(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
for (let controllerIndex = 0; controllerIndex < this.mutableTriples.length; controllerIndex += 1) {
for (let pointIndex = 0; pointIndex < 3; pointIndex += 1) {
const point = this.mutableTriples[controllerIndex][pointIndex];
if (point && Math.abs(mx - point[0]) < SELECTABLE_RANGE && Math.abs(my - point[1]) < SELECTABLE_RANGE) {
this.activeIndex = [controllerIndex, pointIndex];
return;
}
}
}
},
onMouseUp() {
this.activeIndex = undefined;
},
onMouseMove(event: MouseEvent) {
const mx = event.offsetX;
const my = event.offsetY;
if (this.activeIndex) {
this.subpath[POINT_INDEX_TO_MANIPULATOR[this.activeIndex[1]]](this.activeIndex[0], mx, my);
this.mutableTriples[this.activeIndex[0]][this.activeIndex[1]] = [mx, my];
this.subpathSVG = this.callback(this.subpath, this.sliderData, [mx, my], this.computeType);
} else if (this.triggerOnMouseMove) {
this.subpathSVG = this.callback(this.subpath, this.sliderData, [mx, my], this.computeType);
}
},
getSliderValue: (sliderValue: number, sliderUnit?: string | string[]) => (Array.isArray(sliderUnit) ? sliderUnit[sliderValue] : sliderUnit),
},
watch: {
sliderData: {
handler() {
this.subpathSVG = this.callback(this.subpath, this.sliderData, undefined, this.computeType);
},
deep: true,
},
computeType: {
handler() {
this.subpathSVG = this.callback(this.subpath, this.sliderData, undefined, this.computeType);
},
},
},
});
</script>

View File

@ -1,81 +0,0 @@
<template>
<div>
<h3 class="example-pane-header">{{ name }}</h3>
<div v-if="chooseComputeType" class="compute-type-choice">
<strong>ComputeType:</strong>
<input type="radio" :id="`${id}-parametric`" value="Parametric" v-model="computeTypeChoice" />
<label :for="`${id}-parametric`">Parametric</label>
<input type="radio" :id="`${id}-euclidean`" value="Euclidean" v-model="computeTypeChoice" />
<label :for="`${id}-euclidean`">Euclidean</label>
</div>
<div class="example-row">
<div v-for="(example, index) in examples" :key="index">
<SubpathExample
:title="example.title"
:triples="example.triples"
:closed="example.closed"
:callback="callback"
:sliderOptions="sliderOptions"
:triggerOnMouseMove="triggerOnMouseMove"
:computeType="computeTypeChoice"
/>
</div>
</div>
</div>
</template>
<style></style>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { SubpathCallback, SliderOption, ComputeType } from "@/utils/types";
import SubpathExample from "@/components/SubpathExample.vue";
export default defineComponent({
props: {
name: { type: String as PropType<string>, required: true },
callback: { type: Function as PropType<SubpathCallback>, required: true },
sliderOptions: { type: Array as PropType<Array<SliderOption>>, default: () => [] },
triggerOnMouseMove: { type: Boolean as PropType<boolean>, default: false },
chooseComputeType: { type: Boolean as PropType<boolean>, default: false },
},
data() {
return {
examples: [
{
title: "Open Subpath",
triples: [
[[20, 20], undefined, [10, 90]],
[[150, 40], [60, 40], undefined],
[[175, 175], undefined, undefined],
[[100, 100], [40, 120], undefined],
],
closed: false,
},
{
title: "Closed Subpath",
triples: [
[[35, 125], undefined, [40, 40]],
[[130, 30], [120, 120], undefined],
[
[145, 150],
[175, 90],
[70, 185],
],
],
closed: true,
},
],
id: `${Math.random()}`.substring(2),
computeTypeChoice: "Parametric" as ComputeType,
};
},
components: {
SubpathExample,
},
});
</script>

View File

@ -0,0 +1,461 @@
import { WasmBezier } from "@/../wasm/pkg";
import { tSliderOptions, tErrorOptions, tMinimumSeperationOptions } from "@/utils/options";
import { ComputeType, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types";
const bezierFeatures = {
Constructor: {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.to_svg(),
},
"Bezier Through Points": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const points = JSON.parse(bezier.get_points());
if (Object.values(options).length === 1) {
return WasmBezier.quadratic_through_points(points, options.t);
}
return WasmBezier.cubic_through_points(points, options.t, options["midpoint separation"]);
},
demoOptions: {
Linear: {
disabled: true,
},
Quadratic: {
customPoints: [
[30, 50],
[120, 70],
[160, 170],
],
sliderOptions: [
{
min: 0.01,
max: 0.99,
step: 0.01,
default: 0.5,
variable: "t",
},
],
},
Cubic: {
customPoints: [
[30, 50],
[120, 70],
[160, 170],
],
sliderOptions: [
{
min: 0.01,
max: 0.99,
step: 0.01,
default: 0.5,
variable: "t",
},
{
min: 0,
max: 100,
step: 2,
default: 30,
variable: "midpoint separation",
},
],
},
},
},
Length: {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.length(),
},
Evaluate: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => bezier.evaluate(options.computeArgument, computeType),
demoOptions: {
Quadratic: {
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
},
},
chooseComputeType: true,
},
"Lookup Table": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
demoOptions: {
Quadratic: {
sliderOptions: [
{
min: 2,
max: 15,
step: 1,
default: 5,
variable: "steps",
},
],
},
},
},
Derivative: {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.derivative(),
demoOptions: {
Linear: {
disabled: true,
},
Quadratic: {
customPoints: [
[30, 40],
[110, 50],
[120, 130],
],
},
Cubic: {
customPoints: [
[50, 50],
[60, 100],
[100, 140],
[140, 150],
],
},
},
},
Tangent: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.tangent(options.t),
demoOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
Normal: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.normal(options.t),
demoOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
Curvature: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.curvature(options.t),
demoOptions: {
Linear: {
disabled: true,
},
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
Split: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.split(options.t),
demoOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
Trim: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.trim(options.t1, options.t2),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "t1",
min: 0,
max: 1,
step: 0.01,
default: 0.25,
},
{
variable: "t2",
min: 0,
max: 1,
step: 0.01,
default: 0.75,
},
],
},
},
},
Project: {
callback: (bezier: WasmBezierInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
mouseLocation ? bezier.project(mouseLocation[0], mouseLocation[1]) : bezier.to_svg(),
triggerOnMouseMove: true,
},
"Local Extrema": {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.local_extrema(),
demoOptions: {
Quadratic: {
customPoints: [
[40, 40],
[160, 30],
[110, 150],
],
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 160],
],
},
},
},
"Bounding Box": {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.bounding_box(),
},
Inflections: {
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.inflections(),
demoOptions: {
Linear: {
disabled: true,
},
Quadratic: {
disabled: true,
},
},
},
Reduce: {
callback: (bezier: WasmBezierInstance): string => bezier.reduce(),
},
Offset: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.offset(options.distance),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance",
min: -50,
max: 50,
step: 1,
default: 20,
},
],
},
},
},
Outline: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.outline(options.distance),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance",
min: 0,
max: 50,
step: 1,
default: 20,
},
],
},
},
},
"Graduated Outline": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.graduated_outline(options.start_distance, options.end_distance),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "start_distance",
min: 0,
max: 50,
step: 1,
default: 30,
},
{
variable: "end_distance",
min: 0,
max: 50,
step: 1,
default: 30,
},
],
},
},
customPoints: {
Cubic: [
[31, 94],
[40, 40],
[107, 107],
[106, 106],
],
},
},
"Skewed Outline": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "distance1",
min: 0,
max: 50,
step: 1,
default: 20,
},
{
variable: "distance2",
min: 0,
max: 50,
step: 1,
default: 10,
},
{
variable: "distance3",
min: 0,
max: 50,
step: 1,
default: 30,
},
{
variable: "distance4",
min: 0,
max: 50,
step: 1,
default: 5,
},
],
},
},
},
Arcs: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.arcs(options.error, options.max_iterations, options.strategy),
demoOptions: ((): Omit<BezierDemoOptions, "Linear"> => {
const sliderOptions = [
{
variable: "strategy",
min: 0,
max: 2,
step: 1,
default: 0,
unit: [": Automatic", ": FavorLargerArcs", ": FavorCorrectness"],
},
{
variable: "error",
min: 0.05,
max: 1,
step: 0.05,
default: 0.5,
},
{
variable: "max_iterations",
min: 50,
max: 200,
step: 1,
default: 100,
},
];
return {
Quadratic: {
customPoints: [
[50, 50],
[85, 65],
[100, 100],
],
sliderOptions,
disabled: false,
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 160],
],
sliderOptions,
disabled: false,
},
};
})(),
},
"Intersect (Line Segment)": {
callback: (bezier: WasmBezierInstance): string => {
const line = [
[150, 150],
[20, 20],
];
return bezier.intersect_line_segment(line);
},
},
"Intersect (Quadratic)": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const quadratic = [
[20, 80],
[180, 10],
[90, 120],
];
return bezier.intersect_quadratic_segment(quadratic, options.error, options.minimum_seperation);
},
demoOptions: {
Quadratic: {
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
},
},
},
"Intersect (Cubic)": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const cubic = [
[40, 20],
[100, 40],
[40, 120],
[175, 140],
];
return bezier.intersect_cubic_segment(cubic, options.error, options.minimum_seperation);
},
demoOptions: {
Quadratic: {
sliderOptions: [tErrorOptions, tMinimumSeperationOptions],
},
},
},
"Intersect (Self)": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
demoOptions: {
Quadratic: {
sliderOptions: [tErrorOptions],
},
Cubic: {
customPoints: [
[160, 180],
[170, 10],
[30, 90],
[180, 140],
],
},
},
},
"Intersect (Rectangle)": {
callback: (bezier: WasmBezierInstance): string =>
bezier.intersect_rectangle([
[50, 50],
[150, 150],
]),
},
Rotate: {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI, 100, 100),
demoOptions: {
Quadratic: {
sliderOptions: [
{
variable: "angle",
min: 0,
max: 2,
step: 1 / 50,
default: 0.12,
unit: "π",
},
],
},
},
},
"De Casteljau Points": {
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.de_casteljau_points(options.t),
demoOptions: {
Quadratic: {
sliderOptions: [tSliderOptions],
},
},
},
};
export type BezierFeatureName = keyof typeof bezierFeatures;
export type BezierFeatureOptions = {
callback: BezierCallback;
demoOptions?: Partial<BezierDemoOptions>;
triggerOnMouseMove?: boolean;
chooseComputeType?: boolean;
};
export default bezierFeatures as Record<BezierFeatureName, BezierFeatureOptions>;

View File

@ -0,0 +1,60 @@
import { tSliderOptions } from "@/utils/options";
import { ComputeType, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types";
const subpathFeatures = {
Constructor: {
callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
},
Insert: {
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.insert(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
// TODO: Uncomment this after implementing the Euclidean version
// chooseComputeType: true,
},
Length: {
callback: (subpath: WasmSubpathInstance): string => subpath.length(),
},
Evaluate: {
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.evaluate(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
chooseComputeType: true,
},
Project: {
callback: (subpath: WasmSubpathInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(),
triggerOnMouseMove: true,
},
"Intersect (Line Segment)": {
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_line_segment([
[150, 150],
[20, 20],
]),
},
"Intersect (Quadratic segment)": {
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_quadratic_segment([
[20, 80],
[180, 10],
[90, 120],
]),
},
"Intersect (Cubic segment)": {
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_cubic_segment([
[40, 20],
[100, 40],
[40, 120],
[175, 140],
]),
},
};
export type SubpathFeatureName = keyof typeof subpathFeatures;
export type SubpathFeatureOptions = {
callback: SubpathCallback;
sliderOptions?: SliderOption[];
triggerOnMouseMove?: boolean;
chooseComputeType?: boolean;
};
export default subpathFeatures as Record<SubpathFeatureName, SubpathFeatureOptions>;

View File

@ -1,7 +1,62 @@
import { createApp } from "vue"; import BezierDemo from "@/components/BezierDemo";
import BezierDemoPane from "@/components/BezierDemoPane";
import SubpathDemo from "@/components/SubpathDemo";
import SubpathDemoPane from "@/components/SubpathDemoPane";
import App from "@/App.vue"; import bezierFeatures, { BezierFeatureName } from "@/features/bezier-features";
import subpathFeatures, { SubpathFeatureName } from "@/features/subpath-features";
document.title = "Bezier-rs Interactive Documentation"; import "@/style.css";
createApp(App).mount("#app"); window.document.title = "Bezier-rs Interactive Documentation";
window.document.body.innerHTML = `
<h1>Bezier-rs Interactive Documentation</h1>
<p>
This is the interactive documentation for the <a href="https://crates.io/crates/bezier-rs"><b>Bezier-rs</b></a> library. View the
<a href="https://docs.rs/bezier-rs/latest/bezier_rs">crate documentation</a>
for detailed function descriptions and API usage. Click and drag on the endpoints of the demo curves to visualize the various Bezier utilities and functions.
</p>
<h2>Beziers</h2>
<div id="bezier-demos"></div>
<h2>Subpaths</h2>
<div id="subpath-demos"></div>
`.trim();
declare global {
interface HTMLElementTagNameMap {
"bezier-demo": BezierDemo;
"bezier-demo-pane": BezierDemoPane;
"subpath-demo": SubpathDemo;
"subpath-demo-pane": SubpathDemoPane;
}
}
window.customElements.define("bezier-demo", BezierDemo);
window.customElements.define("bezier-demo-pane", BezierDemoPane);
window.customElements.define("subpath-demo", SubpathDemo);
window.customElements.define("subpath-demo-pane", SubpathDemoPane);
const bezierDemos = document.getElementById("bezier-demos");
(Object.keys(bezierFeatures) as BezierFeatureName[]).forEach((featureName) => {
const feature = bezierFeatures[featureName];
const demo = document.createElement("bezier-demo-pane");
demo.setAttribute("name", featureName);
demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {}));
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
bezierDemos?.append(demo);
});
const subpathDemos = document.getElementById("subpath-demos");
(Object.keys(subpathFeatures) as SubpathFeatureName[]).forEach((featureName) => {
const feature = subpathFeatures[featureName];
const demo = document.createElement("subpath-demo-pane");
demo.setAttribute("name", featureName);
demo.setAttribute("sliderOptions", JSON.stringify(feature.sliderOptions || []));
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
subpathDemos?.append(demo);
});

View File

@ -1,6 +0,0 @@
/* eslint-disable */
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -0,0 +1,52 @@
html,
body {
height: 100%;
font-family: Arial, sans-serif;
text-align: center;
margin: 40px 0;
}
body > h1 + p {
max-width: 768px;
line-height: 1.4;
margin: auto;
text-align: justify;
}
body > h2 {
margin-top: 40px;
}
/* Demo Pane styles */
.demo-row {
display: flex;
flex-direction: row;
justify-content: center;
}
.compute-type-choice {
margin-top: 20px;
}
.demo-pane-header {
margin-top: 2em;
margin-bottom: 0;
}
.demo-pane-container {
position: relative;
width: fit-content;
margin: auto;
}
/* Demo styles */
.demo-header {
margin: 20px 0;
}
.demo-figure {
width: 200px;
height: 200px;
margin-bottom: 20px;
border: solid 1px black;
}

View File

@ -0,0 +1,23 @@
export const tSliderOptions = {
min: 0,
max: 1,
step: 0.01,
default: 0.5,
variable: "t",
};
export const tErrorOptions = {
variable: "error",
min: 0.1,
max: 2,
step: 0.1,
default: 0.5,
};
export const tMinimumSeperationOptions = {
variable: "minimum_seperation",
min: 0.001,
max: 0.25,
step: 0.001,
default: 0.05,
};

View File

@ -0,0 +1,98 @@
import { ComputeType, Demo, DemoPane, SliderOption } from "@/utils/types";
export function renderDemo(demo: Demo): void {
const header = document.createElement("h4");
header.className = "demo-header";
header.innerText = demo.title;
const figure = document.createElement("figure");
figure.className = "demo-figure";
figure.addEventListener("mousedown", demo.onMouseDown.bind(demo));
figure.addEventListener("mouseup", demo.onMouseUp.bind(demo));
figure.addEventListener("mousemove", demo.onMouseMove.bind(demo));
demo.append(header);
demo.append(figure);
demo.sliderOptions.forEach((sliderOption: SliderOption) => {
const sliderLabel = document.createElement("div");
const sliderData = demo.sliderData[sliderOption.variable];
const sliderUnit = demo.getSliderUnit(sliderData, sliderOption.variable);
sliderLabel.className = "slider-label";
sliderLabel.innerText = `${sliderOption.variable} = ${sliderData}${sliderUnit}`;
demo.append(sliderLabel);
const sliderInput = document.createElement("input");
sliderInput.className = "slider-input";
sliderInput.type = "range";
sliderInput.max = String(sliderOption.max);
sliderInput.min = String(sliderOption.min);
sliderInput.step = String(sliderOption.step);
sliderInput.value = String(sliderOption.default);
sliderInput.addEventListener("input", (event: Event): void => {
demo.sliderData[sliderOption.variable] = Number((event.target as HTMLInputElement).value);
sliderLabel.innerText = `${sliderOption.variable} = ${demo.sliderData[sliderOption.variable]}${sliderUnit}`;
demo.drawDemo(figure);
});
demo.append(sliderInput);
});
}
export function renderDemoPane(demoPane: DemoPane): void {
const container = document.createElement("div");
container.className = "demo-pane-container";
const header = document.createElement("h3");
header.innerText = demoPane.name;
header.className = "demo-pane-header";
const computeTypeContainer = document.createElement("div");
computeTypeContainer.className = "compute-type-choice";
const computeTypeLabel = document.createElement("strong");
computeTypeLabel.innerText = "ComputeType:";
computeTypeContainer.append(computeTypeLabel);
const radioInputs = ["Parametric", "Euclidean"].map((computeType) => {
const id = `${demoPane.id}-${computeType}`;
const radioInput = document.createElement("input");
radioInput.type = "radio";
radioInput.id = id;
radioInput.value = computeType;
radioInput.name = "ComputeType";
radioInput.checked = computeType === "Parametric";
computeTypeContainer.append(radioInput);
const label = document.createElement("label");
label.htmlFor = id;
label.innerText = computeType;
computeTypeContainer.append(label);
return radioInput;
});
const demoRow = document.createElement("div");
demoRow.className = "demo-row";
demoPane.demos.forEach((demo) => {
if (demo.disabled) {
return;
}
const demoComponent = demoPane.buildDemo(demo);
radioInputs.forEach((radioInput: HTMLElement) => {
radioInput.addEventListener("input", (event: Event): void => {
demoPane.computeType = (event.target as HTMLInputElement).value as ComputeType;
demoComponent.setAttribute("computetype", demoPane.computeType);
});
});
demoRow.append(demoComponent);
});
container.append(header);
if (demoPane.chooseComputeType) {
container.append(computeTypeContainer);
}
container.append(demoRow);
demoPane.append(container);
}

View File

@ -16,11 +16,11 @@ export type ComputeType = "Euclidean" | "Parametric";
export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string; export type BezierCallback = (bezier: WasmBezierInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string;
export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string; export type SubpathCallback = (subpath: WasmSubpathInstance, options: Record<string, number>, mouseLocation?: [number, number], computeType?: ComputeType) => string;
export type ExampleOptions = { export type BezierDemoOptions = {
[key in BezierCurveType]: { [key in BezierCurveType]: {
disabled: boolean; disabled?: boolean;
sliderOptions: SliderOption[]; sliderOptions?: SliderOption[];
customPoints: number[][]; customPoints?: number[][];
}; };
}; };
@ -53,3 +53,39 @@ export function getConstructorKey(bezierCurveType: BezierCurveType): WasmBezierC
}; };
return mapping[bezierCurveType]; return mapping[bezierCurveType];
} }
export interface DemoArgs {
title: string;
disabled?: boolean;
}
export interface BezierDemoArgs extends DemoArgs {
points: number[][];
sliderOptions: SliderOption[];
}
export interface SubpathDemoArgs extends DemoArgs {
triples: (number[] | undefined)[][];
closed: boolean;
}
export interface Demo extends HTMLElement {
sliderOptions: SliderOption[];
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;
getSliderUnit(sliderValue: number, variable: string): string;
}
export interface DemoPane extends HTMLElement {
name: string;
demos: DemoArgs[];
id: string;
chooseComputeType: boolean;
computeType: ComputeType;
buildDemo(demo: DemoArgs): Demo;
}