move tsunami views to webviews. also fix git+ssh urls in package-lock.json (#2355)

This commit is contained in:
Mike Sawka 2025-09-15 17:11:24 -07:00 committed by GitHub
parent 9dd216aba9
commit fb4125465b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 28 deletions

View file

@ -6,28 +6,29 @@ import { atoms, globalStore, WOS } from "@/app/store/global";
import { waveEventSubscribe } from "@/app/store/wps";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import { WebView, WebViewModel } from "@/app/view/webview/webview";
import * as services from "@/store/services";
import * as jotai from "jotai";
import { memo, useEffect } from "react";
class TsunamiViewModel implements ViewModel {
viewType: string;
blockAtom: jotai.Atom<Block>;
blockId: string;
viewIcon: jotai.Atom<string>;
viewName: jotai.Atom<string>;
class TsunamiViewModel extends WebViewModel {
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
shellProcStatusUnsubFn: () => void;
isRestarting: jotai.PrimitiveAtom<boolean>;
constructor(blockId: string, nodeModel: BlockNodeModel) {
super(blockId, nodeModel);
this.viewType = "tsunami";
this.blockId = blockId;
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
this.viewIcon = jotai.atom("cube");
this.viewName = jotai.atom("Tsunami");
this.isRestarting = jotai.atom(false);
// Hide navigation bar (URL bar, back/forward/home buttons)
this.hideNav = jotai.atom(true);
// Set custom partition for tsunami WebView isolation
this.partitionOverride = jotai.atom(`tsunami:${blockId}`);
this.shellProcFullStatus = jotai.atom(null) as jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
const initialShellProcStatus = services.BlockService.GetControllerStatus(blockId);
initialShellProcStatus.then((rts) => {
@ -94,23 +95,32 @@ class TsunamiViewModel implements ViewModel {
}
getSettingsMenuItems(): ContextMenuItem[] {
return [];
const items = super.getSettingsMenuItems();
// Filter out homepage and navigation-related menu items for tsunami view
return items.filter((item) => {
const label = item.label?.toLowerCase() || "";
return (
!label.includes("homepage") &&
!label.includes("home page") &&
!label.includes("navigation") &&
!label.includes("nav")
);
});
}
}
type TsunamiViewProps = {
model: TsunamiViewModel;
};
const TsunamiView = memo(({ model }: TsunamiViewProps) => {
const TsunamiView = memo((props: ViewComponentProps<TsunamiViewModel>) => {
const { model } = props;
const shellProcFullStatus = jotai.useAtomValue(model.shellProcFullStatus);
const blockData = jotai.useAtomValue(model.blockAtom);
const isRestarting = jotai.useAtomValue(model.isRestarting);
const domReady = jotai.useAtomValue(model.domReady);
useEffect(() => {
model.resyncController();
}, [model]);
const appPath = blockData?.meta?.["tsunami:apppath"];
const controller = blockData?.meta?.controller;
@ -139,15 +149,19 @@ const TsunamiView = memo(({ model }: TsunamiViewProps) => {
);
}
// Check if we should show the iframe
const shouldShowIframe =
// Check if we should show the webview
const shouldShowWebView =
shellProcFullStatus?.shellprocstatus === "running" &&
shellProcFullStatus?.tsunamiport &&
shellProcFullStatus.tsunamiport !== 0;
if (shouldShowIframe) {
const iframeUrl = `http://localhost:${shellProcFullStatus.tsunamiport}/?clientid=wave:${model.blockId}`;
return <iframe src={iframeUrl} className="w-full h-full border-0" title="Tsunami Application" name={`tsunami:${shellProcFullStatus.tsunamiport}:${model.blockId}`} />;
if (shouldShowWebView) {
const tsunamiUrl = `http://localhost:${shellProcFullStatus.tsunamiport}/?clientid=wave:${model.blockId}`;
return (
<div className="w-full h-full">
<WebView {...props} initialSrc={tsunamiUrl} />
</div>
);
}
const status = shellProcFullStatus?.shellprocstatus ?? "init";

View file

@ -15,7 +15,7 @@ import {
} from "@/app/suggestion/suggestion";
import { WOS, globalStore } from "@/store/global";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
import { fireAndForget } from "@/util/util";
import { fireAndForget, useAtomValueSafe } from "@/util/util";
import clsx from "clsx";
import { WebviewTag } from "electron";
import { Atom, PrimitiveAtom, atom, useAtomValue, useSetAtom } from "jotai";
@ -60,6 +60,7 @@ export class WebViewModel implements ViewModel {
hideNav: Atom<boolean>;
searchAtoms?: SearchAtoms;
typeaheadOpen: PrimitiveAtom<boolean>;
partitionOverride: PrimitiveAtom<string> | null;
constructor(blockId: string, nodeModel: BlockNodeModel) {
this.nodeModel = nodeModel;
@ -85,6 +86,7 @@ export class WebViewModel implements ViewModel {
this.domReady = atom(false);
this.hideNav = getBlockMetaKeyAtom(blockId, "web:hidenav");
this.typeaheadOpen = atom(false);
this.partitionOverride = null;
this.mediaPlaying = atom(false);
this.mediaMuted = atom(false);
@ -398,6 +400,33 @@ export class WebViewModel implements ViewModel {
}
}
/**
* Load a new URL in the webview and return a promise.
* @param newUrl The new URL to load in the webview.
* @param reason The reason for loading the URL.
* @returns Promise that resolves when the URL is loaded.
*/
loadUrlPromise(newUrl: string, reason: string): Promise<void> {
const defaultSearchAtom = getSettingsKeyAtom("web:defaultsearch");
const searchTemplate = globalStore.get(defaultSearchAtom);
const nextUrl = this.ensureUrlScheme(newUrl, searchTemplate);
console.log("webview loadUrlPromise", reason, nextUrl, "cur=", this.webviewRef.current?.getURL());
if (!this.webviewRef.current) {
return Promise.reject(new Error("WebView ref not available"));
}
if (newUrl != nextUrl) {
globalStore.set(this.url, nextUrl);
}
if (this.webviewRef.current.getURL() != nextUrl) {
return this.webviewRef.current.loadURL(nextUrl);
}
return Promise.resolve();
}
/**
* Get the current URL from the state.
* @returns The URL from the state.
@ -661,9 +690,10 @@ interface WebViewProps {
onFailLoad?: (url: string) => void;
blockRef: React.RefObject<HTMLDivElement>;
contentRef: React.RefObject<HTMLDivElement>;
initialSrc?: string;
}
const WebView = memo(({ model, onFailLoad, blockRef }: WebViewProps) => {
const WebView = memo(({ model, onFailLoad, blockRef, initialSrc }: WebViewProps) => {
const blockData = useAtomValue(model.blockAtom);
const defaultUrl = useAtomValue(model.homepageUrl);
const defaultSearchAtom = getSettingsKeyAtom("web:defaultsearch");
@ -672,7 +702,9 @@ const WebView = memo(({ model, onFailLoad, blockRef }: WebViewProps) => {
metaUrl = model.ensureUrlScheme(metaUrl, defaultSearch);
const metaUrlRef = useRef(metaUrl);
const zoomFactor = useAtomValue(getBlockMetaKeyAtom(model.blockId, "web:zoom")) || 1;
const webPartition = useAtomValue(getBlockMetaKeyAtom(model.blockId, "web:partition")) || undefined;
const partitionOverride = useAtomValueSafe(model.partitionOverride);
const metaPartition = useAtomValue(getBlockMetaKeyAtom(model.blockId, "web:partition"));
const webPartition = partitionOverride || metaPartition || undefined;
// Search
const searchProps = useSearch({ anchorRef: model.webviewRef, viewModel: model });
@ -718,7 +750,7 @@ const WebView = memo(({ model, onFailLoad, blockRef }: WebViewProps) => {
// End Search
// The initial value of the block metadata URL when the component first renders. Used to set the starting src value for the webview.
const [metaUrlInitial] = useState(metaUrl);
const [metaUrlInitial] = useState(initialSrc || metaUrl);
const [webContentsId, setWebContentsId] = useState(null);
const domReady = useAtomValue(model.domReady);
@ -774,11 +806,15 @@ const WebView = memo(({ model, onFailLoad, blockRef }: WebViewProps) => {
// Load a new URL if the block metadata is updated.
useEffect(() => {
if (initialSrc) {
// Skip URL loading if initialSrc is provided (it's already loaded via src attribute)
return;
}
if (metaUrlRef.current != metaUrl) {
metaUrlRef.current = metaUrl;
model.loadUrl(metaUrl, "meta");
}
}, [metaUrl]);
}, [metaUrl, initialSrc]);
useEffect(() => {
const webview = model.webviewRef.current;

8
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "waveterm",
"version": "0.11.5",
"version": "0.11.6-beta.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "waveterm",
"version": "0.11.5",
"version": "0.11.6-beta.0",
"hasInstallScript": true,
"license": "Apache-2.0",
"workspaces": [
@ -5045,7 +5045,7 @@
},
"node_modules/@electron/node-gyp": {
"version": "10.2.0-electron.1",
"resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"resolved": "git+https://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"integrity": "sha512-CrYo6TntjpoMO1SHjl5Pa/JoUsECNqNdB7Kx49WLQpWzPw53eEITJ2Hs9fh/ryUYDn4pxZz11StaBYBrLFJdqg==",
"dev": true,
"license": "MIT",
@ -11621,7 +11621,7 @@
},
"node_modules/@waveterm/docusaurus-og": {
"version": "1.0.0-alpha.132",
"resolved": "git+ssh://git@github.com/wavetermdev/docusaurus-og.git#2156619012b8970d922c1ef47789d2f14e47e283",
"resolved": "git+https://git@github.com/wavetermdev/docusaurus-og.git#2156619012b8970d922c1ef47789d2f14e47e283",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "^3.7.0",