Bezier-rs: Add hash URLs for individual API examples (#968)

* Handle displaying single demos

* Address comments

* Update eslintrc

* Add hash links

* Use hash instead of pathname

* Address small nits

* Change url format

* CSS improvements

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Hannah Li 2023-02-08 23:32:28 -05:00 committed by Keavon Chambers
parent 875f2a5cd1
commit ff51098c11
10 changed files with 209 additions and 87 deletions

View file

@ -53,7 +53,7 @@ module.exports = {
rules: {
// Standard ESLint config
indent: "off",
quotes: ["error", "double"],
quotes: ["error", "double", { allowTemplateLiterals: true }],
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"],

View file

@ -1,5 +1,5 @@
import { WasmBezier } from "@/../wasm/pkg";
import bezierFeatures, { BezierFeatureName } from "@/features/bezier-features";
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
import { renderDemo } from "@/utils/render";
import { getConstructorKey, getCurveType, BezierCallback, BezierCurveType, SliderOption, WasmBezierManipulatorKey, ComputeType, Demo } from "@/utils/types";
@ -18,7 +18,7 @@ class BezierDemo extends HTMLElement implements Demo {
points!: number[][];
name!: BezierFeatureName;
key!: BezierFeatureKey;
sliderOptions!: SliderOption[];
@ -54,12 +54,12 @@ class BezierDemo extends HTMLElement implements Demo {
connectedCallback(): void {
this.title = this.getAttribute("title") || "";
this.points = JSON.parse(this.getAttribute("points") || "[]");
this.name = this.getAttribute("name") as BezierFeatureName;
this.key = this.getAttribute("key") as BezierFeatureKey;
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;
this.callback = bezierFeatures[this.key].callback as BezierCallback;
const curveType = getCurveType(this.points.length);
this.manipulatorKeys = MANIPULATOR_KEYS_FROM_BEZIER_TYPE[curveType];

View file

@ -1,4 +1,4 @@
import { BezierFeatureName } from "@/features/bezier-features";
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
import { renderDemoPane } from "@/utils/render";
import { BezierCurveType, BEZIER_CURVE_TYPE, ComputeType, BezierDemoOptions, SliderOption, Demo, DemoPane, BezierDemoArgs } from "@/utils/types";
@ -28,7 +28,9 @@ const demoDefaults = {
class BezierDemoPane extends HTMLElement implements DemoPane {
// Props
name!: BezierFeatureName;
key!: BezierFeatureKey;
name!: string;
demoOptions!: BezierDemoOptions;
@ -44,10 +46,11 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
computeType!: ComputeType;
connectedCallback(): void {
this.id = `${Math.random()}`.substring(2);
this.computeType = "Parametric";
this.name = (this.getAttribute("name") || "") as BezierFeatureName;
this.key = (this.getAttribute("name") || "") as BezierFeatureKey;
this.id = `bezier/${this.key}`;
this.name = bezierFeatures[this.key].name;
this.demoOptions = JSON.parse(this.getAttribute("demoOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
@ -74,7 +77,7 @@ class BezierDemoPane extends HTMLElement implements DemoPane {
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("key", this.key);
bezierDemo.setAttribute("sliderOptions", JSON.stringify(demo.sliderOptions));
bezierDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
bezierDemo.setAttribute("computetype", this.computeType);

View file

@ -1,5 +1,5 @@
import { WasmSubpath } from "@/../wasm/pkg";
import subpathFeatures, { SubpathFeatureName } from "@/features/subpath-features";
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
import { renderDemo } from "@/utils/render";
import { SubpathCallback, WasmSubpathInstance, WasmSubpathManipulatorKey, SliderOption, ComputeType } from "@/utils/types";
@ -13,7 +13,7 @@ class SubpathDemo extends HTMLElement {
triples!: (number[] | undefined)[][];
name!: SubpathFeatureName;
key!: SubpathFeatureKey;
closed!: boolean;
@ -51,13 +51,13 @@ class SubpathDemo extends HTMLElement {
connectedCallback(): void {
this.title = this.getAttribute("title") || "";
this.triples = JSON.parse(this.getAttribute("triples") || "[]");
this.name = this.getAttribute("name") as SubpathFeatureName;
this.key = this.getAttribute("key") as SubpathFeatureKey;
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.callback = subpathFeatures[this.key].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 })));

View file

@ -1,10 +1,12 @@
import { SubpathFeatureName } from "@/features/subpath-features";
import subpathFeatures, { SubpathFeatureKey } 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;
key!: SubpathFeatureKey;
name!: string;
sliderOptions!: SliderOption[];
@ -45,10 +47,11 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
closed: true,
},
];
this.id = `${Math.random()}`.substring(2);
this.computeType = "Parametric";
this.name = (this.getAttribute("name") || "") as SubpathFeatureName;
this.key = (this.getAttribute("name") || "") as SubpathFeatureKey;
this.id = `subpath/${this.key}`;
this.name = subpathFeatures[this.key].name;
this.sliderOptions = JSON.parse(this.getAttribute("sliderOptions") || "[]");
this.triggerOnMouseMove = this.getAttribute("triggerOnMouseMove") === "true";
this.chooseComputeType = this.getAttribute("chooseComputeType") === "true";
@ -65,7 +68,7 @@ class SubpathDemoPane extends HTMLElement implements DemoPane {
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("key", this.key);
subpathDemo.setAttribute("sliderOptions", JSON.stringify(this.sliderOptions));
subpathDemo.setAttribute("triggerOnMouseMove", String(this.triggerOnMouseMove));
subpathDemo.setAttribute("computetype", this.computeType);

View file

@ -3,10 +3,12 @@ import { tSliderOptions, tErrorOptions, tMinimumSeperationOptions } from "@/util
import { ComputeType, BezierDemoOptions, WasmBezierInstance, BezierCallback } from "@/utils/types";
const bezierFeatures = {
Constructor: {
constructor: {
name: "Constructor",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.to_svg(),
},
"Bezier Through Points": {
"bezier-through-points": {
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) {
@ -59,10 +61,12 @@ const bezierFeatures = {
},
},
},
Length: {
length: {
name: "Length",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.length(),
},
Evaluate: {
evaluate: {
name: "Evaluate",
callback: (bezier: WasmBezierInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => bezier.evaluate(options.computeArgument, computeType),
demoOptions: {
Quadratic: {
@ -71,7 +75,8 @@ const bezierFeatures = {
},
chooseComputeType: true,
},
"Lookup Table": {
"lookup-table": {
name: "Lookup Table",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.compute_lookup_table(options.steps),
demoOptions: {
Quadratic: {
@ -87,7 +92,8 @@ const bezierFeatures = {
},
},
},
Derivative: {
derivative: {
name: "Derivative",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.derivative(),
demoOptions: {
Linear: {
@ -110,7 +116,8 @@ const bezierFeatures = {
},
},
},
Tangent: {
tangent: {
name: "Tangent",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.tangent(options.t),
demoOptions: {
Quadratic: {
@ -118,7 +125,8 @@ const bezierFeatures = {
},
},
},
Normal: {
normal: {
name: "Normal",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.normal(options.t),
demoOptions: {
Quadratic: {
@ -126,7 +134,8 @@ const bezierFeatures = {
},
},
},
Curvature: {
curvature: {
name: "Curvature",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.curvature(options.t),
demoOptions: {
Linear: {
@ -137,7 +146,8 @@ const bezierFeatures = {
},
},
},
Split: {
split: {
name: "Split",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.split(options.t),
demoOptions: {
Quadratic: {
@ -145,7 +155,8 @@ const bezierFeatures = {
},
},
},
Trim: {
trim: {
name: "Trim",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.trim(options.t1, options.t2),
demoOptions: {
Quadratic: {
@ -168,12 +179,14 @@ const bezierFeatures = {
},
},
},
Project: {
project: {
name: "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": {
"local-extrema": {
name: "Local Extrema",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.local_extrema(),
demoOptions: {
Quadratic: {
@ -193,10 +206,12 @@ const bezierFeatures = {
},
},
},
"Bounding Box": {
"bounding-box": {
name: "Bounding Box",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.bounding_box(),
},
Inflections: {
inflections: {
name: "Inflections",
callback: (bezier: WasmBezierInstance, _: Record<string, number>): string => bezier.inflections(),
demoOptions: {
Linear: {
@ -207,10 +222,12 @@ const bezierFeatures = {
},
},
},
Reduce: {
reduce: {
name: "Reduce",
callback: (bezier: WasmBezierInstance): string => bezier.reduce(),
},
Offset: {
offset: {
name: "Offset",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.offset(options.distance),
demoOptions: {
Quadratic: {
@ -226,7 +243,8 @@ const bezierFeatures = {
},
},
},
Outline: {
outline: {
name: "Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.outline(options.distance),
demoOptions: {
Quadratic: {
@ -242,7 +260,8 @@ const bezierFeatures = {
},
},
},
"Graduated Outline": {
"graduated-outline": {
name: "Graduated Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.graduated_outline(options.start_distance, options.end_distance),
demoOptions: {
Quadratic: {
@ -273,7 +292,8 @@ const bezierFeatures = {
],
},
},
"Skewed Outline": {
"skewed-outline": {
name: "Skewed Outline",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.skewed_outline(options.distance1, options.distance2, options.distance3, options.distance4),
demoOptions: {
Quadratic: {
@ -310,7 +330,8 @@ const bezierFeatures = {
},
},
},
Arcs: {
arcs: {
name: "Arcs",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.arcs(options.error, options.max_iterations, options.strategy),
demoOptions: ((): Omit<BezierDemoOptions, "Linear"> => {
const sliderOptions = [
@ -361,7 +382,8 @@ const bezierFeatures = {
};
})(),
},
"Intersect (Line Segment)": {
"intersect-linear": {
name: "Intersect (Line Segment)",
callback: (bezier: WasmBezierInstance): string => {
const line = [
[150, 150],
@ -370,7 +392,8 @@ const bezierFeatures = {
return bezier.intersect_line_segment(line);
},
},
"Intersect (Quadratic)": {
"intersect-quadratic": {
name: "Intersect (Quadratic)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const quadratic = [
[20, 80],
@ -385,7 +408,8 @@ const bezierFeatures = {
},
},
},
"Intersect (Cubic)": {
"intersect-cubic": {
name: "Intersect (Cubic)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => {
const cubic = [
[40, 20],
@ -401,7 +425,8 @@ const bezierFeatures = {
},
},
},
"Intersect (Self)": {
"intersect-self": {
name: "Intersect (Self)",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.intersect_self(options.error),
demoOptions: {
Quadratic: {
@ -417,14 +442,16 @@ const bezierFeatures = {
},
},
},
"Intersect (Rectangle)": {
"intersect-rectangle": {
name: "Intersect (Rectangle)",
callback: (bezier: WasmBezierInstance): string =>
bezier.intersect_rectangle([
[50, 50],
[150, 150],
]),
},
Rotate: {
rotate: {
name: "Rotate",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.rotate(options.angle * Math.PI, 100, 100),
demoOptions: {
Quadratic: {
@ -441,7 +468,8 @@ const bezierFeatures = {
},
},
},
"De Casteljau Points": {
"de-casteljau-points": {
name: "De Casteljau Points",
callback: (bezier: WasmBezierInstance, options: Record<string, number>): string => bezier.de_casteljau_points(options.t),
demoOptions: {
Quadratic: {
@ -451,11 +479,12 @@ const bezierFeatures = {
},
};
export type BezierFeatureName = keyof typeof bezierFeatures;
export type BezierFeatureKey = keyof typeof bezierFeatures;
export type BezierFeatureOptions = {
name: string;
callback: BezierCallback;
demoOptions?: Partial<BezierDemoOptions>;
triggerOnMouseMove?: boolean;
chooseComputeType?: boolean;
};
export default bezierFeatures as Record<BezierFeatureName, BezierFeatureOptions>;
export default bezierFeatures as Record<BezierFeatureKey, BezierFeatureOptions>;

View file

@ -2,44 +2,53 @@ import { tSliderOptions } from "@/utils/options";
import { ComputeType, SliderOption, SubpathCallback, WasmSubpathInstance } from "@/utils/types";
const subpathFeatures = {
Constructor: {
constructor: {
name: "Constructor",
callback: (subpath: WasmSubpathInstance): string => subpath.to_svg(),
},
Insert: {
insert: {
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,
},
Length: {
length: {
name: "Length",
callback: (subpath: WasmSubpathInstance): string => subpath.length(),
},
Evaluate: {
evaluate: {
name: "Evaluate",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.evaluate(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
chooseComputeType: true,
},
Project: {
project: {
name: "Project",
callback: (subpath: WasmSubpathInstance, _: Record<string, number>, mouseLocation?: [number, number]): string =>
mouseLocation ? subpath.project(mouseLocation[0], mouseLocation[1]) : subpath.to_svg(),
triggerOnMouseMove: true,
},
Tangent: {
tangent: {
name: "Tangent",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.tangent(options.t),
sliderOptions: [tSliderOptions],
},
Normal: {
normal: {
name: "Normal",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>): string => subpath.normal(options.t),
sliderOptions: [tSliderOptions],
},
"Intersect (Line Segment)": {
"intersect-linear": {
name: "Intersect (Line Segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_line_segment([
[150, 150],
[20, 20],
]),
},
"Intersect (Quadratic segment)": {
"intersect-quadratic": {
name: "Intersect (Quadratic segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_quadratic_segment([
[20, 80],
@ -47,7 +56,8 @@ const subpathFeatures = {
[90, 120],
]),
},
"Intersect (Cubic segment)": {
"intersect-cubic": {
name: "Intersect (Cubic segment)",
callback: (subpath: WasmSubpathInstance): string =>
subpath.intersect_cubic_segment([
[40, 20],
@ -56,7 +66,8 @@ const subpathFeatures = {
[175, 140],
]),
},
Split: {
split: {
name: "Split",
callback: (subpath: WasmSubpathInstance, options: Record<string, number>, _: undefined, computeType: ComputeType): string => subpath.split(options.computeArgument, computeType),
sliderOptions: [{ ...tSliderOptions, variable: "computeArgument" }],
// TODO: Uncomment this after implementing the Euclidean version
@ -64,11 +75,12 @@ const subpathFeatures = {
},
};
export type SubpathFeatureName = keyof typeof subpathFeatures;
export type SubpathFeatureKey = keyof typeof subpathFeatures;
export type SubpathFeatureOptions = {
name: string;
callback: SubpathCallback;
sliderOptions?: SliderOption[];
triggerOnMouseMove?: boolean;
chooseComputeType?: boolean;
};
export default subpathFeatures as Record<SubpathFeatureName, SubpathFeatureOptions>;
export default subpathFeatures as Record<SubpathFeatureKey, SubpathFeatureOptions>;

View file

@ -3,26 +3,11 @@ import BezierDemoPane from "@/components/BezierDemoPane";
import SubpathDemo from "@/components/SubpathDemo";
import SubpathDemoPane from "@/components/SubpathDemoPane";
import bezierFeatures, { BezierFeatureName } from "@/features/bezier-features";
import subpathFeatures, { SubpathFeatureName } from "@/features/subpath-features";
import bezierFeatures, { BezierFeatureKey } from "@/features/bezier-features";
import subpathFeatures, { SubpathFeatureKey } from "@/features/subpath-features";
import "@/style.css";
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;
@ -32,13 +17,14 @@ declare global {
}
}
window.document.title = "Bezier-rs Interactive Documentation";
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) => {
function renderBezierPane(featureName: BezierFeatureKey, container: HTMLElement | null): void {
const feature = bezierFeatures[featureName];
const demo = document.createElement("bezier-demo-pane");
@ -46,11 +32,10 @@ const bezierDemos = document.getElementById("bezier-demos");
demo.setAttribute("demoOptions", JSON.stringify(feature.demoOptions || {}));
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
bezierDemos?.append(demo);
});
container?.append(demo);
}
const subpathDemos = document.getElementById("subpath-demos");
(Object.keys(subpathFeatures) as SubpathFeatureName[]).forEach((featureName) => {
function renderSubpathPane(featureName: SubpathFeatureKey, container: HTMLElement | null): void {
const feature = subpathFeatures[featureName];
const demo = document.createElement("subpath-demo-pane");
@ -58,5 +43,66 @@ const subpathDemos = document.getElementById("subpath-demos");
demo.setAttribute("sliderOptions", JSON.stringify(feature.sliderOptions || []));
demo.setAttribute("triggerOnMouseMove", String(feature.triggerOnMouseMove));
demo.setAttribute("chooseComputeType", String(feature.chooseComputeType));
subpathDemos?.append(demo);
container?.append(demo);
}
function isUrlSolo(url: string): boolean {
const hash = url.split("#")?.[1];
const splitHash = hash?.split("/");
return splitHash?.length === 3 && splitHash?.[2] === "solo";
}
window.addEventListener("hashchange", (e: Event): void => {
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(): void {
const hash = window.location.hash;
const splitHash = hash.split("/");
// Determine which examples to render based on hash
if (splitHash[0] === "#bezier" && splitHash[1] in bezierFeatures && splitHash[2] === "solo") {
window.document.body.innerHTML = `<div id="bezier-demos"></div>`;
renderBezierPane(splitHash[1] as BezierFeatureKey, document.getElementById("bezier-demos"));
} else if (splitHash[0] === "#subpath" && splitHash[1] in subpathFeatures && splitHash[2] === "solo") {
window.document.body.innerHTML = `<div id="subpath-demos"></div>`;
renderSubpathPane(splitHash[1] as SubpathFeatureKey, document.getElementById("subpath-demos"));
} else {
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();
const bezierDemos = document.getElementById("bezier-demos");
const subpathDemos = document.getElementById("subpath-demos");
(Object.keys(bezierFeatures) as BezierFeatureKey[]).forEach((feature) => renderBezierPane(feature, bezierDemos));
(Object.keys(subpathFeatures) as SubpathFeatureKey[]).forEach((feature) => renderSubpathPane(feature, subpathDemos));
}
// Scroll to specified hash if it exists
if (hash) {
const target = document.getElementById(hash.substring(1));
if (target) {
target.scrollIntoView();
}
}
}
renderExamples();

View file

@ -1,11 +1,17 @@
html,
body {
height: 100%;
font-family: Arial, sans-serif;
text-align: center;
}
body > h1 {
margin: 40px 0;
}
body > h1 ~ :last-child {
margin-bottom: 40px;
}
body > h1 + p {
max-width: 768px;
line-height: 1.4;
@ -29,8 +35,24 @@ body > h2 {
}
.demo-pane-header {
display: inline-block;
position: relative;
margin-top: 2em;
margin-bottom: 0;
padding: 0 1em;
}
.demo-pane-header a {
display: none;
position: absolute;
left: 0;
text-decoration: none;
color: inherit;
opacity: 0.5;
}
.demo-pane-header:hover a {
display: inline-block;
}
.demo-pane-container {

View file

@ -42,9 +42,16 @@ export function renderDemoPane(demoPane: DemoPane): void {
const container = document.createElement("div");
container.className = "demo-pane-container";
const headerAnchorLink = document.createElement("a");
headerAnchorLink.innerText = "#";
const currentHash = window.location.hash.split("/");
// Add href anchor if not on a solo example page
if (currentHash.length !== 3 && currentHash[2] !== "solo") headerAnchorLink.href = `#${demoPane.id}`;
const header = document.createElement("h3");
header.innerText = demoPane.name;
header.className = "demo-pane-header";
header.append(headerAnchorLink);
const computeTypeContainer = document.createElement("div");
computeTypeContainer.className = "compute-type-choice";