egaki 0.7.2 → 0.9.0
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.
- package/CHANGELOG.md +52 -0
- package/dist/cli/bpm-detect.d.ts +7 -0
- package/dist/cli/bpm-detect.d.ts.map +1 -0
- package/dist/cli/bpm-detect.js +123 -0
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +115 -0
- package/dist/cli/loudness-detect.d.ts +13 -0
- package/dist/cli/loudness-detect.d.ts.map +1 -0
- package/dist/cli/loudness-detect.js +96 -0
- package/dist/vite/angled-screen-shader.d.ts +45 -0
- package/dist/vite/angled-screen-shader.d.ts.map +1 -0
- package/dist/vite/angled-screen-shader.js +573 -0
- package/dist/vite/angled-screen.d.ts +12 -3
- package/dist/vite/angled-screen.d.ts.map +1 -1
- package/dist/vite/angled-screen.js +12 -3
- package/dist/vite/code-block.d.ts.map +1 -1
- package/dist/vite/code-block.js +26 -5
- package/dist/vite/layout-editor.js +1 -1
- package/dist/vite/mdx-video.d.ts +4 -2
- package/dist/vite/mdx-video.d.ts.map +1 -1
- package/dist/vite/mdx-video.js +4 -2
- package/dist/vite/mdx-video.test.js +1 -79
- package/dist/vite/player-page.d.ts.map +1 -1
- package/dist/vite/player-page.js +6 -94
- package/dist/vite/render-client.d.ts +27 -0
- package/dist/vite/render-client.d.ts.map +1 -1
- package/dist/vite/render-client.js +47 -51
- package/dist/vite/sdk.d.ts.map +1 -1
- package/dist/vite/sdk.js +4 -3
- package/dist/vite/shader-renderer.d.ts +4 -0
- package/dist/vite/shader-renderer.d.ts.map +1 -1
- package/dist/vite/shader-renderer.js +3 -3
- package/dist/vite/store-hooks.d.ts +2 -0
- package/dist/vite/store-hooks.d.ts.map +1 -1
- package/dist/vite/store-hooks.js +8 -0
- package/dist/vite/store.d.ts +5 -0
- package/dist/vite/store.d.ts.map +1 -1
- package/dist/vite/store.js +5 -0
- package/dist/vite/tweakpane-hook.d.ts +12 -3
- package/dist/vite/tweakpane-hook.d.ts.map +1 -1
- package/dist/vite/tweakpane-hook.js +128 -20
- package/dist/vite/vite-plugin.d.ts +0 -6
- package/dist/vite/vite-plugin.d.ts.map +1 -1
- package/dist/vite/vite-plugin.js +1 -92
- package/package.json +9 -6
- package/skills/egaki/SKILL.md +26 -224
- package/src/cli/bpm-detect.ts +165 -0
- package/src/cli/cli.ts +149 -0
- package/src/cli/loudness-detect.ts +129 -0
- package/src/vite/angled-screen-shader.tsx +706 -0
- package/src/vite/angled-screen.tsx +13 -4
- package/src/vite/code-block.tsx +28 -6
- package/src/vite/layout-editor.tsx +1 -1
- package/src/vite/mdx-video.test.tsx +1 -94
- package/src/vite/mdx-video.tsx +4 -1
- package/src/vite/player-page.tsx +28 -117
- package/src/vite/render-client.ts +52 -51
- package/src/vite/sdk.ts +4 -3
- package/src/vite/shader-renderer.tsx +3 -3
- package/src/vite/store-hooks.ts +11 -0
- package/src/vite/store.ts +10 -0
- package/src/vite/tweakpane-hook.tsx +173 -28
- package/src/vite/vite-plugin.ts +1 -91
|
@@ -28,6 +28,10 @@ export interface ShaderConfig {
|
|
|
28
28
|
/** Property controls that become uniforms and tweakpane sliders. */
|
|
29
29
|
propertyControls: Record<string, PropertyControl>;
|
|
30
30
|
}
|
|
31
|
+
/** Parse a hex color string (#RGB, #RRGGBB, #RRGGBBAA) to [r, g, b, a] in 0-1 range. */
|
|
32
|
+
export declare function parseHexColor(hex: string): [number, number, number, number];
|
|
33
|
+
export declare function compileGlShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader;
|
|
34
|
+
export declare function linkGlProgram(gl: WebGL2RenderingContext, vs: WebGLShader, fs: WebGLShader): WebGLProgram;
|
|
31
35
|
/**
|
|
32
36
|
* Create a React component that renders a WebGL2 fragment shader full-screen.
|
|
33
37
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shader-renderer.d.ts","sourceRoot":"","sources":["../../src/vite/shader-renderer.tsx"],"names":[],"mappings":"AA8BA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAOnE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAA;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAClD;
|
|
1
|
+
{"version":3,"file":"shader-renderer.d.ts","sourceRoot":"","sources":["../../src/vite/shader-renderer.tsx"],"names":[],"mappings":"AA8BA,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAOnE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAA;IAC1B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAE/D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAClD;AAMD,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAa3E;AAwFD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,sBAAsB,EAC1B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,WAAW,CAWb;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,sBAAsB,EAC1B,EAAE,EAAE,WAAW,EACf,EAAE,EAAE,WAAW,GACd,YAAY,CAYd;AAuCD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY;YAsEtC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,aAAa,CAAA;KAAE;;EA+MzD"}
|
|
@@ -33,7 +33,7 @@ import { useTweakpane } from "./tweakpane-hook.js";
|
|
|
33
33
|
// Color parsing
|
|
34
34
|
// ---------------------------------------------------------------------------
|
|
35
35
|
/** Parse a hex color string (#RGB, #RRGGBB, #RRGGBBAA) to [r, g, b, a] in 0-1 range. */
|
|
36
|
-
function parseHexColor(hex) {
|
|
36
|
+
export function parseHexColor(hex) {
|
|
37
37
|
let h = hex.replace('#', '');
|
|
38
38
|
if (h.length === 3 || h.length === 4) {
|
|
39
39
|
h = h
|
|
@@ -112,7 +112,7 @@ function generateFragmentSource(body, controls) {
|
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
113
113
|
// WebGL helpers
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
|
-
function compileGlShader(gl, type, source) {
|
|
115
|
+
export function compileGlShader(gl, type, source) {
|
|
116
116
|
const shader = gl.createShader(type);
|
|
117
117
|
if (!shader)
|
|
118
118
|
throw new Error('Failed to create shader');
|
|
@@ -125,7 +125,7 @@ function compileGlShader(gl, type, source) {
|
|
|
125
125
|
}
|
|
126
126
|
return shader;
|
|
127
127
|
}
|
|
128
|
-
function linkGlProgram(gl, vs, fs) {
|
|
128
|
+
export function linkGlProgram(gl, vs, fs) {
|
|
129
129
|
const program = gl.createProgram();
|
|
130
130
|
if (!program)
|
|
131
131
|
throw new Error('Failed to create program');
|
|
@@ -6,6 +6,8 @@ export declare function useMediaDurations(): Record<string, number>;
|
|
|
6
6
|
export declare function useGenerationStatus(): GenerationStatus | null;
|
|
7
7
|
/** Hook: recent generation errors (auto-cleared after 8s). */
|
|
8
8
|
export declare function useGenerationErrors(): GenerationError[];
|
|
9
|
+
/** Hook: whether the right tweakpane sidebar is open. */
|
|
10
|
+
export declare function useSidebarOpen(): boolean;
|
|
9
11
|
/** Hook: user modules from virtual:egaki-modules. */
|
|
10
12
|
export declare function useModules(): EagerModules;
|
|
11
13
|
export type { GenerationStatus, GenerationError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"store-hooks.d.ts","sourceRoot":"","sources":["../../src/vite/store-hooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAGnB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AA6EvD,6CAA6C;AAC7C,wBAAgB,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAE1D;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,IAAI,CAE7D;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,IAAI,eAAe,EAAE,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,qDAAqD;AACrD,wBAAgB,UAAU,IAAI,YAAY,CAEzC;AAED,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAA"}
|
package/dist/vite/store-hooks.js
CHANGED
|
@@ -69,6 +69,10 @@ const subscribeGenerationErrors = (callback) => egakiStore.subscribe((state) =>
|
|
|
69
69
|
const getGenerationErrors = () => egakiStore.getState().serverGenerationErrors;
|
|
70
70
|
const EMPTY_ERRORS = [];
|
|
71
71
|
const getGenerationErrorsServer = () => EMPTY_ERRORS;
|
|
72
|
+
// Sidebar open state
|
|
73
|
+
const subscribeSidebarOpen = (callback) => egakiStore.subscribe((state) => state.sidebarOpen, callback);
|
|
74
|
+
const getSidebarOpen = () => egakiStore.getState().sidebarOpen;
|
|
75
|
+
const getSidebarOpenServer = () => false;
|
|
72
76
|
// Modules
|
|
73
77
|
const subscribeModules = (callback) => egakiStore.subscribe((state) => state.modules, callback);
|
|
74
78
|
const getModules = () => egakiStore.getState().modules;
|
|
@@ -87,6 +91,10 @@ export function useGenerationStatus() {
|
|
|
87
91
|
export function useGenerationErrors() {
|
|
88
92
|
return useSyncExternalStore(subscribeGenerationErrors, getGenerationErrors, getGenerationErrorsServer);
|
|
89
93
|
}
|
|
94
|
+
/** Hook: whether the right tweakpane sidebar is open. */
|
|
95
|
+
export function useSidebarOpen() {
|
|
96
|
+
return useSyncExternalStore(subscribeSidebarOpen, getSidebarOpen, getSidebarOpenServer);
|
|
97
|
+
}
|
|
90
98
|
/** Hook: user modules from virtual:egaki-modules. */
|
|
91
99
|
export function useModules() {
|
|
92
100
|
return useSyncExternalStore(subscribeModules, getModules, getModules);
|
package/dist/vite/store.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export interface EgakiClientState {
|
|
|
54
54
|
/** Recent generation errors from the server progress stream.
|
|
55
55
|
* Errors auto-clear after ERROR_DISPLAY_DURATION_MS. */
|
|
56
56
|
serverGenerationErrors: GenerationError[];
|
|
57
|
+
/** Whether the right tweakpane sidebar is open. Closed by default so the
|
|
58
|
+
* video takes the full width. */
|
|
59
|
+
sidebarOpen: boolean;
|
|
57
60
|
}
|
|
58
61
|
export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiClientState>, "subscribe"> & {
|
|
59
62
|
subscribe: {
|
|
@@ -64,6 +67,8 @@ export declare const egakiStore: Omit<import("zustand/vanilla").StoreApi<EgakiCl
|
|
|
64
67
|
} | undefined): () => void;
|
|
65
68
|
};
|
|
66
69
|
};
|
|
70
|
+
/** Open or close the right tweakpane sidebar. */
|
|
71
|
+
export declare function setSidebarOpen(open: boolean): void;
|
|
67
72
|
/**
|
|
68
73
|
* Derive per-section max media durations.
|
|
69
74
|
* Returns Record<string, number> (section-index-as-string → seconds).
|
package/dist/vite/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/vite/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,2EAA2E;AAC3E,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IAErB;;;;OAIG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAEhD,oFAAoF;IACpF,sBAAsB,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAE/C,mEAAmE;IACnE,uBAAuB,EAAE,uBAAuB,EAAE,CAAA;IAElD;6DACyD;IACzD,sBAAsB,EAAE,eAAe,EAAE,CAAA;IAEzC;sCACkC;IAClC,WAAW,EAAE,OAAO,CAAA;CACrB;AAMD,eAAO,MAAM,UAAU;;;;;;;;CAStB,CAAA;AAED,iDAAiD;AACjD,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,QAE3C;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWpF;AAED,4EAA4E;AAC5E,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,IAAI,CAEvF"}
|
package/dist/vite/store.js
CHANGED
|
@@ -23,7 +23,12 @@ export const egakiStore = createStore()(subscribeWithSelector(() => ({
|
|
|
23
23
|
serverGenerationStatus: null,
|
|
24
24
|
serverGenerationEntries: [],
|
|
25
25
|
serverGenerationErrors: [],
|
|
26
|
+
sidebarOpen: false,
|
|
26
27
|
})));
|
|
28
|
+
/** Open or close the right tweakpane sidebar. */
|
|
29
|
+
export function setSidebarOpen(open) {
|
|
30
|
+
egakiStore.setState({ sidebarOpen: open });
|
|
31
|
+
}
|
|
27
32
|
// ---------------------------------------------------------------------------
|
|
28
33
|
// Selectors — derive values from state
|
|
29
34
|
// ---------------------------------------------------------------------------
|
|
@@ -71,15 +71,24 @@ export declare let TWEAKPANE_DISABLED: boolean;
|
|
|
71
71
|
* Returns a reactive object with current values. Updating a control in the
|
|
72
72
|
* pane immediately triggers a React re-render with fresh values.
|
|
73
73
|
*
|
|
74
|
+
* Values follow live props: each key returns the schema value from the
|
|
75
|
+
* CURRENT render until the user touches that control in the pane, so
|
|
76
|
+
* animated props (e.g. `translateX={interpolate(frame, ...)}`) flow through
|
|
77
|
+
* every frame. Once touched, the user's pane value wins for that key until
|
|
78
|
+
* the component remounts.
|
|
79
|
+
*
|
|
74
80
|
* On unmount the folder is disposed and removed from the pane.
|
|
75
81
|
*/
|
|
76
82
|
export declare function useTweakpane<T extends ParamSchema>(label: string, schema: T): ResolvedValues<T>;
|
|
77
83
|
/** Sidebar width constant shared with player-page layout. */
|
|
78
84
|
export declare const SIDEBAR_WIDTH = 280;
|
|
79
85
|
/**
|
|
80
|
-
* Right sidebar that hosts the tweakpane Pane.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
86
|
+
* Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
|
|
87
|
+
* video takes the full width; a fixed top-right button opens it. The sidebar
|
|
88
|
+
* container stays mounted while collapsed (hidden via display: none) because
|
|
89
|
+
* useTweakpane registers controls into it — unmounting would lose all
|
|
90
|
+
* registrations until components remount. Also provides player metadata
|
|
91
|
+
* (frame, fps, sections) to the copy prompt button.
|
|
83
92
|
*/
|
|
84
93
|
export declare function TweakpaneRoot({ playerRef, fps, sections, entryPath, }: {
|
|
85
94
|
playerRef: React.RefObject<PlayerRef | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tweakpane-hook.d.ts","sourceRoot":"","sources":["../../src/vite/tweakpane-hook.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
4
|
* Tweakpane integration for the egaki video player.
|
|
5
5
|
*
|
|
@@ -18,9 +18,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
* Tweakpane is pure DOM (no React dependency), so there are no dual-React
|
|
19
19
|
* or SSR issues. During SSR (typeof window === 'undefined') the hook
|
|
20
20
|
* returns the raw default values and skips all DOM work.
|
|
21
|
+
*
|
|
22
|
+
* Live props vs user overrides: each param follows the LIVE schema value
|
|
23
|
+
* passed on every render (so animated props like translateX={interpolate(...)}
|
|
24
|
+
* work) until the user touches that control in the pane. Once touched, the
|
|
25
|
+
* user's value wins for that key until the component remounts. Untouched
|
|
26
|
+
* controls are refreshed in the pane every render so animated values are
|
|
27
|
+
* visible while playing. Programmatic refreshes are guarded so tweakpane's
|
|
28
|
+
* change events (which fire on refresh() too) don't mark keys as touched.
|
|
21
29
|
*/
|
|
22
30
|
import { useCallback, useEffect, useRef, useState, useSyncExternalStore, createContext, useContext } from 'react';
|
|
23
31
|
import { LayoutContainerContext, useIsPremounting } from "./mdx-video.js";
|
|
32
|
+
import { setSidebarOpen } from "./store.js";
|
|
33
|
+
import { useSidebarOpen } from "./store-hooks.js";
|
|
24
34
|
export const TweakpaneContext = createContext(null);
|
|
25
35
|
// ---------------------------------------------------------------------------
|
|
26
36
|
// Global disable switch
|
|
@@ -57,6 +67,22 @@ function cloneValue(value) {
|
|
|
57
67
|
return { ...value };
|
|
58
68
|
return value;
|
|
59
69
|
}
|
|
70
|
+
/** Deep-equal for param values: primitives, small arrays (bezier tuples),
|
|
71
|
+
* and flat objects (range {min, max}). */
|
|
72
|
+
function valuesEqual(a, b) {
|
|
73
|
+
if (a === b)
|
|
74
|
+
return true;
|
|
75
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
76
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
77
|
+
}
|
|
78
|
+
if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {
|
|
79
|
+
const ra = a;
|
|
80
|
+
const rb = b;
|
|
81
|
+
const ka = Object.keys(ra);
|
|
82
|
+
return ka.length === Object.keys(rb).length && ka.every((k) => ra[k] === rb[k]);
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
60
86
|
function resolveDefault(v) {
|
|
61
87
|
if (isCubicBezierParam(v))
|
|
62
88
|
return [...v.value];
|
|
@@ -176,7 +202,10 @@ function serializeTweakpanePrompt(ctx) {
|
|
|
176
202
|
const changed = [];
|
|
177
203
|
for (const [key, current] of Object.entries(entry.params)) {
|
|
178
204
|
const def = entry.defaults[key];
|
|
179
|
-
|
|
205
|
+
// Only user-touched keys count as changed. Untouched keys mirror live
|
|
206
|
+
// (possibly animated) props, so comparing them to mount-time defaults
|
|
207
|
+
// would produce false positives on every playing frame.
|
|
208
|
+
if (entry.touched.has(key) && !valuesEqual(current, def)) {
|
|
180
209
|
changed.push(` ${key}: ${formatValue(current)}`);
|
|
181
210
|
}
|
|
182
211
|
}
|
|
@@ -259,6 +288,12 @@ function CopyChangesButton() {
|
|
|
259
288
|
* Returns a reactive object with current values. Updating a control in the
|
|
260
289
|
* pane immediately triggers a React re-render with fresh values.
|
|
261
290
|
*
|
|
291
|
+
* Values follow live props: each key returns the schema value from the
|
|
292
|
+
* CURRENT render until the user touches that control in the pane, so
|
|
293
|
+
* animated props (e.g. `translateX={interpolate(frame, ...)}`) flow through
|
|
294
|
+
* every frame. Once touched, the user's pane value wins for that key until
|
|
295
|
+
* the component remounts.
|
|
296
|
+
*
|
|
262
297
|
* On unmount the folder is disposed and removed from the pane.
|
|
263
298
|
*/
|
|
264
299
|
export function useTweakpane(label, schema) {
|
|
@@ -281,6 +316,21 @@ export function useTweakpane(label, schema) {
|
|
|
281
316
|
// Track the schema for updates
|
|
282
317
|
const schemaRef = useRef(schema);
|
|
283
318
|
schemaRef.current = schema;
|
|
319
|
+
// Keys the user changed via the pane UI. Untouched keys follow the live
|
|
320
|
+
// schema value on every render (so animated props work); touched keys keep
|
|
321
|
+
// the user's pane value until remount.
|
|
322
|
+
const touchedRef = useRef(new Set());
|
|
323
|
+
// Latest user override values, mirrored from state so the mount effect can
|
|
324
|
+
// seed rebuilt folders without re-running on every pane change.
|
|
325
|
+
const valuesRef = useRef(values);
|
|
326
|
+
valuesRef.current = values;
|
|
327
|
+
// Live handles to the mounted folder + bound params, used by the per-render
|
|
328
|
+
// sync effect below to push animated prop values into the pane.
|
|
329
|
+
const liveRef = useRef(null);
|
|
330
|
+
// True while we call folder.refresh() programmatically. tweakpane emits
|
|
331
|
+
// change events on refresh() too, so handlers must ignore those or every
|
|
332
|
+
// animated frame would mark keys as user-touched.
|
|
333
|
+
const refreshingRef = useRef(false);
|
|
284
334
|
// Stable ID for this hook instance
|
|
285
335
|
const idRef = useRef(`${label}-${Math.random().toString(36).slice(2, 8)}`);
|
|
286
336
|
useEffect(() => {
|
|
@@ -297,7 +347,10 @@ export function useTweakpane(label, schema) {
|
|
|
297
347
|
const def = resolveDefault(param);
|
|
298
348
|
// Clone object/array values so tweakpane's in-place mutations on params
|
|
299
349
|
// don't corrupt the defaults (used by "Copy changes" diffing).
|
|
300
|
-
|
|
350
|
+
// Touched keys keep the user's override when the folder is rebuilt
|
|
351
|
+
// (e.g. premount → active transition re-runs this effect).
|
|
352
|
+
const touched = touchedRef.current.has(key);
|
|
353
|
+
params[key] = cloneValue(touched ? valuesRef.current[key] : def);
|
|
301
354
|
defaults[key] = cloneValue(def);
|
|
302
355
|
}
|
|
303
356
|
let folder = null;
|
|
@@ -318,6 +371,8 @@ export function useTweakpane(label, schema) {
|
|
|
318
371
|
picker: 'inline',
|
|
319
372
|
});
|
|
320
373
|
blade.on('change', (ev) => {
|
|
374
|
+
if (refreshingRef.current)
|
|
375
|
+
return;
|
|
321
376
|
const val = ev.value;
|
|
322
377
|
const tuple = [
|
|
323
378
|
Math.round(val.x1 * 1000) / 1000,
|
|
@@ -326,6 +381,7 @@ export function useTweakpane(label, schema) {
|
|
|
326
381
|
Math.round(val.y2 * 1000) / 1000,
|
|
327
382
|
];
|
|
328
383
|
params[key] = tuple;
|
|
384
|
+
touchedRef.current.add(key);
|
|
329
385
|
setValues((prev) => ({ ...prev, [key]: tuple }));
|
|
330
386
|
});
|
|
331
387
|
continue;
|
|
@@ -337,7 +393,10 @@ export function useTweakpane(label, schema) {
|
|
|
337
393
|
max: param.max,
|
|
338
394
|
step: param.step,
|
|
339
395
|
}).on('change', (ev) => {
|
|
396
|
+
if (refreshingRef.current)
|
|
397
|
+
return;
|
|
340
398
|
const val = ev.value;
|
|
399
|
+
touchedRef.current.add(key);
|
|
341
400
|
setValues((prev) => ({ ...prev, [key]: { min: val.min, max: val.max } }));
|
|
342
401
|
});
|
|
343
402
|
continue;
|
|
@@ -382,15 +441,20 @@ export function useTweakpane(label, schema) {
|
|
|
382
441
|
}
|
|
383
442
|
}
|
|
384
443
|
folder.addBinding(params, key, opts).on('change', (ev) => {
|
|
444
|
+
if (refreshingRef.current)
|
|
445
|
+
return;
|
|
446
|
+
touchedRef.current.add(key);
|
|
385
447
|
setValues((prev) => ({ ...prev, [key]: ev.value }));
|
|
386
448
|
});
|
|
387
449
|
}
|
|
450
|
+
liveRef.current = { folder, params };
|
|
388
451
|
// Register for copy prompt
|
|
389
|
-
activeFolders.set(id, { label, params, defaults });
|
|
452
|
+
activeFolders.set(id, { label, params, defaults, touched: touchedRef.current });
|
|
390
453
|
notifyFolderListeners();
|
|
391
454
|
})();
|
|
392
455
|
return () => {
|
|
393
456
|
disposed = true;
|
|
457
|
+
liveRef.current = null;
|
|
394
458
|
if (folder) {
|
|
395
459
|
folder.dispose();
|
|
396
460
|
folder = null;
|
|
@@ -403,17 +467,59 @@ export function useTweakpane(label, schema) {
|
|
|
403
467
|
}
|
|
404
468
|
};
|
|
405
469
|
}, [label, skip]);
|
|
406
|
-
|
|
470
|
+
// Push live (possibly animated) prop values into the pane for untouched
|
|
471
|
+
// keys. Runs after every render — during playback the component re-renders
|
|
472
|
+
// each frame, so animated values are visible moving in the pane UI.
|
|
473
|
+
// folder.refresh() emits change events; refreshingRef makes handlers
|
|
474
|
+
// ignore them so refreshes never mark keys as touched.
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
const live = liveRef.current;
|
|
477
|
+
if (!live)
|
|
478
|
+
return;
|
|
479
|
+
let dirty = false;
|
|
480
|
+
for (const [key, param] of Object.entries(schemaRef.current)) {
|
|
481
|
+
if (touchedRef.current.has(key))
|
|
482
|
+
continue;
|
|
483
|
+
// Bezier blades are not bindings — refresh() won't read params for
|
|
484
|
+
// them. The returned value still follows live props below.
|
|
485
|
+
if (isCubicBezierParam(param))
|
|
486
|
+
continue;
|
|
487
|
+
const liveVal = resolveDefault(param);
|
|
488
|
+
if (!valuesEqual(live.params[key], liveVal)) {
|
|
489
|
+
live.params[key] = cloneValue(liveVal);
|
|
490
|
+
dirty = true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (dirty) {
|
|
494
|
+
refreshingRef.current = true;
|
|
495
|
+
live.folder.refresh();
|
|
496
|
+
refreshingRef.current = false;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
// Merge: user-touched keys return the pane value, untouched keys follow
|
|
500
|
+
// the live schema value so animated props work without any extra wiring.
|
|
501
|
+
const merged = {};
|
|
502
|
+
for (const [key, param] of Object.entries(schema)) {
|
|
503
|
+
merged[key] = touchedRef.current.has(key) ? values[key] : resolveDefault(param);
|
|
504
|
+
}
|
|
505
|
+
return merged;
|
|
407
506
|
}
|
|
408
507
|
// ---------------------------------------------------------------------------
|
|
409
508
|
// TweakpaneRoot — sidebar container for the tweakpane pane
|
|
410
509
|
// ---------------------------------------------------------------------------
|
|
411
510
|
/** Sidebar width constant shared with player-page layout. */
|
|
412
511
|
export const SIDEBAR_WIDTH = 280;
|
|
512
|
+
/** Lucide-style panel-right icon used for both opening and closing the sidebar. */
|
|
513
|
+
function PanelRightIcon() {
|
|
514
|
+
return (_jsxs("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.5', strokeLinecap: 'round', strokeLinejoin: 'round', children: [_jsx("rect", { x: '3', y: '3', width: '18', height: '18', rx: '2' }), _jsx("line", { x1: '15', y1: '3', x2: '15', y2: '21' })] }));
|
|
515
|
+
}
|
|
413
516
|
/**
|
|
414
|
-
* Right sidebar that hosts the tweakpane Pane.
|
|
415
|
-
*
|
|
416
|
-
*
|
|
517
|
+
* Right sidebar that hosts the tweakpane Pane. Collapsed by default so the
|
|
518
|
+
* video takes the full width; a fixed top-right button opens it. The sidebar
|
|
519
|
+
* container stays mounted while collapsed (hidden via display: none) because
|
|
520
|
+
* useTweakpane registers controls into it — unmounting would lose all
|
|
521
|
+
* registrations until components remount. Also provides player metadata
|
|
522
|
+
* (frame, fps, sections) to the copy prompt button.
|
|
417
523
|
*/
|
|
418
524
|
export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
|
|
419
525
|
// Ref callback sets the container synchronously when the DOM node mounts,
|
|
@@ -435,16 +541,18 @@ export function TweakpaneRoot({ playerRef, fps, sections, entryPath, }) {
|
|
|
435
541
|
contextRef = null;
|
|
436
542
|
};
|
|
437
543
|
}, [playerRef, fps, sections, entryPath]);
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
544
|
+
const sidebarOpen = useSidebarOpen();
|
|
545
|
+
return (_jsxs(_Fragment, { children: [!sidebarOpen && (_jsx("button", { onClick: () => setSidebarOpen(true), title: 'Open controls sidebar', className: 'fixed top-4 right-4 z-10 flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) })), _jsxs("div", { className: 'egaki-sidebar', style: {
|
|
546
|
+
width: SIDEBAR_WIDTH,
|
|
547
|
+
minWidth: SIDEBAR_WIDTH,
|
|
548
|
+
height: '100vh',
|
|
549
|
+
position: 'sticky',
|
|
550
|
+
top: 0,
|
|
551
|
+
overflowY: 'auto',
|
|
552
|
+
overflowX: 'hidden',
|
|
553
|
+
background: '#111',
|
|
554
|
+
borderLeft: '1px solid rgba(255,255,255,0.08)',
|
|
555
|
+
flexShrink: 0,
|
|
556
|
+
display: sidebarOpen ? undefined : 'none',
|
|
557
|
+
}, children: [_jsx("div", { className: 'flex items-center justify-end px-2 pt-2', children: _jsx("button", { onClick: () => setSidebarOpen(false), title: 'Close controls sidebar', className: 'flex items-center justify-center rounded-lg w-8 h-8 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors cursor-pointer', children: _jsx(PanelRightIcon, {}) }) }), _jsx(CopyChangesButton, {}), _jsx("div", { ref: containerRefCallback, id: 'egaki-tweakpane', style: { width: '100%' } })] })] }));
|
|
450
558
|
}
|
|
@@ -18,11 +18,5 @@ export interface VideoPluginOptions {
|
|
|
18
18
|
* All other .mdx files in the project become additional routes. */
|
|
19
19
|
entry?: string;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Compare old and new MDX sources section-by-section. Returns the index
|
|
23
|
-
* of the first added or edited section, or null if nothing changed (or
|
|
24
|
-
* only deletions occurred, which we skip per design).
|
|
25
|
-
*/
|
|
26
|
-
export declare function findChangedSectionIndex(oldSource: string, newSource: string): number | null;
|
|
27
21
|
export declare function video(options?: VideoPluginOptions): PluginOption[];
|
|
28
22
|
//# sourceMappingURL=vite-plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;
|
|
1
|
+
{"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../../src/vite/vite-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AA4BhD,MAAM,WAAW,kBAAkB;IACjC;;wEAEoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiDD,wBAAgB,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAoYlE"}
|
package/dist/vite/vite-plugin.js
CHANGED
|
@@ -46,60 +46,6 @@ function resolveSourceToFile(root, source) {
|
|
|
46
46
|
}
|
|
47
47
|
return undefined;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Extract raw source text ranges per section directly from the mdast.
|
|
51
|
-
* Works with heading nodes' positions to capture the FULL raw heading
|
|
52
|
-
* text including props like `duration=3s` and `transition=20` that
|
|
53
|
-
* `splitIntoSections` strips during parsing. Each section spans from
|
|
54
|
-
* its heading's start offset to the next heading's start (or EOF).
|
|
55
|
-
*/
|
|
56
|
-
function sectionRawTexts(source, mdast) {
|
|
57
|
-
// Collect the start offsets of each heading node
|
|
58
|
-
const headingStarts = [];
|
|
59
|
-
for (const node of mdast.children) {
|
|
60
|
-
if (node.type === 'heading' && node.position?.start?.offset != null) {
|
|
61
|
-
headingStarts.push(node.position.start.offset);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// Slice source between consecutive headings, trimming trailing whitespace
|
|
65
|
-
// so boundary changes (adding/removing a following section) don't cause
|
|
66
|
-
// false positives on the preceding section.
|
|
67
|
-
return headingStarts.map((start, i) => {
|
|
68
|
-
const end = i + 1 < headingStarts.length ? headingStarts[i + 1] : source.length;
|
|
69
|
-
return source.slice(start, end).trimEnd();
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Compare old and new MDX sources section-by-section. Returns the index
|
|
74
|
-
* of the first added or edited section, or null if nothing changed (or
|
|
75
|
-
* only deletions occurred, which we skip per design).
|
|
76
|
-
*/
|
|
77
|
-
export function findChangedSectionIndex(oldSource, newSource) {
|
|
78
|
-
try {
|
|
79
|
-
const oldTexts = sectionRawTexts(oldSource, mdxParse(oldSource));
|
|
80
|
-
const newTexts = sectionRawTexts(newSource, mdxParse(newSource));
|
|
81
|
-
const maxLen = Math.max(oldTexts.length, newTexts.length);
|
|
82
|
-
for (let i = 0; i < maxLen; i++) {
|
|
83
|
-
if (i >= oldTexts.length)
|
|
84
|
-
return i; // new section added
|
|
85
|
-
if (i >= newTexts.length)
|
|
86
|
-
return null; // section deleted, skip
|
|
87
|
-
if (oldTexts[i] !== newTexts[i]) {
|
|
88
|
-
// Distinguish edit from deletion: if the next old section matches
|
|
89
|
-
// the current new section, a section was deleted (shifted down).
|
|
90
|
-
// Look ahead to detect deletions at any position, not just the tail.
|
|
91
|
-
if (newTexts.length < oldTexts.length && oldTexts[i + 1] === newTexts[i]) {
|
|
92
|
-
return null; // deletion, skip
|
|
93
|
-
}
|
|
94
|
-
return i; // content changed
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null; // no scene-level change (frontmatter/preamble only)
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return null; // parse error, don't seek
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
49
|
/** Discover .mdx files in the project root directory (non-recursive).
|
|
104
50
|
* Only root-level files become entries to avoid import resolution issues
|
|
105
51
|
* with nested paths. Returns a map of routePath → absolutePath. */
|
|
@@ -146,8 +92,6 @@ export function video(options) {
|
|
|
146
92
|
let defaultRoute = '';
|
|
147
93
|
/** Whether the user's project has `motion` (framer-motion) installed. */
|
|
148
94
|
let hasMotion = false;
|
|
149
|
-
/** Cached previous MDX sources for section-level diff detection, keyed by abs path. */
|
|
150
|
-
const previousMdxSources = new Map();
|
|
151
95
|
/** Set of absolute paths of all entry MDX files for quick lookup. */
|
|
152
96
|
let entryPathSet = new Set();
|
|
153
97
|
/** Is this file referenced inside a <Server> block of any entry MDX?
|
|
@@ -193,13 +137,6 @@ export function video(options) {
|
|
|
193
137
|
}
|
|
194
138
|
defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
|
|
195
139
|
entryPathSet = new Set(mdxEntries.values());
|
|
196
|
-
// Seed previous sources for section-level diff detection.
|
|
197
|
-
for (const [, absPath] of mdxEntries) {
|
|
198
|
-
try {
|
|
199
|
-
previousMdxSources.set(absPath, fs.readFileSync(absPath, 'utf-8'));
|
|
200
|
-
}
|
|
201
|
-
catch { /* ignore read errors */ }
|
|
202
|
-
}
|
|
203
140
|
// Auto-generate egaki-env.d.ts so MDX LSP knows about built-in
|
|
204
141
|
// components via the global MDXProvidedComponents type. Same
|
|
205
142
|
// pattern Vite uses for vite-env.d.ts.
|
|
@@ -407,21 +344,7 @@ export function video(options) {
|
|
|
407
344
|
mdxEntries.set(relPath.replace(/\.mdx$/, ''), absEntry);
|
|
408
345
|
}
|
|
409
346
|
defaultRoute = resolveDefaultRoute(mdxEntries, options?.entry, root);
|
|
410
|
-
|
|
411
|
-
// Sync previousMdxSources: seed new entries, prune removed ones.
|
|
412
|
-
for (const file of previousMdxSources.keys()) {
|
|
413
|
-
if (!nextEntryPathSet.has(file))
|
|
414
|
-
previousMdxSources.delete(file);
|
|
415
|
-
}
|
|
416
|
-
for (const file of nextEntryPathSet) {
|
|
417
|
-
if (!previousMdxSources.has(file)) {
|
|
418
|
-
try {
|
|
419
|
-
previousMdxSources.set(file, fs.readFileSync(file, 'utf-8'));
|
|
420
|
-
}
|
|
421
|
-
catch { /* file may be mid-write */ }
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
entryPathSet = nextEntryPathSet;
|
|
347
|
+
entryPathSet = new Set(mdxEntries.values());
|
|
425
348
|
invalidateVirtual([RESOLVED_APP, RESOLVED_MDX, RESOLVED_MODULES]);
|
|
426
349
|
if (this.environment.name === 'client') {
|
|
427
350
|
ctx.server.environments.client?.hot.send({ type: 'full-reload' });
|
|
@@ -431,25 +354,11 @@ export function video(options) {
|
|
|
431
354
|
if (isEntryMdx) {
|
|
432
355
|
invalidateVirtual([RESOLVED_APP, RESOLVED_MDX]);
|
|
433
356
|
if (this.environment.name === 'client') {
|
|
434
|
-
// Section-level diff detection for the changed entry.
|
|
435
|
-
const newMdxSource = fs.readFileSync(normalizedFile, 'utf-8');
|
|
436
|
-
const prevSource = previousMdxSources.get(normalizedFile);
|
|
437
|
-
const changedSection = prevSource != null
|
|
438
|
-
? findChangedSectionIndex(prevSource, newMdxSource)
|
|
439
|
-
: null;
|
|
440
|
-
previousMdxSources.set(normalizedFile, newMdxSource);
|
|
441
357
|
ctx.server.environments.client?.hot.send({
|
|
442
358
|
type: 'custom',
|
|
443
359
|
event: 'rsc:update',
|
|
444
360
|
data: { file: ctx.file },
|
|
445
361
|
});
|
|
446
|
-
if (changedSection != null) {
|
|
447
|
-
ctx.server.environments.client?.hot.send({
|
|
448
|
-
type: 'custom',
|
|
449
|
-
event: 'egaki:scene-changed',
|
|
450
|
-
data: { sectionIndex: changedSection, file: normalizedFile },
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
362
|
}
|
|
454
363
|
return [];
|
|
455
364
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "egaki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI image, video, and speech generation CLI with MDX-to-video framework",
|
|
6
6
|
"main": "./dist/cli/main.js",
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
"@ai-sdk/xai": "^3.0.92",
|
|
89
89
|
"@clack/prompts": "^1.0.1",
|
|
90
90
|
"@fal-ai/client": "^1.10.1",
|
|
91
|
-
"@remotion/media": "4.0.
|
|
92
|
-
"@remotion/player": "4.0.
|
|
93
|
-
"@remotion/web-renderer": "4.0.
|
|
91
|
+
"@remotion/media": "4.0.494",
|
|
92
|
+
"@remotion/player": "4.0.494",
|
|
93
|
+
"@remotion/web-renderer": "4.0.494",
|
|
94
94
|
"@tailwindcss/vite": "^4.2.2",
|
|
95
95
|
"@tweakpane/core": "^2.0.5",
|
|
96
96
|
"@tweakpane/plugin-essentials": "^0.2.1",
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
"eventsource-parser": "^3.0.6",
|
|
99
99
|
"goke": "^6.12.1",
|
|
100
100
|
"js-yaml": "^4.1.1",
|
|
101
|
-
"mediabunny": "^1.
|
|
101
|
+
"mediabunny": "^1.50.8",
|
|
102
102
|
"moveable": "^0.53.0",
|
|
103
103
|
"picocolors": "^1.1.1",
|
|
104
104
|
"react": "19.2.7",
|
|
105
105
|
"react-dom": "19.2.7",
|
|
106
|
-
"remotion": "4.0.
|
|
106
|
+
"remotion": "4.0.494",
|
|
107
107
|
"safe-mdx": "^1.11.3",
|
|
108
108
|
"shiki": "^4.2.0",
|
|
109
109
|
"string-dedent": "^3.0.2",
|
|
@@ -135,6 +135,9 @@
|
|
|
135
135
|
"optional": true
|
|
136
136
|
}
|
|
137
137
|
},
|
|
138
|
+
"optionalDependencies": {
|
|
139
|
+
"node-web-audio-api": ">=1"
|
|
140
|
+
},
|
|
138
141
|
"devDependencies": {
|
|
139
142
|
"@types/js-yaml": "^4.0.9",
|
|
140
143
|
"@types/react": "^19.2.17",
|