Refactor ModelUpdate to set up for decoupling sstore (#280)

This PR changes ModelUpdate mechanism from a statically-typed struct to an interface, allowing us to define the update mechanism and the update types separately. This sets us up to move app logic and update mechanisms into separate packages. Ultimately, sstore will only define low-level persistence logic.
This commit is contained in:
Evan Simkowitz 2024-02-09 17:19:44 -08:00 committed by GitHub
parent 7a5afccab3
commit d319e72609
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1082 additions and 861 deletions

View File

@ -13,10 +13,10 @@ import {
HistoryQueryOpts, HistoryQueryOpts,
HistoryTypeStrs, HistoryTypeStrs,
OpenAICmdInfoChatMessageType, OpenAICmdInfoChatMessageType,
OV,
StrWithPos,
} from "../types/types"; } from "../types/types";
import { StrWithPos } from "../types/types";
import * as appconst from "../app/appconst"; import * as appconst from "../app/appconst";
import { OV } from "../types/types";
import { Model } from "./model"; import { Model } from "./model";
import { GlobalCommandRunner } from "./global"; import { GlobalCommandRunner } from "./global";
@ -207,7 +207,6 @@ class InputModel {
this.historyQueryOpts.set(opts); this.historyQueryOpts.set(opts);
let bestIndex = this.findBestNewIndex(oldItem); let bestIndex = this.findBestNewIndex(oldItem);
setTimeout(() => this.setHistoryIndex(bestIndex, true), 10); setTimeout(() => this.setHistoryIndex(bestIndex, true), 10);
return;
})(); })();
} }
@ -624,13 +623,17 @@ class InputModel {
} }
openAIAssistantChat(): void { openAIAssistantChat(): void {
mobx.action(() => {
this.aIChatShow.set(true); this.aIChatShow.set(true);
this.setAIChatFocus(); this.setAIChatFocus();
})();
} }
closeAIAssistantChat(): void { closeAIAssistantChat(): void {
mobx.action(() => {
this.aIChatShow.set(false); this.aIChatShow.set(false);
this.giveFocus(); this.giveFocus();
})();
} }
clearAIAssistantChat(): void { clearAIAssistantChat(): void {
@ -721,14 +724,6 @@ class InputModel {
setCurLine(val: string): void { setCurLine(val: string): void {
let hidx = this.historyIndex.get(); let hidx = this.historyIndex.get();
mobx.action(() => { mobx.action(() => {
// if (val == "\" ") {
// this.setInputMode("comment");
// val = "";
// }
// if (val == "//") {
// this.setInputMode("global");
// val = "";
// }
if (this.modHistory.length <= hidx) { if (this.modHistory.length <= hidx) {
this.modHistory.length = hidx + 1; this.modHistory.length = hidx + 1;
} }

View File

@ -22,7 +22,6 @@ import {
FeCmdPacketType, FeCmdPacketType,
ScreenDataType, ScreenDataType,
PtyDataUpdateType, PtyDataUpdateType,
ModelUpdateType,
UpdateMessage, UpdateMessage,
InfoType, InfoType,
StrWithPos, StrWithPos,
@ -40,15 +39,18 @@ import {
CmdInputTextPacketType, CmdInputTextPacketType,
FileInfoType, FileInfoType,
ExtFile, ExtFile,
HistorySearchParams, OV,
LineStateType, OArr,
OMap,
CV,
ScreenNumRunningCommandsUpdateType,
ScreenStatusIndicatorUpdateType,
} from "../types/types"; } from "../types/types";
import { WSControl } from "./ws"; import { WSControl } from "./ws";
import { cmdStatusIsRunning } from "../app/line/lineutil"; import { cmdStatusIsRunning } from "../app/line/lineutil";
import * as appconst from "../app/appconst"; import * as appconst from "../app/appconst";
import { remotePtrToString, cmdPacketString } from "../util/modelutil"; import { remotePtrToString, cmdPacketString } from "../util/modelutil";
import { checkKeyPressed, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "../util/keyutil"; import { checkKeyPressed, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "../util/keyutil";
import { OV, OArr, OMap, CV } from "../types/types";
import { Session } from "./session"; import { Session } from "./session";
import { ScreenLines } from "./screenlines"; import { ScreenLines } from "./screenlines";
import { InputModel } from "./input"; import { InputModel } from "./input";
@ -187,7 +189,7 @@ class Model {
this.isDev = getApi().getIsDev(); this.isDev = getApi().getIsDev();
this.authKey = getApi().getAuthKey(); this.authKey = getApi().getAuthKey();
this.ws = new WSControl(this.getBaseWsHostPort(), this.clientId, this.authKey, (message: any) => { this.ws = new WSControl(this.getBaseWsHostPort(), this.clientId, this.authKey, (message: any) => {
let interactive = message?.interactive ?? false; const interactive = message?.interactive ?? false;
this.runUpdate(message, interactive); this.runUpdate(message, interactive);
}); });
this.ws.reconnect(); this.ws.reconnect();
@ -200,17 +202,17 @@ class Model {
this.remotesModel = new RemotesModel(this); this.remotesModel = new RemotesModel(this);
this.modalsModel = new ModalsModel(); this.modalsModel = new ModalsModel();
this.mainSidebarModel = new MainSidebarModel(this); this.mainSidebarModel = new MainSidebarModel(this);
let isWaveSrvRunning = getApi().getWaveSrvStatus(); const isWaveSrvRunning = getApi().getWaveSrvStatus();
this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, { this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, {
name: "model-wavesrv-running", name: "model-wavesrv-running",
}); });
this.platform = this.getPlatform(); this.platform = this.getPlatform();
this.termFontSize = mobx.computed(() => { this.termFontSize = mobx.computed(() => {
let cdata = this.clientData.get(); const cdata = this.clientData.get();
if (cdata == null || cdata.feopts == null || cdata.feopts.termfontsize == null) { if (cdata?.feopts?.termfontsize == null) {
return appconst.DefaultTermFontSize; return appconst.DefaultTermFontSize;
} }
let fontSize = Math.ceil(cdata.feopts.termfontsize); const fontSize = Math.ceil(cdata.feopts.termfontsize);
if (fontSize < appconst.MinFontSize) { if (fontSize < appconst.MinFontSize) {
return appconst.MinFontSize; return appconst.MinFontSize;
} }
@ -265,11 +267,11 @@ class Model {
} }
needsTos(): boolean { needsTos(): boolean {
let cdata = this.clientData.get(); const cdata = this.clientData.get();
if (cdata == null) { if (cdata == null) {
return false; return false;
} }
return cdata.clientopts == null || !cdata.clientopts.acceptedtos; return !cdata.clientopts?.acceptedtos;
} }
refreshClient(): void { refreshClient(): void {
@ -288,7 +290,7 @@ class Model {
refocus() { refocus() {
// givefocus() give back focus to cmd or input // givefocus() give back focus to cmd or input
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
if (screen == null) { if (screen == null) {
return; return;
} }
@ -296,8 +298,8 @@ class Model {
} }
getWebSharedScreens(): Screen[] { getWebSharedScreens(): Screen[] {
let rtn: Screen[] = []; const rtn: Screen[] = [];
for (let screen of this.screenMap.values()) { for (const screen of this.screenMap.values()) {
if (screen.shareMode.get() == "web") { if (screen.shareMode.get() == "web") {
rtn.push(screen); rtn.push(screen);
} }
@ -309,7 +311,7 @@ class Model {
if (this.clientData.get() == null) { if (this.clientData.get() == null) {
return true; return true;
} }
let cdata = this.clientData.get(); const cdata = this.clientData.get();
if (cdata.cmdstoretype == "session") { if (cdata.cmdstoretype == "session") {
return true; return true;
} }
@ -318,8 +320,8 @@ class Model {
showAlert(alertMessage: AlertMessageType): Promise<boolean> { showAlert(alertMessage: AlertMessageType): Promise<boolean> {
if (alertMessage.confirmflag != null) { if (alertMessage.confirmflag != null) {
let cdata = this.clientData.get(); const cdata = this.clientData.get();
let noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag]; const noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag];
if (noConfirm) { if (noConfirm) {
return Promise.resolve(true); return Promise.resolve(true);
} }
@ -328,7 +330,7 @@ class Model {
this.alertMessage.set(alertMessage); this.alertMessage.set(alertMessage);
this.modalsModel.pushModal(appconst.ALERT); this.modalsModel.pushModal(appconst.ALERT);
})(); })();
let prtn = new Promise<boolean>((resolve, reject) => { const prtn = new Promise<boolean>((resolve, reject) => {
this.alertPromiseResolver = resolve; this.alertPromiseResolver = resolve;
}); });
return prtn; return prtn;
@ -405,7 +407,7 @@ class Model {
} }
docKeyDownHandler(e: KeyboardEvent) { docKeyDownHandler(e: KeyboardEvent) {
let waveEvent = adaptFromReactOrNativeKeyEvent(e); const waveEvent = adaptFromReactOrNativeKeyEvent(e);
if (isModKeyPress(e)) { if (isModKeyPress(e)) {
return; return;
} }
@ -447,7 +449,7 @@ class Model {
if (this.clearModals()) { if (this.clearModals()) {
return; return;
} }
let inputModel = this.inputModel; const inputModel = this.inputModel;
inputModel.toggleInfoMsg(); inputModel.toggleInfoMsg();
if (inputModel.inputMode.get() != null) { if (inputModel.inputMode.get() != null) {
inputModel.resetInputMode(); inputModel.resetInputMode();
@ -460,9 +462,9 @@ class Model {
} }
if (this.activeMainView.get() == "session" && checkKeyPressed(waveEvent, "Cmd:Ctrl:s")) { if (this.activeMainView.get() == "session" && checkKeyPressed(waveEvent, "Cmd:Ctrl:s")) {
e.preventDefault(); e.preventDefault();
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
if (activeScreen != null) { if (activeScreen != null) {
let isSidebarOpen = activeScreen.isSidebarOpen(); const isSidebarOpen = activeScreen.isSidebarOpen();
if (isSidebarOpen) { if (isSidebarOpen) {
GlobalCommandRunner.screenSidebarClose(); GlobalCommandRunner.screenSidebarClose();
} else { } else {
@ -471,7 +473,7 @@ class Model {
} }
} }
if (checkKeyPressed(waveEvent, "Cmd:d")) { if (checkKeyPressed(waveEvent, "Cmd:d")) {
let ranDelete = this.deleteActiveLine(); const ranDelete = this.deleteActiveLine();
if (ranDelete) { if (ranDelete) {
e.preventDefault(); e.preventDefault();
} }
@ -479,22 +481,22 @@ class Model {
} }
deleteActiveLine(): boolean { deleteActiveLine(): boolean {
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
if (activeScreen == null || activeScreen.getFocusType() != "cmd") { if (activeScreen == null || activeScreen.getFocusType() != "cmd") {
return false; return false;
} }
let selectedLine = activeScreen.selectedLine.get(); const selectedLine = activeScreen.selectedLine.get();
if (selectedLine == null || selectedLine <= 0) { if (selectedLine == null || selectedLine <= 0) {
return false; return false;
} }
let line = activeScreen.getLineByNum(selectedLine); const line = activeScreen.getLineByNum(selectedLine);
if (line == null) { if (line == null) {
return false; return false;
} }
let cmd = activeScreen.getCmd(line); const cmd = activeScreen.getCmd(line);
if (cmd != null) { if (cmd != null) {
if (cmd.isRunning()) { if (cmd.isRunning()) {
let info: InfoType = { infomsg: "Cannot delete a running command" }; const info: InfoType = { infomsg: "Cannot delete a running command" };
this.inputModel.flashInfoMsg(info, 2000); this.inputModel.flashInfoMsg(info, 2000);
return false; return false;
} }
@ -507,11 +509,11 @@ class Model {
if (this.activeMainView.get() != "session") { if (this.activeMainView.get() != "session") {
return; return;
} }
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
if (activeScreen == null) { if (activeScreen == null) {
return; return;
} }
let rtnp = this.showAlert({ const rtnp = this.showAlert({
message: "Are you sure you want to delete this screen?", message: "Are you sure you want to delete this screen?",
confirm: true, confirm: true,
}); });
@ -527,7 +529,7 @@ class Model {
if (this.activeMainView.get() != "session") { if (this.activeMainView.get() != "session") {
return; return;
} }
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
if (activeScreen == null) { if (activeScreen == null) {
return; return;
} }
@ -536,7 +538,7 @@ class Model {
GlobalCommandRunner.lineRestart("E", true); GlobalCommandRunner.lineRestart("E", true);
} else { } else {
// restart selected line // restart selected line
let selectedLine = activeScreen.selectedLine.get(); const selectedLine = activeScreen.selectedLine.get();
if (selectedLine == null || selectedLine == 0) { if (selectedLine == null || selectedLine == 0) {
return; return;
} }
@ -585,7 +587,7 @@ class Model {
} }
getCurRemoteInstance(): RemoteInstanceType { getCurRemoteInstance(): RemoteInstanceType {
let screen = this.getActiveScreen(); const screen = this.getActiveScreen();
if (screen == null) { if (screen == null) {
return null; return null;
} }
@ -603,12 +605,12 @@ class Model {
} }
getContentHeight(context: RendererContext): number { getContentHeight(context: RendererContext): number {
let key = context.screenId + "/" + context.lineId; const key = context.screenId + "/" + context.lineId;
return this.termUsedRowsCache[key]; return this.termUsedRowsCache[key];
} }
setContentHeight(context: RendererContext, height: number): void { setContentHeight(context: RendererContext, height: number): void {
let key = context.screenId + "/" + context.lineId; const key = context.screenId + "/" + context.lineId;
this.termUsedRowsCache[key] = height; this.termUsedRowsCache[key] = height;
GlobalCommandRunner.setTermUsedRows(context, height); GlobalCommandRunner.setTermUsedRows(context, height);
} }
@ -622,7 +624,7 @@ class Model {
} }
getUIContext(): UIContextType { getUIContext(): UIContextType {
let rtn: UIContextType = { const rtn: UIContextType = {
sessionid: null, sessionid: null,
screenid: null, screenid: null,
remote: null, remote: null,
@ -630,10 +632,10 @@ class Model {
linenum: null, linenum: null,
build: appconst.VERSION + " " + appconst.BUILD, build: appconst.VERSION + " " + appconst.BUILD,
}; };
let session = this.getActiveSession(); const session = this.getActiveSession();
if (session != null) { if (session != null) {
rtn.sessionid = session.sessionId; rtn.sessionid = session.sessionId;
let screen = session.getActiveScreen(); const screen = session.getActiveScreen();
if (screen != null) { if (screen != null) {
rtn.screenid = screen.screenId; rtn.screenid = screen.screenId;
rtn.remote = screen.curRemote.get(); rtn.remote = screen.curRemote.get();
@ -653,7 +655,7 @@ class Model {
} }
onLCmd(e: any, mods: KeyModsType) { onLCmd(e: any, mods: KeyModsType) {
let screen = this.getActiveScreen(); const screen = this.getActiveScreen();
if (screen != null) { if (screen != null) {
GlobalCommandRunner.screenSetFocus("cmd"); GlobalCommandRunner.screenSetFocus("cmd");
} }
@ -671,11 +673,11 @@ class Model {
if (this.inputModel.hasFocus()) { if (this.inputModel.hasFocus()) {
return { cmdInputFocus: true }; return { cmdInputFocus: true };
} }
let lineElem: any = document.activeElement.closest(".line[data-lineid]"); const lineElem: any = document.activeElement.closest(".line[data-lineid]");
if (lineElem == null) { if (lineElem == null) {
return { cmdInputFocus: false }; return { cmdInputFocus: false };
} }
let lineNum = parseInt(lineElem.dataset.linenum); const lineNum = parseInt(lineElem.dataset.linenum);
return { return {
cmdInputFocus: false, cmdInputFocus: false,
lineid: lineElem.dataset.lineid, lineid: lineElem.dataset.lineid,
@ -685,17 +687,17 @@ class Model {
} }
cmdStatusUpdate(screenId: string, lineId: string, origStatus: string, newStatus: string) { cmdStatusUpdate(screenId: string, lineId: string, origStatus: string, newStatus: string) {
let wasRunning = cmdStatusIsRunning(origStatus); const wasRunning = cmdStatusIsRunning(origStatus);
let isRunning = cmdStatusIsRunning(newStatus); const isRunning = cmdStatusIsRunning(newStatus);
if (wasRunning && !isRunning) { if (wasRunning && !isRunning) {
let ptr = this.getActiveLine(screenId, lineId); const ptr = this.getActiveLine(screenId, lineId);
if (ptr != null) { if (ptr != null) {
let screen = ptr.screen; const screen = ptr.screen;
let renderer = screen.getRenderer(lineId); const renderer = screen.getRenderer(lineId);
if (renderer != null) { if (renderer != null) {
renderer.setIsDone(); renderer.setIsDone();
} }
let term = screen.getTermWrap(lineId); const term = screen.getTermWrap(lineId);
if (term != null) { if (term != null) {
term.cmdDone(); term.cmdDone();
} }
@ -747,21 +749,25 @@ class Model {
runUpdate(genUpdate: UpdateMessage, interactive: boolean) { runUpdate(genUpdate: UpdateMessage, interactive: boolean) {
mobx.action(() => { mobx.action(() => {
let oldContext = this.getUIContext(); const oldContext = this.getUIContext();
try { try {
this.runUpdate_internal(genUpdate, oldContext, interactive); this.runUpdate_internal(genUpdate, oldContext, interactive);
} catch (e) { } catch (e) {
console.log("error running update", e, genUpdate); console.warn("error running update", e, genUpdate);
throw e; throw e;
} }
let newContext = this.getUIContext(); const newContext = this.getUIContext();
if (oldContext.sessionid != newContext.sessionid || oldContext.screenid != newContext.screenid) { if (oldContext.sessionid != newContext.sessionid || oldContext.screenid != newContext.screenid) {
this.inputModel.resetInput(); this.inputModel.resetInput();
if ("cmdline" in genUpdate) { if (!("ptydata64" in genUpdate)) {
const reversedGenUpdate = genUpdate.slice().reverse();
const lastCmdLine = reversedGenUpdate.find((update) => "cmdline" in update);
if (lastCmdLine) {
// TODO a bit of a hack since this update gets applied in runUpdate_internal. // TODO a bit of a hack since this update gets applied in runUpdate_internal.
// we then undo that update with the resetInput, and then redo it with the line below // we then undo that update with the resetInput, and then redo it with the line below
// not sure how else to handle this for now though // not sure how else to handle this for now though
this.inputModel.updateCmdLine(genUpdate.cmdline); this.inputModel.updateCmdLine(lastCmdLine.cmdline);
}
} }
} else if (remotePtrToString(oldContext.remote) != remotePtrToString(newContext.remote)) { } else if (remotePtrToString(oldContext.remote) != remotePtrToString(newContext.remote)) {
this.inputModel.resetHistory(); this.inputModel.resetHistory();
@ -769,27 +775,10 @@ class Model {
})(); })();
} }
runUpdate_internal(genUpdate: UpdateMessage, uiContext: UIContextType, interactive: boolean) { updateScreens(screens: ScreenDataType[]): void {
if ("ptydata64" in genUpdate) { const mods = genMergeDataMap(
let ptyMsg: PtyDataUpdateType = genUpdate;
if (isBlank(ptyMsg.remoteid)) {
// regular update
this.updatePtyData(ptyMsg);
} else {
// remote update
let ptyData = base64ToArray(ptyMsg.ptydata64);
this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
}
return;
}
let update: ModelUpdateType = genUpdate;
if ("screens" in update) {
if (update.connect) {
this.screenMap.clear();
}
let mods = genMergeDataMap(
this.screenMap, this.screenMap,
update.screens, screens,
(s: Screen) => s.screenId, (s: Screen) => s.screenId,
(sdata: ScreenDataType) => sdata.screenid, (sdata: ScreenDataType) => sdata.screenid,
(sdata: ScreenDataType) => new Screen(sdata, this) (sdata: ScreenDataType) => new Screen(sdata, this)
@ -798,116 +787,177 @@ class Model {
this.removeScreenLinesByScreenId(screenId); this.removeScreenLinesByScreenId(screenId);
} }
} }
if ("sessions" in update || "activesessionid" in update) {
if (update.connect) { updateSessions(sessions: SessionDataType[]): void {
this.sessionList.clear();
}
let [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds();
genMergeData( genMergeData(
this.sessionList, this.sessionList,
update.sessions, sessions,
(s: Session) => s.sessionId, (s: Session) => s.sessionId,
(sdata: SessionDataType) => sdata.sessionid, (sdata: SessionDataType) => sdata.sessionid,
(sdata: SessionDataType) => new Session(sdata, this), (sdata: SessionDataType) => new Session(sdata, this),
(s: Session) => s.sessionIdx.get() (s: Session) => s.sessionIdx.get()
); );
if ("activesessionid" in update) { }
let newSessionId = update.activesessionid;
updateActiveSession(sessionId: string): void {
const [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds();
if (sessionId != null) {
const newSessionId = sessionId;
if (this.activeSessionId.get() != newSessionId) { if (this.activeSessionId.get() != newSessionId) {
this.activeSessionId.set(newSessionId); this.activeSessionId.set(newSessionId);
} }
} }
let [newActiveSessionId, newActiveScreenId] = this.getActiveIds(); const [newActiveSessionId, newActiveScreenId] = this.getActiveIds();
if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) { if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) {
this.activeMainView.set("session"); this.activeMainView.set("session");
this.deactivateScreenLines(); this.deactivateScreenLines();
this.ws.watchScreen(newActiveSessionId, newActiveScreenId); this.ws.watchScreen(newActiveSessionId, newActiveScreenId);
} }
} }
if ("line" in update) {
this.addLineCmd(update.line, update.cmd, interactive); updateScreenNumRunningCommands(numRunningCommandUpdates: ScreenNumRunningCommandsUpdateType[]) {
} else if ("cmd" in update) { for (const update of numRunningCommandUpdates) {
this.updateCmd(update.cmd); this.getScreenById_single(update.screenid)?.setNumRunningCmds(update.num);
}
if ("lines" in update) {
for (const line of update.lines) {
this.addLineCmd(line, null, interactive);
} }
} }
if ("screenlines" in update) {
this.updateScreenLines(update.screenlines, false); updateScreenStatusIndicators(screenStatusIndicators: ScreenStatusIndicatorUpdateType[]) {
} for (const update of screenStatusIndicators) {
if ("remotes" in update) { this.getScreenById_single(update.screenid)?.setStatusIndicator(update.status);
if (update.connect) {
this.remotes.clear();
}
this.updateRemotes(update.remotes);
// This code's purpose is to show view remote connection modal when a new connection is added
if (update.remotes?.length && this.remotesModel.recentConnAddedState.get()) {
this.remotesModel.openReadModal(update.remotes[0].remoteid);
} }
} }
if ("mainview" in update) {
if (update.mainview == "plugins") { runUpdate_internal(genUpdate: UpdateMessage, uiContext: UIContextType, interactive: boolean) {
this.pluginsModel.showPluginsView(); if ("ptydata64" in genUpdate) {
} else if (update.mainview == "bookmarks") { const ptyMsg: PtyDataUpdateType = genUpdate;
this.bookmarksModel.showBookmarksView(update.bookmarks, update.selectedbookmark); if (isBlank(ptyMsg.remoteid)) {
} else if (update.mainview == "session") { // regular update
this.activeMainView.set("session"); this.updatePtyData(ptyMsg);
} else if (update.mainview == "history") {
this.historyViewModel.showHistoryView(update.historyviewdata);
} else { } else {
console.log("invalid mainview in update:", update.mainview); // remote update
const ptyData = base64ToArray(ptyMsg.ptydata64);
this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData);
} }
} else if ("bookmarks" in update) { return;
this.bookmarksModel.mergeBookmarks(update.bookmarks);
} }
if ("clientdata" in update) { let showedRemotesModal = false;
genUpdate.forEach((update) => {
if (update.connect != null) {
if (update.connect.screens != null) {
this.screenMap.clear();
this.updateScreens(update.connect.screens);
}
if (update.connect.sessions != null) {
this.sessionList.clear();
this.updateSessions(update.connect.sessions);
}
if (update.connect.remotes != null) {
this.remotes.clear();
this.updateRemotes(update.connect.remotes);
}
if (update.connect.activesessionid != null) {
this.updateActiveSession(update.connect.activesessionid);
}
if (update.connect.screennumrunningcommands != null) {
this.updateScreenNumRunningCommands(update.connect.screennumrunningcommands);
}
if (update.connect.screenstatusindicators != null) {
this.updateScreenStatusIndicators(update.connect.screenstatusindicators);
}
this.sessionListLoaded.set(true);
this.remotesLoaded.set(true);
} else if (update.screen != null) {
this.updateScreens([update.screen]);
} else if (update.session != null) {
this.updateSessions([update.session]);
} else if (update.activesessionid != null) {
this.updateActiveSession(update.activesessionid);
} else if (update.line != null) {
this.addLineCmd(update.line.line, update.line.cmd, interactive);
} else if (update.cmd != null) {
this.updateCmd(update.cmd);
} else if (update.screenlines != null) {
this.updateScreenLines(update.screenlines, false);
} else if (update.remote != null) {
this.updateRemotes([update.remote]);
// This code's purpose is to show view remote connection modal when a new connection is added
if (!showedRemotesModal && this.remotesModel.recentConnAddedState.get()) {
showedRemotesModal = true;
this.remotesModel.openReadModal(update.remote.remoteid);
}
} else if (update.mainview != null) {
switch (update.mainview.mainview) {
case "session":
this.activeMainView.set("session");
break;
case "history":
if (update.mainview.historyview != null) {
this.historyViewModel.showHistoryView(update.mainview.historyview);
} else {
console.warn("invalid historyview in update:", update.mainview);
}
break;
case "bookmarks":
if (update.mainview.bookmarksview != null) {
this.bookmarksModel.showBookmarksView(
update.mainview.bookmarksview?.bookmarks ?? [],
update.mainview.bookmarksview?.selectedbookmark
);
} else {
console.warn("invalid bookmarksview in update:", update.mainview);
}
break;
case "plugins":
this.pluginsModel.showPluginsView();
break;
default:
console.warn("invalid mainview in update:", update.mainview);
}
} else if (update.bookmarks != null) {
if (update.bookmarks.bookmarks != null) {
this.bookmarksModel.mergeBookmarks(update.bookmarks.bookmarks);
}
} else if (update.clientdata != null) {
this.clientData.set(update.clientdata); this.clientData.set(update.clientdata);
} } else if (update.cmdline != null) {
if (interactive && "info" in update) { this.inputModel.updateCmdLine(update.cmdline);
let info: InfoType = update.info; } else if (update.openaicmdinfochat != null) {
this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat);
} else if (update.screenstatusindicator != null) {
this.updateScreenStatusIndicators([update.screenstatusindicator]);
} else if (update.screennumrunningcommands != null) {
this.updateScreenNumRunningCommands([update.screennumrunningcommands]);
} else if (update.userinputrequest != null) {
let userInputRequest: UserInputRequest = update.userinputrequest;
this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest);
} else if (interactive) {
if (update.info != null) {
const info: InfoType = update.info;
this.inputModel.flashInfoMsg(info, info.timeoutms); this.inputModel.flashInfoMsg(info, info.timeoutms);
} } else if (update.remoteview != null) {
if (interactive && "remoteview" in update) { const rview: RemoteViewType = update.remoteview;
let rview: RemoteViewType = update.remoteview;
if (rview.remoteedit != null) { if (rview.remoteedit != null) {
this.remotesModel.openEditModal({ ...rview.remoteedit }); this.remotesModel.openEditModal({ ...rview.remoteedit });
} }
} } else if (update.alertmessage != null) {
if (interactive && "alertmessage" in update) { const alertMessage: AlertMessageType = update.alertmessage;
let alertMessage: AlertMessageType = update.alertmessage;
this.showAlert(alertMessage); this.showAlert(alertMessage);
} } else if (update.history != null) {
if ("cmdline" in update) { if (
this.inputModel.updateCmdLine(update.cmdline); uiContext.sessionid == update.history.sessionid &&
} uiContext.screenid == update.history.screenid
if (interactive && "history" in update) { ) {
if (uiContext.sessionid == update.history.sessionid && uiContext.screenid == update.history.screenid) {
this.inputModel.setHistoryInfo(update.history); this.inputModel.setHistoryInfo(update.history);
} }
} else if (this.isDev) {
console.log("did not match update", update);
} }
if ("connect" in update) { } else if (this.isDev) {
this.sessionListLoaded.set(true); console.log("did not match update", update);
this.remotesLoaded.set(true);
}
if ("openaicmdinfochat" in update) {
this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat);
}
if ("screenstatusindicators" in update) {
for (const indicator of update.screenstatusindicators) {
this.getScreenById_single(indicator.screenid)?.setStatusIndicator(indicator.status);
}
}
if ("screennumrunningcommands" in update) {
for (const snc of update.screennumrunningcommands) {
this.getScreenById_single(snc.screenid)?.setNumRunningCmds(snc.num);
}
}
if ("userinputrequest" in update) {
let userInputRequest: UserInputRequest = update.userinputrequest;
this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest);
} }
});
} }
updateRemotes(remotes: RemoteType[]): void { updateRemotes(remotes: RemoteType[]): void {
@ -919,7 +969,7 @@ class Model {
} }
getSessionNames(): Record<string, string> { getSessionNames(): Record<string, string> {
let rtn: Record<string, string> = {}; const rtn: Record<string, string> = {};
for (const session of this.sessionList) { for (const session of this.sessionList) {
rtn[session.sessionId] = session.name.get(); rtn[session.sessionId] = session.name.get();
} }
@ -927,8 +977,8 @@ class Model {
} }
getScreenNames(): Record<string, string> { getScreenNames(): Record<string, string> {
let rtn: Record<string, string> = {}; const rtn: Record<string, string> = {};
for (let screen of this.screenMap.values()) { for (const screen of this.screenMap.values()) {
rtn[screen.screenId] = screen.name.get(); rtn[screen.screenId] = screen.name.get();
} }
return rtn; return rtn;
@ -958,13 +1008,13 @@ class Model {
updateScreenLines(slines: ScreenLinesType, load: boolean) { updateScreenLines(slines: ScreenLinesType, load: boolean) {
mobx.action(() => { mobx.action(() => {
let existingWin = this.screenLines.get(slines.screenid); const existingWin = this.screenLines.get(slines.screenid);
if (existingWin == null) { if (existingWin == null) {
if (!load) { if (!load) {
console.log("cannot update screen-lines that does not exist", slines.screenid); console.log("cannot update screen-lines that does not exist", slines.screenid);
return; return;
} }
let newWindow = new ScreenLines(slines.screenid); const newWindow = new ScreenLines(slines.screenid);
this.screenLines.set(slines.screenid, newWindow); this.screenLines.set(slines.screenid, newWindow);
newWindow.updateData(slines, load); newWindow.updateData(slines, load);
} else { } else {
@ -989,8 +1039,8 @@ class Model {
} }
getSessionScreens(sessionId: string): Screen[] { getSessionScreens(sessionId: string): Screen[] {
let rtn: Screen[] = []; const rtn: Screen[] = [];
for (let screen of this.screenMap.values()) { for (const screen of this.screenMap.values()) {
if (screen.sessionId == sessionId) { if (screen.sessionId == sessionId) {
rtn.push(screen); rtn.push(screen);
} }
@ -999,7 +1049,7 @@ class Model {
} }
getScreenLinesForActiveScreen(): ScreenLines { getScreenLinesForActiveScreen(): ScreenLines {
let screen = this.getActiveScreen(); const screen = this.getActiveScreen();
if (screen == null) { if (screen == null) {
return null; return null;
} }
@ -1007,7 +1057,7 @@ class Model {
} }
getActiveScreen(): Screen { getActiveScreen(): Screen {
let session = this.getActiveSession(); const session = this.getActiveSession();
if (session == null) { if (session == null) {
return null; return null;
} }
@ -1018,11 +1068,11 @@ class Model {
if (cmd == null || !cmd.restarted) { if (cmd == null || !cmd.restarted) {
return; return;
} }
let screen = this.screenMap.get(cmd.screenid); const screen = this.screenMap.get(cmd.screenid);
if (screen == null) { if (screen == null) {
return; return;
} }
let termWrap = screen.getTermWrap(cmd.lineid); const termWrap = screen.getTermWrap(cmd.lineid);
if (termWrap == null) { if (termWrap == null) {
return; return;
} }
@ -1030,7 +1080,7 @@ class Model {
} }
addLineCmd(line: LineType, cmd: CmdDataType, interactive: boolean) { addLineCmd(line: LineType, cmd: CmdDataType, interactive: boolean) {
let slines = this.getScreenLinesById(line.screenid); const slines = this.getScreenLinesById(line.screenid);
if (slines == null) { if (slines == null) {
return; return;
} }
@ -1039,7 +1089,7 @@ class Model {
} }
updateCmd(cmd: CmdDataType) { updateCmd(cmd: CmdDataType) {
let slines = this.screenLines.get(cmd.screenid); const slines = this.screenLines.get(cmd.screenid);
if (slines != null) { if (slines != null) {
slines.updateCmd(cmd); slines.updateCmd(cmd);
} }
@ -1050,12 +1100,12 @@ class Model {
if (update == null || "ptydata64" in update) { if (update == null || "ptydata64" in update) {
return false; return false;
} }
return update.info != null || update.history != null; return update.some((u) => u.info != null || u.history != null);
} }
getClientDataLoop(loopNum: number): void { getClientDataLoop(loopNum: number): void {
this.getClientData(); this.getClientData();
let clientStop = this.getHasClientStop(); const clientStop = this.getHasClientStop();
if (this.clientData.get() != null && !clientStop) { if (this.clientData.get() != null && !clientStop) {
return; return;
} }
@ -1073,13 +1123,13 @@ class Model {
} }
getClientData(): void { getClientData(): void {
let url = new URL(this.getBaseHostPort() + "/api/get-client-data"); const url = new URL(this.getBaseHostPort() + "/api/get-client-data");
let fetchHeaders = this.getFetchHeaders(); const fetchHeaders = this.getFetchHeaders();
fetch(url, { method: "post", body: null, headers: fetchHeaders }) fetch(url, { method: "post", body: null, headers: fetchHeaders })
.then((resp) => handleJsonFetchResponse(url, resp)) .then((resp) => handleJsonFetchResponse(url, resp))
.then((data) => { .then((data) => {
mobx.action(() => { mobx.action(() => {
let clientData: ClientDataType = data.data; const clientData: ClientDataType = data.data;
this.clientData.set(clientData); this.clientData.set(clientData);
})(); })();
}) })
@ -1096,10 +1146,10 @@ class Model {
} }
} }
// adding cmdStr for debugging only (easily filter run-command calls in the network tab of debugger) // adding cmdStr for debugging only (easily filter run-command calls in the network tab of debugger)
let cmdStr = cmdPk.metacmd + (cmdPk.metasubcmd ? ":" + cmdPk.metasubcmd : ""); const cmdStr = cmdPk.metacmd + (cmdPk.metasubcmd ? ":" + cmdPk.metasubcmd : "");
let url = new URL(this.getBaseHostPort() + "/api/run-command?cmd=" + cmdStr); const url = new URL(this.getBaseHostPort() + "/api/run-command?cmd=" + cmdStr);
let fetchHeaders = this.getFetchHeaders(); const fetchHeaders = this.getFetchHeaders();
let prtn = fetch(url, { const prtn = fetch(url, {
method: "post", method: "post",
body: JSON.stringify(cmdPk), body: JSON.stringify(cmdPk),
headers: fetchHeaders, headers: fetchHeaders,
@ -1107,7 +1157,7 @@ class Model {
.then((resp) => handleJsonFetchResponse(url, resp)) .then((resp) => handleJsonFetchResponse(url, resp))
.then((data) => { .then((data) => {
mobx.action(() => { mobx.action(() => {
let update = data.data; const update = data.data;
if (update != null) { if (update != null) {
this.runUpdate(update, interactive); this.runUpdate(update, interactive);
} }
@ -1135,7 +1185,7 @@ class Model {
kwargs: Record<string, string>, kwargs: Record<string, string>,
interactive: boolean interactive: boolean
): Promise<CommandRtnType> { ): Promise<CommandRtnType> {
let pk: FeCmdPacketType = { const pk: FeCmdPacketType = {
type: "fecmd", type: "fecmd",
metacmd: metaCmd, metacmd: metaCmd,
metasubcmd: metaSubCmd, metasubcmd: metaSubCmd,
@ -1157,9 +1207,9 @@ class Model {
} }
submitChatInfoCommand(chatMsg: string, curLineStr: string, clear: boolean): Promise<CommandRtnType> { submitChatInfoCommand(chatMsg: string, curLineStr: string, clear: boolean): Promise<CommandRtnType> {
let commandStr = "/chat " + chatMsg; const commandStr = "/chat " + chatMsg;
let interactive = false; const interactive = false;
let pk: FeCmdPacketType = { const pk: FeCmdPacketType = {
type: "fecmd", type: "fecmd",
metacmd: "eval", metacmd: "eval",
args: [commandStr], args: [commandStr],
@ -1179,7 +1229,7 @@ class Model {
} }
submitRawCommand(cmdStr: string, addToHistory: boolean, interactive: boolean): Promise<CommandRtnType> { submitRawCommand(cmdStr: string, addToHistory: boolean, interactive: boolean): Promise<CommandRtnType> {
let pk: FeCmdPacketType = { const pk: FeCmdPacketType = {
type: "fecmd", type: "fecmd",
metacmd: "eval", metacmd: "eval",
args: [cmdStr], args: [cmdStr],
@ -1196,16 +1246,16 @@ class Model {
// returns [sessionId, screenId] // returns [sessionId, screenId]
getActiveIds(): [string, string] { getActiveIds(): [string, string] {
let activeSession = this.getActiveSession(); const activeSession = this.getActiveSession();
let activeScreen = this.getActiveScreen(); const activeScreen = this.getActiveScreen();
return [activeSession?.sessionId, activeScreen?.screenId]; return [activeSession?.sessionId, activeScreen?.screenId];
} }
_loadScreenLinesAsync(newWin: ScreenLines) { _loadScreenLinesAsync(newWin: ScreenLines) {
this.screenLines.set(newWin.screenId, newWin); this.screenLines.set(newWin.screenId, newWin);
let usp = new URLSearchParams({ screenid: newWin.screenId }); const usp = new URLSearchParams({ screenid: newWin.screenId });
let url = new URL(this.getBaseHostPort() + "/api/get-screen-lines?" + usp.toString()); const url = new URL(this.getBaseHostPort() + "/api/get-screen-lines?" + usp.toString());
let fetchHeaders = this.getFetchHeaders(); const fetchHeaders = this.getFetchHeaders();
fetch(url, { headers: fetchHeaders }) fetch(url, { headers: fetchHeaders })
.then((resp) => handleJsonFetchResponse(url, resp)) .then((resp) => handleJsonFetchResponse(url, resp))
.then((data) => { .then((data) => {
@ -1213,7 +1263,7 @@ class Model {
console.log("null screen-lines returned from get-screen-lines"); console.log("null screen-lines returned from get-screen-lines");
return; return;
} }
let slines: ScreenLinesType = data.data; const slines: ScreenLinesType = data.data;
this.updateScreenLines(slines, true); this.updateScreenLines(slines, true);
}) })
.catch((err) => { .catch((err) => {
@ -1222,7 +1272,7 @@ class Model {
} }
loadScreenLines(screenId: string): ScreenLines { loadScreenLines(screenId: string): ScreenLines {
let newWin = new ScreenLines(screenId); const newWin = new ScreenLines(screenId);
setTimeout(() => this._loadScreenLinesAsync(newWin), 0); setTimeout(() => this._loadScreenLinesAsync(newWin), 0);
return newWin; return newWin;
} }
@ -1235,7 +1285,7 @@ class Model {
} }
getRemoteNames(): Record<string, string> { getRemoteNames(): Record<string, string> {
let rtn: Record<string, string> = {}; const rtn: Record<string, string> = {};
for (const remote of this.remotes) { for (const remote of this.remotes) {
if (!isBlank(remote.remotealias)) { if (!isBlank(remote.remotealias)) {
rtn[remote.remoteid] = remote.remotealias; rtn[remote.remoteid] = remote.remotealias;
@ -1260,7 +1310,7 @@ class Model {
} }
getCmdByScreenLine(screenId: string, lineId: string): Cmd { getCmdByScreenLine(screenId: string, lineId: string): Cmd {
let slines = this.getScreenLinesById(screenId); const slines = this.getScreenLinesById(screenId);
if (slines == null) { if (slines == null) {
return null; return null;
} }
@ -1268,14 +1318,14 @@ class Model {
} }
getActiveLine(screenId: string, lineid: string): SWLinePtr { getActiveLine(screenId: string, lineid: string): SWLinePtr {
let slines = this.screenLines.get(screenId); const slines = this.screenLines.get(screenId);
if (slines == null) { if (slines == null) {
return null; return null;
} }
if (!slines.loaded.get()) { if (!slines.loaded.get()) {
return null; return null;
} }
let cmd = slines.getCmd(lineid); const cmd = slines.getCmd(lineid);
if (cmd == null) { if (cmd == null) {
return null; return null;
} }
@ -1289,12 +1339,12 @@ class Model {
if (line == null) { if (line == null) {
return null; return null;
} }
let screen = this.getScreenById_single(slines.screenId); const screen = this.getScreenById_single(slines.screenId);
return { line: line, slines: slines, screen: screen }; return { line: line, slines: slines, screen: screen };
} }
updatePtyData(ptyMsg: PtyDataUpdateType): void { updatePtyData(ptyMsg: PtyDataUpdateType): void {
let linePtr = this.getActiveLine(ptyMsg.screenid, ptyMsg.lineid); const linePtr = this.getActiveLine(ptyMsg.screenid, ptyMsg.lineid);
if (linePtr != null) { if (linePtr != null) {
linePtr.screen.updatePtyData(ptyMsg); linePtr.screen.updatePtyData(ptyMsg);
} }
@ -1320,7 +1370,7 @@ class Model {
} }
sendCmdInputText(screenId: string, sp: StrWithPos) { sendCmdInputText(screenId: string, sp: StrWithPos) {
let pk: CmdInputTextPacketType = { const pk: CmdInputTextPacketType = {
type: "cmdinputtext", type: "cmdinputtext",
seqnum: this.getNextPacketSeqNum(), seqnum: this.getNextPacketSeqNum(),
screenid: screenId, screenid: screenId,
@ -1334,7 +1384,7 @@ class Model {
} }
resolveRemoteIdToRef(remoteId: string) { resolveRemoteIdToRef(remoteId: string) {
let remote = this.getRemote(remoteId); const remote = this.getRemote(remoteId);
if (remote == null) { if (remote == null) {
return "[unknown]"; return "[unknown]";
} }
@ -1345,7 +1395,7 @@ class Model {
} }
resolveRemoteIdToFullRef(remoteId: string) { resolveRemoteIdToFullRef(remoteId: string) {
let remote = this.getRemote(remoteId); const remote = this.getRemote(remoteId);
if (remote == null) { if (remote == null) {
return "[unknown]"; return "[unknown]";
} }
@ -1356,17 +1406,17 @@ class Model {
} }
readRemoteFile(screenId: string, lineId: string, path: string): Promise<ExtFile> { readRemoteFile(screenId: string, lineId: string, path: string): Promise<ExtFile> {
let urlParams = { const urlParams = {
screenid: screenId, screenid: screenId,
lineid: lineId, lineid: lineId,
path: path, path: path,
}; };
let usp = new URLSearchParams(urlParams); const usp = new URLSearchParams(urlParams);
let url = new URL(this.getBaseHostPort() + "/api/read-file?" + usp.toString()); const url = new URL(this.getBaseHostPort() + "/api/read-file?" + usp.toString());
let fetchHeaders = this.getFetchHeaders(); const fetchHeaders = this.getFetchHeaders();
let fileInfo: FileInfoType = null; let fileInfo: FileInfoType = null;
let badResponseStr: string = null; let badResponseStr: string = null;
let prtn = fetch(url, { method: "get", headers: fetchHeaders }) const prtn = fetch(url, { method: "get", headers: fetchHeaders })
.then((resp) => { .then((resp) => {
if (!resp.ok) { if (!resp.ok) {
badResponseStr = sprintf( badResponseStr = sprintf(
@ -1381,14 +1431,14 @@ class Model {
}) })
.then((blobOrText: any) => { .then((blobOrText: any) => {
if (blobOrText instanceof Blob) { if (blobOrText instanceof Blob) {
let blob: Blob = blobOrText; const blob: Blob = blobOrText;
let file = new File([blob], fileInfo.name, { type: blob.type, lastModified: fileInfo.modts }); const file = new File([blob], fileInfo.name, { type: blob.type, lastModified: fileInfo.modts });
let isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits const isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits
(file as any).readOnly = !isWriteable; (file as any).readOnly = !isWriteable;
(file as any).notFound = !!fileInfo.notfound; (file as any).notFound = !!fileInfo.notfound;
return file as ExtFile; return file as ExtFile;
} else { } else {
let textError: string = blobOrText; const textError: string = blobOrText;
if (textError == null || textError.length == 0) { if (textError == null || textError.length == 0) {
throw new Error(badResponseStr); throw new Error(badResponseStr);
} }
@ -1398,7 +1448,7 @@ class Model {
return prtn; return prtn;
} }
writeRemoteFile( async writeRemoteFile(
screenId: string, screenId: string,
lineId: string, lineId: string,
path: string, path: string,
@ -1406,24 +1456,21 @@ class Model {
opts?: { useTemp?: boolean } opts?: { useTemp?: boolean }
): Promise<void> { ): Promise<void> {
opts = opts || {}; opts = opts || {};
let params = { const params = {
screenid: screenId, screenid: screenId,
lineid: lineId, lineid: lineId,
path: path, path: path,
usetemp: !!opts.useTemp, usetemp: !!opts.useTemp,
}; };
let formData = new FormData(); const formData = new FormData();
formData.append("params", JSON.stringify(params)); formData.append("params", JSON.stringify(params));
let blob = new Blob([data], { type: "application/octet-stream" }); const blob = new Blob([data], { type: "application/octet-stream" });
formData.append("data", blob); formData.append("data", blob);
let url = new URL(this.getBaseHostPort() + "/api/write-file"); const url = new URL(this.getBaseHostPort() + "/api/write-file");
let fetchHeaders = this.getFetchHeaders(); const fetchHeaders = this.getFetchHeaders();
let prtn = fetch(url, { method: "post", headers: fetchHeaders, body: formData }); const prtn = fetch(url, { method: "post", headers: fetchHeaders, body: formData });
return prtn const resp = await prtn;
.then((resp) => handleJsonFetchResponse(url, resp)) const _ = await handleJsonFetchResponse(url, resp);
.then((_) => {
return;
});
} }
} }

View File

@ -139,7 +139,7 @@ class ScreenLines {
return; return;
} }
let lineIdx = 0; let lineIdx = 0;
for (lineIdx = 0; lineIdx < lines.length; lineIdx++) { for (lineIdx; lineIdx < lines.length; lineIdx++) {
let lineId = lines[lineIdx].lineid; let lineId = lines[lineIdx].lineid;
let curTs = lines[lineIdx].ts; let curTs = lines[lineIdx].ts;
if (lineId == line.lineid) { if (lineId == line.lineid) {

View File

@ -29,7 +29,6 @@ type SessionDataType = {
// for updates // for updates
remove?: boolean; remove?: boolean;
full?: boolean;
}; };
type LineStateType = { [k: string]: any }; type LineStateType = { [k: string]: any };
@ -92,7 +91,6 @@ type ScreenDataType = {
anchor: { anchorline: number; anchoroffset: number }; anchor: { anchorline: number; anchoroffset: number };
// for updates // for updates
full?: boolean;
remove?: boolean; remove?: boolean;
}; };
@ -260,6 +258,11 @@ type CmdDataType = {
restarted?: boolean; restarted?: boolean;
}; };
type LineUpdateType = {
line: LineType;
cmd: CmdDataType;
};
type PtyDataUpdateType = { type PtyDataUpdateType = {
screenid: string; screenid: string;
lineid: string; lineid: string;
@ -309,30 +312,47 @@ type ScreenNumRunningCommandsUpdateType = {
num: number; num: number;
}; };
type ConnectUpdateType = {
sessions: SessionDataType[];
screens: ScreenDataType[];
remotes: RemoteType[];
screenstatusindicators: ScreenStatusIndicatorUpdateType[];
screennumrunningcommands: ScreenNumRunningCommandsUpdateType[];
activesessionid: string;
};
type BookmarksUpdateType = {
bookmarks: BookmarkType[];
selectedbookmark: string;
};
type MainViewUpdateType = {
mainview: string;
historyview?: HistoryViewDataType;
bookmarksview?: BookmarksUpdateType;
};
type ModelUpdateType = { type ModelUpdateType = {
interactive: boolean; interactive: boolean;
sessions?: SessionDataType[]; session?: SessionDataType;
activesessionid?: string; activesessionid?: string;
screens?: ScreenDataType[]; screen?: ScreenDataType;
screenlines?: ScreenLinesType; screenlines?: ScreenLinesType;
line?: LineType; line?: LineUpdateType;
lines?: LineType[];
cmd?: CmdDataType; cmd?: CmdDataType;
info?: InfoType; info?: InfoType;
cmdline?: StrWithPos; cmdline?: StrWithPos;
remotes?: RemoteType[]; remote?: RemoteType;
history?: HistoryInfoType; history?: HistoryInfoType;
connect?: boolean; connect?: ConnectUpdateType;
mainview?: string; mainview?: MainViewUpdateType;
bookmarks?: BookmarkType[]; bookmarks?: BookmarksUpdateType;
selectedbookmark?: string;
clientdata?: ClientDataType; clientdata?: ClientDataType;
historyviewdata?: HistoryViewDataType;
remoteview?: RemoteViewType; remoteview?: RemoteViewType;
openaicmdinfochat?: OpenAICmdInfoChatMessageType[]; openaicmdinfochat?: OpenAICmdInfoChatMessageType[];
alertmessage?: AlertMessageType; alertmessage?: AlertMessageType;
screenstatusindicators?: ScreenStatusIndicatorUpdateType[]; screenstatusindicator?: ScreenStatusIndicatorUpdateType;
screennumrunningcommands?: ScreenNumRunningCommandsUpdateType[]; screennumrunningcommands?: ScreenNumRunningCommandsUpdateType;
userinputrequest?: UserInputRequest; userinputrequest?: UserInputRequest;
}; };
@ -415,7 +435,7 @@ type ContextMenuOpts = {
showCut?: boolean; showCut?: boolean;
}; };
type UpdateMessage = PtyDataUpdateType | ModelUpdateType; type UpdateMessage = PtyDataUpdateType | ModelUpdateType[];
type RendererContext = { type RendererContext = {
screenId: string; screenId: string;
@ -866,6 +886,7 @@ export type {
CmdInputTextPacketType, CmdInputTextPacketType,
OpenAICmdInfoChatMessageType, OpenAICmdInfoChatMessageType,
ScreenStatusIndicatorUpdateType, ScreenStatusIndicatorUpdateType,
ScreenNumRunningCommandsUpdateType,
OV, OV,
OArr, OArr,
OMap, OMap,

View File

@ -1,4 +1,4 @@
// Copyright 2023, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import * as mobx from "mobx"; import * as mobx from "mobx";
@ -17,7 +17,7 @@ function isBlank(s: string): boolean {
} }
function handleNotOkResp(resp: any, url: URL): Promise<any> { function handleNotOkResp(resp: any, url: URL): Promise<any> {
let errMsg = sprintf( const errMsg = sprintf(
"Bad status code response from fetch '%s': code=%d %s", "Bad status code response from fetch '%s': code=%d %s",
url.toString(), url.toString(),
resp.status, resp.status,
@ -41,18 +41,18 @@ function handleNotOkResp(resp: any, url: URL): Promise<any> {
} }
function fetchJsonData(resp: any, ctErr: boolean): Promise<any> { function fetchJsonData(resp: any, ctErr: boolean): Promise<any> {
let contentType = resp.headers.get("Content-Type"); const contentType = resp.headers.get("Content-Type");
if (contentType != null && contentType.startsWith("application/json")) { if (contentType?.startsWith("application/json")) {
return resp.text().then((textData) => { return resp.text().then((textData) => {
let rtnData: any = null; let rtnData: any = null;
try { try {
rtnData = JSON.parse(textData); rtnData = JSON.parse(textData);
} catch (err) { } catch (err) {
let errMsg = sprintf("Unparseable JSON: " + err.message); const errMsg = sprintf("Unparseable JSON: " + err.message);
let rtnErr = new Error(errMsg); const rtnErr = new Error(errMsg);
throw rtnErr; throw rtnErr;
} }
if (rtnData != null && rtnData.error) { if (rtnData?.error) {
throw new Error(rtnData.error); throw new Error(rtnData.error);
} }
return rtnData; return rtnData;
@ -67,23 +67,23 @@ function handleJsonFetchResponse(url: URL, resp: any): Promise<any> {
if (!resp.ok) { if (!resp.ok) {
return handleNotOkResp(resp, url); return handleNotOkResp(resp, url);
} }
let rtnData = fetchJsonData(resp, true); const rtnData = fetchJsonData(resp, true);
return rtnData; return rtnData;
} }
function base64ToString(b64: string): string { function base64ToString(b64: string): string {
let stringBytes = base64.toByteArray(b64); const stringBytes = base64.toByteArray(b64);
return new TextDecoder().decode(stringBytes); return new TextDecoder().decode(stringBytes);
} }
function stringToBase64(input: string): string { function stringToBase64(input: string): string {
let stringBytes = new TextEncoder().encode(input); const stringBytes = new TextEncoder().encode(input);
return base64.fromByteArray(stringBytes); return base64.fromByteArray(stringBytes);
} }
function base64ToArray(b64: string): Uint8Array { function base64ToArray(b64: string): Uint8Array {
let rawStr = atob(b64); const rawStr = atob(b64);
let rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length)); const rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length));
for (let i = 0; i < rawStr.length; i++) { for (let i = 0; i < rawStr.length; i++) {
rtnArr[i] = rawStr.charCodeAt(i); rtnArr[i] = rawStr.charCodeAt(i);
} }
@ -92,7 +92,6 @@ function base64ToArray(b64: string): Uint8Array {
interface IDataType { interface IDataType {
remove?: boolean; remove?: boolean;
full?: boolean;
} }
interface IObjType<DataType> { interface IObjType<DataType> {
@ -113,31 +112,28 @@ function genMergeSimpleData<T extends ISimpleDataType>(
if (dataArr == null || dataArr.length == 0) { if (dataArr == null || dataArr.length == 0) {
return; return;
} }
let objMap: Record<string, T> = {}; const objMap: Record<string, T> = {};
for (let i = 0; i < objs.length; i++) { for (const obj of objs) {
let obj = objs[i]; const id = idFn(obj);
let id = idFn(obj);
objMap[id] = obj; objMap[id] = obj;
} }
for (let i = 0; i < dataArr.length; i++) { for (const dataItem of dataArr) {
let dataItem = dataArr[i];
if (dataItem == null) { if (dataItem == null) {
console.log("genMergeSimpleData, null item"); console.log("genMergeSimpleData, null item");
console.trace(); console.trace();
} }
let id = idFn(dataItem); const id = idFn(dataItem);
if (dataItem.remove) { if (dataItem.remove) {
delete objMap[id]; delete objMap[id];
continue;
} else { } else {
objMap[id] = dataItem; objMap[id] = dataItem;
} }
} }
let newObjs = Object.values(objMap); const newObjs = Object.values(objMap);
if (sortIdxFn) { if (sortIdxFn) {
newObjs.sort((a, b) => { newObjs.sort((a, b) => {
let astr = sortIdxFn(a); const astr = sortIdxFn(a);
let bstr = sortIdxFn(b); const bstr = sortIdxFn(b);
return astr.localeCompare(bstr); return astr.localeCompare(bstr);
}); });
} }
@ -155,20 +151,18 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
if (dataArr == null || dataArr.length == 0) { if (dataArr == null || dataArr.length == 0) {
return; return;
} }
let objMap: Record<string, ObjType> = {}; const objMap: Record<string, ObjType> = {};
for (let i = 0; i < objs.length; i++) { for (const obj of objs) {
let obj = objs[i]; const id = objIdFn(obj);
let id = objIdFn(obj);
objMap[id] = obj; objMap[id] = obj;
} }
for (let i = 0; i < dataArr.length; i++) { for (const dataItem of dataArr) {
let dataItem = dataArr[i];
if (dataItem == null) { if (dataItem == null) {
console.log("genMergeData, null item"); console.log("genMergeData, null item");
console.trace(); console.trace();
continue; continue;
} }
let id = dataIdFn(dataItem); const id = dataIdFn(dataItem);
let obj = objMap[id]; let obj = objMap[id];
if (dataItem.remove) { if (dataItem.remove) {
if (obj != null) { if (obj != null) {
@ -178,17 +172,13 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
continue; continue;
} }
if (obj == null) { if (obj == null) {
if (!dataItem.full) {
console.log("cannot create object, dataitem is not full", objs, dataItem);
continue;
}
obj = ctorFn(dataItem); obj = ctorFn(dataItem);
objMap[id] = obj; objMap[id] = obj;
continue; continue;
} }
obj.mergeData(dataItem); obj.mergeData(dataItem);
} }
let newObjs = Object.values(objMap); const newObjs = Object.values(objMap);
if (sortIdxFn) { if (sortIdxFn) {
newObjs.sort((a, b) => { newObjs.sort((a, b) => {
return sortIdxFn(a) - sortIdxFn(b); return sortIdxFn(a) - sortIdxFn(b);
@ -204,18 +194,17 @@ function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends ID
dataIdFn: (data: DataType) => string, dataIdFn: (data: DataType) => string,
ctorFn: (data: DataType) => ObjType ctorFn: (data: DataType) => ObjType
): { added: string[]; removed: string[] } { ): { added: string[]; removed: string[] } {
let rtn: { added: string[]; removed: string[] } = { added: [], removed: [] }; const rtn: { added: string[]; removed: string[] } = { added: [], removed: [] };
if (dataArr == null || dataArr.length == 0) { if (dataArr == null || dataArr.length == 0) {
return rtn; return rtn;
} }
for (let i = 0; i < dataArr.length; i++) { for (const dataItem of dataArr) {
let dataItem = dataArr[i];
if (dataItem == null) { if (dataItem == null) {
console.log("genMergeDataMap, null item"); console.log("genMergeDataMap, null item");
console.trace(); console.trace();
continue; continue;
} }
let id = dataIdFn(dataItem); const id = dataIdFn(dataItem);
let obj = objMap.get(id); let obj = objMap.get(id);
if (dataItem.remove) { if (dataItem.remove) {
if (obj != null) { if (obj != null) {
@ -226,10 +215,6 @@ function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends ID
continue; continue;
} }
if (obj == null) { if (obj == null) {
if (!dataItem.full) {
console.log("cannot create object, dataitem is not full", dataItem);
continue;
}
obj = ctorFn(dataItem); obj = ctorFn(dataItem);
objMap.set(id, obj); objMap.set(id, obj);
rtn.added.push(id); rtn.added.push(id);
@ -262,23 +247,23 @@ function incObs(inum: mobx.IObservableValue<number>) {
// @check:font // @check:font
function loadFonts() { function loadFonts() {
let jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", { const jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", {
style: "normal", style: "normal",
weight: "400", weight: "400",
}); });
let jbmFont200 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-200.woff2')", { const jbmFont200 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-200.woff2')", {
style: "normal", style: "normal",
weight: "200", weight: "200",
}); });
let jbmFont700 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-700.woff2')", { const jbmFont700 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-700.woff2')", {
style: "normal", style: "normal",
weight: "700", weight: "700",
}); });
let faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", { const faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", {
style: "normal", style: "normal",
weight: "normal", weight: "normal",
}); });
let docFonts: any = document.fonts; // work around ts typing issue const docFonts: any = document.fonts; // work around ts typing issue
docFonts.add(jbmFontNormal); docFonts.add(jbmFontNormal);
docFonts.add(jbmFont200); docFonts.add(jbmFont200);
docFonts.add(jbmFont700); docFonts.add(jbmFont700);
@ -296,13 +281,13 @@ function getTodayStr(): string {
} }
function getYesterdayStr(): string { function getYesterdayStr(): string {
let d = new Date(); const d = new Date();
d.setDate(d.getDate() - 1); d.setDate(d.getDate() - 1);
return getDateStr(d); return getDateStr(d);
} }
function getDateStr(d: Date): string { function getDateStr(d: Date): string {
let yearStr = String(d.getFullYear()); const yearStr = String(d.getFullYear());
let monthStr = String(d.getMonth() + 1); let monthStr = String(d.getMonth() + 1);
if (monthStr.length == 1) { if (monthStr.length == 1) {
monthStr = "0" + monthStr; monthStr = "0" + monthStr;
@ -311,31 +296,30 @@ function getDateStr(d: Date): string {
if (dayStr.length == 1) { if (dayStr.length == 1) {
dayStr = "0" + dayStr; dayStr = "0" + dayStr;
} }
let dowStr = DOW_STRS[d.getDay()]; const dowStr = DOW_STRS[d.getDay()];
return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr; return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr;
} }
function getRemoteConnVal(r: RemoteType): number { function getRemoteConnVal(r: RemoteType): number {
if (r.status == "connected") { switch (r.status) {
case "connected":
return 1; return 1;
} case "connecting":
if (r.status == "connecting") {
return 2; return 2;
} case "disconnected":
if (r.status == "disconnected") {
return 3; return 3;
} case "error":
if (r.status == "error") {
return 4; return 4;
} default:
return 5; return 5;
}
} }
function sortAndFilterRemotes(origRemotes: RemoteType[]): RemoteType[] { function sortAndFilterRemotes(origRemotes: RemoteType[]): RemoteType[] {
let remotes = origRemotes.filter((r) => !r.archived); const remotes = origRemotes.filter((r) => !r.archived);
remotes.sort((a, b) => { remotes.sort((a, b) => {
let connValA = getRemoteConnVal(a); const connValA = getRemoteConnVal(a);
let connValB = getRemoteConnVal(b); const connValB = getRemoteConnVal(b);
if (connValA != connValB) { if (connValA != connValB) {
return connValA - connValB; return connValA - connValB;
} }
@ -372,7 +356,7 @@ function openLink(url: string): void {
window.open(url, "_blank"); window.open(url, "_blank");
} }
function getColorRGB(colorInput) { function getColorRGB(colorInput: string) {
const tempElement = document.createElement("div"); const tempElement = document.createElement("div");
tempElement.style.color = colorInput; tempElement.style.color = colorInput;
document.body.appendChild(tempElement); document.body.appendChild(tempElement);
@ -399,7 +383,7 @@ function getRemoteName(remote: RemoteType): string {
if (remote == null) { if (remote == null) {
return ""; return "";
} }
let { remotealias, remotecanonicalname } = remote; const { remotealias, remotecanonicalname } = remote;
return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname; return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname;
} }

View File

@ -515,7 +515,7 @@ func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
if err != nil { if err != nil {
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
sstore.MainBus.SendScreenUpdate(ids.ScreenId, update) sstore.MainBus.SendScreenUpdate(ids.ScreenId, update)
return nil, nil return nil, nil
} }
@ -623,7 +623,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
if err != nil { if err != nil {
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
// this update is sent asynchronously for timing issues. the cmd update comes async as well // this update is sent asynchronously for timing issues. the cmd update comes async as well
// so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus // so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus
// it ensures it happens after the command creation event. // it ensures it happens after the command creation event.
@ -729,9 +729,9 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.
if resolveErr == nil { if resolveErr == nil {
screen, sidebarErr := implementRunInSidebar(ctx, ids.ScreenId, historyContext.LineId) screen, sidebarErr := implementRunInSidebar(ctx, ids.ScreenId, historyContext.LineId)
if sidebarErr == nil { if sidebarErr == nil {
modelUpdate.UpdateScreen(screen) sstore.AddScreenUpdate(modelUpdate, screen)
} else { } else {
modelUpdate.AddInfoError(fmt.Sprintf("cannot move command to sidebar: %v", sidebarErr)) sstore.AddInfoMsgUpdateError(modelUpdate, fmt.Sprintf("cannot move command to sidebar: %v", sidebarErr))
} }
} }
} }
@ -775,9 +775,8 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if err != nil { if err != nil {
return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Screens: []*sstore.ScreenType{screen}, sstore.AddUpdate(update, *screen)
}
return update, nil return update, nil
} }
} }
@ -798,7 +797,7 @@ func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if screenId == "" { if screenId == "" {
return nil, fmt.Errorf("/screen:delete no active screen or screen arg passed") return nil, fmt.Errorf("/screen:delete no active screen or screen arg passed")
} }
update, err := sstore.DeleteScreen(ctx, screenId, false) update, err := sstore.DeleteScreen(ctx, screenId, false, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -853,13 +852,14 @@ func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
} }
// Prepare the update packet to send back to the client // Prepare the update packet to send back to the client
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Screens: screens, for _, screen := range screens {
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, *screen)
}
sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: "screen indices updated successfully", InfoMsg: "screen indices updated successfully",
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
return update, nil return update, nil
} }
@ -968,13 +968,13 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
if !setNonAnchor { if !setNonAnchor {
return nil, nil return nil, nil
} }
update := &sstore.ModelUpdate{
Screens: []*sstore.ScreenType{screen}, update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, *screen)
sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
return update, nil return update, nil
} }
@ -1043,7 +1043,9 @@ func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *screen)
return update, nil
} }
func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1055,7 +1057,9 @@ func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *screen)
return update, nil
} }
func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1083,7 +1087,9 @@ func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
if err != nil { if err != nil {
return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err)
} }
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *screen)
return update, nil
} }
func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1105,7 +1111,25 @@ func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if err != nil { if err != nil {
return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err)
} }
return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *screen)
return update, nil
}
func createRemoteViewRemoteIdUpdate(remoteId string) sstore.UpdatePacket {
update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, sstore.RemoteViewType{
PtyRemoteId: remoteId,
})
return update
}
func createRemoteViewRemoteEditUpdate(redit *sstore.RemoteEditType) sstore.UpdatePacket {
update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, sstore.RemoteViewType{
RemoteEdit: redit,
})
return update
} }
func prettyPrintByteSize(size int64) string { func prettyPrintByteSize(size int64) string {
@ -1622,7 +1646,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() { if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() {
writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos) writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos)
err = destRemoteId.MShell.TryAutoConnect() err = destRemoteId.MShell.TryAutoConnect()
@ -1662,11 +1686,7 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
} }
mshell := ids.Remote.MShell mshell := ids.Remote.MShell
go mshell.RunInstall() go mshell.RunInstall()
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: ids.Remote.RemotePtr.RemoteId,
},
}, nil
} }
func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1676,11 +1696,7 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke
} }
mshell := ids.Remote.MShell mshell := ids.Remote.MShell
go mshell.CancelInstall() go mshell.CancelInstall()
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: ids.Remote.RemotePtr.RemoteId,
},
}, nil
} }
func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1689,11 +1705,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
return nil, err return nil, err
} }
go ids.Remote.MShell.Launch(true) go ids.Remote.MShell.Launch(true)
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: ids.Remote.RemotePtr.RemoteId,
},
}, nil
} }
func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1703,11 +1715,7 @@ func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy
} }
force := resolveBool(pk.Kwargs["force"], false) force := resolveBool(pk.Kwargs["force"], false)
go ids.Remote.MShell.Disconnect(force) go ids.Remote.MShell.Disconnect(force)
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: ids.Remote.RemotePtr.RemoteId,
},
}, nil
} }
func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket {
@ -1717,12 +1725,7 @@ func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket {
if err != nil { if err != nil {
redit.ErrorStr = err.Error() redit.ErrorStr = err.Error()
} }
update := &sstore.ModelUpdate{ return createRemoteViewRemoteEditUpdate(redit)
RemoteView: &sstore.RemoteViewType{
RemoteEdit: redit,
},
}
return update
} }
func makeRemoteEditErrorReturn_new(visual bool, err error) (sstore.UpdatePacket, error) { func makeRemoteEditErrorReturn_new(visual bool, err error) (sstore.UpdatePacket, error) {
@ -1744,12 +1747,7 @@ func makeRemoteEditUpdate_edit(ids resolvedIds, err error) sstore.UpdatePacket {
if err != nil { if err != nil {
redit.ErrorStr = err.Error() redit.ErrorStr = err.Error()
} }
update := &sstore.ModelUpdate{ return createRemoteViewRemoteEditUpdate(redit)
RemoteView: &sstore.RemoteViewType{
RemoteEdit: redit,
},
}
return update
} }
func makeRemoteEditErrorReturn_edit(ids resolvedIds, visual bool, err error) (sstore.UpdatePacket, error) { func makeRemoteEditErrorReturn_edit(ids resolvedIds, visual bool, err error) (sstore.UpdatePacket, error) {
@ -1961,11 +1959,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err) return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)
} }
// SUCCESS // SUCCESS
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(r.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: r.RemoteId,
},
}, nil
} }
func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -1990,18 +1984,13 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new error updating remote: %v", err)) return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new error updating remote: %v", err))
} }
if visualEdit { if visualEdit {
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(ids.Remote.RemoteCopy.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: ids.Remote.RemoteCopy.RemoteId,
},
}, nil
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName), InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
return update, nil return update, nil
} }
@ -2011,11 +2000,7 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
return nil, err return nil, err
} }
state := ids.Remote.RState state := ids.Remote.RState
return &sstore.ModelUpdate{ return createRemoteViewRemoteIdUpdate(state.RemoteId), nil
RemoteView: &sstore.RemoteViewType{
PtyRemoteId: state.RemoteId,
},
}, nil
} }
func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -2030,11 +2015,11 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
} }
buf.WriteString(fmt.Sprintf("%-12s %-5s %8s %s\n", rstate.Status, rstate.RemoteType, rstate.RemoteId[0:8], name)) buf.WriteString(fmt.Sprintf("%-12s %-5s %8s %s\n", rstate.Status, rstate.RemoteType, rstate.RemoteId[0:8], name))
} }
return &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
RemoteView: &sstore.RemoteViewType{ sstore.AddUpdate(update, sstore.RemoteViewType{
RemoteShowAll: true, RemoteShowAll: true,
}, })
}, nil return update, nil
} }
func resolveSshConfigPatterns(configFiles []string) ([]string, error) { func resolveSshConfigPatterns(configFiles []string) ([]string, error) {
@ -2330,16 +2315,17 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
visualEdit := resolveBool(pk.Kwargs["visual"], false) visualEdit := resolveBool(pk.Kwargs["visual"], false)
if visualEdit { if visualEdit {
update := &sstore.ModelUpdate{} update := &sstore.ModelUpdate{}
update.AlertMessage = &sstore.AlertMessageType{ sstore.AddUpdate(update, sstore.AlertMessageType{
Title: "SSH Config Import", Title: "SSH Config Import",
Message: outMsg, Message: outMsg,
Markdown: true, Markdown: true,
} })
return update, nil return update, nil
} else { } else {
update := &sstore.ModelUpdate{} update := &sstore.ModelUpdate{}
update.Info = &sstore.InfoMsgType{} sstore.AddUpdate(update, sstore.InfoMsgType{
update.Info.InfoMsg = outMsg InfoMsg: outMsg,
})
return update, nil return update, nil
} }
} }
@ -2363,12 +2349,12 @@ func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr) outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr)
buf.WriteString(outStr) buf.WriteString(outStr)
} }
return &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("all screens for session"), InfoTitle: fmt.Sprintf("all screens for session"),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}, nil return update, nil
} }
func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -2402,8 +2388,8 @@ func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
update.Sessions = []*sstore.SessionType{sessionUpdate} sstore.AddUpdate(update, sessionUpdate)
return update, nil return update, nil
} }
@ -2427,7 +2413,7 @@ func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot get updated screen: %w", err) return nil, fmt.Errorf("cannot get updated screen: %w", err)
} }
update.Screens = []*sstore.ScreenType{screen} sstore.AddUpdate(update, *screen)
return update, nil return update, nil
} }
@ -2476,11 +2462,10 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re
} }
buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr)) buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr))
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -2834,7 +2819,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
if resolveBool(pk.Kwargs["cmdinfo"], false) { if resolveBool(pk.Kwargs["cmdinfo"], false) {
if promptStr == "" { if promptStr == "" {
// this is requesting an update without wanting an openai query // this is requesting an update without wanting an openai query
update, err := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId) update, err := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting update for CmdInfoChat %v", err) return nil, fmt.Errorf("error getting update for CmdInfoChat %v", err)
} }
@ -2879,7 +2864,9 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
// ignore error again (nothing to do) // ignore error again (nothing to do)
log.Printf("openai error updating screen selected line: %v\n", err) log.Printf("openai error updating screen selected line: %v\n", err)
} }
update := &sstore.ModelUpdate{Line: line, Cmd: cmd, Screens: []*sstore.ScreenType{screen}} update := &sstore.ModelUpdate{}
sstore.AddLineUpdate(update, line, cmd)
sstore.AddUpdate(update, *screen)
return update, nil return update, nil
} }
@ -2912,10 +2899,9 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
if err != nil { if err != nil {
return nil, fmt.Errorf("/%s error: cannot resolve screen for update: %w", GetCmdStr(pk), err) return nil, fmt.Errorf("/%s error: cannot resolve screen for update: %w", GetCmdStr(pk), err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Screens: []*sstore.ScreenType{screen}, sstore.AddUpdate(update, *screen)
Interactive: pk.Interactive, sstore.AddInteractiveUpdate(update, pk.Interactive)
}
return update, nil return update, nil
} }
outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate)) outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate))
@ -2929,7 +2915,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
return update, nil return update, nil
} }
@ -3011,11 +2997,9 @@ func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids re
log.Printf("%s error updating screen selected line: %v\n", metaCmd, err) log.Printf("%s error updating screen selected line: %v\n", metaCmd, err)
} }
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Line: rtnLine, sstore.AddLineUpdate(update, rtnLine, cmd)
Cmd: cmd, sstore.AddUpdate(update, *screen)
Screens: []*sstore.ScreenType{screen},
}
sstore.IncrementNumRunningCmds_Update(update, cmd.ScreenId, 1) sstore.IncrementNumRunningCmds_Update(update, cmd.ScreenId, 1)
updateHistoryContext(ctx, rtnLine, cmd, cmd.FeState) updateHistoryContext(ctx, rtnLine, cmd, cmd.FeState)
return update, nil return update, nil
@ -3057,13 +3041,12 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd
if len(comps) == 0 { if len(comps) == 0 {
comps = []string{"(no completions)"} comps = []string{"(no completions)"}
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("%s completions", compType), InfoTitle: fmt.Sprintf("%s completions", compType),
InfoComps: comps, InfoComps: comps,
InfoCompsMore: hasMore, InfoCompsMore: hasMore,
}, })
}
return update return update
} }
@ -3199,9 +3182,8 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
if newSP == nil || cmdSP == *newSP { if newSP == nil || cmdSP == *newSP {
return nil, nil return nil, nil
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
CmdLine: &utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos}, sstore.AddCmdLineUpdate(update, utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos})
}
return update, nil return update, nil
} }
@ -3227,7 +3209,9 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
// ignore error again (nothing to do) // ignore error again (nothing to do)
log.Printf("/comment error updating screen selected line: %v\n", err) log.Printf("/comment error updating screen selected line: %v\n", err)
} }
update := &sstore.ModelUpdate{Line: rtnLine, Screens: []*sstore.ScreenType{screen}} update := &sstore.ModelUpdate{}
sstore.AddLineUpdate(update, rtnLine, nil)
sstore.AddUpdate(update, *screen)
return update, nil return update, nil
} }
@ -3410,9 +3394,9 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot archive session: %v", err) return nil, fmt.Errorf("cannot archive session: %v", err)
} }
update.Info = &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: "session archived", InfoMsg: "session archived",
} })
return update, nil return update, nil
} else { } else {
activate := resolveBool(pk.Kwargs["activate"], false) activate := resolveBool(pk.Kwargs["activate"], false)
@ -3420,9 +3404,9 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot un-archive session: %v", err) return nil, fmt.Errorf("cannot un-archive session: %v", err)
} }
update.Info = &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: "session un-archived", InfoMsg: "session un-archived",
} })
return update, nil return update, nil
} }
} }
@ -3463,12 +3447,12 @@ func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
buf.WriteString(fmt.Sprintf(" %-15s %d\n", "cmds", stats.NumCmds)) buf.WriteString(fmt.Sprintf(" %-15s %d\n", "cmds", stats.NumCmds))
buf.WriteString(fmt.Sprintf(" %-15s %0.2fM\n", "disksize", float64(stats.DiskStats.TotalSize)/1000000)) buf.WriteString(fmt.Sprintf(" %-15s %0.2fM\n", "disksize", float64(stats.DiskStats.TotalSize)/1000000))
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "disk-location", stats.DiskStats.Location)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "disk-location", stats.DiskStats.Location))
return &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: "session info", InfoTitle: "session info",
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}, nil return update, nil
} }
func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -3489,12 +3473,12 @@ func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
outStr := fmt.Sprintf("%-30s %s %s\n", session.Name+archivedStr, session.SessionId, sessionIdxStr) outStr := fmt.Sprintf("%-30s %s %s\n", session.Name+archivedStr, session.SessionId, sessionIdxStr)
buf.WriteString(outStr) buf.WriteString(outStr)
} }
return &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: "all sessions", InfoTitle: "all sessions",
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}, nil return update, nil
} }
func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -3514,21 +3498,17 @@ func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
return nil, fmt.Errorf("setting session name: %v", err) return nil, fmt.Errorf("setting session name: %v", err)
} }
varsUpdated = append(varsUpdated, "name") varsUpdated = append(varsUpdated, "name")
}
if pk.Kwargs["pos"] != "" {
} }
if len(varsUpdated) == 0 { if len(varsUpdated) == 0 {
return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false))
} }
bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId) bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId)
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Sessions: []*sstore.SessionType{bareSession}, sstore.AddUpdate(update, *bareSession)
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)), InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
return update, nil return update, nil
} }
@ -3549,14 +3529,12 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
if err != nil { if err != nil {
return nil, err return nil, err
} }
update := &sstore.ModelUpdate{}
update := &sstore.ModelUpdate{ sstore.AddUpdate(update, (sstore.ActiveSessionIdUpdate)(ritem.Id))
ActiveSessionId: ritem.Id, sstore.AddUpdate(update, sstore.InfoMsgType{
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
// Reset the status indicator for the new active screen // Reset the status indicator for the new active screen
session, err := sstore.GetSessionById(ctx, ritem.Id) session, err := sstore.GetSessionById(ctx, ritem.Id)
@ -3611,8 +3589,8 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
update.Sessions = sstore.MakeSessionsUpdateForRemote(ids.SessionId, remoteInst) sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst))
return update, nil return update, nil
} }
@ -3626,20 +3604,20 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore
if err != nil { if err != nil {
return nil, fmt.Errorf("clearing screen (archiving): %v", err) return nil, fmt.Errorf("clearing screen (archiving): %v", err)
} }
update.Info = &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("screen cleared (all lines archived)"), InfoMsg: fmt.Sprintf("screen cleared (all lines archived)"),
TimeoutMs: 2000, TimeoutMs: 2000,
} })
return update, nil return update, nil
} else { } else {
update, err := sstore.DeleteScreenLines(ctx, ids.ScreenId) update, err := sstore.DeleteScreenLines(ctx, ids.ScreenId)
if err != nil { if err != nil {
return nil, fmt.Errorf("clearing screen: %v", err) return nil, fmt.Errorf("clearing screen: %v", err)
} }
update.Info = &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("screen cleared"), InfoMsg: fmt.Sprintf("screen cleared"),
TimeoutMs: 2000, TimeoutMs: 2000,
} })
return update, nil return update, nil
} }
@ -3751,10 +3729,8 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
} }
hvdata.Lines = lines hvdata.Lines = lines
hvdata.Cmds = cmds hvdata.Cmds = cmds
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
HistoryViewData: hvdata, sstore.AddUpdate(update, &sstore.MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata})
MainView: sstore.MainViewHistory,
}
return update, nil return update, nil
} }
@ -3800,13 +3776,13 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{HistoryView: 1}, "history") sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{HistoryView: 1}, "history")
} }
update := &sstore.ModelUpdate{} update := &sstore.ModelUpdate{}
update.History = &sstore.HistoryInfoType{ sstore.AddUpdate(update, sstore.HistoryInfoType{
HistoryType: htype, HistoryType: htype,
SessionId: ids.SessionId, SessionId: ids.SessionId,
ScreenId: ids.ScreenId, ScreenId: ids.ScreenId,
Items: hresult.Items, Items: hresult.Items,
Show: show, Show: show,
} })
return update, nil return update, nil
} }
@ -3993,18 +3969,16 @@ func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
return nil, fmt.Errorf("error getting updated line/cmd: %w", err) return nil, fmt.Errorf("error getting updated line/cmd: %w", err)
} }
cmd.Restarted = true cmd.Restarted = true
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Line: line, sstore.AddLineUpdate(update, line, cmd)
Cmd: cmd, sstore.AddInteractiveUpdate(update, pk.Interactive)
Interactive: pk.Interactive,
}
screen, focusErr := focusScreenLine(ctx, ids.ScreenId, line.LineNum) screen, focusErr := focusScreenLine(ctx, ids.ScreenId, line.LineNum)
if focusErr != nil { if focusErr != nil {
// not a fatal error, so just log // not a fatal error, so just log
log.Printf("error focusing screen line: %v\n", focusErr) log.Printf("error focusing screen line: %v\n", focusErr)
} }
if screen != nil { if screen != nil {
update.Screens = []*sstore.ScreenType{screen} sstore.AddUpdate(update, *screen)
} }
return update, nil return update, nil
} }
@ -4083,13 +4057,12 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
if err != nil { if err != nil {
return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err) return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Line: updatedLine, sstore.AddLineUpdate(update, updatedLine, nil)
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)), InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
}
return update, nil return update, nil
} }
@ -4135,7 +4108,7 @@ func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
if err != nil { if err != nil {
return nil, err return nil, err
} }
update.Screens = []*sstore.ScreenType{screen} sstore.AddUpdate(update, *screen)
} }
return update, nil return update, nil
} }
@ -4151,10 +4124,12 @@ func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err) return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err)
} }
sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{BookmarksView: 1}, "bookmarks") sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{BookmarksView: 1}, "bookmarks")
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, &sstore.MainViewUpdate{
MainView: sstore.MainViewBookmarks, MainView: sstore.MainViewBookmarks,
Bookmarks: bms, BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms},
} })
return update, nil return update, nil
} }
@ -4188,12 +4163,11 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
if err != nil { if err != nil {
return nil, fmt.Errorf("error retrieving edited bookmark: %v", err) return nil, fmt.Errorf("error retrieving edited bookmark: %v", err)
} }
return &sstore.ModelUpdate{ bms := []*sstore.BookmarkType{bm}
Info: &sstore.InfoMsgType{ update := &sstore.ModelUpdate{}
InfoMsg: "bookmark edited", sstore.AddBookmarksUpdate(update, bms, nil)
}, sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark edited"))
Bookmarks: []*sstore.BookmarkType{bm}, return update, nil
}, nil
} }
func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -4212,13 +4186,11 @@ func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
if err != nil { if err != nil {
return nil, fmt.Errorf("error deleting bookmark: %v", err) return nil, fmt.Errorf("error deleting bookmark: %v", err)
} }
bm := &sstore.BookmarkType{BookmarkId: bookmarkId, Remove: true} update := &sstore.ModelUpdate{}
return &sstore.ModelUpdate{ bms := []*sstore.BookmarkType{{BookmarkId: bookmarkId, Remove: true}}
Info: &sstore.InfoMsgType{ sstore.AddBookmarksUpdate(update, bms, nil)
InfoMsg: "bookmark deleted", sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark deleted"))
}, return update, nil
Bookmarks: []*sstore.BookmarkType{bm},
}, nil
} }
func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -4267,11 +4239,11 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
newBmId = newBm.BookmarkId newBmId = newBm.BookmarkId
} }
bms, err := sstore.GetBookmarks(ctx, "") bms, err := sstore.GetBookmarks(ctx, "")
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, &sstore.MainViewUpdate{
MainView: sstore.MainViewBookmarks, MainView: sstore.MainViewBookmarks,
Bookmarks: bms, BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms, SelectedBookmark: newBmId},
SelectedBookmark: newBmId, })
}
return update, nil return update, nil
} }
@ -4317,7 +4289,9 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
// no line (which is strange given we checked for it above). just return a nop. // no line (which is strange given we checked for it above). just return a nop.
return nil, nil return nil, nil
} }
return &sstore.ModelUpdate{Line: lineObj}, nil update := &sstore.ModelUpdate{}
sstore.AddLineUpdate(update, lineObj, nil)
return update, nil
} }
func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -4352,7 +4326,9 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
// no line (which is strange given we checked for it above). just return a nop. // no line (which is strange given we checked for it above). just return a nop.
return nil, nil return nil, nil
} }
return &sstore.ModelUpdate{Line: lineObj}, nil update := &sstore.ModelUpdate{}
sstore.AddLineUpdate(update, lineObj, nil)
return update, nil
} }
func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -4380,18 +4356,16 @@ func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
} }
update := &sstore.ModelUpdate{} update := &sstore.ModelUpdate{}
for _, lineId := range lineIds { for _, lineId := range lineIds {
lineObj := &sstore.LineType{ line := &sstore.LineType{ScreenId: ids.ScreenId, LineId: lineId, Remove: true}
ScreenId: ids.ScreenId, sstore.AddLineUpdate(update, line, nil)
LineId: lineId,
Remove: true,
}
update.Lines = append(update.Lines, lineObj)
} }
screen, err := sstore.FixupScreenSelectedLine(ctx, ids.ScreenId) screen, err := sstore.FixupScreenSelectedLine(ctx, ids.ScreenId)
if err != nil { if err != nil {
return nil, fmt.Errorf("/line:delete error fixing up screen: %v", err) return nil, fmt.Errorf("/line:delete error fixing up screen: %v", err)
} }
update.Screens = []*sstore.ScreenType{screen} if screen != nil {
sstore.AddUpdate(update, *screen)
}
return update, nil return update, nil
} }
@ -4474,12 +4448,11 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
stateStr = stateStr[0:77] + "..." stateStr = stateStr[0:77] + "..."
} }
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "state", stateStr)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "state", stateStr))
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("line %d info", line.LineNum), InfoTitle: fmt.Sprintf("line %d info", line.LineNum),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -4569,12 +4542,11 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
modeStr = modeStr[len(modeStr)-9:] modeStr = modeStr[len(modeStr)-9:]
} }
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", modeStr)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", modeStr))
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path), InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -4631,12 +4603,11 @@ func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
buf.Write(dataPk.Data) buf.Write(dataPk.Data)
} }
buf.WriteString(fmt.Sprintf("\n\ntotal packets: %d\n", numPackets)) buf.WriteString(fmt.Sprintf("\n\ntotal packets: %d\n", numPackets))
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("view file %q", streamPk.Path), InfoTitle: fmt.Sprintf("view file %q", streamPk.Path),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -4679,7 +4650,7 @@ func CodeEditCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
return update, nil return update, nil
} }
@ -4710,7 +4681,7 @@ func CSVViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
return update, nil return update, nil
} }
@ -4741,7 +4712,7 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
return update, nil return update, nil
} }
@ -4772,7 +4743,7 @@ func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
// TODO tricky error since the command was a success, but we can't show the output // TODO tricky error since the command was a success, but we can't show the output
return nil, err return nil, err
} }
update.Interactive = pk.Interactive sstore.AddInteractiveUpdate(update, pk.Interactive)
return update, nil return update, nil
} }
@ -4836,11 +4807,10 @@ func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
if donePk.Error != "" { if donePk.Error != "" {
return nil, fmt.Errorf("/edit:test %s", donePk.Error) return nil, fmt.Errorf("/edit:test %s", donePk.Error)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("edit test, wrote %q", writePk.Path), InfoTitle: fmt.Sprintf("edit test, wrote %q", writePk.Path),
}, })
}
return update, nil return update, nil
} }
@ -4903,11 +4873,8 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot send signal: %v", err) return nil, fmt.Errorf("cannot send signal: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgUpdate("sent line %s signal %s", lineArg, sigArg))
InfoMsg: fmt.Sprintf("sent line %s signal %s", lineArg, sigArg),
},
}
return update, nil return update, nil
} }
@ -4941,11 +4908,8 @@ func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
pcloud.ResetUpdateWriterNumFailures() pcloud.ResetUpdateWriterNumFailures()
sstore.NotifyUpdateWriter() sstore.NotifyUpdateWriter()
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgUpdate("notified update writer"))
InfoMsg: fmt.Sprintf("notified update writer"),
},
}
return update, nil return update, nil
} }
@ -4971,9 +4935,8 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
ClientData: clientData, sstore.AddUpdate(update, *clientData)
}
return update, nil return update, nil
} }
@ -5021,9 +4984,8 @@ func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketT
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
ClientData: clientData, sstore.AddUpdate(update, *clientData)
}
return update, nil return update, nil
} }
@ -5077,9 +5039,8 @@ func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
ClientData: clientData, sstore.AddUpdate(update, *clientData)
}
return update, nil return update, nil
} }
@ -5227,13 +5188,12 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, *clientData)
sstore.AddUpdate(update, sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)), InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)),
TimeoutMs: 2000, TimeoutMs: 2000,
}, })
ClientData: clientData,
}
return update, nil return update, nil
} }
@ -5259,12 +5219,12 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "client-version", clientVersion)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "client-version", clientVersion))
buf.WriteString(fmt.Sprintf(" %-15s %s %s\n", "server-version", scbase.WaveVersion, scbase.BuildTime)) buf.WriteString(fmt.Sprintf(" %-15s %s %s\n", "server-version", scbase.WaveVersion, scbase.BuildTime))
buf.WriteString(fmt.Sprintf(" %-15s %s (%s)\n", "arch", scbase.ClientArch(), scbase.UnameKernelRelease())) buf.WriteString(fmt.Sprintf(" %-15s %s (%s)\n", "arch", scbase.ClientArch(), scbase.UnameKernelRelease()))
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("client info"), InfoTitle: fmt.Sprintf("client info"),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -5318,7 +5278,7 @@ func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := sstore.InfoMsgUpdate("telemetry is now on") update := sstore.InfoMsgUpdate("telemetry is now on")
update.ClientData = clientData sstore.AddUpdate(update, *clientData)
return update, nil return update, nil
} }
@ -5339,7 +5299,7 @@ func TelemetryOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := sstore.InfoMsgUpdate("telemetry is now off") update := sstore.InfoMsgUpdate("telemetry is now off")
update.ClientData = clientData sstore.AddUpdate(update, *clientData)
return update, nil return update, nil
} }
@ -5350,12 +5310,11 @@ func TelemetryShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
} }
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on"))) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on")))
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
Info: &sstore.InfoMsgType{ sstore.AddUpdate(update, sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("telemetry info"), InfoTitle: fmt.Sprintf("telemetry info"),
InfoLines: splitLinesForInfo(buf.String()), InfoLines: splitLinesForInfo(buf.String()),
}, })
}
return update, nil return update, nil
} }
@ -5427,7 +5386,7 @@ func ReleaseCheckOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := sstore.InfoMsgUpdate("automatic release checking is now on") update := sstore.InfoMsgUpdate("automatic release checking is now on")
update.ClientData = clientData sstore.AddUpdate(update, *clientData)
return update, nil return update, nil
} }
@ -5448,7 +5407,7 @@ func ReleaseCheckOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp
return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err)
} }
update := sstore.InfoMsgUpdate("automatic release checking is now off") update := sstore.InfoMsgUpdate("automatic release checking is now off")
update.ClientData = clientData sstore.AddUpdate(update, *clientData)
return update, nil return update, nil
} }
@ -5471,7 +5430,7 @@ func ReleaseCheckCommand(ctx context.Context, pk *scpacket.FeCommandPacketType)
} }
update := sstore.InfoMsgUpdate(rsp) update := sstore.InfoMsgUpdate(rsp)
update.ClientData = clientData sstore.AddUpdate(update, *clientData)
return update, nil return update, nil
} }

