create-view
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCreating a New View in Wave Terminal
在Wave Terminal中创建新视图
This guide explains how to implement a new view type in Wave Terminal. Views are the core content components displayed within blocks in the terminal interface.
本指南介绍如何在Wave Terminal中实现新的视图类型。视图是终端界面中区块内显示的核心内容组件。
Architecture Overview
架构概述
Wave Terminal uses a Model-View architecture where:
- ViewModel - Contains all state, logic, and UI configuration as Jotai atoms
- ViewComponent - Pure React component that renders the UI using the model
- BlockFrame - Wraps views with a header, connection management, and standard controls
The separation between model and component ensures:
- Models can update state without React hooks
- Components remain pure and testable
- State is centralized in Jotai atoms for easy access
Wave Terminal采用模型-视图架构,其中:
- ViewModel - 包含所有状态、逻辑和UI配置,以Jotai atoms形式存在
- ViewComponent - 纯React组件,使用模型渲染UI
- BlockFrame - 为视图添加头部、连接管理和标准控件的包装器
模型与组件的分离确保:
- 模型可在不使用React hooks的情况下更新状态
- 组件保持纯净且可测试
- 状态集中存储在Jotai atoms中,便于访问
ViewModel Interface
ViewModel接口
Every view must implement the interface defined in :
ViewModelfrontend/types/custom.d.tstypescript
interface ViewModel {
// Required: The type identifier for this view (e.g., "term", "web", "preview")
viewType: string;
// Required: The React component that renders this view
viewComponent: ViewComponent<ViewModel>;
// Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)
viewIcon?: jotai.Atom<string | IconButtonDecl>;
// Optional: Display name shown in block header (e.g., "Terminal", "Web", "Preview")
viewName?: jotai.Atom<string>;
// Optional: Additional header elements (text, buttons, inputs) shown after the name
viewText?: jotai.Atom<string | HeaderElem[]>;
// Optional: Icon button shown before the view name in header
preIconButton?: jotai.Atom<IconButtonDecl>;
// Optional: Icon buttons shown at the end of the header (before settings/close)
endIconButtons?: jotai.Atom<IconButtonDecl[]>;
// Optional: Custom background styling for the block
blockBg?: jotai.Atom<MetaType>;
// Optional: If true, completely hides the block header
noHeader?: jotai.Atom<boolean>;
// Optional: If true, shows connection picker in header for remote connections
manageConnection?: jotai.Atom<boolean>;
// Optional: If true, filters out 'nowsh' connections from connection picker
filterOutNowsh?: jotai.Atom<boolean>;
// Optional: If true, removes default padding from content area
noPadding?: jotai.Atom<boolean>;
// Optional: Atoms for managing in-block search functionality
searchAtoms?: SearchAtoms;
// Optional: Returns whether this is a basic terminal (for multi-input feature)
isBasicTerm?: (getFn: jotai.Getter) => boolean;
// Optional: Returns context menu items for the settings dropdown
getSettingsMenuItems?: () => ContextMenuItem[];
// Optional: Focuses the view when called, returns true if successful
giveFocus?: () => boolean;
// Optional: Handles keyboard events, returns true if handled
keyDownHandler?: (e: WaveKeyboardEvent) => boolean;
// Optional: Cleanup when block is closed
dispose?: () => void;
}每个视图都必须实现中定义的接口:
frontend/types/custom.d.tsViewModeltypescript
interface ViewModel {
// Required: The type identifier for this view (e.g., "term", "web", "preview")
viewType: string;
// Required: The React component that renders this view
viewComponent: ViewComponent<ViewModel>;
// Optional: Icon shown in block header (FontAwesome icon name or IconButtonDecl)
viewIcon?: jotai.Atom<string | IconButtonDecl>;
// Optional: Display name shown in block header (e.g., "Terminal", "Web", "Preview")
viewName?: jotai.Atom<string>;
// Optional: Additional header elements (text, buttons, inputs) shown after the name
viewText?: jotai.Atom<string | HeaderElem[]>;
// Optional: Icon button shown before the view name in header
preIconButton?: jotai.Atom<IconButtonDecl>;
// Optional: Icon buttons shown at the end of the header (before settings/close)
endIconButtons?: jotai.Atom<IconButtonDecl[]>;
// Optional: Custom background styling for the block
blockBg?: jotai.Atom<MetaType>;
// Optional: If true, completely hides the block header
noHeader?: jotai.Atom<boolean>;
// Optional: If true, shows connection picker in header for remote connections
manageConnection?: jotai.Atom<boolean>;
// Optional: If true, filters out 'nowsh' connections from connection picker
filterOutNowsh?: jotai.Atom<boolean>;
// Optional: If true, removes default padding from content area
noPadding?: jotai.Atom<boolean>;
// Optional: Atoms for managing in-block search functionality
searchAtoms?: SearchAtoms;
// Optional: Returns whether this is a basic terminal (for multi-input feature)
isBasicTerm?: (getFn: jotai.Getter) => boolean;
// Optional: Returns context menu items for the settings dropdown
getSettingsMenuItems?: () => ContextMenuItem[];
// Optional: Focuses the view when called, returns true if successful
giveFocus?: () => boolean;
// Optional: Handles keyboard events, returns true if handled
keyDownHandler?: (e: WaveKeyboardEvent) => boolean;
// Optional: Cleanup when block is closed
dispose?: () => void;
}Key Concepts
核心概念
Atoms: All UI-related properties must be Jotai atoms. This enables:
- Reactive updates when state changes
- Access from anywhere via /
globalStore.get()globalStore.set() - Derived atoms that compute values from other atoms
ViewComponent: The React component receives these props:
typescript
type ViewComponentProps<T extends ViewModel> = {
blockId: string; // Unique ID for this block
blockRef: React.RefObject<HTMLDivElement>; // Ref to block container
contentRef: React.RefObject<HTMLDivElement>; // Ref to content area
model: T; // Your ViewModel instance
};Atoms:所有与UI相关的属性必须是Jotai atoms。这支持:
- 状态变化时的响应式更新
- 通过/
globalStore.get()从任何地方访问globalStore.set() - 从其他原子计算值的派生原子
ViewComponent:React组件会接收以下props:
typescript
type ViewComponentProps<T extends ViewModel> = {
blockId: string; // Unique ID for this block
blockRef: React.RefObject<HTMLDivElement>; // Ref to block container
contentRef: React.RefObject<HTMLDivElement>; // Ref to content area
model: T; // Your ViewModel instance
};Step-by-Step Guide
分步指南
1. Create the View Model Class
1. 创建视图模型类
Create a new file for your view model (e.g., ):
frontend/app/view/myview/myview-model.tstypescript
import { BlockNodeModel } from "@/app/block/blocktypes";
import { WOS, globalStore, useBlockAtom } from "@/store/global";
import * as jotai from "jotai";
import { MyView } from "./myview";
export class MyViewModel implements ViewModel {
viewType: string;
blockId: string;
nodeModel: BlockNodeModel;
blockAtom: jotai.Atom<Block>;
// Define your atoms (simple field initializers)
viewIcon = jotai.atom<string>("circle");
viewName = jotai.atom<string>("My View");
noPadding = jotai.atom<boolean>(true);
// Derived atom (created in constructor)
viewText!: jotai.Atom<HeaderElem[]>;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "myview";
this.blockId = blockId;
this.nodeModel = nodeModel;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
// Create derived atoms that depend on block data or other atoms
this.viewText = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const rtn: HeaderElem[] = [];
// Add header buttons/text based on state
rtn.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Refresh",
click: () => this.refresh(),
});
return rtn;
});
}
get viewComponent(): ViewComponent {
return MyView;
}
refresh() {
// Update state using globalStore
// Never use React hooks in model methods
console.log("refreshing...");
}
giveFocus(): boolean {
// Focus your view component
return true;
}
dispose() {
// Cleanup resources (unsubscribe from events, etc.)
}
}为你的视图模型创建新文件(例如):
frontend/app/view/myview/myview-model.tstypescript
import { BlockNodeModel } from "@/app/block/blocktypes";
import { WOS, globalStore, useBlockAtom } from "@/store/global";
import * as jotai from "jotai";
import { MyView } from "./myview";
export class MyViewModel implements ViewModel {
viewType: string;
blockId: string;
nodeModel: BlockNodeModel;
blockAtom: jotai.Atom<Block>;
// Define your atoms (simple field initializers)
viewIcon = jotai.atom<string>("circle");
viewName = jotai.atom<string>("My View");
noPadding = jotai.atom<boolean>(true);
// Derived atom (created in constructor)
viewText!: jotai.Atom<HeaderElem[]>;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "myview";
this.blockId = blockId;
this.nodeModel = nodeModel;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
// Create derived atoms that depend on block data or other atoms
this.viewText = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const rtn: HeaderElem[] = [];
// Add header buttons/text based on state
rtn.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Refresh",
click: () => this.refresh(),
});
return rtn;
});
}
get viewComponent(): ViewComponent {
return MyView;
}
refresh() {
// Update state using globalStore
// Never use React hooks in model methods
console.log("refreshing...");
}
giveFocus(): boolean {
// Focus your view component
return true;
}
dispose() {
// Cleanup resources (unsubscribe from events, etc.)
}
}2. Create the View Component
2. 创建视图组件
Create your React component (e.g., ):
frontend/app/view/myview/myview.tsxtypescript
import { ViewComponentProps } from "@/app/block/blocktypes";
import { MyViewModel } from "./myview-model";
import { useAtomValue } from "jotai";
import "./myview.scss";
export const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({
blockId,
model,
contentRef
}) => {
// Use atoms from the model (these are React hooks - call at top level!)
const blockData = useAtomValue(model.blockAtom);
return (
<div className="myview-container" ref={contentRef}>
<div>Block ID: {blockId}</div>
<div>View: {model.viewType}</div>
{/* Your view content here */}
</div>
);
};创建你的React组件(例如):
frontend/app/view/myview/myview.tsxtypescript
import { ViewComponentProps } from "@/app/block/blocktypes";
import { MyViewModel } from "./myview-model";
import { useAtomValue } from "jotai";
import "./myview.scss";
export const MyView: React.FC<ViewComponentProps<MyViewModel>> = ({
blockId,
model,
contentRef
}) => {
// Use atoms from the model (these are React hooks - call at top level!)
const blockData = useAtomValue(model.blockAtom);
return (
<div className="myview-container" ref={contentRef}>
<div>Block ID: {blockId}</div>
<div>View: {model.viewType}</div>
{/* Your view content here */}
</div>
);
};3. Register the View
3. 注册视图
Add your view to the in :
BlockRegistryfrontend/app/block/block.tsxtypescript
const BlockRegistry: Map<string, ViewModelClass> = new Map();
BlockRegistry.set("term", TermViewModel);
BlockRegistry.set("preview", PreviewModel);
BlockRegistry.set("web", WebViewModel);
// ... existing registrations ...
BlockRegistry.set("myview", MyViewModel); // Add your view hereThe registry key (e.g., ) becomes the view type used in block metadata.
"myview"在的中添加你的视图:
frontend/app/block/block.tsxBlockRegistrytypescript
const BlockRegistry: Map<string, ViewModelClass> = new Map();
BlockRegistry.set("term", TermViewModel);
BlockRegistry.set("preview", PreviewModel);
BlockRegistry.set("web", WebViewModel);
// ... existing registrations ...
BlockRegistry.set("myview", MyViewModel); // Add your view here注册表键(例如)将成为区块元数据中使用的视图类型。
"myview"4. Create Blocks with Your View
4. 使用你的视图创建区块
Users can create blocks with your view type:
- Via CLI:
wsh view myview - Via RPC: Use the block's field set to
meta.view"myview"
用户可以使用你的视图类型创建区块:
- 通过CLI:
wsh view myview - 通过RPC:将区块的字段设置为
meta.view"myview"
Real-World Examples
实际示例
Example 1: Terminal View (term-model.ts
)
term-model.ts示例1:终端视图(term-model.ts
)
term-model.tsThe terminal view demonstrates:
- Connection management via atom
manageConnection - Dynamic header buttons showing shell status (play/restart)
- Mode switching between terminal and vdom views
- Custom keyboard handling for terminal-specific shortcuts
- Focus management to focus the xterm.js instance
- Shell integration status showing AI capability indicators
Key features:
typescript
this.manageConnection = jotai.atom((get) => {
const termMode = get(this.termMode);
if (termMode == "vdom") return false;
return true; // Show connection picker for regular terminal mode
});
this.endIconButtons = jotai.atom((get) => {
const shellProcStatus = get(this.shellProcStatus);
const buttons: IconButtonDecl[] = [];
if (shellProcStatus == "running") {
buttons.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Restart Shell",
click: this.forceRestartController.bind(this),
});
}
return buttons;
});终端视图展示了:
- 通过原子实现的连接管理
manageConnection - 显示shell状态(运行/重启)的动态头部按钮
- 在终端视图和vdom视图之间的模式切换
- 针对终端特定快捷键的自定义键盘处理
- 聚焦xterm.js实例的焦点管理
- 显示AI功能指示器的Shell集成状态
核心特性:
typescript
this.manageConnection = jotai.atom((get) => {
const termMode = get(this.termMode);
if (termMode == "vdom") return false;
return true; // Show connection picker for regular terminal mode
});
this.endIconButtons = jotai.atom((get) => {
const shellProcStatus = get(this.shellProcStatus);
const buttons: IconButtonDecl[] = [];
if (shellProcStatus == "running") {
buttons.push({
elemtype: "iconbutton",
icon: "refresh",
title: "Restart Shell",
click: this.forceRestartController.bind(this),
});
}
return buttons;
});Example 2: Web View (webview.tsx
)
webview.tsx示例2:Web视图(webview.tsx
)
webview.tsxThe web view shows:
- Complex header controls (back/forward/home/URL input)
- State management for loading, URL, and navigation
- Event handling for webview navigation events
- Custom styling with for full-bleed content
noPadding - Media controls showing play/pause/mute when media is active
Key features:
typescript
this.viewText = jotai.atom((get) => {
const url = get(this.url);
const rtn: HeaderElem[] = [];
// Navigation buttons
rtn.push({
elemtype: "iconbutton",
icon: "chevron-left",
click: this.handleBack.bind(this),
disabled: this.shouldDisableBackButton(),
});
// URL input with nested controls
rtn.push({
elemtype: "div",
className: "block-frame-div-url",
children: [
{
elemtype: "input",
value: url,
onChange: this.handleUrlChange.bind(this),
onKeyDown: this.handleKeyDown.bind(this),
},
{
elemtype: "iconbutton",
icon: "rotate-right",
click: this.handleRefresh.bind(this),
},
],
});
return rtn;
});Web视图展示了:
- 复杂头部控件(后退/前进/主页/URL输入)
- 针对加载状态、URL和导航的状态管理
- 针对webview导航事件的事件处理
- 使用实现的自定义样式,用于全宽内容
noPadding - 媒体播放时显示播放/暂停/静音的媒体控件
核心特性:
typescript
this.viewText = jotai.atom((get) => {
const url = get(this.url);
const rtn: HeaderElem[] = [];
// Navigation buttons
rtn.push({
elemtype: "iconbutton",
icon: "chevron-left",
click: this.handleBack.bind(this),
disabled: this.shouldDisableBackButton(),
});
// URL input with nested controls
rtn.push({
elemtype: "div",
className: "block-frame-div-url",
children: [
{
elemtype: "input",
value: url,
onChange: this.handleUrlChange.bind(this),
onKeyDown: this.handleKeyDown.bind(this),
},
{
elemtype: "iconbutton",
icon: "rotate-right",
click: this.handleRefresh.bind(this),
},
],
});
return rtn;
});Header Elements (HeaderElem
)
HeaderElem头部元素(HeaderElem
)
HeaderElemThe atom can return an array of these element types:
viewTexttypescript
// Icon button
{
elemtype: "iconbutton",
icon: "refresh",
title: "Tooltip text",
click: () => { /* handler */ },
disabled?: boolean,
iconColor?: string,
iconSpin?: boolean,
noAction?: boolean, // Shows icon but no click action
}
// Text element
{
elemtype: "text",
text: "Display text",
className?: string,
noGrow?: boolean,
ref?: React.RefObject<HTMLElement>,
onClick?: (e: React.MouseEvent) => void,
}
// Text button
{
elemtype: "textbutton",
text: "Button text",
className?: string,
title: "Tooltip",
onClick: (e: React.MouseEvent) => void,
}
// Input field
{
elemtype: "input",
value: string,
className?: string,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,
ref?: React.RefObject<HTMLInputElement>,
}
// Container with children
{
elemtype: "div",
className?: string,
children: HeaderElem[],
onMouseOver?: (e: React.MouseEvent) => void,
onMouseOut?: (e: React.MouseEvent) => void,
}
// Menu button (dropdown)
{
elemtype: "menubutton",
// ... MenuButtonProps ...
}viewTexttypescript
// Icon button
{
elemtype: "iconbutton",
icon: "refresh",
title: "Tooltip text",
click: () => { /* handler */ },
disabled?: boolean,
iconColor?: string,
iconSpin?: boolean,
noAction?: boolean, // Shows icon but no click action
}
// Text element
{
elemtype: "text",
text: "Display text",
className?: string,
noGrow?: boolean,
ref?: React.RefObject<HTMLElement>,
onClick?: (e: React.MouseEvent) => void,
}
// Text button
{
elemtype: "textbutton",
text: "Button text",
className?: string,
title: "Tooltip",
onClick: (e: React.MouseEvent) => void,
}
// Input field
{
elemtype: "input",
value: string,
className?: string,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void,
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void,
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void,
ref?: React.RefObject<HTMLInputElement>,
}
// Container with children
{
elemtype: "div",
className?: string,
children: HeaderElem[],
onMouseOver?: (e: React.MouseEvent) => void,
onMouseOut?: (e: React.MouseEvent) => void,
}
// Menu button (dropdown)
{
elemtype: "menubutton",
// ... MenuButtonProps ...
}Best Practices
最佳实践
Jotai Model Pattern
Jotai模型模式
Follow these rules for Jotai atoms in models:
-
Simple atoms as field initializers:typescript
viewIcon = jotai.atom<string>("circle"); noPadding = jotai.atom<boolean>(true); -
Derived atoms in constructor (need dependency on other atoms):typescript
constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewText = jotai.atom((get) => { const blockData = get(this.blockAtom); return [/* computed based on blockData */]; }); } -
Models never use React hooks - Use/
globalStore.get():set()typescriptrefresh() { const currentData = globalStore.get(this.blockAtom); globalStore.set(this.dataAtom, newData); } -
Components use hooks for atoms:typescript
const data = useAtomValue(model.dataAtom); const [value, setValue] = useAtom(model.valueAtom);
为模型中的Jotai原子遵循以下规则:
-
简单原子作为字段初始化器:typescript
viewIcon = jotai.atom<string>("circle"); noPadding = jotai.atom<boolean>(true); -
派生原子在构造函数中创建(依赖其他原子):typescript
constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewText = jotai.atom((get) => { const blockData = get(this.blockAtom); return [/* computed based on blockData */]; }); } -
模型绝不使用React hooks - 使用/
globalStore.get():set()typescriptrefresh() { const currentData = globalStore.get(this.blockAtom); globalStore.set(this.dataAtom, newData); } -
组件使用hooks访问原子:typescript
const data = useAtomValue(model.dataAtom); const [value, setValue] = useAtom(model.valueAtom);
State Management
状态管理
- All view state should live in atoms on the model
- Use helper for block-scoped atoms that persist
useBlockAtom() - Use for imperative access outside React components
globalStore - Subscribe to Wave events using
waveEventSubscribe()
- 所有视图状态应存储在模型的原子中
- 使用助手函数创建可持久化的区块作用域原子
useBlockAtom() - 使用在React组件外进行命令式访问
globalStore - 使用订阅Wave事件
waveEventSubscribe()
Styling
样式
- Create a file for your view styles
.scss - Use Tailwind utilities where possible (v4)
- Add for full-bleed content
noPadding: atom(true) - Use atom to customize block background
blockBg
- 为你的视图创建样式文件
.scss - 尽可能使用Tailwind工具类(v4版本)
- 设置以实现全宽内容
noPadding: atom(true) - 使用原子自定义区块背景
blockBg
Focus Management
焦点管理
Implement to focus your view when:
giveFocus()- Block gains focus via keyboard navigation
- User clicks the block
- Return if successfully focused,
trueotherwisefalse
实现以在以下场景中聚焦你的视图:
giveFocus()- 区块通过键盘导航获得焦点时
- 用户点击区块时
- 成功聚焦返回,否则返回
truefalse
Keyboard Handling
键盘处理
Implement for:
keyDownHandler(e: WaveKeyboardEvent)- View-specific keyboard shortcuts
- Return if event was handled (prevents propagation)
true - Use for shortcut checks
keyutil.checkKeyPressed(waveEvent, "Cmd:K")
实现以支持:
keyDownHandler(e: WaveKeyboardEvent)- 视图特定的键盘快捷键
- 处理事件后返回(阻止事件传播)
true - 使用检查快捷键
keyutil.checkKeyPressed(waveEvent, "Cmd:K")
Cleanup
清理
Implement to:
dispose()- Unsubscribe from Wave events
- Unregister routes/handlers
- Clear timers/intervals
- Release resources
实现以:
dispose()- 取消订阅Wave事件
- 注销路由/处理器
- 清除计时器/间隔任务
- 释放资源
Connection Management
连接管理
For views that need remote connections:
typescript
this.manageConnection = jotai.atom(true); // Show connection picker
this.filterOutNowsh = jotai.atom(true); // Hide nowsh connectionsAccess connection status:
typescript
const connStatus = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const connName = blockData?.meta?.connection;
return get(getConnStatusAtom(connName));
});对于需要远程连接的视图:
typescript
this.manageConnection = jotai.atom(true); // Show connection picker
this.filterOutNowsh = jotai.atom(true); // Hide nowsh connections访问连接状态:
typescript
const connStatus = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const connName = blockData?.meta?.connection;
return get(getConnStatusAtom(connName));
});Common Patterns
常见模式
Reading Block Metadata
读取区块元数据
typescript
import { getBlockMetaKeyAtom } from "@/store/global";
// In constructor:
this.someFlag = getBlockMetaKeyAtom(blockId, "myview:flag");
// In component:
const flag = useAtomValue(model.someFlag);typescript
import { getBlockMetaKeyAtom } from "@/store/global";
// In constructor:
this.someFlag = getBlockMetaKeyAtom(blockId, "myview:flag");
// In component:
const flag = useAtomValue(model.someFlag);Configuration Overrides
配置覆盖
Wave has a hierarchical config system (global → connection → block):
typescript
import { getOverrideConfigAtom } from "@/store/global";
this.settingAtom = jotai.atom((get) => {
// Checks block meta, then connection config, then global settings
return get(getOverrideConfigAtom(this.blockId, "myview:setting")) ?? defaultValue;
});Wave具有分层配置系统(全局 → 连接 → 区块):
typescript
import { getOverrideConfigAtom } from "@/store/global";
this.settingAtom = jotai.atom((get) => {
// Checks block meta, then connection config, then global settings
return get(getOverrideConfigAtom(this.blockId, "myview:setting")) ?? defaultValue;
});Updating Block Metadata
更新区块元数据
typescript
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { WOS } from "@/store/global";
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "myview:key": value },
});typescript
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { WOS } from "@/store/global";
await RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "myview:key": value },
});Additional Resources
额外资源
- - Block header rendering
frontend/app/block/blockframe-header.tsx - - Complex view example
frontend/app/view/term/term-model.ts - - Navigation UI example
frontend/app/view/webview/webview.tsx - - Type definitions
frontend/types/custom.d.ts
- - 区块头部渲染逻辑
frontend/app/block/blockframe-header.tsx - - 复杂视图示例
frontend/app/view/term/term-model.ts - - 导航UI示例
frontend/app/view/webview/webview.tsx - - 类型定义
frontend/types/custom.d.ts