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
52 changes: 33 additions & 19 deletions frontend/app/view/tsunami/tsunami.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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";
Expand Down
46 changes: 41 additions & 5 deletions frontend/app/view/webview/webview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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");
Expand All @@ -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 });
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading