add block icon

This commit is contained in:
sawka 2024-06-21 15:15:38 -07:00
parent 8683105f70
commit e7550c0a3e
4 changed files with 80 additions and 28 deletions

View File

@ -12,6 +12,10 @@
min-height: 0; min-height: 0;
position: relative; position: relative;
.block-frame-icon {
margin-right: 0.5em;
}
.block-content { .block-content {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;

View File

@ -25,17 +25,20 @@ interface BlockProps {
onClose?: () => void; onClose?: () => void;
dragHandleRef?: React.RefObject<HTMLDivElement>; dragHandleRef?: React.RefObject<HTMLDivElement>;
} }
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; return null;
} }
const tagRegex = /<(\/)?([a-z]+)(?::([#a-z0-9-]+))?>/g; const tagRegex = /<(\/)?([a-z]+)(?::([#a-z0-9-]+))?>/g;
let lastIdx = 0; let lastIdx = 0;
let match; let match;
let partsStack = [[]]; let partsStack = [[]];
while ((match = tagRegex.exec(iconString)) != null) { while ((match = tagRegex.exec(titleString)) != null) {
const lastPart = partsStack[partsStack.length - 1]; const lastPart = partsStack[partsStack.length - 1];
const before = iconString.substring(lastIdx, match.index); const before = titleString.substring(lastIdx, match.index);
lastPart.push(before); lastPart.push(before);
lastIdx = match.index + match[0].length; lastIdx = match.index + match[0].length;
const [_, isClosing, tagName, tagParam] = match; const [_, isClosing, tagName, tagParam] = match;
@ -51,9 +54,6 @@ function processTextString(iconString: string): React.ReactNode {
continue; continue;
} }
if (tagName == "c" || tagName == "color") { if (tagName == "c" || tagName == "color") {
if (tagParam == null) {
continue;
}
if (isClosing) { if (isClosing) {
if (partsStack.length <= 1) { if (partsStack.length <= 1) {
continue; continue;
@ -61,6 +61,12 @@ function processTextString(iconString: string): React.ReactNode {
partsStack.pop(); partsStack.pop();
continue; continue;
} }
if (tagParam == null) {
continue;
}
if (!tagParam.match(colorRegex)) {
continue;
}
let children = []; let children = [];
const rtag = React.createElement("span", { key: match.index, style: { color: tagParam } }, children); const rtag = React.createElement("span", { key: match.index, style: { color: tagParam } }, children);
lastPart.push(rtag); lastPart.push(rtag);
@ -82,7 +88,7 @@ function processTextString(iconString: string): React.ReactNode {
continue; continue;
} }
} }
partsStack[partsStack.length - 1].push(iconString.substring(lastIdx)); partsStack[partsStack.length - 1].push(titleString.substring(lastIdx));
return partsStack[0]; return partsStack[0];
} }
@ -90,15 +96,34 @@ function getBlockHeaderText(blockData: Block): React.ReactNode {
if (!blockData) { if (!blockData) {
return "no block data"; return "no block data";
} }
if (!util.isBlank(blockData?.meta?.title)) { let blockIcon: React.ReactNode = null;
try { if (!util.isBlank(blockData?.meta?.["icon"])) {
return processTextString(blockData.meta.title); const iconName = blockData.meta.icon;
} catch (e) { let iconColor = blockData.meta["icon:color"];
console.error("error processing title", blockData.meta.title, e); if (iconColor && !iconColor.match(colorRegex)) {
return blockData.meta.title; iconColor = null;
}
let iconStyle = null;
if (!util.isBlank(iconColor)) {
iconStyle = { color: iconColor };
}
if (iconName.match(/^[a-z0-9-]+$/)) {
blockIcon = <i style={iconStyle} className={`block-frame-icon fa fa-solid fa-${blockData.meta.icon}`} />;
} }
} }
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 { interface FramelessBlockHeaderProps {
@ -276,12 +301,18 @@ const BlockFrame = (props: BlockFrameProps) => {
if (!blockId || !blockData) { if (!blockId || !blockData) {
return null; return null;
} }
let FrameElem = BlockFrame_Tech;
// if 0 or 1 blocks, use frameless, otherwise use tech // if 0 or 1 blocks, use frameless, otherwise use tech
const numBlocks = tabData?.blockids?.length ?? 0; const numBlocks = tabData?.blockids?.length ?? 0;
if (numBlocks <= 1) { if (numBlocks <= 1) {
return <BlockFrame_Frameless {...props} />; FrameElem = BlockFrame_Frameless;
} }
return <BlockFrame_Tech {...props} />; if (blockData?.meta?.["frame"] === "tech") {
FrameElem = BlockFrame_Tech;
} else if (blockData?.meta?.["frame"] === "frameless") {
FrameElem = BlockFrame_Frameless;
}
return <FrameElem {...props} />;
}; };
const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => {

View File

@ -12,11 +12,13 @@ import { CenteredDiv } from "../element/quickelems";
import "./workspace.less"; import "./workspace.less";
const iconRegex = /^[a-z0-9-]+$/;
function Widgets() { function Widgets() {
const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom); const settingsConfig = jotai.useAtomValue(atoms.settingsConfigAtom);
const newWidgetModalVisible = React.useState(false); const newWidgetModalVisible = React.useState(false);
async function clickTerminal() { async function clickTerminal() {
const termBlockDef = { const termBlockDef: BlockDef = {
controller: "shell", controller: "shell",
view: "term", view: "term",
}; };
@ -44,6 +46,20 @@ function Widgets() {
await services.FileService.RemoveWidget(idx); 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 ( return (
<div className="workspace-widgets"> <div className="workspace-widgets">
<div className="widget" onClick={() => clickTerminal()}> <div className="widget" onClick={() => clickTerminal()}>
@ -54,20 +70,19 @@ function Widgets() {
</div> </div>
<div className="widget" onClick={() => clickHome()}> <div className="widget" onClick={() => clickHome()}>
<div className="widget-icon"> <div className="widget-icon">
<i className="fa-sharp fa-solid fa-house"></i> <i className="fa-sharp fa-solid fa-home"></i>
</div> </div>
<div className="widget-label">home</div> <div className="widget-label">home</div>
</div> </div>
{settingsConfig.widgets.map((data, idx) => ( {settingsConfig.widgets.map((data, idx) => (
<div <div
className="widget" className="widget"
style={{ color: data.color }}
onClick={() => handleWidgetSelect(data.blockdef)} onClick={() => handleWidgetSelect(data.blockdef)}
key={`widget-${idx}`} key={`widget-${idx}`}
title={data.description || data.label} title={data.description || data.label}
> >
<div className="widget-icon"> <div className="widget-icon" style={{ color: data.color }}>
<i className={data.icon}></i> <i className={getIconClass(data.icon)}></i>
</div> </div>
{!util.isBlank(data.label) ? <div className="widget-label">{data.label}</div> : null} {!util.isBlank(data.label) ? <div className="widget-label">{data.label}</div> : null}
</div> </div>

View File

@ -54,6 +54,7 @@ type RunCmdFnType = func(ctx context.Context, cmd wshutil.BlockCommand, cmdCtx w
type BlockController struct { type BlockController struct {
Lock *sync.Mutex Lock *sync.Mutex
ControllerType string
TabId string TabId string
BlockId string BlockId string
BlockDef *wstore.BlockDef BlockDef *wstore.BlockDef
@ -308,12 +309,13 @@ func StartBlockController(ctx context.Context, tabId string, blockId string, run
return nil return nil
} }
bc := &BlockController{ bc := &BlockController{
Lock: &sync.Mutex{}, Lock: &sync.Mutex{},
TabId: tabId, ControllerType: blockData.Controller,
BlockId: blockId, TabId: tabId,
Status: "init", BlockId: blockId,
InputCh: make(chan wshutil.BlockCommand), Status: "init",
RunCmdFn: runCmdFn, InputCh: make(chan wshutil.BlockCommand),
RunCmdFn: runCmdFn,
} }
blockControllerMap[blockId] = bc blockControllerMap[blockId] = bc
go bc.Run(blockData) go bc.Run(blockData)