stickers and terminal serialization (#57)

This commit is contained in:
Mike Sawka
2024-06-17 22:38:48 -07:00
committed by GitHub
parent b6c85e38f6
commit 4ded6d94b6
20 changed files with 670 additions and 147 deletions
+54 -2
View File
@@ -1,8 +1,13 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { LayoutTreeActionType, LayoutTreeInsertNodeAction, newLayoutNode } from "@/faraday/index";
import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
import { layoutTreeStateReducer } from "@/faraday/lib/layoutState";
import * as jotai from "jotai";
import * as rxjs from "rxjs";
import * as services from "./services";
import * as WOS from "./wos";
import { WSControl } from "./ws";
@@ -62,8 +67,6 @@ const atoms = {
workspace: workspaceAtom,
};
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
// key is "eventType" or "eventType|oref"
const eventSubjects = new Map<string, SubjectWithRef<WSEventType>>();
const fileSubjects = new Map<string, SubjectWithRef<WSFileEventData>>();
@@ -215,9 +218,58 @@ function getApi(): ElectronApi {
return (window as any).api;
}
async function createBlock(blockDef: BlockDef) {
const rtOpts: RuntimeOpts = { termsize: { rows: 25, cols: 80 } };
const blockId = await services.ObjectService.CreateBlock(blockDef, rtOpts);
const insertNodeAction: LayoutTreeInsertNodeAction<TabLayoutData> = {
type: LayoutTreeActionType.InsertNode,
node: newLayoutNode<TabLayoutData>(undefined, undefined, undefined, { blockId }),
};
const activeTabId = globalStore.get(atoms.uiContext).activetabid;
const layoutStateAtom = getLayoutStateAtomForTab(
activeTabId,
WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", activeTabId))
);
const curState = globalStore.get(layoutStateAtom);
globalStore.set(layoutStateAtom, layoutTreeStateReducer(curState, insertNodeAction));
}
// when file is not found, returns {data: null, fileInfo: null}
async function fetchWaveFile(
zoneId: string,
fileName: string,
offset?: number
): Promise<{ data: Uint8Array; fileInfo: WaveFile }> {
let usp = new URLSearchParams();
usp.set("zoneid", zoneId);
usp.set("name", fileName);
if (offset != null) {
usp.set("offset", offset.toString());
}
const resp = await fetch(getBackendHostPort() + "/wave/file?" + usp.toString());
if (!resp.ok) {
if (resp.status === 404) {
return { data: null, fileInfo: null };
}
throw new Error("error getting wave file: " + resp.statusText);
}
if (resp.status == 204) {
return { data: null, fileInfo: null };
}
let fileInfo64 = resp.headers.get("X-ZoneFileInfo");
if (fileInfo64 == null) {
throw new Error(`missing zone file info for ${zoneId}:${fileName}`);
}
let fileInfo = JSON.parse(atob(fileInfo64));
const data = await resp.arrayBuffer();
return { data: new Uint8Array(data), fileInfo };
}
export {
WOS,
atoms,
createBlock,
fetchWaveFile,
getApi,
getBackendHostPort,
getEventORefSubject,
+4
View File
@@ -7,6 +7,10 @@ import * as WOS from "./wos";
// blockservice.BlockService (block)
class BlockServiceType {
SaveTerminalState(arg2: string, arg3: string, arg4: string, arg5: number): Promise<void> {
return WOS.callBackendService("block", "SaveTerminalState", Array.from(arguments))
}
// send command to block
SendCommand(blockid: string, cmd: BlockCommand): Promise<void> {
return WOS.callBackendService("block", "SendCommand", Array.from(arguments))
+1
View File
@@ -27,4 +27,5 @@
/* z-index values */
--zindex-header-hover: 100;
--zindex-termstickers: 20;
}
+27
View File
@@ -9,6 +9,7 @@
overflow: hidden;
border-left: 4px solid transparent;
padding-left: 4px;
position: relative;
&:focus-within {
border-left: 4px solid var(--accent-color);
@@ -84,4 +85,30 @@
border: none;
}
}
.term-stickers {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: var(--zindex-termstickers);
pointer-events: none;
.term-sticker-image {
img {
object-fit: contain;
width: 100%;
height: 100%;
}
}
.term-sticker-svg {
svg {
object-fit: contain;
width: 100%;
height: 100%;
}
}
}
}
+23 -94
View File
@@ -1,17 +1,8 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import {
WOS,
atoms,
getBackendHostPort,
getFileSubject,
globalStore,
sendWSCommand,
useBlockAtom,
} from "@/store/global";
import { WOS, atoms, globalStore, sendWSCommand, useBlockAtom } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray } from "@/util/util";
import { FitAddon } from "@xterm/addon-fit";
import type { ITheme } from "@xterm/xterm";
import { Terminal } from "@xterm/xterm";
@@ -20,9 +11,10 @@ import { produce } from "immer";
import * as jotai from "jotai";
import * as React from "react";
import { IJsonView } from "./ijson";
import { TermStickers } from "./termsticker";
import { TermWrap } from "./termwrap";
import "public/xterm.css";
import { debounce } from "throttle-debounce";
import "./term.less";
function getThemeFromCSSVars(el: Element): ITheme {
@@ -149,7 +141,7 @@ function setBlockFocus(blockId: string) {
const TerminalView = ({ blockId }: { blockId: string }) => {
const connectElemRef = React.useRef<HTMLDivElement>(null);
const termRef = React.useRef<Terminal>(null);
const termRef = React.useRef<TermWrap>(null);
const initialLoadRef = React.useRef<InitialLoadDataType>({ loaded: false, heldData: [] });
const htmlElemFocusRef = React.useRef<HTMLInputElement>(null);
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
@@ -161,8 +153,7 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
});
const isFocused = jotai.useAtomValue(isFocusedAtom);
React.useEffect(() => {
console.log("terminal created");
const newTerm = new Terminal({
const termWrap = new TermWrap(blockId, connectElemRef.current, {
theme: getThemeFromCSSVars(connectElemRef.current),
fontSize: 12,
fontFamily: "Hack",
@@ -170,93 +161,22 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
fontWeight: "normal",
fontWeightBold: "bold",
});
termRef.current = newTerm;
const newFitAddon = new FitAddon();
newTerm.loadAddon(newFitAddon);
newTerm.open(connectElemRef.current);
newFitAddon.fit();
sendWSCommand({
wscommand: "setblocktermsize",
blockid: blockId,
termsize: { rows: newTerm.rows, cols: newTerm.cols },
});
connectElemRef.current.addEventListener(
"keydown",
(ev) => {
if (ev.code == "Escape" && ev.metaKey) {
ev.preventDefault();
ev.stopPropagation();
const metaCmd: BlockSetMetaCommand = { command: "setmeta", meta: { "term:mode": "html" } };
services.BlockService.SendCommand(blockId, metaCmd);
return false;
}
},
true
);
newTerm.onData((data) => {
const b64data = btoa(data);
const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data };
services.BlockService.SendCommand(blockId, inputCmd);
});
newTerm.textarea.addEventListener("focus", () => {
(window as any).term = termWrap;
termRef.current = termWrap;
termWrap.addFocusListener(() => {
setBlockFocus(blockId);
});
const mainFileSubject = getFileSubject(blockId, "main");
mainFileSubject.subscribe((msg: WSFileEventData) => {
if (msg.fileop != "append") {
console.log("bad fileop for terminal", msg);
return;
}
const decodedData = base64ToArray(msg.data64);
if (initialLoadRef.current.loaded) {
newTerm.write(decodedData);
} else {
initialLoadRef.current.heldData.push(decodedData);
}
});
// load data from filestore
const startTs = Date.now();
let loadedBytes = 0;
const localTerm = termRef.current; // avoids devmode double effect running issue (terminal gets created twice)
const usp = new URLSearchParams();
usp.set("zoneid", blockId);
usp.set("name", "main");
fetch(getBackendHostPort() + "/wave/file?" + usp.toString())
.then((resp) => {
if (resp.ok) {
return resp.arrayBuffer();
}
console.log("error loading file", resp.status, resp.statusText);
})
.then((data: ArrayBuffer) => {
const uint8View = new Uint8Array(data);
localTerm.write(uint8View);
loadedBytes = uint8View.byteLength;
})
.finally(() => {
initialLoadRef.current.heldData.forEach((data) => {
localTerm.write(data);
});
initialLoadRef.current.loaded = true;
initialLoadRef.current.heldData = [];
console.log(`terminal loaded file ${loadedBytes} bytes, ${Date.now() - startTs}ms`);
});
const resize_debounced = debounce(50, () => {
handleResize(newFitAddon, blockId, newTerm);
});
const rszObs = new ResizeObserver(() => {
resize_debounced();
termWrap.handleResize_debounced();
});
rszObs.observe(connectElemRef.current);
termWrap.initTerminal();
return () => {
newTerm.dispose();
mainFileSubject.release();
termWrap.dispose();
};
}, []);
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const handleHtmlKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.code === "Escape" && event.metaKey) {
// reset term:mode
const metaCmd: BlockSetMetaCommand = { command: "setmeta", meta: { "term:mode": null } };
@@ -280,15 +200,24 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
React.useEffect(() => {
if (isFocused && termMode == "term") {
termRef.current?.focus();
termRef.current?.terminal.focus();
}
if (isFocused && termMode == "html") {
htmlElemFocusRef.current?.focus();
}
});
let stickerConfig = {
charWidth: 8,
charHeight: 16,
rows: termRef.current?.terminal.rows ?? 24,
cols: termRef.current?.terminal.cols ?? 80,
blockId: blockId,
};
return (
<div className={clsx("view-term", "term-mode-" + termMode, isFocused ? "is-focused" : null)}>
<TermStickers config={stickerConfig} />
<div key="conntectElem" className="term-connectelem" ref={connectElemRef}></div>
<div
key="htmlElem"
@@ -305,7 +234,7 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
type="text"
value={""}
ref={htmlElemFocusRef}
onKeyDown={handleKeyDown}
onKeyDown={handleHtmlKeyDown}
onChange={() => {}}
/>
</div>
+204
View File
@@ -0,0 +1,204 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { createBlock, getBackendHostPort } from "@/store/global";
import * as services from "@/store/services";
import clsx from "clsx";
import * as jotai from "jotai";
import * as React from "react";
import GaugeChart from "react-gauge-chart";
import "./term.less";
type StickerType = {
position: "absolute";
top?: number;
left?: number;
right?: number;
bottom?: number;
width?: number;
height?: number;
color?: string;
opacity?: number;
pointerevents?: boolean;
fontsize?: number;
transform?: string;
stickertype: "icon" | "image" | "gauge";
icon?: string;
imgsrc?: string;
clickcmd?: string;
clickblockdef?: BlockDef;
};
type StickerTermConfig = {
charWidth: number;
charHeight: number;
rows: number;
cols: number;
blockId: string;
};
function convertWidthDimToPx(dim: number, config: StickerTermConfig) {
if (dim == null) {
return null;
}
return dim * config.charWidth;
}
function convertHeightDimToPx(dim: number, config: StickerTermConfig) {
if (dim == null) {
return null;
}
return dim * config.charHeight;
}
var valueAtom = jotai.atom(Math.random() * 100);
function GaugeSticker() {
let [value, setValue] = jotai.useAtom(valueAtom);
React.useEffect(() => {
let interval = setInterval(() => {
var amt = Math.random() * 10 - 5;
setValue((value) => Math.max(0, Math.min(100, value + amt)));
}, 1000);
return () => clearInterval(interval);
});
return <GaugeChart id="gauge-chart1" nrOfLevels={20} percent={value / 100} />;
}
function TermSticker({ sticker, config }: { sticker: StickerType; config: StickerTermConfig }) {
let style: React.CSSProperties = {
position: sticker.position,
top: convertHeightDimToPx(sticker.top, config),
left: convertWidthDimToPx(sticker.left, config),
right: convertWidthDimToPx(sticker.right, config),
bottom: convertHeightDimToPx(sticker.bottom, config),
width: convertWidthDimToPx(sticker.width, config),
height: convertHeightDimToPx(sticker.height, config),
color: sticker.color,
fontSize: sticker.fontsize,
transform: sticker.transform,
opacity: sticker.opacity,
fill: sticker.color,
stroke: sticker.color,
};
if (sticker.pointerevents) {
style.pointerEvents = "auto";
}
if (style.width != null) {
style.overflowX = "hidden";
}
if (style.height != null) {
style.overflowY = "hidden";
}
let clickHandler = null;
if (sticker.pointerevents && (sticker.clickcmd || sticker.clickblockdef)) {
style.cursor = "pointer";
clickHandler = () => {
console.log("clickHandler", sticker.clickcmd, sticker.clickblockdef);
if (sticker.clickcmd) {
const b64data = btoa(sticker.clickcmd);
const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data };
services.BlockService.SendCommand(config.blockId, inputCmd);
}
if (sticker.clickblockdef) {
createBlock(sticker.clickblockdef);
}
};
}
if (sticker.stickertype == "icon") {
return (
<div className="term-sticker" style={style} onClick={clickHandler}>
<i className={clsx("fa", "fa-" + sticker.icon)} />
</div>
);
}
if (sticker.stickertype == "image") {
if (sticker.imgsrc == null) {
return null;
}
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(sticker.imgsrc);
return (
<div className="term-sticker term-sticker-image" style={style} onClick={clickHandler}>
<img src={streamingUrl} />
</div>
);
}
if (sticker.stickertype == "gauge") {
return (
<div className="term-sticker term-sticker-gauge" style={style}>
<GaugeSticker />
</div>
);
}
return null;
}
export function TermStickers({ config }: { config: StickerTermConfig }) {
let stickers: StickerType[] = [];
if (config.blockId.startsWith("d1eaddcb")) {
stickers.push({
position: "absolute",
top: 5,
right: 7,
stickertype: "icon",
icon: "paw",
color: "#40cc40aa",
fontsize: 30,
transform: "rotate(-18deg)",
pointerevents: true,
clickcmd: "ls\n",
});
stickers.push({
position: "absolute",
top: 8,
right: 8,
stickertype: "icon",
icon: "paw",
color: "#4040ccaa",
fontsize: 30,
transform: "rotate(-20deg)",
pointerevents: true,
clickcmd: "git status\n",
});
stickers.push({
position: "absolute",
top: 10,
right: 5,
stickertype: "icon",
icon: "paw",
color: "#cc4040aa",
fontsize: 30,
transform: "rotate(-15deg)",
pointerevents: true,
clickcmd: "cd ~/work/wails/thenextwave\n",
});
stickers.push({
position: "absolute",
top: 18,
right: 8,
stickertype: "image",
width: 12,
height: 6,
imgsrc: "~/Downloads/natureicon.png",
opacity: 0.8,
pointerevents: true,
clickblockdef: { view: "preview", meta: { file: "~/" } },
});
stickers.push({
position: "absolute",
top: 2,
right: 25,
width: 20,
stickertype: "gauge",
opacity: 0.7,
});
}
return (
<div className="term-stickers">
{stickers.map((sticker, i) => (
<TermSticker key={i} sticker={sticker} config={config} />
))}
</div>
);
}
+172
View File
@@ -0,0 +1,172 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { fetchWaveFile, getFileSubject, sendWSCommand } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray } from "@/util/util";
import { FitAddon } from "@xterm/addon-fit";
import { SerializeAddon } from "@xterm/addon-serialize";
import * as TermTypes from "@xterm/xterm";
import { Terminal } from "@xterm/xterm";
import { debounce } from "throttle-debounce";
export class TermWrap {
blockId: string;
ptyOffset: number;
dataBytesProcessed: number;
terminal: Terminal;
connectElem: HTMLDivElement;
fitAddon: FitAddon;
serializeAddon: SerializeAddon;
mainFileSubject: SubjectWithRef<Uint8Array>;
loaded: boolean;
heldData: Uint8Array[];
handleResize_debounced: () => void;
constructor(
blockId: string,
connectElem: HTMLDivElement,
options?: TermTypes.ITerminalOptions & TermTypes.ITerminalInitOnlyOptions
) {
this.blockId = blockId;
this.ptyOffset = 0;
this.dataBytesProcessed = 0;
this.terminal = new Terminal(options);
this.fitAddon = new FitAddon();
this.serializeAddon = new SerializeAddon();
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.connectElem = connectElem;
this.mainFileSubject = null;
this.loaded = false;
this.heldData = [];
this.handleResize_debounced = debounce(50, this.handleResize.bind(this));
this.terminal.open(this.connectElem);
this.handleResize();
}
async initTerminal() {
this.connectElem.addEventListener("keydown", this.keydownListener.bind(this), true);
this.terminal.onData(this.handleTermData.bind(this));
this.mainFileSubject = getFileSubject(this.blockId, "main");
this.mainFileSubject.subscribe(this.handleNewFileSubjectData.bind(this));
try {
await this.loadInitialTerminalData();
} finally {
this.loaded = true;
}
this.runProcessIdleTimeout();
}
dispose() {
this.terminal.dispose();
this.mainFileSubject.release();
}
handleTermData(data: string) {
const b64data = btoa(data);
if (b64data.length < 512) {
const wsCmd: BlockInputWSCommand = { wscommand: "blockinput", blockid: this.blockId, inputdata64: b64data };
sendWSCommand(wsCmd);
} else {
const inputCmd: BlockInputCommand = { command: "controller:input", inputdata64: b64data };
services.BlockService.SendCommand(this.blockId, inputCmd);
}
}
addFocusListener(focusFn: () => void) {
this.terminal.textarea.addEventListener("focus", focusFn);
}
keydownListener(ev: KeyboardEvent) {
if (ev.code == "Escape" && ev.metaKey) {
ev.preventDefault();
ev.stopPropagation();
const metaCmd: BlockSetMetaCommand = { command: "setmeta", meta: { "term:mode": "html" } };
services.BlockService.SendCommand(this.blockId, metaCmd);
return false;
}
}
handleNewFileSubjectData(msg: WSFileEventData) {
if (msg.fileop != "append") {
console.log("bad fileop for terminal", msg);
return;
}
const decodedData = base64ToArray(msg.data64);
if (this.loaded) {
this.doTerminalWrite(decodedData, null);
} else {
this.heldData.push(decodedData);
}
}
doTerminalWrite(data: string | Uint8Array, setPtyOffset?: number): Promise<void> {
let resolve: () => void = null;
let prtn = new Promise<void>((presolve, _) => {
resolve = presolve;
});
this.terminal.write(data, () => {
if (setPtyOffset != null) {
this.ptyOffset = setPtyOffset;
} else {
this.ptyOffset += data.length;
this.dataBytesProcessed += data.length;
}
resolve();
});
return prtn;
}
async loadInitialTerminalData(): Promise<void> {
let startTs = Date.now();
const { data: cacheData, fileInfo: cacheFile } = await fetchWaveFile(this.blockId, "cache:term:full");
let ptyOffset = 0;
if (cacheFile != null) {
ptyOffset = cacheFile.meta["ptyoffset"] ?? 0;
if (cacheData.byteLength > 0) {
this.doTerminalWrite(cacheData, ptyOffset);
}
}
const { data: mainData, fileInfo: mainFile } = await fetchWaveFile(this.blockId, "main", ptyOffset);
console.log(
`terminal loaded cachefile:${cacheData?.byteLength ?? 0} main:${mainData?.byteLength ?? 0} bytes, ${Date.now() - startTs}ms`
);
if (mainFile != null) {
await this.doTerminalWrite(mainData, null);
}
}
handleResize() {
const oldRows = this.terminal.rows;
const oldCols = this.terminal.cols;
this.fitAddon.fit();
if (oldRows !== this.terminal.rows || oldCols !== this.terminal.cols) {
const wsCommand: SetBlockTermSizeWSCommand = {
wscommand: "setblocktermsize",
blockid: this.blockId,
termsize: { rows: this.terminal.rows, cols: this.terminal.cols },
};
sendWSCommand(wsCommand);
}
}
processAndCacheData() {
if (this.dataBytesProcessed < 10 * 1024) {
return;
}
const serializedOutput = this.serializeAddon.serialize();
console.log("idle timeout term", this.dataBytesProcessed, serializedOutput.length);
services.BlockService.SaveTerminalState(this.blockId, serializedOutput, "full", this.ptyOffset);
this.dataBytesProcessed = 0;
}
runProcessIdleTimeout() {
setTimeout(() => {
window.requestIdleCallback(() => {
this.processAndCacheData();
this.runProcessIdleTimeout();
});
}, 5000);
}
}
+1 -29
View File
@@ -3,41 +3,13 @@
import { TabBar } from "@/app/tab/tabbar";
import { TabContent } from "@/app/tab/tabcontent";
import { atoms } from "@/store/global";
import * as services from "@/store/services";
import * as WOS from "@/store/wos";
import { atoms, createBlock } from "@/store/global";
import * as jotai from "jotai";
import { CenteredDiv } from "../element/quickelems";
import { LayoutTreeActionType, LayoutTreeInsertNodeAction, newLayoutNode } from "@/faraday/index";
import { getLayoutStateAtomForTab, useLayoutTreeStateReducerAtom } from "@/faraday/lib/layoutAtom";
import { useMemo } from "react";
import "./workspace.less";
function Widgets() {
const windowData = jotai.useAtomValue(atoms.waveWindow);
const activeTabAtom = useMemo(() => {
return getLayoutStateAtomForTab(
windowData.activetabid,
WOS.getWaveObjectAtom<Tab>(WOS.makeORef("tab", windowData.activetabid))
);
}, [windowData.activetabid]);
const [, dispatchLayoutStateAction] = useLayoutTreeStateReducerAtom(activeTabAtom);
function addBlockToTab(blockId: string) {
const insertNodeAction: LayoutTreeInsertNodeAction<TabLayoutData> = {
type: LayoutTreeActionType.InsertNode,
node: newLayoutNode<TabLayoutData>(undefined, undefined, undefined, { blockId }),
};
dispatchLayoutStateAction(insertNodeAction);
}
async function createBlock(blockDef: BlockDef) {
const rtOpts: RuntimeOpts = { termsize: { rows: 25, cols: 80 } };
const blockId = await services.ObjectService.CreateBlock(blockDef, rtOpts);
addBlockToTab(blockId);
}
async function clickTerminal() {
const termBlockDef = {
controller: "shell",
+2
View File
@@ -10,6 +10,8 @@ declare global {
isDev: () => boolean;
isDevServer: () => boolean;
};
type SubjectWithRef<T> = rxjs.Subject<T> & { refCount: number; release: () => void };
}
export {};
+64 -16
View File
@@ -4,12 +4,14 @@
// generated by cmd/generate/main-generate.go
declare global {
// wstore.Block
type Block = WaveObj & {
blockdef: BlockDef;
controller: string;
view: string;
runtimeopts?: RuntimeOpts;
stickers?: StickerType[];
meta: MetaType;
};
@@ -29,28 +31,20 @@ declare global {
type BlockCommand = {
command: string;
} & (
| BlockSetMetaCommand
| BlockGetMetaCommand
| BlockMessageCommand
| BlockAppendFileCommand
| BlockAppendIJsonCommand
| BlockInputCommand
| BlockSetViewCommand
);
} & ( BlockAppendIJsonCommand | ResolveIdsCommand | BlockInputCommand | BlockSetViewCommand | BlockSetMetaCommand | BlockGetMetaCommand | BlockMessageCommand | BlockAppendFileCommand );
// wstore.BlockDef
type BlockDef = {
controller?: string;
view?: string;
files?: { [key: string]: FileDef };
files?: {[key: string]: FileDef};
meta?: MetaType;
};
// wshutil.BlockGetMetaCommand
type BlockGetMetaCommand = {
command: "getmeta";
oid: string;
oref: string;
};
// wshutil.BlockInputCommand
@@ -61,6 +55,13 @@ declare global {
termsize?: TermSize;
};
// webcmd.BlockInputWSCommand
type BlockInputWSCommand = {
wscommand: "blockinput";
blockid: string;
inputdata64: string;
};
// wshutil.BlockMessageCommand
type BlockMessageCommand = {
command: "message";
@@ -70,7 +71,7 @@ declare global {
// wshutil.BlockSetMetaCommand
type BlockSetMetaCommand = {
command: "setmeta";
oid?: string;
oref?: string;
meta: MetaType;
};
@@ -106,6 +107,14 @@ declare global {
mimetype?: string;
};
// filestore.FileOptsType
type FileOptsType = {
maxsize?: number;
circular?: boolean;
ijson?: boolean;
ijsonbudget?: number;
};
// fileservice.FullFile
type FullFile = {
info: FileInfo;
@@ -118,7 +127,7 @@ declare global {
meta?: MetaType;
};
type MetaType = { [key: string]: any };
type MetaType = {[key: string]: any}
// tsgenmeta.MethodMeta
type MethodMeta = {
@@ -139,6 +148,12 @@ declare global {
y: number;
};
// wshutil.ResolveIdsCommand
type ResolveIdsCommand = {
command: "resolveids";
ids: string[];
};
// wstore.RuntimeOpts
type RuntimeOpts = {
termsize?: TermSize;
@@ -152,6 +167,27 @@ declare global {
termsize: TermSize;
};
// wstore.StickerClickOptsType
type StickerClickOptsType = {
sendinput?: string;
createblock?: BlockDef;
};
// wstore.StickerDisplayOptsType
type StickerDisplayOptsType = {
icon: string;
imgsrc: string;
svgblob?: string;
};
// wstore.StickerType
type StickerType = {
stickertype: string;
style: MetaType;
clickopts?: StickerClickOptsType;
display: StickerDisplayOptsType;
};
// wstore.Tab
type Tab = WaveObj & {
name: string;
@@ -174,7 +210,7 @@ declare global {
type WSCommandType = {
wscommand: string;
} & SetBlockTermSizeWSCommand;
} & ( SetBlockTermSizeWSCommand | BlockInputWSCommand );
// eventbus.WSEventType
type WSEventType = {
@@ -191,6 +227,17 @@ declare global {
data64: string;
};
// filestore.WaveFile
type WaveFile = {
zoneid: string;
name: string;
opts: FileOptsType;
createdts: number;
size: number;
modts: number;
meta: MetaType;
};
// waveobj.WaveObj
type WaveObj = {
otype: string;
@@ -233,7 +280,7 @@ declare global {
workspaceid: string;
activetabid: string;
activeblockid?: string;
activeblockmap: { [key: string]: string };
activeblockmap: {[key: string]: string};
pos: Point;
winsize: WinSize;
lastfocusts: number;
@@ -246,6 +293,7 @@ declare global {
tabids: string[];
meta: MetaType;
};
}
export {};
export {}