splits components into more managable chunks #8
17
src/App.tsx
17
src/App.tsx
@ -1,23 +1,24 @@
|
|||||||
import { Navs, useNavigator } from "./hooks/useNavigator"
|
|
||||||
import { useEffect } from "kaioken"
|
import { useEffect } from "kaioken"
|
||||||
import { useStationsProvider } from "./providers/StationsProvider"
|
|
||||||
import Main from "./pages/Main"
|
import Main from "./pages/Main"
|
||||||
import Player from "./pages/Player"
|
import Player from "./pages/Player"
|
||||||
import { useStorageContext } from "./providers/StorageProvider"
|
|
||||||
import Add from "./pages/Add"
|
import Add from "./pages/Add"
|
||||||
|
import useNavigationStore, { Navs } from "./hooks/navigationStores"
|
||||||
|
import { useStorage } from "./hooks/storageStores"
|
||||||
|
import { useStationsStore } from "./hooks/stationStores"
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { setStations } = useStationsProvider()
|
const { getStationsFile } = useStorage()
|
||||||
const { getStationsFile } = useStorageContext()
|
const { override } = useStationsStore()
|
||||||
const { nav } = useNavigator()
|
const { value } = useNavigationStore()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStationsFile()
|
getStationsFile()
|
||||||
.then(res => res && setStations(res))
|
.then(res => res && override(res))
|
||||||
.catch()
|
.catch()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
switch (nav) {
|
|
||||||
|
switch (value) {
|
||||||
case Navs.MAIN:
|
case Navs.MAIN:
|
||||||
return <Main />
|
return <Main />
|
||||||
case Navs.ADD:
|
case Navs.ADD:
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import { App } from "./App";
|
|
||||||
import { StationsContextProvider } from "./providers/StationsProvider";
|
|
||||||
import { StorageContextProvider } from "./providers/StorageProvider";
|
|
||||||
|
|
||||||
export default function ProviderWrapper() {
|
|
||||||
return (
|
|
||||||
<StationsContextProvider>
|
|
||||||
<StorageContextProvider>
|
|
||||||
<App />
|
|
||||||
</StorageContextProvider>
|
|
||||||
</StationsContextProvider>
|
|
||||||
)
|
|
||||||
}
|
|
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,19 +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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
|
import { App } from "./App"
|
||||||
import "./styles.css"
|
import "./styles.css"
|
||||||
import { mount } from "kaioken"
|
import { mount } from "kaioken"
|
||||||
import ProviderWrapper from "./ProviderWrapper"
|
|
||||||
|
|
||||||
const root = document.getElementById("root")!
|
const root = document.getElementById("root")!
|
||||||
mount(ProviderWrapper, root)
|
mount(App, root)
|
||||||
|
@ -1,35 +1,31 @@
|
|||||||
import { writeTextFile } from "@tauri-apps/api/fs"
|
import { writeTextFile } from "@tauri-apps/api/fs"
|
||||||
import { Navs, useNavigator } from "../hooks/useNavigator"
|
|
||||||
import { Station, useStationsProvider } from "../providers/StationsProvider"
|
|
||||||
import { useModel } from "kaioken"
|
import { useModel } from "kaioken"
|
||||||
import { useStorageContext } from "../providers/StorageProvider"
|
import { Station, useStationsStore } from "../hooks/stationStores"
|
||||||
|
import { useStorageStore } from "../hooks/storageStores"
|
||||||
|
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||||
|
|
||||||
export default function Add() {
|
export default function Add() {
|
||||||
const { setNavitation } = useNavigator()
|
const { value } = useStorageStore()
|
||||||
const { setStations } = useStationsProvider()
|
|
||||||
const { appDataDirRef } = useStorageContext()
|
|
||||||
const [titleRef, title,] = useModel<HTMLInputElement, string>('')
|
const [titleRef, title,] = useModel<HTMLInputElement, string>('')
|
||||||
const [streamRef, streamUrl,] = useModel<HTMLInputElement, string>('')
|
const [streamRef, streamUrl,] = useModel<HTMLInputElement, string>('')
|
||||||
const [avatarRef, avatarUrl,] = useModel<HTMLInputElement, string>('')
|
const [avatarRef, avatarUrl,] = useModel<HTMLInputElement, string>('')
|
||||||
|
|
||||||
function _handleStationAdd() {
|
function _handleStationAdd() {
|
||||||
const data: Station = {
|
const data: Station = {
|
||||||
|
id: Math.random().toString(16).slice(2),
|
||||||
url: streamUrl,
|
url: streamUrl,
|
||||||
avatar: avatarUrl,
|
avatar: avatarUrl,
|
||||||
title: title
|
title: title
|
||||||
}
|
}
|
||||||
setStations(prev => {
|
const store = useStationsStore.methods.add(data)
|
||||||
const newStations = [...(prev ?? []), data]
|
const valid = store && value
|
||||||
// write file
|
valid && void writeTextFile(value, JSON.stringify(store))
|
||||||
writeTextFile(appDataDirRef.current!, JSON.stringify(newStations))
|
useNavigationStore.setState(Navs.MAIN)
|
||||||
return newStations
|
|
||||||
})
|
|
||||||
setNavitation(Navs.MAIN)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div >
|
<div >
|
||||||
<button onclick={() => setNavitation(0)} className="ml-2 px-2 py-1 hover:bg-gray-300 rounded-full">
|
<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">
|
<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" />
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import { Navs, useNavigator } from "../hooks/useNavigator"
|
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||||
import { Station, useStationsProvider } from "../providers/StationsProvider"
|
import { Station, useSelectStationStore } from "../hooks/stationStores"
|
||||||
|
import { useStationsStore } from "../hooks/stationStores"
|
||||||
|
|
||||||
export default function Main() {
|
export default function Main() {
|
||||||
const { setSelectedStation, stations } = useStationsProvider()
|
const { make } = useSelectStationStore()
|
||||||
const { setNavitation } = useNavigator()
|
const { navigate } = useNavigationStore()
|
||||||
|
const stations = useStationsStore.getState()
|
||||||
|
|
||||||
function _handleStationClick(station: Station) {
|
function _handleStationClick(station: Station) {
|
||||||
setSelectedStation(station)
|
make(station)
|
||||||
setNavitation(Navs.PLAYER)
|
navigate(Navs.PLAYER)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stations?.length) {
|
if (!stations?.length) {
|
||||||
return (
|
return (
|
||||||
<div className="p-4 flex flex-col align-between h-full gap-2">
|
<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>
|
<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>
|
<button className="bg-white rounded-xl p-2" onclick={() => navigate(Navs.ADD)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -40,7 +42,7 @@ export default function Main() {
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
<button className="bg-white rounded-xl p-2" onclick={() => setNavitation(1)}>+</button>
|
<button className="bg-white rounded-xl p-2" onclick={() => navigate(Navs.ADD)}>+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { Navs, useNavigator } from "../hooks/useNavigator"
|
import useNavigationStore, { Navs } from "../hooks/navigationStores"
|
||||||
import { useStationsProvider } from "../providers/StationsProvider"
|
import { useSelectStationStore } from "../hooks/stationStores"
|
||||||
|
|
||||||
export default function Player() {
|
export default function Player() {
|
||||||
const { setNavitation } = useNavigator()
|
const { value: selectedStation, make } = useSelectStationStore()
|
||||||
const { setSelectedStation, selectedStation } = useStationsProvider()
|
const { navigate } = useNavigationStore()
|
||||||
|
|
||||||
function _handlePlayerBackClick() {
|
function _handlePlayerBackClick() {
|
||||||
setNavitation(Navs.MAIN)
|
navigate(Navs.MAIN)
|
||||||
setSelectedStation(null)
|
make(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { createContext, useContext, useState } from 'kaioken';
|
|
||||||
|
|
||||||
interface StationsContextType {
|
|
||||||
stations: Station[] | null
|
|
||||||
setStations: (value: Kaioken.StateSetter<Station[] | null>) => void
|
|
||||||
selectedStation: Station | null
|
|
||||||
setSelectedStation: (value: Kaioken.StateSetter<Station | null>) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const StationsContext = createContext<StationsContextType>({} as StationsContextType);
|
|
||||||
|
|
||||||
export const useStationsProvider = () => useContext(StationsContext)
|
|
||||||
|
|
||||||
interface MyContextProviderProps {
|
|
||||||
children?: Kaioken.VNode | Kaioken.VNode[]
|
|
||||||
}
|
|
||||||
export function StationsContextProvider(props: MyContextProviderProps) {
|
|
||||||
const [stations, setStations] = useState<Station[] | null>(null)
|
|
||||||
const [selectedStation, setSelectedStation] = useState<Station | null>(null)
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
stations, setStations,
|
|
||||||
selectedStation, setSelectedStation
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StationsContext.Provider value={value}>
|
|
||||||
{props.children}
|
|
||||||
</StationsContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Station {
|
|
||||||
url: string
|
|
||||||
avatar: string
|
|
||||||
title: string
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import { exists, readTextFile, writeTextFile } from '@tauri-apps/api/fs';
|
|
||||||
import { appDataDir } from '@tauri-apps/api/path';
|
|
||||||
import { createContext, useContext, useRef } from 'kaioken';
|
|
||||||
import { Station } from './StationsProvider';
|
|
||||||
|
|
||||||
interface StorageContextType {
|
|
||||||
appDataDirRef: Kaioken.Ref<string>,
|
|
||||||
getStationsFile: () => Promise<Station[] | undefined>
|
|
||||||
}
|
|
||||||
|
|
||||||
const StorageContext = createContext<StorageContextType>({} as StorageContextType);
|
|
||||||
|
|
||||||
export const useStorageContext = () => useContext(StorageContext)
|
|
||||||
|
|
||||||
export function StorageContextProvider(props: any) {
|
|
||||||
const appDataDirRef = useRef<string>(null)
|
|
||||||
|
|
||||||
async function _getStationsFile(): Promise<Station[] | undefined> {
|
|
||||||
let dir: null | string = null
|
|
||||||
try {
|
|
||||||
dir = await appDataDir()
|
|
||||||
} catch (err) {
|
|
||||||
console.error(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[]
|
|
||||||
appDataDirRef.current = path
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
async function _createStationsFile(path: string): Promise<Station[]> {
|
|
||||||
await writeTextFile(path, "[]", { append: false })
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const value: StorageContextType = {
|
|
||||||
appDataDirRef,
|
|
||||||
getStationsFile: _getStationsFile
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StorageContext.Provider value={value}>
|
|
||||||
{props.children}
|
|
||||||
</StorageContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user