diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index 7ebf9ac8b..c1248e598 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -11,6 +11,8 @@ overflow: hidden; min-height: 0; position: relative; + border-radius: 8px; + background-color: rgba(255, 255, 255, 0.1); .block-frame-icon { margin-right: 0.5em; @@ -53,6 +55,88 @@ } } + &.block-frame-default { + border: 2px solid transparent; + + &.block-focused { + border: 2px solid var(--accent-color); + + &.block-no-highlight, + &.block-preview { + border: 2px solid transparent; + } + } + + .block-frame-default-header { + display: flex; + height: 26px; + padding-left: 6px; + align-items: center; + gap: 8px; + align-self: stretch; + font: var(--header-font); + background-color: #262626; + border-radius: 8px 8px 0 0; + + .block-frame-default-header-iconview { + display: flex; + align-items: center; + gap: 8px; + color: var(--main-text-color); + + .block-frame-view-icon { + font-size: var(--header-icon-size); + opacity: 0.5; + width: var(--header-icon-width); + i { + margin-right: 0; + } + } + + .block-frame-view-type { + line-height: 12px; + font-weight: 700; + } + + .block-frame-blockid { + opacity: 0.5; + } + } + + .block-frame-end-icons { + display: flex; + align-items: center; + + .block-frame-settings { + display: flex; + width: 24px; + padding: 6px; + justify-content: center; + align-items: center; + opacity: 0.5; + + &:hover { + opacity: 1; + } + } + + .block-frame-default-close { + display: flex; + justify-content: center; + align-items: center; + opacity: 0.5; + font-size: 11px; + width: 24px; + padding: 4px 6px; + + &:hover { + opacity: 1; + } + } + } + } + } + &.block-frame-tech { border: 2px solid var(--border-color); border-radius: 7px; @@ -62,6 +146,11 @@ width: 100%; overflow: visible; + .block-header-icon { + font-size: 14px; + padding: 0 0; + } + &.block-preview { background-color: var(--main-bg-color); diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index e0f53a8c8..9bd2dd73a 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -95,6 +95,26 @@ function processTitleString(titleString: string): React.ReactNode[] { return partsStack[0]; } +function getBlockHeaderIcon(blockIcon: string, blockData: Block): React.ReactNode { + let blockIconElem: React.ReactNode = null; + if (util.isBlank(blockIcon)) { + blockIcon = "square"; + } + let iconColor = blockData?.meta?.["icon:color"]; + if (iconColor && !iconColor.match(colorRegex)) { + iconColor = null; + } + let iconStyle = null; + if (!util.isBlank(iconColor)) { + iconStyle = { color: iconColor }; + } + const iconClass = util.makeIconClass(blockIcon, true); + if (iconClass != null) { + blockIconElem = ; + } + return blockIconElem; +} + function getBlockHeaderText(blockIcon: string, blockData: Block, settings: SettingsConfigType): React.ReactNode { if (!blockData) { return "no block data"; @@ -103,21 +123,7 @@ function getBlockHeaderText(blockIcon: string, blockData: Block, settings: Setti if (settings?.blockheader?.showblockids) { blockIdStr = ` [${blockData.oid.substring(0, 8)}]`; } - let blockIconElem: React.ReactNode = null; - if (!util.isBlank(blockIcon)) { - let iconColor = blockData?.meta?.["icon:color"]; - if (iconColor && !iconColor.match(colorRegex)) { - iconColor = null; - } - let iconStyle = null; - if (!util.isBlank(iconColor)) { - iconStyle = { color: iconColor }; - } - const iconClass = util.makeIconClass(blockIcon, false); - if (iconClass != null) { - blockIconElem = ; - } - } + let blockIconElem = getBlockHeaderIcon(blockIcon, blockData); if (!util.isBlank(blockData?.meta?.title)) { try { const rtn = processTitleString(blockData.meta.title) ?? []; @@ -207,6 +213,7 @@ interface BlockFrameProps { onClick?: () => void; onFocusCapture?: React.FocusEventHandler; preview: boolean; + numBlocksInTab?: number; children?: React.ReactNode; blockRef?: React.RefObject; dragHandleRef?: React.RefObject; @@ -272,6 +279,117 @@ const BlockFrame_Tech_Component = ({ const BlockFrame_Tech = React.memo(BlockFrame_Tech_Component) as typeof BlockFrame_Tech_Component; +const BlockFrame_Default_Component = ({ + blockId, + onClose, + onClick, + preview, + blockRef, + dragHandleRef, + numBlocksInTab, + onFocusCapture, + children, +}: BlockFrameProps) => { + const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); + const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); + const isFocusedAtom = useBlockAtom(blockId, "isFocused", () => { + return jotai.atom((get) => { + const winData = get(atoms.waveWindow); + return winData?.activeblockid === 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"]; + } + let handleSettings = (e: React.MouseEvent) => { + let menuItems: ContextMenuItem[] = []; + menuItems.push({ + label: "Focus Block", + click: () => { + alert("Not Implemented"); + }, + }); + menuItems.push({ label: "Minimize" }); + menuItems.push({ type: "separator" }); + menuItems.push({ + label: "Move to New Window", + click: () => { + let currentTabId = globalStore.get(atoms.activeTabId); + try { + services.WindowService.MoveBlockToNewWindow(currentTabId, blockData.oid); + } catch (e) { + console.error("error moving block to new window", e); + } + }, + }); + menuItems.push({ type: "separator" }); + menuItems.push({ + label: "Copy BlockId", + click: () => { + navigator.clipboard.writeText(blockData.oid); + }, + }); + menuItems.push({ type: "separator" }); + menuItems.push({ label: "Close", click: onClose }); + ContextMenuModel.showContextMenu(menuItems, e); + }; + if (preview) { + handleSettings = null; + } + + return ( +
+
handleHeaderContextMenu(e, blockData, onClose)} + > +
+
{getBlockHeaderIcon(blockIcon, blockData)}
+
{blockViewToName(blockData?.view)}
+ {settingsConfig?.blockheader?.showblockids && ( +
[{blockId.substring(0, 8)}]
+ )} +
+
+
+
+ +
+
+ +
+
+
+ + {preview ?
: children} +
+ ); +}; + +const BlockFrame_Default = React.memo(BlockFrame_Default_Component) as typeof BlockFrame_Default_Component; + const BlockFrame_Frameless = ({ blockId, onClose, @@ -354,27 +472,27 @@ const BlockFrame = React.memo((props: BlockFrameProps) => { if (!blockId || !blockData) { return null; } - let FrameElem = BlockFrame_Tech; + let FrameElem = BlockFrame_Default; + const numBlocks = tabData?.blockids?.length ?? 0; // Preview should always render as the full tech frame if (!props.preview) { // if 0 or 1 blocks, use frameless, otherwise use tech - const numBlocks = tabData?.blockids?.length ?? 0; - if (numBlocks <= 1) { - FrameElem = BlockFrame_Frameless; - } + // if (numBlocks <= 1) { + // FrameElem = BlockFrame_Frameless; + // } if (blockData?.meta?.["frame"] === "tech") { FrameElem = BlockFrame_Tech; } else if (blockData?.meta?.["frame"] === "frameless") { FrameElem = BlockFrame_Frameless; } } - return ; + return ; }); function blockViewToIcon(view: string): string { console.log("blockViewToIcon", view); if (view == "term") { - return "square-terminal"; + return "terminal"; } if (view == "preview") { return "file"; @@ -388,6 +506,22 @@ function blockViewToIcon(view: string): string { return null; } +function blockViewToName(view: string): string { + if (view == "term") { + return "Terminal"; + } + if (view == "preview") { + return "Preview"; + } + if (view == "web") { + return "Web"; + } + if (view == "waveai") { + return "WaveAI"; + } + return view; +} + function useBlockIcon(blockId: string): string { const blockIconOverrideAtom = useBlockAtom(blockId, "blockicon:override", () => { return jotai.atom(null); diff --git a/frontend/app/theme.less b/frontend/app/theme.less index 2185491b5..7becf3120 100644 --- a/frontend/app/theme.less +++ b/frontend/app/theme.less @@ -8,7 +8,7 @@ --grey-text-color: #666; --main-bg-color: #000000; --border-color: #333333; - --base-font: normal 15px / normal "Inter", sans-serif; + --base-font: normal 14px / normal "Inter", sans-serif; --fixed-font: normal 12px / normal "Hack", monospace; --accent-color: rgb(88, 193, 66); --panel-bg-color: rgba(31, 33, 31, 1); @@ -25,6 +25,10 @@ --scrollbar-thumb-hover-color: rgba(255, 255, 255, 0.5); --scrollbar-thumb-active-color: rgba(255, 255, 255, 0.6); + --header-font: 700 11px / normal "Inter", sans-serif; + --header-icon-size: 14px; + --header-icon-width: 16px; + --tab-green: rgb(88, 193, 66); /* z-index values */ diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 66c0ba591..06812d538 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -262,10 +262,10 @@ const TerminalView = ({ blockId }: { blockId: string }) => { shellProcStatusRef.current = status; if (status == "running") { termRef.current?.setIsRunning(true); - globalStore.set(blockIconOverrideAtom, "square-terminal"); + globalStore.set(blockIconOverrideAtom, "terminal"); } else { termRef.current?.setIsRunning(false); - globalStore.set(blockIconOverrideAtom, "regular@square-terminal"); + globalStore.set(blockIconOverrideAtom, "regular@terminal"); } } const initialRTStatus = services.BlockService.GetControllerStatus(blockId);