visualfries 0.1.10120 → 0.3.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.
Files changed (133) hide show
  1. package/README.md +97 -0
  2. package/bin/visualfries.js +1570 -0
  3. package/dist/DIContainer.d.ts +2 -1
  4. package/dist/DIContainer.js +16 -5
  5. package/dist/SceneBuilder.svelte.d.ts +70 -1
  6. package/dist/SceneBuilder.svelte.js +96 -29
  7. package/dist/agent/audioMixer.d.ts +34 -0
  8. package/dist/agent/audioMixer.js +308 -0
  9. package/dist/agent/broll.d.ts +35 -0
  10. package/dist/agent/broll.js +226 -0
  11. package/dist/agent/capabilities.d.ts +34 -0
  12. package/dist/agent/capabilities.js +246 -0
  13. package/dist/agent/captionScene.d.ts +23 -0
  14. package/dist/agent/captionScene.js +204 -0
  15. package/dist/agent/catalog.d.ts +24 -0
  16. package/dist/agent/catalog.js +67 -0
  17. package/dist/agent/cueFiles.d.ts +54 -0
  18. package/dist/agent/cueFiles.js +219 -0
  19. package/dist/agent/cuePresets.d.ts +7 -0
  20. package/dist/agent/cuePresets.js +50 -0
  21. package/dist/agent/deterministicMedia.d.ts +61 -0
  22. package/dist/agent/deterministicMedia.js +303 -0
  23. package/dist/agent/index.d.ts +18 -0
  24. package/dist/agent/index.js +18 -0
  25. package/dist/agent/inspectScene.d.ts +36 -0
  26. package/dist/agent/inspectScene.js +139 -0
  27. package/dist/agent/localRender.d.ts +82 -0
  28. package/dist/agent/localRender.js +702 -0
  29. package/dist/agent/overlays.d.ts +40 -0
  30. package/dist/agent/overlays.js +325 -0
  31. package/dist/agent/productionPlan.d.ts +131 -0
  32. package/dist/agent/productionPlan.js +322 -0
  33. package/dist/agent/renderPlan.d.ts +27 -0
  34. package/dist/agent/renderPlan.js +68 -0
  35. package/dist/agent/renderRanges.d.ts +24 -0
  36. package/dist/agent/renderRanges.js +73 -0
  37. package/dist/agent/runtimeSupport.d.ts +19 -0
  38. package/dist/agent/runtimeSupport.js +192 -0
  39. package/dist/agent/streamingEncoder.d.ts +31 -0
  40. package/dist/agent/streamingEncoder.js +208 -0
  41. package/dist/agent/transcripts.d.ts +29 -0
  42. package/dist/agent/transcripts.js +146 -0
  43. package/dist/agent/transitions.d.ts +20 -0
  44. package/dist/agent/transitions.js +169 -0
  45. package/dist/animations/PixiAnimationTarget.d.ts +29 -0
  46. package/dist/animations/PixiAnimationTarget.js +63 -0
  47. package/dist/animations/builders/LineHighlighterAnimationBuilder.js +20 -3
  48. package/dist/animations/builders/WordHighlighterAnimationBuilder.js +15 -2
  49. package/dist/browser/browserExport.d.ts +73 -0
  50. package/dist/browser/browserExport.js +180 -0
  51. package/dist/browser/glTransitions.d.ts +37 -0
  52. package/dist/browser/glTransitions.js +261 -0
  53. package/dist/browser/index.d.ts +3 -0
  54. package/dist/browser/index.js +3 -0
  55. package/dist/browser/mediaDecode.d.ts +31 -0
  56. package/dist/browser/mediaDecode.js +152 -0
  57. package/dist/builders/PixiComponentBuilder.d.ts +6 -0
  58. package/dist/builders/PixiComponentBuilder.js +12 -0
  59. package/dist/builders/_ComponentState.svelte.d.ts +7 -5
  60. package/dist/builders/_ComponentState.svelte.js +29 -15
  61. package/dist/builders/html/StyleBuilder.js +9 -8
  62. package/dist/builders/html/TextComponentHtmlBuilder.js +4 -2
  63. package/dist/builders/html/processors/AppearanceStyleProcessor.js +1 -1
  64. package/dist/builders/html/processors/TextAppearanceStyleProcessor.js +7 -0
  65. package/dist/builders/html/processors/TextEffectsStyleProcessor.js +32 -13
  66. package/dist/commands/ReplaceSourceOnTimeCommand.js +9 -1
  67. package/dist/commands/SeekCommand.js +19 -6
  68. package/dist/components/AnimatedGIF.d.ts +3 -0
  69. package/dist/components/AnimatedGIF.js +28 -1
  70. package/dist/components/Component.svelte.d.ts +5 -5
  71. package/dist/components/Component.svelte.js +29 -13
  72. package/dist/components/ComponentContext.svelte.js +68 -23
  73. package/dist/components/hooks/AnimationHook.js +1 -1
  74. package/dist/components/hooks/CanvasFillHook.d.ts +12 -0
  75. package/dist/components/hooks/CanvasFillHook.js +75 -0
  76. package/dist/components/hooks/DeterministicMediaFrameHook.js +3 -0
  77. package/dist/components/hooks/HtmlTextHook.js +11 -3
  78. package/dist/components/hooks/HtmlToCanvasHook.js +8 -25
  79. package/dist/components/hooks/MediaHook.js +172 -47
  80. package/dist/components/hooks/MediaSeekingHook.js +138 -88
  81. package/dist/components/hooks/PixiGifHook.js +4 -4
  82. package/dist/components/hooks/PixiSplitScreenDisplayObjectHook.js +73 -6
  83. package/dist/components/hooks/PixiVideoTextureHook.js +10 -4
  84. package/dist/components/hooks/PixiVisualTransformHook.d.ts +16 -0
  85. package/dist/components/hooks/PixiVisualTransformHook.js +102 -0
  86. package/dist/components/hooks/SubtitlesHook.js +48 -24
  87. package/dist/components/hooks/VerifyMediaHook.js +2 -2
  88. package/dist/directors/ComponentDirector.d.ts +1 -0
  89. package/dist/directors/ComponentDirector.js +41 -8
  90. package/dist/fonts/fontDiscovery.js +12 -1
  91. package/dist/layers/Layer.svelte.d.ts +54 -0
  92. package/dist/layers/Layer.svelte.js +2 -25
  93. package/dist/managers/AppManager.svelte.js +8 -5
  94. package/dist/managers/ComponentsManager.svelte.d.ts +7 -7
  95. package/dist/managers/ComponentsManager.svelte.js +29 -15
  96. package/dist/managers/DeterministicMediaManager.js +38 -3
  97. package/dist/managers/EventManager.js +3 -1
  98. package/dist/managers/LayersManager.svelte.d.ts +54 -0
  99. package/dist/managers/RenderManager.d.ts +5 -0
  100. package/dist/managers/RenderManager.js +67 -21
  101. package/dist/managers/StateManager.svelte.d.ts +56 -0
  102. package/dist/managers/StateManager.svelte.js +60 -58
  103. package/dist/managers/SubtitlesManager.svelte.js +1 -3
  104. package/dist/managers/TimeManager.svelte.d.ts +4 -0
  105. package/dist/managers/TimeManager.svelte.js +23 -7
  106. package/dist/schemas/runtime/deterministic.js +5 -1
  107. package/dist/schemas/runtime/types.d.ts +36 -15
  108. package/dist/schemas/scene/components.d.ts +126 -0
  109. package/dist/schemas/scene/components.js +11 -0
  110. package/dist/schemas/scene/core.d.ts +109 -10
  111. package/dist/schemas/scene/core.js +1 -1
  112. package/dist/utils/mediaWindow.d.ts +13 -0
  113. package/dist/utils/mediaWindow.js +25 -0
  114. package/dist/utils/svgGenerator.js +1 -1
  115. package/dist/utils/timelineWindow.d.ts +2 -0
  116. package/dist/utils/timelineWindow.js +7 -0
  117. package/dist/utils/utils.js +2 -4
  118. package/docs/AGENT_PATTERNS.md +243 -0
  119. package/docs/AGENT_WORKFLOW.md +364 -0
  120. package/docs/AUTHORING_BEST_PRACTICES.md +188 -0
  121. package/docs/AUTHORING_SYSTEM_AUDIT.md +98 -0
  122. package/docs/BROWSER_EXPORT_SPIKE.md +55 -0
  123. package/docs/DETERMINISTIC_RENDERER.md +83 -0
  124. package/docs/NEXT_RELEASE.md +28 -0
  125. package/docs/PROPOSED_VISUALFRIES_SKILL.md +68 -0
  126. package/docs/examples/agent-cues.json +49 -0
  127. package/docs/examples/agent-transcript.json +23 -0
  128. package/docs/examples/agent-transcript.srt +7 -0
  129. package/docs/examples/agent-transcript.vtt +7 -0
  130. package/docs/examples/image-animation.json +87 -0
  131. package/docs/examples/native-text-metric-badge.json +109 -0
  132. package/package.json +133 -100
  133. package/skills/visualfries/SKILL.md +328 -0
