Auditing async usage in frontend code (#1402)

I found a lot of places where asyncs weren't being properly wrapped or
awaited
This commit is contained in:
Evan Simkowitz 2024-12-05 21:09:54 -05:00 committed by GitHub
parent 297e2b627f
commit 7a61f25331
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 194 additions and 181 deletions

View File

@ -165,7 +165,7 @@ export class WaveBrowserWindow extends BaseWindow {
} }
focusedWaveWindow = this; focusedWaveWindow = this;
console.log("focus win", this.waveWindowId); console.log("focus win", this.waveWindowId);
fireAndForget(async () => await ClientService.FocusWindow(this.waveWindowId)); fireAndForget(() => ClientService.FocusWindow(this.waveWindowId));
setWasInFg(true); setWasInFg(true);
setWasActive(true); setWasActive(true);
}); });
@ -235,7 +235,7 @@ export class WaveBrowserWindow extends BaseWindow {
} }
if (this.deleteAllowed) { if (this.deleteAllowed) {
console.log("win removing window from backend DB", this.waveWindowId); console.log("win removing window from backend DB", this.waveWindowId);
fireAndForget(async () => await WindowService.CloseWindow(this.waveWindowId, true)); fireAndForget(() => WindowService.CloseWindow(this.waveWindowId, true));
} }
for (const tabView of this.allTabViews.values()) { for (const tabView of this.allTabViews.values()) {
tabView?.destroy(); tabView?.destroy();

View File

@ -368,7 +368,7 @@ electron.ipcMain.on("quicklook", (event, filePath: string) => {
electron.ipcMain.on("open-native-path", (event, filePath: string) => { electron.ipcMain.on("open-native-path", (event, filePath: string) => {
console.log("open-native-path", filePath); console.log("open-native-path", filePath);
fireAndForget(async () => fireAndForget(() =>
electron.shell.openPath(filePath).then((excuse) => { electron.shell.openPath(filePath).then((excuse) => {
if (excuse) console.error(`Failed to open ${filePath} in native application: ${excuse}`); if (excuse) console.error(`Failed to open ${filePath} in native application: ${excuse}`);
}) })

View File

@ -96,7 +96,7 @@ export class Updater {
body: "A new version of Wave Terminal is ready to install.", body: "A new version of Wave Terminal is ready to install.",
}); });
updateNotification.on("click", () => { updateNotification.on("click", () => {
fireAndForget(() => this.promptToInstallUpdate()); fireAndForget(this.promptToInstallUpdate.bind(this));
}); });
updateNotification.show(); updateNotification.show();
}); });
@ -188,7 +188,7 @@ export class Updater {
if (allWindows.length > 0) { if (allWindows.length > 0) {
await dialog.showMessageBox(focusedWaveWindow ?? allWindows[0], dialogOpts).then(({ response }) => { await dialog.showMessageBox(focusedWaveWindow ?? allWindows[0], dialogOpts).then(({ response }) => {
if (response === 0) { if (response === 0) {
fireAndForget(async () => this.installUpdate()); fireAndForget(this.installUpdate.bind(this));
} }
}); });
} }
@ -210,7 +210,7 @@ export function getResolvedUpdateChannel(): string {
return isDev() ? "dev" : (autoUpdater.channel ?? "latest"); return isDev() ? "dev" : (autoUpdater.channel ?? "latest");
} }
ipcMain.on("install-app-update", () => fireAndForget(() => updater?.promptToInstallUpdate())); ipcMain.on("install-app-update", () => fireAndForget(updater?.promptToInstallUpdate.bind(updater)));
ipcMain.on("get-app-update-status", (event) => { ipcMain.on("get-app-update-status", (event) => {
event.returnValue = updater?.status; event.returnValue = updater?.status;
}); });

View File

@ -12,6 +12,7 @@ import { FlexiModal } from "./modal";
import { QuickTips } from "@/app/element/quicktips"; import { QuickTips } from "@/app/element/quicktips";
import { atoms, getApi } from "@/app/store/global"; import { atoms, getApi } from "@/app/store/global";
import { modalsModel } from "@/app/store/modalmodel"; import { modalsModel } from "@/app/store/modalmodel";
import { fireAndForget } from "@/util/util";
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai"; import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
import "./tos.scss"; import "./tos.scss";
@ -20,25 +21,22 @@ const pageNumAtom: PrimitiveAtom<number> = atom<number>(1);
const ModalPage1 = () => { const ModalPage1 = () => {
const settings = useAtomValue(atoms.settingsAtom); const settings = useAtomValue(atoms.settingsAtom);
const clientData = useAtomValue(atoms.client); const clientData = useAtomValue(atoms.client);
const [tosOpen, setTosOpen] = useAtom(modalsModel.tosOpen);
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(!!settings["telemetry:enabled"]); const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(!!settings["telemetry:enabled"]);
const setPageNum = useSetAtom(pageNumAtom); const setPageNum = useSetAtom(pageNumAtom);
const acceptTos = () => { const acceptTos = () => {
if (!clientData.tosagreed) { if (!clientData.tosagreed) {
services.ClientService.AgreeTos(); fireAndForget(services.ClientService.AgreeTos);
} }
setPageNum(2); setPageNum(2);
}; };
const setTelemetry = (value: boolean) => { const setTelemetry = (value: boolean) => {
services.ClientService.TelemetryUpdate(value) fireAndForget(() =>
.then(() => { services.ClientService.TelemetryUpdate(value).then(() => {
setTelemetryEnabled(value); setTelemetryEnabled(value);
}) })
.catch((error) => { );
console.error("failed to set telemetry:", error);
});
}; };
const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled"; const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled";

View File

@ -5,9 +5,9 @@ import { Modal } from "@/app/modals/modal";
import { Markdown } from "@/element/markdown"; import { Markdown } from "@/element/markdown";
import { modalsModel } from "@/store/modalmodel"; import { modalsModel } from "@/store/modalmodel";
import * as keyutil from "@/util/keyutil"; import * as keyutil from "@/util/keyutil";
import { UserInputService } from "../store/services"; import { fireAndForget } from "@/util/util";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { UserInputService } from "../store/services";
import "./userinputmodal.scss"; import "./userinputmodal.scss";
const UserInputModal = (userInputRequest: UserInputRequest) => { const UserInputModal = (userInputRequest: UserInputRequest) => {
@ -16,33 +16,39 @@ const UserInputModal = (userInputRequest: UserInputRequest) => {
const checkboxRef = useRef<HTMLInputElement>(); const checkboxRef = useRef<HTMLInputElement>();
const handleSendErrResponse = useCallback(() => { const handleSendErrResponse = useCallback(() => {
UserInputService.SendUserInputResponse({ fireAndForget(() =>
type: "userinputresp", UserInputService.SendUserInputResponse({
requestid: userInputRequest.requestid, type: "userinputresp",
errormsg: "Canceled by the user", requestid: userInputRequest.requestid,
}); errormsg: "Canceled by the user",
})
);
modalsModel.popModal(); modalsModel.popModal();
}, [responseText, userInputRequest]); }, [responseText, userInputRequest]);
const handleSendText = useCallback(() => { const handleSendText = useCallback(() => {
UserInputService.SendUserInputResponse({ fireAndForget(() =>
type: "userinputresp", UserInputService.SendUserInputResponse({
requestid: userInputRequest.requestid, type: "userinputresp",
text: responseText, requestid: userInputRequest.requestid,
checkboxstat: checkboxRef?.current?.checked ?? false, text: responseText,
}); checkboxstat: checkboxRef?.current?.checked ?? false,
})
);
modalsModel.popModal(); modalsModel.popModal();
}, [responseText, userInputRequest]); }, [responseText, userInputRequest]);
console.log("bar"); console.log("bar");
const handleSendConfirm = useCallback( const handleSendConfirm = useCallback(
(response: boolean) => { (response: boolean) => {
UserInputService.SendUserInputResponse({ fireAndForget(() =>
type: "userinputresp", UserInputService.SendUserInputResponse({
requestid: userInputRequest.requestid, type: "userinputresp",
confirm: response, requestid: userInputRequest.requestid,
checkboxstat: checkboxRef?.current?.checked ?? false, confirm: response,
}); checkboxstat: checkboxRef?.current?.checked ?? false,
})
);
modalsModel.popModal(); modalsModel.popModal();
}, },
[userInputRequest] [userInputRequest]

View File

@ -19,6 +19,7 @@ import {
} from "@/layout/index"; } from "@/layout/index";
import { getLayoutModelForStaticTab } from "@/layout/lib/layoutModelHooks"; import { getLayoutModelForStaticTab } from "@/layout/lib/layoutModelHooks";
import * as keyutil from "@/util/keyutil"; import * as keyutil from "@/util/keyutil";
import { fireAndForget } from "@/util/util";
import * as jotai from "jotai"; import * as jotai from "jotai";
const simpleControlShiftAtom = jotai.atom(false); const simpleControlShiftAtom = jotai.atom(false);
@ -83,7 +84,7 @@ function genericClose(tabId: string) {
return; return;
} }
const layoutModel = getLayoutModelForTab(tabAtom); const layoutModel = getLayoutModelForTab(tabAtom);
layoutModel.closeFocusedNode(); fireAndForget(layoutModel.closeFocusedNode.bind(layoutModel));
} }
function switchBlockByBlockNum(index: number) { function switchBlockByBlockNum(index: number) {

View File

@ -6,6 +6,7 @@
import { waveEventSubscribe } from "@/app/store/wps"; import { waveEventSubscribe } from "@/app/store/wps";
import { getWebServerEndpoint } from "@/util/endpoints"; import { getWebServerEndpoint } from "@/util/endpoints";
import { fetch } from "@/util/fetchutil"; import { fetch } from "@/util/fetchutil";
import { fireAndForget } from "@/util/util";
import { atom, Atom, Getter, PrimitiveAtom, Setter, useAtomValue } from "jotai"; import { atom, Atom, Getter, PrimitiveAtom, Setter, useAtomValue } from "jotai";
import { useEffect } from "react"; import { useEffect } from "react";
import { globalStore } from "./jotaiStore"; import { globalStore } from "./jotaiStore";
@ -301,7 +302,7 @@ function setObjectValue<T extends WaveObj>(value: T, setFn?: Setter, pushToServe
} }
setFn(wov.dataAtom, { value: value, loading: false }); setFn(wov.dataAtom, { value: value, loading: false });
if (pushToServer) { if (pushToServer) {
ObjectService.UpdateObject(value, false); fireAndForget(() => ObjectService.UpdateObject(value, false));
} }
} }

View File

@ -6,6 +6,7 @@ import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil"; import { TabRpcClient } from "@/app/store/wshrpcutil";
import { Button } from "@/element/button"; import { Button } from "@/element/button";
import { ContextMenuModel } from "@/store/contextmenu"; import { ContextMenuModel } from "@/store/contextmenu";
import { fireAndForget } from "@/util/util";
import { clsx } from "clsx"; import { clsx } from "clsx";
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
import { ObjectService } from "../store/services"; import { ObjectService } from "../store/services";
@ -72,14 +73,21 @@ const Tab = memo(
}; };
}, []); }, []);
const handleRenameTab = (event) => { const selectEditableText = useCallback(() => {
if (editableRef.current) {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editableRef.current);
selection.removeAllRanges();
selection.addRange(range);
}
}, []);
const handleRenameTab: React.MouseEventHandler<HTMLDivElement> = (event) => {
event?.stopPropagation(); event?.stopPropagation();
setIsEditable(true); setIsEditable(true);
editableTimeoutRef.current = setTimeout(() => { editableTimeoutRef.current = setTimeout(() => {
if (editableRef.current) { selectEditableText();
editableRef.current.focus();
document.execCommand("selectAll", false);
}
}, 0); }, 0);
}; };
@ -88,20 +96,14 @@ const Tab = memo(
newText = newText || originalName; newText = newText || originalName;
editableRef.current.innerText = newText; editableRef.current.innerText = newText;
setIsEditable(false); setIsEditable(false);
ObjectService.UpdateTabName(id, newText); fireAndForget(() => ObjectService.UpdateTabName(id, newText));
setTimeout(() => refocusNode(null), 10); setTimeout(() => refocusNode(null), 10);
}; };
const handleKeyDown = (event) => { const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === "a") { if ((event.metaKey || event.ctrlKey) && event.key === "a") {
event.preventDefault(); event.preventDefault();
if (editableRef.current) { selectEditableText();
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(editableRef.current);
selection.removeAllRanges();
selection.addRange(range);
}
return; return;
} }
// this counts glyphs, not characters // this counts glyphs, not characters
@ -150,7 +152,10 @@ const Tab = memo(
let menu: ContextMenuItem[] = [ let menu: ContextMenuItem[] = [
{ label: isPinned ? "Unpin Tab" : "Pin Tab", click: () => onPinChange() }, { label: isPinned ? "Unpin Tab" : "Pin Tab", click: () => onPinChange() },
{ label: "Rename Tab", click: () => handleRenameTab(null) }, { label: "Rename Tab", click: () => handleRenameTab(null) },
{ label: "Copy TabId", click: () => navigator.clipboard.writeText(id) }, {
label: "Copy TabId",
click: () => fireAndForget(() => navigator.clipboard.writeText(id)),
},
{ type: "separator" }, { type: "separator" },
]; ];
const fullConfig = globalStore.get(atoms.fullConfigAtom); const fullConfig = globalStore.get(atoms.fullConfigAtom);
@ -175,10 +180,11 @@ const Tab = memo(
} }
submenu.push({ submenu.push({
label: preset["display:name"] ?? presetName, label: preset["display:name"] ?? presetName,
click: () => { click: () =>
ObjectService.UpdateObjectMeta(oref, preset); fireAndForget(async () => {
RpcApi.ActivityCommand(TabRpcClient, { settabtheme: 1 }); await ObjectService.UpdateObjectMeta(oref, preset);
}, await RpcApi.ActivityCommand(TabRpcClient, { settabtheme: 1 });
}),
}); });
} }
menu.push({ label: "Backgrounds", type: "submenu", submenu }, { type: "separator" }); menu.push({ label: "Backgrounds", type: "submenu", submenu }, { type: "separator" });

