more structure, store, types, views, global store to track tabs/blocks. two views

This commit is contained in:
sawka
2024-05-13 23:45:41 -07:00
parent 77a4987384
commit 540f2fe0c0
13 changed files with 261 additions and 108 deletions
+16 -23
View File
@@ -8,48 +8,41 @@ import * as rx from "rxjs";
import { clsx } from "clsx";
import { TabContent } from "@/app/tab/tab";
import { v4 as uuidv4 } from "uuid";
import { globalStore, atoms } from "@/store/global";
import "/public/style.less";
const jotaiStore = jotai.createStore();
const tabArr = [
{ name: "Tab 1", tabid: uuidv4() },
{ name: "Tab 2", tabid: uuidv4() },
{ name: "Tab 3", tabid: uuidv4() },
];
const activeTabIdAtom = jotai.atom(tabArr[0].tabid);
const App = () => {
return (
<Provider store={jotaiStore}>
<Provider store={globalStore}>
<AppInner />
</Provider>
);
};
const Tab = ({ tab }: { tab: TabData }) => {
const [activeTab, setActiveTab] = jotai.useAtom(atoms.activeTabId);
return (
<div className={clsx("tab", { active: activeTab === tab.tabid })} onClick={() => setActiveTab(tab.tabid)}>
{tab.name}
</div>
);
};
const TabBar = () => {
const [activeTab, setActiveTab] = jotai.useAtom(activeTabIdAtom);
const [activeTab, setActiveTab] = jotai.useAtom(atoms.activeTabId);
const tabs = jotai.useAtomValue(atoms.tabsAtom);
return (
<div className="tab-bar">
{tabArr.map((tab, idx) => {
return (
<div
key={idx}
className={clsx("tab", { active: activeTab === tab.tabid })}
onClick={() => setActiveTab(tab.tabid)}
>
{tab.name}
</div>
);
{tabs.map((tab, idx) => {
return <Tab key={idx} tab={tab} />;
})}
</div>
);
};
const Workspace = () => {
const activeTabId = jotai.useAtomValue(activeTabIdAtom);
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
return (
<div className="workspace">
<TabBar />
+16 -17
View File
@@ -10,23 +10,22 @@
height: 100%;
overflow: hidden;
min-height: 0;
}
.block .block-header {
display: flex;
flex-shrink: 0;
height: 30px;
width: 100%;
align-items: center;
justify-content: center;
background-color: var(--panel-bg-color);
}
.block-header {
display: flex;
flex-shrink: 0;
height: 30px;
width: 100%;
align-items: center;
justify-content: center;
background-color: var(--panel-bg-color);
}
.block .block-term-connectelem {
display: flex;
flex-grow: 1;
width: 100%;
background-color: green;
overflow: hidden;
min-height: 0;
.block-content {
display: flex;
flex-grow: 1;
width: 100%;
overflow: hidden;
min-height: 0;
}
}
+17 -54
View File
@@ -3,74 +3,35 @@
import * as React from "react";
import * as jotai from "jotai";
import { Terminal } from "@xterm/xterm";
import type { ITheme } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { atoms } from "@/store/global";
import { TerminalView } from "@/app/view/term";
import { PreviewView } from "@/app/view/preview";
import "/public/xterm.css";
import "./block.less";
function getThemeFromCSSVars(el: Element): ITheme {
const theme: ITheme = {};
const elemStyle = getComputedStyle(el);
theme.foreground = elemStyle.getPropertyValue("--term-foreground");
theme.background = elemStyle.getPropertyValue("--term-background");
theme.black = elemStyle.getPropertyValue("--term-black");
theme.red = elemStyle.getPropertyValue("--term-red");
theme.green = elemStyle.getPropertyValue("--term-green");
theme.yellow = elemStyle.getPropertyValue("--term-yellow");
theme.blue = elemStyle.getPropertyValue("--term-blue");
theme.magenta = elemStyle.getPropertyValue("--term-magenta");
theme.cyan = elemStyle.getPropertyValue("--term-cyan");
theme.white = elemStyle.getPropertyValue("--term-white");
theme.brightBlack = elemStyle.getPropertyValue("--term-bright-black");
theme.brightRed = elemStyle.getPropertyValue("--term-bright-red");
theme.brightGreen = elemStyle.getPropertyValue("--term-bright-green");
theme.brightYellow = elemStyle.getPropertyValue("--term-bright-yellow");
theme.brightBlue = elemStyle.getPropertyValue("--term-bright-blue");
theme.brightMagenta = elemStyle.getPropertyValue("--term-bright-magenta");
theme.brightCyan = elemStyle.getPropertyValue("--term-bright-cyan");
theme.brightWhite = elemStyle.getPropertyValue("--term-bright-white");
theme.selectionBackground = elemStyle.getPropertyValue("--term-selection-background");
theme.selectionInactiveBackground = elemStyle.getPropertyValue("--term-selection-background");
theme.cursor = elemStyle.getPropertyValue("--term-selection-background");
theme.cursorAccent = elemStyle.getPropertyValue("--term-cursor-accent");
return theme;
}
const Block = ({ blockId }: { blockId: string }) => {
const blockRef = React.useRef<HTMLDivElement>(null);
const connectElemRef = React.useRef<HTMLDivElement>(null);
const [dims, setDims] = React.useState({ width: 0, height: 0 });
React.useEffect(() => {
if (!blockRef.current) {
return;
}
const rect = blockRef.current.getBoundingClientRect();
const newWidth = parseInt(rect.width);
const newHeight = parseInt(rect.height);
const newWidth = Math.floor(rect.width);
const newHeight = Math.floor(rect.height);
if (newWidth !== dims.width || newHeight !== dims.height) {
setDims({ width: newWidth, height: newHeight });
}
}, [blockRef.current]);
React.useEffect(() => {
const term = new Terminal({
theme: getThemeFromCSSVars(blockRef.current),
fontSize: 12,
fontFamily: "Hack",
drawBoldTextInBrightColors: false,
fontWeight: "normal",
fontWeightBold: "bold",
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(connectElemRef.current);
fitAddon.fit();
term.write("Hello, world!");
return () => {
term.dispose();
};
}, [connectElemRef.current]);
let blockElem: JSX.Element = null;
const blockAtom = atoms.blockAtomFamily(blockId);
const blockData = jotai.useAtomValue(blockAtom);
if (blockData.view === "term") {
blockElem = <TerminalView blockId={blockId} />;
} else if (blockData.view === "preview") {
blockElem = <PreviewView blockId={blockId} />;
}
return (
<div className="block" ref={blockRef}>
<div key="header" className="block-header">
@@ -78,7 +39,9 @@ const Block = ({ blockId }: { blockId: string }) => {
Block [{blockId.substring(0, 8)}] {dims.width}x{dims.height}
</div>
</div>
<div key="conntectElem" className="block-term-connectelem" ref={connectElemRef}></div>
<div key="content" className="block-content">
{blockElem}
</div>
</div>
);
};
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as jotai from "jotai";
import { atomFamily } from "jotai/utils";
import { v4 as uuidv4 } from "uuid";
const globalStore = jotai.createStore();
const tabId1 = uuidv4();
const tabId2 = uuidv4();
const blockId1 = uuidv4();
const blockId2 = uuidv4();
const blockId3 = uuidv4();
const tabArr: TabData[] = [
{ name: "Tab 1", tabid: tabId1, blockIds: [blockId1, blockId2] },
{ name: "Tab 2", tabid: tabId2, blockIds: [blockId3] },
];
const blockAtomFamily = atomFamily<string, jotai.Atom<BlockData>>((blockId: string) => {
if (blockId === blockId1) {
return jotai.atom({ blockid: blockId1, view: "term" });
}
if (blockId === blockId2) {
return jotai.atom({ blockid: blockId2, view: "preview" });
}
if (blockId === blockId3) {
return jotai.atom({ blockid: blockId3, view: "term" });
}
return jotai.atom(null);
});
const atoms = {
activeTabId: jotai.atom<string>(tabId1),
tabsAtom: jotai.atom<TabData[]>(tabArr),
blockAtomFamily,
};
export { globalStore, atoms };
+17 -11
View File
@@ -3,23 +3,29 @@
import * as React from "react";
import * as jotai from "jotai";
import { Block } from "../block/block.tsx";
import { v4 as uuidv4 } from "uuid";
import { Block } from "@/app/block/block";
import { atoms } from "@/store/global";
import "./tab.less";
const TabContent = () => {
const blockId1 = React.useMemo(() => uuidv4(), []);
const blockId2 = React.useMemo(() => uuidv4(), []);
const blockId1 = "44113b0c-1528-4db1-94f0-2cafa1542941";
const blockId2 = "6bd76ccb-76ae-4f29-aa64-35206767e1ac";
const TabContent = ({ tabId }: { tabId: string }) => {
const tabs = jotai.useAtomValue(atoms.tabsAtom);
const tabData = tabs.find((tab) => tab.tabid === tabId);
if (!tabData) {
return <div className="tabcontent">Tab not found</div>;
}
return (
<div className="tabcontent">
<div className="block-container block1">
<Block blockId={blockId1} />
</div>
<div className="block-container block2">
<Block blockId={blockId2} />
</div>
{tabData.blockIds.map((blockId: string) => {
return (
<div key={blockId} className="block-container">
<Block blockId={blockId} />
</div>
);
})}
</div>
);
};
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as jotai from "jotai";
import "./view.less";
const PreviewView = ({ blockId }: { blockId: string }) => {
return (
<div className="view-preview">
<div>Preview</div>
</div>
);
};
export { PreviewView };
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as jotai from "jotai";
import { Terminal } from "@xterm/xterm";
import type { ITheme } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import "./view.less";
import "/public/xterm.css";
function getThemeFromCSSVars(el: Element): ITheme {
const theme: ITheme = {};
const elemStyle = getComputedStyle(el);
theme.foreground = elemStyle.getPropertyValue("--term-foreground");
theme.background = elemStyle.getPropertyValue("--term-background");
theme.black = elemStyle.getPropertyValue("--term-black");
theme.red = elemStyle.getPropertyValue("--term-red");
theme.green = elemStyle.getPropertyValue("--term-green");
theme.yellow = elemStyle.getPropertyValue("--term-yellow");
theme.blue = elemStyle.getPropertyValue("--term-blue");
theme.magenta = elemStyle.getPropertyValue("--term-magenta");
theme.cyan = elemStyle.getPropertyValue("--term-cyan");
theme.white = elemStyle.getPropertyValue("--term-white");
theme.brightBlack = elemStyle.getPropertyValue("--term-bright-black");
theme.brightRed = elemStyle.getPropertyValue("--term-bright-red");
theme.brightGreen = elemStyle.getPropertyValue("--term-bright-green");
theme.brightYellow = elemStyle.getPropertyValue("--term-bright-yellow");
theme.brightBlue = elemStyle.getPropertyValue("--term-bright-blue");
theme.brightMagenta = elemStyle.getPropertyValue("--term-bright-magenta");
theme.brightCyan = elemStyle.getPropertyValue("--term-bright-cyan");
theme.brightWhite = elemStyle.getPropertyValue("--term-bright-white");
theme.selectionBackground = elemStyle.getPropertyValue("--term-selection-background");
theme.selectionInactiveBackground = elemStyle.getPropertyValue("--term-selection-background");
theme.cursor = elemStyle.getPropertyValue("--term-selection-background");
theme.cursorAccent = elemStyle.getPropertyValue("--term-cursor-accent");
return theme;
}
const TerminalView = ({ blockId }: { blockId: string }) => {
const connectElemRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (!connectElemRef.current) {
return;
}
const term = new Terminal({
theme: getThemeFromCSSVars(connectElemRef.current),
fontSize: 12,
fontFamily: "Hack",
drawBoldTextInBrightColors: false,
fontWeight: "normal",
fontWeightBold: "bold",
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(connectElemRef.current);
fitAddon.fit();
term.write("Hello, world!");
return () => {
term.dispose();
};
}, [connectElemRef.current]);
return <div key="conntectElem" className="view-term term-connectelem" ref={connectElemRef}></div>;
};
export { TerminalView };
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
.view-term {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
}
.view-preview {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
flex-grow: 1;
overflow: hidden;
align-items: center;
justify-content: center;
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
declare global {
type TabData = {
name: string;
tabid: string;
blockIds: string[];
};
type BlockData = {
blockid: string;
view: string;
};
}
export {};