Skip to content

Commit

Permalink
refactor(web): migrate to tailwindcss and daisyui
Browse files Browse the repository at this point in the history
  • Loading branch information
rocka committed Nov 1, 2024
1 parent a13682e commit be71e6f
Show file tree
Hide file tree
Showing 19 changed files with 2,328 additions and 2,503 deletions.
4,521 changes: 2,146 additions & 2,375 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 25 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"preview": "npm run preview:liveion",
"preview:liveion": "vite preview -c web/liveion/vite.config.ts",
"preview:liveman": "vite preview -c web/liveman/vite.config.ts",
"build": "npm run build:liveion && npm run build:liveman",
"lint": "eslint && tsc --noEmit",
"build": "npm run lint && npm run build:liveion && npm run build:liveman",
"build:liveion": "vite build -c web/liveion/vite.config.ts",
"build:liveman": "vite build -c web/liveman/vite.config.ts",
"e2e:cluster": "vitest",
Expand All @@ -21,20 +22,31 @@
"dependencies": {
"@binbat/whip-whep": "^1.1.1-sdp-trickle-throw",
"@nuintun/qrcode": "^4.1.5",
"preact": "^10.23.2",
"@heroicons/react": "^2.1.5",
"preact": "^10.24.3",
"react-daisyui": "^5.0.5",
"typescript-event-target": "^1.1.1",
"wretch": "^2.9.1"
"wretch": "^2.10.0"
},
"devDependencies": {
"@eslint/js": "^9.10.0",
"@preact/preset-vite": "^2.9.0",
"@stylistic/eslint-plugin-js": "^2.7.2",
"eslint": "^9.10.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"unocss": "^0.62.3",
"vite": "^5.4.3",
"vitepress": "^1.3.4",
"vitest": "^2.0.5"
"@eslint/js": "^9.13.0",
"@preact/preset-vite": "^2.9.1",
"@stylistic/eslint-plugin-js": "^2.9.0",
"@types/node": "^22.7.7",
"daisyui": "^4.12.13",
"eslint": "^9.13.0",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9",
"vitepress": "^1.4.1",
"vitest": "^2.1.3"
},
"postcss": {
"plugins": {
"tailwindcss": {
"config": "./web/tailwind.config.ts"
}
}
}
}
5 changes: 5 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"paths": {
"@/*": ["./web/*"],
"react": ["./node_modules/preact/compat/"],
"react-dom": ["./node_modules/preact/compat/"]
},
Expand All @@ -26,5 +27,9 @@
"noFallthroughCasesInSwitch": true
},
"include": ["web"],
"exclude": [
"./web/**/vite.config.ts",
"./web/tailwind.config.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}
5 changes: 2 additions & 3 deletions tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
"strict": true
},
"include": [
"./web/uno.config.ts",
"./web/vite.config.ts",
"./web/*/vite.config.ts"
"./web/**/vite.config.ts",
"./web/tailwind.config.ts"
]
}
4 changes: 2 additions & 2 deletions web/liveion/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from 'preact';

import 'virtual:uno.css';
import '../shared/index.css';
import '../shared/tailwind.css';

import { Liveion } from './liveion';

render(<Liveion />, document.getElementById('app')!);
143 changes: 84 additions & 59 deletions web/liveman/components/login.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,121 @@
import { useState } from 'preact/hooks';
import { useEffect, useRef, useState } from 'preact/hooks';
import { TargetedEvent } from 'preact/compat';
import { WretchError } from 'wretch/resolver';
import { Alert, Button, Loading, Modal, Tabs } from 'react-daisyui';

import * as livemanApi from '../api';
import * as sharedApi from '../../shared/api';
import { alertError } from '../../shared/utils';
import * as sharedApi from '@/shared/api';

enum AuthorizeType {
Password = 'Password',
Token = 'Token'
}

const AuthTypes = [
AuthorizeType.Password,
AuthorizeType.Token
];