View File

@ -467,13 +467,12 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
// Reset dragging state // Reset dragging state
setDraggingTab(null); setDraggingTab(null);
// Update workspace tab ids // Update workspace tab ids
fireAndForget( fireAndForget(() =>
async () => WorkspaceService.UpdateTabIds(
await WorkspaceService.UpdateTabIds( workspace.oid,
workspace.oid, tabIds.slice(pinnedTabCount),
tabIds.slice(pinnedTabCount), tabIds.slice(0, pinnedTabCount)
tabIds.slice(0, pinnedTabCount) )
)
); );
}), }),
[] []
@ -579,9 +578,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
const handlePinChange = useCallback( const handlePinChange = useCallback(
(tabId: string, pinned: boolean) => { (tabId: string, pinned: boolean) => {
console.log("handlePinChange", tabId, pinned); console.log("handlePinChange", tabId, pinned);
fireAndForget(async () => { fireAndForget(() => WorkspaceService.ChangeTabPinning(workspace.oid, tabId, pinned));
await WorkspaceService.ChangeTabPinning(workspace.oid, tabId, pinned);
});
}, },
[workspace] [workspace]
); );

View File

@ -189,12 +189,10 @@ const WorkspaceSwitcher = () => {
}, []); }, []);
const onDeleteWorkspace = useCallback((workspaceId: string) => { const onDeleteWorkspace = useCallback((workspaceId: string) => {
fireAndForget(async () => { getApi().deleteWorkspace(workspaceId);
getApi().deleteWorkspace(workspaceId); setTimeout(() => {
setTimeout(() => { fireAndForget(updateWorkspaceList);
fireAndForget(updateWorkspaceList); }, 10);
}, 10);
});
}, []); }, []);
const isActiveWorkspaceSaved = !!(activeWorkspace.name && activeWorkspace.icon); const isActiveWorkspaceSaved = !!(activeWorkspace.name && activeWorkspace.icon);
@ -267,12 +265,10 @@ const WorkspaceSwitcherItem = ({
const isCurrentWorkspace = activeWorkspace.oid === workspace.oid; const isCurrentWorkspace = activeWorkspace.oid === workspace.oid;
const setWorkspace = useCallback((newWorkspace: Workspace) => { const setWorkspace = useCallback((newWorkspace: Workspace) => {
fireAndForget(async () => { if (newWorkspace.name != "") {
if (newWorkspace.name != "") { setObjectValue({ ...newWorkspace, otype: "workspace" }, undefined, true);
setObjectValue({ ...newWorkspace, otype: "workspace" }, undefined, true); }
} setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace });
setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace });
});
}, []); }, []);
const isActive = !!workspaceEntry.windowId; const isActive = !!workspaceEntry.windowId;

