mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
react.memo (#79)
This commit is contained in:
parent
f036459dd5
commit
4f627a0342
@ -174,7 +174,7 @@ interface FramelessBlockHeaderProps {
|
||||
dragHandleRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const FramelessBlockHeader = ({ blockId, onClose, dragHandleRef }: FramelessBlockHeaderProps) => {
|
||||
const FramelessBlockHeader = React.memo(({ blockId, onClose, dragHandleRef }: FramelessBlockHeaderProps) => {
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
|
||||
@ -193,7 +193,7 @@ const FramelessBlockHeader = ({ blockId, onClose, dragHandleRef }: FramelessBloc
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
const hoverStateOff = "off";
|
||||
const hoverStatePending = "pending";
|
||||
@ -209,62 +209,56 @@ interface BlockFrameProps {
|
||||
dragHandleRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const BlockFrame_Tech = ({
|
||||
blockId,
|
||||
onClose,
|
||||
onClick,
|
||||
preview,
|
||||
blockRef,
|
||||
dragHandleRef,
|
||||
children,
|
||||
}: BlockFrameProps) => {
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const isFocusedAtom = useBlockAtom<boolean>(blockId, "isFocused", () => {
|
||||
return jotai.atom((get) => {
|
||||
const winData = get(atoms.waveWindow);
|
||||
return winData.activeblockid === blockId;
|
||||
const BlockFrame_Tech = React.memo(
|
||||
({ blockId, onClose, onClick, preview, blockRef, dragHandleRef, children }: BlockFrameProps) => {
|
||||
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const isFocusedAtom = useBlockAtom<boolean>(blockId, "isFocused", () => {
|
||||
return jotai.atom((get) => {
|
||||
const winData = get(atoms.waveWindow);
|
||||
return winData.activeblockid === blockId;
|
||||
});
|
||||
});
|
||||
});
|
||||
let isFocused = jotai.useAtomValue(isFocusedAtom);
|
||||
const blockIcon = useBlockIcon(blockId);
|
||||
let isFocused = jotai.useAtomValue(isFocusedAtom);
|
||||
const blockIcon = useBlockIcon(blockId);
|
||||
|
||||
if (preview) {
|
||||
isFocused = true;
|
||||
}
|
||||
let style: React.CSSProperties = {};
|
||||
if (!isFocused && blockData?.meta?.["frame:bordercolor"]) {
|
||||
style.borderColor = blockData.meta["frame:bordercolor"];
|
||||
}
|
||||
if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) {
|
||||
style.borderColor = blockData.meta["frame:bordercolor:focused"];
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"block",
|
||||
"block-frame-tech",
|
||||
isFocused ? "block-focused" : null,
|
||||
preview ? "block-preview" : null
|
||||
)}
|
||||
onClick={onClick}
|
||||
ref={blockRef}
|
||||
style={style}
|
||||
>
|
||||
if (preview) {
|
||||
isFocused = true;
|
||||
}
|
||||
let style: React.CSSProperties = {};
|
||||
if (!isFocused && blockData?.meta?.["frame:bordercolor"]) {
|
||||
style.borderColor = blockData.meta["frame:bordercolor"];
|
||||
}
|
||||
if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) {
|
||||
style.borderColor = blockData.meta["frame:bordercolor:focused"];
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="block-frame-tech-header"
|
||||
ref={dragHandleRef}
|
||||
onContextMenu={(e) => handleHeaderContextMenu(e, blockData, onClose)}
|
||||
className={clsx(
|
||||
"block",
|
||||
"block-frame-tech",
|
||||
isFocused ? "block-focused" : null,
|
||||
preview ? "block-preview" : null
|
||||
)}
|
||||
onClick={onClick}
|
||||
ref={blockRef}
|
||||
style={style}
|
||||
>
|
||||
{getBlockHeaderText(blockIcon, blockData, settingsConfig)}
|
||||
<div
|
||||
className="block-frame-tech-header"
|
||||
ref={dragHandleRef}
|
||||
onContextMenu={(e) => handleHeaderContextMenu(e, blockData, onClose)}
|
||||
>
|
||||
{getBlockHeaderText(blockIcon, blockData, settingsConfig)}
|
||||
</div>
|
||||
<div className={clsx("block-frame-tech-close")} onClick={onClose}>
|
||||
<i className="fa fa-solid fa-xmark fa-fw" />
|
||||
</div>
|
||||
{preview ? <div className="block-frame-preview" /> : children}
|
||||
</div>
|
||||
<div className={clsx("block-frame-tech-close")} onClick={onClose}>
|
||||
<i className="fa fa-solid fa-xmark fa-fw" />
|
||||
</div>
|
||||
{preview ? <div className="block-frame-preview" /> : children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const BlockFrame_Frameless = ({
|
||||
blockId,
|
||||
@ -340,7 +334,7 @@ const BlockFrame_Frameless = ({
|
||||
);
|
||||
};
|
||||
|
||||
const BlockFrame = (props: BlockFrameProps) => {
|
||||
const BlockFrame = React.memo((props: BlockFrameProps) => {
|
||||
const blockId = props.blockId;
|
||||
const [blockData, blockDataLoading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||
const tabData = jotai.useAtomValue(atoms.tabAtom);
|
||||
@ -360,7 +354,7 @@ const BlockFrame = (props: BlockFrameProps) => {
|
||||
FrameElem = BlockFrame_Frameless;
|
||||
}
|
||||
return <FrameElem {...props} />;
|
||||
};
|
||||
});
|
||||
|
||||
function blockViewToIcon(view: string): string {
|
||||
console.log("blockViewToIcon", view);
|
||||
@ -398,7 +392,16 @@ function useBlockIcon(blockId: string): string {
|
||||
return blockIcon;
|
||||
}
|
||||
|
||||
const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => {
|
||||
const wm = new WeakMap();
|
||||
let wmCounter = 0;
|
||||
function getObjectId(obj: any): number {
|
||||
if (!wm.has(obj)) {
|
||||
wm.set(obj, wmCounter++);
|
||||
}
|
||||
return wm.get(obj);
|
||||
}
|
||||
|
||||
const Block = React.memo(({ blockId, onClose, dragHandleRef }: BlockProps) => {
|
||||
let blockElem: JSX.Element = null;
|
||||
const focusElemRef = React.useRef<HTMLInputElement>(null);
|
||||
const blockRef = React.useRef<HTMLDivElement>(null);
|
||||
@ -417,24 +420,29 @@ const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => {
|
||||
setBlockFocus(blockId);
|
||||
}, [blockClicked]);
|
||||
|
||||
const setBlockClickedTrue = React.useCallback(() => {
|
||||
setBlockClicked(true);
|
||||
}, []);
|
||||
|
||||
if (!blockId || !blockData) return null;
|
||||
if (blockDataLoading) {
|
||||
blockElem = <CenteredDiv>Loading...</CenteredDiv>;
|
||||
} else if (blockData.view === "term") {
|
||||
blockElem = <TerminalView blockId={blockId} />;
|
||||
blockElem = <TerminalView key={blockId} blockId={blockId} />;
|
||||
} else if (blockData.view === "preview") {
|
||||
blockElem = <PreviewView blockId={blockId} />;
|
||||
blockElem = <PreviewView key={blockId} blockId={blockId} />;
|
||||
} else if (blockData.view === "plot") {
|
||||
blockElem = <PlotView />;
|
||||
blockElem = <PlotView key={blockId} />;
|
||||
} else if (blockData.view === "codeedit") {
|
||||
blockElem = <CodeEdit text={null} filename={null} />;
|
||||
blockElem = <CodeEdit key={blockId} text={null} filename={null} />;
|
||||
}
|
||||
return (
|
||||
<BlockFrame
|
||||
key={blockId}
|
||||
blockId={blockId}
|
||||
onClose={onClose}
|
||||
preview={false}
|
||||
onClick={() => setBlockClicked(true)}
|
||||
onClick={setBlockClickedTrue}
|
||||
blockRef={blockRef}
|
||||
dragHandleRef={dragHandleRef}
|
||||
>
|
||||
@ -448,6 +456,6 @@ const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => {
|
||||
</div>
|
||||
</BlockFrame>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { Block, BlockFrame };
|
||||
|
@ -6,7 +6,7 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({ className = "primary", children, disabled, ...props }) => {
|
||||
const Button: React.FC<ButtonProps> = React.memo(({ className = "primary", children, disabled, ...props }) => {
|
||||
const hasIcon = React.Children.toArray(children).some(
|
||||
(child) => React.isValidElement(child) && (child as React.ReactElement).type === "svg"
|
||||
);
|
||||
@ -23,6 +23,6 @@ const Button: React.FC<ButtonProps> = ({ className = "primary", children, disabl
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { Button };
|
||||
|
@ -6,6 +6,7 @@ import { ContextMenuModel } from "@/store/contextmenu";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
import { clsx } from "clsx";
|
||||
import * as React from "react";
|
||||
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||
|
||||
import "./tab.less";
|
||||
@ -22,129 +23,131 @@ interface TabProps {
|
||||
onLoaded: () => void;
|
||||
}
|
||||
|
||||
const Tab = forwardRef<HTMLDivElement, TabProps>(
|
||||
({ id, active, isFirst, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => {
|
||||
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", id));
|
||||
const [originalName, setOriginalName] = useState("");
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const Tab = React.memo(
|
||||
forwardRef<HTMLDivElement, TabProps>(
|
||||
({ id, active, isFirst, isBeforeActive, isDragging, onLoaded, onSelect, onClose, onDragStart }, ref) => {
|
||||
const [tabData, tabLoading] = WOS.useWaveObjectValue<Tab>(WOS.makeORef("tab", id));
|
||||
const [originalName, setOriginalName] = useState("");
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
|
||||
const editableRef = useRef<HTMLDivElement>(null);
|
||||
const editableTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const loadedRef = useRef(false);
|
||||
const editableRef = useRef<HTMLDivElement>(null);
|
||||
const editableTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const loadedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabData?.name) {
|
||||
setOriginalName(tabData.name);
|
||||
}
|
||||
}, [tabData]);
|
||||
useEffect(() => {
|
||||
if (tabData?.name) {
|
||||
setOriginalName(tabData.name);
|
||||
}
|
||||
}, [tabData]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (editableTimeoutRef.current) {
|
||||
clearTimeout(editableTimeoutRef.current);
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (editableTimeoutRef.current) {
|
||||
clearTimeout(editableTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDoubleClick = (event) => {
|
||||
event.stopPropagation();
|
||||
setIsEditable(true);
|
||||
editableTimeoutRef.current = setTimeout(() => {
|
||||
if (editableRef.current) {
|
||||
editableRef.current.focus();
|
||||
document.execCommand("selectAll", false);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
let newText = editableRef.current.innerText.trim();
|
||||
newText = newText || originalName;
|
||||
editableRef.current.innerText = newText;
|
||||
setIsEditable(false);
|
||||
services.ObjectService.UpdateTabName(id, newText);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "a") {
|
||||
event.preventDefault();
|
||||
if (editableRef.current) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.selectNodeContents(editableRef.current);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
if (editableRef.current.innerText.trim() === "") {
|
||||
editableRef.current.innerText = originalName;
|
||||
}
|
||||
editableRef.current.blur();
|
||||
} else if (event.key === "Escape") {
|
||||
editableRef.current.innerText = originalName;
|
||||
editableRef.current.blur();
|
||||
} else if (
|
||||
editableRef.current.innerText.length >= 8 &&
|
||||
!["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleDoubleClick = (event) => {
|
||||
event.stopPropagation();
|
||||
setIsEditable(true);
|
||||
editableTimeoutRef.current = setTimeout(() => {
|
||||
if (editableRef.current) {
|
||||
editableRef.current.focus();
|
||||
document.execCommand("selectAll", false);
|
||||
useEffect(() => {
|
||||
if (!loadedRef.current) {
|
||||
onLoaded();
|
||||
loadedRef.current = true;
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
}, [onLoaded]);
|
||||
|
||||
const handleBlur = () => {
|
||||
let newText = editableRef.current.innerText.trim();
|
||||
newText = newText || originalName;
|
||||
editableRef.current.innerText = newText;
|
||||
setIsEditable(false);
|
||||
services.ObjectService.UpdateTabName(id, newText);
|
||||
};
|
||||
// Prevent drag from being triggered on mousedown
|
||||
const handleMouseDownOnClose = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "a") {
|
||||
event.preventDefault();
|
||||
if (editableRef.current) {
|
||||
const range = document.createRange();
|
||||
const selection = window.getSelection();
|
||||
range.selectNodeContents(editableRef.current);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
return;
|
||||
function handleContextMenu(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
e.preventDefault();
|
||||
let menu: ContextMenuItem[] = [];
|
||||
menu.push({ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) });
|
||||
menu.push({ type: "separator" });
|
||||
menu.push({ label: "Close Tab", click: () => onClose(null) });
|
||||
ContextMenuModel.showContextMenu(menu, e);
|
||||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
if (editableRef.current.innerText.trim() === "") {
|
||||
editableRef.current.innerText = originalName;
|
||||
}
|
||||
editableRef.current.blur();
|
||||
} else if (event.key === "Escape") {
|
||||
editableRef.current.innerText = originalName;
|
||||
editableRef.current.blur();
|
||||
} else if (
|
||||
editableRef.current.innerText.length >= 8 &&
|
||||
!["Backspace", "Delete", "ArrowLeft", "ArrowRight"].includes(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadedRef.current) {
|
||||
onLoaded();
|
||||
loadedRef.current = true;
|
||||
}
|
||||
}, [onLoaded]);
|
||||
|
||||
// Prevent drag from being triggered on mousedown
|
||||
const handleMouseDownOnClose = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
function handleContextMenu(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
e.preventDefault();
|
||||
let menu: ContextMenuItem[] = [];
|
||||
menu.push({ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) });
|
||||
menu.push({ type: "separator" });
|
||||
menu.push({ label: "Close Tab", click: () => onClose(null) });
|
||||
ContextMenuModel.showContextMenu(menu, e);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx("tab", { active, isDragging, "before-active": isBeforeActive })}
|
||||
onMouseDown={onDragStart}
|
||||
onClick={onSelect}
|
||||
onContextMenu={handleContextMenu}
|
||||
data-tab-id={id}
|
||||
>
|
||||
{isFirst && <div className="vertical-line first" />}
|
||||
return (
|
||||
<div
|
||||
ref={editableRef}
|
||||
className={clsx("name", { focused: isEditable })}
|
||||
contentEditable={isEditable}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
suppressContentEditableWarning={true}
|
||||
ref={ref}
|
||||
className={clsx("tab", { active, isDragging, "before-active": isBeforeActive })}
|
||||
onMouseDown={onDragStart}
|
||||
onClick={onSelect}
|
||||
onContextMenu={handleContextMenu}
|
||||
data-tab-id={id}
|
||||
>
|
||||
{tabData?.name}
|
||||
{isFirst && <div className="vertical-line first" />}
|
||||
<div
|
||||
ref={editableRef}
|
||||
className={clsx("name", { focused: isEditable })}
|
||||
contentEditable={isEditable}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
suppressContentEditableWarning={true}
|
||||
>
|
||||
{tabData?.name}
|
||||
</div>
|
||||
{!isDragging && <div className="vertical-line" />}
|
||||
{active && <div className="mask" />}
|
||||
<Button className="secondary ghost close" onClick={onClose} onMouseDown={handleMouseDownOnClose}>
|
||||
<i className="fa fa-solid fa-xmark" />
|
||||
</Button>
|
||||
</div>
|
||||
{!isDragging && <div className="vertical-line" />}
|
||||
{active && <div className="mask" />}
|
||||
<Button className="secondary ghost close" onClick={onClose} onMouseDown={handleMouseDownOnClose}>
|
||||
<i className="fa fa-solid fa-xmark" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export { Tab };
|
||||
|
@ -37,7 +37,7 @@ interface TabBarProps {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
const TabBar = ({ workspace }: TabBarProps) => {
|
||||
const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
const [tabIds, setTabIds] = useState<string[]>([]);
|
||||
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
|
||||
const [draggingTab, setDraggingTab] = useState<string>();
|
||||
@ -497,6 +497,6 @@ const TabBar = ({ workspace }: TabBarProps) => {
|
||||
<WindowDrag ref={draggerRightRef} className="right" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { TabBar };
|
||||
|
@ -4,16 +4,17 @@
|
||||
import { Block, BlockFrame } from "@/app/block/block";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
import * as React from "react";
|
||||
|
||||
import { CenteredDiv, CenteredLoadingDiv } from "@/element/quickelems";
|
||||
import { TileLayout } from "@/faraday/index";
|
||||
import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { getApi } from "../store/global";
|
||||
import "./tabcontent.less";
|
||||
|
||||
const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
const TabContent = React.memo(({ tabId }: { tabId: string }) => {
|
||||
const oref = useMemo(() => WOS.makeORef("tab", tabId), [tabId]);
|
||||
const loadingAtom = useMemo(() => WOS.getWaveObjectLoadingAtom(oref), [oref]);
|
||||
const tabLoading = useAtomValue(loadingAtom);
|
||||
@ -21,31 +22,40 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
const layoutStateAtom = useMemo(() => getLayoutStateAtomForTab(tabId, tabAtom), [tabAtom, tabId]);
|
||||
const tabData = useAtomValue(tabAtom);
|
||||
|
||||
const renderBlock = useCallback(
|
||||
(
|
||||
const tileLayoutContents = useMemo(() => {
|
||||
function renderBlock(
|
||||
tabData: TabLayoutData,
|
||||
ready: boolean,
|
||||
onClose: () => void,
|
||||
dragHandleRef: React.RefObject<HTMLDivElement>
|
||||
) => {
|
||||
) {
|
||||
if (!tabData.blockId || !ready) {
|
||||
return null;
|
||||
}
|
||||
return <Block blockId={tabData.blockId} onClose={onClose} dragHandleRef={dragHandleRef} />;
|
||||
},
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<Block
|
||||
key={tabData.blockId}
|
||||
blockId={tabData.blockId}
|
||||
onClose={onClose}
|
||||
dragHandleRef={dragHandleRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderPreview = useCallback((tabData: TabLayoutData) => {
|
||||
return <BlockFrame blockId={tabData.blockId} preview={true} />;
|
||||
}, []);
|
||||
function renderPreview(tabData: TabLayoutData) {
|
||||
return <BlockFrame key={tabData.blockId} blockId={tabData.blockId} preview={true} />;
|
||||
}
|
||||
|
||||
const onNodeDelete = useCallback((data: TabLayoutData) => {
|
||||
return services.ObjectService.DeleteBlock(data.blockId);
|
||||
}, []);
|
||||
function onNodeDelete(data: TabLayoutData) {
|
||||
return services.ObjectService.DeleteBlock(data.blockId);
|
||||
}
|
||||
|
||||
const getCursorPoint = useCallback(() => {
|
||||
return getApi().getCursorPoint();
|
||||
return {
|
||||
renderContent: renderBlock,
|
||||
renderPreview: renderPreview,
|
||||
tabId: tabId,
|
||||
onNodeDelete: onNodeDelete,
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (tabLoading) {
|
||||
@ -68,15 +78,12 @@ const TabContent = ({ tabId }: { tabId: string }) => {
|
||||
<div className="tabcontent">
|
||||
<TileLayout
|
||||
key={tabId}
|
||||
tabId={tabId}
|
||||
renderContent={renderBlock}
|
||||
renderPreview={renderPreview}
|
||||
contents={tileLayoutContents}
|
||||
layoutTreeStateAtom={layoutStateAtom}
|
||||
onNodeDelete={onNodeDelete}
|
||||
getCursorPoint={getCursorPoint}
|
||||
getCursorPoint={getApi().getCursorPoint}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export { TabContent };
|
||||
|
@ -67,6 +67,13 @@
|
||||
&.col-size {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dir-table-lastmod,
|
||||
.dir-table-modestr,
|
||||
.dir-table-size,
|
||||
.dir-table-type {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,37 +158,40 @@ function DirectoryTable({ data, cwd, setFileName }: DirectoryTableProps) {
|
||||
() => [
|
||||
columnHelper.accessor("mimetype", {
|
||||
cell: (info) => <i className={getIconFromMimeType(info.getValue() ?? "")}></i>,
|
||||
header: () => <span></span>,
|
||||
header: () => <span>Type</span>,
|
||||
id: "logo",
|
||||
size: 25,
|
||||
enableSorting: false,
|
||||
}),
|
||||
columnHelper.accessor("path", {
|
||||
cell: (info) => info.getValue(),
|
||||
cell: (info) => <span className="dir-table-path">{info.getValue()}</span>,
|
||||
header: () => <span>Name</span>,
|
||||
sortingFn: "alphanumeric",
|
||||
}),
|
||||
columnHelper.accessor("modestr", {
|
||||
cell: (info) => info.getValue(),
|
||||
cell: (info) => <span className="dir-table-modestr">{info.getValue()}</span>,
|
||||
header: () => <span>Permissions</span>,
|
||||
size: 91,
|
||||
sortingFn: "alphanumeric",
|
||||
}),
|
||||
columnHelper.accessor("modtime", {
|
||||
cell: (info) =>
|
||||
getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format),
|
||||
cell: (info) => (
|
||||
<span className="dir-table-lastmod">
|
||||
{getLastModifiedTime(info.getValue(), settings.datetime.locale, settings.datetime.format)}
|
||||
</span>
|
||||
),
|
||||
header: () => <span>Last Modified</span>,
|
||||
size: 185,
|
||||
sortingFn: "datetime",
|
||||
}),
|
||||
columnHelper.accessor("size", {
|
||||
cell: (info) => getBestUnit(info.getValue()),
|
||||
cell: (info) => <span className="dir-table-size">{getBestUnit(info.getValue())}</span>,
|
||||
header: () => <span>Size</span>,
|
||||
size: 55,
|
||||
sortingFn: "auto",
|
||||
}),
|
||||
columnHelper.accessor("mimetype", {
|
||||
cell: (info) => info.getValue(),
|
||||
cell: (info) => <span className="dir-table-type">{info.getValue()}</span>,
|
||||
header: () => <span>Type</span>,
|
||||
sortingFn: "alphanumeric",
|
||||
}),
|
||||
|
@ -14,7 +14,7 @@ import "./workspace.less";
|
||||
|
||||
const iconRegex = /^[a-z0-9-]+$/;
|
||||
|
||||
function Widgets() {
|
||||
const Widgets = React.memo(() => {
|
||||
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
|
||||
const newWidgetModalVisible = React.useState(false);
|
||||
async function clickTerminal() {
|
||||
@ -92,27 +92,27 @@ function Widgets() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function WorkspaceElem() {
|
||||
const WorkspaceElem = React.memo(() => {
|
||||
const windowData = jotai.useAtomValue(atoms.waveWindow);
|
||||
const activeTabId = windowData?.activetabid;
|
||||
const ws = jotai.useAtomValue(atoms.workspace);
|
||||
return (
|
||||
<div className="workspace">
|
||||
<TabBar workspace={ws} />
|
||||
<TabBar key={ws.oid} workspace={ws} />
|
||||
<div className="workspace-tabcontent">
|
||||
{activeTabId == "" ? (
|
||||
<CenteredDiv>No Active Tab</CenteredDiv>
|
||||
) : (
|
||||
<>
|
||||
<TabContent key={windowData.workspaceid} tabId={activeTabId} />
|
||||
<TabContent key={activeTabId} tabId={activeTabId} />
|
||||
<Widgets />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export { WorkspaceElem as Workspace };
|
||||
|
@ -37,11 +37,11 @@ import {
|
||||
import "./tilelayout.less";
|
||||
import { Dimensions, FlexDirection, setTransform as createTransform, determineDropDirection } from "./utils";
|
||||
|
||||
export interface TileLayoutProps<T> {
|
||||
/**
|
||||
* The atom containing the layout tree state.
|
||||
*/
|
||||
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
||||
/**
|
||||
* contains callbacks and information about the contents (or styling) of of the TileLayout
|
||||
* nothing in here is specific to the TileLayout itself
|
||||
*/
|
||||
export interface TileLayoutContents<T> {
|
||||
/**
|
||||
* A callback that accepts the data from the leaf node and displays the leaf contents to the user.
|
||||
*/
|
||||
@ -64,6 +64,18 @@ export interface TileLayoutProps<T> {
|
||||
* tabId this TileLayout is associated with
|
||||
*/
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export interface TileLayoutProps<T> {
|
||||
/**
|
||||
* The atom containing the layout tree state.
|
||||
*/
|
||||
layoutTreeStateAtom: WritableLayoutTreeStateAtom<T>;
|
||||
|
||||
/**
|
||||
* callbacks and information about the contents (or styling) of the TileLayout or contents
|
||||
*/
|
||||
contents: TileLayoutContents<T>;
|
||||
|
||||
/**
|
||||
* A callback for getting the cursor point in reference to the current window. This removes Electron as a runtime dependency, allowing for better integration with Storybook.
|
||||
@ -75,15 +87,7 @@ export interface TileLayoutProps<T> {
|
||||
const DragPreviewWidth = 300;
|
||||
const DragPreviewHeight = 300;
|
||||
|
||||
export const TileLayout = <T,>({
|
||||
layoutTreeStateAtom,
|
||||
tabId,
|
||||
className,
|
||||
renderContent,
|
||||
renderPreview,
|
||||
onNodeDelete,
|
||||
getCursorPoint,
|
||||
}: TileLayoutProps<T>) => {
|
||||
export const TileLayout = React.memo(<T,>({ layoutTreeStateAtom, contents, getCursorPoint }: TileLayoutProps<T>) => {
|
||||
const overlayContainerRef = useRef<HTMLDivElement>(null);
|
||||
const displayContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -126,7 +130,7 @@ export const TileLayout = <T,>({
|
||||
const [layoutLeafTransforms, setLayoutLeafTransformsRaw] = useState<Record<string, CSSProperties>>({});
|
||||
|
||||
const setLayoutLeafTransforms = (transforms: Record<string, CSSProperties>) => {
|
||||
globalLayoutTransformsMap.set(tabId, transforms);
|
||||
globalLayoutTransformsMap.set(contents.tabId, transforms);
|
||||
setLayoutLeafTransformsRaw(transforms);
|
||||
};
|
||||
|
||||
@ -247,30 +251,23 @@ export const TileLayout = <T,>({
|
||||
// console.log("calling dispatch", deleteAction);
|
||||
dispatch(deleteAction);
|
||||
// console.log("calling onNodeDelete", node);
|
||||
await onNodeDelete?.(node.data);
|
||||
await contents.onNodeDelete?.(node.data);
|
||||
// console.log("node deleted");
|
||||
},
|
||||
[onNodeDelete, dispatch]
|
||||
[contents.onNodeDelete, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<div className={clsx("tile-layout", className, { animate })} onPointerOut={onPointerLeave}>
|
||||
<div className={clsx("tile-layout", contents.className, { animate })} onPointerOut={onPointerLeave}>
|
||||
<div key="display" ref={displayContainerRef} className="display-container">
|
||||
{layoutLeafTransforms &&
|
||||
layoutTreeState.leafs.map((leaf) => {
|
||||
return (
|
||||
<DisplayNode
|
||||
key={leaf.id}
|
||||
layoutNode={leaf}
|
||||
renderContent={renderContent}
|
||||
renderPreview={renderPreview}
|
||||
transform={layoutLeafTransforms[leaf.id]}
|
||||
onLeafClose={onLeafClose}
|
||||
ready={animate}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<DisplayNodesWrapper
|
||||
contents={contents}
|
||||
ready={animate}
|
||||
onLeafClose={onLeafClose}
|
||||
layoutTreeState={layoutTreeState}
|
||||
layoutLeafTransforms={layoutLeafTransforms}
|
||||
/>
|
||||
</div>
|
||||
<Placeholder
|
||||
key="placeholder"
|
||||
@ -296,21 +293,63 @@ export const TileLayout = <T,>({
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
interface DisplayNodesWrapperProps<T> {
|
||||
/**
|
||||
* The layout tree state.
|
||||
*/
|
||||
layoutTreeState: LayoutTreeState<T>;
|
||||
/**
|
||||
* contains callbacks and information about the contents (or styling) of of the TileLayout
|
||||
*/
|
||||
contents: TileLayoutContents<T>;
|
||||
/**
|
||||
* A callback that is called when a leaf node gets closed.
|
||||
* @param node The node that is closed.
|
||||
*/
|
||||
onLeafClose: (node: LayoutNode<T>) => void;
|
||||
/**
|
||||
* A series of CSS properties used to display a leaf node with the correct dimensions and position, as determined from its corresponding OverlayNode.
|
||||
*/
|
||||
layoutLeafTransforms: Record<string, CSSProperties>;
|
||||
/**
|
||||
* Determines whether the leaf nodes are ready to be displayed to the user.
|
||||
*/
|
||||
ready: boolean;
|
||||
}
|
||||
|
||||
const DisplayNodesWrapper = React.memo(
|
||||
<T,>({ layoutTreeState, contents, onLeafClose, layoutLeafTransforms, ready }: DisplayNodesWrapperProps<T>) => {
|
||||
if (!layoutLeafTransforms) {
|
||||
return null;
|
||||
}
|
||||
return layoutTreeState.leafs.map((leaf) => {
|
||||
return (
|
||||
<DisplayNode
|
||||
key={leaf.id}
|
||||
layoutNode={leaf}
|
||||
contents={contents}
|
||||
transform={layoutLeafTransforms[leaf.id]}
|
||||
onLeafClose={onLeafClose}
|
||||
ready={ready}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
interface DisplayNodeProps<T> {
|
||||
/**
|
||||
* The leaf node object, containing the data needed to display the leaf contents to the user.
|
||||
*/
|
||||
layoutNode: LayoutNode<T>;
|
||||
|
||||
/**
|
||||
* A callback that accepts the data from the leaf node and displays the leaf contents to the user.
|
||||
* contains callbacks and information about the contents (or styling) of of the TileLayout
|
||||
*/
|
||||
renderContent: ContentRenderer<T>;
|
||||
/**
|
||||
* A callback that accepts the data from the leaf node and returns a preview that can be shown when the user drags a node.
|
||||
*/
|
||||
renderPreview?: PreviewRenderer<T>;
|
||||
contents: TileLayoutContents<T>;
|
||||
|
||||
/**
|
||||
* A callback that is called when a leaf node gets closed.
|
||||
* @param node The node that is closed.
|
||||
@ -331,14 +370,7 @@ const dragItemType = "TILE_ITEM";
|
||||
/**
|
||||
* The draggable and displayable portion of a leaf node in a layout tree.
|
||||
*/
|
||||
const DisplayNode = <T,>({
|
||||
layoutNode,
|
||||
renderContent,
|
||||
renderPreview,
|
||||
transform,
|
||||
onLeafClose,
|
||||
ready,
|
||||
}: DisplayNodeProps<T>) => {
|
||||
const DisplayNode = React.memo(<T,>({ layoutNode, contents, transform, onLeafClose, ready }: DisplayNodeProps<T>) => {
|
||||
const tileNodeRef = useRef<HTMLDivElement>(null);
|
||||
const dragHandleRef = useRef<HTMLDivElement>(null);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
@ -370,11 +402,11 @@ const DisplayNode = <T,>({
|
||||
transform: `scale(${1 / devicePixelRatio})`,
|
||||
}}
|
||||
>
|
||||
{renderPreview?.(layoutNode.data)}
|
||||
{contents.renderPreview?.(layoutNode.data)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, [renderPreview, devicePixelRatio]);
|
||||
}, [contents.renderPreview, devicePixelRatio]);
|
||||
|
||||
const [previewImage, setPreviewImage] = useState<HTMLImageElement>(null);
|
||||
const [previewImageGeneration, setPreviewImageGeneration] = useState(0);
|
||||
@ -414,7 +446,7 @@ const DisplayNode = <T,>({
|
||||
return (
|
||||
layoutNode.data && (
|
||||
<div key="leaf" className="tile-leaf">
|
||||
{renderContent(layoutNode.data, ready, onClose, dragHandleRef)}
|
||||
{contents.renderContent(layoutNode.data, ready, onClose, dragHandleRef)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@ -436,7 +468,7 @@ const DisplayNode = <T,>({
|
||||
{previewElement}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
interface OverlayNodeProps<T> {
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user