mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-11-27 20:50:25 +08:00
Handle both types of paste data. Write utility functions to normalize
paste events to {text; image}[]. Fix duplication issue (call
preventDefault() early). Handle multiple image pasting (by adding a
slight delay). Convert Ctrl:Shift:v to use a *native paste* which allows
capturing of images!
7.3 KiB
7.3 KiB
Wave Terminal ViewModel Guide
Overview
Wave Terminal uses a modular ViewModel system to define interactive blocks. Each block has a ViewModel, which manages its metadata, configuration, and state using Jotai atoms. The ViewModel also specifies a React component (ViewComponent) that renders the block.
Key Concepts
-
ViewModel Structure
- Implements the
ViewModelinterface. - Defines:
viewType: Unique block type identifier.viewIcon,viewName,viewText: Atoms for UI metadata.preIconButton,endIconButtons: Atoms for action buttons.blockBg: Atom for background styling.manageConnection,noPadding,searchAtoms.viewComponent: React component rendering the block.- Lifecycle methods like
dispose(),giveFocus(),keyDownHandler().
- Implements the
-
ViewComponent Structure
- A React function component implementing
ViewComponentProps<T extends ViewModel>. - Uses
blockId,blockRef,contentRef, andmodelas props. - Retrieves ViewModel state using Jotai atoms.
- Returns JSX for rendering.
- A React function component implementing
-
Header Elements (
HeaderElem[])- Can include:
- Icons (
IconButtonDecl): Clickable buttons. - Text (
HeaderText): Metadata or status. - Inputs (
HeaderInput): Editable fields. - Menu Buttons (
MenuButton): Dropdowns.
- Icons (
- Can include:
-
Jotai Atoms for State Management
- Use
atom<T>,PrimitiveAtom<T>,WritableAtom<T>for dynamic properties. splitAtomfor managing lists of atoms.- Read settings from
globalStoreand override with block metadata.
- Use
-
Metadata vs. Global Config
- Block Metadata (
SetMetaCommand): Each block persists its own configuration in its metadata (blockAtom.meta). - Global Config (
SetConfigCommand): Provides default settings for all blocks, stored in config files. - Cascading Behavior:
- Blocks first check their own metadata for settings.
- If no override exists, they fall back to global config.
- Updating a block's setting is done via
SetMetaCommand(persisted per block). - Updating a global setting is done via
SetConfigCommand(applies globally unless overridden).
- Block Metadata (
-
Useful Helper Functions
- To avoid repetitive boilerplate, use these global utilities from
global.ts:useBlockMetaKeyAtom(blockId, key): Retrieves and updates block-specific metadata.useOverrideConfigAtom(blockId, key): Reads from global config but allows per-block overrides.useSettingsKeyAtom(key): Accesses global settings efficiently.
- To avoid repetitive boilerplate, use these global utilities from
-
Styling
- Use TailWind CSS to style components
- Accent color is: text-accent, for a 50% transparent accent background use bg-accentbg
- Hover background is: bg-hoverbg
- Border color is "border", so use border-border
- Colors are also defined for error, warning, and success (text-error, text-warning, text-sucess)
Relevant TypeScript Types
type ViewComponentProps<T extends ViewModel> = {
blockId: string;
blockRef: React.RefObject<HTMLDivElement>;
contentRef: React.RefObject<HTMLDivElement>;
model: T;
};
type ViewComponent = React.FC<ViewComponentProps<any>>;
interface ViewModel {
viewType: string;
viewIcon?: jotai.Atom<string | IconButtonDecl>;
viewName?: jotai.Atom<string>;
viewText?: jotai.Atom<string | HeaderElem[]>;
preIconButton?: jotai.Atom<IconButtonDecl>;
endIconButtons?: jotai.Atom<IconButtonDecl[]>;
blockBg?: jotai.Atom<MetaType>;
manageConnection?: jotai.Atom<boolean>;
noPadding?: jotai.Atom<boolean>;
searchAtoms?: SearchAtoms;
viewComponent: ViewComponent;
dispose?: () => void;
giveFocus?: () => boolean;
keyDownHandler?: (e: WaveKeyboardEvent) => boolean;
}
interface IconButtonDecl {
elemtype: "iconbutton";
icon: string | React.ReactNode;
click?: (e: React.MouseEvent<any>) => void;
}
type HeaderElem =
| IconButtonDecl
| ToggleIconButtonDecl
| HeaderText
| HeaderInput
| HeaderDiv
| HeaderTextButton
| ConnectionButton
| MenuButton;
type IconButtonCommon = {
icon: string | React.ReactNode;
iconColor?: string;
iconSpin?: boolean;
className?: string;
title?: string;
disabled?: boolean;
noAction?: boolean;
};
type IconButtonDecl = IconButtonCommon & {
elemtype: "iconbutton";
click?: (e: React.MouseEvent<any>) => void;
longClick?: (e: React.MouseEvent<any>) => void;
};
type ToggleIconButtonDecl = IconButtonCommon & {
elemtype: "toggleiconbutton";
active: jotai.WritableAtom<boolean, [boolean], void>;
};
type HeaderTextButton = {
elemtype: "textbutton";
text: string;
className?: string;
title?: string;
onClick?: (e: React.MouseEvent<any>) => void;
};
type HeaderText = {
elemtype: "text";
text: string;
ref?: React.RefObject<HTMLDivElement>;
className?: string;
noGrow?: boolean;
onClick?: (e: React.MouseEvent<any>) => void;
};
type HeaderInput = {
elemtype: "input";
value: string;
className?: string;
isDisabled?: boolean;
ref?: React.RefObject<HTMLInputElement>;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
};
type HeaderDiv = {
elemtype: "div";
className?: string;
children: HeaderElem[];
onMouseOver?: (e: React.MouseEvent<any>) => void;
onMouseOut?: (e: React.MouseEvent<any>) => void;
onClick?: (e: React.MouseEvent<any>) => void;
};
type ConnectionButton = {
elemtype: "connectionbutton";
icon: string;
text: string;
iconColor: string;
onClick?: (e: React.MouseEvent<any>) => void;
connected: boolean;
};
type MenuItem = {
label: string;
icon?: string | React.ReactNode;
subItems?: MenuItem[];
onClick?: (e: React.MouseEvent<any>) => void;
};
type MenuButtonProps = {
items: MenuItem[];
className?: string;
text: string;
title?: string;
menuPlacement?: Placement;
};
type MenuButton = {
elemtype: "menubutton";
} & MenuButtonProps;
Minimal "Hello World" Example
This example defines a simple ViewModel and ViewComponent for a block that displays "Hello, World!".
import * as jotai from "jotai";
import React from "react";
class HelloWorldModel implements ViewModel {
viewType = "helloworld";
viewIcon = jotai.atom("smile");
viewName = jotai.atom("Hello World");
viewText = jotai.atom("A simple greeting block");
viewComponent = HelloWorldView;
}
const HelloWorldView: ViewComponent<HelloWorldModel> = ({ model }) => {
return <div style={{ padding: "10px" }}>Hello, World!</div>;
};
export { HelloWorldModel };
Instructions to AI
- Generate a new ViewModel class for a block, following the structure above.
- Generate a corresponding ViewComponent.
- Use Jotai atoms to store all dynamic state.
- Ensure the ViewModel defines header elements (
viewText,viewIcon,endIconButtons). - Export the view model (to be registered in the BlockRegistry)
- Use existing metadata patterns for config and settings.
Other Notes
- The types you see above don't need to be imported, they are global types (custom.d.ts)
Output Format:
- TypeScript code defining the ViewModel.
- TypeScript code defining the ViewComponent.
- Ensure alignment with the patterns in
waveai.tsx,preview.tsx,sysinfo.tsx, andterm.tsx.