feat: add off, booting and sddm view

This commit is contained in:
Pihkaal
2024-09-11 17:16:52 +02:00
parent e332f87384
commit 14ef2317fa
8 changed files with 313 additions and 23 deletions

View File

@@ -1,4 +1,3 @@
import { useState } from "react";
import { Kitty } from "./components/Kitty";
import { AppProvider } from "./providers/AppProvider";
import { Music } from "./components/Music";
@@ -6,21 +5,33 @@ import { Nvim } from "./components/Nvim";
import { Waybar } from "./components/Waybar";
import { useApp } from "./hooks/useApp";
import { clamp } from "./utils/math";
import { Sddm } from "./components/Sddm";
import { Boot } from "./components/Boot";
import { Off } from "./components/Off";
const AppRoot = () => {
const [loggedIn, setLoggedIn] = useState(false);
const { brightness } = useApp();
const { brightness, state } = useApp();
const opacity = clamp(0.5 - (0.5 * brightness) / 100, 0, 0.5);
if (state === "off") {
return <Off />;
}
if (state === "boot") {
return <Boot />;
}
return (
<>
<div
className="pointer-events-none fixed inset-0 z-10 bg-black"
style={{ opacity }}
/>
<main className="h-screen w-screen overflow-hidden bg-[url(/wallpaper.jpg)] bg-cover">
{loggedIn ? (
<main className="h-screen w-screen overflow-hidden bg-[url(/wallpaper.jpg)] bg-cover bg-center">
{state === "login" ? (
<Sddm />
) : (
<div className="h-full flex-col">
<div className="relative z-10 px-2 py-2">
<Waybar />
@@ -34,15 +45,6 @@ const AppRoot = () => {
<Music />
</div>
</div>
) : (
<div className="flex h-full items-center justify-center">
<button
className="rounded-md border border-black px-2 py-1 hover:border-2 hover:font-bold"
onClick={() => setLoggedIn(true)}
>
Log in
</button>
</div>
)}
</main>
</>

37
src/components/Boot.tsx Normal file
View File

@@ -0,0 +1,37 @@
import { useEffect, useState } from "react";
import { useApp } from "~/hooks/useApp";
const LINES = [
"Loading Linux...",
"Loading initial ramdisk...",
"Feeding the cat...",
"Cleaning my room...",
"Preparing tuna tomato couscous...",
"Ready",
];
export const Boot = () => {
const { setState } = useApp();
const [line, setLine] = useState(0);
useEffect(() => {
if (line >= LINES.length) {
setState("login");
return;
}
const timeout = setTimeout(
() => setLine(line + 1),
Math.random() * 750 + 200,
);
return () => clearTimeout(timeout);
}, [setState, line]);
return (
<main className="h-screen w-screen bg-black text-white">
{LINES.filter((_, i) => i <= line).map((line, i) => (
<p key={i}>{line}</p>
))}
</main>
);
};

33
src/components/Off.tsx Normal file
View File

@@ -0,0 +1,33 @@
import { useState } from "react";
import { useApp } from "~/hooks/useApp";
export const Off = () => {
const { setState } = useApp();
const [clicked, setClicked] = useState(false);
const handleClick = () => {
setClicked(true);
setTimeout(() => {
setState("boot");
}, 1000);
};
return (
<div className="flex h-screen w-screen items-center justify-center bg-black">
<button
className={`drop-shadow-white cursor-pointer transition-all ${
clicked ? "animate-disappear" : "animate-breathing"
}`}
onClick={handleClick}
>
<svg viewBox="0 0 34 34" width="128">
<path
fill="#ffffff"
d="M 14 1 L 14 13 L 15 13 L 15 1 L 14 1 z M 19 1 L 19 13 L 20 13 L 20 1 L 19 1 z M 9 3.1855469 C 4.1702837 5.9748853 1.0026451 11.162345 1 17 C 1 25.836556 8.163444 33 17 33 C 25.836556 33 33 25.836556 33 17 C 32.99593 11.163669 29.828666 5.9780498 25 3.1894531 L 25 4.3496094 C 29.280842 7.0494632 31.988612 11.788234 32 17 C 32 25.284271 25.284271 32 17 32 C 8.7157288 32 2 25.284271 2 17 C 2.0120649 11.788824 4.7195457 7.0510246 9 4.3515625 L 9 3.1855469 z "
/>
</svg>
</button>
</div>
);
};

188
src/components/Sddm.tsx Normal file
View File

@@ -0,0 +1,188 @@
import { type ReactNode, useEffect, useRef, useState } from "react";
import { useApp } from "~/hooks/useApp";
const SddmActionButton = (props: {
icon: ReactNode;
text: string;
onClick?: () => void;
}) => (
<button
className="flex select-none flex-col items-center text-white transition-colors hover:text-zinc-800"
onClick={props.onClick}
>
{props.icon}
<span>{props.text}</span>
</button>
);
const PASSWORD_LENGTH = 12;
export const Sddm = () => {
const { setState } = useApp();
const passwordInputRef = useRef<HTMLInputElement>(null);
const [password, setPassword] = useState(0);
const [showPassword, setShowPassword] = useState(false);
const [now, setNow] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setNow(new Date());
}, 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (password >= PASSWORD_LENGTH) {
passwordInputRef.current?.blur();
return;
}
const timeout = setTimeout(
() => {
passwordInputRef.current?.focus();
const canType =
password < 4 ||
password === PASSWORD_LENGTH - 1 ||
Math.random() > 0.18;
setPassword(Math.max(0, password + (canType ? 1 : -1)));
},
password === 0 ? 3000 : Math.random() * 250 + 250,
);
return () => clearTimeout(timeout);
}, [password]);
return (
<>
<div className="pointer-events-none absolute inset-0 z-10 animate-fadeOut bg-black" />
<div className="flex h-full cursor-default items-center justify-around text-white">
<div className="flex h-full w-full flex-col justify-center backdrop-blur-2xl sm:w-2/3 md:w-1/2 lg:w-2/5">
<div className="flex flex h-4/5 flex-col items-center justify-between">
<div className="text-center">
<p className="text-6xl leading-10">Welcome!</p>
<p className="text-5xl">
{now.toLocaleTimeString("en-us", {
hour: "2-digit",
minute: "2-digit",
})}
</p>
<p>{now.toLocaleDateString("en-us", { dateStyle: "long" })}</p>
</div>
<div className="flex min-w-[210px] flex-col gap-5 lg:w-1/2">
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 flex items-center ps-3.5">
</div>
<input
type="text"
className="block w-full rounded-full border border-white bg-transparent p-2 text-center text-white placeholder-zinc-200"
value={"Pihkaal"}
readOnly
spellCheck={false}
maxLength={15}
/>
</div>
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 flex items-center ps-3.5">
</div>
<input
type="text"
ref={passwordInputRef}
readOnly
className="block w-full rounded-full border border-white bg-transparent p-2 text-center text-white placeholder-zinc-200"
value={
showPassword
? password > 0
? "no."
: ""
: "•".repeat(password)
}
placeholder="Password"
spellCheck={false}
maxLength={PASSWORD_LENGTH}
/>
</div>
<label className="flex select-none items-center gap-2 transition-colors hover:border-black hover:text-black">
<div
className={`relative flex h-3.5 w-3.5 items-center justify-center border border-current`}
>
<input
type="checkbox"
className="peer h-2 w-2 appearance-none checked:bg-current"
checked={showPassword}
onChange={() => setShowPassword((show) => !show)}
/>
</div>
<p className="text-sm">Show Password</p>
</label>
</div>
<div className="flex min-w-[210px] flex-col gap-1 text-left transition-colors lg:w-1/2">
<button
onClick={() => setState("desktop")}
className={`w-full select-none rounded-full p-2 ${
password === PASSWORD_LENGTH
? "bg-neutral-800 hover:bg-zinc-800"
: "cursor-default bg-white bg-opacity-30"
}`}
>
Login
</button>
<p className="text-sm">Session: Hyprland</p>
</div>
<div className="flex gap-8">
<SddmActionButton
key="suspend"
icon={
<svg viewBox="0 0 34 34" width="38">
<path
fill="currentColor"
d="M 17,1 C 8.163444,1 1,8.163444 1,17 1,25.836556 8.163444,33 17,33 25.836556,33 33,25.836556 33,17 33,8.163444 25.836556,1 17,1 Z m 0,1 C 25.284271,2 32,8.7157288 32,17 32,25.284271 25.284271,32 17,32 8.7157288,32 2,25.284271 2,17 2,8.7157288 8.7157288,2 17,2 Z m -4,9 v 12 h 1 V 11 Z m 7,0 v 12 h 1 V 11 Z"
/>
</svg>
}
text="Suspend"
/>
<SddmActionButton
key="reboot"
icon={
<svg width="38" viewBox="0 0 34 34">
<g transform="matrix(1.000593,0,0,1.0006688,0.99050505,-287.73702)">
<path
fill="currentColor"
d="M 19.001953,1.1308594 V 2 H 19 v 11 h 1 V 2.3359375 A 15,15 45 0 1 32,17 15,15 45 0 1 21.001953,31.455078 v 1.033203 A 16.009488,16.010701 45 0 0 33.009766,17 16.009488,16.010701 45 0 0 19.001953,1.1308594 Z M 12.998047,1.5117188 A 16.009488,16.010701 45 0 0 0.99023438,17 16.009488,16.010701 45 0 0 14.998047,32.869141 V 32 H 15 V 21 H 14 V 31.664062 A 15,15 45 0 1 2,17 15,15 45 0 1 12.998047,2.5449219 Z"
transform="matrix(0.70668771,-0.70663419,0.70668771,0.70663419,-8.0273788,304.53335)"
/>
</g>
</svg>
}
text="Reboot"
/>
<SddmActionButton
key="shutdown"
icon={
<svg viewBox="0 0 34 34" width="38">
<path
fill="currentColor"
d="M 14 1 L 14 13 L 15 13 L 15 1 L 14 1 z M 19 1 L 19 13 L 20 13 L 20 1 L 19 1 z M 9 3.1855469 C 4.1702837 5.9748853 1.0026451 11.162345 1 17 C 1 25.836556 8.163444 33 17 33 C 25.836556 33 33 25.836556 33 17 C 32.99593 11.163669 29.828666 5.9780498 25 3.1894531 L 25 4.3496094 C 29.280842 7.0494632 31.988612 11.788234 32 17 C 32 25.284271 25.284271 32 17 32 C 8.7157288 32 2 25.284271 2 17 C 2.0120649 11.788824 4.7195457 7.0510246 9 4.3515625 L 9 3.1855469 z "
/>
</svg>
}
text="Shutdown"
/>
</div>
</div>
</div>
</div>
</>
);
};

View File

@@ -1,11 +1,11 @@
import { createContext } from "react";
import { type Prettify, type State } from "~/utils/types";
export const AppContext = createContext<
| Prettify<
State<"activeKitty", string> &
State<"brightness", number> &
State<"volume", number> & { screenWidth: number }
>
| undefined
>(undefined);
export const AppContext = createContext<AppContextProps | undefined>(undefined);
export type AppContextProps = Prettify<
State<"state", "off" | "boot" | "login" | "desktop"> &
State<"activeKitty", string> &
State<"brightness", number> &
State<"volume", number> & { screenWidth: number }
>;

View File

@@ -51,3 +51,7 @@ body {
font-style: normal;
font-display: swap;
}
.drop-shadow-white:hover {
filter: drop-shadow(0px 0px 4px rgba(255, 255, 255, 1));
}

View File

@@ -1,10 +1,11 @@
import { type ReactNode, useState, useEffect } from "react";
import { AppContext } from "~/context/AppContext";
import { AppContext, type AppContextProps } from "~/context/AppContext";
export const AppProvider = (props: { children?: ReactNode }) => {
const [activeKitty, setActiveKitty] = useState(":r0:");
const [brightness, setBrightness] = useState(100);
const [volume, setVolume] = useState(100);
const [state, setState] = useState<AppContextProps["state"]>("off");
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
@@ -28,6 +29,8 @@ export const AppProvider = (props: { children?: ReactNode }) => {
volume,
setVolume,
screenWidth,
state,
setState,
}}
>
{props.children}