feat: modularize ui

main
lambda 2 years ago
parent 4868d41046
commit cb90344b5f

@ -0,0 +1,23 @@
const STOP_STATE_POINTS = "650,0, 1350,0, 1350,500, 650,500";
const PLAY_STATE_POINTS = "0,0, 2000,250, 2000,250, 0,500";
const startIcebergsAnimation = () => {
[1, 2, 3].map((i) => {
const polygon = document.getElementById(`iceberg${i}`);
polygon.classList.add("active");
});
};
const stopIcebergsAnimation = () => {
[1, 2, 3].map((i) => {
const polygon = document.getElementById(`iceberg${i}`);
polygon.classList.remove("active");
});
};
export {
startIcebergsAnimation,
stopIcebergsAnimation,
STOP_STATE_POINTS,
PLAY_STATE_POINTS,
};

@ -0,0 +1,15 @@
const audio = document.querySelector("audio");
const playButton = document.querySelector(".play-stream");
const playButtonAnimation = document.getElementById("play-button-animation");
const artistBlock = document.querySelector(".now-playing_artist");
const titleBlock = document.querySelector(".now-playing_song");
const logoDot = document.querySelector(".logo_dot");
export {
audio,
playButton,
playButtonAnimation,
artistBlock,
titleBlock,
logoDot,
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -43,14 +43,20 @@
</head>
<body>
<h1>Radio . Iceberg</h1>
<span class="logo">
<h1>Radio</h1>
<span class="logo_dot">
<span class="logo_dot_inner"></span>
</span>
<h1>Iceberg</h1>
</span>
<button class="play-stream">
<svg viewBox="0 0 2000 500">
<defs></defs>
<polygon
points="0,0, 2000,250, 2000,250, 0,500"
fill="var(--color-primary-1)"
fill="var(--color-colors-additional-light)"
>
<animate
id="play-button-animation"
@ -63,7 +69,7 @@
/>
</polygon>
</svg>
<audio controls preload="none" src="/stream" />
<audio controls preload="auto" src="/stream" />
</button>
<div class="now-playing">
<div class="now-playing_title">now playing:</div>
@ -75,20 +81,20 @@
<polygon
id="iceberg1"
points="0,0, 100,0, 100,100, 0,100"
fill="var(--color-primary-1)"
fill="var(--color-colors-additional-light)"
></polygon>
<polygon
id="iceberg2"
points="5,5, 95,5, 95,95, 5,95"
fill="var(--color-primary-2)"
fill="var(--color-colors-additional-dark)"
></polygon>
<polygon
id="iceberg3"
points="10,10, 90,10, 90,90, 10,90"
fill="var(--color-complement-1)"
fill="var(--color-colors-additional-medium)"
></polygon>
</svg>
</div>
<script src="main.js"></script>
<script src="main.js" type="module"></script>
</body>
</html>

@ -0,0 +1,51 @@
import { audio, playButton, playButtonAnimation } from "./dom.js";
import {
STOP_STATE_POINTS,
PLAY_STATE_POINTS,
startIcebergsAnimation,
stopIcebergsAnimation,
} from "./animations.js";
const hideAudio = () => {
audio.hidden = true;
};
const playAudio = () => {
audio?.play();
};
const pauseAudio = () => {
audio?.pause();
};
const setButtonPlayState = () => {
playButtonAnimation.setAttribute("from", PLAY_STATE_POINTS);
playButtonAnimation.setAttribute("to", STOP_STATE_POINTS);
playButtonAnimation.beginElement();
};
const setButtonStopState = () => {
playButtonAnimation.setAttribute("from", STOP_STATE_POINTS);
playButtonAnimation.setAttribute("to", PLAY_STATE_POINTS);
playButtonAnimation.beginElement();
};
const handleAudioPlay = () => {
playAudio();
setButtonPlayState();
startIcebergsAnimation();
};
const handleAudioStop = () => {
pauseAudio();
setButtonStopState();
stopIcebergsAnimation();
};
const addPlayerInteractions = () => {
playButton.addEventListener("click", () => {
audio?.paused ? handleAudioPlay() : handleAudioStop();
});
};
export { hideAudio, addPlayerInteractions };

@ -1,110 +1,5 @@
const STOP_STATE_POINTS = "650,0, 1350,0, 1350,500, 650,500";
const PLAY_STATE_POINTS = "0,0, 2000,250, 2000,250, 0,500";
const getAudio = () => document.querySelector("audio");
const getPlayButton = () => document.querySelector(".play-stream");
const getPlayButtonAnimation = () =>
document.getElementById("play-button-animation");
const hideAudio = () => {
const a = getAudio();
a.hidden = true;
};
const playAudio = () => {
getAudio()?.play();
};
const pauseAudio = () => {
getAudio()?.pause();
};
const setButtonPlayState = () => {
const animationNode = getPlayButtonAnimation();
animationNode.setAttribute("from", PLAY_STATE_POINTS);
animationNode.setAttribute("to", STOP_STATE_POINTS);
animationNode.beginElement();
};
const setButtonStopState = () => {
const animationNode = getPlayButtonAnimation();
animationNode.setAttribute("from", STOP_STATE_POINTS);
animationNode.setAttribute("to", PLAY_STATE_POINTS);
animationNode.beginElement();
};
const startIcebergsAnimation = () => {
[1, 2, 3].map((i) => {
const polygon = document.getElementById(`iceberg${i}`);
polygon.classList.add("active");
});
};
const stopIcebergsAnimation = () => {
[1, 2, 3].map((i) => {
const polygon = document.getElementById(`iceberg${i}`);
polygon.classList.remove("active");
});
};
const handleAudioPlay = () => {
playAudio();
setButtonPlayState();
startIcebergsAnimation();
};
const handleAudioStop = () => {
pauseAudio();
setButtonStopState();
stopIcebergsAnimation();
};
const addPlayerInteractions = () => {
const playerButton = getPlayButton();
const audio = getAudio();
playerButton.addEventListener("click", () => {
audio.paused ? handleAudioPlay() : handleAudioStop();
});
audio.addEventListener("timeupdate", () => {
window.requestAnimationFrame(() => {
document.documentElement.style.setProperty(
"--time",
`${audio.currentTime * 100}deg`
);
});
});
};
async function connectToServer() {
const ws = new WebSocket(`ws://${location.host}/meta`);
ws.addEventListener("message", changeMetadata);
}
const changeMetadata = (metadataEvent) => {
const metaObject = JSON.parse(metadataEvent.data);
const { artist, title } = metaObject;
const artistBlock = document.querySelector(".now-playing_artist");
const titleBlock = document.querySelector(".now-playing_song");
if (artist) {
artistBlock.innerHTML = artist;
} else {
artistBlock.innerHTML = "";
}
if (title) {
titleBlock.innerHTML = title;
document.title = `${title} on Radioiceberg`;
} else {
titleBlock.innerHTML = "";
}
if (artist && title) {
document.title = `${artist} - ${title} on Radioiceberg`;
}
};
import { addPlayerInteractions, hideAudio } from "./interactions.js";
import { connectToServer } from "./websocket.js";
const main = () => {
hideAudio();

@ -0,0 +1,58 @@
import { artistBlock, titleBlock, logoDot } from "./dom.js";
const changeTitles = ({ artist, title }) => {
if (artist) {
artistBlock.innerHTML = artist;
} else {
artistBlock.innerHTML = "";
}
if (title) {
titleBlock.innerHTML = title;
document.title = `${title} on Radioiceberg`;
} else {
titleBlock.innerHTML = "";
}
if (artist && title) {
document.title = `${artist} - ${title} on Radioiceberg`;
}
};
const metadataChange = ({ data }) => {
changeTitles(data);
};
const liveStarted = () => {
logoDot.classList.add("live");
};
const liveEnded = () => {
logoDot.classList.remove("live");
};
const setState = ({ data }) => {
const { lastPlayed } = data;
changeTitles(lastPlayed);
};
const actions = {
metadataChange,
liveStarted,
liveEnded,
setState,
};
export const processMessage = ({ data }) => {
try {
const message = JSON.parse(data);
const action = actions[message.command];
if (action) {
action(message);
} else {
console.log("no action for", message);
}
} catch (error) {
console.error("Error while process websocket message", error);
}
};

@ -1,10 +0,0 @@
const getAudio = () => document.querySelector("audio");
const getPlayButton = () => document.querySelector(".play-stream");
const getPlayButtonAnimation = () =>
document.getElementById("play-button-animation");
export {
getAudio,
getPlayButton,
getPlayButtonAnimation
};

@ -1,14 +0,0 @@
import { getAudio } from "./dom.js";
const hideAudio = () => {
const a = getAudio();
a.hidden = true;
};
const playAudio = () => {
getAudio()?.play();
};
const pauseAudio = () => {
getAudio()?.pause();
};

@ -1,15 +1,29 @@
@import url("styles/global.css");
@import url("styles/system.css");
@import url("styles/fonts.css");
@import url("styles/variables.css");
@import url("styles/animations.css");
html {
font-size: 16pt;
}
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: var(--color-primary-0);
color: var(--color-primary-1);
background-color: var(--color-colors-base);
color: var(--color-colors-additional-light);
height: 100vh;
font-family: var(--typography-font-family), sans-serif;
max-height: 100vh;
overflow: hidden;
position: relative;
line-height: 1.75;
padding: 1rem;
padding-left: 1rem;
display: grid;
align-content: center;
}
h1,
@ -17,8 +31,7 @@ h2,
h3,
h4,
h5 {
margin: 3rem 0 1.38rem;
font-family: "Lato", sans-serif;
margin: 0;
font-weight: 400;
line-height: 1.3;
}
@ -29,7 +42,7 @@ h1 {
h1 {
margin-top: 0;
font-size: 5.063rem;
font-size: var(--size-font-large);
}
.play-stream {
@ -41,8 +54,13 @@ h1 {
}
.now-playing {
font-family: "Lato Light Italic";
font-size: 1.5rem;
font-size: var(--size-font-medium);
}
.now-playing_artist,
.now-playing_song {
font-weight: 300;
font-variation-settings: "slnt" -10;
}
.icebergs {
@ -54,17 +72,63 @@ h1 {
max-height: 70vh;
}
#iceberg1,
#iceberg2,
#iceberg3 {
transition: transform 2s ease-in-out;
transform: translate(0);
#iceberg1.active,
#iceberg2.active {
animation-name: slide;
animation-iteration-count: infinite;
animation-direction: alternate;
}
#iceberg1.active {
transform: translate(0, calc(100px * sin(var(--time))));
animation-duration: 3s;
}
#iceberg2.active {
transform: translate(0, calc(50px * cos(var(--time))));
animation-duration: 2s;
}
.logo {
display: flex;
align-items: baseline;
}
.logo_dot {
width: 0.25rem;
height: 0.25rem;
background: white;
border-radius: 100%;
margin: 0 0.25rem;
display: grid;
align-items: center;
justify-items: center;
transition: all 0.25s;
}
.logo_dot > .logo_dot_inner {
display: none;
}
.logo_dot.live {
width: 0.5rem;
height: 0.5rem;
background: var(--color-colors-warning);
box-sizing: border-box;
}
.logo_dot.live > .logo_dot_inner {
display: initial;
}
.logo_dot_inner {
width: 100%;
height: 100%;
border: 1px solid var(--color-colors-warning);
transform: scale(1);
border-radius: 100%;
display: flex;
box-sizing: border-box;
animation-name: grow;
animation-iteration-count: infinite;
animation-direction: normal;
animation-duration: 3s;
}

@ -0,0 +1,19 @@
@keyframes slide {
from {
transform: translate(0, 0);
}
to {
transform: translate(0, 25%);
}
}
@keyframes grow {
from {
border-color: var(--color-colors-warning);
transform: scale(0);
}
to {
border-color: transparent;
transform: scale(3);
}
}

@ -0,0 +1,4 @@
@font-face {
font-family: "Inter";
src: url("/fonts/Inter.ttf") format("truetype");
}

@ -1,9 +0,0 @@
html {
font-size: 100%;
}
html,
body {
margin: 0;
padding: 0;
}

@ -1,36 +0,0 @@
/* Typography */
@font-face {
font-family: "Lato";
src: url("fonts/lato-regular-webfont.woff2") format("woff2"),
url("fonts/lato-regular-webfont.woff") format("woff");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Lato Light Italic";
src: url("fonts/lato-lightitalic.woff2") format("woff2"),
url("fonts/lato-lightitalic.woff") format("woff");
font-weight: normal;
font-style: normal;
}
/* Colors */
:root {
--color-primary-0: #252525;
--color-primary-1: #ded8d8;
--color-primary-2: #7d7d7d;
--color-primary-3: #010101;
--color-primary-4: #0e0404;
--color-complement-0: #1e1e1e;
--color-complement-1: #adb2ad;
--color-complement-2: #646464;
--color-complement-3: #010101;
--color-complement-4: #030b03;
--time: 0;
--lol: calc(100px * sin(var(--time)));
}

@ -0,0 +1,14 @@
:root {
--color-colors-base: #252525;
--color-colors-warning: #ff0000;
--color-colors-additional-dark: #bebebe;
--color-colors-additional-medium: #cbcaca;
--color-colors-additional-light: #d9d9d9;
--size-font-small: 0.75rem;
--size-font-medium: 1rem;
--size-font-large: 2rem;
--size-font-base: 1rem;
--typography-font-family: Inter;
}

@ -0,0 +1,8 @@
import { processMessage } from "./messagesProcessor.js";
const socketProtocol = location.protocol === "http:" ? "ws:" : "wss:";
export async function connectToServer() {
const ws = new WebSocket(`${socketProtocol}//${location.host}/meta`);
ws.addEventListener("message", processMessage);
}