Skip to content

Commit

Permalink
Enhanced UI controls (#66)
Browse files Browse the repository at this point in the history
* More component splits

* feat: Added tooltip

* Added center icon pop

* bugfix: When interstitial is in idle state, show playing when play is requested

* Added settings and properties to dashboard for player

* Dark mode
  • Loading branch information
matvp91 authored Oct 18, 2024
1 parent 6dbcd58 commit c5fde3c
Show file tree
Hide file tree
Showing 71 changed files with 2,347 additions and 445 deletions.
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"./client": "./src/client.ts"
},
"scripts": {
"dev": "bun run ./src/index.ts --watch",
"dev": "bun --watch ./src/index.ts",
"build": "bun build ./src/index.ts --target=bun --outdir=./dist",
"lint": "eslint \"./src/**/*.ts\" && prettier --check \"./src/**/*.ts\"",
"typecheck": "tsc"
Expand Down
33 changes: 4 additions & 29 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Elysia, t } from "elysia";
import { cors } from "@elysiajs/cors";
import { swagger } from "@elysiajs/swagger";
import { swagger, onAfterHandle } from "./swagger";
import { addTranscodeJob, addPackageJob } from "@mixwave/artisan/producer";
import {
LangCodeSchema,
Expand All @@ -14,36 +14,9 @@ import { StorageFolderSchema, StorageFileSchema, JobSchema } from "./types";

export type App = typeof app;

const CUSTOM_SCALAR_CSS = `
.scalar-container.z-overlay {
padding-left: 16px;
padding-right: 16px;
}
.scalar-api-client__send-request-button, .show-api-client-button {
background: var(--scalar-button-1);
}
`;

const app = new Elysia()
.use(cors())
.use(
swagger({
documentation: {
info: {
title: "Mixwave API",
description:
"The Mixwave API is organized around REST, returns JSON-encoded responses " +
"and uses standard HTTP response codes and verbs.",
version: "1.0.0",
},
},
scalarConfig: {
hideDownloadButton: true,
customCss: CUSTOM_SCALAR_CSS,
},
}),
)
.use(swagger)
.model({
LangCode: LangCodeSchema,
VideoCodec: VideoCodecSchema,
Expand Down Expand Up @@ -287,6 +260,8 @@ const app = new Elysia()
},
);

app.onAfterHandle(onAfterHandle);

app.on("stop", () => {
process.exit(0);
});
Expand Down
71 changes: 71 additions & 0 deletions packages/api/src/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { swagger as elysiaSwagger } from "@elysiajs/swagger";

const CUSTOM_SCALAR_CSS = `
.scalar-container.z-overlay {
padding-left: 16px;
padding-right: 16px;
}
.scalar-api-client__send-request-button, .show-api-client-button {
background: var(--scalar-button-1);
}
`;

export const swagger = elysiaSwagger({
documentation: {
info: {
title: "Mixwave API",
description:
"The Mixwave API is organized around REST, returns JSON-encoded responses " +
"and uses standard HTTP response codes and verbs.",
version: "1.0.0",
},
},
scalarConfig: {
hideDownloadButton: true,
customCss: CUSTOM_SCALAR_CSS,
},
});

const scalarScript = `
<script>
const searchParams = new URLSearchParams(window.location.search);
const theme = searchParams.get("theme");
const isDark = localStorage.getItem("isDark") === "true";
if (theme === "dark" && !isDark) {
localStorage.setItem("isDark", "true");
location.reload();
} else if (theme !== "dark" && isDark) {
localStorage.setItem("isDark", "false");
location.reload();
}
</script>
`;

export async function onAfterHandle({
request,
response,
}: {
request: Request;
response: Response;
}) {
const url = new URL(request.url);

if (url.pathname.endsWith("/swagger")) {
const text = await response.text();
const lines = text.split("\n");
lines.splice(
lines.findIndex((line) => line.trim() === "<body>"),
0,
scalarScript,
);
return new Response(lines.join(""), {
headers: {
"content-type": "text/html; charset=utf8",
},
});
}

return response;
}
7 changes: 6 additions & 1 deletion packages/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tanstack/react-query": "^5.51.21",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"hls.js": "1.6.0-beta.1",
"lucide-react": "^0.424.0",
"monaco-editor": "^0.51.0",
Expand All @@ -36,12 +39,14 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"react-resizable-panels": "^2.1.4",
"react-router-dom": "^6.26.0",
"react-syntax-highlighter": "^15.5.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"timeago.js": "4.0.0-beta.3",
"uniqolor": "^1.1.1"
"uniqolor": "^1.1.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@mixwave/api": "workspace:*",
Expand Down
15 changes: 10 additions & 5 deletions packages/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import { JobPage } from "@/pages/JobPage";
import { ApiPage } from "@/pages/ApiPage";
import { RootLayout } from "@/pages/RootLayout";
import { Suspense } from "react";
import { Toaster } from "@/components/ui/toaster";
import { PlayerPage } from "./pages/PlayerPage";
import { StoragePage } from "./pages/StoragePage";
import { ThemeProvider } from "@/components/ui/theme-provider";

const queryClient = new QueryClient();

Expand Down Expand Up @@ -49,10 +51,13 @@ const router = createBrowserRouter([

export function App() {
return (
<QueryClientProvider client={queryClient}>
<Suspense>
<RouterProvider router={router} />
</Suspense>
</QueryClientProvider>
<ThemeProvider defaultTheme="light" storageKey="vite-ui-theme">
<QueryClientProvider client={queryClient}>
<Suspense>
<RouterProvider router={router} />
</Suspense>
<Toaster />
</QueryClientProvider>
</ThemeProvider>
);
}
62 changes: 45 additions & 17 deletions packages/dashboard/src/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import MonacoEditor from "@monaco-editor/react";
import type { BeforeMount, OnChange, OnMount } from "@monaco-editor/react";
import { useEffect, useState } from "react";
import { useTheme } from "@/components/ui/theme-provider";
import type { BeforeMount, OnChange, OnMount } from "@monaco-editor/react";

type EditorProps = {
schema: object;
Expand All @@ -15,6 +16,9 @@ export function Editor({
onSave,
localStorageKey,
}: EditorProps) {
const { theme } = useTheme();
const style = useMonacoStyle();

const [defaultValue] = useState(() => {
const localStorageValue = localStorageKey
? localStorage.getItem(localStorageKey)
Expand Down Expand Up @@ -53,23 +57,47 @@ export function Editor({
};

return (
<div className="h-full flex flex-col bg-[#1e1e1e]">
<div className="p-4 flex gap-2">
<div className="text-white flex items-center">{title}</div>
<div className="h-full flex flex-col">
<div
className="border-b flex px-4"
style={{ height: "calc(3.5rem + 4px)" }}
>
<div className="flex items-center">{title}</div>
</div>
<div className="h-full relative">
{style}
<MonacoEditor
className="absolute inset-0"
defaultLanguage="json"
defaultValue={defaultValue}
beforeMount={beforeMount}
onMount={onMount}
onChange={onChange}
defaultPath="custom"
theme={theme === "dark" ? "vs-dark" : "light"}
options={{
minimap: {
enabled: false,
},
tabSize: 2,
}}
/>
</div>
<MonacoEditor
className="h-full"
defaultLanguage="json"
defaultValue={defaultValue}
beforeMount={beforeMount}
onMount={onMount}
onChange={onChange}
defaultPath="custom"
theme="vs-dark"
options={{
tabSize: 2,
}}
/>
</div>
);
}

function useMonacoStyle() {
const { theme } = useTheme();

if (theme === "dark") {
return (
<style>{`
.monaco-editor, .monaco-editor-background { background-color: inherit; }
.monaco-editor .margin { background-color: inherit; }
`}</style>
);
}

return null;
}
23 changes: 18 additions & 5 deletions packages/dashboard/src/components/JobState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,31 @@ import type { Job } from "@/api";

export function JobState({ state }: { state: Job["state"] }) {
if (state === "completed") {
return createCircle("bg-emerald-200 text-emerald-800", Check);
return createCircle(
"bg-emerald-200 text-emerald-800 dark:bg-emerald-400",
Check,
);
}
if (state === "failed") {
return createCircle("bg-red-200 text-red-800", X);
return createCircle("bg-red-200 text-red-800 dark:bg-red-400", X);
}
if (state === "running") {
return createCircle("bg-blue-200 text-blue-800", Loader, "animate-spin");
return createCircle(
"bg-blue-200 text-blue-800 dark:bg-blue-400",
Loader,
"animate-spin",
);
}
if (state === "skipped") {
return createCircle("bg-gray-200 text-gray-800", CircleOff);
return createCircle(
"bg-gray-200 text-gray-800 dark:bg-gray-400",
CircleOff,
);
}
return createCircle("bg-violet-200 text-violet-800", CircleDotDashed);
return createCircle(
"bg-violet-200 text-violet-800 dark:bg-gray-400",
CircleDotDashed,
);
}

function createCircle(
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/src/components/JobsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function JobsList({ jobs }: JobsListProps) {
<li key={job.id} className="mb-2">
<Link
to={`/jobs/${job.id}`}
className="px-4 h-20 flex items-center border border-border rounded-md bg-white hover:shadow-sm transition-shadow hover:border-gray-300"
className="px-4 h-20 flex items-center border border-border rounded-md hover:shadow-sm transition-shadow hover:bg-muted/50"
>
<div className="grow grid grid-cols-3 gap-2 items-center">
<div className="flex items-center gap-4">
Expand Down
10 changes: 5 additions & 5 deletions packages/dashboard/src/components/JsonHighlight.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useMemo } from "react";
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import json from "react-syntax-highlighter/dist/esm/languages/hljs/json";
import style from "react-syntax-highlighter/dist/esm/styles/hljs/stackoverflow-light";

style["hljs"].padding = "1rem";
delete style["hljs"].background;
import { useTheme } from "@/components/ui/theme-provider";
import { styleLight, styleDark } from "@/lib/syntax-styles";

SyntaxHighlighter.registerLanguage("json", json);

Expand All @@ -13,6 +11,8 @@ type SyntaxHighlightProps = {
};

export function JsonHighlight({ json }: SyntaxHighlightProps) {
const { theme } = useTheme();

const data = useMemo(() => {
const parsed = JSON.parse(json);
return JSON.stringify(parsed, null, 2);
Expand All @@ -21,7 +21,7 @@ export function JsonHighlight({ json }: SyntaxHighlightProps) {
return (
<SyntaxHighlighter
className="rounded-md text-xs border border-border"
style={style}
style={theme === "dark" ? styleDark : styleLight}
language="json"
>
{data}
Expand Down
11 changes: 7 additions & 4 deletions packages/dashboard/src/components/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import {
ControllerProvider,
} from "@mixwave/player/react";
import { useEffect, useState } from "react";
import type { Lang, Metadata } from "@mixwave/player/react";

type PlayerProps = {
url?: string;
url?: string | null;
metadata: Metadata;
lang: Lang;
};

export function Player({ url }: PlayerProps) {
export function Player({ url, lang, metadata }: PlayerProps) {
const [hls] = useState(() => new Hls());
const controller = useController(hls);

Expand All @@ -29,14 +32,14 @@ export function Player({ url }: PlayerProps) {
return (
<ControllerProvider controller={controller}>
<div
className="relative aspect-video bg-black overflow-hidden"
className="relative aspect-video bg-black overflow-hidden rounded-md"
data-mix-container
>
<video
ref={controller.mediaRef}
className="absolute inset-O w-full h-full"
/>
<Controls />
<Controls lang={lang} metadata={metadata} />
</div>
</ControllerProvider>
);
Expand Down
Loading

0 comments on commit c5fde3c

Please sign in to comment.