aiprompts/view-prompt.md
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.
ViewModel Structure
ViewModel interface.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.dispose(), giveFocus(), keyDownHandler().ViewComponent Structure
ViewComponentProps<T extends ViewModel>.blockId, blockRef, contentRef, and model as props.Header Elements (HeaderElem[])
IconButtonDecl): Clickable buttons.HeaderText): Metadata or status.HeaderInput): Editable fields.MenuButton): Dropdowns.Jotai Atoms for State Management
atom<T>, PrimitiveAtom<T>, WritableAtom<T> for dynamic properties.splitAtom for managing lists of atoms.globalStore and override with block metadata.Metadata vs. Global Config
SetMetaCommand): Each block persists its own configuration in its metadata (blockAtom.meta).SetConfigCommand): Provides default settings for all blocks, stored in config files.SetMetaCommand (persisted per block).SetConfigCommand (applies globally unless overridden).Useful Helper Functions
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.Styling
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;
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 };
viewText, viewIcon, endIconButtons).Output Format:
waveai.tsx, preview.tsx, sysinfo.tsx, and term.tsx.