diff --git a/src/css/style.css b/src/css/style.css index bb41558..f57ab03 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -60,7 +60,6 @@ aside { .menu-list { list-style: none; - background-color: brown; margin-top: 0px; } diff --git a/src/index.html b/src/index.html index 4a80a66..730294f 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - stream.t-juice.club + diff --git a/src/js/api.ts b/src/js/api.ts index 518ce0b..3021467 100644 --- a/src/js/api.ts +++ b/src/js/api.ts @@ -1,4 +1,8 @@ +export interface SiteInfo { + siteName: string +} + export class MinistreamApiClient { ENV: string @@ -21,14 +25,22 @@ export class MinistreamApiClient { const resp = await fetch( url, ); - const res = resp.json() as unknown as string[]; - data = res; + data = await resp.json() as unknown as string[]; } catch (e) { throw e; } - return data; + const sortedStreams = data.sort((a, b) => { + if (a > b) { + return -1 + } else if (a < b) { + return 1 + } + return 0 + }) + + return sortedStreams; } async postOffer(streamKey: string, offer_sdp: RTCSessionDescription): Promise { @@ -43,8 +55,23 @@ export class MinistreamApiClient { body: offer_sdp.sdp }) const body = await resp.text() - const answer = new RTCSessionDescription({type: "answer", sdp: body}) + const answer = new RTCSessionDescription({ type: "answer", sdp: body }) return answer } + + async siteInfo(): Promise { + var url = "/api/siteinfo" + if (this.ENV !== "production") { + url = "http://localhost:8080/api/siteinfo" + } + + const resp = await fetch(url, + { + method: "GET", + }) + const data = resp.json() + + return data as unknown as SiteInfo + } } diff --git a/src/js/app.tsx b/src/js/app.tsx index 7dfe717..69c93e6 100644 --- a/src/js/app.tsx +++ b/src/js/app.tsx @@ -1,92 +1,91 @@ import { createRoot } from "react-dom/client"; -import React from "react"; import { Menu } from "./menu"; -import { createContext, useState } from "react"; +import { createContext, useEffect, useState } from "react"; import { MediaContainer } from "./media"; import { MinistreamApiClient } from "./api"; +import React from "react"; -type ThemeContextType = "light" | "dark"; - -const ThemeContext = createContext("light"); - -type AppState = { - streamList: string[] - selectedStream: string | undefined +interface AppProps { + api: MinistreamApiClient } -type AppProps = {} -export class App extends React.Component { - apiClient: MinistreamApiClient - constructor(props: AppProps) { - super(props) - this.state = { - streamList: [], - selectedStream: undefined - } - this.apiClient = new MinistreamApiClient() - this.updateSelect = this.updateSelect.bind(this) +const titleKey = "ministream.title" + +function setTitleFromLocalstorage() { + var title = localStorage.getItem(titleKey) + if (title) { + setTitle(title) + } +} + +function setTitle(title: string) { + const el = document.querySelector('title') + if (el) { + el.textContent = title + } +} + +export function App({ api }: AppProps) { + const [streamList, setStreamList] = useState([]) + const [selectedStream, setSelectedStream] = useState(null) + + const updateSelect = (item: string | null) => { + setSelectedStream(item) } - async componentDidMount(): Promise { - await this.updateStreamList() - setInterval(() => { - this.updateStreamList() - }, 10000) - } - - async updateStreamList() { - const streams = await this.apiClient.listStreams() - const sortedStreams = streams.sort((a, b) => { - if (a > b) { - return -1 - } else if (a < b) { - return 1 + const updateStreamList = () => { + api.listStreams().then((list) => { + setStreamList(list) + if (list.length != streamList.length) { + setStreamList(list) } - return 0 - }) - if (sortedStreams.length != this.state.streamList.length) { - this.setStreamList(streams) - } - - if (!sortedStreams.every((_, idx) => { - return sortedStreams[idx] === this.state.streamList[idx] - })) { - this.setStreamList(streams) - } - } - - setStreamList(streamList: string[]) { - console.log("stream list updated") - this.setState((state) => { - return { selectedStream: state.selectedStream, streamList: streamList } + if (!list.every((_, idx) => { + return list[idx] === streamList[idx] + })) { + setStreamList(list) + } }) } - updateSelect(selected: string): void { - if (selected !== this.state.selectedStream) { - this.setState((state) => { - return { streamList: state.streamList, selectedStream: selected } - }) - } + const updateTitle = () => { + api.siteInfo().then((info) => { + if (info.siteName != document.title) { + setTitle(info.siteName) + localStorage.setItem(titleKey, info.siteName) + } + }) + return } - render() { - return ( - <> - - - - ) - } + setInterval(() => { + updateStreamList() + }, 10000) + + useEffect(() => { + updateStreamList() + }, []) + + useEffect(() => { + updateTitle() + }, []) + + return ( + <> + + + + ) } const rootElement = document.getElementById("app"); +setTitleFromLocalstorage() if (rootElement) { - const root = createRoot(rootElement); - root.render() + const root = createRoot(rootElement) + const api = new MinistreamApiClient() + root.render() } \ No newline at end of file diff --git a/src/js/media.tsx b/src/js/media.tsx index 2ea5df7..c76a9d7 100644 --- a/src/js/media.tsx +++ b/src/js/media.tsx @@ -1,9 +1,10 @@ -import React, { ComponentProps, useRef, useEffect, useState } from "react" +import { ComponentProps, useRef, useEffect, useState } from "react" import { MinistreamApiClient } from "./api" import { resolve } from "path" +import React from "react" type MediaContainerProps = { - selectedStream: string | undefined + selectedStream: string | null api: MinistreamApiClient } diff --git a/src/js/menu.tsx b/src/js/menu.tsx index bc947e3..ec8f87f 100644 --- a/src/js/menu.tsx +++ b/src/js/menu.tsx @@ -1,45 +1,42 @@ import { createRoot } from "react-dom/client"; import React from "react"; -type StreamSelectCallback = (selected: string) => void +type StreamSelectCallback = (selected: string | null) => void -type MenuProps = { +interface MenuProps { items: string[] - selectedItem: string | undefined + selectedItem: string | null selectCallback: StreamSelectCallback } -export class Menu extends React.Component { - constructor(props: MenuProps) { - super(props) - } - - render() { - return ( - - ) - } - - private menuitems() { +export function Menu({ items, selectedItem, selectCallback }: MenuProps) { + const title = document.title + const menuitems = () => { return ( <> - {this.props.items.map((value, idx) => { - if (this.props.selectedItem == value) { + {items.map((value, idx) => { + if (selectedItem == value) { return
  • {value}
  • } else { return
  • { - this.props.selectCallback(value) + selectCallback(value) }} className="menu-item">{value}
  • } })} ) } + return ( + + ) + }