persist vm session
This commit is contained in:
@@ -19,6 +19,15 @@ import { VirtualMachine } from "../virtual_machine";
|
||||
import { openFile, saveFile } from "../utils";
|
||||
|
||||
import "../virtual_machine/ui";
|
||||
import "./save";
|
||||
import { SaveDialog } from "./save";
|
||||
|
||||
declare global {
|
||||
const __COMMIT_HASH__: string;
|
||||
const __BUILD_DATE__: string;
|
||||
}
|
||||
|
||||
import packageJson from "../../../package.json"
|
||||
|
||||
@customElement("ic10emu-app")
|
||||
export class App extends BaseElement {
|
||||
@@ -43,10 +52,15 @@ export class App extends BaseElement {
|
||||
`,
|
||||
];
|
||||
|
||||
version = packageJson.version;
|
||||
gitVer = __COMMIT_HASH__;
|
||||
buildDate = __BUILD_DATE__;
|
||||
|
||||
editorSettings: { fontSize: number; relativeLineNumbers: boolean };
|
||||
|
||||
@query("ace-ic10") accessor editor: IC10Editor;
|
||||
@query("session-share-dialog") accessor shareDialog: ShareSessionDialog;
|
||||
@query("ace-ic10") editor: IC10Editor;
|
||||
@query("session-share-dialog") shareDialog: ShareSessionDialog;
|
||||
@query("save-dialog") saveDialog: SaveDialog;
|
||||
|
||||
// get editor() {
|
||||
// return this.renderRoot.querySelector("ace-ic10") as IC10Editor;
|
||||
@@ -67,6 +81,7 @@ export class App extends BaseElement {
|
||||
root.addEventListener("app-share-session", this._handleShare.bind(this));
|
||||
root.addEventListener("app-open-file", this._handleOpenFile.bind(this));
|
||||
root.addEventListener("app-save-as", this._handleSaveAs.bind(this));
|
||||
root.addEventListener("app-save", this._handleSave.bind(this));
|
||||
return root;
|
||||
}
|
||||
|
||||
@@ -86,6 +101,7 @@ export class App extends BaseElement {
|
||||
</sl-split-panel>
|
||||
</div>
|
||||
<session-share-dialog></session-share-dialog>
|
||||
<save-dialog></save-dialog>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -102,6 +118,9 @@ export class App extends BaseElement {
|
||||
saveFile(window.Editor.editorValue);
|
||||
}
|
||||
|
||||
_handleSave(_e: Event) {
|
||||
this.saveDialog.show("save");
|
||||
}
|
||||
_handleOpenFile(_e: Event) {
|
||||
openFile(window.Editor.editor);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { App } from "./app";
|
||||
import { Nav } from "./nav";
|
||||
import { SaveDialog } from "./save";
|
||||
import { ShareSessionDialog } from "./share";
|
||||
import "./icons";
|
||||
export { App, Nav, ShareSessionDialog }
|
||||
|
||||
@@ -107,6 +107,7 @@ export class Nav extends BaseElement {
|
||||
<sl-menu class="menu" @sl-select=${this._menuClickHandler} style="z-index: 10">
|
||||
<sl-menu-item value="share">Share</sl-menu-item>
|
||||
<sl-menu-item value="openFile">Open File</sl-menu-item>
|
||||
<sl-menu-item value="save">Save</sl-menu-item>
|
||||
<sl-menu-item value="saveAs">Save As</sl-menu-item>
|
||||
<sl-divider></sl-divider>
|
||||
<sl-menu-item value="editorSettings"
|
||||
@@ -179,6 +180,9 @@ export class Nav extends BaseElement {
|
||||
case "openFile":
|
||||
this.dispatchEvent(new CustomEvent("app-open-file", { bubbles: true }));
|
||||
break;
|
||||
case "save":
|
||||
this.dispatchEvent(new CustomEvent("app-save", { bubbles: true }));
|
||||
break;
|
||||
case "saveAs":
|
||||
this.dispatchEvent(new CustomEvent("app-save-as", { bubbles: true }));
|
||||
break;
|
||||
|
||||
114
www/src/ts/app/save.ts
Normal file
114
www/src/ts/app/save.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { HTMLTemplateResult, html, css, CSSResultGroup } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "../components";
|
||||
import { VMState } from "../session";
|
||||
|
||||
import '@shoelace-style/shoelace/dist/components/dialog/dialog.js';
|
||||
import '@shoelace-style/shoelace/dist/components/format-date/format-date.js';
|
||||
import '@shoelace-style/shoelace/dist/components/relative-time/relative-time.js';
|
||||
import '@shoelace-style/shoelace/dist/components/format-bytes/format-bytes.js';
|
||||
import SlInput from '@shoelace-style/shoelace/dist/components/input/input.js';
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.js";
|
||||
import { when } from "lit/directives/when.js";
|
||||
|
||||
export type SaveDialogMode = "save" | "load";
|
||||
|
||||
@customElement("save-dialog")
|
||||
export class SaveDialog extends BaseElement {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
];
|
||||
|
||||
@state() saves: { name: string, date: Date, session: VMState }[];
|
||||
@state() mode: SaveDialogMode;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.mode = "save";
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback()
|
||||
window.App.get().then(app => app.session.addEventListener("sessions-local-update", this._handleSessionsUpdate.bind(this)));
|
||||
this.loadsaves();
|
||||
}
|
||||
|
||||
_handleSessionsUpdate() {
|
||||
this.loadsaves();
|
||||
}
|
||||
|
||||
loadsaves() {
|
||||
window.App.get().then(async (app) => {
|
||||
const saves = await app.session.getLocalSaved();
|
||||
this.saves = saves;
|
||||
});
|
||||
}
|
||||
|
||||
@query("sl-dialog") dialog: SlDialog;
|
||||
|
||||
show(mode: SaveDialogMode) {
|
||||
this.mode = mode;
|
||||
this.dialog.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.dialog.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<sl-dialog label="Save Session">
|
||||
${when(this.mode === "save",
|
||||
() => html`
|
||||
<div>
|
||||
<sl-input class="save-name-input" autofocus></sl-input>
|
||||
<sl-button variant="success" @click=${this._handleSaveClick}>Save</sl-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<sl-input class="filter-input" ?autofocus=${this.mode === "load"} placeholder="Filter Saves" clearable
|
||||
@sl-input=${this._handleSearchInput}>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Date</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
${when(
|
||||
typeof this.saves !== "undefined",
|
||||
() => repeat(this.saves, (save) => save.name, (save) => {
|
||||
const size = JSON.stringify(save.session).length
|
||||
return html`
|
||||
<tr>
|
||||
<td>${save.name}</td>
|
||||
<td>
|
||||
<sl-format-date .date=${save.date}></sl-format-date>
|
||||
<sl-relative-time .date=${save.date}></sl-relative-time>
|
||||
</td>
|
||||
<td><sl-format-bytes .value=${size}></sl-format-bytes></td>
|
||||
</tr>
|
||||
`;
|
||||
},
|
||||
)
|
||||
)}
|
||||
</table>
|
||||
</sl-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@query(".save-name-input") saveInput: SlInput;
|
||||
|
||||
async _handleSaveClick(_e: CustomEvent) {
|
||||
const name = this.saveInput.value;
|
||||
const app = await window.App.get();
|
||||
console.log(app);
|
||||
await app.session.saveLocal(name);
|
||||
}
|
||||
|
||||
_handleSearchInput() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -274,11 +274,11 @@ export class IC10Editor extends BaseElement {
|
||||
|
||||
const app = await window.App.get();
|
||||
app.session.onLoad(((e: CustomEvent) => {
|
||||
const session = e.detail;
|
||||
const session = app.session;
|
||||
const updated_ids: number[] = [];
|
||||
for (const [id, _] of session.programs) {
|
||||
for (const [id, code] of session.programs) {
|
||||
updated_ids.push(id);
|
||||
that.createOrSetSession(id, session.programs.get(id));
|
||||
that.createOrSetSession(id, code);
|
||||
}
|
||||
that.activateSession(that.active_session);
|
||||
for (const [id, _] of that.sessions) {
|
||||
@@ -484,18 +484,19 @@ export class IC10Editor extends BaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
createOrSetSession(session_id: number, content: any) {
|
||||
createOrSetSession(session_id: number, content: string) {
|
||||
if (!this.sessions.has(session_id)) {
|
||||
this.newSession(session_id);
|
||||
this.newSession(session_id, content);
|
||||
} else {
|
||||
this.sessions.get(session_id).setValue(content);
|
||||
}
|
||||
this.sessions.get(session_id)?.setValue(content);
|
||||
}
|
||||
|
||||
newSession(session_id: number) {
|
||||
newSession(session_id: number, content?: string) {
|
||||
if (this.sessions.has(session_id)) {
|
||||
return false;
|
||||
}
|
||||
const session = ace.createEditSession("", this.mode as any);
|
||||
const session = ace.createEditSession(content ?? "", this.mode as any);
|
||||
session.setOptions({
|
||||
firstLineNumber: 0,
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ class DeferedApp {
|
||||
get(): Promise<App> {
|
||||
const that = this;
|
||||
return new Promise(resolve => {
|
||||
if (typeof that.app !== "undefined") {
|
||||
if (typeof that.app === "undefined") {
|
||||
that.resolvers.push(resolve);
|
||||
} else {
|
||||
resolve(that.app);
|
||||
@@ -45,7 +45,7 @@ class DeferedVM {
|
||||
get(): Promise<VirtualMachine> {
|
||||
const that = this;
|
||||
return new Promise(resolve => {
|
||||
if (typeof that.vm !== "undefined") {
|
||||
if (typeof that.vm === "undefined") {
|
||||
that.resolvers.push(resolve);
|
||||
} else {
|
||||
resolve(that.vm);
|
||||
|
||||
@@ -63,6 +63,11 @@ j ra
|
||||
import type { ICError, FrozenVM } from "ic10emu_wasm";
|
||||
import { App } from "./app";
|
||||
|
||||
import { openDB, DBSchema } from 'idb';
|
||||
import { fromJson, toJson } from "./utils";
|
||||
|
||||
const LOCAL_DB_VERSION = 1;
|
||||
|
||||
export class Session extends EventTarget {
|
||||
private _programs: Map<number, string>;
|
||||
private _errors: Map<number, ICError[]>;
|
||||
@@ -179,17 +184,17 @@ export class Session extends EventTarget {
|
||||
save() {
|
||||
if (this._save_timeout) clearTimeout(this._save_timeout);
|
||||
this._save_timeout = setTimeout(() => {
|
||||
this.saveToFragment();
|
||||
if (this.app.vm) {
|
||||
this.app.vm.updateCode();
|
||||
}
|
||||
this.saveToFragment();
|
||||
this._save_timeout = undefined;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async saveToFragment() {
|
||||
const toSave = { vmState: this.app.vm.saveVMState(), activeIC: this.activeIC };
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(toSave));
|
||||
const toSave = { vm: this.app.vm.saveVMState(), activeIC: this.activeIC };
|
||||
const bytes = new TextEncoder().encode(toJson(toSave));
|
||||
try {
|
||||
const c_bytes = await compress(bytes, defaultCompression);
|
||||
const fragment = base64url_encode(c_bytes);
|
||||
@@ -200,11 +205,29 @@ export class Session extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
async load(data: VMState | OldPrograms | string) {
|
||||
if (typeof data === "string") {
|
||||
this._programs = new Map([[1, data]]);
|
||||
} else if ( "programs" in data) {
|
||||
this._programs = new Map(data.programs);
|
||||
} else if ( "vm" in data ) {
|
||||
this._programs = new Map();
|
||||
const state = data.vm;
|
||||
// assign first so it's present when the
|
||||
// vm fires events
|
||||
this._activeIC = data.activeIC;
|
||||
this.app.vm.restoreVMState(state);
|
||||
this.programs = this.app.vm.getPrograms();
|
||||
// assign again to fire event
|
||||
this.activeIC = data.activeIC;
|
||||
}
|
||||
this._fireOnLoad();
|
||||
}
|
||||
|
||||
async loadFromFragment() {
|
||||
const fragment = window.location.hash.slice(1);
|
||||
if (fragment === "demo") {
|
||||
this._programs = new Map([[1, demoCode]]);
|
||||
this._fireOnLoad();
|
||||
this.load(demoCode);
|
||||
return;
|
||||
}
|
||||
if (fragment.length > 0) {
|
||||
@@ -215,39 +238,78 @@ export class Session extends EventTarget {
|
||||
const data = getJson(txt);
|
||||
if (data === null) {
|
||||
// backwards compatible
|
||||
this._programs = new Map([[1, txt]]);
|
||||
this, this._fireOnLoad();
|
||||
this.load(txt);
|
||||
return;
|
||||
} else if ("programs" in data) {
|
||||
try {
|
||||
this._programs = new Map(data.programs);
|
||||
this._fireOnLoad();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("Bad session data:", e);
|
||||
}
|
||||
} else if ("vmState" in data && "activeIC" in data) {
|
||||
try {
|
||||
this._programs = new Map();
|
||||
const state = data.vmState as FrozenVM;
|
||||
// assign first so it's present when the
|
||||
// vm setting the programs list fires events
|
||||
this._activeIC = data.activeIC;
|
||||
this.app.vm.restoreVMState(state);
|
||||
this.programs = this.app.vm.getPrograms();
|
||||
// assign again to fire event
|
||||
this.activeIC = data.activeIC;
|
||||
this._fireOnLoad();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log("Bad session data:", e);
|
||||
}
|
||||
this.load(data as OldPrograms);
|
||||
return;
|
||||
} else if ("vm" in data && "activeIC" in data) {
|
||||
this.load(data as VMState);
|
||||
} else {
|
||||
console.log("Bad session data:", data);
|
||||
console.log("Bad session data:", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async openIndexDB() {
|
||||
return await openDB<AppDBSchemaV1>("ic10-vm-sessions", LOCAL_DB_VERSION, {
|
||||
upgrade(db, oldVersion, newVersion, transaction, event) {
|
||||
// only db verison currently known is v1
|
||||
if (oldVersion < 1) {
|
||||
const sessionStore = db.createObjectStore('sessions');
|
||||
sessionStore.createIndex('by-date', 'date');
|
||||
sessionStore.createIndex('by-name', 'name');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async saveLocal(name: string) {
|
||||
const state: VMState = {
|
||||
vm: (await window.VM.get()).ic10vm.saveVMState(),
|
||||
activeIC: this.activeIC,
|
||||
};
|
||||
const db = await this.openIndexDB();
|
||||
const transaction = db.transaction(['sessions'], "readwrite");
|
||||
const sessionStore = transaction.objectStore("sessions");
|
||||
sessionStore.put({
|
||||
name,
|
||||
date: new Date(),
|
||||
session: state,
|
||||
}, name);
|
||||
this.dispatchEvent(new CustomEvent("sessions-local-update"))
|
||||
}
|
||||
|
||||
async getLocalSaved() {
|
||||
const db = await this.openIndexDB();
|
||||
const sessions = await db.getAll('sessions');
|
||||
return sessions;
|
||||
}
|
||||
}
|
||||
|
||||
export interface VMState {
|
||||
activeIC: number;
|
||||
vm: FrozenVM;
|
||||
}
|
||||
|
||||
interface AppDBSchemaV1 extends DBSchema {
|
||||
sessions: {
|
||||
key: string;
|
||||
value: {
|
||||
name: string;
|
||||
date: Date;
|
||||
session: VMState;
|
||||
}
|
||||
indexes: {
|
||||
'by-date': Date;
|
||||
'by-name': string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface OldPrograms {
|
||||
programs: [number, string][]
|
||||
}
|
||||
|
||||
const byteToHex: string[] = [];
|
||||
@@ -298,7 +360,7 @@ async function decompressFragment(c_bytes: ArrayBuffer) {
|
||||
|
||||
function getJson(value: any) {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
return fromJson(value);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,11 @@ function replacer(key: any, value: any) {
|
||||
return {
|
||||
dataType: 'Number',
|
||||
value: "NaN",
|
||||
}
|
||||
};
|
||||
} else if (typeof value === "undefined" ) {
|
||||
return {
|
||||
dataType: 'undefined',
|
||||
};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
@@ -35,6 +39,8 @@ function reviver(_key: any, value: any) {
|
||||
return new Map(value.value);
|
||||
} else if (value.dataType === 'Number') {
|
||||
return parseFloat(value.value)
|
||||
} else if (value.dataType === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -60,22 +60,22 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
device: DeviceRef;
|
||||
|
||||
@state() accessor name: string | null = null;
|
||||
@state() accessor nameHash: number | null = null;
|
||||
@state() accessor prefabName: string | null;
|
||||
@state() accessor fields: LogicFields;
|
||||
@state() accessor slots: Slot[];
|
||||
@state() accessor reagents: Reagents;
|
||||
@state() accessor connections: Connection[];
|
||||
@state() accessor icIP: number;
|
||||
@state() accessor icOpCount: number;
|
||||
@state() accessor icState: string;
|
||||
@state() accessor errors: ICError[];
|
||||
@state() accessor registers: Registers | null;
|
||||
@state() accessor stack: Stack | null;
|
||||
@state() accessor aliases: Aliases | null;
|
||||
@state() accessor defines: Defines | null;
|
||||
@state() accessor pins: Pins | null;
|
||||
@state() name: string | null = null;
|
||||
@state() nameHash: number | null = null;
|
||||
@state() prefabName: string | null;
|
||||
@state() fields: LogicFields;
|
||||
@state() slots: Slot[];
|
||||
@state() reagents: Reagents;
|
||||
@state() connections: Connection[];
|
||||
@state() icIP: number;
|
||||
@state() icOpCount: number;
|
||||
@state() icState: string;
|
||||
@state() errors: ICError[];
|
||||
@state() registers: Registers | null;
|
||||
@state() stack: Stack | null;
|
||||
@state() aliases: Aliases | null;
|
||||
@state() defines: Defines | null;
|
||||
@state() pins: Pins | null;
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
|
||||
@@ -46,6 +46,7 @@ import { DeviceDB, DeviceDBEntry } from "./device_db";
|
||||
import { connectionFromDeviceDBConnection } from "./utils";
|
||||
import { SlDialog } from "@shoelace-style/shoelace";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { cache } from "lit/directives/cache.js";
|
||||
|
||||
@customElement("vm-device-card")
|
||||
export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
@@ -221,7 +222,8 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
|
||||
class="device-name"
|
||||
size="small"
|
||||
pill
|
||||
placeholder="${this.prefabName}"
|
||||
placeholder=${this.prefabName}
|
||||
value=${this.name}
|
||||
@sl-change=${this._handleChangeName}
|
||||
>
|
||||
<span slot="prefix">Name</span>
|
||||
@@ -814,18 +816,20 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
this.deviceDB = e.detail;
|
||||
}
|
||||
|
||||
renderSearchResults(): HTMLTemplateResult {
|
||||
const renderedResults: HTMLTemplateResult[] = this._searchResults?.map(
|
||||
(result) => html`
|
||||
renderSearchResults() {
|
||||
return repeat(
|
||||
this._searchResults ?? [],
|
||||
(result) => result.name,
|
||||
(result) => cache(html`
|
||||
<vm-device-template
|
||||
prefab_name=${result.name}
|
||||
class="card"
|
||||
@add-device-template=${this._handleDeviceAdd}
|
||||
>
|
||||
</vm-device-template>
|
||||
`,
|
||||
`)
|
||||
);
|
||||
return html`${renderedResults}`;
|
||||
|
||||
}
|
||||
|
||||
_handleDeviceAdd() {
|
||||
@@ -851,7 +855,7 @@ export class VMAddDeviceButton extends BaseElement {
|
||||
@sl-input=${this._handleSearchInput}
|
||||
>
|
||||
<span slot="prefix">Search Structures</span>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>"
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
<div class="search-results">${this.renderSearchResults()}</div>
|
||||
<sl-button
|
||||
@@ -926,8 +930,7 @@ export class VmDeviceTemplate extends BaseElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const that = this;
|
||||
window.VM.get().then((vm) => (that.deviceDB = vm.db));
|
||||
this.deviceDB = window.VM.vm.db;
|
||||
}
|
||||
|
||||
get deviceDB(): DeviceDB {
|
||||
|
||||
@@ -127,6 +127,7 @@ class VirtualMachine extends EventTarget {
|
||||
detail: ids,
|
||||
}),
|
||||
);
|
||||
this.app.session.save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +153,7 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
this.update(false);
|
||||
}
|
||||
|
||||
step() {
|
||||
@@ -193,7 +194,7 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
update(save: boolean = true) {
|
||||
this.updateDevices();
|
||||
this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => {
|
||||
if (this.devices.has(id)) {
|
||||
@@ -202,16 +203,18 @@ class VirtualMachine extends EventTarget {
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
this.updateDevice(this.activeIC);
|
||||
this.updateDevice(this.activeIC, save);
|
||||
if (save) this.app.session.save();
|
||||
}
|
||||
|
||||
updateDevice(device: DeviceRef) {
|
||||
updateDevice(device: DeviceRef, save: boolean = true) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: device.id }),
|
||||
);
|
||||
if (typeof device.ic !== "undefined") {
|
||||
this.app.session.setActiveLine(device.id, device.ip!);
|
||||
}
|
||||
if (save) this.app.session.save();
|
||||
}
|
||||
|
||||
handleVmError(err: Error) {
|
||||
@@ -272,6 +275,7 @@ class VirtualMachine extends EventTarget {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
this.app.session.save();
|
||||
return true;
|
||||
} catch (e) {
|
||||
this.handleVmError(e);
|
||||
@@ -372,6 +376,7 @@ class VirtualMachine extends EventTarget {
|
||||
detail: Array.from(device_ids),
|
||||
}),
|
||||
);
|
||||
this.app.session.save();
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
@@ -413,9 +418,4 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
export interface VMState {
|
||||
activeIC: number;
|
||||
vm: FrozenVM;
|
||||
}
|
||||
|
||||
export { VirtualMachine };
|
||||
|
||||
Reference in New Issue
Block a user