View File

@ -66,9 +66,8 @@ func CheckNewRelease(ctx context.Context, force bool) (ReleaseCheckResult, error
return Failure, fmt.Errorf("error getting updated client data: %w", err) return Failure, fmt.Errorf("error getting updated client data: %w", err)
} }
update := &sstore.ModelUpdate{ update := &sstore.ModelUpdate{}
ClientData: clientData, sstore.AddUpdate(update, *clientData)
}
sstore.MainBus.SendUpdate(update) sstore.MainBus.SendUpdate(update)
return Success, nil return Success, nil

View File

@ -678,18 +678,19 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
func (msh *MShellProc) NotifyRemoteUpdate() { func (msh *MShellProc) NotifyRemoteUpdate() {
rstate := msh.GetRemoteRuntimeState() rstate := msh.GetRemoteRuntimeState()
update := &sstore.ModelUpdate{Remotes: []RemoteRuntimeState{rstate}} update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, rstate)
sstore.MainBus.SendUpdate(update) sstore.MainBus.SendUpdate(update)
} }
func GetAllRemoteRuntimeState() []RemoteRuntimeState { func GetAllRemoteRuntimeState() []*RemoteRuntimeState {
GlobalStore.Lock.Lock() GlobalStore.Lock.Lock()
defer GlobalStore.Lock.Unlock() defer GlobalStore.Lock.Unlock()
var rtn []RemoteRuntimeState var rtn []*RemoteRuntimeState
for _, proc := range GlobalStore.Map { for _, proc := range GlobalStore.Map {
state := proc.GetRemoteRuntimeState() state := proc.GetRemoteRuntimeState()
rtn = append(rtn, state) rtn = append(rtn, &state)
} }
return rtn return rtn
} }
@ -1997,7 +1998,8 @@ func (msh *MShellProc) notifyHangups_nolock() {
if err != nil { if err != nil {
continue continue
} }
update := &sstore.ModelUpdate{Cmd: cmd} update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *cmd)
sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update) sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update)
go pushNumRunningCmdsUpdate(&ck, -1) go pushNumRunningCmdsUpdate(&ck, -1)
} }
@ -2027,7 +2029,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
// fall-through (nothing to do) // fall-through (nothing to do)
} }
if screen != nil { if screen != nil {
update.Screens = []*sstore.ScreenType{screen} sstore.AddUpdate(update, *screen)
} }
rct := msh.GetRunningCmd(donePk.CK) rct := msh.GetRunningCmd(donePk.CK)
var statePtr *sstore.ShellStatePtr var statePtr *sstore.ShellStatePtr
@ -2039,7 +2041,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
// fall-through (nothing to do) // fall-through (nothing to do)
} }
if remoteInst != nil { if remoteInst != nil {
update.Sessions = sstore.MakeSessionsUpdateForRemote(rct.SessionId, remoteInst) sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst))
} }
statePtr = &sstore.ShellStatePtr{BaseHash: donePk.FinalState.GetHashVal(false)} statePtr = &sstore.ShellStatePtr{BaseHash: donePk.FinalState.GetHashVal(false)}
} else if donePk.FinalStateDiff != nil && rct != nil { } else if donePk.FinalStateDiff != nil && rct != nil {
@ -2059,7 +2061,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
// fall-through (nothing to do) // fall-through (nothing to do)
} }
if remoteInst != nil { if remoteInst != nil {
update.Sessions = sstore.MakeSessionsUpdateForRemote(rct.SessionId, remoteInst) sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst))
} }
diffHashArr := append(([]string)(nil), donePk.FinalStateDiff.DiffHashArr...) diffHashArr := append(([]string)(nil), donePk.FinalStateDiff.DiffHashArr...)
diffHashArr = append(diffHashArr, donePk.FinalStateDiff.GetHashVal(false)) diffHashArr = append(diffHashArr, donePk.FinalStateDiff.GetHashVal(false))
@ -2102,9 +2104,10 @@ func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType)
log.Printf("error getting cmd(2) in handleCmdFinalPacket (not found)\n") log.Printf("error getting cmd(2) in handleCmdFinalPacket (not found)\n")
return return
} }
update := &sstore.ModelUpdate{Cmd: rtnCmd} update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *rtnCmd)
if screen != nil { if screen != nil {
update.Screens = []*sstore.ScreenType{screen} sstore.AddUpdate(update, *screen)
} }
go pushNumRunningCmdsUpdate(&finalPk.CK, -1) go pushNumRunningCmdsUpdate(&finalPk.CK, -1)
sstore.MainBus.SendUpdate(update) sstore.MainBus.SendUpdate(update)
@ -2172,7 +2175,9 @@ func (msh *MShellProc) makeHandleCmdFinalPacketClosure(finalPk *packet.CmdFinalP
func sendScreenUpdates(screens []*sstore.ScreenType) { func sendScreenUpdates(screens []*sstore.ScreenType) {
for _, screen := range screens { for _, screen := range screens {
sstore.MainBus.SendUpdate(&sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}) update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, *screen)
sstore.MainBus.SendUpdate(update)
} }
} }

