Compare commits
	
		
			7 Commits
		
	
	
		
			a267f9e0df
			...
			717790cb97
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 717790cb97 | |||
| fbe9153bfc | |||
| 2a5f0ea601 | |||
| f0dd632922 | |||
| 031505600c | |||
| 03a5fb5497 | |||
| c193e5da2d | 
| @@ -60,7 +60,6 @@ aside { | ||||
|  | ||||
| .menu-list { | ||||
|     list-style: none; | ||||
|     background-color: brown; | ||||
|     margin-top: 0px; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="utf-8"/> | ||||
|     <title>stream.t-juice.club</title> | ||||
|     <title></title> | ||||
|     <script src="./js/app.tsx" type="module"></script> | ||||
|     <link rel="stylesheet" href="./css/style.css" /> | ||||
|     <link rel="stylesheet" href="./css/fonts.css" /> | ||||
|   | ||||
| @@ -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<RTCSessionDescription> { | ||||
| @@ -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<SiteInfo> { | ||||
|         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 | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										141
									
								
								src/js/app.tsx
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								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<ThemeContextType>("light"); | ||||
|  | ||||
| type AppState = { | ||||
|     streamList: string[] | ||||
|     selectedStream: string | undefined | ||||
| interface AppProps { | ||||
|     api: MinistreamApiClient | ||||
| } | ||||
| type AppProps = {} | ||||
|  | ||||
| export class App extends React.Component<AppProps, AppState> { | ||||
|     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<string[]>([]) | ||||
|     const [selectedStream, setSelectedStream] = useState<string | null>(null) | ||||
|  | ||||
|     const updateSelect = (item: string | null) => { | ||||
|         setSelectedStream(item) | ||||
|     } | ||||
|  | ||||
|     async componentDidMount(): Promise<void> { | ||||
|         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 ( | ||||
|             <> | ||||
|                 <Menu | ||||
|                     items={this.state.streamList} | ||||
|                     selectedItem={this.state.selectedStream} | ||||
|                     selectCallback={this.updateSelect} | ||||
|                 /> | ||||
|                 <MediaContainer selectedStream={this.state.selectedStream} api={this.apiClient} /> | ||||
|             </> | ||||
|         ) | ||||
|     } | ||||
|     setInterval(() => { | ||||
|         updateStreamList() | ||||
|     }, 10000) | ||||
|  | ||||
|     useEffect(() => { | ||||
|         updateStreamList() | ||||
|     }, []) | ||||
|  | ||||
|     useEffect(() => { | ||||
|         updateTitle() | ||||
|     }, []) | ||||
|  | ||||
|     return ( | ||||
|         <> | ||||
|             <Menu | ||||
|                 items={streamList} | ||||
|                 selectedItem={selectedStream} | ||||
|                 selectCallback={updateSelect} | ||||
|             /> | ||||
|             <MediaContainer selectedStream={selectedStream} api={api} /> | ||||
|         </> | ||||
|     ) | ||||
| } | ||||
|  | ||||
| const rootElement = document.getElementById("app"); | ||||
| setTitleFromLocalstorage() | ||||
| if (rootElement) { | ||||
|     const root = createRoot(rootElement); | ||||
|     root.render(<App />) | ||||
|     const root = createRoot(rootElement) | ||||
|     const api = new MinistreamApiClient() | ||||
|     root.render(<App api={api} />) | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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<MenuProps> { | ||||
|     constructor(props: MenuProps) { | ||||
|         super(props) | ||||
|     } | ||||
|  | ||||
|     render() { | ||||
|         return ( | ||||
|             <div id="menu"> | ||||
|                 <aside> | ||||
|                     <a className="menu-heading" href="#">stream.t-juice.club</a> | ||||
|                     <ul className="menu-list"> | ||||
|                         {this.menuitems()} | ||||
|                     </ul> | ||||
|                 </aside> | ||||
|             </div> | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     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 <li key={idx} className="menu-item menu-selected"><a href={"#" + value} className="menu-link">{value}</a></li> | ||||
|                     } else { | ||||
|                         return <li key={idx} onClick={() => { | ||||
|                             this.props.selectCallback(value) | ||||
|                             selectCallback(value) | ||||
|                         }} className="menu-item"><a href={"#" + value} className="menu-link">{value}</a></li> | ||||
|                     } | ||||
|                 })} | ||||
|             </> | ||||
|         ) | ||||
|     } | ||||
|     return ( | ||||
|         <div id="menu"> | ||||
|             <aside> | ||||
|                 <a className="menu-heading" onClick={() => { | ||||
|                     selectCallback(null) | ||||
|                 }}href="#">{title}</a> | ||||
|                 <ul className="menu-list"> | ||||
|                     {menuitems()} | ||||
|                 </ul> | ||||
|             </aside> | ||||
|         </div> | ||||
|     ) | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user