// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { blockViewToIcon, blockViewToName, getBlockHeaderIcon, IconButton, Input } from "@/app/block/blockutil"; import { Button } from "@/app/element/button"; import { ContextMenuModel } from "@/app/store/contextmenu"; import { atoms, globalStore, useBlockAtom, WOS } from "@/app/store/global"; import * as services from "@/app/store/services"; import { MagnifyIcon } from "@/element/magnify"; import { LayoutTreeState } from "@/layout/index"; import { getLayoutStateAtomForTab } from "@/layout/lib/layoutAtom"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import { isBlockMagnified } from "@/util/layoututil"; import * as util from "@/util/util"; import clsx from "clsx"; import * as jotai from "jotai"; import * as React from "react"; import { BlockFrameProps, LayoutComponentModel } from "./blocktypes"; function handleHeaderContextMenu( e: React.MouseEvent, blockData: Block, viewModel: ViewModel, onMagnifyToggle: () => void, onClose: () => void ) { e.preventDefault(); e.stopPropagation(); let menu: ContextMenuItem[] = [ { label: "Magnify Block", 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); } function getViewIconElem(viewIconUnion: string | HeaderIconButton, blockData: Block): JSX.Element { if (viewIconUnion == null || typeof viewIconUnion === "string") { const viewIcon = viewIconUnion as string; return
{getBlockHeaderIcon(viewIcon, blockData)}
; } else { return ; } } const OptMagnifyButton = React.memo( ({ blockData, layoutModel }: { blockData: Block; layoutModel: LayoutComponentModel }) => { const tabId = globalStore.get(atoms.activeTabId); const tabAtom = WOS.getWaveObjectAtom(WOS.makeORef("tab", tabId)); const layoutTreeState = util.useAtomValueSafe(getLayoutStateAtomForTab(tabId, tabAtom)); const isMagnified = isBlockMagnified(layoutTreeState, blockData.oid); const magnifyDecl: HeaderIconButton = { elemtype: "iconbutton", icon: , title: isMagnified ? "Minimize" : "Magnify", click: layoutModel?.onMagnifyToggle, }; return ; } ); function computeEndIcons(blockData: Block, viewModel: ViewModel, layoutModel: LayoutComponentModel): JSX.Element[] { const endIconsElem: JSX.Element[] = []; const endIconButtons = util.useAtomValueSafe(viewModel.endIconButtons); if (endIconButtons && endIconButtons.length > 0) { endIconsElem.push(...endIconButtons.map((button, idx) => )); } const settingsDecl: HeaderIconButton = { elemtype: "iconbutton", icon: "cog", title: "Settings", click: (e) => handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onMagnifyToggle, layoutModel?.onClose), }; endIconsElem.push(); endIconsElem.push(); const closeDecl: HeaderIconButton = { elemtype: "iconbutton", icon: "xmark-large", title: "Close", click: layoutModel?.onClose, }; endIconsElem.push(); return endIconsElem; } const BlockFrame_Header = ({ blockId, layoutModel, viewModel }: BlockFrameProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const viewName = util.useAtomValueSafe(viewModel.viewName) ?? blockViewToName(blockData?.meta?.view); const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view); const preIconButton = util.useAtomValueSafe(viewModel.preIconButton); const headerTextUnion = util.useAtomValueSafe(viewModel.viewText); const endIconsElem = computeEndIcons(blockData, viewModel, layoutModel); const viewIconElem = getViewIconElem(viewIconUnion, blockData); let preIconButtonElem: JSX.Element = null; if (preIconButton) { preIconButtonElem = ; } function handleDoubleClick() { layoutModel?.onMagnifyToggle(); } const headerTextElems: JSX.Element[] = []; if (typeof headerTextUnion === "string") { if (!util.isBlank(headerTextUnion)) { headerTextElems.push(
{headerTextUnion}
); } } else if (Array.isArray(headerTextUnion)) { headerTextElems.push(...renderHeaderElements(headerTextUnion)); } return (
handleHeaderContextMenu(e, blockData, viewModel, layoutModel?.onMagnifyToggle, layoutModel?.onClose) } > {preIconButtonElem}
{viewIconElem}
{viewName}
{settingsConfig?.blockheader?.showblockids && (
[{blockId.substring(0, 8)}]
)}
{headerTextElems}
{endIconsElem}
); }; const HeaderTextElem = React.memo(({ elem }: { elem: HeaderElem }) => { if (elem.elemtype == "iconbutton") { return ; } else if (elem.elemtype == "input") { return ; } else if (elem.elemtype == "text") { return
{elem.text}
; } else if (elem.elemtype == "textbutton") { return ( ); } else if (elem.elemtype == "div") { return (
{elem.children.map((child, childIdx) => ( ))}
); } return null; }); function renderHeaderElements(headerTextUnion: HeaderElem[]): JSX.Element[] { const headerTextElems: JSX.Element[] = []; for (let idx = 0; idx < headerTextUnion.length; idx++) { const elem = headerTextUnion[idx]; const renderedElement = ; if (renderedElement) { headerTextElems.push(renderedElement); } } return headerTextElems; } function BlockNum({ blockId }: { blockId: string }) { const tabId = jotai.useAtomValue(atoms.activeTabId); const tabAtom = WOS.getWaveObjectAtom(WOS.makeORef("tab", tabId)); const layoutTreeState: LayoutTreeState = globalStore.get(getLayoutStateAtomForTab(tabId, tabAtom)); if (!layoutTreeState || !layoutTreeState.leafs) { return null; } for (let idx = 0; idx < layoutTreeState.leafs.length; idx++) { const leaf = layoutTreeState.leafs[idx]; if (leaf?.data?.blockId == blockId) { return String(idx + 1); } } return null; } const BlockMask = ({ blockId, preview, isFocused }: { blockId: string; preview: boolean; isFocused: boolean }) => { const isLayoutMode = jotai.useAtomValue(atoms.cmdShiftDelayAtom); const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const 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"]; } let innerElem = null; if (isLayoutMode) { innerElem = (
); } return (
{innerElem}
); }; const BlockFrame_Default_Component = (props: BlockFrameProps) => { const { blockId, layoutModel, viewModel, blockModel, preview, numBlocksInTab, children } = props; const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const isFocusedAtom = useBlockAtom(blockId, "isFocused", () => { return jotai.atom((get) => { const winData = get(atoms.waveWindow); return winData?.activeblockid === blockId; }); }); const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view); const customBg = util.useAtomValueSafe(viewModel.blockBg); let isFocused = jotai.useAtomValue(isFocusedAtom); if (preview) { isFocused = true; } const viewIconElem = getViewIconElem(viewIconUnion, blockData); function handleKeyDown(e: React.KeyboardEvent) { const waveEvent = adaptFromReactOrNativeKeyEvent(e); if (checkKeyPressed(waveEvent, "Cmd:m")) { e.preventDefault(); layoutModel?.onMagnifyToggle(); return; } } 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 =
{viewIconElem}
; return (
{preview ? previewElem : children}
); }; const BlockFrame_Default = React.memo(BlockFrame_Default_Component) as typeof BlockFrame_Default_Component; const BlockFrame = React.memo((props: BlockFrameProps) => { const blockId = props.blockId; const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const tabData = jotai.useAtomValue(atoms.tabAtom); if (!blockId || !blockData) { return null; } let FrameElem = BlockFrame_Default; const numBlocks = tabData?.blockids?.length ?? 0; return ; }); export { BlockFrame };