View File

@ -388,11 +388,12 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) {
"%s\n\n"+ "%s\n\n"+
"**Offending Keys** \n"+ "**Offending Keys** \n"+
"%s", key.Type(), correctKeyFingerprint, strings.Join(bulletListKnownHosts, " \n"), strings.Join(offendingKeysFmt, " \n")) "%s", key.Type(), correctKeyFingerprint, strings.Join(bulletListKnownHosts, " \n"), strings.Join(offendingKeysFmt, " \n"))
update := &sstore.ModelUpdate{AlertMessage: &sstore.AlertMessageType{ update := &sstore.ModelUpdate{}
sstore.AddUpdate(update, sstore.AlertMessageType{
Markdown: true, Markdown: true,
Title: "Known Hosts Key Changed", Title: "Known Hosts Key Changed",
Message: alertText, Message: alertText,
}} })
sstore.MainBus.SendUpdate(update) sstore.MainBus.SendUpdate(update)
return fmt.Errorf("remote host identification has changed") return fmt.Errorf("remote host identification has changed")
} }

View File

@ -162,16 +162,17 @@ func (ws *WSState) ReplaceShell(shell *wsshell.WSShell) {
func (ws *WSState) handleConnection() error { func (ws *WSState) handleConnection() error {
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn() defer cancelFn()
update, err := sstore.GetAllSessions(ctx) connectUpdate, err := sstore.GetConnectUpdate(ctx)
if err != nil { if err != nil {
return fmt.Errorf("getting sessions: %w", err) return fmt.Errorf("getting sessions: %w", err)
} }
remotes := remote.GetAllRemoteRuntimeState() remotes := remote.GetAllRemoteRuntimeState()
update.Remotes = remotes connectUpdate.Remotes = remotes
// restore status indicators // restore status indicators
update.ScreenStatusIndicators, update.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState() connectUpdate.ScreenStatusIndicators, connectUpdate.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState()
update.Connect = true mu := &sstore.ModelUpdate{}
err = ws.Shell.WriteJson(update) sstore.AddUpdate(mu, *connectUpdate)
err = ws.Shell.WriteJson(mu)
if err != nil { if err != nil {
return err return err
} }

View File

@ -453,20 +453,32 @@ func GetBareSessionById(ctx context.Context, sessionId string) (*SessionType, er
return &rtn, nil return &rtn, nil
} }
func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { const getAllSessionsQuery = `SELECT * FROM session ORDER BY archived, sessionidx, archivedts`
return WithTxRtn(ctx, func(tx *TxWrap) (*ModelUpdate, error) {
update := &ModelUpdate{} // Gets all sessions, including archived
query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts` func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
tx.Select(&update.Sessions, query) return WithTxRtn(ctx, func(tx *TxWrap) ([]*SessionType, error) {
rtn := []*SessionType{}
tx.Select(&rtn, getAllSessionsQuery)
return rtn, nil
})
}
// Get all sessions and screens, including remotes
func GetConnectUpdate(ctx context.Context) (*ConnectUpdate, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*ConnectUpdate, error) {
update := &ConnectUpdate{}
sessions := []*SessionType{}
tx.Select(&sessions, getAllSessionsQuery)
sessionMap := make(map[string]*SessionType) sessionMap := make(map[string]*SessionType)
for _, session := range update.Sessions { for _, session := range sessions {
sessionMap[session.SessionId] = session sessionMap[session.SessionId] = session
session.Full = true update.Sessions = append(update.Sessions, session)
} }
query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` query := `SELECT * FROM screen ORDER BY archived, screenidx, archivedts`
update.Screens = dbutil.SelectMapsGen[*ScreenType](tx, query) screens := dbutil.SelectMapsGen[*ScreenType](tx, query)
for _, screen := range update.Screens { for _, screen := range screens {
screen.Full = true update.Screens = append(update.Screens, screen)
} }
query = `SELECT * FROM remote_instance` query = `SELECT * FROM remote_instance`
riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query) riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query)
@ -502,19 +514,15 @@ func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, er
return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) { return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) {
query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts` query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts`
rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId) rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId)
for _, screen := range rtn {
screen.Full = true
}
return rtn, nil return rtn, nil
}) })
} }
func GetSessionById(ctx context.Context, id string) (*SessionType, error) { func GetSessionById(ctx context.Context, id string) (*SessionType, error) {
allSessionsUpdate, err := GetAllSessions(ctx) allSessions, err := GetAllSessions(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
allSessions := allSessionsUpdate.Sessions
for _, session := range allSessions { for _, session := range allSessions {
if session.SessionId == id { if session.SessionId == id {
return session, nil return session, nil
@ -569,7 +577,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
if err != nil { if err != nil {
return err return err
} }
newScreen = screenUpdate.Screens[0] screenUpdateItems := GetUpdateItems[ScreenType](screenUpdate)
if len(screenUpdateItems) < 1 {
return fmt.Errorf("no screen update items")
}
newScreen = screenUpdateItems[0]
if activate { if activate {
query = `UPDATE client SET activesessionid = ?` query = `UPDATE client SET activesessionid = ?`
tx.Exec(query, newSessionId) tx.Exec(query, newSessionId)
@ -583,12 +595,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
if err != nil { if err != nil {
return nil, err return nil, err
} }
update := &ModelUpdate{ update := &ModelUpdate{}
Sessions: []*SessionType{session}, AddUpdate(update, *session)
Screens: []*ScreenType{newScreen}, AddUpdate(update, *newScreen)
}
if activate { if activate {
update.ActiveSessionId = newSessionId AddUpdate(update, ActiveSessionIdUpdate(newSessionId))
} }
return update, nil return update, nil
} }
@ -742,14 +753,15 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string,
if err != nil { if err != nil {
return nil, err return nil, err
} }
update := &ModelUpdate{Screens: []*ScreenType{newScreen}} update := &ModelUpdate{}
AddUpdate(update, *newScreen)
if activate { if activate {
bareSession, err := GetBareSessionById(ctx, sessionId) bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil { if err != nil {
return nil, txErr return nil, txErr
} }
update.Sessions = []*SessionType{bareSession} AddUpdate(update, *bareSession)
update.OpenAICmdInfoChat = ScreenMemGetCmdInfoChat(newScreenId).Messages UpdateWithCurrentOpenAICmdInfoChat(newScreenId, update)
} }
return update, nil return update, nil
} }
@ -758,7 +770,6 @@ func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) {
query := `SELECT * FROM screen WHERE screenid = ?` query := `SELECT * FROM screen WHERE screenid = ?`
screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId) screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId)
screen.Full = true
return screen, nil return screen, nil
}) })
} }
@ -866,17 +877,18 @@ func GetCmdByScreenId(ctx context.Context, screenId string, lineId string) (*Cmd
func UpdateWithClearOpenAICmdInfo(screenId string) (*ModelUpdate, error) { func UpdateWithClearOpenAICmdInfo(screenId string) (*ModelUpdate, error) {
ScreenMemClearCmdInfoChat(screenId) ScreenMemClearCmdInfoChat(screenId)
return UpdateWithCurrentOpenAICmdInfoChat(screenId) return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil)
} }
func UpdateWithAddNewOpenAICmdInfoPacket(ctx context.Context, screenId string, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { func UpdateWithAddNewOpenAICmdInfoPacket(ctx context.Context, screenId string, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) {
ScreenMemAddCmdInfoChatMessage(screenId, pk) ScreenMemAddCmdInfoChatMessage(screenId, pk)
return UpdateWithCurrentOpenAICmdInfoChat(screenId) return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil)
} }
func UpdateWithCurrentOpenAICmdInfoChat(screenId string) (*ModelUpdate, error) { func UpdateWithCurrentOpenAICmdInfoChat(screenId string, update *ModelUpdate) (*ModelUpdate, error) {
cmdInfoUpdate := ScreenMemGetCmdInfoChat(screenId).Messages ret := &ModelUpdate{}
return &ModelUpdate{OpenAICmdInfoChat: cmdInfoUpdate}, nil AddOpenAICmdInfoChatUpdate(ret, ScreenMemGetCmdInfoChat(screenId).Messages)
return ret, nil
} }
func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, messageID int, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, messageID int, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) {
@ -884,7 +896,7 @@ func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, m
if err != nil { if err != nil {
return nil, err return nil, err
} }
return UpdateWithCurrentOpenAICmdInfoChat(screenId) return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil)
} }
func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdPid int, remotePid int, termOpts *TermOpts) error { func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdPid int, remotePid int, termOpts *TermOpts) error {
@ -935,7 +947,8 @@ func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, donePk *packet.C
return nil, fmt.Errorf("cmd data not found for ck[%s]", ck) return nil, fmt.Errorf("cmd data not found for ck[%s]", ck)
} }
update := &ModelUpdate{Cmd: rtnCmd} update := &ModelUpdate{}
AddUpdate(update, *rtnCmd)
// Update in-memory screen indicator status // Update in-memory screen indicator status
var indicator StatusIndicatorLevel var indicator StatusIndicatorLevel
@ -1101,11 +1114,13 @@ func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
update := &ModelUpdate{ActiveSessionId: sessionId, Sessions: []*SessionType{bareSession}} update := &ModelUpdate{}
AddUpdate(update, (ActiveSessionIdUpdate)(sessionId))
AddUpdate(update, *bareSession)
memState := GetScreenMemState(screenId) memState := GetScreenMemState(screenId)
if memState != nil { if memState != nil {
update.CmdLine = &memState.CmdInputText AddCmdLineUpdate(update, memState.CmdInputText)
update.OpenAICmdInfoChat = ScreenMemGetCmdInfoChat(screenId).Messages UpdateWithCurrentOpenAICmdInfoChat(screenId, update)
// Clear any previous status indicator for this screen // Clear any previous status indicator for this screen
err := ResetStatusIndicator_Update(update, screenId) err := ResetStatusIndicator_Update(update, screenId)
@ -1173,13 +1188,14 @@ func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (Upda
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot retrive archived screen: %w", err) return nil, fmt.Errorf("cannot retrive archived screen: %w", err)
} }
update := &ModelUpdate{Screens: []*ScreenType{newScreen}} update := &ModelUpdate{}
AddUpdate(update, *newScreen)
if isActive { if isActive {
bareSession, err := GetBareSessionById(ctx, sessionId) bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
update.Sessions = []*SessionType{bareSession} AddUpdate(update, *bareSession)
} }
return update, nil return update, nil
} }
@ -1199,7 +1215,7 @@ func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) err
} }
// if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that) // if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that)
func DeleteScreen(ctx context.Context, screenId string, sessionDel bool) (*ModelUpdate, error) { func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *ModelUpdate) (*ModelUpdate, error) {
var sessionId string var sessionId string
var isActive bool var isActive bool
var screenTombstone *ScreenTombstoneType var screenTombstone *ScreenTombstoneType
@ -1259,14 +1275,17 @@ func DeleteScreen(ctx context.Context, screenId string, sessionDel bool) (*Model
if !sessionDel { if !sessionDel {
GoDeleteScreenDirs(screenId) GoDeleteScreenDirs(screenId)
} }
update := &ModelUpdate{ScreenTombstones: []*ScreenTombstoneType{screenTombstone}} if update == nil {
update.Screens = []*ScreenType{{SessionId: sessionId, ScreenId: screenId, Remove: true}} update = &ModelUpdate{}
}
AddUpdate(update, *screenTombstone)
AddUpdate(update, ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true})
if isActive { if isActive {
bareSession, err := GetBareSessionById(ctx, sessionId) bareSession, err := GetBareSessionById(ctx, sessionId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
update.Sessions = []*SessionType{bareSession} AddUpdate(update, *bareSession)
} }
return update, nil return update, nil
} }
@ -1516,7 +1535,9 @@ func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ModelUpdate{ScreenLines: screenLines}, nil ret := &ModelUpdate{}
AddUpdate(ret, *screenLines)
return ret, nil
} }
func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) {
@ -1558,7 +1579,10 @@ func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, erro
} }
screenLines.Lines = append(screenLines.Lines, line) screenLines.Lines = append(screenLines.Lines, line)
} }
return &ModelUpdate{Screens: []*ScreenType{screen}, ScreenLines: screenLines}, nil ret := &ModelUpdate{}
AddUpdate(ret, *screen)
AddUpdate(ret, *screenLines)
return ret, nil
} }
func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, error) { func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, error) {
@ -1621,16 +1645,10 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error)
query := `SELECT screenid FROM screen WHERE sessionid = ?` query := `SELECT screenid FROM screen WHERE sessionid = ?`
screenIds = tx.SelectStrings(query, sessionId) screenIds = tx.SelectStrings(query, sessionId)
for _, screenId := range screenIds { for _, screenId := range screenIds {
screenUpdate, err := DeleteScreen(tx.Context(), screenId, true) _, err := DeleteScreen(tx.Context(), screenId, true, update)
if err != nil { if err != nil {
return fmt.Errorf("error deleting screen[%s]: %v", screenId, err) return fmt.Errorf("error deleting screen[%s]: %v", screenId, err)
} }
if len(screenUpdate.Screens) > 0 {
update.Screens = append(update.Screens, screenUpdate.Screens...)
}
if len(screenUpdate.ScreenTombstones) > 0 {
update.ScreenTombstones = append(update.ScreenTombstones, screenUpdate.ScreenTombstones...)
}
} }
query = `DELETE FROM session WHERE sessionid = ?` query = `DELETE FROM session WHERE sessionid = ?`
tx.Exec(query, sessionId) tx.Exec(query, sessionId)
@ -1650,10 +1668,12 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error)
} }
GoDeleteScreenDirs(screenIds...) GoDeleteScreenDirs(screenIds...)
if newActiveSessionId != "" { if newActiveSessionId != "" {
update.ActiveSessionId = newActiveSessionId AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId))
}
AddUpdate(update, SessionType{SessionId: sessionId, Remove: true})
if sessionTombstone != nil {
AddUpdate(update, *sessionTombstone)
} }
update.Sessions = append(update.Sessions, &SessionType{SessionId: sessionId, Remove: true})
update.SessionTombstones = []*SessionTombstoneType{sessionTombstone}
return update, nil return update, nil
} }
@ -1705,10 +1725,10 @@ func ArchiveSession(ctx context.Context, sessionId string) (*ModelUpdate, error)
bareSession, _ := GetBareSessionById(ctx, sessionId) bareSession, _ := GetBareSessionById(ctx, sessionId)
update := &ModelUpdate{} update := &ModelUpdate{}
if bareSession != nil { if bareSession != nil {
update.Sessions = append(update.Sessions, bareSession) AddUpdate(update, *bareSession)
} }
if newActiveSessionId != "" { if newActiveSessionId != "" {
update.ActiveSessionId = newActiveSessionId AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId))
} }
return update, nil return update, nil
} }
@ -1740,11 +1760,12 @@ func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*Mo
} }
bareSession, _ := GetBareSessionById(ctx, sessionId) bareSession, _ := GetBareSessionById(ctx, sessionId)
update := &ModelUpdate{} update := &ModelUpdate{}
if bareSession != nil { if bareSession != nil {
update.Sessions = append(update.Sessions, bareSession) AddUpdate(update, *bareSession)
} }
if activate { if activate {
update.ActiveSessionId = sessionId AddUpdate(update, (ActiveSessionIdUpdate)(sessionId))
} }
return update, nil return update, nil
} }

