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;
position: relative;
.block-frame-icon {
margin-right: 0.5em;
}
.block-content {
display: flex;
flex-grow: 1;

View File

@ -25,17 +25,20 @@ interface BlockProps {
onClose?: () => void;
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;
}
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";
}
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 = <i style={iconStyle} className={`block-frame-icon fa fa-solid fa-${blockData.meta.icon}`} />;
}
}
if (!util.isBlank(blockData?.meta?.title)) {
try {
return processTextString(blockData.meta.title);
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 blockData.meta.title;
return [blockIcon, blockData.meta.title];
}
}
return `${blockData?.view} [${blockData.oid.substring(0, 8)}]`;
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 <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) => {

View File

@ -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 (
<div className="workspace-widgets">
<div className="widget" onClick={() => clickTerminal()}>
@ -54,20 +70,19 @@ function Widgets() {
</div>
<div className="widget" onClick={() => clickHome()}>
<div className="widget-icon">
<i className="fa-sharp fa-solid fa-house"></i>
<i className="fa-sharp fa-solid fa-home"></i>
</div>
<div className="widget-label">home</div>
</div>
{settingsConfig.widgets.map((data, idx) => (
<div
className="widget"
style={{ color: data.color }}
onClick={() => handleWidgetSelect(data.blockdef)}
key={`widget-${idx}`}
title={data.description || data.label}
>
<div className="widget-icon">
<i className={data.icon}></i>
<div className="widget-icon" style={{ color: data.color }}>
<i className={getIconClass(data.icon)}></i>
</div>
{!util.isBlank(data.label) ? <div className="widget-label">{data.label}</div> : null}
</div>

View File

@ -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
@ -309,6 +310,7 @@ func StartBlockController(ctx context.Context, tabId string, blockId string, run
}
bc := &BlockController{
Lock: &sync.Mutex{},
ControllerType: blockData.Controller,
TabId: tabId,
BlockId: blockId,
Status: "init",