Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions frontend/app/block/block.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import {
Expand All @@ -9,12 +9,15 @@ import {
FullSubBlockProps,
SubBlockProps,
} from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import { useTabModel } from "@/app/store/tab-model";
import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff";
import { LauncherViewModel } from "@/app/view/launcher/launcher";
import { PreviewModel } from "@/app/view/preview/preview-model";
import { SysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
import { TsunamiViewModel } from "@/app/view/tsunami/tsunami";
import { VDomModel } from "@/app/view/vdom/vdom-model";
import { useWaveEnv, WaveEnv } from "@/app/waveenv/waveenv";
import { ErrorBoundary } from "@/element/errorboundary";
import { CenteredDiv } from "@/element/quickelems";
import { useDebouncedNodeInnerRect } from "@/layout/index";
Expand All @@ -26,8 +29,6 @@ import {
registerBlockComponentModel,
unregisterBlockComponentModel,
} from "@/store/global";
import type { TabModel } from "@/app/store/tab-model";
import { useTabModel } from "@/app/store/tab-model";
import { getWaveObjectAtom, makeORef, useWaveObjectValue } from "@/store/wos";
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
import { isBlank, useAtomValueSafe } from "@/util/util";
Expand Down Expand Up @@ -59,10 +60,16 @@ BlockRegistry.set("tsunami", TsunamiViewModel);
BlockRegistry.set("aifilediff", AiFileDiffViewModel);
BlockRegistry.set("waveconfig", WaveConfigViewModel);

function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel, tabModel: TabModel): ViewModel {
function makeViewModel(
blockId: string,
blockView: string,
nodeModel: BlockNodeModel,
tabModel: TabModel,
waveEnv: WaveEnv
): ViewModel {
const ctor = BlockRegistry.get(blockView);
if (ctor != null) {
return new ctor(blockId, nodeModel, tabModel);
return new ctor({ blockId, nodeModel, tabModel, waveEnv });
}
return makeDefaultViewModel(blockId, blockView);
}
Expand All @@ -86,7 +93,7 @@ function getViewElem(

function makeDefaultViewModel(blockId: string, viewType: string): ViewModel {
const blockDataAtom = getWaveObjectAtom<Block>(makeORef("block", blockId));
let viewModel: ViewModel = {
const viewModel: ViewModel = {
viewType: viewType,
viewIcon: atom((get) => {
const blockData = get(blockDataAtom);
Expand Down Expand Up @@ -308,11 +315,12 @@ const Block = memo((props: BlockProps) => {
counterInc("render-Block");
counterInc("render-Block-" + props.nodeModel?.blockId?.substring(0, 8));
const tabModel = useTabModel();
const waveEnv = useWaveEnv();
const [blockData, loading] = useWaveObjectValue<Block>(makeORef("block", props.nodeModel.blockId));
const bcm = getBlockComponentModel(props.nodeModel.blockId);
let viewModel = bcm?.viewModel;
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel);
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel, waveEnv);
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
}
useEffect(() => {
Expand All @@ -334,11 +342,12 @@ const SubBlock = memo((props: SubBlockProps) => {
counterInc("render-Block");
counterInc("render-Block-" + props.nodeModel?.blockId?.substring(0, 8));
const tabModel = useTabModel();
const waveEnv = useWaveEnv();
const [blockData, loading] = useWaveObjectValue<Block>(makeORef("block", props.nodeModel.blockId));
const bcm = getBlockComponentModel(props.nodeModel.blockId);
let viewModel = bcm?.viewModel;
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel);
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel, tabModel, waveEnv);
registerBlockComponentModel(props.nodeModel.blockId, { viewModel });
}
useEffect(() => {
Expand Down
53 changes: 25 additions & 28 deletions frontend/app/store/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,6 @@ function getBlockMetaKeyAtom<T extends keyof MetaType>(blockId: string, key: T):
return metaAtom;
}

function useBlockMetaKeyAtom<T extends keyof MetaType>(blockId: string, key: T): MetaType[T] {
return useAtomValue(getBlockMetaKeyAtom(blockId, key));
}

function getOrefMetaKeyAtom<T extends keyof MetaType>(oref: string, key: T): Atom<MetaType[T]> {
const orefCache = getSingleOrefAtomCache(oref);
const metaAtomName = "#meta-" + key;
Expand Down Expand Up @@ -614,33 +610,34 @@ function subscribeToConnEvents() {
});
}

function makeDefaultConnStatus(conn: string): ConnStatus {
if (isLocalConnName(conn)) {
return {
connection: conn,
connected: true,
error: null,
status: "connected",
hasconnected: true,
activeconnnum: 0,
wshenabled: false,
};
}
return {
connection: conn,
connected: false,
error: null,
status: "disconnected",
hasconnected: false,
activeconnnum: 0,
wshenabled: false,
};
}

function getConnStatusAtom(conn: string): PrimitiveAtom<ConnStatus> {
const connStatusMap = globalStore.get(ConnStatusMapAtom);
let rtn = connStatusMap.get(conn);
if (rtn == null) {
if (isLocalConnName(conn)) {
const connStatus: ConnStatus = {
connection: conn,
connected: true,
error: null,
status: "connected",
hasconnected: true,
activeconnnum: 0,
wshenabled: false,
};
rtn = atom(connStatus);
} else {
const connStatus: ConnStatus = {
connection: conn,
connected: false,
error: null,
status: "disconnected",
hasconnected: false,
activeconnnum: 0,
wshenabled: false,
};
rtn = atom(connStatus);
}
rtn = atom(makeDefaultConnStatus(conn));
const newConnStatusMap = new Map(connStatusMap);
newConnStatusMap.set(conn, rtn);
globalStore.set(ConnStatusMapAtom, newConnStatusMap);
Expand Down Expand Up @@ -692,6 +689,7 @@ export {
initGlobalWaveEventSubs,
isDev,
loadConnStatus,
makeDefaultConnStatus,
openLink,
readAtom,
recordTEvent,
Expand All @@ -706,7 +704,6 @@ export {
useBlockAtom,
useBlockCache,
useBlockDataLoaded,
useBlockMetaKeyAtom,
useOrefMetaKeyAtom,
useOverrideConfigAtom,
useSettingsKeyAtom,
Expand Down
11 changes: 3 additions & 8 deletions frontend/app/store/wos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

// WaveObjectStore

import { waveEventSubscribeSingle } from "@/app/store/wps";
import { isPreviewWindow } from "@/app/store/windowtype";
import { waveEventSubscribeSingle } from "@/app/store/wps";
import { getWebServerEndpoint } from "@/util/endpoints";
import { fetch } from "@/util/fetchutil";
import { fireAndForget } from "@/util/util";
Expand Down Expand Up @@ -218,14 +218,9 @@ function loadAndPinWaveObject<T extends WaveObj>(oref: string): Promise<T> {
return wov.pendingPromise;
}

function getWaveObjectAtom<T extends WaveObj>(oref: string): WritableWaveObjectAtom<T> {
function getWaveObjectAtom<T extends WaveObj>(oref: string): Atom<T> {
const wov = getWaveObjectValue<T>(oref);
return atom(
(get) => get(wov.dataAtom).value,
(_get, set, value: T) => {
setObjectValue(value, set, true);
}
);
return atom((get) => get(wov.dataAtom).value);
}

function getWaveObjectLoadingAtom(oref: string): Atom<boolean> {
Expand Down
22 changes: 2 additions & 20 deletions frontend/app/tab/tabbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { Button } from "@/app/element/button";
Expand Down Expand Up @@ -149,24 +149,6 @@ function strArrayIsEqual(a: string[], b: string[]) {
return true;
}

function setIsEqual(a: Set<string> | null, b: Set<string> | null): boolean {
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.size !== b.size) {
return false;
}
for (const item of a) {
if (!b.has(item)) {
return false;
}
}
return true;
}

const TabBar = memo(({ workspace }: TabBarProps) => {
const [tabIds, setTabIds] = useState<string[]>([]);
const [dragStartPositions, setDragStartPositions] = useState<number[]>([]);
Expand Down Expand Up @@ -506,7 +488,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
[]
);

const handleMouseUp = (event: MouseEvent) => {
const handleMouseUp = (_event: MouseEvent) => {
const { tabIndex, dragged } = draggingTabDataRef.current;

// Update the final position of the dragged tab
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/view/aifilediff/aifilediff.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import type { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { base64ToString } from "@/util/util";
import { DiffViewer } from "@/app/view/codeeditor/diffviewer";
import { globalStore, WOS } from "@/store/global";
import { base64ToString } from "@/util/util";
import * as jotai from "jotai";
import { useEffect } from "react";

Expand All @@ -30,7 +30,7 @@ export class AiFileDiffViewModel implements ViewModel {
viewName: jotai.Atom<string>;
viewText: jotai.Atom<string>;

constructor(blockId: string, nodeModel: BlockNodeModel, tabModel: TabModel) {
constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
this.blockId = blockId;
this.nodeModel = nodeModel;
this.tabModel = tabModel;
Expand Down
8 changes: 3 additions & 5 deletions frontend/app/view/helpview/helpview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import { globalStore, WOS } from "@/app/store/global";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
Expand All @@ -16,8 +14,8 @@ class HelpViewModel extends WebViewModel {
return HelpView;
}

constructor(blockId: string, nodeModel: BlockNodeModel, tabModel: TabModel) {
super(blockId, nodeModel, tabModel);
constructor(initOpts: ViewModelInitType) {
super(initOpts);
this.viewText = atom((get) => {
// force a dependency on meta.url so we re-render the buttons when the url changes
void (get(this.blockAtom)?.meta?.url || get(this.homepageUrl));
Expand Down
8 changes: 4 additions & 4 deletions frontend/app/view/launcher/launcher.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import type { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import logoUrl from "@/app/asset/logo.svg?url";
import type { BlockNodeModel } from "@/app/block/blocktypes";
import { atoms, globalStore, replaceBlock } from "@/app/store/global";
import type { TabModel } from "@/app/store/tab-model";
import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
import { isBlank, makeIconClass } from "@/util/util";
import clsx from "clsx";
Expand Down Expand Up @@ -35,7 +35,7 @@ export class LauncherViewModel implements ViewModel {
containerSize = atom({ width: 0, height: 0 });
gridLayout: GridLayoutType = null;

constructor(blockId: string, nodeModel: BlockNodeModel, tabModel: TabModel) {
constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
this.blockId = blockId;
this.nodeModel = nodeModel;
this.tabModel = tabModel;
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/view/preview/preview-model.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import { ContextMenuModel } from "@/app/store/contextmenu";
import type { TabModel } from "@/app/store/tab-model";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { getConnStatusAtom, getOverrideConfigAtom, getSettingsKeyAtom, globalStore, refocusNode } from "@/store/global";
Expand Down Expand Up @@ -168,7 +168,7 @@ export class PreviewModel implements ViewModel {
directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;
codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean;

constructor(blockId: string, nodeModel: BlockNodeModel, tabModel: TabModel) {
constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
this.viewType = "preview";
this.blockId = blockId;
this.nodeModel = nodeModel;
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/view/quicktipsview/quicktipsview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2025, Command Line Inc.
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import type { BlockNodeModel } from "@/app/block/blocktypes";
import type { TabModel } from "@/app/store/tab-model";
import { QuickTips } from "@/app/element/quicktips";
import { globalStore } from "@/app/store/global";
import type { TabModel } from "@/app/store/tab-model";
import { Atom, atom, PrimitiveAtom } from "jotai";

class QuickTipsViewModel implements ViewModel {
Expand All @@ -15,7 +15,7 @@ class QuickTipsViewModel implements ViewModel {
showTocAtom: PrimitiveAtom<boolean>;
endIconButtons: Atom<IconButtonDecl[]>;

constructor(blockId: string, nodeModel: BlockNodeModel, tabModel: TabModel) {
constructor({ blockId, nodeModel, tabModel }: ViewModelInitType) {
this.blockId = blockId;
this.nodeModel = nodeModel;
this.tabModel = tabModel;
Expand Down
Loading
Loading