Set title and convert all class component to functional #4
@ -60,7 +60,6 @@ aside {
|
|||||||
|
|
||||||
.menu-list {
|
.menu-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
background-color: brown;
|
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<title>stream.t-juice.club</title>
|
<title></title>
|
||||||
<script src="./js/app.tsx" type="module"></script>
|
<script src="./js/app.tsx" type="module"></script>
|
||||||
<link rel="stylesheet" href="./css/style.css" />
|
<link rel="stylesheet" href="./css/style.css" />
|
||||||
<link rel="stylesheet" href="./css/fonts.css" />
|
<link rel="stylesheet" href="./css/fonts.css" />
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
|
||||||
|
export interface SiteInfo {
|
||||||
|
siteName: string
|
||||||
|
}
|
||||||
|
|
||||||
export class MinistreamApiClient {
|
export class MinistreamApiClient {
|
||||||
ENV: string
|
ENV: string
|
||||||
|
|
||||||
@ -21,14 +25,22 @@ export class MinistreamApiClient {
|
|||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
const res = resp.json() as unknown as string[];
|
data = await resp.json() as unknown as string[];
|
||||||
data = res;
|
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
throw 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> {
|
async postOffer(streamKey: string, offer_sdp: RTCSessionDescription): Promise<RTCSessionDescription> {
|
||||||
@ -43,8 +55,23 @@ export class MinistreamApiClient {
|
|||||||
body: offer_sdp.sdp
|
body: offer_sdp.sdp
|
||||||
})
|
})
|
||||||
const body = await resp.text()
|
const body = await resp.text()
|
||||||
const answer = new RTCSessionDescription({type: "answer", sdp: body})
|
const answer = new RTCSessionDescription({ type: "answer", sdp: body })
|
||||||
|
|
||||||
return answer
|
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 { createRoot } from "react-dom/client";
|
||||||
import React from "react";
|
|
||||||
import { Menu } from "./menu";
|
import { Menu } from "./menu";
|
||||||
import { createContext, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
import { MediaContainer } from "./media";
|
import { MediaContainer } from "./media";
|
||||||
import { MinistreamApiClient } from "./api";
|
import { MinistreamApiClient } from "./api";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
type ThemeContextType = "light" | "dark";
|
interface AppProps {
|
||||||
|
api: MinistreamApiClient
|
||||||
const ThemeContext = createContext<ThemeContextType>("light");
|
|
||||||
|
|
||||||
type AppState = {
|
|
||||||
streamList: string[]
|
|
||||||
selectedStream: string | undefined
|
|
||||||
}
|
}
|
||||||
type AppProps = {}
|
|
||||||
|
|
||||||
export class App extends React.Component<AppProps, AppState> {
|
const titleKey = "ministream.title"
|
||||||
apiClient: MinistreamApiClient
|
|
||||||
constructor(props: AppProps) {
|
function setTitleFromLocalstorage() {
|
||||||
super(props)
|
var title = localStorage.getItem(titleKey)
|
||||||
this.state = {
|
if (title) {
|
||||||
streamList: [],
|
setTitle(title)
|
||||||
selectedStream: undefined
|
}
|
||||||
}
|
}
|
||||||
this.apiClient = new MinistreamApiClient()
|
|
||||||
this.updateSelect = this.updateSelect.bind(this)
|
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> {
|
const updateStreamList = () => {
|
||||||
await this.updateStreamList()
|
api.listStreams().then((list) => {
|
||||||
setInterval(() => {
|
setStreamList(list)
|
||||||
this.updateStreamList()
|
if (list.length != streamList.length) {
|
||||||
}, 10000)
|
setStreamList(list)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
})
|
|
||||||
|
|
||||||
if (sortedStreams.length != this.state.streamList.length) {
|
if (!list.every((_, idx) => {
|
||||||
this.setStreamList(streams)
|
return list[idx] === streamList[idx]
|
||||||
}
|
})) {
|
||||||
|
setStreamList(list)
|
||||||
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 }
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelect(selected: string): void {
|
const updateTitle = () => {
|
||||||
if (selected !== this.state.selectedStream) {
|
api.siteInfo().then((info) => {
|
||||||
this.setState((state) => {
|
if (info.siteName != document.title) {
|
||||||
return { streamList: state.streamList, selectedStream: selected }
|
setTitle(info.siteName)
|
||||||
})
|
localStorage.setItem(titleKey, info.siteName)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
setInterval(() => {
|
||||||
return (
|
updateStreamList()
|
||||||
<>
|
}, 10000)
|
||||||
<Menu
|
|
||||||
items={this.state.streamList}
|
useEffect(() => {
|
||||||
selectedItem={this.state.selectedStream}
|
updateStreamList()
|
||||||
selectCallback={this.updateSelect}
|
}, [])
|
||||||
/>
|
|
||||||
<MediaContainer selectedStream={this.state.selectedStream} api={this.apiClient} />
|
useEffect(() => {
|
||||||
</>
|
updateTitle()
|
||||||
)
|
}, [])
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Menu
|
||||||
|
items={streamList}
|
||||||
|
selectedItem={selectedStream}
|
||||||
|
selectCallback={updateSelect}
|
||||||
|
/>
|
||||||
|
<MediaContainer selectedStream={selectedStream} api={api} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootElement = document.getElementById("app");
|
const rootElement = document.getElementById("app");
|
||||||
|
setTitleFromLocalstorage()
|
||||||
if (rootElement) {
|
if (rootElement) {
|
||||||
const root = createRoot(rootElement);
|
const root = createRoot(rootElement)
|
||||||
root.render(<App />)
|
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 { MinistreamApiClient } from "./api"
|
||||||
import { resolve } from "path"
|
import { resolve } from "path"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
type MediaContainerProps = {
|
type MediaContainerProps = {
|
||||||
selectedStream: string | undefined
|
selectedStream: string | null
|
||||||
api: MinistreamApiClient
|
api: MinistreamApiClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,45 +1,42 @@
|
|||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
type StreamSelectCallback = (selected: string) => void
|
type StreamSelectCallback = (selected: string | null) => void
|
||||||
|
|
||||||
type MenuProps = {
|
interface MenuProps {
|
||||||
items: string[]
|
items: string[]
|
||||||
selectedItem: string | undefined
|
selectedItem: string | null
|
||||||
selectCallback: StreamSelectCallback
|
selectCallback: StreamSelectCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Menu extends React.Component<MenuProps> {
|
export function Menu({ items, selectedItem, selectCallback }: MenuProps) {
|
||||||
constructor(props: MenuProps) {
|
const title = document.title
|
||||||
super(props)
|
const menuitems = () => {
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.props.items.map((value, idx) => {
|
{items.map((value, idx) => {
|
||||||
if (this.props.selectedItem == value) {
|
if (selectedItem == value) {
|
||||||
return <li key={idx} className="menu-item menu-selected"><a href={"#" + value} className="menu-link">{value}</a></li>
|
return <li key={idx} className="menu-item menu-selected"><a href={"#" + value} className="menu-link">{value}</a></li>
|
||||||
} else {
|
} else {
|
||||||
return <li key={idx} onClick={() => {
|
return <li key={idx} onClick={() => {
|
||||||
this.props.selectCallback(value)
|
selectCallback(value)
|
||||||
}} className="menu-item"><a href={"#" + value} className="menu-link">{value}</a></li>
|
}} 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>
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user