@editframe/elements 0.15.0-beta.8 → 0.16.0-beta.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.
Files changed (52) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +14 -10
  2. package/dist/EF_FRAMEGEN.js +17 -28
  3. package/dist/elements/EFCaptions.js +0 -7
  4. package/dist/elements/EFImage.js +0 -4
  5. package/dist/elements/EFMedia.d.ts +13 -7
  6. package/dist/elements/EFMedia.js +217 -111
  7. package/dist/elements/EFSourceMixin.js +2 -1
  8. package/dist/elements/EFTemporal.browsertest.d.ts +4 -3
  9. package/dist/elements/EFTemporal.d.ts +14 -11
  10. package/dist/elements/EFTemporal.js +63 -87
  11. package/dist/elements/EFTimegroup.d.ts +2 -4
  12. package/dist/elements/EFTimegroup.js +15 -103
  13. package/dist/elements/EFVideo.js +3 -1
  14. package/dist/elements/EFWaveform.d.ts +3 -2
  15. package/dist/elements/EFWaveform.js +39 -26
  16. package/dist/elements/durationConverter.d.ts +8 -8
  17. package/dist/elements/durationConverter.js +2 -2
  18. package/dist/elements/updateAnimations.d.ts +9 -0
  19. package/dist/elements/updateAnimations.js +62 -0
  20. package/dist/getRenderInfo.d.ts +51 -0
  21. package/dist/getRenderInfo.js +72 -0
  22. package/dist/gui/EFFilmstrip.js +7 -16
  23. package/dist/gui/EFFitScale.d.ts +27 -0
  24. package/dist/gui/EFFitScale.js +138 -0
  25. package/dist/gui/EFWorkbench.d.ts +2 -5
  26. package/dist/gui/EFWorkbench.js +13 -56
  27. package/dist/gui/TWMixin.css.js +1 -1
  28. package/dist/gui/TWMixin.js +14 -2
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +6 -1
  31. package/dist/style.css +6 -3
  32. package/package.json +9 -4
  33. package/src/elements/EFCaptions.browsertest.ts +2 -2
  34. package/src/elements/EFCaptions.ts +0 -7
  35. package/src/elements/EFImage.browsertest.ts +2 -2
  36. package/src/elements/EFImage.ts +0 -4
  37. package/src/elements/EFMedia.browsertest.ts +14 -14
  38. package/src/elements/EFMedia.ts +291 -136
  39. package/src/elements/EFSourceMixin.ts +4 -4
  40. package/src/elements/EFTemporal.browsertest.ts +64 -31
  41. package/src/elements/EFTemporal.ts +99 -119
  42. package/src/elements/EFTimegroup.ts +15 -133
  43. package/src/elements/EFVideo.ts +3 -1
  44. package/src/elements/EFWaveform.ts +54 -39
  45. package/src/elements/durationConverter.ts +9 -4
  46. package/src/elements/updateAnimations.ts +88 -0
  47. package/src/gui/ContextMixin.ts +0 -3
  48. package/src/gui/EFFilmstrip.ts +7 -16
  49. package/src/gui/EFFitScale.ts +152 -0
  50. package/src/gui/EFWorkbench.ts +18 -65
  51. package/src/gui/TWMixin.ts +19 -2
  52. package/types.json +1 -1
@@ -45,8 +45,15 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
45
45
  type: String,
46
46
  attribute: "mode",
47
47
  })
48
- mode: "roundBars" | "bars" | "bricks" | "line" | "pixel" | "wave" | "spikes" =
49
- "bars";
48
+ mode:
49
+ | "roundBars"
50
+ | "bars"
51
+ | "bricks"
52
+ | "line"
53
+ | "curve"
54
+ | "pixel"
55
+ | "wave"
56
+ | "spikes" = "bars";
50
57
 
51
58
  @property({ type: String })
52
59
  color = "currentColor";
@@ -54,6 +61,9 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
54
61
  @property({ type: String, reflect: true })
55
62
  target = "";
56
63
 
64
+ @property({ type: Number, attribute: "bar-spacing" })
65
+ barSpacing = 0.5;
66
+
57
67
  @state()
58
68
  targetElement: EFAudio | EFVideo | null = null;
59
69
 
@@ -147,7 +157,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
147
157
  const waveHeight = canvas.height;
148
158
 
149
159
  const totalBars = frequencyData.length;
