Skip to content

Commit

Permalink
Merge pull request #72 from Virenbar/playlist
Browse files Browse the repository at this point in the history
  • Loading branch information
Virenbar authored Jun 15, 2024
2 parents 310e127 + e0821b7 commit b315aa5
Show file tree
Hide file tree
Showing 22 changed files with 327 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/update.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from "fs";
import type { Station, Stations } from "../../types";
import type { Station, Stations } from "../../types/update";

async function Update() {
const response = await fetch("https://www.radiorecord.ru/api/stations/");
Expand Down
13 changes: 8 additions & 5 deletions app.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
<script setup lang="ts">
const title = "Каналы радио Record";
const description = "Автоматически обновляемый список ссылок на каналы радио Record";
useHead({
title: "Каналы радио Record",
title,
link: [
{ rel: "icon", type: "image/png", href: "https://www.radiorecord.ru/favicon.ico" }
],
meta: [
{ name: "description", content: "Автоматически обновляемый список ссылок на каналы радио Record" }
{ name: "description", content: description }
],
htmlAttrs: {
"data-bs-theme": "dark"
}
});
useServerSeoMeta({
ogType: "website",
ogTitle: "Каналы радио Record",
ogSiteName: "Каналы радио Record",
ogTitle: title,
ogSiteName: title,
ogUrl: "https://virenbar.ru/RadioRecord/",
ogDescription: "Автоматически обновляемый список ссылок на каналы радио Record",
ogDescription: description,
description,
ogImage: "https://www.radiorecord.ru/logo300-300.jpg",
ogImageSecureUrl: "https://www.radiorecord.ru/logo300-300.jpg"
});
Expand Down
5 changes: 5 additions & 0 deletions assets/css/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ $borders-night-light: #2A2A2A;
// Bootstrap Overrides
// Colors
$primary: $brand-accent;
$secondary: $neutrals-500;
$red: #F91A3F;

// Fonts
Expand All @@ -47,6 +48,10 @@ $font-family-sans-serif: "Roboto", "Segoe UI", "Helvetica Neue", "Noto Sans", "L
$link-color: $brand-accent;
$link-color-dark: $brand-accent;

// Navbar
$navbar-light-active-color: $brand-accent;
$navbar-dark-active-color: $brand-accent;

// Body
$body-bg: $neutrals-800;
$body-bg-dark: $neutrals-800;
Expand Down
26 changes: 20 additions & 6 deletions components/Page/Header.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
<template>
<header class="navbar navbar-expand-lg navbar-dark px-3 sticky-top">
<nav class="container font-monospace">
<a class="navbar-brand mx-auto" href="https://virenbar.github.io/RadioRecord/">
<img height="36" width="112" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 144 46'%3E%3Cpath d='M68.3 24H79c8-11.4 29.8-18.8 41.7-24l-5.4 1.6-.3.2-15.3 4.6-3.1.9-17 5.3-3.4-3.1h-5.4l-3.5 3.2L43 5 32 1.6 26.6 0c12.3 5.4 35.1 14.5 41.7 24zm5.2-11.4c.9 0 1.6.3 2.2.8.6.5.9 1.1.9 1.9 0 .8-.4 2-1.1 3-.5.7-1.2 1.2-1.9 1.3-.7 0-1.3-.5-1.8-1.2-.7-.9-1.1-2-1.1-2.9v-.2c-.2-1.5 1.1-2.7 2.8-2.7zm70.3 17.5c-.5-1.3-2.4-1.5-4.1-1.5h-15.9l-.1.4-.1.2-4.8 16-.1.2-.2.7h16.8c2.8 0 4.6-1 5.5-3.6L143 35c1-2.6 1.2-3.9.8-4.9zm-10.2 11.7h-6.3l2.7-8.9h6.3l-2.7 8.9zM94.5 29.1c-.8-.4-2-.5-3.1-.5H79.9c-2.8 0-4.6 1-5.5 3.6l-2.2 7.5c-.6 2-1.8 4.8.3 5.9.8.4 2 .5 3.1.5h11.5c2.8 0 4.6-1 5.5-3.6l2.2-7.5c.6-2 1.8-4.9-.3-5.9zm-9.2 12.7h-6.4l2.7-8.9H88l-2.7 8.9zm-39.8 0H32.8l.7-2.3h10.9l.1-.4.1-.2.9-2.9.1-.2.2-.7h-11l.7-2.3h13.8l.1-.4.1-.2.8-2.8.1-.2.2-.7H29.3l-.1.4-.2.2-4.8 16-.1.2-.1.7h21.2l.1-.4.1-.2.8-2.8.1-.2.2-.7h-1zm70.2-13.3H99.2c-1.6 5.5-3.3 11-4.9 16.6l-.1.2-.3.7h7.5l.1-.4.1-.2 1.7-5.6h2.5l2.7 5.7.1.1.1.3h8.2l-3.3-6.6c3.4-.4 4.2-1.8 4.8-4.2.2-.6.3-1.2.5-1.7 1.2-3.5-.1-4.8-3.2-4.9zm-4.3 5.1L111 35c-.1.4-.3.5-.7.6l-6.4 1.7 1.4-4.5h5.5c.6 0 .8.2.6.8zm-86.9 1.6c.2-.6.3-1.2.5-1.7 1.2-3.6-.1-4.9-3.2-4.9H5.3C3.7 34.1 2 39.6.4 45.2l-.1.2-.3.6h7.4l.1-.4.1-.2 1.7-5.6h2.5l2.7 5.7.1.1.1.3h8.2l-3.3-6.6c3.5-.3 4.3-1.7 4.9-4.1zm-7-1.6l-.4 1.4c-.1.4-.3.5-.7.6L10 37.3l1.4-4.5h5.5c.6 0 .8.2.6.8zM71.1 35l1.2-3.8c.5-1.3-.4-2.8-1.9-2.7H57c-2.8 0-4.6 1-5.5 3.6l-2.2 7.5c-.6 2-1.8 4.8.3 5.9.8.4 2 .5 3.1.5h12.5c1.5 0 3.2-1.4 3.6-2.7l1-3.7h-5.7l-.6 2.1h-7.3l2.7-8.9h7l-.8 2.2h6z' fill-rule='evenodd' clip-rule='evenodd' fill='%23fff'/%3E%3C/svg%3E" alt="Radio Record">
</a>
<button aria-controls="global-navbar" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-bs-target="#navbar" data-bs-toggle="collapse" type="button">
<NuxtLink class="navbar-brand mx-auto" to="/">
<img
height="36" width="112"
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 144 46'%3E%3Cpath d='M68.3 24H79c8-11.4 29.8-18.8 41.7-24l-5.4 1.6-.3.2-15.3 4.6-3.1.9-17 5.3-3.4-3.1h-5.4l-3.5 3.2L43 5 32 1.6 26.6 0c12.3 5.4 35.1 14.5 41.7 24zm5.2-11.4c.9 0 1.6.3 2.2.8.6.5.9 1.1.9 1.9 0 .8-.4 2-1.1 3-.5.7-1.2 1.2-1.9 1.3-.7 0-1.3-.5-1.8-1.2-.7-.9-1.1-2-1.1-2.9v-.2c-.2-1.5 1.1-2.7 2.8-2.7zm70.3 17.5c-.5-1.3-2.4-1.5-4.1-1.5h-15.9l-.1.4-.1.2-4.8 16-.1.2-.2.7h16.8c2.8 0 4.6-1 5.5-3.6L143 35c1-2.6 1.2-3.9.8-4.9zm-10.2 11.7h-6.3l2.7-8.9h6.3l-2.7 8.9zM94.5 29.1c-.8-.4-2-.5-3.1-.5H79.9c-2.8 0-4.6 1-5.5 3.6l-2.2 7.5c-.6 2-1.8 4.8.3 5.9.8.4 2 .5 3.1.5h11.5c2.8 0 4.6-1 5.5-3.6l2.2-7.5c.6-2 1.8-4.9-.3-5.9zm-9.2 12.7h-6.4l2.7-8.9H88l-2.7 8.9zm-39.8 0H32.8l.7-2.3h10.9l.1-.4.1-.2.9-2.9.1-.2.2-.7h-11l.7-2.3h13.8l.1-.4.1-.2.8-2.8.1-.2.2-.7H29.3l-.1.4-.2.2-4.8 16-.1.2-.1.7h21.2l.1-.4.1-.2.8-2.8.1-.2.2-.7h-1zm70.2-13.3H99.2c-1.6 5.5-3.3 11-4.9 16.6l-.1.2-.3.7h7.5l.1-.4.1-.2 1.7-5.6h2.5l2.7 5.7.1.1.1.3h8.2l-3.3-6.6c3.4-.4 4.2-1.8 4.8-4.2.2-.6.3-1.2.5-1.7 1.2-3.5-.1-4.8-3.2-4.9zm-4.3 5.1L111 35c-.1.4-.3.5-.7.6l-6.4 1.7 1.4-4.5h5.5c.6 0 .8.2.6.8zm-86.9 1.6c.2-.6.3-1.2.5-1.7 1.2-3.6-.1-4.9-3.2-4.9H5.3C3.7 34.1 2 39.6.4 45.2l-.1.2-.3.6h7.4l.1-.4.1-.2 1.7-5.6h2.5l2.7 5.7.1.1.1.3h8.2l-3.3-6.6c3.5-.3 4.3-1.7 4.9-4.1zm-7-1.6l-.4 1.4c-.1.4-.3.5-.7.6L10 37.3l1.4-4.5h5.5c.6 0 .8.2.6.8zM71.1 35l1.2-3.8c.5-1.3-.4-2.8-1.9-2.7H57c-2.8 0-4.6 1-5.5 3.6l-2.2 7.5c-.6 2-1.8 4.8.3 5.9.8.4 2 .5 3.1.5h12.5c1.5 0 3.2-1.4 3.6-2.7l1-3.7h-5.7l-.6 2.1h-7.3l2.7-8.9h7l-.8 2.2h6z' fill-rule='evenodd' clip-rule='evenodd' fill='%23fff'/%3E%3C/svg%3E"
alt="Radio Record">
</NuxtLink>
<button
aria-controls="global-navbar" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler" data-bs-target="#navbar"
data-bs-toggle="collapse" type="button">
<span class="navbar-toggler-icon" />
</button>
<div id="navbar" class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="https://radiorecord.ru/">Сайт радио Record</a>
<NuxtLink class="nav-link" active-class="active" to="/">
Каналы
</NuxtLink>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/Virenbar/RadioRecord/tree/master/playlists">Плейлисты в M3U</a>
<NuxtLink class="nav-link" active-class="active" to="/playlist/">
Плейлисты
</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="nav-link" active-class="active" to="/about/">
О сайте
</NuxtLink>
</li>
</ul>
</div>
Expand Down
9 changes: 4 additions & 5 deletions components/Station/Card.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<!-- eslint-disable vue/no-v-html -->
<script setup lang="ts">
import type { Station } from "~/types";

const props = defineProps<{ station?: Station }>();
const S = computed(() => props.station);
Expand All @@ -9,7 +8,7 @@ const S = computed(() => props.station);
<div v-if="S" class="card">
<div class="card-header d-flex flex-nowrap align-items-center">
<h5>
<a :href="S.shareUrl">{{ S.title }}</a>
<NuxtLink :href="S.shareUrl" target="_blank">{{ S.title }}</NuxtLink>
</h5>
<div v-if="S.new" class="mx-1 new">
NEW
Expand All @@ -21,9 +20,9 @@ const S = computed(() => props.station);
</div>
<div class="card-footer text-center">
<div class="btn-group btn-group-sm" role="group" area-label="Links">
<a class="btn btn-outline-primary" :href="S.stream_64">AAC 64</a>
<a class="btn btn-outline-primary" :href="S.stream_128">AAC 96</a>
<a class="btn btn-outline-primary" :href="S.stream_hls">M3U</a>
<NuxtLink class="btn btn-outline-primary" :to="S.stream_64" target="_blank">AAC 64</NuxtLink>
<NuxtLink class="btn btn-outline-primary" :to="S.stream_128" target="_blank">AAC 96</NuxtLink>
<NuxtLink class="btn btn-outline-primary" :to="S.stream_hls" target="_blank">M3U</NuxtLink>
</div>
</div>
</div>
Expand Down
15 changes: 1 addition & 14 deletions components/Station/List.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
<script setup lang="ts">
import { sortBy } from "lodash-es";
import Stations from "../../data/stations.json";
const sort = useSort();
const stations = computed(() => {
switch (sort.value) {
case "A-Z":
return sortBy(Stations.list, S => S.title.toLowerCase());
case "new":
return sortBy(Stations.list, S => S.prefix == "record" ? 0 : -S.id);
default:
return Stations.list;
}
});
const { stations } = useData();
const fake = (4 - (stations.value.length % 4)) % 4;
</script>
<template>
Expand Down
16 changes: 6 additions & 10 deletions components/Station/Sort.client.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
<script setup lang="ts">
const { getParameter, setParameter } = useHash();
const sort = useSort();
sort.value = getHash().get("sort") || "default";
sort.value = getParameter("sort") || "default";
function getHash() { return new URLSearchParams(location.hash.replace("#", "?")); }
function onSortChange(value: string) {
const params = getHash();
params.set("sort", value);
location.hash = params.toString();
function onSortClick(value: Sort) {
setParameter("sort", value);
sort.value = value;
console.log(sort.value);
}
const options = [
const options: SortOptions = [
{ key: "default", value: "По умолчанию" },
{ key: "A-Z", value: "По алфавиту" },
{ key: "new", value: "По новизне" }
Expand All @@ -25,7 +21,7 @@ const options = [
</button>
<ul class="dropdown-menu">
<li v-for="O in options" :key="O.key">
<button class="dropdown-item" :class="{ 'active': sort == O.key }" type="button" :onClick="() => onSortChange(O.key)">
<button class="dropdown-item" :class="{ 'active': sort == O.key }" type="button" @click="onSortClick(O.key)">
{{ O.value }}
</button>
</li>
Expand Down
20 changes: 20 additions & 0 deletions composables/useData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { sortBy } from "lodash-es";
import { list } from "../data/stations.json";

export default function () {
const sort = useSort();
const stations = computed(() => {
switch (sort.value) {
case "A-Z":
return sortBy(list, S => S.title.toLowerCase());
case "new":
return sortBy(list, S => S.prefix == "record" ? 0 : -S.id);
default:
return list;
}
});

return {
stations
};
}
26 changes: 26 additions & 0 deletions composables/useHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function () {
function getHash() {
return new URLSearchParams(location.hash.replace("#", "?"));
}

function getParameter<K extends keyof Parameters>(key: K) {
const params = getHash();
const value = params.get(key);
return value as Parameters[K] | null;
}

function setParameter<K extends keyof Parameters>(key: K, value: Parameters[K]) {
const params = getHash();
params.set(key, value);
location.hash = params.toString();
}

return {
getParameter,
setParameter
};
}

interface Parameters {
sort: Sort
}
22 changes: 22 additions & 0 deletions composables/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default function () {
function getItem<K extends keyof Storage>(key: K): Storage[K] | null {
const storage = window.localStorage;
const value = storage.getItem(key);
if (!value) { return null; }
return JSON.parse(value) as Storage[K];
}

function setItem<K extends keyof Storage>(key: K, value: Storage[K]) {
const storage = window.localStorage;
storage.setItem(key, JSON.stringify(value));
}

return {
getItem,
setItem
};
}

interface Storage {
stations: number[]
}
36 changes: 36 additions & 0 deletions composables/useSave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { saveAs } from "file-saver";

function getTrack(station: Station, quality: PlaylistQuality) {
switch (quality) {
case "AAC 64":
return station.stream_64;
case "AAC 96":
return station.stream_128;
default:
return station.stream_64;
}
}

function savePlaylist(quality: PlaylistQuality, checked: Set<number>) {
const { stations } = useData();
const tracks = stations.value.filter(s => checked.has(s.id));

const name = `Radio Record (${quality})`;
let playlist = "#EXTM3U\n";
playlist += `#PLAYLIST:${name}\n`;
tracks.forEach(T => {
playlist += `#EXTINF: -1,${T.title}\n`;
playlist += `${getTrack(T, quality)}\n`;
});
const blob = new Blob([playlist]);
saveAs(blob, `${name}.m3u8`);
console.log(`Playlist saved: ${name}(${tracks.length})`);

}

export default function () {
return {
savePlaylist
};
}

2 changes: 1 addition & 1 deletion composables/useSort.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const useSort = () => useState<string>("sort", () => "");
export const useSort = () => useState<Sort>("sort", () => "default");
13 changes: 10 additions & 3 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { execSync } from "child_process";

const exec = (command: string) => execSync(command).toString().trim();
const branch = exec("git branch --show-current");
const hash = exec("git rev-parse HEAD");
const date = new Date().toISOString();

export default defineNuxtConfig({
css: [
"@/assets/css/styles.scss"
Expand All @@ -12,9 +19,9 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
repository: "https://github.com/Virenbar/RadioRecord",
branch: process.env.HEAD || "master",
hash: process.env.COMMIT_REF || "unknown",
date: new Date().toISOString()
branch: branch,
hash: hash,
date: date
}
},
vite: {
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "virenbar.radio-record",
"version": "3.0.0",
"version": "3.2.0",
"description": "Auto-updatable website with links to radio Record stations",
"author": "Virenbar",
"license": "MIT",
Expand All @@ -17,19 +17,21 @@
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"file-saver": "^2.0.5",
"lodash-es": "^4.17.21"
},
"devDependencies": {
"@artmizu/yandex-metrika-nuxt": "^1.0.4",
"@nuxt/eslint": "^0.3.13",
"@types/bootstrap": "^5.2.10",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.12",
"eslint": "^9.3.0",
"nuxt": "^3.11.2",
"nuxt-gtag": "^2.0.6",
"sass": "^1.77.2",
"nuxt": "^3.11.2",
"sass-loader": "^14.2.1",
"sass": "^1.77.2",
"tsx": "^4.11.0",
"typescript": "^5.4.5",
"webpack": "^5.91.0"
Expand Down
32 changes: 32 additions & 0 deletions pages/about.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
const { repository, branch } = useRuntimeConfig().public;
</script>
<template>
<div class="container p-3 w-50">
<div class="card text-center">
<div class="card-header">
<b>О сайте</b>
</div>
<div class="card-body text-center w-75 mx-auto">
<p>
Данный сайт использовался для изучения HTML/CSS/JS.
</p>
<p>
Изначально был создан в виде самописного генератора страницы, затем был переписан на preact (альтернатива react).
И в конечном итоге переписан на фреймворк Nuxt.
</p>
<p>
Плейлисты с всегда актуальным списком каналов доступны
<NuxtLink target="_blank" :to="`${repository}/tree/${branch}/playlists`">
<span>здесь</span>
</NuxtLink>.
</p>
</div>
<div class="card-footer text-center">
<NuxtLink class="btn btn-outline-primary" target="_blank" to="https://radiorecord.ru/">
Сайт радио Record
</NuxtLink>
</div>
</div>
</div>
</template>
Loading

0 comments on commit b315aa5

Please sign in to comment.