View File

@ -543,26 +543,26 @@ function TableBody({
}, },
{ {
label: "Copy File Name", label: "Copy File Name",
click: () => navigator.clipboard.writeText(fileName), click: () => fireAndForget(() => navigator.clipboard.writeText(fileName)),
}, },
{ {
label: "Copy Full File Name", label: "Copy Full File Name",
click: () => navigator.clipboard.writeText(finfo.path), click: () => fireAndForget(() => navigator.clipboard.writeText(finfo.path)),
}, },
{ {
label: "Copy File Name (Shell Quoted)", label: "Copy File Name (Shell Quoted)",
click: () => navigator.clipboard.writeText(shellQuote([fileName])), click: () => fireAndForget(() => navigator.clipboard.writeText(shellQuote([fileName]))),
}, },
{ {
label: "Copy Full File Name (Shell Quoted)", label: "Copy Full File Name (Shell Quoted)",
click: () => navigator.clipboard.writeText(shellQuote([finfo.path])), click: () => fireAndForget(() => navigator.clipboard.writeText(shellQuote([finfo.path]))),
}, },
{ {
type: "separator", type: "separator",
}, },
{ {
label: "Download File", label: "Download File",
click: async () => { click: () => {
getApi().downloadFile(normPath); getApi().downloadFile(normPath);
}, },
}, },
@ -572,7 +572,7 @@ function TableBody({
// TODO: Only show this option for local files, resolve correct host path if connection is WSL // TODO: Only show this option for local files, resolve correct host path if connection is WSL
{ {
label: openNativeLabel, label: openNativeLabel,
click: async () => { click: () => {
getApi().openNativePath(normPath); getApi().openNativePath(normPath);
}, },
}, },
@ -581,30 +581,32 @@ function TableBody({
}, },
{ {
label: "Open Preview in New Block", label: "Open Preview in New Block",
click: async () => { click: () =>
const blockDef: BlockDef = { fireAndForget(async () => {
meta: { const blockDef: BlockDef = {
view: "preview", meta: {
file: finfo.path, view: "preview",
}, file: finfo.path,
}; },
await createBlock(blockDef); };
}, await createBlock(blockDef);
}),
}, },
]; ];
if (finfo.mimetype == "directory") { if (finfo.mimetype == "directory") {
menu.push({ menu.push({
label: "Open Terminal in New Block", label: "Open Terminal in New Block",
click: async () => { click: () =>
const termBlockDef: BlockDef = { fireAndForget(async () => {
meta: { const termBlockDef: BlockDef = {
controller: "shell", meta: {
view: "term", controller: "shell",
"cmd:cwd": finfo.path, view: "term",
}, "cmd:cwd": finfo.path,
}; },
await createBlock(termBlockDef); };
}, await createBlock(termBlockDef);
}),
}); });
} }
menu.push( menu.push(
@ -613,9 +615,11 @@ function TableBody({
}, },
{ {
label: "Delete", label: "Delete",
click: async () => { click: () => {
await FileService.DeleteFile(conn, finfo.path).catch((e) => console.log(e)); fireAndForget(async () => {
setRefreshVersion((current) => current + 1); await FileService.DeleteFile(conn, finfo.path).catch((e) => console.log(e));
setRefreshVersion((current) => current + 1);
});
}, },
} }
); );

