splits components into more managable chunks #8
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "big-word",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -12,7 +12,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1",
|
||||
"kaioken": "^0.10.0"
|
||||
"kaioken": "^0.10.5",
|
||||
"vite-plugin-kaioken": "^0.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1",
|
||||
@ -21,8 +22,7 @@
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-kaioken": "^0.0.7"
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
20
src-tauri/Cargo.lock
generated
20
src-tauri/Cargo.lock
generated
@ -119,16 +119,6 @@ version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "big-word"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -1378,6 +1368,16 @@ dependencies = [
|
||||
"treediff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "klectr_radio"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.2"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "big-word"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
name = "klectr_radio"
|
||||
version = "0.1.0"
|
||||
description = "Your own personal radio app for Klecting the stations you love"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.big-word.dev",
|
||||
"identifier": "com.KlectrRadio.dev",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
|
230
src/App.tsx
230
src/App.tsx
@ -1,212 +1,32 @@
|
||||
import { exists, readTextFile, writeTextFile } from "@tauri-apps/api/fs"
|
||||
import { Navs, useNavigator } from "./hooks/useNavigator"
|
||||
import { appDataDir } from "@tauri-apps/api/path"
|
||||
import { useEffect, useModel, useRef, useState } from "kaioken"
|
||||
import { useEffect } from "kaioken"
|
||||
import Main from "./pages/Main"
|
||||
import Player from "./pages/Player"
|
||||
import Add from "./pages/Add"
|
||||
import useNavigationStore, { Navs } from "./hooks/navigationStores"
|
||||
import { useStorage } from "./hooks/storageStores"
|
||||
import { useStationsStore } from "./hooks/stationStores"
|
||||
|
||||
interface Station {
|
||||
url: string
|
||||
avatar: string
|
||||
title: string
|
||||
}
|
||||
export function App() {
|
||||
const { nav, setNavitation } = useNavigator()
|
||||
const [stations, setStations] = useState<Station[] | null>(null)
|
||||
const [titleRef, title,] = useModel<HTMLInputElement, string>('')
|
||||
const [streamRef, streamUrl,] = useModel<HTMLInputElement, string>('')
|
||||
const [avatarRef, avatarUrl,] = useModel<HTMLInputElement, string>('')
|
||||
const [selectedStation, setSelectedStation] = useState<Station | null>(null)
|
||||
const appDataDirRef = useRef<string>(null)
|
||||
const { getStationsFile } = useStorage()
|
||||
const { override } = useStationsStore()
|
||||
const { value } = useNavigationStore()
|
||||
|
||||
useEffect(() => {
|
||||
appDataDir()
|
||||
.then(async (res) => {
|
||||
const path = `${res}/stations.json`
|
||||
if (await exists(path)) {
|
||||
console.log("file exists: ", path);
|
||||
const jsonString = await readTextFile(path)
|
||||
const json = JSON.parse(jsonString) as Station[]
|
||||
console.log(json)
|
||||
setStations(json)
|
||||
appDataDirRef.current = path
|
||||
return
|
||||
}
|
||||
|
||||
console.error("File does not exist... creating")
|
||||
writeTextFile(path, "[]", { append: false })
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
|
||||
|
||||
getStationsFile()
|
||||
.then(res => res && override(res))
|
||||
.catch()
|
||||
}, [])
|
||||
function _handleStationAdd() {
|
||||
console.log({ title, streamUrl, avatarUrl })
|
||||
const data: Station = {
|
||||
url: streamUrl,
|
||||
avatar: avatarUrl,
|
||||
title: title
|
||||
}
|
||||
setStations(prev => {
|
||||
const newStations = [...(prev ?? []), data]
|
||||
// write file
|
||||
writeTextFile(appDataDirRef.current!, JSON.stringify(newStations))
|
||||
return newStations
|
||||
})
|
||||
console.log("Added station: ", data)
|
||||
setNavitation(Navs.MAIN)
|
||||
|
||||
|
||||
switch (value) {
|
||||
case Navs.MAIN:
|
||||
return <Main />
|
||||
case Navs.ADD:
|
||||
return <Add />
|
||||
case Navs.PLAYER:
|
||||
return <Player />
|
||||
default:
|
||||
return <h1>404 Not Found</h1>
|
||||
}
|
||||
|
||||
function _handleStationClick(station: Station) {
|
||||
return function() {
|
||||
setSelectedStation(station)
|
||||
setNavitation(Navs.PLAYER)
|
||||
}
|
||||
}
|
||||
|
||||
if (nav === Navs.MAIN) {
|
||||
if (!stations?.length) {
|
||||
return (
|
||||
<div className="p-4 flex flex-col align-between h-full gap-2">
|
||||
<h2 className="text-black text-opacity-50 font-bold text-center">No Stations Added</h2>
|
||||
<button className="bg-white rounded-xl p-2" onclick={() => setNavitation(1)}>+</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-end">
|
||||
<button className="mr-4 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-4 flex flex-col align-between h-full gap-2">
|
||||
{stations.map((s) => (
|
||||
<button onclick={_handleStationClick(s)} className="paper p-2 flex flex-row border gap-4">
|
||||
<img src={s.avatar} width={40} height={40} className="w-20 rounded-xl" />
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-xl">{s.title}</h2>
|
||||
<button className="border border-red-500 rounded w-full text-red-500 px-4">Delete</button>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<button className="bg-white rounded-xl p-2" onclick={() => setNavitation(1)}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if (nav === Navs.ADD) {
|
||||
return (
|
||||
<div >
|
||||
<button onclick={() => setNavitation(0)} className="ml-2 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="flex flex-col gap-3 p-2">
|
||||
<input ref={titleRef} className="rounded p-1" ariaLabel={"title"} placeholder="Title" />
|
||||
<input ref={streamRef} className="rounded p-1" ariaLabel={"url"} placeholder="Stream URL" />
|
||||
<input ref={avatarRef} className="rounded p-1" ariaLabel={"avatar"} placeholder="Image URL" />
|
||||
<button onclick={_handleStationAdd} className="rounded bg-blue-400 p-1">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function _handlePlayerBackClick() {
|
||||
setNavitation(Navs.MAIN)
|
||||
setSelectedStation(null)
|
||||
}
|
||||
|
||||
if (nav === Navs.PLAYER) {
|
||||
return (
|
||||
<div >
|
||||
<button onclick={_handlePlayerBackClick} className="ml-2 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="flex justify-center">
|
||||
<div className="p-8 w-80">
|
||||
{/*<!-- Album Cover -->*/}
|
||||
<img src={selectedStation?.avatar} alt={selectedStation?.title} className="w-64 h-64 mx-auto rounded-lg mb-4 shadow-md shadow-black-200" />
|
||||
{/*<!-- Song Title -->*/}
|
||||
<h2 className="text-xl font-semibold text-center">{selectedStation?.title}</h2>
|
||||
{/*<!-- Artist Name -->*/}
|
||||
<p className="text-gray-600 text-sm text-center">Live Radio</p>
|
||||
<br />
|
||||
<audio autoplay controls src={selectedStation?.url}></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>Not a navigation</div>
|
||||
)
|
||||
}
|
||||
// <div className="flex flex-col gap-3 p-2">
|
||||
// <button onclick={() => setNavitation(0)}>{"<"}</button>
|
||||
// <div>
|
||||
// <img src={selectedStation?.avatar} />
|
||||
// <h2>{selectedStation?.title}</h2>
|
||||
// <p>Live</p>
|
||||
// <div>
|
||||
// {/* progress bar */}
|
||||
// </div>
|
||||
//
|
||||
// <div>
|
||||
// {/* play pause button */}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
//
|
||||
//
|
||||
//{/*<!-- Music Controls -->*/}
|
||||
// <div className="mt-6 flex justify-center items-center">
|
||||
// <button className="p-3 rounded-full bg-gray-200 hover:bg-gray-300 focus:outline-none">
|
||||
// <svg width="64px" height="64px" viewBox="0 0 24 24" className="w-4 h-4 text-gray-600" fill="none" xmlns="http://www.w3.org/2000/svg" transform="matrix(-1, 0, 0, 1, 0, 0)">
|
||||
// <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
// <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
// <g id="SVGRepo_iconCarrier">
|
||||
// <path d="M16.6598 14.6474C18.4467 13.4935 18.4467 10.5065 16.6598 9.35258L5.87083 2.38548C4.13419 1.26402 2 2.72368 2 5.0329V18.9671C2 21.2763 4.13419 22.736 5.87083 21.6145L16.6598 14.6474Z" fill="#000000"></path>
|
||||
// <path d="M22.75 5C22.75 4.58579 22.4142 4.25 22 4.25C21.5858 4.25 21.25 4.58579 21.25 5V19C21.25 19.4142 21.5858 19.75 22 19.75C22.4142 19.75 22.75 19.4142 22.75 19V5Z" fill="#000000"></path>
|
||||
// </g>
|
||||
// </svg>
|
||||
// </button>
|
||||
// <button className="p-4 rounded-full bg-gray-200 hover:bg-gray-300 focus:outline-none mx-4">
|
||||
// <svg width="64px" height="64px" viewBox="0 0 24 24" className="w-6 h-6 text-gray-600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
// <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
// <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
// <g id="SVGRepo_iconCarrier">
|
||||
// <path d="M2 6C2 4.11438 2 3.17157 2.58579 2.58579C3.17157 2 4.11438 2 6 2C7.88562 2 8.82843 2 9.41421 2.58579C10 3.17157 10 4.11438 10 6V18C10 19.8856 10 20.8284 9.41421 21.4142C8.82843 22 7.88562 22 6 22C4.11438 22 3.17157 22 2.58579 21.4142C2 20.8284 2 19.8856 2 18V6Z" fill="#000000"></path>
|
||||
// <path d="M14 6C14 4.11438 14 3.17157 14.5858 2.58579C15.1716 2 16.1144 2 18 2C19.8856 2 20.8284 2 21.4142 2.58579C22 3.17157 22 4.11438 22 6V18C22 19.8856 22 20.8284 21.4142 21.4142C20.8284 22 19.8856 22 18 22C16.1144 22 15.1716 22 14.5858 21.4142C14 20.8284 14 19.8856 14 18V6Z" fill="#000000"></path>
|
||||
// </g>
|
||||
// </svg>
|
||||
// </button>
|
||||
// <button className="p-3 rounded-full bg-gray-200 hover:bg-gray-300 focus:outline-none">
|
||||
// <svg width="64px" height="64px" viewBox="0 0 24 24" className="w-4 h-4 text-gray-600" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
// <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
|
||||
// <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
|
||||
// <g id="SVGRepo_iconCarrier">
|
||||
// <path d="M16.6598 14.6474C18.4467 13.4935 18.4467 10.5065 16.6598 9.35258L5.87083 2.38548C4.13419 1.26402 2 2.72368 2 5.0329V18.9671C2 21.2763 4.13419 22.736 5.87083 21.6145L16.6598 14.6474Z" fill="#000000"></path>
|
||||
// <path d="M22.75 5C22.75 4.58579 22.4142 4.25 22 4.25C21.5858 4.25 21.25 4.58579 21.25 5V19C21.25 19.4142 21.5858 19.75 22 19.75C22.4142 19.75 22.75 19.4142 22.75 19V5Z" fill="#000000"></path>
|
||||
// </g>
|
||||
// </svg>
|
||||
// </button>
|
||||
// </div>
|
||||
// {/*<!-- Progress Bar -->*/}
|
||||
// <div className="mt-6 bg-gray-200 h-2 rounded-full">
|
||||
// <div className="bg-teal-500 h-2 rounded-full w-1/2"></div>
|
||||
// </div>
|
||||
// {/*<!-- Time Information -->*/}
|
||||
// <div className="flex justify-between mt-2 text-sm text-gray-600">
|
||||
// <span>1:57</span>
|
||||
// <span>3:53</span>
|
||||
// </div>
|
||||
|
||||
|
13
src/hooks/navigationStores.ts
Normal file
13
src/hooks/navigationStores.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { createStore } from "kaioken"
|
||||
|
||||
export enum Navs {
|
||||
MAIN,
|
||||
ADD,
|
||||
PLAYER,
|
||||
}
|
||||
|
||||
const useNavigationStore = createStore(Navs.MAIN, (set) => ({
|
||||
navigate: (value) => set(() => value),
|
||||
}))
|
||||
|
||||
export default useNavigationStore
|
39
src/hooks/stationStores.ts
Normal file
39
src/hooks/stationStores.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { writeTextFile } from "@tauri-apps/api/fs"
|
||||
import { createStore } from "kaioken"
|
||||
|
||||
export const useStationsStore = createStore(
|
||||
null as Station[] | null,
|
||||
(set) => ({
|
||||
add: (station: Station): Station[] => {
|
||||
let newState: Station[] | null = null
|
||||
set((state) => {
|
||||
newState = [...(state ?? []), station]
|
||||
return newState
|
||||
})
|
||||
//@ts-ignore
|
||||
return newState
|
||||
},
|
||||
delete: (stationId) =>
|
||||
set(
|
||||
(state) => state?.filter((station) => station.id !== stationId) ?? []
|
||||
),
|
||||
override: (stationsList: Station[]) => {
|
||||
set((_state) => stationsList)
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
export const useSelectStationStore = createStore(
|
||||
null as Station | null,
|
||||
(set) => ({
|
||||
make: (station) => set((_state) => station),
|
||||
clear: () => set((_state) => null),
|
||||
})
|
||||
)
|
||||
|
||||
export interface Station {
|
||||
id: string
|
||||
url: string
|
||||
avatar: string
|
||||
title: string
|
||||
}
|
35
src/hooks/storageStores.ts
Normal file
35
src/hooks/storageStores.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { exists, readTextFile, writeTextFile } from "@tauri-apps/api/fs"
|
||||
import { appDataDir } from "@tauri-apps/api/path"
|
||||
import { createStore } from "kaioken"
|
||||
import { Station } from "../hooks/stationStores"
|
||||
|
||||
export const useStorageStore = createStore(
|
||||
null as string | null,
|
||||
(_set) => ({})
|
||||
)
|
||||
|
||||
export function useStorage() {
|
||||
async function _createStationsFile(path: string): Promise<Station[]> {
|
||||
await writeTextFile(path, "[]", { append: false })
|
||||
return []
|
||||
}
|
||||
|
||||
async function getStationsFile(): Promise<Station[] | undefined> {
|
||||
let dir: null | string = null
|
||||
try {
|
||||
dir = await appDataDir()
|
||||
} catch (err) {
|
||||
console.error("getStationsFile: ", err)
|
||||
return undefined
|
||||
}
|
||||
if (!dir) return undefined
|
||||
const path = `${dir}stations.json`
|
||||
if (!(await exists(path))) return await _createStationsFile(path)
|
||||
const jsonString = await readTextFile(path)
|
||||
const json = JSON.parse(jsonString) as Station[]
|
||||
useStorageStore.setState(() => path)
|
||||
return json
|
||||
}
|
||||
|
||||
return { getStationsFile }
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { useState } from "kaioken"
|
||||
export enum Navs {
|
||||
MAIN,
|
||||
ADD,
|
||||
PLAYER,
|
||||
}
|
||||
|
||||
export function useNavigator() {
|
||||
const [nav, setNav] = useState<Navs>(Navs.MAIN)
|
||||
function _setNavitation(newNav: Navs) {
|
||||
setNav(newNav)
|
||||
}
|
||||
return {
|
||||
nav,
|
||||
setNavitation: _setNavitation,
|
||||
}
|
||||
}
|
10
src/main.ts
10
src/main.ts
@ -1,6 +1,6 @@
|
||||
import "./styles.css";
|
||||
import { mount } from "kaioken";
|
||||
import { App } from "./App";
|
||||
import { App } from "./App"
|
||||
import "./styles.css"
|
||||
import { mount } from "kaioken"
|
||||
|
||||
const root = document.getElementById("root")!;
|
||||
mount(App, root);
|
||||
const root = document.getElementById("root")!
|
||||
mount(App, root)
|
||||
|
41
src/pages/Add.tsx
Normal file
41
src/pages/Add.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { writeTextFile } from "@tauri-apps/api/fs"
|
||||
import { useModel } from "kaioken"
|
||||
import { Station, useStationsStore } from "../hooks/stationStores"
|
||||
import { useStorageStore } from "../hooks/storageStores"
|
||||
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||
|
||||
export default function Add() {
|
||||
const { value } = useStorageStore()
|
||||
const [titleRef, title,] = useModel<HTMLInputElement, string>('')
|
||||
const [streamRef, streamUrl,] = useModel<HTMLInputElement, string>('')
|
||||
const [avatarRef, avatarUrl,] = useModel<HTMLInputElement, string>('')
|
||||
|
||||
function _handleStationAdd() {
|
||||
const data: Station = {
|
||||
id: Math.random().toString(16).slice(2),
|
||||
url: streamUrl,
|
||||
avatar: avatarUrl,
|
||||
title: title
|
||||
}
|
||||
const store = useStationsStore.methods.add(data)
|
||||
const valid = store && value
|
||||
valid && void writeTextFile(value, JSON.stringify(store))
|
||||
useNavigationStore.setState(Navs.MAIN)
|
||||
}
|
||||
|
||||
return (
|
||||
<div >
|
||||
<button onclick={() => useNavigationStore.setState(Navs.MAIN)} className="ml-2 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="flex flex-col gap-3 p-2">
|
||||
<input ref={titleRef} className="rounded p-1" ariaLabel={"title"} placeholder="Title" />
|
||||
<input ref={streamRef} className="rounded p-1" ariaLabel={"url"} placeholder="Stream URL" />
|
||||
<input ref={avatarRef} className="rounded p-1" ariaLabel={"avatar"} placeholder="Image URL" />
|
||||
<button onclick={_handleStationAdd} className="rounded bg-blue-400 p-1">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
49
src/pages/Main.tsx
Normal file
49
src/pages/Main.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||
import { Station, useSelectStationStore } from "../hooks/stationStores"
|
||||
import { useStationsStore } from "../hooks/stationStores"
|
||||
|
||||
export default function Main() {
|
||||
const { make } = useSelectStationStore()
|
||||
const { navigate } = useNavigationStore()
|
||||
const stations = useStationsStore.getState()
|
||||
|
||||
function _handleStationClick(station: Station) {
|
||||
make(station)
|
||||
navigate(Navs.PLAYER)
|
||||
}
|
||||
|
||||
if (!stations?.length) {
|
||||
return (
|
||||
<div className="p-4 flex flex-col align-between h-full gap-2">
|
||||
<h2 className="text-black text-opacity-50 font-bold text-center">No Stations Added</h2>
|
||||
<button className="bg-white rounded-xl p-2" onclick={() => navigate(Navs.ADD)}>+</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-end">
|
||||
<button className="mr-4 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-4 flex flex-col align-between h-full gap-2">
|
||||
{stations.map((s) => (
|
||||
<button onclick={() => _handleStationClick(s)} className="paper p-2 flex flex-row border gap-4">
|
||||
<img src={s.avatar} width={40} height={40} className="w-20 rounded-xl" />
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<h2 className="text-xl">{s.title}</h2>
|
||||
<button className="border border-red-500 rounded w-full text-red-500 px-4">Delete</button>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<button className="bg-white rounded-xl p-2" onclick={() => navigate(Navs.ADD)}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
34
src/pages/Player.tsx
Normal file
34
src/pages/Player.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||
import { useSelectStationStore } from "../hooks/stationStores"
|
||||
|
||||
export default function Player() {
|
||||
const { value: selectedStation, make } = useSelectStationStore()
|
||||
const { navigate } = useNavigationStore()
|
||||
|
||||
function _handlePlayerBackClick() {
|
||||
navigate(Navs.MAIN)
|
||||
make(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div >
|
||||
<button onclick={_handlePlayerBackClick} className="ml-2 px-2 py-1 hover:bg-gray-300 rounded-full">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="flex justify-center">
|
||||
<div className="p-8 w-80">
|
||||
{/*<!-- Album Cover -->*/}
|
||||
<img src={selectedStation?.avatar} alt={selectedStation?.title} className="w-64 h-64 mx-auto rounded-lg mb-4 shadow-md shadow-black-200" />
|
||||
{/*<!-- Song Title -->*/}
|
||||
<h2 className="text-xl font-semibold text-center">{selectedStation?.title}</h2>
|
||||
{/*<!-- Artist Name -->*/}
|
||||
<p className="text-gray-600 text-sm text-center">Live Radio</p>
|
||||
<br />
|
||||
<audio autoplay controls src={selectedStation?.url}></audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user