mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
more structure, store, types, views, global store to track tabs/blocks. two views
This commit is contained in:
+16
-23
@@ -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 />
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
}
|
||||
Vendored
+17
@@ -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 {};
|
||||
@@ -10,6 +10,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.2",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@wailsio/runtime": "latest",
|
||||
"less": "^4.2.0",
|
||||
|
||||
+4
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"include": ["frontend/**/*"],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"jsx": "preserve",
|
||||
"esModuleInterop": true,
|
||||
@@ -15,8 +15,9 @@
|
||||
"downlevelIteration": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/app/*": ["frontend/app/*"], // Points to the src folder
|
||||
"@/util/*": ["frontend/util/*"], // Points to the src folder
|
||||
"@/app/*": ["frontend/app/*"],
|
||||
"@/util/*": ["frontend/util/*"],
|
||||
"@/store/*": ["frontend/app/store/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export default defineConfig({
|
||||
plugins: [react({}), tsconfigPaths()],
|
||||
publicDir: "public",
|
||||
build: {
|
||||
target: "es6",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
app: "public/index.html",
|
||||
|
||||
@@ -474,6 +474,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
"@types/prop-types@*":
|
||||
version "15.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
|
||||
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
|
||||
|
||||
"@types/react@^18.3.2":
|
||||
version "18.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.2.tgz#462ae4904973bc212fa910424d901e3d137dbfcd"
|
||||
integrity sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/uuid@^9.0.8":
|
||||
version "9.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
|
||||
integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==
|
||||
|
||||
"@vitejs/plugin-react@^4.2.1":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz#744d8e4fcb120fc3dbaa471dadd3483f5a304bb9"
|
||||
@@ -562,6 +580,11 @@ copy-anything@^2.0.1:
|
||||
dependencies:
|
||||
is-what "^3.14.1"
|
||||
|
||||
csstype@^3.0.2:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
|
||||
Reference in New Issue
Block a user