mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
feat: integrate plots into new block setup
This commit is contained in:
+2
-33
@@ -5,10 +5,10 @@ import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { Provider } from "jotai";
|
||||
import { clsx } from "clsx";
|
||||
import { TabContent } from "@/app/tab/tab";
|
||||
import { Workspace } from "@/app/workspace/workspace";
|
||||
import { globalStore, atoms } from "@/store/global";
|
||||
|
||||
import "/public/style.less";
|
||||
import "../../public/style.less";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
@@ -18,37 +18,6 @@ const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
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(atoms.activeTabId);
|
||||
const tabs = jotai.useAtomValue(atoms.tabsAtom);
|
||||
return (
|
||||
<div className="tab-bar">
|
||||
{tabs.map((tab, idx) => {
|
||||
return <Tab key={idx} tab={tab} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Workspace = () => {
|
||||
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
return (
|
||||
<div className="workspace">
|
||||
<TabBar />
|
||||
<TabContent tabId={activeTabId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const AppInner = () => {
|
||||
return (
|
||||
<div className="mainapp">
|
||||
|
||||
@@ -13,12 +13,25 @@
|
||||
|
||||
.block-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--panel-bg-color);
|
||||
|
||||
.block-header-text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
font-size: 12px;
|
||||
padding-right: 5px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.block-content {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { atoms } from "@/store/global";
|
||||
import { atoms, blockDataMap, removeBlockFromTab } from "@/store/global";
|
||||
|
||||
import { TerminalView } from "@/app/view/term";
|
||||
import { PreviewView } from "@/app/view/preview";
|
||||
@@ -12,9 +12,14 @@ import { CenteredLoadingDiv } from "@/element/quickelems";
|
||||
|
||||
import "./block.less";
|
||||
|
||||
const Block = ({ blockId }: { blockId: string }) => {
|
||||
const Block = ({ tabId, blockId }: { tabId: string; blockId: string }) => {
|
||||
const blockRef = React.useRef<HTMLDivElement>(null);
|
||||
const [dims, setDims] = React.useState({ width: 0, height: 0 });
|
||||
|
||||
function handleClose() {
|
||||
removeBlockFromTab(tabId, blockId);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!blockRef.current) {
|
||||
return;
|
||||
@@ -27,7 +32,7 @@ const Block = ({ blockId }: { blockId: string }) => {
|
||||
}
|
||||
}, [blockRef.current]);
|
||||
let blockElem: JSX.Element = null;
|
||||
const blockAtom = atoms.blockAtomFamily(blockId);
|
||||
const blockAtom = blockDataMap.get(blockId);
|
||||
const blockData = jotai.useAtomValue(blockAtom);
|
||||
if (blockData.view === "term") {
|
||||
blockElem = <TerminalView blockId={blockId} />;
|
||||
@@ -39,9 +44,13 @@ const Block = ({ blockId }: { blockId: string }) => {
|
||||
return (
|
||||
<div className="block" ref={blockRef}>
|
||||
<div key="header" className="block-header">
|
||||
<div className="text-fixed">
|
||||
<div className="block-header-text text-fixed">
|
||||
Block [{blockId.substring(0, 8)}] {dims.width}x{dims.height}
|
||||
</div>
|
||||
<div className="flex-spacer" />
|
||||
<div className="close-button" onClick={() => handleClose()}>
|
||||
<i className="fa fa-solid fa-xmark-large" />
|
||||
</div>
|
||||
</div>
|
||||
<div key="content" className="block-content">
|
||||
<React.Suspense fallback={<CenteredLoadingDiv />}>{blockElem}</React.Suspense>
|
||||
|
||||
@@ -7,42 +7,21 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import * as rxjs from "rxjs";
|
||||
import type { WailsEvent } from "@wailsio/runtime/types/events";
|
||||
import { Events } from "@wailsio/runtime";
|
||||
import { produce } from "immer";
|
||||
import * as BlockService from "@/bindings/pkg/service/blockservice/BlockService";
|
||||
|
||||
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, blockId3] },
|
||||
{ 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",
|
||||
meta: { mimetype: "text/markdown", file: "README.md" },
|
||||
});
|
||||
}
|
||||
if (blockId === blockId3) {
|
||||
return jotai.atom({ blockid: blockId3, view: "plot" });
|
||||
}
|
||||
return jotai.atom(null);
|
||||
});
|
||||
const tabArr: TabData[] = [{ name: "Tab 1", tabid: tabId1, blockIds: [] }];
|
||||
const blockDataMap = new Map<string, jotai.Atom<BlockData>>();
|
||||
const blockAtomCache = new Map<string, Map<string, jotai.Atom<any>>>();
|
||||
|
||||
const atoms = {
|
||||
activeTabId: jotai.atom<string>(tabId1),
|
||||
tabsAtom: jotai.atom<TabData[]>(tabArr),
|
||||
blockAtomFamily,
|
||||
blockDataMap: blockDataMap,
|
||||
};
|
||||
|
||||
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
|
||||
@@ -81,4 +60,43 @@ Events.On("block:ptydata", (event: any) => {
|
||||
subject.next(data);
|
||||
});
|
||||
|
||||
export { globalStore, atoms, getBlockSubject };
|
||||
function addBlockIdToTab(tabId: string, blockId: string) {
|
||||
let tabArr = globalStore.get(atoms.tabsAtom);
|
||||
const newTabArr = produce(tabArr, (draft) => {
|
||||
const tab = draft.find((tab) => tab.tabid == tabId);
|
||||
tab.blockIds.push(blockId);
|
||||
});
|
||||
globalStore.set(atoms.tabsAtom, newTabArr);
|
||||
}
|
||||
|
||||
function removeBlock(blockId: string) {
|
||||
blockDataMap.delete(blockId);
|
||||
blockAtomCache.delete(blockId);
|
||||
}
|
||||
|
||||
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom<T>): jotai.Atom<T> {
|
||||
let blockCache = blockAtomCache.get(blockId);
|
||||
if (blockCache == null) {
|
||||
blockCache = new Map<string, jotai.Atom<any>>();
|
||||
blockAtomCache.set(blockId, blockCache);
|
||||
}
|
||||
let atom = blockCache.get(name);
|
||||
if (atom == null) {
|
||||
atom = makeFn();
|
||||
blockCache.set(name, atom);
|
||||
}
|
||||
return atom as jotai.Atom<T>;
|
||||
}
|
||||
|
||||
function removeBlockFromTab(tabId: string, blockId: string) {
|
||||
let tabArr = globalStore.get(atoms.tabsAtom);
|
||||
const newTabArr = produce(tabArr, (draft) => {
|
||||
const tab = draft.find((tab) => tab.tabid == tabId);
|
||||
tab.blockIds = tab.blockIds.filter((id) => id !== blockId);
|
||||
});
|
||||
globalStore.set(atoms.tabsAtom, newTabArr);
|
||||
removeBlock(blockId);
|
||||
BlockService.CloseBlock(blockId);
|
||||
}
|
||||
|
||||
export { globalStore, atoms, getBlockSubject, addBlockIdToTab, blockDataMap, useBlockAtom, removeBlockFromTab };
|
||||
|
||||
@@ -19,7 +19,7 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
{tabData.blockIds.map((blockId: string) => {
|
||||
return (
|
||||
<div key={blockId} className="block-container">
|
||||
<Block blockId={blockId} />
|
||||
<Block tabId={tabId} blockId={blockId} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -3,33 +3,16 @@
|
||||
|
||||
import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { atoms } from "@/store/global";
|
||||
import { atoms, blockDataMap, useBlockAtom } from "@/store/global";
|
||||
import { Markdown } from "@/element/markdown";
|
||||
import * as FileService from "@/bindings/pkg/service/fileservice/FileService";
|
||||
import * as util from "@/util/util";
|
||||
import { loadable } from "jotai/utils";
|
||||
|
||||
import "./view.less";
|
||||
|
||||
const markdownText = `
|
||||
# Markdown Preview
|
||||
|
||||
* list item 1
|
||||
* list item 2
|
||||
* item 3
|
||||
|
||||
\`\`\`
|
||||
let foo = "bar";
|
||||
console.log(foo);
|
||||
\`\`\`
|
||||
`;
|
||||
|
||||
const readmeAtom = jotai.atom(async () => {
|
||||
const readme = await FileService.ReadFile("README.md");
|
||||
return util.base64ToString(readme);
|
||||
});
|
||||
|
||||
const MarkdownPreview = ({ blockData }: { blockData: BlockData }) => {
|
||||
const readmeText = jotai.useAtomValue(readmeAtom);
|
||||
const MarkdownPreview = ({ contentAtom }: { contentAtom: jotai.Atom<Promise<string>> }) => {
|
||||
const readmeText = jotai.useAtomValue(contentAtom);
|
||||
return (
|
||||
<div className="view-preview view-preview-markdown">
|
||||
<Markdown text={readmeText} />
|
||||
@@ -37,14 +20,54 @@ const MarkdownPreview = ({ blockData }: { blockData: BlockData }) => {
|
||||
);
|
||||
};
|
||||
|
||||
let counter = 0;
|
||||
|
||||
const PreviewView = ({ blockId }: { blockId: string }) => {
|
||||
const blockData: BlockData = jotai.useAtomValue(atoms.blockAtomFamily(blockId));
|
||||
if (blockData.meta?.mimetype === "text/markdown") {
|
||||
return <MarkdownPreview blockData={blockData} />;
|
||||
const blockDataAtom: jotai.Atom<BlockData> = blockDataMap.get(blockId);
|
||||
const fileNameAtom = useBlockAtom(blockId, "preview:filename", () =>
|
||||
jotai.atom<string>((get) => {
|
||||
return get(blockDataAtom)?.meta?.file;
|
||||
})
|
||||
);
|
||||
const fullFileAtom = useBlockAtom(blockId, "preview:fullfile", () =>
|
||||
jotai.atom<Promise<FullFile>>(async (get) => {
|
||||
const fileName = get(fileNameAtom);
|
||||
if (fileName == null) {
|
||||
return null;
|
||||
}
|
||||
const file = await FileService.ReadFile(fileName);
|
||||
return file;
|
||||
})
|
||||
);
|
||||
const fileMimeTypeAtom = useBlockAtom(blockId, "preview:mimetype", () =>
|
||||
jotai.atom<Promise<string>>(async (get) => {
|
||||
const fullFile = await get(fullFileAtom);
|
||||
return fullFile?.info?.mimetype;
|
||||
})
|
||||
);
|
||||
const fileContentAtom = useBlockAtom(blockId, "preview:filecontent", () =>
|
||||
jotai.atom<Promise<string>>(async (get) => {
|
||||
const fullFile = await get(fullFileAtom);
|
||||
return util.base64ToString(fullFile?.data64);
|
||||
})
|
||||
);
|
||||
let mimeType = jotai.useAtomValue(fileMimeTypeAtom);
|
||||
if (mimeType == null) {
|
||||
mimeType = "";
|
||||
}
|
||||
if (mimeType === "text/markdown") {
|
||||
return <MarkdownPreview contentAtom={fileContentAtom} />;
|
||||
}
|
||||
if (mimeType.startsWith("text/")) {
|
||||
return (
|
||||
<div className="view-preview view-preview-text">
|
||||
<pre>{jotai.useAtomValue(fileContentAtom)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="view-preview">
|
||||
<div>Preview</div>
|
||||
<div>Preview ({mimeType})</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-43
@@ -44,8 +44,7 @@ function getThemeFromCSSVars(el: Element): ITheme {
|
||||
|
||||
const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
const connectElemRef = React.useRef<HTMLDivElement>(null);
|
||||
const [term, setTerm] = React.useState<Terminal | null>(null);
|
||||
const [blockStarted, setBlockStarted] = React.useState<boolean>(false);
|
||||
const termRef = React.useRef<Terminal>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!connectElemRef.current) {
|
||||
@@ -59,26 +58,30 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
fontWeight: "normal",
|
||||
fontWeightBold: "bold",
|
||||
});
|
||||
setTerm(term);
|
||||
termRef.current = term;
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(connectElemRef.current);
|
||||
fitAddon.fit();
|
||||
term.write("Hello, world!\r\n");
|
||||
console.log(term);
|
||||
BlockService.SendCommand(blockId, {
|
||||
command: "controller:input",
|
||||
termsize: { rows: term.rows, cols: term.cols },
|
||||
});
|
||||
term.onData((data) => {
|
||||
const b64data = btoa(data);
|
||||
const inputCmd = { command: "input", blockid: blockId, inputdata64: b64data };
|
||||
const inputCmd = { command: "controller:input", blockid: blockId, inputdata64: b64data };
|
||||
BlockService.SendCommand(blockId, inputCmd);
|
||||
});
|
||||
|
||||
// resize observer
|
||||
const rszObs = new ResizeObserver(() => {
|
||||
const oldRows = term.rows;
|
||||
const oldCols = term.cols;
|
||||
fitAddon.fit();
|
||||
if (oldRows !== term.rows || oldCols !== term.cols) {
|
||||
BlockService.SendCommand(blockId, { command: "input", termsize: { rows: term.rows, cols: term.cols } });
|
||||
BlockService.SendCommand(blockId, {
|
||||
command: "controller:input",
|
||||
termsize: { rows: term.rows, cols: term.cols },
|
||||
});
|
||||
}
|
||||
});
|
||||
rszObs.observe(connectElemRef.current);
|
||||
@@ -98,43 +101,8 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
||||
};
|
||||
}, [connectElemRef.current]);
|
||||
|
||||
async function handleRunClick() {
|
||||
try {
|
||||
if (!blockStarted) {
|
||||
await BlockService.StartBlock(blockId);
|
||||
setBlockStarted(true);
|
||||
}
|
||||
let termSize = { rows: term.rows, cols: term.cols };
|
||||
await BlockService.SendCommand(blockId, { command: "run", cmdstr: "ls -l", termsize: termSize });
|
||||
} catch (e) {
|
||||
console.log("run click error: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleStartTerminalClick() {
|
||||
try {
|
||||
if (!blockStarted) {
|
||||
await BlockService.StartBlock(blockId);
|
||||
setBlockStarted(true);
|
||||
}
|
||||
let termSize = { rows: term.rows, cols: term.cols };
|
||||
await BlockService.SendCommand(blockId, { command: "runshell", termsize: termSize });
|
||||
} catch (e) {
|
||||
console.log("start terminal click error: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="view-term">
|
||||
<div className="term-header">
|
||||
<div>Terminal</div>
|
||||
<Button className="term-inline" onClick={() => handleRunClick()}>
|
||||
Run `ls`
|
||||
</Button>
|
||||
<Button className="term-inline" onClick={() => handleStartTerminalClick()}>
|
||||
Start Terminal
|
||||
</Button>
|
||||
</div>
|
||||
<div key="conntectElem" className="term-connectelem" ref={connectElemRef}></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -40,4 +40,14 @@
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
&.view-preview-text {
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
overflow: auto;
|
||||
|
||||
pre {
|
||||
font: var(--fixed-font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
.workspace {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.workspace-tabcontent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workspace-widgets {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 40px;
|
||||
overflow: hidden;
|
||||
background-color: var(--panel-bg-color);
|
||||
border-left: 1px solid var(--border-color);
|
||||
|
||||
.widget {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 10px 2px 10px 0;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 20px;
|
||||
&:hover:not(.no-hover) {
|
||||
background-color: var(--highlight-bg-color);
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 35px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
&.active {
|
||||
background-color: var(--highlight-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-add {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
border-left: 1px solid transparent;
|
||||
&:hover {
|
||||
border-left: 1px solid white;
|
||||
background-color: var(--highlight-bg-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as React from "react";
|
||||
import * as jotai from "jotai";
|
||||
import { TabContent } from "@/app/tab/tab";
|
||||
import { clsx } from "clsx";
|
||||
import { atoms, addBlockIdToTab, blockDataMap } from "@/store/global";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import * as BlockService from "@/bindings/pkg/service/blockservice/BlockService";
|
||||
|
||||
import "./workspace.less";
|
||||
|
||||
function 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>
|
||||
);
|
||||
}
|
||||
|
||||
function TabBar() {
|
||||
const [tabData, setTabData] = jotai.useAtom(atoms.tabsAtom);
|
||||
const [activeTab, setActiveTab] = jotai.useAtom(atoms.activeTabId);
|
||||
const tabs = jotai.useAtomValue(atoms.tabsAtom);
|
||||
|
||||
function handleAddTab() {
|
||||
const newTabId = uuidv4();
|
||||
const newTabName = "Tab " + (tabData.length + 1);
|
||||
setTabData([...tabData, { name: newTabName, tabid: newTabId, blockIds: [] }]);
|
||||
setActiveTab(newTabId);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tab-bar">
|
||||
{tabs.map((tab, idx) => {
|
||||
return <Tab key={idx} tab={tab} />;
|
||||
})}
|
||||
<div className="tab-add" onClick={() => handleAddTab()}>
|
||||
<i className="fa fa-solid fa-plus fa-fw" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Widgets() {
|
||||
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
|
||||
async function createBlock(blockDef: BlockDef) {
|
||||
const rtOpts = { termsize: { rows: 25, cols: 80 } };
|
||||
const rtnBlock: BlockData = await BlockService.CreateBlock(blockDef, rtOpts);
|
||||
const newBlockAtom = jotai.atom(rtnBlock);
|
||||
blockDataMap.set(rtnBlock.blockid, newBlockAtom);
|
||||
addBlockIdToTab(activeTabId, rtnBlock.blockid);
|
||||
}
|
||||
|
||||
async function clickTerminal() {
|
||||
const termBlockDef = {
|
||||
controller: "shell",
|
||||
view: "term",
|
||||
};
|
||||
createBlock(termBlockDef);
|
||||
}
|
||||
|
||||
async function clickPreview(fileName: string) {
|
||||
const markdownDef = {
|
||||
view: "preview",
|
||||
meta: { file: fileName },
|
||||
};
|
||||
createBlock(markdownDef);
|
||||
}
|
||||
|
||||
async function clickPlot() {
|
||||
const plotDef = {
|
||||
view: "plot",
|
||||
};
|
||||
createBlock(plotDef);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="workspace-widgets">
|
||||
<div className="widget" onClick={() => clickTerminal()}>
|
||||
<i className="fa fa-solid fa-square-terminal fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("README.md")}>
|
||||
<i className="fa fa-solid fa-files fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPreview("go.mod")}>
|
||||
<i className="fa fa-solid fa-files fa-fw" />
|
||||
</div>
|
||||
<div className="widget" onClick={() => clickPlot()}>
|
||||
<i className="fa fa-solid fa-chart-simple fa-fw" />
|
||||
</div>
|
||||
<div className="widget no-hover">
|
||||
<i className="fa fa-solid fa-plus fa-fw" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Workspace() {
|
||||
const activeTabId = jotai.useAtomValue(atoms.activeTabId);
|
||||
return (
|
||||
<div className="workspace">
|
||||
<TabBar />
|
||||
<div className="workspace-tabcontent">
|
||||
<TabContent key={activeTabId} tabId={activeTabId} />
|
||||
<Widgets />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { Workspace };
|
||||
Vendored
+36
-1
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
declare global {
|
||||
type MetaDataType = Record<string, any>;
|
||||
|
||||
type TabData = {
|
||||
name: string;
|
||||
tabid: string;
|
||||
@@ -10,8 +12,41 @@ declare global {
|
||||
|
||||
type BlockData = {
|
||||
blockid: string;
|
||||
blockdef: BlockDef;
|
||||
controller: string;
|
||||
controllerstatus: string;
|
||||
view: string;
|
||||
meta?: Record<string, any>;
|
||||
meta?: MetaDataType;
|
||||
};
|
||||
|
||||
type FileDef = {
|
||||
filetype?: string;
|
||||
path?: string;
|
||||
url?: string;
|
||||
content?: string;
|
||||
meta?: MetaDataType;
|
||||
};
|
||||
|
||||
type BlockDef = {
|
||||
controller?: string;
|
||||
view: string;
|
||||
files?: FileDef[];
|
||||
meta?: MetaDataType;
|
||||
};
|
||||
|
||||
type FileInfo = {
|
||||
path: string;
|
||||
notfound: boolean;
|
||||
size: number;
|
||||
mode: number;
|
||||
modtime: number;
|
||||
isdir: boolean;
|
||||
mimetype: string;
|
||||
};
|
||||
|
||||
type FullFile = {
|
||||
info: FileInfo;
|
||||
data64: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user