diff --git a/package.json b/package.json
index bd58c08..ddbdd97 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"axios": "^1.6.7",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-dom-curse": "github:pihkaal/react-dom-curse",
"react-router-dom": "^6.21.3",
"tailwind-merge": "^2.2.1",
"vite-tsconfig-paths": "^4.3.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 37d1bad..d5f3a5a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,9 @@ dependencies:
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
+ react-dom-curse:
+ specifier: github:pihkaal/react-dom-curse
+ version: git@github.com+pihkaal/react-dom-curse/99f542457ffbe8dbba08261c8b423a7546fea56e(@types/react@18.2.47)
react-router-dom:
specifier: ^6.21.3
version: 6.21.3(react-dom@18.2.0)(react@18.2.0)
@@ -672,7 +675,6 @@ packages:
/@types/prop-types@15.7.11:
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
- dev: true
/@types/react-dom@18.2.18:
resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==}
@@ -686,11 +688,9 @@ packages:
'@types/prop-types': 15.7.11
'@types/scheduler': 0.16.8
csstype: 3.1.3
- dev: true
/@types/scheduler@0.16.8:
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
- dev: true
/@types/semver@7.5.6:
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
@@ -1177,7 +1177,6 @@ packages:
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
- dev: true
/damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -3260,6 +3259,14 @@ packages:
punycode: 2.3.1
dev: true
+ /use-sync-external-store@1.2.0(react@18.2.0):
+ resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.2.0
+ dev: false
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
@@ -3412,3 +3419,37 @@ packages:
/zod@3.22.4:
resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}
dev: false
+
+ /zustand@4.5.0(@types/react@18.2.47)(react@18.2.0):
+ resolution: {integrity: sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ dependencies:
+ '@types/react': 18.2.47
+ react: 18.2.0
+ use-sync-external-store: 1.2.0(react@18.2.0)
+ dev: false
+
+ git@github.com+pihkaal/react-dom-curse/99f542457ffbe8dbba08261c8b423a7546fea56e(@types/react@18.2.47):
+ resolution: {commit: 99f542457ffbe8dbba08261c8b423a7546fea56e, repo: git@github.com:pihkaal/react-dom-curse.git, type: git}
+ id: git@github.com+pihkaal/react-dom-curse/99f542457ffbe8dbba08261c8b423a7546fea56e
+ name: react-dom-curse
+ version: 0.0.0
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ zustand: 4.5.0(@types/react@18.2.47)(react@18.2.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+ dev: false
diff --git a/src/App.tsx b/src/App.tsx
index 850ca6b..fecae5c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,7 @@ import { BrowserRouter } from "react-router-dom";
import { MusicPlayer } from "./components/MusicPlayer";
import { MusicVisualizer } from "./components/MusicVisualizer";
import { Nvim } from "./components/Nvim/Nvim";
-import { Terminal } from "./components/Terminal";
+import { Kitty } from "./components/Kitty";
import { AppContextProvider } from "./context/AppContext";
import { Waybar } from "./components/Waybar/Waybar";
@@ -17,23 +17,19 @@ function App() {
>
-
-
-
+
-
-
+
+
-
+
-
-
-
+
diff --git a/src/components/Kitty.tsx b/src/components/Kitty.tsx
new file mode 100644
index 0000000..85d7f81
--- /dev/null
+++ b/src/components/Kitty.tsx
@@ -0,0 +1,15 @@
+import { type ReactNode } from "react";
+import clsx from "clsx";
+import { Terminal } from "react-dom-curse";
+
+export const Kitty = (props: { children?: ReactNode; className?: string }) => (
+
+ {props.children}
+
+);
diff --git a/src/components/MusicPlayer.tsx b/src/components/MusicPlayer.tsx
index b14f132..1f085f4 100644
--- a/src/components/MusicPlayer.tsx
+++ b/src/components/MusicPlayer.tsx
@@ -1,20 +1,6 @@
-import { useTerminal } from "~/context/TerminalContext";
-import { TerminalRenderer } from "~/utils/terminal/renderer";
-import { TerminalBoxElement } from "~/utils/terminal/elements/box";
import { useEffect, useState } from "react";
-
-const theme = {
- black: "#45475a",
- red: "#f38ba8",
- green: "#a6e3a1",
- yellow: "#f9e2af",
- blue: "#89bafa",
- magenta: "#f5c2e7",
- cyan: "#94e2d5",
- white: "#bac2de",
- grey: "#585B70",
- lightGrey: "#a6adc8",
-};
+import { Bar, Frame, Group, Terminal, Text, useTerminal } from "react-dom-curse";
+import { theme } from "~/utils/theme";
const formatDurationMSS = (duration: number) => {
const minutes = Math.floor(duration / 60);
@@ -29,8 +15,7 @@ export const MusicPlayer = (props: {
album: string;
duration: number;
}) => {
- const { cols } = useTerminal();
- const canvas = new TerminalRenderer(cols, 5);
+ const { terminal } = useTerminal();
const [played, setPlayed] = useState(0);
useEffect(() => {
@@ -41,45 +26,58 @@ export const MusicPlayer = (props: {
return () => clearInterval(interval);
}, [setPlayed, props.duration]);
- canvas.writeElement(
- new TerminalBoxElement(canvas.width, canvas.height),
- 0,
- 0,
- );
-
- canvas.write(1, 0, "Playback".substring(0, Math.min(8, canvas.width - 2)), {
- foreground: theme.magenta,
- });
-
- const inner = new TerminalRenderer(canvas.width - 2, canvas.height - 2);
- // Title and Artist
- inner.write(2, 0, `${props.title} · ${props.artist}`, {
- foreground: theme.cyan,
- fontWeight: 700,
- });
- inner.apply(0, 0, {
- char: "\udb81\udc0a",
- foreground: theme.cyan,
- fontWeight: 800,
- });
-
- // Album
- inner.write(0, 1, props.album, { foreground: theme.yellow });
-
- // Bar
- inner.write(0, 2, " ".repeat(inner.width), {
- foreground: theme.green,
- background: "#55576d",
- });
- inner.write(0, 2, " ".repeat((inner.width * played) / props.duration), {
- foreground: "#55576d",
- background: theme.green,
- });
const time = `${formatDurationMSS(played)}/${formatDurationMSS(
props.duration,
)}`;
- inner.write(inner.width / 2 - time.length / 2, 2, time, { fontWeight: 800 });
- canvas.writeElement(inner, 1, 1);
- return {canvas.render()}
;
+ return (
+
+
+
+ {props.title} · {props.artist}
+
+
+ {props.album}
+
+
+
+
+
+
+ {time}
+
+
+
+
+
+ Playback
+
+
+ );
};
diff --git a/src/components/Terminal.tsx b/src/components/Terminal.tsx
deleted file mode 100644
index 17d18d8..0000000
--- a/src/components/Terminal.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useRef, useState, useEffect, type ReactNode } from "react";
-import clsx from "clsx";
-import { TerminalContextProvider } from "~/context/TerminalContext";
-
-export const Terminal = (props: {
- children?: ReactNode;
- className?: string;
-}) => {
- const terminalRef = useRef(null);
-
- const [size, setSize] = useState<{ cols: number; rows: number }>();
-
- useEffect(() => {
- const precision = 300;
-
- const calculateSize = () => {
- if (!terminalRef.current) return;
-
- const node = document.createElement("span");
- node.style.color = "transparent";
- node.style.position = "absolute";
- node.textContent = "A".repeat(precision);
-
- terminalRef.current.appendChild(node);
-
- setSize({
- cols: Math.floor(
- (terminalRef.current.offsetWidth - 4) /
- (node.offsetWidth / precision),
- ),
- rows: Math.floor(
- (terminalRef.current.offsetHeight - 4) / node.offsetHeight,
- ),
- });
-
- node.remove();
- };
-
- calculateSize();
-
- setTimeout(() => calculateSize(), 1);
-
- window.addEventListener("resize", calculateSize);
-
- return () => {
- window.removeEventListener("resize", calculateSize);
- };
- }, []);
-
- return (
-
-
- {size && props.children}
-
-
- );
-};
diff --git a/src/context/TerminalContext.tsx b/src/context/TerminalContext.tsx
deleted file mode 100644
index 9dd44b5..0000000
--- a/src/context/TerminalContext.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-/* eslint-disable react-refresh/only-export-components */
-import { createContext, useContext } from "react";
-
-const TerminalContext = createContext<
- { cols: number; rows: number } | undefined
->(undefined);
-
-export const TerminalContextProvider = TerminalContext.Provider;
-
-export const useTerminal = () => {
- const context = useContext(TerminalContext);
- if (!context)
- throw new Error("useTerminal must be used inside a Terminal component");
-
- return context;
-};
diff --git a/src/utils/terminal/cell.ts b/src/utils/terminal/cell.ts
deleted file mode 100644
index f2984cc..0000000
--- a/src/utils/terminal/cell.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export type Cell = {
- char: string;
-} & CellStyle;
-
-export type CellStyle = Partial<{
- foreground: string;
- background: string;
- fontWeight: number;
-}>;
diff --git a/src/utils/terminal/element.ts b/src/utils/terminal/element.ts
deleted file mode 100644
index 2768d78..0000000
--- a/src/utils/terminal/element.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { type Cell } from "./cell";
-
-export interface TerminalElement {
- readonly data: Array>;
- readonly width: number;
- readonly height: number;
-}
diff --git a/src/utils/terminal/elements/box.ts b/src/utils/terminal/elements/box.ts
deleted file mode 100644
index 4c3cd0d..0000000
--- a/src/utils/terminal/elements/box.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { type Cell, type CellStyle } from "../cell";
-import { TerminalRenderer } from "../renderer";
-import { type TerminalElement } from "../element";
-
-export class TerminalBoxElement implements TerminalElement {
- public readonly data: Array>;
-
- constructor(
- public readonly width: number,
- public readonly height: number,
- style: CellStyle = {},
- ) {
- const canvas = new TerminalRenderer(width, height, style);
-
- if (width == 1 && height > 1) {
- for (let y = 0; y < height - 1; y++) {
- canvas.write(0, y, "│");
- }
- } else if (height == 1 && width > 1) {
- canvas.write(0, 0, "─".repeat(width - 2));
- } else {
- canvas.write(0, 0, "┌");
- canvas.write(width - 1, 0, "┐");
- canvas.write(0, height - 1, "└");
- canvas.write(width - 1, height - 1, "┘");
-
- canvas.write(1, 0, "─".repeat(width - 2));
- canvas.write(1, height - 1, "─".repeat(width - 2));
-
- for (let y = 1; y < height - 1; y++) {
- canvas.write(0, y, "│");
- canvas.write(width - 1, y, "│");
- }
- }
-
- this.data = canvas.data;
- }
-}
diff --git a/src/utils/terminal/renderer.tsx b/src/utils/terminal/renderer.tsx
deleted file mode 100644
index 4917ce4..0000000
--- a/src/utils/terminal/renderer.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { type ReactNode } from "react";
-import { floorAll } from "../math";
-import { type CellStyle, type Cell } from "./cell";
-import { type TerminalElement } from "./element";
-
-export class TerminalRenderer implements TerminalElement {
- public readonly data: Array>;
-
- constructor(
- public readonly width: number,
- public readonly height: number,
- public readonly defaultStyle: CellStyle = {},
- ) {
- [this.width, this.height] = floorAll(this.width, this.height);
-
- this.data = new Array(this.height).fill(0).map(() =>
- new Array(this.width).fill({
- char: " ",
- ...defaultStyle,
- }),
- );
- }
-
- apply(x: number, y: number, cell: Partial): void {
- [x, y] = floorAll(x, y);
-
- if (x < 0 || x >= this.width || y < 0 || y >= this.height) return;
-
- this.data[y][x] = {
- ...this.data[y][x],
- ...cell,
- };
- }
-
- write(x: number, y: number, text: string, style: CellStyle = {}): void {
- [x, y] = floorAll(x, y);
-
- for (let i = 0; i < text.length; i++) {
- this.apply(x + i, y, {
- char: text[i],
- ...style,
- });
- }
- }
-
- writeFilter(
- x: number,
- y: number,
- text: string,
- filter: (cell: Cell) => Cell,
- ): void {
- [x, y] = floorAll(x, y);
-
- for (let i = 0; i < text.length; i++) {
- this.apply(x + i, y, {
- ...filter(this.data[y][x + i]),
- char: text[i],
- });
- }
- }
-
- writeElement(canvas: TerminalElement, dx: number, dy: number): void {
- [dx, dy] = floorAll(dx, dy);
-
- for (let y = 0; y < canvas.height; y++) {
- for (let x = 0; x < canvas.width; x++) {
- this.apply(dx + x, dy + y, canvas.data[y][x]);
- }
- }
- }
-
- subCanvas(
- x: number,
- y: number,
- width: number,
- height: number,
- ): TerminalRenderer {
- [x, y, width, height] = floorAll(x, y, width, height);
-
- const canvas = new TerminalRenderer(width, height);
- for (let cy = 0; cy < height; cy++) {
- for (let cx = 0; cx < width; cx++) {
- canvas.apply(cx, cy, this.data[y + cy][x + cx]);
- }
- }
-
- return canvas;
- }
-
- render(): Array {
- const nodes: Array = [];
-
- for (let y = 0; y < this.height; y++) {
- for (let x = 0; x < this.width; x++) {
- const cell = this.data[y][x];
- /*
- const span = document.createElement("span");
- span.innerHTML = cell.char;
- span.style.color = cell.foreground ?? "unset";
- span.style.background = cell.background ?? "unset";
- span.style.fontWeight = String(cell.fontWeight ?? "unset");
-
- target.appendChild(span);
- */
- nodes.push(
-
- {cell.char}
- ,
- );
- }
-
- nodes.push( );
- }
-
- return nodes;
- }
-}
diff --git a/src/utils/terminal/theme.ts b/src/utils/theme.ts
similarity index 100%
rename from src/utils/terminal/theme.ts
rename to src/utils/theme.ts
| |