150
- const paddingInner = 0.5;
160
+ const paddingInner = this.barSpacing;
151
161
  const paddingOuter = 0.01;
152
162
  const availableWidth = waveWidth;
153
163
  const barWidth =
@@ -157,7 +167,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
157
167
  const path = new Path2D();
158
168
 
159
169
  frequencyData.forEach((value, i) => {
160
- const normalizedValue = Math.min((value / 255) * 2, 1);
170
+ const normalizedValue = value / 255;
161
171
  const barHeight = normalizedValue * waveHeight;
162
172
  const y = (waveHeight - barHeight) / 2;
163
173
  const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
@@ -183,7 +193,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
183
193
  const maxBricks = Math.floor(waveHeight / (boxSize + verticalGap)); // Account for gaps in height calculation
184
194
 
185
195
  frequencyData.forEach((value, i) => {
186
- const normalizedValue = Math.min((value / 255) * 2, 1);
196
+ const normalizedValue = value / 255;
187
197
  const brickCount = Math.floor(normalizedValue * maxBricks);
188
198
 
189
199
  for (let j = 0; j < brickCount; j++) {
@@ -206,7 +216,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
206
216
 
207
217
  // Similar padding calculation as drawBars
208
218
  const totalBars = frequencyData.length;
209
- const paddingInner = 0.5;
219
+ const paddingInner = this.barSpacing;
210
220
  const paddingOuter = 0.01;
211
221
  const availableWidth = waveWidth;
212
222
  const barWidth =
@@ -218,7 +228,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
218
228
  const path = new Path2D();
219
229
 
220
230
  frequencyData.forEach((value, i) => {
221
- const normalizedValue = Math.min((value / 255) * 2, 1);
231
+ const normalizedValue = value / 255;
222
232
  const height = normalizedValue * waveHeight; // Use full wave height like in drawBars
223
233
  const x = waveWidth * paddingOuter + i * (barWidth * (1 + paddingInner));
224
234
  const y = (waveHeight - height) / 2; // Center vertically
@@ -231,52 +241,49 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
231
241
  ctx.fill(path);
232
242
  }
233
243
 
234
- protected drawEqualizer(
235
- ctx: CanvasRenderingContext2D,
236
- frequencyData: Uint8Array,
237
- ) {
244
+ protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
238
245
  const canvas = ctx.canvas;
239
246
  const waveWidth = canvas.width;
240
247
  const waveHeight = canvas.height;
241
- const baseline = waveHeight / 2;
242
- const barWidth = (waveWidth / frequencyData.length) * 0.8;
243
248
 
244
249
  ctx.clearRect(0, 0, waveWidth, waveHeight);
250
+ const path = new Path2D();
245
251
 
246
- // Create paths for baseline and bars
247
- const baselinePath = new Path2D();
248
- const barsPath = new Path2D();
249
-
250
- // Draw baseline
251
- baselinePath.moveTo(0, baseline);
252
- baselinePath.lineTo(waveWidth, baseline);
252
+ // Sample fewer points to make sharp angles more visible
253
+ const sampleRate = 1; // Only use every 4th point
253
254
 
254
- // Draw bars
255
- frequencyData.forEach((value, i) => {
256
- const height = (value / 255) * (waveHeight / 2);
257
- const x = i * (waveWidth / frequencyData.length);
258
- const y = baseline - height;
259
- barsPath.rect(x, y, barWidth, Math.max(height * 2, 1));
260
- });
255
+ for (let i = 0; i < frequencyData.length; i += sampleRate) {
256
+ const x = (i / frequencyData.length) * waveWidth;
257
+ const y = (1 - (frequencyData[i] ?? 0) / 255) * waveHeight;
261
258
 
262
- // Render baseline
263
- ctx.lineWidth = 2;
264
- ctx.stroke(baselinePath);
259
+ if (i === 0) {
260
+ path.moveTo(x, y);
261
+ } else {
262
+ path.lineTo(x, y);
263
+ }
264
+ }
265
+ // Ensure we draw to the end
266
+ const lastX = waveWidth;
267
+ const lastY =
268
+ (1 - (frequencyData[frequencyData.length - 1] ?? 0) / 255) * waveHeight;
269
+ path.lineTo(lastX, lastY);
265
270
 
266
- // Render bars
267
- ctx.fill(barsPath);
271
+ ctx.lineWidth = this.lineWidth;
272
+ ctx.stroke(path);
268
273
  }
269
274
 
270
- protected drawLine(ctx: CanvasRenderingContext2D, frequencyData: Uint8Array) {
275
+ protected drawCurve(
276
+ ctx: CanvasRenderingContext2D,
277
+ frequencyData: Uint8Array,
278
+ ) {
271
279
  const canvas = ctx.canvas;
272
280
  const waveWidth = canvas.width;
273
281
  const waveHeight = canvas.height;
274
282
 
275
283
  ctx.clearRect(0, 0, waveWidth, waveHeight);
276
-
277
- // Create a single Path2D object for the curve
278
284
  const path = new Path2D();
279
285
 
286
+ // Draw smooth curves between points using quadratic curves
280
287
  frequencyData.forEach((value, i) => {
281
288
  const x = (i / frequencyData.length) * waveWidth;
282
289
  const y = (1 - value / 255) * waveHeight;
@@ -284,7 +291,11 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
284
291
  if (i === 0) {
285
292
  path.moveTo(x, y);
286
293
  } else {
287
- path.lineTo(x, y);
294
+ const prevX = ((i - 1) / frequencyData.length) * waveWidth;
295
+ const prevY = (1 - (frequencyData[i - 1] ?? 0) / 255) * waveHeight;
296
+ const xc = (prevX + x) / 2;
297
+ const yc = (prevY + y) / 2;
298
+ path.quadraticCurveTo(prevX, prevY, xc, yc);
288
299
  }
289
300
  });
290
301
 
@@ -306,7 +317,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
306
317
  const path = new Path2D();
307
318
 
308
319
  frequencyData.forEach((value, i) => {
309
- const normalizedValue = Math.min((value / 255) * 2, 1); // Updated normalization
320
+ const normalizedValue = value / 255;
310
321
  const x = i * (waveWidth / frequencyData.length);
311
322
  const barHeight = normalizedValue * (waveHeight / 2); // Half height since we extend both ways
312
323
  const y = baseline - barHeight;
@@ -469,7 +480,8 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
469
480
  if (!ctx) return;
470
481
 
471
482
  const frequencyData = this.targetElement.frequencyDataTask.value;
472
- if (!frequencyData) return;
483
+ const byteTimeData = this.targetElement.byteTimeDomainTask.value;
484
+ if (!frequencyData || !byteTimeData) return;
473
485
 
474
486
  ctx.save();
475
487
  if (this.color === "currentColor") {
@@ -490,7 +502,10 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
490
502
  this.drawBricks(ctx, frequencyData);
491
503
  break;
492
504
  case "line":
493
- this.drawLine(ctx, frequencyData);
505
+ this.drawLine(ctx, byteTimeData);
506
+ break;
507
+ case "curve":
508
+ this.drawCurve(ctx, byteTimeData);
494
509
  break;
495
510
  case "pixel":
496
511
  this.drawPixel(ctx, frequencyData);
@@ -1,19 +1,24 @@
1
1
  import { parseTimeToMs } from "./parseTimeToMs.js";
2
2
 
3
3
  export const durationConverter = {
4
- fromAttribute: (value: string): number => parseTimeToMs(value),
5
- toAttribute: (value: number) => `${value}s`,
4
+ fromAttribute: (value: string | null) =>
5
+ value === null ? null : parseTimeToMs(value),
6
+ toAttribute: (value: number | null) => (value === null ? null : `${value}s`),
6
7
  };
7
8
 
8
9
  const positiveDurationConverter = (error: string) => {
9
10
  return {
10
- fromAttribute: (value: string): number => {
11
+ fromAttribute: (value: string | null): number | null => {
12
+ if (value === null) {
13
+ return null;
14
+ }
11
15
  if (value.startsWith("-")) {
12
16
  throw new Error(error);
13
17
  }
14
18
  return parseTimeToMs(value);
15
19
  },
16
- toAttribute: (value: number) => `${value}s`,
20
+ toAttribute: (value: number | null) =>
21
+ value === null ? null : `${value}s`,
17
22
  };
18
23
  };
19
24
 
@@ -0,0 +1,88 @@
1
+ import { isEFTemporal } from "./EFTemporal.ts";
2
+ import type { EFTimegroup } from "./EFTimegroup.ts";
3
+
4
+ export const updateAnimations = (
5
+ element: HTMLElement & {
6
+ currentTimeMs: number;
7
+ durationMs: number;
8
+ rootTimegroup?: EFTimegroup;
9
+ parentTimegroup?: EFTimegroup;
10
+ startTimeMs: number;
11
+ endTimeMs: number;
12
+ },
13
+ ) => {
14
+ element.style.setProperty(
15
+ "--ef-progress",
16
+ `${Math.max(0, Math.min(1, element.currentTimeMs / element.durationMs)) * 100}%`,
17
+ );
18
+ const timelineTimeMs = (element.rootTimegroup ?? element).currentTimeMs;
19
+ if (
20
+ element.startTimeMs > timelineTimeMs ||
21
+ element.endTimeMs < timelineTimeMs
22
+ ) {
23
+ element.style.display = "none";
24
+ return;
25
+ }
26
+ element.style.display = "";
27
+ const animations = element.getAnimations({ subtree: true });
28
+ element.style.setProperty("--ef-duration", `${element.durationMs}ms`);
29
+ element.style.setProperty(
30
+ "--ef-transition-duration",
31
+ `${element.parentTimegroup?.overlapMs ?? 0}ms`,
32
+ );
33
+ element.style.setProperty(
34
+ "--ef-transition-out-start",
35
+ `${element.durationMs - (element.parentTimegroup?.overlapMs ?? 0)}ms`,
36
+ );
37
+
38
+ for (const animation of animations) {
39
+ if (animation.playState === "running") {
40
+ animation.pause();
41
+ }
42
+ const effect = animation.effect;
43
+ if (!(effect && effect instanceof KeyframeEffect)) {
44
+ continue;
45
+ }
46
+ const target = effect.target;
47
+ // TODO: better generalize work avoidance for temporal elements
48
+ if (!target) {
49
+ continue;
50
+ }
51
+ if (target.closest("ef-timegroup") !== element) {
52
+ continue;
53
+ }
54
+
55
+ const timing = effect.getTiming();
56
+ const duration = Number(timing.duration) ?? 0;
57
+ const delay = Number(timing.delay) ?? 0;
58
+ const iterations = Number(timing.iterations) ?? 1;
59
+
60
+ const timeTarget = isEFTemporal(target)
61
+ ? target
62
+ : target.closest("ef-timegroup");
63
+ if (!timeTarget) {
64
+ continue;
65
+ }
66
+
67
+ const currentTime = timeTarget.ownCurrentTimeMs;
68
+
69
+ // Handle delay - don't start animation until delay is complete
70
+ if (currentTime < delay) {
71
+ animation.currentTime = 0;
72
+ continue;
73
+ }
74
+
75
+ const currentIteration = Math.floor((currentTime - delay) / duration);
76
+ const currentIterationTime = (currentTime - delay) % duration;
77
+
78
+ if (currentIteration >= iterations) {
79
+ // Stop just before the end to prevent DOM removal
80
+ animation.currentTime = duration - 0.01;
81
+ continue;
82
+ }
83
+
84
+ // Ensure we never reach exactly duration
85
+ animation.currentTime =
86
+ Math.min(currentIterationTime, duration - 0.01) + delay;
87
+ }
88
+ };
@@ -129,9 +129,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
129
129
  @property({ type: Boolean, reflect: true })
130
130
  loop = false;
131
131
 
132
- // @state()
133
- // private stageScale = 1;
134
-
135
132
  @property({ type: Boolean })
136
133
  rendering = false;
137
134
 
@@ -87,31 +87,22 @@ class FilmstripItem extends TWMixin(LitElement) {
87
87
  @property({ type: Number })
88
88
  pixelsPerMs = 0.04;
89
89
 
90
+ // Gutter styles represent the entire source media.
91
+ // If there is no trim, then the gutter and trim portion are the same.
90
92
  get gutterStyles() {
91
- if (this.element.sourceInMs || this.element.sourceOutMs) {
92
- return {
93
- position: "relative",
94
- left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs - this.element.sourceInMs)}px`,
95
- width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs + this.element.sourceOutMs + this.element.sourceInMs)}px`,
96
- };
97
- }
98
93
  return {
99
94
  position: "relative",
100
- left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs)}px`,
101
- width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs)}px`,
95
+ left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.sourceStartMs)}px`,
96
+ width: `${this.pixelsPerMs * (this.element.intrinsicDurationMs ?? this.element.durationMs)}px`,
102
97
  };
103
98
  }
104
99
 
100
+ // Trim portion is the section of source that will be placed in the timeline
101
+ // If there is no trim, then the gutter and trim portion are the same.
105
102
  get trimPortionStyles() {
106
- if (this.element.sourceInMs || this.element.sourceOutMs) {
107
- return {
108
- width: `${this.pixelsPerMs * this.element.durationMs}px`,
109
- left: `${this.pixelsPerMs * (this.element.trimStartMs + this.element.sourceInMs)}px`,
110
- };
111
- }
112
103
  return {
113
104
  width: `${this.pixelsPerMs * this.element.durationMs}px`,
114
- left: `${this.pixelsPerMs * this.element.trimStartMs}px`,
105
+ left: `${this.pixelsPerMs * this.element.sourceStartMs}px`,
115
106
  };
116
107
  }
117
108
 
@@ -0,0 +1,152 @@
1
+ import { LitElement } from "lit";
2
+ import { customElement, state } from "lit/decorators.js";
3
+ import { createRef } from "lit/directives/ref.js";
4
+
5
+ @customElement("ef-fit-scale")
6
+ export class EFFitScale extends LitElement {
7
+ containerRef = createRef<HTMLDivElement>();
8
+ contentRef = createRef<HTMLSlotElement>();
9
+
10
+ createRenderRoot() {
11
+ Object.assign(this.style, {
12
+ display: "grid",
13
+ width: "100%",
14
+ height: "100%",
15
+ gridTemplateColumns: "100%",
16
+ gridTemplateRows: "100%",
17
+ overflow: "hidden",
18
+ boxSizing: "border-box",
19
+ contain: "strict",
20
+ position: "relative",
21
+ });
22
+ this.id = `${this.uniqueId}`;
23
+ return this;
24
+ }
25
+
26
+ uniqueId = Math.random().toString(36).substring(2, 15);
27
+
28
+ @state()
29
+ private scale = 1;
30
+
31
+ private animationFrameId?: number;
32
+
33
+ get contentChild() {
34
+ const firstElement = this.children[0];
35
+ if (!firstElement) return null;
36
+
37
+ let current: Element = firstElement;
38
+ while (current) {
39
+ if (current instanceof HTMLSlotElement) {
40
+ const assigned = current.assignedElements()[0];
41
+ if (!assigned) break;
42
+ current = assigned;
43
+ continue;
44
+ }
45
+
46
+ const display = window.getComputedStyle(current).display;
47
+ if (display !== "contents" && display !== "none") {
48
+ return current as HTMLElement;
49
+ }
50
+ const firstChild = current.children[0];
51
+ if (!firstChild) break;
52
+ current = firstChild;
53
+ }
54
+ return firstElement as HTMLElement; // Fallback to first element if no non-contents found
55
+ }
56
+
57
+ get scaleInfo() {
58
+ if (!this.contentChild) {
59
+ return {
60
+ scale: 1,
61
+ containerWidth: 0,
62
+ containerHeight: 0,
63
+ contentWidth: 0,
64
+ contentHeight: 0,
65
+ };
66
+ }
67
+
68
+ const containerWidth = this.clientWidth;
69
+ const containerHeight = this.clientHeight;
70
+ const contentWidth = this.contentChild.clientWidth;
71
+ const contentHeight = this.contentChild.clientHeight;
72
+
73
+ const containerRatio = containerWidth / containerHeight;
74
+ const contentRatio = contentWidth / contentHeight;
75
+
76
+ const scale =
77
+ containerRatio > contentRatio
78
+ ? containerHeight / contentHeight
79
+ : containerWidth / contentWidth;
80
+
81
+ return {
82
+ scale,
83
+ containerWidth,
84
+ containerHeight,
85
+ contentWidth,
86
+ contentHeight,
87
+ };
88
+ }
89
+
90
+ scaleLastSetOn: HTMLElement | null = null;
91
+
92
+ setScale = () => {
93
+ if (this.isConnected) {
94
+ const { scale } = this.scaleInfo;
95
+ if (this.contentChild) {
96
+ const containerRect = this.getBoundingClientRect();
97
+ const contentRect = this.contentChild.getBoundingClientRect();
98
+
99
+ const unscaledWidth = contentRect.width / this.scale;
100
+ const unscaledHeight = contentRect.height / this.scale;
101
+ const scaledWidth = unscaledWidth * scale;
102
+ const scaledHeight = unscaledHeight * scale;
103
+ const translateX = (containerRect.width - scaledWidth) / 2;
104
+ const translateY = (containerRect.height - scaledHeight) / 2;
105
+
106
+ // In the rare event that the content child is changed, we need to remove the scale
107
+ // because we don't want to have a scale on the old content child that is somewhere else in the DOM
108
+ if (this.scaleLastSetOn !== this.contentChild) {
109
+ this.removeScale();
110
+ }
111
+ // Use toFixed to avoid floating point precision issues
112
+ // this will update every frame with sub-pixel changes if we don't pin it down
113
+ Object.assign(this.contentChild.style, {
114
+ transform: `translate(${translateX.toFixed(4)}px, ${translateY.toFixed(4)}px) scale(${scale.toFixed(4)})`,
115
+ transformOrigin: "top left",
116
+ });
117
+ this.scale = scale;
118
+ this.scaleLastSetOn = this.contentChild;
119
+ }
120
+ this.animationFrameId = requestAnimationFrame(this.setScale);
121
+ }
122
+ };
123
+
124
+ removeScale = () => {
125
+ if (this.scaleLastSetOn) {
126
+ Object.assign(this.scaleLastSetOn.style, {
127
+ transform: "",
128
+ transformOrigin: "",
129
+ });
130
+ this.scaleLastSetOn = null;
131
+ }
132
+ };
133
+
134
+ connectedCallback(): void {
135
+ super.connectedCallback();
136
+ this.animationFrameId = requestAnimationFrame(this.setScale);
137
+ }
138
+
139
+ disconnectedCallback(): void {
140
+ super.disconnectedCallback();
141
+ this.removeScale();
142
+ if (this.animationFrameId) {
143
+ cancelAnimationFrame(this.animationFrameId);
144
+ }
145
+ }
146
+ }
147
+
148
+ declare global {
149
+ interface HTMLElementTagNameMap {
150
+ "ef-fit-scale": EFFitScale;
151
+ }
152
+ }
@@ -1,5 +1,5 @@
1
1
  import { LitElement, type PropertyValueMap, css, html } from "lit";
2
- import { customElement, eventOptions, state } from "lit/decorators.js";
2
+ import { customElement, eventOptions, property } from "lit/decorators.js";
3
3
  import { createRef, ref } from "lit/directives/ref.js";
4
4
 
5
5
  import { ContextMixin } from "./ContextMixin.js";
@@ -17,69 +17,22 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
17
17
  `,
18
18
  ];
19
19
 
20
- stageRef = createRef<HTMLDivElement>();
21
- canvasRef = createRef<HTMLSlotElement>();
20
+ @property({ type: Boolean })
21
+ rendering = false;
22
+
23
+ focusOverlay = createRef<HTMLDivElement>();
22
24
 
23
25
  @eventOptions({ passive: false, capture: true })
24
26
  handleStageWheel(event: WheelEvent) {
25
27
  event.preventDefault();
26
28
  }
27
29
 
28
- focusOverlay = createRef<HTMLDivElement>();
29
-
30
- @state()
31
- private stageScale = 1;
32
-
33
- setStageScale = () => {
34
- if (this.isConnected && !this.rendering) {
35
- const canvasElement = this.canvasRef.value;
36
- const stageElement = this.stageRef.value;
37
- const canvasChild = canvasElement?.assignedElements()[0];
38
- if (stageElement && canvasElement && canvasChild) {
39
- // Determine the appropriate scale factor to make the canvas fit into
40
- canvasElement.style.width = `${canvasChild.clientWidth}px`;
41
- canvasElement.style.height = `${canvasChild.clientHeight}px`;
42
- const stageWidth = stageElement.clientWidth;
43
- const stageHeight = stageElement.clientHeight;
44
- const canvasWidth = canvasElement.clientWidth;
45
- const canvasHeight = canvasElement.clientHeight;
46
- const stageRatio = stageWidth / stageHeight;
47
- const canvasRatio = canvasWidth / canvasHeight;
48
-
49
- if (stageRatio > canvasRatio) {
50
- const scale = stageHeight / canvasHeight;
51
- if (this.stageScale !== scale) {
52
- canvasElement.style.transform = `scale(${scale})`;
53
- canvasElement.style.transformOrigin = "top center";
54
- }
55
- this.stageScale = scale;
56
- } else {
57
- const scale = stageWidth / canvasWidth;
58
- if (this.stageScale !== scale) {
59
- canvasElement.style.transform = `scale(${scale})`;
60
- canvasElement.style.transformOrigin = "center";
61
- }
62
- this.stageScale = scale;
63
- }
64
- }
65
- }
66
- if (this.isConnected) {
67
- requestAnimationFrame(this.setStageScale);
68
- }
69
- };
70
-
71
30
  connectedCallback(): void {
72
- // Set the body and html to 100% width and height to avoid scaling issues
73
- // this is a hack to avoid scaling issues when the stage is scaled and during output rendering
74
- // What I've discovered recently is that having the workbench be so smart as to determine
75
- // how it should be displayed at render time causes problems. Much of that has been extracted
76
- // but this is a quick fix to avoid scaling issues.
77
31
  document.body.style.width = "100%";
78
32
  document.body.style.height = "100%";
79
33
  document.documentElement.style.width = "100%";
80
34
  document.documentElement.style.height = "100%";
81
35
  super.connectedCallback();
82
- requestAnimationFrame(this.setStageScale);
83
36
  }
84
37
 
85
38
  disconnectedCallback(): void {
@@ -121,30 +74,30 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
121
74
  };
122
75
 
123
76
  render() {
124
- if (this.rendering) {
77
+ // TODO: this.rendering is not correctly set when using the framegen bridge
78
+ // so to hack we're checking for the existence of EF_RENDERING on the window
79
+ if (
80
+ this.rendering ||
81
+ (typeof window !== "undefined" && window.EF_RENDERING?.() === true)
82
+ ) {
83
+ console.log("WORKBENCH RENDERING“");
125
84
  return html`
126
- <slot
127
- ${ref(this.canvasRef)}
128
- class="fixed inset-0 h-full w-full"
129
- name="canvas"
130
- ></slot>
85
+ <slot class="fixed inset-0 h-full w-full" name="canvas"></slot>
131
86
  `;
132
87
  }
88
+ console.log("WORKBENCH NOT RENDERING");
133
89
  return html`
134
90
  <div
135
91
  class="grid h-full w-full bg-slate-800"
136
92
  style="grid-template-rows: 1fr 300px; grid-template-columns: 100%;"
137
93
  >
138
94
  <div
139
- ${ref(this.stageRef)}
140
- class="relative grid h-full w-full justify-center overflow-hidden"
95
+ class="relative h-full w-full overflow-hidden"
141
96
  @wheel=${this.handleStageWheel}
142
97
  >
143
- <slot
144
- ${ref(this.canvasRef)}
145
- name="canvas"
146
- class="inline-block"
147
- ></slot>
98
+ <ef-fit-scale class="h-full grid place-content-center">
99
+ <slot name="canvas" class="contents"></slot>
100
+ </ef-fit-scale>
148
101
  <div
149
102
  class="border border-blue-500 bg-blue-200 bg-opacity-20 absolute"
150
103
  ${ref(this.focusOverlay)}
@@ -1,4 +1,4 @@
1
- import type { LitElement } from "lit";
1
+ import type { CSSResult, LitElement } from "lit";
2
2
  // @ts-expect-error cannot figure out how to declare this module as a string
3
3
  import twStyle from "./TWMixin.css?inline";
4
4
 
@@ -21,13 +21,30 @@ export function TWMixin<T extends new (...args: any[]) => LitElement>(Base: T) {
21
21
  "twSheet not found. Probable cause: CSSStyleSheet not supported in this environment",
22
22
  );
23
23
  }
24
+
25
+ const constructorStylesheets: CSSStyleSheet[] = [];
26
+ const constructorStyles = (("styles" in this.constructor &&
27
+ this.constructor.styles) ||
28
+ []) as CSSResult | CSSResult[];
29
+
30
+ if (Array.isArray(constructorStyles)) {
31
+ for (const item of constructorStyles) {
32
+ if (item.styleSheet) {
33
+ constructorStylesheets.push(item.styleSheet);
34
+ }
35
+ }
36
+ } else if (constructorStyles.styleSheet) {
37
+ constructorStylesheets.push(constructorStyles.styleSheet);
38
+ }
39
+
24
40
  if (renderRoot?.adoptedStyleSheets) {
25
41
  renderRoot.adoptedStyleSheets = [
26
42
  twSheet,
27
43
  ...renderRoot.adoptedStyleSheets,
44
+ ...constructorStylesheets,
28
45
  ];
29
46
  } else {
30
- renderRoot.adoptedStyleSheets = [twSheet];
47
+ renderRoot.adoptedStyleSheets = [twSheet, ...constructorStylesheets];
31
48
  }
32
49
  return renderRoot;
33
50
  }