refactor(all): rework how state is being managed

this rewrites a lot of hooks and removes providers in exchange for the store api

BREAKING CHANGE: breaks everything

fix #1
This commit is contained in:
Triston Armstrong 2024-03-17 15:01:06 -05:00
parent 8426d8acd7
commit 13da06a270
12 changed files with 124 additions and 159 deletions

View File

@ -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:

View File

@ -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>
)
}

View 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

View 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
}

View 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 }
}

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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>

View File

@ -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>
) )

View File

@ -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 (

View File

@ -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
}

View File

@ -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>
);
}