View File

@ -336,6 +336,10 @@ func (cdata *ClientData) Clean() *ClientData {
return &rtn return &rtn
} }
func (ClientData) UpdateType() string {
return "clientdata"
}
type SessionType struct { type SessionType struct {
SessionId string `json:"sessionid"` SessionId string `json:"sessionid"`
Name string `json:"name"` Name string `json:"name"`
@ -349,7 +353,17 @@ type SessionType struct {
// only for updates // only for updates
Remove bool `json:"remove,omitempty"` Remove bool `json:"remove,omitempty"`
Full bool `json:"full,omitempty"` }
func (SessionType) UpdateType() string {
return "session"
}
func MakeSessionUpdateForRemote(sessionId string, ri *RemoteInstance) SessionType {
return SessionType{
SessionId: sessionId,
Remotes: []*RemoteInstance{ri},
}
} }
type SessionTombstoneType struct { type SessionTombstoneType struct {
@ -360,6 +374,10 @@ type SessionTombstoneType struct {
func (SessionTombstoneType) UseDBMap() {} func (SessionTombstoneType) UseDBMap() {}
func (SessionTombstoneType) UpdateType() string {
return "sessiontombstone"
}
type SessionStatsType struct { type SessionStatsType struct {
SessionId string `json:"sessionid"` SessionId string `json:"sessionid"`
NumScreens int `json:"numscreens"` NumScreens int `json:"numscreens"`
@ -429,6 +447,10 @@ type ScreenLinesType struct {
func (ScreenLinesType) UseDBMap() {} func (ScreenLinesType) UseDBMap() {}
func (ScreenLinesType) UpdateType() string {
return "screenlines"
}
type ScreenWebShareOpts struct { type ScreenWebShareOpts struct {
ShareName string `json:"sharename"` ShareName string `json:"sharename"`
ViewKey string `json:"viewkey"` ViewKey string `json:"viewkey"`
@ -476,9 +498,7 @@ type ScreenType struct {
ArchivedTs int64 `json:"archivedts,omitempty"` ArchivedTs int64 `json:"archivedts,omitempty"`
// only for updates // only for updates
Full bool `json:"full,omitempty"`
Remove bool `json:"remove,omitempty"` Remove bool `json:"remove,omitempty"`
StatusIndicator string `json:"statusindicator,omitempty"`
} }
func (s *ScreenType) ToMap() map[string]interface{} { func (s *ScreenType) ToMap() map[string]interface{} {
@ -526,6 +546,24 @@ func (s *ScreenType) FromMap(m map[string]interface{}) bool {
return true return true
} }
func (ScreenType) UpdateType() string {
return "screen"
}
func AddScreenUpdate(update *ModelUpdate, newScreen *ScreenType) {
if newScreen == nil {
return
}
screenUpdates := GetUpdateItems[ScreenType](update)
for _, screenUpdate := range screenUpdates {
if screenUpdate.ScreenId == newScreen.ScreenId {
screenUpdate = newScreen
return
}
}
AddUpdate(update, newScreen)
}
type ScreenTombstoneType struct { type ScreenTombstoneType struct {
ScreenId string `json:"screenid"` ScreenId string `json:"screenid"`
SessionId string `json:"sessionid"` SessionId string `json:"sessionid"`
@ -536,6 +574,10 @@ type ScreenTombstoneType struct {
func (ScreenTombstoneType) UseDBMap() {} func (ScreenTombstoneType) UseDBMap() {}
func (ScreenTombstoneType) UpdateType() string {
return "screentombstone"
}
const ( const (
LayoutFull = "full" LayoutFull = "full"
) )
@ -1015,6 +1057,10 @@ func (state RemoteRuntimeState) ExpandHomeDir(pathStr string) (string, error) {
return path.Join(homeDir, pathStr[2:]), nil return path.Join(homeDir, pathStr[2:]), nil
} }
func (RemoteRuntimeState) UpdateType() string {
return "remote"
}
type RemoteType struct { type RemoteType struct {
RemoteId string `json:"remoteid"` RemoteId string `json:"remoteid"`
RemoteType string `json:"remotetype"` RemoteType string `json:"remotetype"`
@ -1079,6 +1125,10 @@ type CmdType struct {
Restarted bool `json:"restarted,omitempty"` // not persisted to DB Restarted bool `json:"restarted,omitempty"` // not persisted to DB
} }
func (CmdType) UpdateType() string {
return "cmd"
}
func (r *RemoteType) ToMap() map[string]interface{} { func (r *RemoteType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{}) rtn := make(map[string]interface{})
rtn["remoteid"] = r.RemoteId rtn["remoteid"] = r.RemoteId
@ -1456,10 +1506,10 @@ func SetStatusIndicatorLevel_Update(ctx context.Context, update *ModelUpdate, sc
} }
} }
update.ScreenStatusIndicators = []*ScreenStatusIndicatorType{{ AddUpdate(update, ScreenStatusIndicatorType{
ScreenId: screenId, ScreenId: screenId,
Status: newStatus, Status: newStatus,
}} })
return nil return nil
} }
@ -1489,10 +1539,11 @@ func ResetStatusIndicator(screenId string) error {
func IncrementNumRunningCmds_Update(update *ModelUpdate, screenId string, delta int) { func IncrementNumRunningCmds_Update(update *ModelUpdate, screenId string, delta int) {
newNum := ScreenMemIncrementNumRunningCommands(screenId, delta) newNum := ScreenMemIncrementNumRunningCommands(screenId, delta)
log.Printf("IncrementNumRunningCmds_Update: screenId=%s, newNum=%d\n", screenId, newNum) log.Printf("IncrementNumRunningCmds_Update: screenId=%s, newNum=%d\n", screenId, newNum)
update.ScreenNumRunningCommands = []*ScreenNumRunningCommandsType{{ AddUpdate(update, ScreenNumRunningCommandsType{
ScreenId: screenId, ScreenId: screenId,
Num: newNum, Num: newNum,
}} })
} }
func IncrementNumRunningCmds(screenId string, delta int) { func IncrementNumRunningCmds(screenId string, delta int) {

View File

@ -5,14 +5,13 @@ package sstore
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"sync" "sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/utilfn"
"github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket"
) )
@ -23,6 +22,7 @@ const ModelUpdateStr = "model"
const UpdateChSize = 100 const UpdateChSize = 100
type UpdatePacket interface { type UpdatePacket interface {
// The key to use when marshalling to JSON and interpreting in the client
UpdateType() string UpdateType() string
Clean() Clean()
} }
@ -42,136 +42,61 @@ func (*PtyDataUpdate) UpdateType() string {
func (pdu *PtyDataUpdate) Clean() {} func (pdu *PtyDataUpdate) Clean() {}
type ModelUpdate struct { // A collection of independent model updates to be sent to the client. Will be evaluated in order on the client.
Sessions []*SessionType `json:"sessions,omitempty"` type ModelUpdate []*ModelUpdateItem
ActiveSessionId string `json:"activesessionid,omitempty"`
Screens []*ScreenType `json:"screens,omitempty"`
ScreenLines *ScreenLinesType `json:"screenlines,omitempty"`
Line *LineType `json:"line,omitempty"`
Lines []*LineType `json:"lines,omitempty"`
Cmd *CmdType `json:"cmd,omitempty"`
CmdLine *utilfn.StrWithPos `json:"cmdline,omitempty"`
Info *InfoMsgType `json:"info,omitempty"`
ClearInfo bool `json:"clearinfo,omitempty"`
Remotes []RemoteRuntimeState `json:"remotes,omitempty"`
History *HistoryInfoType `json:"history,omitempty"`
Interactive bool `json:"interactive"`
Connect bool `json:"connect,omitempty"`
MainView string `json:"mainview,omitempty"`
Bookmarks []*BookmarkType `json:"bookmarks,omitempty"`
SelectedBookmark string `json:"selectedbookmark,omitempty"`
HistoryViewData *HistoryViewData `json:"historyviewdata,omitempty"`
ClientData *ClientData `json:"clientdata,omitempty"`
RemoteView *RemoteViewType `json:"remoteview,omitempty"`
ScreenTombstones []*ScreenTombstoneType `json:"screentombstones,omitempty"`
SessionTombstones []*SessionTombstoneType `json:"sessiontombstones,omitempty"`
OpenAICmdInfoChat []*packet.OpenAICmdInfoChatMessage `json:"openaicmdinfochat,omitempty"`
AlertMessage *AlertMessageType `json:"alertmessage,omitempty"`
ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"`
ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"`
UserInputRequest *UserInputRequestType `json:"userinputrequest,omitempty"`
}
func (*ModelUpdate) UpdateType() string { func (*ModelUpdate) UpdateType() string {
return ModelUpdateStr return ModelUpdateStr
} }
func (mu *ModelUpdate) MarshalJSON() ([]byte, error) {
rtn := make([]map[string]any, 0)
for _, u := range *mu {
m := make(map[string]any)
m[(*u).UpdateType()] = u
rtn = append(rtn, m)
}
return json.Marshal(rtn)
}
// An interface for all model updates
type ModelUpdateItem interface {
// The key to use when marshalling to JSON and interpreting in the client
UpdateType() string
}
// Clean the ClientData in an update, if present
func (update *ModelUpdate) Clean() { func (update *ModelUpdate) Clean() {
if update == nil { if update == nil {
return return
} }
update.ClientData = update.ClientData.Clean() clientDataUpdates := GetUpdateItems[ClientData](update)
} if len(clientDataUpdates) > 0 {
lastUpdate := clientDataUpdates[len(clientDataUpdates)-1]
func (update *ModelUpdate) UpdateScreen(newScreen *ScreenType) { lastUpdate.Clean()
if newScreen == nil {
return
}
for idx, screen := range update.Screens {
if screen.ScreenId == newScreen.ScreenId {
update.Screens[idx] = newScreen
return
}
}
update.Screens = append(update.Screens, newScreen)
}
// only sets InfoError if InfoError is not already set
func (update *ModelUpdate) AddInfoError(errStr string) {
if update.Info == nil {
update.Info = &InfoMsgType{}
}
if update.Info.InfoError == "" {
update.Info.InfoError = errStr
} }
} }
type RemoteViewType struct { func (update *ModelUpdate) append(item *ModelUpdateItem) {
RemoteShowAll bool `json:"remoteshowall,omitempty"` *update = append(*update, item)
PtyRemoteId string `json:"ptyremoteid,omitempty"`
RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"`
} }
func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate { // Add a collection of model updates to the update
msg := fmt.Sprintf(infoMsgFmt, args...) func AddUpdate(update *ModelUpdate, item ...ModelUpdateItem) {
return &ModelUpdate{ for _, i := range item {
Info: &InfoMsgType{InfoMsg: msg}, update.append(&i)
} }
} }
type HistoryViewData struct { // Returns the items in the update that are of type I
Items []*HistoryItemType `json:"items"` func GetUpdateItems[I ModelUpdateItem](update *ModelUpdate) []*I {
Offset int `json:"offset"` ret := make([]*I, 0)
RawOffset int `json:"rawoffset"` for _, item := range *update {
NextRawOffset int `json:"nextrawoffset"` if i, ok := (*item).(I); ok {
HasMore bool `json:"hasmore"` ret = append(ret, &i)
Lines []*LineType `json:"lines"` }
Cmds []*CmdType `json:"cmds"` }
} return ret
type RemoteEditType struct {
RemoteEdit bool `json:"remoteedit"`
RemoteId string `json:"remoteid,omitempty"`
ErrorStr string `json:"errorstr,omitempty"`
InfoStr string `json:"infostr,omitempty"`
KeyStr string `json:"keystr,omitempty"`
HasPassword bool `json:"haspassword,omitempty"`
}
type AlertMessageType struct {
Title string `json:"title,omitempty"`
Message string `json:"message"`
Confirm bool `json:"confirm,omitempty"`
Markdown bool `json:"markdown,omitempty"`
}
type InfoMsgType struct {
InfoTitle string `json:"infotitle"`
InfoError string `json:"infoerror,omitempty"`
InfoMsg string `json:"infomsg,omitempty"`
InfoMsgHtml bool `json:"infomsghtml,omitempty"`
WebShareLink bool `json:"websharelink,omitempty"`
InfoComps []string `json:"infocomps,omitempty"`
InfoCompsMore bool `json:"infocompssmore,omitempty"`
InfoLines []string `json:"infolines,omitempty"`
TimeoutMs int64 `json:"timeoutms,omitempty"`
}
type HistoryInfoType struct {
HistoryType string `json:"historytype"`
SessionId string `json:"sessionid,omitempty"`
ScreenId string `json:"screenid,omitempty"`
Items []*HistoryItemType `json:"items"`
Show bool `json:"show"`
}
type UserInputRequestType struct {
RequestId string `json:"requestid"`
QueryText string `json:"querytext"`
ResponseType string `json:"responsetype"`
Title string `json:"title"`
Markdown bool `json:"markdown"`
TimeoutMs int `json:"timeoutms"`
} }
type UpdateChannel struct { type UpdateChannel struct {
@ -267,29 +192,6 @@ func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) {
} }
} }
func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType {
return []*SessionType{
{
SessionId: sessionId,
Remotes: []*RemoteInstance{ri},
},
}
}
type BookmarksViewType struct {
Bookmarks []*BookmarkType `json:"bookmarks"`
}
type ScreenStatusIndicatorType struct {
ScreenId string `json:"screenid"`
Status StatusIndicatorLevel `json:"status"`
}
type ScreenNumRunningCommandsType struct {
ScreenId string `json:"screenid"`
Num int `json:"num"`
}
func (bus *UpdateBus) registerUserInputChannel() (string, chan *scpacket.UserInputResponsePacketType) { func (bus *UpdateBus) registerUserInputChannel() (string, chan *scpacket.UserInputResponsePacketType) {
bus.Lock.Lock() bus.Lock.Lock()
defer bus.Lock.Unlock() defer bus.Lock.Unlock()
@ -323,7 +225,8 @@ func (bus *UpdateBus) GetUserInput(ctx context.Context, userInputRequest *UserIn
userInputRequest.RequestId = id userInputRequest.RequestId = id
deadline, _ := ctx.Deadline() deadline, _ := ctx.Deadline()
userInputRequest.TimeoutMs = int(time.Until(deadline).Milliseconds()) - 500 userInputRequest.TimeoutMs = int(time.Until(deadline).Milliseconds()) - 500
update := &ModelUpdate{UserInputRequest: userInputRequest} update := &ModelUpdate{}
AddUpdate(update, *userInputRequest)
bus.SendUpdate(update) bus.SendUpdate(update)
log.Printf("test: %+v", userInputRequest) log.Printf("test: %+v", userInputRequest)

View File

@ -0,0 +1,234 @@
package sstore
import (
"fmt"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/utilfn"
)
type ActiveSessionIdUpdate string
func (ActiveSessionIdUpdate) UpdateType() string {
return "activesessionid"
}
type LineUpdate struct {
Line LineType `json:"line"`
Cmd CmdType `json:"cmd,omitempty"`
}
func (LineUpdate) UpdateType() string {
return "line"
}
func AddLineUpdate(update *ModelUpdate, newLine *LineType, newCmd *CmdType) {
if newLine == nil {
return
}
newLineUpdate := LineUpdate{
Line: *newLine,
}
if newCmd != nil {
newLineUpdate.Cmd = *newCmd
}
AddUpdate(update, newLineUpdate)
}
type CmdLineUpdate utilfn.StrWithPos
func (CmdLineUpdate) UpdateType() string {
return "cmdline"
}
func AddCmdLineUpdate(update *ModelUpdate, cmdLine utilfn.StrWithPos) {
AddUpdate(update, CmdLineUpdate(cmdLine))
}
type InfoMsgType struct {
InfoTitle string `json:"infotitle"`
InfoError string `json:"infoerror,omitempty"`
InfoMsg string `json:"infomsg,omitempty"`
InfoMsgHtml bool `json:"infomsghtml,omitempty"`
WebShareLink bool `json:"websharelink,omitempty"`
InfoComps []string `json:"infocomps,omitempty"`
InfoCompsMore bool `json:"infocompssmore,omitempty"`
InfoLines []string `json:"infolines,omitempty"`
TimeoutMs int64 `json:"timeoutms,omitempty"`
}
func (InfoMsgType) UpdateType() string {
return "info"
}
func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate {
msg := fmt.Sprintf(infoMsgFmt, args...)
ret := &ModelUpdate{}
newInfoUpdate := InfoMsgType{InfoMsg: msg}
AddUpdate(ret, newInfoUpdate)
return ret
}
// only sets InfoError if InfoError is not already set
func AddInfoMsgUpdateError(update *ModelUpdate, errStr string) {
infoUpdates := GetUpdateItems[InfoMsgType](update)
if len(infoUpdates) > 0 {
lastUpdate := infoUpdates[len(infoUpdates)-1]
if lastUpdate.InfoError == "" {
lastUpdate.InfoError = errStr
return
}
} else {
AddUpdate(update, InfoMsgType{InfoError: errStr})
}
}
type ClearInfoUpdate bool
func (ClearInfoUpdate) UpdateType() string {
return "clearinfo"
}
type HistoryInfoType struct {
HistoryType string `json:"historytype"`
SessionId string `json:"sessionid,omitempty"`
ScreenId string `json:"screenid,omitempty"`
Items []*HistoryItemType `json:"items"`
Show bool `json:"show"`
}
func (HistoryInfoType) UpdateType() string {
return "history"
}
type InteractiveUpdate bool
func (InteractiveUpdate) UpdateType() string {
return "interactive"
}
func AddInteractiveUpdate(update *ModelUpdate, interactive bool) {
AddUpdate(update, InteractiveUpdate(interactive))
}
type ConnectUpdate struct {
Sessions []*SessionType `json:"sessions,omitempty"`
Screens []*ScreenType `json:"screens,omitempty"`
Remotes []*RemoteRuntimeState `json:"remotes,omitempty"`
ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"`
ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"`
ActiveSessionId string `json:"activesessionid,omitempty"`
}
func (ConnectUpdate) UpdateType() string {
return "connect"
}
type MainViewUpdate struct {
MainView string `json:"mainview"`
HistoryView *HistoryViewData `json:"historyview,omitempty"`
BookmarksView *BookmarksUpdate `json:"bookmarksview,omitempty"`
}
func (MainViewUpdate) UpdateType() string {
return "mainview"
}
type BookmarksUpdate struct {
Bookmarks []*BookmarkType `json:"bookmarks"`
SelectedBookmark string `json:"selectedbookmark,omitempty"`
}
func (BookmarksUpdate) UpdateType() string {
return "bookmarks"
}
func AddBookmarksUpdate(update *ModelUpdate, bookmarks []*BookmarkType, selectedBookmark *string) {
if selectedBookmark == nil {
AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks})
} else {
AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark})
}
}
type HistoryViewData struct {
Items []*HistoryItemType `json:"items"`
Offset int `json:"offset"`
RawOffset int `json:"rawoffset"`
NextRawOffset int `json:"nextrawoffset"`
HasMore bool `json:"hasmore"`
Lines []*LineType `json:"lines"`
Cmds []*CmdType `json:"cmds"`
}
type RemoteEditType struct {
RemoteEdit bool `json:"remoteedit"`
RemoteId string `json:"remoteid,omitempty"`
ErrorStr string `json:"errorstr,omitempty"`
InfoStr string `json:"infostr,omitempty"`
KeyStr string `json:"keystr,omitempty"`
HasPassword bool `json:"haspassword,omitempty"`
}
type RemoteViewType struct {
RemoteShowAll bool `json:"remoteshowall,omitempty"`
PtyRemoteId string `json:"ptyremoteid,omitempty"`
RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"`
}
func (RemoteViewType) UpdateType() string {
return "remoteview"
}
type OpenAICmdInfoChatUpdate []*packet.OpenAICmdInfoChatMessage
func (OpenAICmdInfoChatUpdate) UpdateType() string {
return "openaicmdinfochat"
}
func AddOpenAICmdInfoChatUpdate(update *ModelUpdate, chatMessages []*packet.OpenAICmdInfoChatMessage) {
AddUpdate(update, OpenAICmdInfoChatUpdate(chatMessages))
}
type AlertMessageType struct {
Title string `json:"title,omitempty"`
Message string `json:"message"`
Confirm bool `json:"confirm,omitempty"`
Markdown bool `json:"markdown,omitempty"`
}
func (AlertMessageType) UpdateType() string {
return "alertmessage"
}
type ScreenStatusIndicatorType struct {
ScreenId string `json:"screenid"`
Status StatusIndicatorLevel `json:"status"`
}
func (ScreenStatusIndicatorType) UpdateType() string {
return "screenstatusindicator"
}
type ScreenNumRunningCommandsType struct {
ScreenId string `json:"screenid"`
Num int `json:"num"`
}
func (ScreenNumRunningCommandsType) UpdateType() string {
return "screennumrunningcommands"
}
type UserInputRequestType struct {
RequestId string `json:"requestid"`
QueryText string `json:"querytext"`
ResponseType string `json:"responsetype"`
Title string `json:"title"`
Markdown bool `json:"markdown"`
TimeoutMs int `json:"timeoutms"`
}
func (UserInputRequestType) UpdateType() string {
return "userinputrequest"
}