@rr0/ufoathome 0.1.0 → 0.2.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.
@@ -1,275 +0,0 @@
1
- const g = `
2
- <div class="toolbar">
3
- <button id="play" type="button">Play</button>
4
- <button id="pause" type="button">Pause</button>
5
- <input id="seek" type="range" min="0" max="0" value="0" step="1"/>
6
- </div>
7
- <canvas id="canvas" width="640" height="360"></canvas>
8
- `, v = `
9
- :host {
10
- display: block;
11
- font-family: sans-serif;
12
- }
13
- .toolbar {
14
- display: flex;
15
- align-items: center;
16
- gap: 0.5em;
17
- margin-bottom: 0.5em;
18
- }
19
- canvas {
20
- background: #050510;
21
- border: 1px solid #333;
22
- }
23
- input[type=range] {
24
- flex: 1;
25
- }
26
- `;
27
- function x(a, t, s) {
28
- const { bounds: e } = a;
29
- return t >= e.x && t <= e.x + e.width && s >= e.y && s <= e.y + e.height;
30
- }
31
- class o {
32
- keyframes = [];
33
- addKeyframe(t, s) {
34
- const e = this.findInsertIndex(t);
35
- this.keyframes[e]?.t === t ? this.keyframes[e] = { t, shapes: s } : this.keyframes.splice(e, 0, { t, shapes: s });
36
- }
37
- findInsertIndex(t) {
38
- let s = 0, e = this.keyframes.length;
39
- for (; s < e; ) {
40
- const i = s + e >>> 1;
41
- this.keyframes[i].t < t ? s = i + 1 : e = i;
42
- }
43
- return s;
44
- }
45
- getKeyframeAt(t) {
46
- const s = this.findInsertIndex(t), e = this.keyframes[s];
47
- return e?.t === t ? e : void 0;
48
- }
49
- getShapeAt(t, s) {
50
- return this.getKeyframeAt(t)?.shapes.find((e) => e.sourceId === s)?.shape;
51
- }
52
- /**
53
- * Finds the most recent shape recorded at-or-before t for that source (hold-last-value),
54
- * which is what playback needs since not every source has a keyframe at every sampled tick.
55
- */
56
- getLatestShapeAt(t, s) {
57
- let e = this.findInsertIndex(t);
58
- for (this.keyframes[e]?.t !== t && (e -= 1); e >= 0; e--) {
59
- const i = this.keyframes[e].shapes.find((n) => n.sourceId === s);
60
- if (i) return i.shape;
61
- }
62
- }
63
- hitTest(t, s, e) {
64
- const i = this.getKeyframeAt(t);
65
- if (i) {
66
- for (let n = i.shapes.length - 1; n >= 0; n--)
67
- if (x(i.shapes[n].shape, s, e))
68
- return i.shapes[n];
69
- }
70
- }
71
- get duration() {
72
- return this.keyframes.length === 0 ? 0 : this.keyframes[this.keyframes.length - 1].t;
73
- }
74
- get sourceIds() {
75
- const t = /* @__PURE__ */ new Set();
76
- for (const s of this.keyframes)
77
- for (const e of s.shapes)
78
- t.add(e.sourceId);
79
- return [...t];
80
- }
81
- get allKeyframes() {
82
- return this.keyframes;
83
- }
84
- toJSON() {
85
- return { keyframes: this.keyframes };
86
- }
87
- static fromJSON(t) {
88
- const s = new o();
89
- for (const e of t.keyframes)
90
- s.addKeyframe(e.t, e.shapes);
91
- return s;
92
- }
93
- }
94
- class c {
95
- constructor(t, s, e) {
96
- this.event = t, this.timeline = s, this.witnessId = e;
97
- }
98
- static create(t, s, e) {
99
- return new c({ eventType: "sighting", time: t, place: s }, new o(), e);
100
- }
101
- }
102
- class d {
103
- constructor(t, s) {
104
- this.timeline = t, this.onFrame = s;
105
- }
106
- rafId = null;
107
- state = "stopped";
108
- currentT = 0;
109
- lastWallTime = 0;
110
- play() {
111
- if (this.state === "playing") return;
112
- this.state = "playing", this.lastWallTime = performance.now();
113
- const t = () => {
114
- if (this.state !== "playing") return;
115
- const s = performance.now();
116
- if (this.currentT += s - this.lastWallTime, this.lastWallTime = s, this.currentT >= this.timeline.duration) {
117
- this.currentT = this.timeline.duration, this.resolveFrame(this.currentT), this.stop();
118
- return;
119
- }
120
- this.resolveFrame(this.currentT), this.rafId = requestAnimationFrame(t);
121
- };
122
- this.rafId = requestAnimationFrame(t);
123
- }
124
- pause() {
125
- this.state === "playing" && (this.state = "paused", this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null));
126
- }
127
- stop() {
128
- this.state = "stopped", this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null);
129
- }
130
- seek(t) {
131
- this.currentT = Math.max(0, Math.min(t, this.timeline.duration)), this.resolveFrame(this.currentT);
132
- }
133
- get playbackState() {
134
- return this.state;
135
- }
136
- get time() {
137
- return this.currentT;
138
- }
139
- resolveFrame(t) {
140
- const s = /* @__PURE__ */ new Map();
141
- for (const e of this.timeline.sourceIds) {
142
- const i = this.timeline.getLatestShapeAt(t, e);
143
- i && s.set(e, i);
144
- }
145
- this.onFrame(t, s);
146
- }
147
- }
148
- const l = 6, u = l / 2, k = 20;
149
- class I {
150
- constructor(t) {
151
- this.ctx = t;
152
- }
153
- clear(t, s) {
154
- this.ctx.clearRect(0, 0, t, s);
155
- }
156
- paintShape(t) {
157
- this.ctx.save(), this.ctx.globalAlpha = 1 - t.transparency, t.haloScale > 0 && this.paintHalo(t), this.paintBase(t), t.selected && this.paintSelectionHandles(t), this.ctx.restore();
158
- }
159
- paintBase(t) {
160
- if (this.ctx.fillStyle = t.color, this.ctx.beginPath(), t.kind === "oval") {
161
- const { x: s, y: e, width: i, height: n } = t.bounds, r = i / 2, h = n / 2;
162
- this.ctx.ellipse(s + r, e + h, r, h, t.angle, 0, 2 * Math.PI);
163
- } else {
164
- const { x: s, y: e } = t.bounds;
165
- this.ctx.save(), this.ctx.translate(s, e), this.ctx.rotate(t.angle), t.points.forEach((i, n) => {
166
- n === 0 ? this.ctx.moveTo(i.x, i.y) : this.ctx.lineTo(i.x, i.y);
167
- }), this.ctx.closePath(), this.ctx.restore();
168
- }
169
- this.ctx.fill();
170
- }
171
- paintHalo(t) {
172
- this.ctx.save(), this.ctx.shadowColor = t.color, this.ctx.shadowBlur = k * t.haloScale, this.paintBase(t), this.ctx.restore();
173
- }
174
- paintSelectionHandles(t) {
175
- const { x: s, y: e, width: i, height: n } = t.bounds, r = i / 2, h = n / 2;
176
- this.ctx.strokeStyle = "lightgray", this.ctx.strokeRect(s, e, i, n), this.ctx.fillStyle = "lightgray";
177
- const f = [
178
- [s, e],
179
- [s + r, e],
180
- [s + i, e],
181
- [s + i, e + h],
182
- [s + i, e + n],
183
- [s + r, e + n],
184
- [s, e + n],
185
- [s, e + h]
186
- ];
187
- for (const [p, y] of f)
188
- this.ctx.fillRect(p - u, y - u, l, l);
189
- }
190
- }
191
- function S(a) {
192
- return {
193
- version: 1,
194
- time: a.event.time,
195
- place: a.event.place,
196
- witnessId: a.witnessId,
197
- timeline: a.timeline.toJSON()
198
- };
199
- }
200
- function w(a) {
201
- return new c(
202
- { eventType: "sighting", time: a.time, place: a.place },
203
- o.fromJSON(a.timeline),
204
- a.witnessId
205
- );
206
- }
207
- class b extends HTMLElement {
208
- static get observedAttributes() {
209
- return ["src"];
210
- }
211
- shadow;
212
- canvas;
213
- canvasRenderer;
214
- playButton;
215
- pauseButton;
216
- seekInput;
217
- currentSighting = c.create();
218
- player;
219
- constructor() {
220
- super(), this.shadow = this.attachShadow({ mode: "open" });
221
- const t = document.createElement("template");
222
- t.innerHTML = `<style>${v}</style>${g}`, this.shadow.appendChild(t.content.cloneNode(!0)), this.canvas = this.shadow.getElementById("canvas"), this.canvasRenderer = new I(this.canvas.getContext("2d")), this.playButton = this.shadow.getElementById("play"), this.pauseButton = this.shadow.getElementById("pause"), this.seekInput = this.shadow.getElementById("seek"), this.playButton.addEventListener("click", () => this.player.play()), this.pauseButton.addEventListener("click", () => this.player.pause()), this.seekInput.addEventListener("input", () => this.player.seek(Number(this.seekInput.value))), this.player = new d(this.currentSighting.timeline, (s, e) => this.onFrame(s, e)), this.refresh();
223
- }
224
- connectedCallback() {
225
- const t = this.getAttribute("src");
226
- t && this.loadFromSrc(t);
227
- }
228
- attributeChangedCallback(t, s, e) {
229
- t === "src" && e && e !== s && this.isConnected && this.loadFromSrc(e);
230
- }
231
- /** Fetches a SightingRecordingJson from `url` and loads it — what the `src` attribute uses. */
232
- async loadFromSrc(t) {
233
- const s = await fetch(t);
234
- this.sightingData = await s.json();
235
- }
236
- get sightingData() {
237
- return S(this.currentSighting);
238
- }
239
- set sightingData(t) {
240
- this.currentSighting = w(t), this.player = new d(this.currentSighting.timeline, (s, e) => this.onFrame(s, e)), this.refresh();
241
- }
242
- /**
243
- * The live Sighting/Timeline, exposed so UfoRecorderElement (which composes
244
- * this element) can add keyframes to it directly as it records.
245
- */
246
- get sighting() {
247
- return this.currentSighting;
248
- }
249
- /** Exposed so UfoRecorderElement can paint a live drag preview on the same canvas. */
250
- get canvasElement() {
251
- return this.canvas;
252
- }
253
- get renderer() {
254
- return this.canvasRenderer;
255
- }
256
- /**
257
- * Re-reads the timeline's duration into the seek slider and repaints the
258
- * current frame — call after externally mutating `sighting.timeline`
259
- * (e.g. UfoRecorderElement adding keyframes while recording).
260
- */
261
- refresh() {
262
- this.seekInput.max = String(this.currentSighting.timeline.duration), this.player.seek(this.player.time);
263
- }
264
- onFrame(t, s) {
265
- this.canvasRenderer.clear(this.canvas.width, this.canvas.height);
266
- for (const e of s.values())
267
- this.canvasRenderer.paintShape(e);
268
- this.seekInput.value = String(t);
269
- }
270
- }
271
- const m = "rr0-ufo-player";
272
- function E() {
273
- customElements.get(m) || customElements.define(m, b);
274
- }
275
- E();