@@ -58,7 +58,7 @@ export class SubtitlesHook {
58
58
  const assetSubtitles = this.subtitlesManager.getAssetSubtitles(assetId);
59
59
  const languageCode = source?.languageCode || 'default';
60
60
  const subtitleArray = assetSubtitles[languageCode] || Object.values(assetSubtitles)[0] || [];
61
- this.#subtitles = subtitleArray;
61
+ this.#subtitles = [...subtitleArray].sort((a, b) => a.start_at - b.start_at);
62
62
  // const el = this.#buildHtmlElement();
63
63
  // this.#context.setResource('wrapperHtmlEl', el);
64
64
  if (this.activeSubtitle === undefined) {
@@ -66,16 +66,38 @@ export class SubtitlesHook {
66
66
  }
67
67
  }
68
68
  get activeSubtitle() {
69
+ const currentTime = this.state.currentTime;
69
70
  if (this.#currentSubtitle) {
70
- if (this.state.currentTime >= this.#currentSubtitle.start_at &&
71
- this.state.currentTime <= this.#currentSubtitle.end_at) {
72
- return this.#currentSubtitle.visible ? this.#currentSubtitle : undefined;
71
+ if (currentTime >= this.#currentSubtitle.start_at &&
72
+ currentTime <= this.#currentSubtitle.end_at) {
73
+ return this.#currentSubtitle.visible !== false ? this.#currentSubtitle : undefined;
73
74
  }
74
75
  }
75
- const subtitle = this.#subtitles.find((sub) => this.state.currentTime >= sub.start_at &&
76
- this.state.currentTime <= sub.end_at &&
77
- sub.visible !== false);
78
- return subtitle;
76
+ let low = 0;
77
+ let high = this.#subtitles.length - 1;
78
+ let candidateIndex = -1;
79
+ while (low <= high) {
80
+ const mid = Math.floor((low + high) / 2);
81
+ const subtitle = this.#subtitles[mid];
82
+ if (subtitle.start_at <= currentTime) {
83
+ candidateIndex = mid;
84
+ low = mid + 1;
85
+ }
86
+ else {
87
+ high = mid - 1;
88
+ }
89
+ }
90
+ if (candidateIndex === -1) {
91
+ this.#currentSubtitle = undefined;
92
+ return undefined;
93
+ }
94
+ const subtitle = this.#subtitles[candidateIndex];
95
+ if (currentTime <= subtitle.end_at && subtitle.visible !== false) {
96
+ this.#currentSubtitle = subtitle;
97
+ return subtitle;
98
+ }
99
+ this.#currentSubtitle = undefined;
100
+ return undefined;
79
101
  }
80
102
  #buildFakeContext() {
81
103
  let background = this.#context.data.appearance.background;
@@ -108,19 +130,21 @@ export class SubtitlesHook {
108
130
  this.#context.updateContextData(updateData);
109
131
  }
110
132
  async #handleUpdate() {
111
- const contextId = this.activeSubtitle ? this.activeSubtitle.id : undefined;
133
+ const activeSubtitle = this.activeSubtitle;
134
+ const contextId = activeSubtitle?.id;
112
135
  if (contextId === this.#currentId && !this.#refreshed) {
113
136
  return;
114
137
  }
115
- if ((this.activeSubtitle && this.activeSubtitle.id !== this.#context.contextData.id) ||
116
- (this.activeSubtitle && this.#refreshed)) {
117
- this.#currentId = this.activeSubtitle.id;
118
- const startTime = this.activeSubtitle?.start_at ?? 0;
119
- const endTime = this.activeSubtitle?.end_at ?? 0;
138
+ if ((activeSubtitle && activeSubtitle.id !== this.#context.contextData.id) ||
139
+ (activeSubtitle && this.#refreshed)) {
140
+ this.#currentSubtitle = activeSubtitle;
141
+ this.#currentId = activeSubtitle.id;
142
+ const startTime = activeSubtitle.start_at ?? 0;
143
+ const endTime = activeSubtitle.end_at ?? 0;
120
144
  const duration = endTime - startTime;
121
145
  const animationData = {};
122
146
  const wordTimings = [];
123
- this.activeSubtitle?.words?.forEach((word) => {
147
+ activeSubtitle.words?.forEach((word) => {
124
148
  wordTimings.push(gsap.utils.clamp(0, duration, word[1] - startTime)); // timings is relative to the start of the subtitle
125
149
  });
126
150
  // reorder wordTimings by start_at
@@ -135,24 +159,24 @@ export class SubtitlesHook {
135
159
  this.#context.setResource('animationData', animationData);
136
160
  // we need to rebuild subtitle component as text component
137
161
  const currentSize = get(this.#context.data, 'appearance.text.fontSize.value', get(this.#context.data, 'appearance.text.fontSize', 50));
138
- const colorOverride = this.activeSubtitle.color ? { color: this.activeSubtitle.color } : {};
139
- const visibleOverride = 'visible' in this.activeSubtitle ? { visible: this.activeSubtitle.visible } : {};
140
- const fontSizeOverride = 'enlarge' in this.activeSubtitle && currentSize
162
+ const colorOverride = activeSubtitle.color ? { color: activeSubtitle.color } : {};
163
+ const visibleOverride = 'visible' in activeSubtitle ? { visible: activeSubtitle.visible } : {};
164
+ const fontSizeOverride = 'enlarge' in activeSubtitle && currentSize
141
165
  ? {
142
166
  fontSize: {
143
- value: currentSize * (this.activeSubtitle.enlarge / 100),
167
+ value: currentSize * (activeSubtitle.enlarge / 100),
144
168
  unit: this.#context.data.appearance.text?.fontSize?.unit
145
169
  }
146
170
  }
147
171
  : {};
148
172
  const subtitleContextData = {
149
173
  ...this.#context.data,
150
- id: this.activeSubtitle.id,
174
+ id: activeSubtitle.id,
151
175
  type: 'TEXT',
152
- text: this.#removePunctuation(this.activeSubtitle.text || ''),
176
+ text: this.#removePunctuation(activeSubtitle.text || ''),
153
177
  timeline: {
154
- startAt: this.activeSubtitle.start_at,
155
- endAt: this.activeSubtitle.end_at
178
+ startAt: activeSubtitle.start_at,
179
+ endAt: activeSubtitle.end_at
156
180
  },
157
181
  appearance: {
158
182
  ...this.#context.data.appearance,
@@ -169,7 +193,7 @@ export class SubtitlesHook {
169
193
  this.#refreshed = false;
170
194
  }
171
195
  }
172
- if (!this.activeSubtitle) {
196
+ if (!activeSubtitle) {
173
197
  this.#buildFakeContext();
174
198
  }
175
199
  }
@@ -1,6 +1,6 @@
1
1
  import { VideoComponentShape } from '../..';
2
2
  export class VerifyMediaHook {
3
- types = ['setup', 'refresh'];
3
+ types = ['setup', 'refresh:content'];
4
4
  priority = 1;
5
5
  #context;
6
6
  async #handleSetup() {
@@ -14,7 +14,7 @@ export class VerifyMediaHook {
14
14
  }
15
15
  async handle(type, context) {
16
16
  this.#context = context;
17
- if (type === 'setup' || type === 'refresh') {
17
+ if (type === 'setup' || type === 'refresh:content') {
18
18
  return await this.#handleSetup();
19
19
  }
20
20
  }
@@ -19,6 +19,7 @@ export declare class ComponentDirector {
19
19
  constructImage(): import("..").IComponent;
20
20
  constructGif(): import("..").IComponent;
21
21
  constructShape(): import("..").IComponent;
22
+ constructFill(): import("..").IComponent;
22
23
  constructSubtitle(): import("..").IComponent;
23
24
  constructText(): import("..").IComponent;
24
25
  }
@@ -36,18 +36,23 @@ export class ComponentDirector {
36
36
  return this.constructText();
37
37
  case 'COLOR':
38
38
  case 'GRADIENT':
39
- return this.builder.getComponent();
39
+ return this.constructFill();
40
40
  default:
41
41
  throw new Error(`Unsupported component type`);
42
42
  }
43
43
  }
44
44
  constructVideo() {
45
45
  if (this.shouldUseDeterministicMedia) {
46
- this.builder.withDeterministicMedia().withTexture().withSplitScreen();
46
+ this.builder
47
+ .withDeterministicMedia()
48
+ .withTexture()
49
+ .withSplitScreen()
50
+ .withPixiAnimationTarget()
51
+ .withAnimation();
47
52
  return this.builder.getComponent();
48
53
  }
49
54
  this.builder.withMedia().withMediaSeeking();
50
- this.builder.withVideoTexture().withSplitScreen();
55
+ this.builder.withVideoTexture().withSplitScreen().withPixiAnimationTarget().withAnimation();
51
56
  return this.builder.getComponent();
52
57
  }
53
58
  constructAudio() {
@@ -55,15 +60,25 @@ export class ComponentDirector {
55
60
  return this.builder.getComponent();
56
61
  }
57
62
  constructImage() {
58
- this.builder.withImage().withTexture().withSplitScreen();
63
+ this.builder
64
+ .withImage()
65
+ .withTexture()
66
+ .withSplitScreen()
67
+ .withPixiAnimationTarget()
68
+ .withAnimation();
59
69
  return this.builder.getComponent();
60
70
  }
61
71
  constructGif() {
62
72
  if (this.shouldUseDeterministicMedia) {
63
- this.builder.withDeterministicMedia().withTexture().withDisplayObject();
73
+ this.builder
74
+ .withDeterministicMedia()
75
+ .withTexture()
76
+ .withDisplayObject()
77
+ .withPixiAnimationTarget()
78
+ .withAnimation();
64
79
  return this.builder.getComponent();
65
80
  }
66
- this.builder.withGif(); //.withDisplayObject();
81
+ this.builder.withGif().withPixiAnimationTarget().withAnimation();
67
82
  return this.builder.getComponent();
68
83
  }
69
84
  constructShape() {
@@ -72,14 +87,32 @@ export class ComponentDirector {
72
87
  if (shapeData.shape?.type === 'progress') {
73
88
  // Progress shapes use GPU-based PIXI Graphics rendering
74
89
  // Still need DisplayObjectHook to add the progress display object to the stage
75
- this.builder.withProgressShape().withDisplayObject();
90
+ this.builder
91
+ .withProgressShape()
92
+ .withDisplayObject()
93
+ .withPixiAnimationTarget()
94
+ .withAnimation();
76
95
  }
77
96
  else {
78
97
  // Regular shapes use canvas-based rendering
79
- this.builder.withCanvasShape().withTexture().withDisplayObject();
98
+ this.builder
99
+ .withCanvasShape()
100
+ .withTexture()
101
+ .withDisplayObject()
102
+ .withPixiAnimationTarget()
103
+ .withAnimation();
80
104
  }
81
105
  return this.builder.getComponent();
82
106
  }
107
+ constructFill() {
108
+ this.builder
109
+ .withCanvasFill()
110
+ .withTexture()
111
+ .withDisplayObject()
112
+ .withPixiAnimationTarget()
113
+ .withAnimation();
114
+ return this.builder.getComponent();
115
+ }
83
116
  constructSubtitle() {
84
117
  this.builder.withSubtitles().withHtmlText().withAnimation();
85
118
  if (this.sceneState.environment === 'server') {
@@ -128,11 +128,17 @@ const collectComponentTextVariants = (component, configuredLookup, output) => {
128
128
  if (!textAppearance) {
129
129
  return;
130
130
  }
131
- const family = normalizeFamily(textAppearance.fontFamily) ?? normalizeFamily(textAppearance.fontSource?.family);
131
+ const family = normalizeFamily(textAppearance.fontFamily) ??
132
+ normalizeFamily(textAppearance.fontSource?.family);
132
133
  if (!family) {
133
134
  return;
134
135
  }
135
136
  const configMatch = configuredLookup.get(family.toLowerCase());
137
+ // An unconfigured family is a browser/system-font request, not an implicit
138
+ // Google Fonts dependency. Remote loading must be explicit and reproducible.
139
+ if (!textAppearance.fontSource?.source && !configMatch) {
140
+ return;
141
+ }
136
142
  const source = (textAppearance.fontSource?.source ?? configMatch?.source ?? 'google');
137
143
  const fileUrl = normalizeFamily(textAppearance.fontSource?.fileUrl) ?? configMatch?.fileUrl ?? undefined;
138
144
  const weights = new Set();
@@ -191,6 +197,11 @@ export const discoverRequiredFontVariants = (sceneData, configuredFonts = []) =>
191
197
  if (component.type !== 'TEXT' && component.type !== 'SUBTITLES') {
192
198
  continue;
193
199
  }
200
+ if (component.type === 'TEXT' &&
201
+ typeof component.text === 'string' &&
202
+ !component.text.trim()) {
203
+ continue;
204
+ }
194
205
  collectComponentTextVariants(component, configuredLookup, variants);
195
206
  }
196
207
  }
@@ -482,6 +482,12 @@ export declare class Layer implements ILayer {
482
482
  unit: "px" | "em" | "rem" | "%";
483
483
  } | null | undefined;
484
484
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
485
+ padding?: number | {
486
+ top: number;
487
+ right: number;
488
+ bottom: number;
489
+ left: number;
490
+ } | undefined;
485
491
  shadow?: {
486
492
  enabled?: boolean | undefined;
487
493
  preset?: string | undefined;
@@ -1101,6 +1107,12 @@ export declare class Layer implements ILayer {
1101
1107
  unit: "px" | "em" | "rem" | "%";
1102
1108
  } | null | undefined;
1103
1109
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1110
+ padding?: number | {
1111
+ top: number;
1112
+ right: number;
1113
+ bottom: number;
1114
+ left: number;
1115
+ } | undefined;
1104
1116
  shadow?: {
1105
1117
  enabled?: boolean | undefined;
1106
1118
  preset?: string | undefined;
@@ -1694,6 +1706,12 @@ export declare class Layer implements ILayer {
1694
1706
  unit: "px" | "em" | "rem" | "%";
1695
1707
  } | null | undefined;
1696
1708
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
1709
+ padding?: number | {
1710
+ top: number;
1711
+ right: number;
1712
+ bottom: number;
1713
+ left: number;
1714
+ } | undefined;
1697
1715
  shadow?: {
1698
1716
  enabled?: boolean | undefined;
1699
1717
  preset?: string | undefined;
@@ -2285,6 +2303,12 @@ export declare class Layer implements ILayer {
2285
2303
  unit: "px" | "em" | "rem" | "%";
2286
2304
  } | null | undefined;
2287
2305
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
2306
+ padding?: number | {
2307
+ top: number;
2308
+ right: number;
2309
+ bottom: number;
2310
+ left: number;
2311
+ } | undefined;
2288
2312
  shadow?: {
2289
2313
  enabled?: boolean | undefined;
2290
2314
  preset?: string | undefined;
@@ -2910,6 +2934,12 @@ export declare class Layer implements ILayer {
2910
2934
  unit: "px" | "em" | "rem" | "%";
2911
2935
  } | null | undefined;
2912
2936
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
2937
+ padding?: number | {
2938
+ top: number;
2939
+ right: number;
2940
+ bottom: number;
2941
+ left: number;
2942
+ } | undefined;
2913
2943
  shadow?: {
2914
2944
  enabled?: boolean | undefined;
2915
2945
  preset?: string | undefined;
@@ -3505,6 +3535,12 @@ export declare class Layer implements ILayer {
3505
3535
  unit: "px" | "em" | "rem" | "%";
3506
3536
  } | null | undefined;
3507
3537
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
3538
+ padding?: number | {
3539
+ top: number;
3540
+ right: number;
3541
+ bottom: number;
3542
+ left: number;
3543
+ } | undefined;
3508
3544
  shadow?: {
3509
3545
  enabled?: boolean | undefined;
3510
3546
  preset?: string | undefined;
@@ -4051,6 +4087,12 @@ export declare class Layer implements ILayer {
4051
4087
  unit: "px" | "em" | "rem" | "%";
4052
4088
  } | null | undefined;
4053
4089
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4090
+ padding?: number | {
4091
+ top: number;
4092
+ right: number;
4093
+ bottom: number;
4094
+ left: number;
4095
+ } | undefined;
4054
4096
  shadow?: {
4055
4097
  enabled?: boolean | undefined;
4056
4098
  preset?: string | undefined;
@@ -4602,6 +4644,12 @@ export declare class Layer implements ILayer {
4602
4644
  unit: "px" | "em" | "rem" | "%";
4603
4645
  } | null | undefined;
4604
4646
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
4647
+ padding?: number | {
4648
+ top: number;
4649
+ right: number;
4650
+ bottom: number;
4651
+ left: number;
4652
+ } | undefined;
4605
4653
  shadow?: {
4606
4654
  enabled?: boolean | undefined;
4607
4655
  preset?: string | undefined;
@@ -5146,6 +5194,12 @@ export declare class Layer implements ILayer {
5146
5194
  unit: "px" | "em" | "rem" | "%";
5147
5195
  } | null | undefined;
5148
5196
  textTransform?: "none" | "uppercase" | "lowercase" | "capitalize" | undefined;
5197
+ padding?: number | {
5198
+ top: number;
5199
+ right: number;
5200
+ bottom: number;
5201
+ left: number;
5202
+ } | undefined;
5149
5203
  shadow?: {
5150
5204
  enabled?: boolean | undefined;
5151
5205
  preset?: string | undefined;
@@ -49,7 +49,7 @@ export class Layer {
49
49
  this.#displayObject.addChild(component.displayObject);
50
50
  }
51
51
  // resort components
52
- this.components.sort((a, b) => a.props.timeline.startAt - b.props.timeline.startAt);
52
+ this.components.sort((a, b) => a.props.order - b.props.order || a.props.timeline.startAt - b.props.timeline.startAt);
53
53
  this.#emit('layerschange');
54
54
  }
55
55
  syncDisplayObjects() {
@@ -92,29 +92,6 @@ export class Layer {
92
92
  }
93
93
  componentDisplayIndex += 1;
94
94
  }
95
- // Ensure deterministic ordering among only component display objects without
96
- // assuming a 1:1 mapping with all container children.
97
- if (typeof this.#displayObject.getChildIndex === 'function' &&
98
- typeof this.#displayObject.setChildIndex === 'function') {
99
- let nextIndex = 0;
100
- for (const component of this.components) {
101
- const displayObject = component.displayObject;
102
- if (!displayObject || displayObject.parent !== this.#displayObject) {
103
- continue;
104
- }
105
- const childCount = this.#displayObject.children?.length ?? 0;
106
- if (childCount === 0) {
107
- break;
108
- }
109
- const currentIndex = this.#displayObject.getChildIndex(displayObject);
110
- const targetIndex = Math.max(0, Math.min(nextIndex, childCount - 1));
111
- if (currentIndex >= 0 && currentIndex !== targetIndex) {
112
- this.#displayObject.setChildIndex(displayObject, targetIndex);
113
- changed = true;
114
- }
115
- nextIndex += 1;
116
- }
117
- }
118
95
  return changed;
119
96
  }
120
97
  removeComponent(component) {
@@ -124,7 +101,7 @@ export class Layer {
124
101
  if (component.displayObject) {
125
102
  this.#displayObject.removeChild(component.displayObject);
126
103
  }
127
- this.components.sort((a, b) => a.props.timeline.startAt - b.props.timeline.startAt);
104
+ this.components.sort((a, b) => a.props.order - b.props.order || a.props.timeline.startAt - b.props.timeline.startAt);
128
105
  this.#emit('layerschange');
129
106
  }
130
107
  }
@@ -45,6 +45,8 @@ export class AppManager {
45
45
  const { PixiPlugin } = await registerGsapPlugins();
46
46
  // give the plugin a reference to the PIXI object
47
47
  PixiPlugin.registerPIXI(PIXI);
48
+ const sceneBackground = this.state.data.settings.backgroundColor;
49
+ const solidBackground = typeof sceneBackground === 'string' ? sceneBackground : 'transparent';
48
50
  const baseOptions = {
49
51
  ...PIXI_DEFAULTS,
50
52
  preserveDrawingBuffer: environment === 'server',
@@ -52,8 +54,8 @@ export class AppManager {
52
54
  width,
53
55
  height,
54
56
  view: canvas,
55
- backgroundColor: environment === 'server' ? 'transparent' : '#ffffff',
56
- backgroundAlpha: environment === 'server' ? 0 : 1
57
+ backgroundColor: solidBackground,
58
+ backgroundAlpha: solidBackground === 'transparent' ? 0 : 1
57
59
  };
58
60
  let options = this.#buildRendererOptions(baseOptions, environment, rendererSelection);
59
61
  try {
@@ -175,10 +177,11 @@ export class AppManager {
175
177
  }
176
178
  #checkWebGLSupport() {
177
179
  try {
178
- if (typeof document === 'undefined') {
179
- return { supported: false, reason: 'WebGL unavailable: no document in server environment' };
180
+ const probe = this.dom?.canvas ??
181
+ (typeof document !== 'undefined' ? document.createElement('canvas') : null);
182
+ if (!probe || typeof probe.getContext !== 'function') {
183
+ return { supported: false, reason: 'WebGL unavailable: no canvas available for probing' };
180
184
  }
181
- const probe = document.createElement('canvas');
182
185
  if (this.preferWebGL2 && probe.getContext('webgl2')) {
183
186
  return { supported: true, reason: '' };
184
187
  }
@@ -24,9 +24,9 @@ export declare class ComponentsManager implements ResourceManager<IComponent, Co
24
24
  private isVisible;
25
25
  getAll(): IComponent[];
26
26
  getMediaComponents(): (Component & MediaComponent)[];
27
- update(componentId: string, data: Partial<AppearanceInput>, refreshType?: ComponentRefreshType): Promise<void>;
27
+ update(componentId: string, data: Partial<AppearanceInput>, refreshType?: ComponentRefreshType): void;
28
28
  get(componentId: string): IComponent | undefined;
29
- delete(componentId: string): void;
29
+ delete(componentId: string): Promise<void>;
30
30
  create(componentData: ComponentData): Promise<IComponent | null>;
31
31
  setOrder(id: string, order: number): void;
32
32
  moveUp(id: string): void;
@@ -39,11 +39,11 @@ export declare class ComponentsManager implements ResourceManager<IComponent, Co
39
39
  id: string;
40
40
  data: Partial<AppearanceInput>;
41
41
  }[]): void;
42
- bulkDelete(ids: string[]): void;
43
- hide(id: string): Promise<void>;
44
- show(id: string): Promise<void>;
45
- toggle(id: string): Promise<void>;
42
+ bulkDelete(ids: string[]): Promise<void>;
43
+ hide(id: string): void;
44
+ show(id: string): void;
45
+ toggle(id: string): void;
46
46
  filter(predicate: (component: IComponent) => boolean): IComponent[];
47
47
  isComponentVisible(componentId: string): boolean;
48
- destroy(): void;
48
+ destroy(): Promise<void>;
49
49
  }
@@ -6,6 +6,7 @@ import { PixiComponentBuilder } from '../builders/PixiComponentBuilder.js';
6
6
  import { ComponentState } from '../builders/_ComponentState.svelte.js';
7
7
  import { asClass, asValue } from 'awilix/browser';
8
8
  import { debounce } from 'lodash-es';
9
+ import { isTimeWithinTimeline } from '../utils/timelineWindow.js';
9
10
  // import { builtTextComponentForSubtitle } from '../utils/subtitles.js';
10
11
  export class ComponentsManager {
11
12
  components = $state(new Map());
@@ -28,7 +29,7 @@ export class ComponentsManager {
28
29
  this.initializeEventListeners();
29
30
  }
30
31
  initializeEventListeners() {
31
- this.eventManager.on('subtitleschange', this.debouncedRefreshSubtitles.bind(this));
32
+ this.eventManager.on('subtitleschange', this.debouncedRefreshSubtitles);
32
33
  }
33
34
  #scheduleSubtitleRefresh() {
34
35
  // Schedule the refresh on the next animation frame to avoid blocking
@@ -43,7 +44,7 @@ export class ComponentsManager {
43
44
  const { currentTime, duration } = this.state;
44
45
  const startAt = component.props.timeline.startAt || 0;
45
46
  const endAt = component.props.timeline.endAt || duration;
46
- return currentTime >= startAt && currentTime <= endAt;
47
+ return isTimeWithinTimeline(currentTime, startAt, endAt);
47
48
  }
48
49
  getAll() {
49
50
  return Array.from(this.components.values());
@@ -57,10 +58,10 @@ export class ComponentsManager {
57
58
  component.refresh(refreshType);
58
59
  }
59
60
  }
60
- async update(componentId, data, refreshType = 'refresh') {
61
+ update(componentId, data, refreshType = 'refresh') {
61
62
  const component = this.get(componentId);
62
63
  if (component) {
63
- await component.props.updateAppearance(data);
64
+ component.props.updateAppearance(data);
64
65
  // Only manually refresh if auto-refresh is not enabled
65
66
  if (!component.autoRefresh) {
66
67
  this.#refreshComponent(component, refreshType);
@@ -70,7 +71,7 @@ export class ComponentsManager {
70
71
  get(componentId) {
71
72
  return this.components.get(componentId);
72
73
  }
73
- delete(componentId) {
74
+ async delete(componentId) {
74
75
  const component = this.components.get(componentId);
75
76
  if (component) {
76
77
  const layers = this.layersManager.getAll();
@@ -82,8 +83,8 @@ export class ComponentsManager {
82
83
  this.layersManager.delete(componentLayer.id);
83
84
  }
84
85
  }
85
- component.destroy();
86
86
  this.components.delete(componentId);
87
+ await component.destroy();
87
88
  }
88
89
  }
89
90
  async #buildComponent(componentData) {
@@ -236,25 +237,30 @@ export class ComponentsManager {
236
237
  bulkUpdate(updates) {
237
238
  updates.forEach(({ id, data }) => this.update(id, data));
238
239
  }
239
- bulkDelete(ids) {
240
- ids.forEach((id) => this.delete(id));
240
+ async bulkDelete(ids) {
241
+ const results = await Promise.allSettled(ids.map((id) => this.delete(id)));
242
+ const errors = results
243
+ .filter((result) => result.status === 'rejected')
244
+ .map((result) => result.reason);
245
+ if (errors.length > 0)
246
+ throw new AggregateError(errors, 'Component deletion failed.');
241
247
  }
242
- async hide(id) {
248
+ hide(id) {
243
249
  const component = this.get(id);
244
250
  if (component) {
245
- await component.props.setVisible(false);
251
+ component.props.setVisible(false);
246
252
  }
247
253
  }
248
- async show(id) {
254
+ show(id) {
249
255
  const component = this.get(id);
250
256
  if (component) {
251
- await component.props.setVisible(true);
257
+ component.props.setVisible(true);
252
258
  }
253
259
  }
254
- async toggle(id) {
260
+ toggle(id) {
255
261
  const component = this.get(id);
256
262
  if (component) {
257
- await component.props.setVisible(!component.props.visible);
263
+ component.props.setVisible(!component.props.visible);
258
264
  }
259
265
  }
260
266
  filter(predicate) {
@@ -267,8 +273,16 @@ export class ComponentsManager {
267
273
  }
268
274
  return this.isVisible(component);
269
275
  }
270
- destroy() {
276
+ async destroy() {
271
277
  this.eventManager.removeEventListener('subtitleschange', this.debouncedRefreshSubtitles);
272
278
  this.debouncedRefreshSubtitles.cancel(); // Cancel any pending debounced calls
279
+ const components = [...this.components.values()];
280
+ this.components.clear();
281
+ const results = await Promise.allSettled(components.map((component) => component.destroy()));
282
+ const errors = results
283
+ .filter((result) => result.status === 'rejected')
284
+ .map((result) => result.reason);
285
+ if (errors.length > 0)
286
+ throw new AggregateError(errors, 'Component teardown failed.');
273
287
  }
274
288
  }