@bycrux/editor 0.8.3 → 0.8.4
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/package.json
CHANGED
|
@@ -84,6 +84,31 @@ function OverlayVideo({ src, currentTime, itemStart, inPoint, isPlaying, muted,
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// ---------------------------------------------------------------------------
|
|
87
|
+
// Recursively rewrite absolute workspace path strings in overlay props to
|
|
88
|
+
// servable proxy URLs (via fileUrl), so <img src> resolves in the browser
|
|
89
|
+
// preview. Mirrors the render-side rewritePathsToFileUrls (bundle.js), which
|
|
90
|
+
// already recurses — without this, image paths nested in array/object props
|
|
91
|
+
// (e.g. players[].src, items[].src) reach <img> raw as /var/hub-scratch/... and
|
|
92
|
+
// render blank in preview even though they render correctly in the final MP4.
|
|
93
|
+
// Already-proxied /api/ URLs are left untouched (idempotent).
|
|
94
|
+
export function resolveOverlayPropPaths(
|
|
95
|
+
value: unknown,
|
|
96
|
+
fileUrl: (path: string) => string,
|
|
97
|
+
): unknown {
|
|
98
|
+
if (typeof value === 'string') {
|
|
99
|
+
return value.startsWith('/') && !value.startsWith('/api/') ? fileUrl(value) : value
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
return value.map((v) => resolveOverlayPropPaths(v, fileUrl))
|
|
103
|
+
}
|
|
104
|
+
if (value && typeof value === 'object') {
|
|
105
|
+
return Object.fromEntries(
|
|
106
|
+
Object.entries(value).map(([k, v]) => [k, resolveOverlayPropPaths(v, fileUrl)]),
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return value
|
|
110
|
+
}
|
|
111
|
+
|
|
87
112
|
// CustomOverlay: fetches, compiles, and renders a custom JSX overlay file
|
|
88
113
|
//
|
|
89
114
|
// Live overlay-edit reload behavior (Montaj host):
|
|
@@ -158,14 +183,7 @@ function CustomOverlay({
|
|
|
158
183
|
|
|
159
184
|
if (!factory) return null
|
|
160
185
|
|
|
161
|
-
const resolvedProps =
|
|
162
|
-
Object.entries(props).map(([k, v]) => [
|
|
163
|
-
k,
|
|
164
|
-
typeof v === 'string' && v.startsWith('/') && !v.startsWith('/api/')
|
|
165
|
-
? fileUrl(v)
|
|
166
|
-
: v,
|
|
167
|
-
]),
|
|
168
|
-
)
|
|
186
|
+
const resolvedProps = resolveOverlayPropPaths(props, fileUrl) as Record<string, unknown>
|
|
169
187
|
|
|
170
188
|
const element = factory(frame, fps, durationFrames, resolvedProps)
|
|
171
189
|
if (!element) return null
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { resolveOverlayPropPaths } from '../OverlayItemsLayer'
|
|
3
|
+
|
|
4
|
+
// Stand-in for the Hub adapter's fileUrl: workspace path → servable proxy URL.
|
|
5
|
+
const fileUrl = (p: string) => `/api/hub/montaj/files?path=${encodeURIComponent(p)}`
|
|
6
|
+
|
|
7
|
+
describe('resolveOverlayPropPaths', () => {
|
|
8
|
+
it('rewrites a top-level workspace path string (photo_card.src case)', () => {
|
|
9
|
+
const out = resolveOverlayPropPaths(
|
|
10
|
+
{ src: '/var/hub-scratch/p1/assets/a.jpg', text: 'hi' },
|
|
11
|
+
fileUrl,
|
|
12
|
+
)
|
|
13
|
+
expect(out).toEqual({
|
|
14
|
+
src: '/api/hub/montaj/files?path=' + encodeURIComponent('/var/hub-scratch/p1/assets/a.jpg'),
|
|
15
|
+
text: 'hi',
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('rewrites workspace paths nested in an array of objects (player_trio.players case)', () => {
|
|
20
|
+
const out = resolveOverlayPropPaths(
|
|
21
|
+
{
|
|
22
|
+
players: [
|
|
23
|
+
{ src: '/var/hub-scratch/p1/assets/a.jpg', name: 'A' },
|
|
24
|
+
{ src: '/var/hub-scratch/p1/assets/b.jpg', name: 'B' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
fileUrl,
|
|
28
|
+
) as { players: Array<{ src: string; name: string }> }
|
|
29
|
+
expect(out.players[0].src).toBe(fileUrl('/var/hub-scratch/p1/assets/a.jpg'))
|
|
30
|
+
expect(out.players[1].src).toBe(fileUrl('/var/hub-scratch/p1/assets/b.jpg'))
|
|
31
|
+
expect(out.players[0].name).toBe('A')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('leaves already-proxied /api/ URLs and remote URLs untouched (idempotent)', () => {
|
|
35
|
+
const proxied = '/api/hub/montaj/files?path=%2Fx.jpg'
|
|
36
|
+
const remote = 'https://cdn.example.com/x.jpg'
|
|
37
|
+
const out = resolveOverlayPropPaths(
|
|
38
|
+
{ items: [{ src: proxied }, { src: remote }] },
|
|
39
|
+
fileUrl,
|
|
40
|
+
) as { items: Array<{ src: string }> }
|
|
41
|
+
expect(out.items[0].src).toBe(proxied)
|
|
42
|
+
expect(out.items[1].src).toBe(remote)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('preserves non-string scalars and nesting depth', () => {
|
|
46
|
+
const out = resolveOverlayPropPaths(
|
|
47
|
+
{ duration: 90, nested: { deep: [{ src: '/var/x.png' }] } },
|
|
48
|
+
fileUrl,
|
|
49
|
+
) as { duration: number; nested: { deep: Array<{ src: string }> } }
|
|
50
|
+
expect(out.duration).toBe(90)
|
|
51
|
+
expect(out.nested.deep[0].src).toBe(fileUrl('/var/x.png'))
|
|
52
|
+
})
|
|
53
|
+
})
|