mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-08 19:38:51 +01:00
0a45311f30
This PR adds support for Outer variants of each DropDirection. When calculating the drop direction, the cursor position is calculated relevant to the box over which it is hovering. The following diagram shows how drop directions are calculated. The colored in center is currently not supported, it is assigned to the top, bottom, left, right direction for now, though it will ultimately be its own distinct direction. ![IMG_3505](https://github.com/wavetermdev/thenextwave/assets/16651283/a7ea7387-b95d-4831-9e29-d3225b824c97) When an outer drop direction is provided for a move operation, if the reference node flexes in the same axis as the drop direction, the new node will be inserted at the same level as the parent of the reference node. If the reference node flexes in a different direction or the reference node does not have a grandparent, the operation will fall back to its non-Outer variant. This also removes some chatty debug statements, adds a blur to the currently-dragging node to indicate that it cannot be dropped onto, and simplifies the deriving of the layout state atom from the tab atom so there's no longer another intermediate derived atom for the layout node. This also adds rudimentary support for rendering custom preview images for any tile being dragged. Right now, this is a simple block containing the block ID, but this can be anything. This resolves an issue where letting React-DnD generate its own previews could take up to a half second, and would block dragging until complete. For Monaco, this was outright failing. It also fixes an issue where the tile layout could animate on first paint. Now, I use React Suspense to prevent the layout from displaying until all the children have loaded.
172 lines
5.7 KiB
TypeScript
172 lines
5.7 KiB
TypeScript
// Copyright 2024, Command Line Inc.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import { TabContent } from "@/app/tab/tab";
|
|
import { atoms } from "@/store/global";
|
|
import * as WOS from "@/store/wos";
|
|
import { clsx } from "clsx";
|
|
import * as jotai from "jotai";
|
|
import { CenteredDiv } from "../element/quickelems";
|
|
|
|
import { LayoutTreeActionType, LayoutTreeInsertNodeAction, newLayoutNode } from "@/faraday/index";
|
|
import {
|
|
deleteLayoutStateAtomForTab,
|
|
getLayoutStateAtomForTab,
|
|
useLayoutTreeStateReducerAtom,
|
|
} from "@/faraday/lib/layoutAtom";
|
|
import { useCallback, useMemo } from "react";
|
|
import "./workspace.less";
|
|
|
|
function Tab({ tabId }: { tabId: string }) {
|
|
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
|
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
|
|
function setActiveTab() {
|
|
WOS.SetActiveTab(tabId);
|
|
}
|
|
function handleCloseTab() {
|
|
WOS.CloseTab(tabId);
|
|
deleteLayoutStateAtomForTab(tabId);
|
|
}
|
|
return (
|
|
<div
|
|
className={clsx("tab", { active: tabData != null && windowData.activetabid === tabData.oid })}
|
|
onClick={() => setActiveTab()}
|
|
>
|
|
<div className="tab-close" onClick={() => handleCloseTab()}>
|
|
<div>
|
|
<i className="fa fa-solid fa-xmark" />
|
|
</div>
|
|
</div>
|
|
{tabData?.name ?? "..."}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TabBar({ workspace }: { workspace: Workspace }) {
|
|
function handleAddTab() {
|
|
const newTabName = `Tab-${workspace.tabids.length + 1}`;
|
|
WOS.AddTabToWorkspace(newTabName, true);
|
|
}
|
|
const tabIds = workspace?.tabids ?? [];
|
|
return (
|
|
<div className="tab-bar">
|
|
{tabIds.map((tabid, idx) => {
|
|
return <Tab key={idx} tabId={tabid} />;
|
|
})}
|
|
<div className="tab-add" onClick={() => handleAddTab()}>
|
|
<i className="fa fa-solid fa-plus fa-fw" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Widgets() {
|
|
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
|
const activeTabAtom = useMemo(() => {
|
|
return getLayoutStateAtomForTab(
|
|
windowData.activetabid,
|
|
WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", windowData.activetabid))
|
|
);
|
|
}, [windowData.activetabid]);
|
|
const [, dispatchLayoutStateAction] = useLayoutTreeStateReducerAtom(activeTabAtom);
|
|
|
|
const addBlockToTab = useCallback(
|
|
(blockId: string) => {
|
|
const insertNodeAction: LayoutTreeInsertNodeAction<TabLayoutData> = {
|
|
type: LayoutTreeActionType.InsertNode,
|
|
node: newLayoutNode<TabLayoutData>(undefined, undefined, undefined, { blockId }),
|
|
};
|
|
dispatchLayoutStateAction(insertNodeAction);
|
|
},
|
|
[activeTabAtom, dispatchLayoutStateAction]
|
|
);
|
|
|
|
async function createBlock(blockDef: BlockDef) {
|
|
const rtOpts: RuntimeOpts = { termsize: { rows: 25, cols: 80 } };
|
|
const { blockId } = await WOS.CreateBlock(blockDef, rtOpts);
|
|
addBlockToTab(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: BlockDef = {
|
|
view: "plot",
|
|
};
|
|
createBlock(plotDef);
|
|
}
|
|
|
|
async function clickEdit() {
|
|
const editDef: BlockDef = {
|
|
view: "codeedit",
|
|
};
|
|
createBlock(editDef);
|
|
}
|
|
|
|
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={() => clickPreview("build/appicon.png")}>
|
|
<i className="fa fa-solid fa-files fa-fw" />
|
|
</div>
|
|
<div className="widget" onClick={() => clickPreview("~")}>
|
|
<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" onClick={() => clickEdit()}>
|
|
<i className="fa-sharp fa-solid fa-pen-to-square"></i>
|
|
</div>
|
|
<div className="widget no-hover">
|
|
<i className="fa fa-solid fa-plus fa-fw" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WorkspaceElem() {
|
|
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
|
const activeTabId = windowData?.activetabid;
|
|
const ws = jotai.useAtomValue(atoms.workspace);
|
|
return (
|
|
<div className="workspace">
|
|
<TabBar workspace={ws} />
|
|
<div className="workspace-tabcontent">
|
|
{activeTabId == "" ? (
|
|
<CenteredDiv>No Active Tab</CenteredDiv>
|
|
) : (
|
|
<>
|
|
<TabContent key={windowData.workspaceid} tabId={activeTabId} />
|
|
<Widgets />
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { WorkspaceElem as Workspace };
|