From e7550c0a3eaf4bd5c4a035b9383ed9caf0f452ff Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 21 Jun 2024 15:15:38 -0700 Subject: [PATCH] add block icon --- frontend/app/block/block.less | 4 ++ frontend/app/block/block.tsx | 65 +++++++++++++++++++------- frontend/app/workspace/workspace.tsx | 25 ++++++++-- pkg/blockcontroller/blockcontroller.go | 14 +++--- 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index 516ff4480..368df48ae 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -12,6 +12,10 @@ min-height: 0; position: relative; + .block-frame-icon { + margin-right: 0.5em; + } + .block-content { display: flex; flex-grow: 1; diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index ad311b9fb..b2705bb15 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -25,17 +25,20 @@ interface BlockProps { onClose?: () => void; dragHandleRef?: React.RefObject; } -function processTextString(iconString: string): React.ReactNode { - if (iconString == null) { + +const colorRegex = /^((#[0-9a-f]{6,8})|([a-z]+))$/; + +function processTitleString(titleString: string): React.ReactNode[] { + if (titleString == null) { return null; } const tagRegex = /<(\/)?([a-z]+)(?::([#a-z0-9-]+))?>/g; let lastIdx = 0; let match; let partsStack = [[]]; - while ((match = tagRegex.exec(iconString)) != null) { + while ((match = tagRegex.exec(titleString)) != null) { const lastPart = partsStack[partsStack.length - 1]; - const before = iconString.substring(lastIdx, match.index); + const before = titleString.substring(lastIdx, match.index); lastPart.push(before); lastIdx = match.index + match[0].length; const [_, isClosing, tagName, tagParam] = match; @@ -51,9 +54,6 @@ function processTextString(iconString: string): React.ReactNode { continue; } if (tagName == "c" || tagName == "color") { - if (tagParam == null) { - continue; - } if (isClosing) { if (partsStack.length <= 1) { continue; @@ -61,6 +61,12 @@ function processTextString(iconString: string): React.ReactNode { partsStack.pop(); continue; } + if (tagParam == null) { + continue; + } + if (!tagParam.match(colorRegex)) { + continue; + } let children = []; const rtag = React.createElement("span", { key: match.index, style: { color: tagParam } }, children); lastPart.push(rtag); @@ -82,7 +88,7 @@ function processTextString(iconString: string): React.ReactNode { continue; } } - partsStack[partsStack.length - 1].push(iconString.substring(lastIdx)); + partsStack[partsStack.length - 1].push(titleString.substring(lastIdx)); return partsStack[0]; } @@ -90,15 +96,34 @@ function getBlockHeaderText(blockData: Block): React.ReactNode { if (!blockData) { return "no block data"; } - if (!util.isBlank(blockData?.meta?.title)) { - try { - return processTextString(blockData.meta.title); - } catch (e) { - console.error("error processing title", blockData.meta.title, e); - return blockData.meta.title; + let blockIcon: React.ReactNode = null; + if (!util.isBlank(blockData?.meta?.["icon"])) { + const iconName = blockData.meta.icon; + let iconColor = blockData.meta["icon:color"]; + if (iconColor && !iconColor.match(colorRegex)) { + iconColor = null; + } + let iconStyle = null; + if (!util.isBlank(iconColor)) { + iconStyle = { color: iconColor }; + } + if (iconName.match(/^[a-z0-9-]+$/)) { + blockIcon = ; } } - return `${blockData?.view} [${blockData.oid.substring(0, 8)}]`; + if (!util.isBlank(blockData?.meta?.title)) { + try { + const rtn = processTitleString(blockData.meta.title) ?? []; + if (blockIcon) { + rtn.unshift(blockIcon); + } + return rtn; + } catch (e) { + console.error("error processing title", blockData.meta.title, e); + return [blockIcon, blockData.meta.title]; + } + } + return [blockIcon, `${blockData?.view} [${blockData.oid.substring(0, 8)}]`]; } interface FramelessBlockHeaderProps { @@ -276,12 +301,18 @@ const BlockFrame = (props: BlockFrameProps) => { if (!blockId || !blockData) { return null; } + let FrameElem = BlockFrame_Tech; // if 0 or 1 blocks, use frameless, otherwise use tech const numBlocks = tabData?.blockids?.length ?? 0; if (numBlocks <= 1) { - return ; + FrameElem = BlockFrame_Frameless; } - return ; + if (blockData?.meta?.["frame"] === "tech") { + FrameElem = BlockFrame_Tech; + } else if (blockData?.meta?.["frame"] === "frameless") { + FrameElem = BlockFrame_Frameless; + } + return ; }; const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index 05be832cc..306f879d5 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -12,11 +12,13 @@ import { CenteredDiv } from "../element/quickelems"; import "./workspace.less"; +const iconRegex = /^[a-z0-9-]+$/; + function Widgets() { const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); const newWidgetModalVisible = React.useState(false); async function clickTerminal() { - const termBlockDef = { + const termBlockDef: BlockDef = { controller: "shell", view: "term", }; @@ -44,6 +46,20 @@ function Widgets() { await services.FileService.RemoveWidget(idx); } + function isIconValid(icon: string): boolean { + if (util.isBlank(icon)) { + return false; + } + return icon.match(iconRegex) != null; + } + + function getIconClass(icon: string): string { + if (!isIconValid(icon)) { + return "fa fa-solid fa-question fa-fw"; + } + return `fa fa-solid fa-${icon} fa-fw`; + } + return (
clickTerminal()}> @@ -54,20 +70,19 @@ function Widgets() {
clickHome()}>
- +
home
{settingsConfig.widgets.map((data, idx) => (
handleWidgetSelect(data.blockdef)} key={`widget-${idx}`} title={data.description || data.label} > -
- +
+
{!util.isBlank(data.label) ?
{data.label}
: null}
diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index 48aaa7d47..f6f32de37 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -54,6 +54,7 @@ type RunCmdFnType = func(ctx context.Context, cmd wshutil.BlockCommand, cmdCtx w type BlockController struct { Lock *sync.Mutex + ControllerType string TabId string BlockId string BlockDef *wstore.BlockDef @@ -308,12 +309,13 @@ func StartBlockController(ctx context.Context, tabId string, blockId string, run return nil } bc := &BlockController{ - Lock: &sync.Mutex{}, - TabId: tabId, - BlockId: blockId, - Status: "init", - InputCh: make(chan wshutil.BlockCommand), - RunCmdFn: runCmdFn, + Lock: &sync.Mutex{}, + ControllerType: blockData.Controller, + TabId: tabId, + BlockId: blockId, + Status: "init", + InputCh: make(chan wshutil.BlockCommand), + RunCmdFn: runCmdFn, } blockControllerMap[blockId] = bc go bc.Run(blockData)