alpha
Login
or
Join now
tokono.ma
/
diffuse-applets
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Experiment to rebuild Diffuse using web applets.
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
feat: fade in/out artwork
author
Steven Vandevelde
date
11 months ago
(Jul 1, 2025, 4:04 PM +0200)
commit
de5180af
de5180af353acedfd22ae32005ded6140a123399
parent
d29e4e54
d29e4e54d8edeec2f2313e3882d2334d45aba943
+52
-23
3 changed files
Expand all
Collapse all
Unified
Split
src
pages
constituent
blur
artwork-controller
_applet.astro
orchestrator
input-cache
_applet.astro
scripts
processor
artwork
worker.ts
+47
-16
src/pages/constituent/blur/artwork-controller/_applet.astro
Reviewed
···
31
31
</main>
32
32
33
33
<style>
34
34
+
:root {
35
35
+
--transition-durition: 500ms;
36
36
+
}
37
37
+
34
38
main {
35
39
background: var(--color-3);
36
40
color: white;
···
42
46
overflow: hidden;
43
47
position: relative;
44
48
transition:
45
45
-
background-color 500ms,
46
46
-
color 500ms;
49
49
+
background-color var(--transition-durition),
50
50
+
color var(--transition-durition);
47
51
}
48
52
49
53
/* Artwork */
···
57
61
height: 100%;
58
62
left: 0;
59
63
object-fit: cover;
64
64
+
opacity: 0;
60
65
pointer-events: none;
61
66
position: absolute;
62
67
top: 0;
68
68
+
transition-duration: var(--transition-durition);
69
69
+
transition-property: opacity;
63
70
width: 100%;
64
71
z-index: 0;
65
72
}
···
78
85
text-transform: uppercase;
79
86
top: var(--space-xs);
80
87
transition:
81
81
-
background-color 500ms,
82
82
-
color 500ms;
88
88
+
background-color var(--transition-durition),
89
89
+
color var(--transition-durition);
83
90
z-index: 1;
84
91
}
85
92
···
122
129
inset: 0;
123
130
opacity: 0.5;
124
131
position: absolute;
125
125
-
transition: background-color 500ms;
132
132
+
transition: background-color var(--transition-durition);
126
133
z-index: 1;
127
134
}
128
135
···
306
313
import scope from "astro:scope";
307
314
import { FastAverageColor } from "fast-average-color";
308
315
import { Temporal } from "@js-temporal/polyfill";
316
316
+
import { xxh32r } from "xxh32/dist/raw.js";
309
317
310
318
import { computed, effect, type Signal, signal } from "spellcaster";
311
319
import { tags, text, type ElementConfigurator } from "spellcaster/hyperscript.js";
···
485
493
////////////////////////////////////////////
486
494
// UI ░ ARTWORK
487
495
////////////////////////////////////////////
496
496
+
497
497
+
const timeouts: Record<string, ReturnType<typeof setTimeout>> = {};
488
498
489
499
effect(() => {
490
500
const art = artwork();
491
501
492
492
-
console.log("ART", art[0]);
502
502
+
// No artwork, fade out existing.
503
503
+
if (art.length === 0) {
504
504
+
showcase.querySelectorAll("img").forEach((node) => {
505
505
+
node.style.opacity = "0";
506
506
+
timeouts[hash] = setTimeout(() => node.remove(), 1000);
507
507
+
});
508
508
+
return;
509
509
+
}
510
510
+
511
511
+
// Determine if the current artwork needs to be replaced.
512
512
+
const hash = xxh32r(art[0].bytes).toString();
513
513
+
const existingArtwork = showcase.querySelector<HTMLImageElement>(`img[data-hash="${hash}"]`);
493
514
494
494
-
// TODO: Remove existing art?
495
495
-
if (art.length === 0) {
515
515
+
// If the artwork is the same, stop here.
516
516
+
if (existingArtwork) {
517
517
+
const timeoutId = timeouts[hash];
518
518
+
if (timeoutId) clearTimeout(timeoutId);
519
519
+
existingArtwork.style.opacity = "1";
496
520
return;
497
521
}
498
522
499
499
-
// Show artwork
523
523
+
// Add new artwork
500
524
const blob = new Blob([art[0].bytes], { type: art[0].mime });
501
525
const url = URL.createObjectURL(blob);
502
526
503
503
-
// Remove existing artwork
504
504
-
// TODO: Fade in new artwork and then remove other
505
505
-
showcase.querySelectorAll("img").forEach((node) => {
506
506
-
showcase.removeChild(node);
527
527
+
// Create img for new artwork
528
528
+
const img = h("img", {
529
529
+
src: url,
530
530
+
className: "artwork",
531
531
+
attrs: {
532
532
+
"data-hash": hash,
533
533
+
},
507
534
});
508
508
-
509
509
-
// Create img for new artwork
510
510
-
const img = h("img", { src: url, className: "artwork" });
511
535
512
536
// Extract average color
513
537
img.onload = () => {
···
516
540
setArtworkColor(color.rgba);
517
541
bg.style.backgroundColor = color.rgba;
518
542
main.style.backgroundColor = color.rgba;
543
543
+
img.style.opacity = "1";
544
544
+
545
545
+
showcase.querySelectorAll("img").forEach((node) => {
546
546
+
if (node === img) return;
547
547
+
node.style.opacity = "0";
548
548
+
timeouts[hash] = setTimeout(() => node.remove(), 1000);
549
549
+
});
519
550
};
520
551
521
552
// Insert new artwork
+5
-5
src/pages/orchestrator/input-cache/_applet.astro
Reviewed
···
23
23
};
24
24
25
25
// Start processing once settled and tracks are loaded
26
26
-
context
27
27
-
.settled()
28
28
-
.then(() => configurator.output)
29
29
-
.then((output) => wait(output, (d) => d?.tracks.state === "loaded"))
30
30
-
.then(() => (context.isMainInstance() ? process() : undefined));
26
26
+
// context
27
27
+
// .settled()
28
28
+
// .then(() => configurator.output)
29
29
+
// .then((output) => wait(output, (d) => d?.tracks.state === "loaded"))
30
30
+
// .then(() => (context.isMainInstance() ? process() : undefined));
31
31
32
32
////////////////////////////////////////////
33
33
// ACTIONS
-2
src/scripts/processor/artwork/worker.ts
Reviewed
···
121
121
let art: Artwork[] = [];
122
122
123
123
// Get metadata + possible artwork from file metadata
124
124
-
console.log("ART REQ", req);
125
124
const meta = await musicMetadataTags({ ...req, includeArtwork: true });
126
126
-
console.log("ART META", meta);
127
125
if (!req.tags) req.tags = meta.tags;
128
126
129
127
// Add artwork from metadata