mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-03 18:47:56 +01:00
add block icon
This commit is contained in:
parent
8683105f70
commit
e7550c0a3e
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user