function useInput(label: string, type = 'text') {
const [value, setValue] = useState('');

const inputElement = (
<div>
<label>
<span class="inline-block min-w-24 font-bold">{label}</span>
<input type={type} value={value} onInput={e => setValue(e.currentTarget?.value)} />
</label>
</div>
<label class="input input-bordered flex items-center gap-2 my-4">
<span>{label}</span>
<input type={type} class="grow" name={label} value={value} onInput={e => setValue(e.currentTarget?.value)} />
</label>
);

return [value, inputElement] as const;
}

enum AuthorizeType {
Password = 'Password', Token = 'Token'
}

export interface LoginProps {
show: boolean;
onSuccess?: (token: string) => void;
}

export function Login({ onSuccess }: LoginProps) {
export function Login({ show, onSuccess }: LoginProps) {
const refDialog = useRef<HTMLDialogElement>(null);
const [authType, setAuthType] = useState(AuthorizeType.Password);
const [username, usernameInput] = useInput('Username');
const [password, passwordInput] = useInput('Password', 'password');

const [token, tokenInput] = useInput('Token');
const [loading, setLoading] = useState(false);
const [errMsg, setErrMsg] = useState<string | null>(null);

const [authType, setAuthType] = useState(AuthorizeType.Password);
const onAuthTypeInput = (e: TargetedEvent<HTMLInputElement>) => {
setAuthType(e.currentTarget.value as AuthorizeType);
};
useEffect(() => {
if (show) {
refDialog.current?.showModal();
} else {
refDialog.current?.close();
}
}, [show]);

const onLoginSubmit = async (e: TargetedEvent) => {
e.preventDefault();
try {
const res = await livemanApi.login(username, password);
const tk = `${res.token_type} ${res.access_token}`;
livemanApi.setAuthToken(tk);
sharedApi.setAuthToken(tk);
onSuccess?.(res.access_token);
} catch (e) {
alertError(e);
const handleDialogClose = () => {
if (show) {
refDialog.current?.showModal();
}
};

const onTokenSubmit = async (e: TargetedEvent) => {
const handleLogin = async (e: TargetedEvent) => {
setErrMsg(null);
setLoading(true);
e.preventDefault();
const tk = token.indexOf(' ') < 0 ? `Bearer ${token}` : token;
livemanApi.setAuthToken(tk);
sharedApi.setAuthToken(tk);
try {
await livemanApi.getNodes();
onSuccess?.(token);
let tokenType, tokenValue;
switch (authType) {
case AuthorizeType.Password: {
const res = await livemanApi.login(username, password);
tokenType = res.token_type;
tokenValue = res.access_token;
break;
}
case AuthorizeType.Token: {
tokenType = 'Bearer';
tokenValue = token;
livemanApi.setAuthToken(`${tokenType} ${tokenValue}`);
await livemanApi.getNodes();
break;
}
}
const tk = `${tokenType} ${tokenValue}`;
livemanApi.setAuthToken(tk);
sharedApi.setAuthToken(tk);
onSuccess?.(tokenValue);
} catch (e) {
livemanApi.setAuthToken('');
sharedApi.setAuthToken('');
alertError(e);
if (e instanceof WretchError) {
setErrMsg(e.json?.error ?? e.text ?? `Status: ${e.status}`);
} else if (e instanceof Error) {
setErrMsg(e.message);
} else {
setErrMsg(String(e));
}
}
setLoading(false);
};

return (
<fieldset>
<legend>Authorization Required</legend>
<div>
<span>Authorize Type:</span>
{[AuthorizeType.Password, AuthorizeType.Token].map(t => (
<label>
<input type="radio" name="authorizeType" value={t} checked={authType === t} onInput={onAuthTypeInput} />
<span>{t}</span>
</label>
<Modal ref={refDialog} onClose={handleDialogClose}>
<h3 class="text-lg font-bold">Authorization Required</h3>
{/* @ts-expect-error -- size */}
<Tabs variant="bordered" size="lg" className="my-4">
{AuthTypes.map(t => (
<Tabs.Tab className="text-base" active={t === authType} onClick={() => setAuthType(t)}>{t}</Tabs.Tab>
))}
</div>
{authType === AuthorizeType.Password ? (
<form onSubmit={onLoginSubmit}>
{usernameInput}
{passwordInput}
<input type="submit" value="Login" />
</form>
) : authType === AuthorizeType.Token ? (
<form onSubmit={onTokenSubmit}>
{tokenInput}
<input type="submit" value="Login" />
</form>
) : null}
</fieldset>
</Tabs>
{typeof errMsg === 'string' ? <Alert status="error" >{errMsg}</Alert> : null}
<form onSubmit={handleLogin}>
{authType === AuthorizeType.Password ? [usernameInput, passwordInput]
: authType === AuthorizeType.Token ? tokenInput
: null}
<Button type="submit" color="primary" className="w-full text-base" disabled={loading}>
{/* @ts-expect-error -- size */}
{loading ? <Loading size="sm" /> : null}
<span>Login</span>
</Button>
</form>
</Modal>
);
}
2 changes: 1 addition & 1 deletion web/liveman/components/nodes-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type NodeStrategyTableProps = Pick<Node, 'strategy'>;
function NodeStrategyTable({ strategy }: NodeStrategyTableProps) {
return (
<div class="h-[1lh] overflow-hidden relative group hover:overflow-visible">
<table class="mx-auto px-1 bg-white @dark:bg-neutral-800 rounded group-hover:absolute group-hover:inset-x-0 group-hover:z-1 group-hover:outline group-hover:outline-indigo-500">
<table class="mx-auto px-1 rounded group-hover:absolute group-hover:inset-x-0 group-hover:z-1 group-hover:outline group-hover:outline-indigo-500" data-theme="">
<tbody>
{Object.entries(strategy).map(([k, v]) => (
<tr>
Expand Down
16 changes: 6 additions & 10 deletions web/liveman/liveman.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,13 @@ export function Liveman() {
return (
<TokenContext.Provider value={{ token }}>
<Live777Logo />
{needsAuthorizaiton ? (
<>
<Login onSuccess={onLoginSuccess} />
</>
) : (
<>
<NodesTable />
<StreamsTable renderExtraActions={renderCreateToken} />
</>
)}
<NodesTable />
<StreamsTable renderExtraActions={renderCreateToken} />
<StreamTokenDialog ref={refStreamTokenDialog} />
<Login
show={needsAuthorizaiton}
onSuccess={onLoginSuccess}
/>
</TokenContext.Provider>
);
}
4 changes: 2 additions & 2 deletions web/liveman/main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from 'preact';

import 'virtual:uno.css';
import '../shared/index.css';
import '@/shared/tailwind.css';

import { Liveman } from './liveman';

render(<Liveman />, document.getElementById('app')!);
1 change: 1 addition & 0 deletions web/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface Session {
targetUrl?: string;
sessionUrl: string;
};
reforward?: boolean;
}

export interface Cascade {
Expand Down
2 changes: 1 addition & 1 deletion web/shared/components/dialog-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export const PreviewDialog = forwardRef<IPreviewDialog, Props>((props, ref) => {
decoder.start();
decoder.addEventListener('latency', (e: CustomEvent<number>) => {
setLatency(`${e.detail} ms`);
})
});
};

return (
Expand Down
2 changes: 1 addition & 1 deletion web/shared/components/dialog-web-stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const WebStreamDialog = forwardRef<IWebStreamDialog, Props>((props, ref)
video: true
});
handleStreamStart(stream);
}
};

const handleEncodeLatencyStart = () => {
if (!refQrCodeStream.current) {
Expand Down
3 changes: 3 additions & 0 deletions web/shared/tailwind.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
Loading

0 comments on commit be71e6f

Please sign in to comment.