View File

@ -24,7 +24,7 @@ import * as WOS from "@/store/wos";
import { getWebServerEndpoint } from "@/util/endpoints"; import { getWebServerEndpoint } from "@/util/endpoints";
import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil"; import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
import { base64ToString, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util"; import { base64ToString, fireAndForget, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util";
import { Monaco } from "@monaco-editor/react"; import { Monaco } from "@monaco-editor/react";
import clsx from "clsx"; import clsx from "clsx";
import { Atom, atom, Getter, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai"; import { Atom, atom, Getter, PrimitiveAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai";
@ -257,7 +257,7 @@ export class PreviewModel implements ViewModel {
className: clsx( className: clsx(
`${saveClassName} warning border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500` `${saveClassName} warning border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500`
), ),
onClick: this.handleFileSave.bind(this), onClick: () => fireAndForget(this.handleFileSave.bind(this)),
}); });
if (get(this.canPreview)) { if (get(this.canPreview)) {
viewTextChildren.push({ viewTextChildren.push({
@ -265,7 +265,7 @@ export class PreviewModel implements ViewModel {
text: "Preview", text: "Preview",
className: className:
"grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500", "grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500",
onClick: () => this.setEditMode(false), onClick: () => fireAndForget(() => this.setEditMode(false)),
}); });
} }
} else if (get(this.canPreview)) { } else if (get(this.canPreview)) {
@ -274,7 +274,7 @@ export class PreviewModel implements ViewModel {
text: "Edit", text: "Edit",
className: className:
"grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500", "grey border-radius-4 vertical-padding-2 horizontal-padding-10 font-size-11 font-weight-500",
onClick: () => this.setEditMode(true), onClick: () => fireAndForget(() => this.setEditMode(true)),
}); });
} }
return [ return [
@ -497,7 +497,7 @@ export class PreviewModel implements ViewModel {
return; return;
} }
const blockOref = WOS.makeORef("block", this.blockId); const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta); await services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
// Clear the saved file buffers // Clear the saved file buffers
globalStore.set(this.fileContentSaved, null); globalStore.set(this.fileContentSaved, null);
@ -538,7 +538,7 @@ export class PreviewModel implements ViewModel {
} }
console.log(newFileInfo.path); console.log(newFileInfo.path);
this.updateOpenFileModalAndError(false); this.updateOpenFileModalAndError(false);
this.goHistory(newFileInfo.path); await this.goHistory(newFileInfo.path);
refocusNode(this.blockId); refocusNode(this.blockId);
} catch (e) { } catch (e) {
globalStore.set(this.openFileError, e.message); globalStore.set(this.openFileError, e.message);
@ -546,7 +546,7 @@ export class PreviewModel implements ViewModel {
} }
} }
goHistoryBack() { async goHistoryBack() {
const blockMeta = globalStore.get(this.blockAtom)?.meta; const blockMeta = globalStore.get(this.blockAtom)?.meta;
const curPath = globalStore.get(this.metaFilePath); const curPath = globalStore.get(this.metaFilePath);
const updateMeta = goHistoryBack("file", curPath, blockMeta, true); const updateMeta = goHistoryBack("file", curPath, blockMeta, true);
@ -555,10 +555,10 @@ export class PreviewModel implements ViewModel {
} }
updateMeta.edit = false; updateMeta.edit = false;
const blockOref = WOS.makeORef("block", this.blockId); const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta); await services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
} }
goHistoryForward() { async goHistoryForward() {
const blockMeta = globalStore.get(this.blockAtom)?.meta; const blockMeta = globalStore.get(this.blockAtom)?.meta;
const curPath = globalStore.get(this.metaFilePath); const curPath = globalStore.get(this.metaFilePath);
const updateMeta = goHistoryForward("file", curPath, blockMeta); const updateMeta = goHistoryForward("file", curPath, blockMeta);
@ -567,13 +567,13 @@ export class PreviewModel implements ViewModel {
} }
updateMeta.edit = false; updateMeta.edit = false;
const blockOref = WOS.makeORef("block", this.blockId); const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, updateMeta); await services.ObjectService.UpdateObjectMeta(blockOref, updateMeta);
} }
setEditMode(edit: boolean) { async setEditMode(edit: boolean) {
const blockMeta = globalStore.get(this.blockAtom)?.meta; const blockMeta = globalStore.get(this.blockAtom)?.meta;
const blockOref = WOS.makeORef("block", this.blockId); const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, { ...blockMeta, edit }); await services.ObjectService.UpdateObjectMeta(blockOref, { ...blockMeta, edit });
} }
async handleFileSave() { async handleFileSave() {
@ -588,7 +588,7 @@ export class PreviewModel implements ViewModel {
} }
const conn = globalStore.get(this.connection) ?? ""; const conn = globalStore.get(this.connection) ?? "";
try { try {
services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent)); await services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent));
globalStore.set(this.fileContent, newFileContent); globalStore.set(this.fileContent, newFileContent);
globalStore.set(this.newFileContent, null); globalStore.set(this.newFileContent, null);
console.log("saved file", filePath); console.log("saved file", filePath);
@ -630,42 +630,44 @@ export class PreviewModel implements ViewModel {
getSettingsMenuItems(): ContextMenuItem[] { getSettingsMenuItems(): ContextMenuItem[] {
const menuItems: ContextMenuItem[] = []; const menuItems: ContextMenuItem[] = [];
const blockData = globalStore.get(this.blockAtom);
menuItems.push({ menuItems.push({
label: "Copy Full Path", label: "Copy Full Path",
click: async () => { click: () =>
const filePath = await globalStore.get(this.normFilePath); fireAndForget(async () => {
if (filePath == null) { const filePath = await globalStore.get(this.normFilePath);
return; if (filePath == null) {
} return;
navigator.clipboard.writeText(filePath); }
}, await navigator.clipboard.writeText(filePath);
}),
}); });
menuItems.push({ menuItems.push({
label: "Copy File Name", label: "Copy File Name",
click: async () => { click: () =>
const fileInfo = await globalStore.get(this.statFile); fireAndForget(async () => {
if (fileInfo == null || fileInfo.name == null) { const fileInfo = await globalStore.get(this.statFile);
return; if (fileInfo == null || fileInfo.name == null) {
} return;
navigator.clipboard.writeText(fileInfo.name); }
}, await navigator.clipboard.writeText(fileInfo.name);
}),
}); });
const mimeType = jotaiLoadableValue(globalStore.get(this.fileMimeTypeLoadable), ""); const mimeType = jotaiLoadableValue(globalStore.get(this.fileMimeTypeLoadable), "");
if (mimeType == "directory") { if (mimeType == "directory") {
menuItems.push({ menuItems.push({
label: "Open Terminal in New Block", label: "Open Terminal in New Block",
click: async () => { click: () =>
const fileInfo = await globalStore.get(this.statFile); fireAndForget(async () => {
const termBlockDef: BlockDef = { const fileInfo = await globalStore.get(this.statFile);
meta: { const termBlockDef: BlockDef = {
view: "term", meta: {
controller: "shell", view: "term",
"cmd:cwd": fileInfo.dir, controller: "shell",
}, "cmd:cwd": fileInfo.dir,
}; },
await createBlock(termBlockDef); };
}, await createBlock(termBlockDef);
}),
}); });
} }
const loadableSV = globalStore.get(this.loadableSpecializedView); const loadableSV = globalStore.get(this.loadableSpecializedView);
@ -677,11 +679,11 @@ export class PreviewModel implements ViewModel {
menuItems.push({ type: "separator" }); menuItems.push({ type: "separator" });
menuItems.push({ menuItems.push({
label: "Save File", label: "Save File",
click: this.handleFileSave.bind(this), click: () => fireAndForget(this.handleFileSave.bind(this)),
}); });
menuItems.push({ menuItems.push({
label: "Revert File", label: "Revert File",
click: this.handleFileRevert.bind(this), click: () => fireAndForget(this.handleFileRevert.bind(this)),
}); });
} }
menuItems.push({ type: "separator" }); menuItems.push({ type: "separator" });
@ -689,12 +691,13 @@ export class PreviewModel implements ViewModel {
label: "Word Wrap", label: "Word Wrap",
type: "checkbox", type: "checkbox",
checked: wordWrap, checked: wordWrap,
click: () => { click: () =>
const blockOref = WOS.makeORef("block", this.blockId); fireAndForget(async () => {
services.ObjectService.UpdateObjectMeta(blockOref, { const blockOref = WOS.makeORef("block", this.blockId);
"editor:wordwrap": !wordWrap, await services.ObjectService.UpdateObjectMeta(blockOref, {
}); "editor:wordwrap": !wordWrap,
}, });
}),
}); });
} }
} }
@ -716,16 +719,16 @@ export class PreviewModel implements ViewModel {
keyDownHandler(e: WaveKeyboardEvent): boolean { keyDownHandler(e: WaveKeyboardEvent): boolean {
if (checkKeyPressed(e, "Cmd:ArrowLeft")) { if (checkKeyPressed(e, "Cmd:ArrowLeft")) {
this.goHistoryBack(); fireAndForget(this.goHistoryBack.bind(this));
return true; return true;
} }
if (checkKeyPressed(e, "Cmd:ArrowRight")) { if (checkKeyPressed(e, "Cmd:ArrowRight")) {
this.goHistoryForward(); fireAndForget(this.goHistoryForward.bind(this));
return true; return true;
} }
if (checkKeyPressed(e, "Cmd:ArrowUp")) { if (checkKeyPressed(e, "Cmd:ArrowUp")) {
// handle up directory // handle up directory
this.goParentDirectory({}); fireAndForget(() => this.goParentDirectory({}));
return true; return true;
} }
const openModalOpen = globalStore.get(this.openFileModal); const openModalOpen = globalStore.get(this.openFileModal);
@ -739,7 +742,7 @@ export class PreviewModel implements ViewModel {
if (canPreview) { if (canPreview) {
if (checkKeyPressed(e, "Cmd:e")) { if (checkKeyPressed(e, "Cmd:e")) {
const editMode = globalStore.get(this.editMode); const editMode = globalStore.get(this.editMode);
this.setEditMode(!editMode); fireAndForget(() => this.setEditMode(!editMode));
return true; return true;
} }
} }
@ -833,15 +836,15 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean { function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
if (checkKeyPressed(e, "Cmd:e")) { if (checkKeyPressed(e, "Cmd:e")) {
model.setEditMode(false); fireAndForget(() => model.setEditMode(false));
return true; return true;
} }
if (checkKeyPressed(e, "Cmd:s") || checkKeyPressed(e, "Ctrl:s")) { if (checkKeyPressed(e, "Cmd:s") || checkKeyPressed(e, "Ctrl:s")) {
model.handleFileSave(); fireAndForget(model.handleFileSave.bind(model));
return true; return true;
} }
if (checkKeyPressed(e, "Cmd:r")) { if (checkKeyPressed(e, "Cmd:r")) {
model.handleFileRevert(); fireAndForget(model.handleFileRevert.bind(model));
return true; return true;
} }
return false; return false;
@ -990,7 +993,7 @@ const OpenFileModal = memo(
const handleCommandOperations = async () => { const handleCommandOperations = async () => {
if (checkKeyPressed(waveEvent, "Enter")) { if (checkKeyPressed(waveEvent, "Enter")) {
model.handleOpenFile(filePath); await model.handleOpenFile(filePath);
return true; return true;
} }
return false; return false;

View File

@ -119,7 +119,11 @@ export class TermWrap {
data = data.substring(nextSlashIdx); data = data.substring(nextSlashIdx);
} }
setTimeout(() => { setTimeout(() => {
services.ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), { "cmd:cwd": data }); fireAndForget(() =>
services.ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), {
"cmd:cwd": data,
})
);
}, 0); }, 0);
return true; return true;
}); });
@ -284,7 +288,9 @@ export class TermWrap {
const serializedOutput = this.serializeAddon.serialize(); const serializedOutput = this.serializeAddon.serialize();
const termSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols }; const termSize: TermSize = { rows: this.terminal.rows, cols: this.terminal.cols };
console.log("idle timeout term", this.dataBytesProcessed, serializedOutput.length, termSize); console.log("idle timeout term", this.dataBytesProcessed, serializedOutput.length, termSize);
services.BlockService.SaveTerminalState(this.blockId, serializedOutput, "full", this.ptyOffset, termSize); fireAndForget(() =>
services.BlockService.SaveTerminalState(this.blockId, serializedOutput, "full", this.ptyOffset, termSize)
);
this.dataBytesProcessed = 0; this.dataBytesProcessed = 0;
} }

