@editframe/elements 0.15.0-beta.18 → 0.15.0-beta.20
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/dist/EF_FRAMEGEN.js +1 -0
- package/dist/elements/EFMedia.d.ts +7 -3
- package/dist/elements/EFMedia.js +42 -90
- package/dist/elements/EFTemporal.browsertest.d.ts +4 -3
- package/dist/elements/EFTemporal.d.ts +14 -11
- package/dist/elements/EFTemporal.js +63 -87
- package/dist/elements/EFTimegroup.d.ts +1 -1
- package/dist/elements/EFTimegroup.js +12 -107
- package/dist/elements/EFVideo.js +3 -1
- package/dist/elements/durationConverter.d.ts +8 -8
- package/dist/elements/durationConverter.js +2 -2
- package/dist/elements/updateAnimations.d.ts +9 -0
- package/dist/elements/updateAnimations.js +62 -0
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/EFFilmstrip.js +7 -16
- package/dist/gui/EFFitScale.d.ts +27 -0
- package/dist/gui/EFFitScale.js +137 -0
- package/dist/gui/EFWorkbench.d.ts +1 -5
- package/dist/gui/EFWorkbench.js +7 -54
- package/dist/gui/TWMixin.css.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/style.css +0 -6
- package/package.json +2 -2
- package/src/elements/EFMedia.browsertest.ts +10 -10
- package/src/elements/EFMedia.ts +48 -118
- package/src/elements/EFTemporal.browsertest.ts +64 -31
- package/src/elements/EFTemporal.ts +99 -119
- package/src/elements/EFTimegroup.ts +12 -131
- package/src/elements/EFVideo.ts +3 -1
- package/src/elements/durationConverter.ts +9 -4
- package/src/elements/updateAnimations.ts +88 -0
- package/src/gui/ContextMixin.ts +0 -3
- package/src/gui/EFFilmstrip.ts +7 -16
- package/src/gui/EFFitScale.ts +151 -0
- package/src/gui/EFWorkbench.ts +13 -63
- package/types.json +1 -1
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -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.
|
|
101
|
-
width: `${this.pixelsPerMs * (this.element.
|
|
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.
|
|
105
|
+
left: `${this.pixelsPerMs * this.element.sourceStartMs}px`,
|
|
115
106
|
};
|
|
116
107
|
}
|
|
117
108
|
|
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
if (this.animationFrameId) {
|
|
142
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
declare global {
|
|
148
|
+
interface HTMLElementTagNameMap {
|
|
149
|
+
"ef-fit-scale": EFFitScale;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/gui/EFWorkbench.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement, type PropertyValueMap, css, html } from "lit";
|
|
2
|
-
import { customElement, eventOptions
|
|
2
|
+
import { customElement, eventOptions } from "lit/decorators.js";
|
|
3
3
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
4
4
|
|
|
5
5
|
import { ContextMixin } from "./ContextMixin.js";
|
|
@@ -17,67 +17,19 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
|
|
17
17
|
`,
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
canvasRef = createRef<HTMLSlotElement>();
|
|
20
|
+
focusOverlay = createRef<HTMLDivElement>();
|
|
22
21
|
|
|
23
22
|
@eventOptions({ passive: false, capture: true })
|
|
24
23
|
handleStageWheel(event: WheelEvent) {
|
|
25
24
|
event.preventDefault();
|
|
26
25
|
}
|
|
27
26
|
|
|
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
|
-
}
|
|
54
|
-
this.stageScale = scale;
|
|
55
|
-
} else {
|
|
56
|
-
const scale = stageWidth / canvasWidth;
|
|
57
|
-
if (this.stageScale !== scale) {
|
|
58
|
-
canvasElement.style.transform = `scale(${scale})`;
|
|
59
|
-
}
|
|
60
|
-
this.stageScale = scale;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (this.isConnected) {
|
|
65
|
-
requestAnimationFrame(this.setStageScale);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
27
|
connectedCallback(): void {
|
|
70
|
-
// Set the body and html to 100% width and height to avoid scaling issues
|
|
71
|
-
// this is a hack to avoid scaling issues when the stage is scaled and during output rendering
|
|
72
|
-
// What I've discovered recently is that having the workbench be so smart as to determine
|
|
73
|
-
// how it should be displayed at render time causes problems. Much of that has been extracted
|
|
74
|
-
// but this is a quick fix to avoid scaling issues.
|
|
75
28
|
document.body.style.width = "100%";
|
|
76
29
|
document.body.style.height = "100%";
|
|
77
30
|
document.documentElement.style.width = "100%";
|
|
78
31
|
document.documentElement.style.height = "100%";
|
|
79
32
|
super.connectedCallback();
|
|
80
|
-
requestAnimationFrame(this.setStageScale);
|
|
81
33
|
}
|
|
82
34
|
|
|
83
35
|
disconnectedCallback(): void {
|
|
@@ -119,13 +71,14 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
|
|
119
71
|
};
|
|
120
72
|
|
|
121
73
|
render() {
|
|
122
|
-
|
|
74
|
+
// TODO: this.rendering is not correctly set when using the framegen bridge
|
|
75
|
+
// so to hack we're checking for the existence of the framegen bridge on the window
|
|
76
|
+
if (
|
|
77
|
+
this.rendering ||
|
|
78
|
+
(typeof window !== "undefined" && window.FRAMEGEN_BRIDGE)
|
|
79
|
+
) {
|
|
123
80
|
return html`
|
|
124
|
-
<slot
|
|
125
|
-
${ref(this.canvasRef)}
|
|
126
|
-
class="fixed inset-0 h-full w-full"
|
|
127
|
-
name="canvas"
|
|
128
|
-
></slot>
|
|
81
|
+
<slot class="fixed inset-0 h-full w-full" name="canvas"></slot>
|
|
129
82
|
`;
|
|
130
83
|
}
|
|
131
84
|
return html`
|
|
@@ -134,15 +87,12 @@ export class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {
|
|
|
134
87
|
style="grid-template-rows: 1fr 300px; grid-template-columns: 100%;"
|
|
135
88
|
>
|
|
136
89
|
<div
|
|
137
|
-
|
|
138
|
-
class="relative grid h-full w-full justify-center overflow-hidden place-content-center place-items-center"
|
|
90
|
+
class="relative h-full w-full overflow-hidden"
|
|
139
91
|
@wheel=${this.handleStageWheel}
|
|
140
92
|
>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class="inline-block"
|
|
145
|
-
></slot>
|
|
93
|
+
<ef-fit-scale class="h-full grid place-content-center">
|
|
94
|
+
<slot name="canvas" class="contents"></slot>
|
|
95
|
+
</ef-fit-scale>
|
|
146
96
|
<div
|
|
147
97
|
class="border border-blue-500 bg-blue-200 bg-opacity-20 absolute"
|
|
148
98
|
${ref(this.focusOverlay)}
|