diff --git a/src/pages/constituent/blur/artwork-controller/_applet.astro b/src/pages/constituent/blur/artwork-controller/_applet.astro index d307299..617e1dc 100644 --- a/src/pages/constituent/blur/artwork-controller/_applet.astro +++ b/src/pages/constituent/blur/artwork-controller/_applet.astro @@ -2,6 +2,7 @@ import "@styles/reset.css"; import "@styles/variables.css"; import "@styles/fonts.css"; +import "@styles/animations.css"; import "@styles/icons/phosphor.css"; import "@styles/diffuse/colors.css"; @@ -192,13 +193,21 @@ import "@styles/diffuse/fonts.css"; text-align: center; } + .controller .menu__loader { + line-height: 0; + transform-origin: center; + } + .controller command { cursor: pointer; line-height: 0; + transition-duration: var(--transition-durition); + transition-property: opacity; } .controller .ph-pause, - .controller .ph-play { + .controller .ph-play, + .controller .menu__loader { font-size: var(--fs-xl); } @@ -354,26 +363,30 @@ import "@styles/diffuse/fonts.css"; const [artworkColor, setArtworkColor] = signal(undefined); const [artworkLightMode, setArtworkLightMode] = signal(false); const [duration, setDuration] = signal("0:00"); - const [groupId, setGroupId] = signal(context.groupId); + const [isLoading, setIsLoading] = signal(true); const [isPlaying, setIsPlaying] = signal(false); const [progress, setProgress] = signal(0); const [time, setTime] = signal("0:00"); const [volume, setVolume] = signal(0); + // Is main group + function isMainGroup() { + return context.groupId === undefined || context.groupId.toLowerCase() === "main"; + } + // Applet connections const configurator = { input: await applet("/configurator/input"), - output: await applet("/configurator/output"), }; const engine = { - audio: await applet("/engine/audio", { groupId: groupId() }), - queue: await applet("/engine/queue", { groupId: groupId() }), + audio: await applet("/engine/audio", { groupId: context.groupId }), + queue: await applet("/engine/queue", { groupId: context.groupId }), }; const _orchestrator = { queueAudio: await applet("/orchestrator/queue-audio", { - groupId: groupId(), + groupId: context.groupId, }), // When using the `main` group, load additional orchestrators: @@ -425,10 +438,23 @@ import "@styles/diffuse/fonts.css"; return `${duration.hours > 0 ? duration.hours.toFixed(0) + ":" : ""}${duration.hours > 0 ? (duration.minutes > 9 ? duration.minutes.toFixed(0) : "0" + duration.minutes.toFixed(0)) : duration.minutes.toFixed(0)}:${duration.seconds > 9 ? duration.seconds.toFixed(0) : "0" + duration.seconds.toFixed(0)}`; } + //////////////////////////////////////////// + // ✨ EFFECTS + // ⏳️ Loading + //////////////////////////////////////////// + + const debouncedSetIsLoading = debounce(2000, setIsLoading); + //////////////////////////////////////////// // 🔊 AUDIO //////////////////////////////////////////// + reactive( + engine.audio, + (data) => data.items[engine.queue.data.now?.id ?? Infinity]?.loadingState === "loaded", + (isLoaded) => debouncedSetIsLoading(!isLoaded), + ); + reactive( engine.audio, (data) => @@ -600,8 +626,7 @@ import "@styles/diffuse/fonts.css"; const GroupId = h( "label", computed(() => { - const gid = groupId(); - const display = gid === undefined || gid.toLowerCase() === "main" ? "none" : "block"; + const display = isMainGroup() ? "none" : "block"; return { attrs: { @@ -621,7 +646,21 @@ import "@styles/diffuse/fonts.css"; const NowPlaying = h("cite", {}, [ h("strong", {}, text(computed(() => activeTrack()?.tags?.title || "Diffuse"))), tags.br(), - h("span", {}, text(computed(() => activeTrack()?.tags?.artist || ""))), + h( + "span", + computed(() => { + return { + style: isMainGroup() && !activeTrack() ? "font-style: italic" : "", + }; + }), + text( + computed( + () => + activeTrack()?.tags?.artist || + (isMainGroup() && !activeTrack() ? "Waiting for tracks to be queued ..." : ""), + ), + ), + ), ]); controller.appendChild(NowPlaying); @@ -676,11 +715,19 @@ import "@styles/diffuse/fonts.css"; const Controls = h("menu", {}, [ Control("Previous track", "ph-fill ph-rewind", { onclick: previous }), + h( + "div", + computed(() => { + const style = `display: ${activeTrack() && isLoading() ? "inherit" : "none"}`; + return { className: "animate-bounce menu__loader", style }; + }), + [h("i", { className: "ph-fill ph-vinyl-record", title: "Loading ..." })], + ), Control( "Play", "ph-fill ph-play", computed(() => { - const style = `display: ${!isPlaying() ? "inline" : "none"}`; + const style = `display: ${(!isPlaying() && !isLoading()) || !activeTrack() ? "inline" : "none"}`; return { onclick: playPause, style }; }), ), @@ -688,7 +735,7 @@ import "@styles/diffuse/fonts.css"; "Pause", "ph-fill ph-pause", computed(() => { - const style = `display: ${isPlaying() ? "inline" : "none"}`; + const style = `display: ${isPlaying() && !isLoading() ? "inline" : "none"}`; return { onclick: playPause, style }; }), ), diff --git a/src/scripts/applet/common.ts b/src/scripts/applet/common.ts index 0326130..d924503 100644 --- a/src/scripts/applet/common.ts +++ b/src/scripts/applet/common.ts @@ -305,14 +305,9 @@ export function reactive( } export function makeConnect(context: BroadcastedApplet) { - return ( - applet: Applet, - dataFn: (data: D) => T, - effectFn: (t: T, setter: (t: T) => void) => void, - ) => { - // if (!context.isMainInstance()) return; - return reactive(applet, dataFn, (t, setter) => { - if (context.isMainInstance()) effectFn(t, setter); + return (applet: Applet, dataFn: (data: D) => T, effectFn: (t: T) => void) => { + return reactive(applet, dataFn, (t: T) => { + if (context.isMainInstance()) effectFn(t); }); }; } diff --git a/src/styles/animations.css b/src/styles/animations.css new file mode 100644 index 0000000..c637996 --- /dev/null +++ b/src/styles/animations.css @@ -0,0 +1,54 @@ +:root { + --animate-spin: spin 1s linear infinite; + --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-bounce: bounce 1s infinite; +} + +.animate-spin { + animation: var(--animate-spin); +} + +.animate-ping { + animation: var(--animate-ping); +} + +.animate-pulse { + animation: var(--animate-pulse); +} + +.animate-bounce { + animation: var(--animate-bounce); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes ping { + 75%, + 100% { + transform: scale(2); + opacity: 0; + } +} + +@keyframes pulse { + 50% { + opacity: 0.5; + } +} + +@keyframes bounce { + 0%, + 100% { + transform: translateY(-25%); + animation-timing-function: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: none; + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + } +}