2024-08-02 00:35:13 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-08-24 03:12:40 +02:00
|
|
|
import {
|
|
|
|
blockViewToIcon,
|
|
|
|
blockViewToName,
|
|
|
|
ConnectionButton,
|
2024-08-30 23:36:16 +02:00
|
|
|
ControllerStatusIcon,
|
2024-08-24 03:12:40 +02:00
|
|
|
getBlockHeaderIcon,
|
|
|
|
IconButton,
|
|
|
|
Input,
|
|
|
|
} from "@/app/block/blockutil";
|
2024-08-02 00:35:13 +02:00
|
|
|
import { Button } from "@/app/element/button";
|
2024-08-27 01:19:03 +02:00
|
|
|
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
|
2024-08-02 00:35:13 +02:00
|
|
|
import { ContextMenuModel } from "@/app/store/contextmenu";
|
2024-09-05 09:21:08 +02:00
|
|
|
import {
|
|
|
|
atoms,
|
|
|
|
getBlockComponentModel,
|
|
|
|
getConnStatusAtom,
|
2024-09-06 03:54:12 +02:00
|
|
|
getHostName,
|
|
|
|
getUserName,
|
2024-09-05 09:21:08 +02:00
|
|
|
globalStore,
|
|
|
|
useBlockAtom,
|
|
|
|
useSettingsKeyAtom,
|
|
|
|
WOS,
|
|
|
|
} from "@/app/store/global";
|
2024-08-02 00:35:13 +02:00
|
|
|
import * as services from "@/app/store/services";
|
2024-08-27 01:19:03 +02:00
|
|
|
import { WshServer } from "@/app/store/wshserver";
|
2024-08-06 01:13:26 +02:00
|
|
|
import { MagnifyIcon } from "@/element/magnify";
|
2024-08-26 20:56:00 +02:00
|
|
|
import { NodeModel } from "@/layout/index";
|
2024-08-27 01:19:03 +02:00
|
|
|
import * as keyutil from "@/util/keyutil";
|
2024-08-02 00:35:13 +02:00
|
|
|
import * as util from "@/util/util";
|
|
|
|
import clsx from "clsx";
|
|
|
|
import * as jotai from "jotai";
|
|
|
|
import * as React from "react";
|
2024-08-26 20:56:00 +02:00
|
|
|
import { BlockFrameProps } from "./blocktypes";
|
2024-08-02 00:35:13 +02:00
|
|
|
|
|
|
|
function handleHeaderContextMenu(
|
|
|
|
e: React.MouseEvent<HTMLDivElement>,
|
|
|
|
blockData: Block,
|
|
|
|
viewModel: ViewModel,
|
2024-08-26 20:56:00 +02:00
|
|
|
magnified: boolean,
|
2024-08-02 00:35:13 +02:00
|
|
|
onMagnifyToggle: () => void,
|
|
|
|
onClose: () => void
|
|
|
|
) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
let menu: ContextMenuItem[] = [
|
|
|
|
{
|
2024-08-30 23:01:48 +02:00
|
|
|
label: magnified ? "Un-Magnify Block" : "Magnify Block",
|
2024-08-02 00:35:13 +02:00
|
|
|
click: () => {
|
|
|
|
onMagnifyToggle();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: "Move to New Window",
|
|
|
|
click: () => {
|
|
|
|
const currentTabId = globalStore.get(atoms.activeTabId);
|
|
|
|
try {
|
|
|
|
services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid);
|
|
|
|
} catch (e) {
|
|
|
|
console.error("error moving block to new window", e);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ type: "separator" },
|
|
|
|
{
|
|
|
|
label: "Copy BlockId",
|
|
|
|
click: () => {
|
|
|
|
navigator.clipboard.writeText(blockData.oid);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
const extraItems = viewModel?.getSettingsMenuItems?.();
|
|
|
|
if (extraItems && extraItems.length > 0) menu.push({ type: "separator" }, ...extraItems);
|
|
|
|
menu.push(
|
|
|
|
{ type: "separator" },
|
|
|
|
{
|
|
|
|
label: "Close Block",
|
|
|
|
click: onClose,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
ContextMenuModel.showContextMenu(menu, e);
|
|
|
|
}
|
|
|
|
|
2024-08-02 00:51:38 +02:00
|
|
|
function getViewIconElem(viewIconUnion: string | HeaderIconButton, blockData: Block): JSX.Element {
|
2024-08-02 00:35:13 +02:00
|
|
|
if (viewIconUnion == null || typeof viewIconUnion === "string") {
|
|
|
|
const viewIcon = viewIconUnion as string;
|
2024-08-02 00:51:38 +02:00
|
|
|
return <div className="block-frame-view-icon">{getBlockHeaderIcon(viewIcon, blockData)}</div>;
|
2024-08-02 00:35:13 +02:00
|
|
|
} else {
|
2024-08-02 00:51:38 +02:00
|
|
|
return <IconButton decl={viewIconUnion} className="block-frame-view-icon" />;
|
2024-08-02 00:35:13 +02:00
|
|
|
}
|
2024-08-02 00:51:38 +02:00
|
|
|
}
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
const OptMagnifyButton = React.memo(
|
2024-09-03 20:24:45 +02:00
|
|
|
({ magnified, toggleMagnify, disabled }: { magnified: boolean; toggleMagnify: () => void; disabled: boolean }) => {
|
2024-08-26 20:56:00 +02:00
|
|
|
const magnifyDecl: HeaderIconButton = {
|
|
|
|
elemtype: "iconbutton",
|
|
|
|
icon: <MagnifyIcon enabled={magnified} />,
|
|
|
|
title: magnified ? "Minimize" : "Magnify",
|
|
|
|
click: toggleMagnify,
|
2024-09-03 20:24:45 +02:00
|
|
|
disabled,
|
2024-08-26 20:56:00 +02:00
|
|
|
};
|
|
|
|
return <IconButton key="magnify" decl={magnifyDecl} className="block-frame-magnify" />;
|
|
|
|
}
|
|
|
|
);
|
2024-08-02 01:01:11 +02:00
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
function computeEndIcons(
|
|
|
|
viewModel: ViewModel,
|
2024-09-03 20:24:45 +02:00
|
|
|
nodeModel: NodeModel,
|
2024-08-26 20:56:00 +02:00
|
|
|
onContextMenu: (e: React.MouseEvent<HTMLDivElement>) => void
|
|
|
|
): JSX.Element[] {
|
2024-08-02 00:35:13 +02:00
|
|
|
const endIconsElem: JSX.Element[] = [];
|
2024-08-02 00:51:38 +02:00
|
|
|
const endIconButtons = util.useAtomValueSafe(viewModel.endIconButtons);
|
2024-09-03 20:24:45 +02:00
|
|
|
const magnified = jotai.useAtomValue(nodeModel.isMagnified);
|
|
|
|
const numLeafs = jotai.useAtomValue(nodeModel.numLeafs);
|
|
|
|
const magnifyDisabled = numLeafs <= 1;
|
|
|
|
|
2024-08-02 00:35:13 +02:00
|
|
|
if (endIconButtons && endIconButtons.length > 0) {
|
2024-08-05 23:54:33 +02:00
|
|
|
endIconsElem.push(...endIconButtons.map((button, idx) => <IconButton key={idx} decl={button} />));
|
2024-08-02 00:35:13 +02:00
|
|
|
}
|
|
|
|
const settingsDecl: HeaderIconButton = {
|
|
|
|
elemtype: "iconbutton",
|
|
|
|
icon: "cog",
|
|
|
|
title: "Settings",
|
2024-08-26 20:56:00 +02:00
|
|
|
click: onContextMenu,
|
2024-08-02 00:35:13 +02:00
|
|
|
};
|
2024-08-05 23:54:33 +02:00
|
|
|
endIconsElem.push(<IconButton key="settings" decl={settingsDecl} className="block-frame-settings" />);
|
2024-09-03 20:24:45 +02:00
|
|
|
endIconsElem.push(
|
|
|
|
<OptMagnifyButton
|
|
|
|
key="unmagnify"
|
|
|
|
magnified={magnified}
|
|
|
|
toggleMagnify={nodeModel.toggleMagnify}
|
|
|
|
disabled={magnifyDisabled}
|
|
|
|
/>
|
|
|
|
);
|
2024-08-02 00:35:13 +02:00
|
|
|
const closeDecl: HeaderIconButton = {
|
|
|
|
elemtype: "iconbutton",
|
|
|
|
icon: "xmark-large",
|
|
|
|
title: "Close",
|
2024-09-03 20:24:45 +02:00
|
|
|
click: nodeModel.onClose,
|
2024-08-02 00:35:13 +02:00
|
|
|
};
|
2024-08-05 23:54:33 +02:00
|
|
|
endIconsElem.push(<IconButton key="close" decl={closeDecl} className="block-frame-default-close" />);
|
2024-08-02 00:51:38 +02:00
|
|
|
return endIconsElem;
|
|
|
|
}
|
2024-08-02 00:35:13 +02:00
|
|
|
|
2024-08-27 06:03:12 +02:00
|
|
|
const BlockFrame_Header = ({
|
|
|
|
nodeModel,
|
|
|
|
viewModel,
|
|
|
|
preview,
|
2024-08-29 08:47:45 +02:00
|
|
|
connBtnRef,
|
2024-08-27 06:03:12 +02:00
|
|
|
changeConnModalAtom,
|
|
|
|
}: BlockFrameProps & { changeConnModalAtom: jotai.PrimitiveAtom<boolean> }) => {
|
2024-08-26 20:56:00 +02:00
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
2024-08-02 00:51:38 +02:00
|
|
|
const viewName = util.useAtomValueSafe(viewModel.viewName) ?? blockViewToName(blockData?.meta?.view);
|
2024-08-28 03:49:49 +02:00
|
|
|
const showBlockIds = jotai.useAtomValue(useSettingsKeyAtom("blockheader:showblockids"));
|
2024-08-02 00:51:38 +02:00
|
|
|
const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view);
|
|
|
|
const preIconButton = util.useAtomValueSafe(viewModel.preIconButton);
|
|
|
|
const headerTextUnion = util.useAtomValueSafe(viewModel.viewText);
|
2024-08-26 20:56:00 +02:00
|
|
|
const magnified = jotai.useAtomValue(nodeModel.isMagnified);
|
2024-08-27 01:56:37 +02:00
|
|
|
const manageConnection = util.useAtomValueSafe(viewModel.manageConnection);
|
2024-08-26 20:56:00 +02:00
|
|
|
const dragHandleRef = preview ? null : nodeModel.dragHandleRef;
|
|
|
|
|
|
|
|
const onContextMenu = React.useCallback(
|
|
|
|
(e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
handleHeaderContextMenu(e, blockData, viewModel, magnified, nodeModel.toggleMagnify, nodeModel.onClose);
|
|
|
|
},
|
|
|
|
[magnified]
|
|
|
|
);
|
2024-08-02 00:35:13 +02:00
|
|
|
|
2024-09-03 20:24:45 +02:00
|
|
|
const endIconsElem = computeEndIcons(viewModel, nodeModel, onContextMenu);
|
2024-08-02 00:51:38 +02:00
|
|
|
const viewIconElem = getViewIconElem(viewIconUnion, blockData);
|
|
|
|
let preIconButtonElem: JSX.Element = null;
|
|
|
|
if (preIconButton) {
|
|
|
|
preIconButtonElem = <IconButton decl={preIconButton} className="block-frame-preicon-button" />;
|
|
|
|
}
|
2024-08-02 00:35:13 +02:00
|
|
|
|
|
|
|
const headerTextElems: JSX.Element[] = [];
|
|
|
|
if (typeof headerTextUnion === "string") {
|
|
|
|
if (!util.isBlank(headerTextUnion)) {
|
|
|
|
headerTextElems.push(
|
|
|
|
<div key="text" className="block-frame-text">
|
2024-09-05 00:25:25 +02:00
|
|
|
‎{headerTextUnion}
|
2024-08-02 00:35:13 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if (Array.isArray(headerTextUnion)) {
|
2024-08-26 20:56:00 +02:00
|
|
|
headerTextElems.push(...renderHeaderElements(headerTextUnion, preview));
|
2024-08-02 00:35:13 +02:00
|
|
|
}
|
2024-09-03 01:48:10 +02:00
|
|
|
headerTextElems.unshift(<ControllerStatusIcon key="connstatus" blockId={nodeModel.blockId} />);
|
2024-08-02 00:35:13 +02:00
|
|
|
|
2024-08-02 00:51:38 +02:00
|
|
|
return (
|
2024-08-26 20:56:00 +02:00
|
|
|
<div className="block-frame-default-header" ref={dragHandleRef} onContextMenu={onContextMenu}>
|
2024-08-02 00:51:38 +02:00
|
|
|
{preIconButtonElem}
|
|
|
|
<div className="block-frame-default-header-iconview">
|
|
|
|
{viewIconElem}
|
|
|
|
<div className="block-frame-view-type">{viewName}</div>
|
2024-08-28 03:49:49 +02:00
|
|
|
{showBlockIds && <div className="block-frame-blockid">[{nodeModel.blockId.substring(0, 8)}]</div>}
|
2024-08-02 00:51:38 +02:00
|
|
|
</div>
|
2024-09-05 00:25:25 +02:00
|
|
|
{manageConnection && (
|
|
|
|
<ConnectionButton
|
|
|
|
ref={connBtnRef}
|
|
|
|
key="connbutton"
|
|
|
|
connection={blockData?.meta?.connection}
|
|
|
|
changeConnModalAtom={changeConnModalAtom}
|
|
|
|
/>
|
|
|
|
)}
|
2024-08-02 00:51:38 +02:00
|
|
|
<div className="block-frame-textelems-wrapper">{headerTextElems}</div>
|
|
|
|
<div className="block-frame-end-icons">{endIconsElem}</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
const HeaderTextElem = React.memo(({ elem, preview }: { elem: HeaderElem; preview: boolean }) => {
|
2024-08-02 01:01:11 +02:00
|
|
|
if (elem.elemtype == "iconbutton") {
|
|
|
|
return <IconButton decl={elem} className={clsx("block-frame-header-iconbutton", elem.className)} />;
|
|
|
|
} else if (elem.elemtype == "input") {
|
2024-08-26 20:56:00 +02:00
|
|
|
return <Input decl={elem} className={clsx("block-frame-input", elem.className)} preview={preview} />;
|
2024-08-02 01:01:11 +02:00
|
|
|
} else if (elem.elemtype == "text") {
|
2024-08-29 08:47:45 +02:00
|
|
|
return (
|
2024-08-31 20:57:45 +02:00
|
|
|
<div className={clsx("block-frame-text", elem.className)}>
|
|
|
|
<span ref={preview ? null : elem.ref} onClick={() => elem?.onClick()}>
|
2024-09-05 00:25:25 +02:00
|
|
|
‎{elem.text}
|
2024-08-31 20:57:45 +02:00
|
|
|
</span>
|
2024-08-29 08:47:45 +02:00
|
|
|
</div>
|
|
|
|
);
|
2024-08-02 01:01:11 +02:00
|
|
|
} else if (elem.elemtype == "textbutton") {
|
|
|
|
return (
|
|
|
|
<Button className={elem.className} onClick={(e) => elem.onClick(e)}>
|
|
|
|
{elem.text}
|
|
|
|
</Button>
|
|
|
|
);
|
|
|
|
} else if (elem.elemtype == "div") {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={clsx("block-frame-div", elem.className)}
|
|
|
|
onMouseOver={elem.onMouseOver}
|
|
|
|
onMouseOut={elem.onMouseOut}
|
|
|
|
>
|
|
|
|
{elem.children.map((child, childIdx) => (
|
2024-08-26 20:56:00 +02:00
|
|
|
<HeaderTextElem elem={child} key={childIdx} preview={preview} />
|
2024-08-02 01:01:11 +02:00
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
);
|
2024-08-02 00:51:38 +02:00
|
|
|
}
|
2024-08-02 01:01:11 +02:00
|
|
|
return null;
|
|
|
|
});
|
2024-08-02 00:51:38 +02:00
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
function renderHeaderElements(headerTextUnion: HeaderElem[], preview: boolean): JSX.Element[] {
|
2024-08-02 01:01:11 +02:00
|
|
|
const headerTextElems: JSX.Element[] = [];
|
2024-08-02 00:51:38 +02:00
|
|
|
for (let idx = 0; idx < headerTextUnion.length; idx++) {
|
|
|
|
const elem = headerTextUnion[idx];
|
2024-08-26 20:56:00 +02:00
|
|
|
const renderedElement = <HeaderTextElem elem={elem} key={idx} preview={preview} />;
|
2024-08-02 00:51:38 +02:00
|
|
|
if (renderedElement) {
|
|
|
|
headerTextElems.push(renderedElement);
|
|
|
|
}
|
2024-08-02 00:35:13 +02:00
|
|
|
}
|
2024-08-02 00:51:38 +02:00
|
|
|
return headerTextElems;
|
|
|
|
}
|
|
|
|
|
2024-09-05 09:21:08 +02:00
|
|
|
const ConnStatusOverlay = React.memo(
|
|
|
|
({
|
|
|
|
nodeModel,
|
|
|
|
viewModel,
|
|
|
|
changeConnModalAtom,
|
|
|
|
}: {
|
|
|
|
nodeModel: NodeModel;
|
|
|
|
viewModel: ViewModel;
|
|
|
|
changeConnModalAtom: jotai.PrimitiveAtom<boolean>;
|
|
|
|
}) => {
|
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
|
|
|
const [connModalOpen, setConnModalOpen] = jotai.useAtom(changeConnModalAtom);
|
|
|
|
const connName = blockData.meta?.connection;
|
|
|
|
const connStatus = jotai.useAtomValue(getConnStatusAtom(connName));
|
|
|
|
const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
|
|
|
|
const handleTryReconnect = React.useCallback(() => {
|
|
|
|
const prtn = WshServer.ConnConnectCommand(connName, { timeout: 60000 });
|
|
|
|
prtn.catch((e) => console.log("error reconnecting", connName, e));
|
|
|
|
}, [connName]);
|
|
|
|
const handleSwitchConnection = React.useCallback(() => {
|
|
|
|
setConnModalOpen(true);
|
|
|
|
}, [setConnModalOpen]);
|
|
|
|
if (isLayoutMode || connStatus.status == "connected" || connModalOpen) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let statusText = `Disconnected from "${connName}"`;
|
|
|
|
let showReconnect = true;
|
|
|
|
if (connStatus.status == "connecting") {
|
|
|
|
statusText = `Connecting to "${connName}"...`;
|
|
|
|
showReconnect = false;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="connstatus-overlay">
|
|
|
|
<div className="connstatus-mainelem">
|
|
|
|
<div style={{ marginBottom: 5 }}>{statusText}</div>
|
|
|
|
{!util.isBlank(connStatus.error) ? (
|
|
|
|
<div className="connstatus-error">error: {connStatus.error}</div>
|
|
|
|
) : null}
|
|
|
|
{showReconnect ? (
|
|
|
|
<div className="connstatus-actions">
|
|
|
|
<Button className="secondary" onClick={handleTryReconnect}>
|
|
|
|
<i className="fa-sharp fa-solid fa-arrow-right-arrow-left" style={{ marginRight: 5 }} />
|
|
|
|
Reconnect Now
|
|
|
|
</Button>
|
|
|
|
<Button className="secondary" onClick={handleSwitchConnection}>
|
|
|
|
<i className="fa-sharp fa-solid fa-arrow-right-arrow-left" style={{ marginRight: 5 }} />
|
|
|
|
Switch Connection
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
|
2024-08-26 20:56:00 +02:00
|
|
|
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
|
|
|
|
const blockNum = jotai.useAtomValue(nodeModel.blockNum);
|
2024-08-14 23:38:02 +02:00
|
|
|
const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
|
2024-08-26 20:56:00 +02:00
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
2024-08-03 00:39:22 +02:00
|
|
|
const style: React.CSSProperties = {};
|
2024-09-05 09:21:08 +02:00
|
|
|
let showBlockMask = false;
|
|
|
|
|
2024-08-03 00:39:22 +02:00
|
|
|
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"];
|
|
|
|
}
|
|
|
|
let innerElem = null;
|
|
|
|
if (isLayoutMode) {
|
2024-09-05 09:21:08 +02:00
|
|
|
showBlockMask = true;
|
2024-08-03 00:39:22 +02:00
|
|
|
innerElem = (
|
|
|
|
<div className="block-mask-inner">
|
2024-08-26 20:56:00 +02:00
|
|
|
<div className="bignum">{blockNum}</div>
|
2024-08-03 00:39:22 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
2024-09-05 09:21:08 +02:00
|
|
|
<div className={clsx("block-mask", { "show-block-mask": showBlockMask })} style={style}>
|
2024-08-03 00:39:22 +02:00
|
|
|
{innerElem}
|
|
|
|
</div>
|
|
|
|
);
|
2024-09-05 09:21:08 +02:00
|
|
|
});
|
2024-08-03 00:39:22 +02:00
|
|
|
|
2024-08-02 00:51:38 +02:00
|
|
|
const BlockFrame_Default_Component = (props: BlockFrameProps) => {
|
2024-08-26 20:56:00 +02:00
|
|
|
const { nodeModel, viewModel, blockModel, preview, numBlocksInTab, children } = props;
|
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
|
|
|
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
|
2024-08-02 00:51:38 +02:00
|
|
|
const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view);
|
|
|
|
const customBg = util.useAtomValueSafe(viewModel.blockBg);
|
2024-09-05 09:21:08 +02:00
|
|
|
const manageConnection = util.useAtomValueSafe(viewModel.manageConnection);
|
2024-08-27 06:03:12 +02:00
|
|
|
const changeConnModalAtom = useBlockAtom(nodeModel.blockId, "changeConn", () => {
|
|
|
|
return jotai.atom(false);
|
|
|
|
}) as jotai.PrimitiveAtom<boolean>;
|
2024-08-29 08:47:45 +02:00
|
|
|
const connBtnRef = React.useRef<HTMLDivElement>();
|
2024-09-05 09:21:08 +02:00
|
|
|
React.useEffect(() => {
|
|
|
|
if (!manageConnection) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const bcm = getBlockComponentModel(nodeModel.blockId);
|
|
|
|
if (bcm != null) {
|
|
|
|
bcm.openSwitchConnection = () => {
|
|
|
|
globalStore.set(changeConnModalAtom, true);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return () => {
|
|
|
|
const bcm = getBlockComponentModel(nodeModel.blockId);
|
|
|
|
if (bcm != null) {
|
|
|
|
bcm.openSwitchConnection = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}, [manageConnection]);
|
|
|
|
React.useEffect(() => {
|
|
|
|
// on mount, if manageConnection, call ConnEnsure
|
|
|
|
if (!manageConnection || blockData == null || preview) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const connName = blockData?.meta?.connection;
|
|
|
|
if (!util.isBlank(connName)) {
|
|
|
|
console.log("ensure conn", nodeModel.blockId, connName);
|
|
|
|
WshServer.ConnEnsureCommand(connName, { timeout: 60000 }).catch((e) => {
|
|
|
|
console.log("error ensuring connection", nodeModel.blockId, connName, e);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, [manageConnection, blockData]);
|
|
|
|
|
2024-08-02 00:51:38 +02:00
|
|
|
const viewIconElem = getViewIconElem(viewIconUnion, blockData);
|
2024-08-02 00:35:13 +02:00
|
|
|
const innerStyle: React.CSSProperties = {};
|
|
|
|
if (!preview && customBg?.bg != null) {
|
|
|
|
innerStyle.background = customBg.bg;
|
|
|
|
if (customBg["bg:opacity"] != null) {
|
|
|
|
innerStyle.opacity = customBg["bg:opacity"];
|
|
|
|
}
|
|
|
|
if (customBg["bg:blendmode"] != null) {
|
|
|
|
innerStyle.backgroundBlendMode = customBg["bg:blendmode"];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const previewElem = <div className="block-frame-preview">{viewIconElem}</div>;
|
|
|
|
return (
|
|
|
|
<div
|
2024-08-26 20:56:00 +02:00
|
|
|
className={clsx("block", "block-frame-default", "block-" + nodeModel.blockId, {
|
|
|
|
"block-focused": isFocused || preview,
|
|
|
|
"block-preview": preview,
|
|
|
|
"block-no-highlight": numBlocksInTab === 1,
|
|
|
|
})}
|
2024-08-30 01:06:15 +02:00
|
|
|
data-blockid={nodeModel.blockId}
|
2024-08-02 00:35:13 +02:00
|
|
|
onClick={blockModel?.onClick}
|
|
|
|
onFocusCapture={blockModel?.onFocusCapture}
|
|
|
|
ref={blockModel?.blockRef}
|
|
|
|
>
|
2024-08-26 20:56:00 +02:00
|
|
|
<BlockMask nodeModel={nodeModel} />
|
2024-09-05 09:21:08 +02:00
|
|
|
<ConnStatusOverlay nodeModel={nodeModel} viewModel={viewModel} changeConnModalAtom={changeConnModalAtom} />
|
2024-08-29 08:47:45 +02:00
|
|
|
<div className="block-frame-default-inner" style={innerStyle}>
|
|
|
|
<BlockFrame_Header {...props} connBtnRef={connBtnRef} changeConnModalAtom={changeConnModalAtom} />
|
|
|
|
{preview ? previewElem : children}
|
|
|
|
</div>
|
2024-08-27 01:19:03 +02:00
|
|
|
{preview ? null : (
|
|
|
|
<ChangeConnectionBlockModal
|
|
|
|
blockId={nodeModel.blockId}
|
2024-09-03 01:48:10 +02:00
|
|
|
nodeModel={nodeModel}
|
2024-08-27 01:19:03 +02:00
|
|
|
viewModel={viewModel}
|
|
|
|
blockRef={blockModel?.blockRef}
|
2024-08-27 06:03:12 +02:00
|
|
|
changeConnModalAtom={changeConnModalAtom}
|
2024-08-29 08:47:45 +02:00
|
|
|
connBtnRef={connBtnRef}
|
2024-08-27 01:19:03 +02:00
|
|
|
/>
|
|
|
|
)}
|
2024-08-02 00:35:13 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-08-27 01:19:03 +02:00
|
|
|
const ChangeConnectionBlockModal = React.memo(
|
|
|
|
({
|
|
|
|
blockId,
|
|
|
|
viewModel,
|
|
|
|
blockRef,
|
2024-08-29 08:47:45 +02:00
|
|
|
connBtnRef,
|
2024-08-27 06:03:12 +02:00
|
|
|
changeConnModalAtom,
|
2024-09-03 01:48:10 +02:00
|
|
|
nodeModel,
|
2024-08-27 01:19:03 +02:00
|
|
|
}: {
|
|
|
|
blockId: string;
|
|
|
|
viewModel: ViewModel;
|
|
|
|
blockRef: React.RefObject<HTMLDivElement>;
|
2024-08-29 08:47:45 +02:00
|
|
|
connBtnRef: React.RefObject<HTMLDivElement>;
|
2024-08-27 06:03:12 +02:00
|
|
|
changeConnModalAtom: jotai.PrimitiveAtom<boolean>;
|
2024-09-03 01:48:10 +02:00
|
|
|
nodeModel: NodeModel;
|
2024-08-27 01:19:03 +02:00
|
|
|
}) => {
|
|
|
|
const [connSelected, setConnSelected] = React.useState("");
|
2024-08-27 06:03:12 +02:00
|
|
|
const changeConnModalOpen = jotai.useAtomValue(changeConnModalAtom);
|
2024-09-02 21:34:49 +02:00
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
2024-09-03 01:48:10 +02:00
|
|
|
const isNodeFocused = jotai.useAtomValue(nodeModel.isFocused);
|
2024-09-06 04:25:52 +02:00
|
|
|
const connection = blockData?.meta?.connection;
|
2024-09-06 02:02:44 +02:00
|
|
|
const connStatusAtom = getConnStatusAtom(connection);
|
|
|
|
const connStatus = jotai.useAtomValue(connStatusAtom);
|
2024-09-06 07:15:14 +02:00
|
|
|
const [connList, setConnList] = React.useState<Array<string>>([]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (!changeConnModalOpen) {
|
|
|
|
setConnList([]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const prtn = WshServer.ConnListCommand({ timeout: 2000 });
|
|
|
|
prtn.then((connList) => {
|
|
|
|
setConnList(connList ?? []);
|
|
|
|
}).catch((e) => console.log("unable to load conn list from backend. using blank list: ", e));
|
|
|
|
}, [changeConnModalOpen]);
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
console.log("connSelected is: ", connSelected);
|
|
|
|
}, [connSelected]);
|
|
|
|
|
2024-08-27 01:19:03 +02:00
|
|
|
const changeConnection = React.useCallback(
|
|
|
|
async (connName: string) => {
|
2024-09-05 09:21:08 +02:00
|
|
|
if (connName == "") {
|
|
|
|
connName = null;
|
|
|
|
}
|
|
|
|
if (connName == blockData?.meta?.connection) {
|
|
|
|
return;
|
|
|
|
}
|
2024-09-02 21:34:49 +02:00
|
|
|
const oldCwd = blockData?.meta?.file ?? "";
|
|
|
|
let newCwd: string;
|
|
|
|
if (oldCwd == "") {
|
|
|
|
newCwd = "";
|
|
|
|
} else {
|
|
|
|
newCwd = "~";
|
|
|
|
}
|
2024-08-27 01:19:03 +02:00
|
|
|
await WshServer.SetMetaCommand({
|
|
|
|
oref: WOS.makeORef("block", blockId),
|
2024-09-02 21:34:49 +02:00
|
|
|
meta: { connection: connName, file: newCwd },
|
2024-08-27 01:19:03 +02:00
|
|
|
});
|
2024-09-05 09:21:08 +02:00
|
|
|
const tabId = globalStore.get(atoms.activeTabId);
|
|
|
|
try {
|
|
|
|
await WshServer.ConnEnsureCommand(connName, { timeout: 60000 });
|
|
|
|
} catch (e) {
|
|
|
|
console.log("error connecting", blockId, connName, e);
|
|
|
|
}
|
2024-08-27 01:19:03 +02:00
|
|
|
},
|
2024-09-05 09:21:08 +02:00
|
|
|
[blockId, blockData]
|
2024-08-27 01:19:03 +02:00
|
|
|
);
|
2024-09-06 02:02:44 +02:00
|
|
|
|
2024-09-06 07:15:14 +02:00
|
|
|
let createNew: boolean = true;
|
|
|
|
let showLocal: boolean = true;
|
|
|
|
let showReconnect: boolean = true;
|
|
|
|
if (connSelected == "") {
|
|
|
|
createNew = false;
|
|
|
|
} else {
|
|
|
|
showLocal = false;
|
|
|
|
showReconnect = false;
|
|
|
|
}
|
|
|
|
const filteredList: Array<string> = [];
|
|
|
|
for (const conn of connList) {
|
|
|
|
if (conn === connSelected) {
|
|
|
|
createNew = false;
|
|
|
|
}
|
|
|
|
if (conn.includes(connSelected)) {
|
|
|
|
filteredList.push(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// priority handles special suggestions when necessary
|
|
|
|
// for instance, when reconnecting
|
|
|
|
const newConnectionSuggestion: SuggestionConnectionItem = {
|
|
|
|
status: "connected",
|
|
|
|
icon: "plus",
|
|
|
|
iconColor: "var(--conn-icon-color)",
|
|
|
|
label: `${connSelected} (New Connection)`,
|
|
|
|
value: "",
|
|
|
|
onSelect: (_: string) => {
|
|
|
|
changeConnection(connSelected);
|
|
|
|
globalStore.set(changeConnModalAtom, false);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const reconnectSuggestion: SuggestionConnectionItem = {
|
|
|
|
status: "connected",
|
|
|
|
icon: "arrow-right-arrow-left",
|
|
|
|
iconColor: "var(--conn-icon-color)",
|
|
|
|
label: `Reconnect to ${connStatus.connection}`,
|
|
|
|
value: "",
|
|
|
|
onSelect: async (_: string) => {
|
|
|
|
const prtn = WshServer.ConnConnectCommand(connStatus.connection, { timeout: 60000 });
|
|
|
|
prtn.catch((e) => console.log("error reconnecting", connStatus.connection, e));
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const priorityItems: Array<SuggestionConnectionItem> = [];
|
|
|
|
if (createNew) {
|
|
|
|
console.log("added to priority items");
|
|
|
|
priorityItems.push(newConnectionSuggestion);
|
|
|
|
}
|
|
|
|
if (showReconnect && (connStatus.status == "disconnected" || connStatus.status == "error")) {
|
|
|
|
priorityItems.push(reconnectSuggestion);
|
|
|
|
}
|
|
|
|
const prioritySuggestions: SuggestionConnectionScope = {
|
|
|
|
headerText: "",
|
|
|
|
items: priorityItems,
|
|
|
|
};
|
|
|
|
const localName = getUserName() + "@" + getHostName();
|
|
|
|
const localSuggestion: SuggestionConnectionScope = {
|
|
|
|
headerText: "Local",
|
|
|
|
items: [],
|
|
|
|
};
|
|
|
|
if (showLocal) {
|
|
|
|
localSuggestion.items.push({
|
|
|
|
status: "connected",
|
|
|
|
icon: "laptop",
|
|
|
|
iconColor: "var(--grey-text-color)",
|
|
|
|
value: "",
|
|
|
|
label: localName,
|
|
|
|
// TODO: need to specify user name and host name
|
|
|
|
onSelect: (_: string) => {
|
|
|
|
changeConnection("");
|
|
|
|
globalStore.set(changeConnModalAtom, false);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const remoteItems = filteredList.map((connName) => {
|
|
|
|
const item: SuggestionConnectionItem = {
|
|
|
|
status: "connected",
|
|
|
|
icon: "arrow-right-arrow-left",
|
|
|
|
iconColor: "var(--conn-icon-color)",
|
|
|
|
value: connName,
|
|
|
|
label: connName,
|
2024-09-06 02:02:44 +02:00
|
|
|
};
|
2024-09-06 07:15:14 +02:00
|
|
|
return item;
|
|
|
|
});
|
|
|
|
const remoteSuggestions: SuggestionConnectionScope = {
|
|
|
|
headerText: "Remote",
|
|
|
|
items: remoteItems,
|
|
|
|
};
|
|
|
|
|
|
|
|
let suggestions: Array<SuggestionsType> = [];
|
|
|
|
if (prioritySuggestions.items.length > 0) {
|
|
|
|
suggestions.push(prioritySuggestions);
|
|
|
|
}
|
|
|
|
if (localSuggestion.items.length > 0) {
|
|
|
|
suggestions.push(localSuggestion);
|
|
|
|
}
|
|
|
|
if (remoteSuggestions.items.length > 0) {
|
|
|
|
suggestions.push(remoteSuggestions);
|
|
|
|
}
|
|
|
|
|
2024-08-27 01:19:03 +02:00
|
|
|
const handleTypeAheadKeyDown = React.useCallback(
|
|
|
|
(waveEvent: WaveKeyboardEvent): boolean => {
|
|
|
|
if (keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
|
|
|
changeConnection(connSelected);
|
2024-08-27 06:03:12 +02:00
|
|
|
globalStore.set(changeConnModalAtom, false);
|
2024-08-27 01:19:03 +02:00
|
|
|
setConnSelected("");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (keyutil.checkKeyPressed(waveEvent, "Escape")) {
|
2024-08-27 06:03:12 +02:00
|
|
|
globalStore.set(changeConnModalAtom, false);
|
2024-08-27 01:19:03 +02:00
|
|
|
setConnSelected("");
|
|
|
|
viewModel.giveFocus();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
},
|
2024-08-27 06:03:12 +02:00
|
|
|
[changeConnModalAtom, viewModel, blockId, connSelected]
|
2024-08-27 01:19:03 +02:00
|
|
|
);
|
2024-09-06 07:15:14 +02:00
|
|
|
|
2024-08-27 06:03:12 +02:00
|
|
|
if (!changeConnModalOpen) {
|
2024-08-27 01:19:03 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<TypeAheadModal
|
2024-08-29 08:47:45 +02:00
|
|
|
blockRef={blockRef}
|
|
|
|
anchorRef={connBtnRef}
|
2024-09-06 02:02:44 +02:00
|
|
|
suggestions={suggestions}
|
2024-08-27 01:19:03 +02:00
|
|
|
onSelect={(selected: string) => {
|
|
|
|
changeConnection(selected);
|
2024-08-29 08:47:45 +02:00
|
|
|
globalStore.set(changeConnModalAtom, false);
|
2024-08-27 01:19:03 +02:00
|
|
|
}}
|
2024-09-03 01:48:10 +02:00
|
|
|
autoFocus={isNodeFocused}
|
2024-08-27 01:19:03 +02:00
|
|
|
onKeyDown={(e) => keyutil.keydownWrapper(handleTypeAheadKeyDown)(e)}
|
|
|
|
onChange={(current: string) => setConnSelected(current)}
|
|
|
|
value={connSelected}
|
2024-09-06 03:54:12 +02:00
|
|
|
label="Connect to (username@host)..."
|
2024-08-29 08:47:45 +02:00
|
|
|
onClickBackdrop={() => globalStore.set(changeConnModalAtom, false)}
|
2024-08-27 01:19:03 +02:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2024-08-02 00:35:13 +02:00
|
|
|
const BlockFrame_Default = React.memo(BlockFrame_Default_Component) as typeof BlockFrame_Default_Component;
|
|
|
|
|
|
|
|
const BlockFrame = React.memo((props: BlockFrameProps) => {
|
2024-08-26 20:56:00 +02:00
|
|
|
const blockId = props.nodeModel.blockId;
|
2024-08-02 00:35:13 +02:00
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
|
|
|
const tabData = jotai.useAtomValue(atoms.tabAtom);
|
|
|
|
|
|
|
|
if (!blockId || !blockData) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-08-27 22:41:36 +02:00
|
|
|
const FrameElem = BlockFrame_Default;
|
2024-08-02 00:35:13 +02:00
|
|
|
const numBlocks = tabData?.blockids?.length ?? 0;
|
|
|
|
return <FrameElem {...props} numBlocksInTab={numBlocks} />;
|
|
|
|
});
|
|
|
|
|
|
|
|
export { BlockFrame };
|