odori 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ "use client";
2
+ import {
3
+ Fill,
4
+ SAMPLE_RATE,
5
+ cueSamples,
6
+ useBrand,
7
+ useDesignScale,
8
+ useFrame,
9
+ useVideo
10
+ } from "./chunk-2SROZSZM.js";
11
+ import "./chunk-PXG45HYV.js";
12
+
13
+ // src/cue-wave.tsx
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ var CueWave = ({ cue, label, columns = 120 }) => {
16
+ const frame = useFrame();
17
+ const brand = useBrand();
18
+ const scale = useDesignScale();
19
+ const { fps, durationInFrames } = useVideo();
20
+ const channel = cue.render({ samples: cueSamples(cue, fps), sampleRate: SAMPLE_RATE }).channels[0];
21
+ const width = Math.max(1, Math.floor(channel.length / columns));
22
+ const peaks = Array.from({ length: columns }, (_, index) => {
23
+ let highest = 0;
24
+ for (let sample = index * width; sample < Math.min(channel.length, (index + 1) * width); sample += 1) {
25
+ highest = Math.max(highest, Math.abs(channel[sample]));
26
+ }
27
+ return highest;
28
+ });
29
+ const progress = durationInFrames <= 1 ? 0 : frame / (durationInFrames - 1);
30
+ return /* @__PURE__ */ jsxs(Fill, { style: { alignItems: "center", gap: 34 * scale, justifyContent: "center", padding: 120 * scale }, children: [
31
+ /* @__PURE__ */ jsx("div", { style: { alignItems: "center", display: "flex", gap: 4 * scale, height: 260 * scale }, children: peaks.map((value, index) => /* @__PURE__ */ jsx(
32
+ "span",
33
+ {
34
+ style: {
35
+ background: index / columns <= progress ? brand.colors.accent : brand.colors.border,
36
+ borderRadius: 999,
37
+ height: Math.max(2 * scale, value * 260 * scale),
38
+ width: 6 * scale
39
+ }
40
+ },
41
+ index
42
+ )) }),
43
+ /* @__PURE__ */ jsxs("div", { style: { color: brand.colors.muted, fontFamily: brand.typography.mono, fontSize: 28 * scale }, children: [
44
+ label ?? cue.name,
45
+ " \xB7 ",
46
+ cue.durationInFrames,
47
+ " frames",
48
+ cue.loops ? " \xB7 loops" : ""
49
+ ] })
50
+ ] });
51
+ };
52
+
53
+ // src/preview-boundary.tsx
54
+ import { Component } from "react";
55
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
56
+ var sourceFrame = (stack) => {
57
+ if (!stack) return null;
58
+ for (const line of stack.split("\n")) {
59
+ const match = /\(?((?:\/|[A-Za-z]:\\)[^):]+\.(?:tsx|ts|jsx|js)):(\d+):(\d+)\)?/.exec(line);
60
+ if (match && !match[1].includes("node_modules")) return `${match[1]}:${match[2]}:${match[3]}`;
61
+ }
62
+ return null;
63
+ };
64
+ var openInEditor = (location) => {
65
+ void fetch(`/__open-in-editor?file=${encodeURIComponent(location)}`).catch(() => {
66
+ });
67
+ };
68
+ var PreviewErrorPanel = ({ error, componentStack, label }) => {
69
+ const location = sourceFrame(componentStack ?? null);
70
+ const [file] = location ? location.split(":") : [null];
71
+ return /* @__PURE__ */ jsxs2(
72
+ "div",
73
+ {
74
+ "data-odori-preview-error": true,
75
+ style: {
76
+ alignItems: "flex-start",
77
+ background: "#120d0d",
78
+ border: "1px solid #532",
79
+ borderRadius: 12,
80
+ color: "#f5eceb",
81
+ display: "flex",
82
+ flexDirection: "column",
83
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
84
+ fontSize: 13,
85
+ gap: 12,
86
+ inset: 0,
87
+ justifyContent: "center",
88
+ lineHeight: 1.6,
89
+ overflow: "auto",
90
+ padding: 24,
91
+ position: "absolute",
92
+ textAlign: "left"
93
+ },
94
+ children: [
95
+ /* @__PURE__ */ jsx2("strong", { style: { color: "#ff9b91", fontSize: 12, letterSpacing: "0.08em", textTransform: "uppercase" }, children: label ?? "This composition threw" }),
96
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 15 }, children: error.message }),
97
+ location ? /* @__PURE__ */ jsxs2(
98
+ "button",
99
+ {
100
+ type: "button",
101
+ onClick: () => openInEditor(location),
102
+ style: {
103
+ background: "transparent",
104
+ border: "1px solid #644",
105
+ borderRadius: 8,
106
+ color: "#f5eceb",
107
+ cursor: "pointer",
108
+ fontFamily: "inherit",
109
+ fontSize: 12,
110
+ padding: "6px 10px"
111
+ },
112
+ children: [
113
+ file?.split("/").slice(-2).join("/"),
114
+ location.slice(file?.length ?? 0)
115
+ ]
116
+ }
117
+ ) : null,
118
+ componentStack ? /* @__PURE__ */ jsx2("pre", { style: { color: "#c9b6b4", fontSize: 11, margin: 0, whiteSpace: "pre-wrap" }, children: componentStack.trim().split("\n").slice(0, 6).join("\n") }) : null,
119
+ /* @__PURE__ */ jsx2("span", { style: { color: "#c9b6b4", fontSize: 11 }, children: "Fix the file and save. The preview recovers on reload." })
120
+ ]
121
+ }
122
+ );
123
+ };
124
+ var PreviewBoundary = class extends Component {
125
+ state = { error: null, componentStack: null };
126
+ static getDerivedStateFromError(error) {
127
+ return { error };
128
+ }
129
+ componentDidUpdate(previous) {
130
+ if (previous.resetKey !== this.props.resetKey && this.state.error) {
131
+ this.setState({ error: null, componentStack: null });
132
+ }
133
+ }
134
+ componentDidCatch(error, info) {
135
+ this.setState({ componentStack: info.componentStack ?? null });
136
+ console.error(error);
137
+ }
138
+ render() {
139
+ const { error, componentStack } = this.state;
140
+ if (!error) return this.props.children;
141
+ return /* @__PURE__ */ jsx2(PreviewErrorPanel, { error, componentStack, label: this.props.label });
142
+ }
143
+ };
144
+
145
+ // src/preview.ts
146
+ var defineComponentPreview = (preview) => ({
147
+ kind: "odori-component-preview",
148
+ examples: [{ name: "Default", props: {} }],
149
+ ...preview
150
+ });
151
+ export {
152
+ CueWave,
153
+ PreviewBoundary,
154
+ defineComponentPreview
155
+ };
156
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cue-wave.tsx","../src/preview-boundary.tsx","../src/preview.ts"],"sourcesContent":["import {Fill} from \"./runtime\";\nimport {useBrand, useDesignScale, useFrame, useVideo} from \"./context\";\nimport {cueSamples, type CueDefinition} from \"./cue\";\nimport {SAMPLE_RATE} from \"./synth\";\n\nexport type CueWaveProps = {\n /** The score to draw. Its samples are rendered, not approximated. */\n cue: CueDefinition;\n /** Shown under the wave. Defaults to the name the cue registers. */\n label?: string;\n /** Bars across the canvas. More is finer, and slower to render. */\n columns?: number;\n};\n\n/**\n * A cue drawn from its own samples, with a playhead crossing it in time.\n *\n * A sound needs a picture on a page that cannot make noise until it is\n * clicked, and the honest picture is the waveform the score actually produces:\n * rendering is deterministic, so this is the sound rather than a decorative\n * squiggle. It lives in `odori/preview` because it is preview furniture — a\n * project owns the score it installed, not the chart of it, and fifteen cues\n * should not carry fifteen copies of this loop.\n */\nexport const CueWave = ({cue, label, columns = 120}: CueWaveProps) => {\n const frame = useFrame();\n const brand = useBrand();\n const scale = useDesignScale();\n const {fps, durationInFrames} = useVideo();\n\n const channel = cue.render({samples: cueSamples(cue, fps), sampleRate: SAMPLE_RATE}).channels[0];\n const width = Math.max(1, Math.floor(channel.length / columns));\n const peaks = Array.from({length: columns}, (_, index) => {\n let highest = 0;\n for (let sample = index * width; sample < Math.min(channel.length, (index + 1) * width); sample += 1) {\n highest = Math.max(highest, Math.abs(channel[sample]));\n }\n return highest;\n });\n\n const progress = durationInFrames <= 1 ? 0 : frame / (durationInFrames - 1);\n\n return (\n <Fill style={{alignItems: \"center\", gap: 34 * scale, justifyContent: \"center\", padding: 120 * scale}}>\n <div style={{alignItems: \"center\", display: \"flex\", gap: 4 * scale, height: 260 * scale}}>\n {peaks.map((value, index) => (\n <span\n key={index}\n style={{\n background: index / columns <= progress ? brand.colors.accent : brand.colors.border,\n borderRadius: 999,\n height: Math.max(2 * scale, value * 260 * scale),\n width: 6 * scale,\n }}\n />\n ))}\n </div>\n <div style={{color: brand.colors.muted, fontFamily: brand.typography.mono, fontSize: 28 * scale}}>\n {label ?? cue.name} · {cue.durationInFrames} frames{cue.loops ? \" · loops\" : \"\"}\n </div>\n </Fill>\n );\n};\n","\"use client\";\n\nimport {Component, type ErrorInfo, type ReactNode} from \"react\";\n\nexport type PreviewBoundaryProps = {\n children: ReactNode;\n /**\n * Changing this clears a caught error. Pass whatever identifies the thing\n * being previewed — an entry id, a format, a brand — so switching away from\n * a broken composition shows the next one instead of its predecessor's\n * error. A module edit clears it on its own, through the HMR hook below.\n */\n resetKey?: string | number;\n /** Shown above the message. Defaults to naming the composition generically. */\n label?: string;\n};\n\ntype State = {error: Error | null; componentStack: string | null};\n\n/** The first project file in a component stack, which is where an author looks. */\nexport const sourceFrame = (stack: string | null): string | null => {\n if (!stack) return null;\n for (const line of stack.split(\"\\n\")) {\n const match = /\\(?((?:\\/|[A-Za-z]:\\\\)[^):]+\\.(?:tsx|ts|jsx|js)):(\\d+):(\\d+)\\)?/.exec(line);\n if (match && !match[1].includes(\"node_modules\")) return `${match[1]}:${match[2]}:${match[3]}`;\n }\n return null;\n};\n\n/**\n * Vite's dev server exposes an editor hook. Studio and the docs site are both\n * Vite, so a file reference in this panel is a link to the line rather than a\n * path to copy by hand. It fails silently: an editor that will not open is not\n * worth a second error on top of the first.\n */\nconst openInEditor = (location: string) => {\n void fetch(`/__open-in-editor?file=${encodeURIComponent(location)}`).catch(() => {});\n};\n\nexport type PreviewErrorPanelProps = {\n error: Error;\n componentStack?: string | null;\n label?: string;\n};\n\n/** What a caught error looks like. Separated so it can be rendered and read. */\nexport const PreviewErrorPanel = ({error, componentStack, label}: PreviewErrorPanelProps) => {\n const location = sourceFrame(componentStack ?? null);\n const [file] = location ? location.split(\":\") : [null];\n\n return (\n <div\n data-odori-preview-error\n style={{\n alignItems: \"flex-start\",\n background: \"#120d0d\",\n border: \"1px solid #532\",\n borderRadius: 12,\n color: \"#f5eceb\",\n display: \"flex\",\n flexDirection: \"column\",\n fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\",\n fontSize: 13,\n gap: 12,\n inset: 0,\n justifyContent: \"center\",\n lineHeight: 1.6,\n overflow: \"auto\",\n padding: 24,\n position: \"absolute\",\n textAlign: \"left\",\n }}\n >\n <strong style={{color: \"#ff9b91\", fontSize: 12, letterSpacing: \"0.08em\", textTransform: \"uppercase\"}}>\n {label ?? \"This composition threw\"}\n </strong>\n <span style={{fontSize: 15}}>{error.message}</span>\n {location ? (\n <button\n type=\"button\"\n onClick={() => openInEditor(location)}\n style={{\n background: \"transparent\",\n border: \"1px solid #644\",\n borderRadius: 8,\n color: \"#f5eceb\",\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n fontSize: 12,\n padding: \"6px 10px\",\n }}\n >\n {file?.split(\"/\").slice(-2).join(\"/\")}\n {location.slice(file?.length ?? 0)}\n </button>\n ) : null}\n {componentStack ? (\n <pre style={{color: \"#c9b6b4\", fontSize: 11, margin: 0, whiteSpace: \"pre-wrap\"}}>\n {componentStack.trim().split(\"\\n\").slice(0, 6).join(\"\\n\")}\n </pre>\n ) : null}\n <span style={{color: \"#c9b6b4\", fontSize: 11}}>Fix the file and save. The preview recovers on reload.</span>\n </div>\n );\n};\n\n/**\n * The designed failure for a composition that throws.\n *\n * Preview only. A render worker must fail loudly and exit non-zero rather than\n * paint an apology into an exported frame, which is why this lives in\n * `odori/preview` and no export path imports it.\n *\n * Without it, one bad component takes the whole surface down and leaves\n * whatever React last painted, with the real message in a console the author\n * may not have open.\n */\nexport class PreviewBoundary extends Component<PreviewBoundaryProps, State> {\n state: State = {error: null, componentStack: null};\n\n static getDerivedStateFromError(error: Error): Partial<State> {\n return {error};\n }\n\n componentDidUpdate(previous: PreviewBoundaryProps) {\n if (previous.resetKey !== this.props.resetKey && this.state.error) {\n this.setState({error: null, componentStack: null});\n }\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n this.setState({componentStack: info.componentStack ?? null});\n // Still report it: the panel is for the person watching, the console is\n // for the person debugging, and they are not always looking at the same\n // thing.\n console.error(error);\n }\n\n render() {\n const {error, componentStack} = this.state;\n if (!error) return this.props.children;\n return <PreviewErrorPanel error={error} componentStack={componentStack} label={this.props.label} />;\n }\n}\n","import {type ComponentType} from \"react\";\nimport {type Duration} from \"./time\";\nimport {type FieldDescriptor} from \"./schema\";\nimport {type Brand} from \"./brand\";\n\nexport type ComponentPreview<Props extends object = Record<string, unknown>> = {\n readonly kind: \"odori-component-preview\";\n title: string;\n category: string;\n description?: string;\n component: ComponentType<Props>;\n canvas: {width: number; height: number; duration: Duration};\n controls?: Record<string, FieldDescriptor>;\n brands?: Brand[];\n examples: Array<{name: string; props: Partial<Props>}>;\n};\n\nexport type ComponentPreviewInput<Props extends object> = Omit<ComponentPreview<Props>, \"kind\" | \"examples\"> & {\n examples?: Array<{name: string; props: Partial<Props>}>;\n};\n\n/**\n * A development-only fixture. Production bundles never import preview modules,\n * so this contract can be as rich as Studio needs.\n */\nexport const defineComponentPreview = <Props extends object>(\n preview: ComponentPreviewInput<Props>,\n): ComponentPreview<Props> => ({\n kind: \"odori-component-preview\",\n examples: [{name: \"Default\", props: {} as Partial<Props>}],\n ...preview,\n});\n\nexport {CueWave, type CueWaveProps} from \"./cue-wave\";\nexport {PreviewBoundary, type PreviewBoundaryProps} from \"./preview-boundary\";\n"],"mappings":"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","names":["jsx","jsxs"]}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A tiny serializable input contract.
3
+ *
4
+ * Odori needs three things from a schema: validation with defaults, a JSON
5
+ * description Studio can turn into controls, and zero runtime dependencies.
6
+ * Any zod-compatible object with `parse()` is also accepted.
7
+ */
8
+ type FieldDescriptor = {
9
+ type: "text";
10
+ defaultValue: string;
11
+ maxLength?: number;
12
+ multiline?: boolean;
13
+ } | {
14
+ type: "number";
15
+ defaultValue: number;
16
+ min?: number;
17
+ max?: number;
18
+ step?: number;
19
+ } | {
20
+ type: "boolean";
21
+ defaultValue: boolean;
22
+ } | {
23
+ type: "select";
24
+ defaultValue: string;
25
+ options: string[];
26
+ } | {
27
+ type: "color";
28
+ defaultValue: string;
29
+ } | {
30
+ type: "json";
31
+ defaultValue: unknown;
32
+ };
33
+ type InputSchema<Value = Record<string, unknown>> = {
34
+ readonly kind: "odori-schema";
35
+ readonly fields: Record<string, FieldDescriptor>;
36
+ parse(input: unknown): Value;
37
+ safeParse(input: unknown): {
38
+ success: true;
39
+ data: Value;
40
+ } | {
41
+ success: false;
42
+ issues: string[];
43
+ };
44
+ defaults(): Value;
45
+ describe(): Record<string, FieldDescriptor>;
46
+ };
47
+ type ParsableSchema<Value = unknown> = InputSchema<Value> | {
48
+ parse(input: unknown): Value;
49
+ };
50
+ declare const defineInputSchema: <Fields extends Record<string, FieldDescriptor>>(fields: Fields) => InputSchema<Record<string, unknown>>;
51
+ declare const isOdoriSchema: (schema: unknown) => schema is InputSchema;
52
+ declare const parseWithSchema: <Value>(schema: ParsableSchema<Value> | undefined, input: unknown) => Value;
53
+
54
+ export { type FieldDescriptor as F, type InputSchema as I, type ParsableSchema as P, defineInputSchema as d, isOdoriSchema as i, parseWithSchema as p };
package/package.json CHANGED
@@ -1,8 +1,55 @@
1
1
  {
2
2
  "name": "odori",
3
- "version": "0.0.1",
4
- "description": "Reserved. A code-first video framework — work in progress.",
5
- "license": "MIT",
6
- "keywords": ["video", "motion", "framework"],
7
- "files": ["README.md"]
8
- }
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "description": "An independent React video framework with a first-class videos source root, deterministic frame runtime, timeline compiler, player, and render manifest.",
6
+ "license": "Apache-2.0",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./preview": {
13
+ "types": "./dist/preview.d.ts",
14
+ "import": "./dist/preview.js"
15
+ },
16
+ "./manifest": {
17
+ "types": "./dist/manifest.d.ts",
18
+ "import": "./dist/manifest.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": "^19.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "22.19.0",
30
+ "@types/react": "19.2.7",
31
+ "@types/react-dom": "19.2.3",
32
+ "react": "19.2.3",
33
+ "react-dom": "19.2.3",
34
+ "tsup": "^8.5.1",
35
+ "tsx": "4.20.5",
36
+ "typescript": "5.9.3"
37
+ },
38
+ "keywords": [
39
+ "video",
40
+ "react",
41
+ "framework",
42
+ "motion",
43
+ "render"
44
+ ],
45
+ "sideEffects": false,
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "typecheck": "tsc --noEmit"
52
+ },
53
+ "main": "./dist/index.js",
54
+ "types": "./dist/index.d.ts"
55
+ }