Migrate tensamin/ui to methanium/ui
All checks were successful
/ package (push) Successful in 57s

This commit is contained in:
Alois 2026-07-25 13:09:54 +02:00
commit 7bc6d002a9
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
90 changed files with 13401 additions and 1 deletions

20
src/screens/error.tsx Normal file
View file

@ -0,0 +1,20 @@
import Link from "../link";
import CreateScreen, { StorageDestoryer } from "./util";
/**
* Executes Screen.
* @param props Parameter props.
* @returns unknown.
*/
export default function Screen(props: { error: string; description: string }) {
return (
<CreateScreen>
<p className="text-base font-semibold">{props.error}</p>
<p className="pt-4 text-sm w-1/2 text-center text-muted-foreground pb-8">
{props.description}
</p>
<Link label="status.methanium.net" link="https://status.methanium.net" />
<StorageDestoryer />
</CreateScreen>
);
}

102
src/screens/loading.tsx Normal file
View file

@ -0,0 +1,102 @@
import { useRef, useState, useEffect } from "react";
import CreateScreen, { StorageDestoryer } from "./util";
const DELAY = 250;
type ScreenProps = {
title?: string;
description?: string;
fullscreen?: boolean;
} & (
| { noProgress: true; progress?: never }
| { noProgress?: false; progress: number }
);
/**
* Executes Screen.
* @param props Parameter props.
* @returns unknown.
*/
export default function Screen(props: ScreenProps) {
const [displayProgress, setDisplayProgress] = useState(0);
const displayProgressRef = useRef(0);
useEffect(() => {
displayProgressRef.current = displayProgress;
}, [displayProgress]);
useEffect(() => {
if (props.noProgress) {
return;
}
const target = props.progress;
const start = displayProgressRef.current;
const delta = target - start;
if (delta === 0) {
return;
}
const duration = DELAY;
const startTime = performance.now();
let frameId = 0;
/**
* Executes animate.
* @param now Parameter now.
* @returns unknown.
*/
function animate(now: number) {
const elapsed = now - startTime;
const t = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - t, 3);
setDisplayProgress(start + delta * eased);
if (t < 1) {
frameId = requestAnimationFrame(animate);
}
}
frameId = requestAnimationFrame(animate);
return () => {
cancelAnimationFrame(frameId);
};
}, [props.noProgress, props.progress]);
return (
<CreateScreen>
<h2 className="text-base font-semibold text-foreground">
{props.title ?? "Loading"}
</h2>
{props.description ? (
<p className="text-sm text-muted-foreground mt-1 mb-5">
{props.description}
</p>
) : (
<div className="mb-5" />
)}
<div
className="w-64 h-1.5 bg-secondary rounded-full overflow-hidden relative"
role={props.noProgress ? "status" : "progressbar"}
aria-label="Loading"
aria-valuemin={props.noProgress ? undefined : 0}
aria-valuemax={props.noProgress ? undefined : 100}
aria-valuenow={props.noProgress ? undefined : displayProgress}
>
{props.noProgress ? (
<div className="loading-line-wobble h-full bg-primary rounded-full" />
) : (
<div
className="h-full bg-primary absolute left-0 top-0"
style={{
width: `${displayProgress}%`,
}}
/>
)}
</div>
<StorageDestoryer />
</CreateScreen>
);
}

98
src/screens/util.tsx Normal file
View file

@ -0,0 +1,98 @@
import { cn } from "../lib/utils";
import Controls from "../windowControls";
import { isTauri } from "@tauri-apps/api/core";
export default function CreateScreen({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<div
className={cn(
"bg-background w-screen h-screen flex flex-col justify-center items-center",
className,
)}
data-tauri-drag-region
>
<>
<Controls className="fixed top-0 right-0 m-1.75 mr-[7.6px]! ring-foreground/15" />
{children}
</>
</div>
);
}
import { Button } from "../cmp/button";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "../cmp/alert-dialog";
import { useIsMobile } from "../lib/utils";
export function ClearStorageButton({ className }: { className?: string }) {
const isMobile = useIsMobile();
return (
<AlertDialog>
<AlertDialogTrigger
render={
<Button
variant="destructive"
className={cn(isMobile ? "w-full" : "", className)}
>
Clear storage
</Button>
}
/>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Are you sure you want to clear storage?
</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. It will clear all your settings and
credentials.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction
onClick={async () => {
localStorage.clear();
for (const db of await indexedDB.databases()) {
indexedDB.deleteDatabase(db.name!);
}
location.reload();
}}
>
Continue
</AlertDialogAction>
<AlertDialogCancel>Cancel</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
export function StorageDestoryer() {
const isMobile = useIsMobile();
return (
<div
className={cn(
"fixed p-5 bottom-0",
isMobile ? "w-full" : "right-0",
isTauri() ? "py-10" : "",
)}
>
<ClearStorageButton />
</div>
);
}