View File

@ -221,12 +221,12 @@ export class WaveAiModel implements ViewModel {
({ ({
label: preset[1]["display:name"], label: preset[1]["display:name"],
onClick: () => onClick: () =>
fireAndForget(async () => { fireAndForget(() =>
await ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), { ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), {
...preset[1], ...preset[1],
"ai:preset": preset[0], "ai:preset": preset[0],
}); })
}), ),
}) as MenuItem }) as MenuItem
); );
dropdownItems.push({ dropdownItems.push({
@ -386,7 +386,7 @@ export class WaveAiModel implements ViewModel {
this.setLocked(false); this.setLocked(false);
this.cancel = false; this.cancel = false;
}; };
handleAiStreamingResponse(); fireAndForget(handleAiStreamingResponse);
} }
useWaveAi() { useWaveAi() {
@ -404,14 +404,14 @@ export class WaveAiModel implements ViewModel {
keyDownHandler(waveEvent: WaveKeyboardEvent): boolean { keyDownHandler(waveEvent: WaveKeyboardEvent): boolean {
if (checkKeyPressed(waveEvent, "Cmd:l")) { if (checkKeyPressed(waveEvent, "Cmd:l")) {
this.clearMessages(); fireAndForget(this.clearMessages.bind(this));
return true; return true;
} }
return false; return false;
} }
} }
function makeWaveAiViewModel(blockId): WaveAiModel { function makeWaveAiViewModel(blockId: string): WaveAiModel {
const waveAiModel = new WaveAiModel(blockId); const waveAiModel = new WaveAiModel(blockId);
return waveAiModel; return waveAiModel;
} }
@ -634,7 +634,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
// a weird workaround to initialize ansynchronously // a weird workaround to initialize ansynchronously
useEffect(() => { useEffect(() => {
model.populateMessages(); fireAndForget(model.populateMessages.bind(model));
}, []); }, []);
const handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { const handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {

View File

@ -293,7 +293,7 @@ export class WebViewModel implements ViewModel {
* @param url The URL that has been navigated to. * @param url The URL that has been navigated to.
*/ */
handleNavigate(url: string) { handleNavigate(url: string) {
ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), { url }); fireAndForget(() => ObjectService.UpdateObjectMeta(WOS.makeORef("block", this.blockId), { url }));
globalStore.set(this.url, url); globalStore.set(this.url, url);
} }
@ -432,22 +432,18 @@ export class WebViewModel implements ViewModel {
return [ return [
{ {
label: "Set Block Homepage", label: "Set Block Homepage",
click: async () => { click: () => fireAndForget(() => this.setHomepageUrl(this.getUrl(), "block")),
await this.setHomepageUrl(this.getUrl(), "block");
},
}, },
{ {
label: "Set Default Homepage", label: "Set Default Homepage",
click: async () => { click: () => fireAndForget(() => this.setHomepageUrl(this.getUrl(), "global")),
await this.setHomepageUrl(this.getUrl(), "global");
},
}, },
{ {
type: "separator", type: "separator",
}, },
{ {
label: this.webviewRef.current?.isDevToolsOpened() ? "Close DevTools" : "Open DevTools", label: this.webviewRef.current?.isDevToolsOpened() ? "Close DevTools" : "Open DevTools",
click: async () => { click: () => {
if (this.webviewRef.current) { if (this.webviewRef.current) {
if (this.webviewRef.current.isDevToolsOpened()) { if (this.webviewRef.current.isDevToolsOpened()) {
this.webviewRef.current.closeDevTools(); this.webviewRef.current.closeDevTools();

View File

@ -58,7 +58,6 @@ function TileLayoutComponent({ tabAtom, contents, getCursorPoint }: TileLayoutPr
const setActiveDrag = useSetAtom(layoutModel.activeDrag); const setActiveDrag = useSetAtom(layoutModel.activeDrag);
const setReady = useSetAtom(layoutModel.ready); const setReady = useSetAtom(layoutModel.ready);
const isResizing = useAtomValue(layoutModel.isResizing); const isResizing = useAtomValue(layoutModel.isResizing);
const ephemeralNode = useAtomValue(layoutModel.ephemeralNode);
const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({ const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({
activeDrag: monitor.isDragging(), activeDrag: monitor.isDragging(),

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import { getSettingsKeyAtom } from "@/app/store/global"; import { getSettingsKeyAtom } from "@/app/store/global";
import { atomWithThrottle, boundNumber } from "@/util/util"; import { atomWithThrottle, boundNumber, fireAndForget } from "@/util/util";
import { Atom, atom, Getter, PrimitiveAtom, Setter } from "jotai"; import { Atom, atom, Getter, PrimitiveAtom, Setter } from "jotai";
import { splitAtom } from "jotai/utils"; import { splitAtom } from "jotai/utils";
import { createRef, CSSProperties } from "react"; import { createRef, CSSProperties } from "react";
@ -852,7 +852,7 @@ export class LayoutModel {
animationTimeS: this.animationTimeS, animationTimeS: this.animationTimeS,
ready: this.ready, ready: this.ready,
disablePointerEvents: this.activeDrag, disablePointerEvents: this.activeDrag,
onClose: async () => await this.closeNode(nodeid), onClose: () => fireAndForget(() => this.closeNode(nodeid)),
toggleMagnify: () => this.magnifyNodeToggle(nodeid), toggleMagnify: () => this.magnifyNodeToggle(nodeid),
focusNode: () => this.focusNode(nodeid), focusNode: () => this.focusNode(nodeid),
dragHandleRef: createRef(), dragHandleRef: createRef(),

View File

@ -24,7 +24,7 @@ export function getLayoutModelForTab(tabAtom: Atom<Tab>): LayoutModel {
} }
const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom); const layoutTreeStateAtom = withLayoutTreeStateAtomFromTab(tabAtom);
const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set); const layoutModel = new LayoutModel(layoutTreeStateAtom, globalStore.get, globalStore.set);
globalStore.sub(layoutTreeStateAtom, () => fireAndForget(async () => layoutModel.onTreeStateAtomUpdated())); globalStore.sub(layoutTreeStateAtom, () => fireAndForget(layoutModel.onTreeStateAtomUpdated.bind(layoutModel)));
layoutModelMap.set(tabId, layoutModel); layoutModelMap.set(tabId, layoutModel);
return layoutModel; return layoutModel;
} }
@ -56,7 +56,7 @@ export function useTileLayout(tabAtom: Atom<Tab>, tileContent: TileLayoutContent
useOnResize(layoutModel?.displayContainerRef, layoutModel?.onContainerResize); useOnResize(layoutModel?.displayContainerRef, layoutModel?.onContainerResize);
// Once the TileLayout is mounted, re-run the state update to get all the nodes to flow in the layout. // Once the TileLayout is mounted, re-run the state update to get all the nodes to flow in the layout.
useEffect(() => fireAndForget(async () => layoutModel.onTreeStateAtomUpdated(true)), []); useEffect(() => fireAndForget(() => layoutModel.onTreeStateAtomUpdated(true)), []);
useEffect(() => layoutModel.registerTileLayout(tileContent), [tileContent]); useEffect(() => layoutModel.registerTileLayout(tileContent), [tileContent]);
return layoutModel; return layoutModel;