From 4ca5557d9f559257b1d19ee8c71258024aff6faa Mon Sep 17 00:00:00 2001 From: Logan Snow <4197432+lsnow99@users.noreply.github.com> Date: Sun, 18 Jan 2026 12:17:44 -0500 Subject: [PATCH 1/2] Introduce LoadableState --- src/App.svelte | 4 +- src/SaveLoad.svelte | 3 +- src/classes.svelte.js | 72 ++++++++++++++++++++++------------ test/sheet-and-formula.test.js | 2 +- 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 5ad6bd2..6e36b8b 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -162,7 +162,7 @@ return undefined; } } - return State.load(data); + return State.fromData(data); } let dontSave = $state(false); @@ -309,7 +309,7 @@ return; } dontSave = true; - globals = Object.assign(State.load(e.state), { + globals = Object.assign(State.fromData(e.state), { currentSheetIndex: globals.currentSheetIndex, mode: globals.mode, helpOpen: globals.helpOpen, diff --git a/src/SaveLoad.svelte b/src/SaveLoad.svelte index fe7397e..9bdc4ff 100644 --- a/src/SaveLoad.svelte +++ b/src/SaveLoad.svelte @@ -72,7 +72,8 @@ const data = reader.result.match(/,(.*)/)[1]; decompressText(data) .then((result) => { - globals = State.load(JSON.parse(result)); + globals.load(JSON.parse(result)); + globals.imageOpen = false; }) .catch((e) => { console.error(e); diff --git a/src/classes.svelte.js b/src/classes.svelte.js index ff714ba..9432238 100644 --- a/src/classes.svelte.js +++ b/src/classes.svelte.js @@ -14,20 +14,7 @@ function minmax(min, x, max) { return Math.min(Math.max(x, min), max); } -export class State { - sheets = $state([]); - currentSheetIndex = $state(0); - currentSheet = $derived(this.sheets[this.currentSheetIndex]); - selected = $state(new Selection()); - mode = $state("normal"); - keyQueue = $state([]); - pasteBuffer = $state(new Register()); - elements = $state({}); - helpOpen = $state(false); - editorOpen = $state(false); - imageOpen = $state(false); - llmOpen = $state(false); - formulaCode = $state(`// Examples of user-defined formula functions +const sampleFormulaCode = () => `// Examples of user-defined formula functions functions.factorial = (n) => { if (n == 0) return 1; @@ -50,14 +37,49 @@ functions.crypto = async (ticker) => { ), ); }; -`); +`; + +class LoadableState { + sheets = $state([]); + formulaCode = $state(sampleFormulaCode()); + + constructor(sheets, formulaCode) { + this.sheets = sheets; + this.sheets.forEach((sheet) => (sheet.globals = this)); + if (formulaCode != null) { + this.formulaCode = formulaCode; + evalCode(this.formulaCode); + } + } +} + +export class State { + sheets = $state([]); + currentSheetIndex = $state(0); + currentSheet = $derived(this.sheets[this.currentSheetIndex]); + selected = $state(new Selection()); + mode = $state("normal"); + keyQueue = $state([]); + pasteBuffer = $state(new Register()); + elements = $state({}); + helpOpen = $state(false); + editorOpen = $state(false); + imageOpen = $state(false); + llmOpen = $state(false); settings = $state({ mobileZoom: 100, }); + formulaCode = $state(sampleFormulaCode()); forceSave = $state(0); - static load(data) { - let result = new State( + static fromData(data) { + const state = new State([]); + state.load(data); + return state; + } + + load(data) { + let result = new LoadableState( data.sheets.map((sheet) => { let s = new Sheet( sheet.name, @@ -72,18 +94,16 @@ functions.crypto = async (ticker) => { }), data.formulaCode, ); - return result; + this._reload(result); + } + + _reload(loadableState) { + this.sheets = loadableState.sheets; + this.formulaCode = loadableState.formulaCode; } constructor(sheets, formulaCode) { - this.sheets = sheets; - this.sheets.forEach((sheet) => { - sheet.globals = this; - }); - if (formulaCode != null) { - this.formulaCode = formulaCode; - evalCode(this.formulaCode); - } + this._reload(new LoadableState(sheets, formulaCode)); } getSelectedCells() { diff --git a/test/sheet-and-formula.test.js b/test/sheet-and-formula.test.js index 51588ba..a1f1bdc 100644 --- a/test/sheet-and-formula.test.js +++ b/test/sheet-and-formula.test.js @@ -3,7 +3,7 @@ import { test, expect, beforeEach } from "vitest"; import { evalCode, functions } from "../src/formula-functions.svelte.js"; function createSheet(cells, formulaCode = "") { - return State.load({ + return State.fromData({ sheets: [ { name: "Sheet 1", From ad49121ba911c719630637f55eb12eafa8131a9e Mon Sep 17 00:00:00 2001 From: Logan Snow <4197432+lsnow99@users.noreply.github.com> Date: Sun, 18 Jan 2026 13:12:31 -0500 Subject: [PATCH 2/2] Add State.load test --- test/sheet-and-formula.test.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/sheet-and-formula.test.js b/test/sheet-and-formula.test.js index a1f1bdc..a96dad5 100644 --- a/test/sheet-and-formula.test.js +++ b/test/sheet-and-formula.test.js @@ -1,4 +1,4 @@ -import { Sheet, State } from "../src/classes.svelte.js"; +import { State } from "../src/classes.svelte.js"; import { test, expect, beforeEach } from "vitest"; import { evalCode, functions } from "../src/formula-functions.svelte.js"; @@ -690,3 +690,25 @@ test("Binary literals", async () => { [8, -8, -9, 7, 9], ]); }); + +test("State.load preserves elements", async () => { + const state = createSheet([["1", "2", "3"]]); + await expectSheet(state.currentSheet, [[1, 2, 3]]); + state.elements.formulaBar = document.createElement("textarea"); + state.elements.formulaBar.setAttribute("data-testid", "shouldPreserve"); + + const savedState = createSheet([["4", "5", "6"]]); + await expectSheet(savedState.currentSheet, [[4, 5, 6]]); + + const testFormulaCode = "functions.testFormulaCode = () => {}"; + state.load({ + sheets: savedState.sheets, + formulaCode: testFormulaCode, + }); + + await expectSheet(state.currentSheet, [[4, 5, 6]]); + expect(state.formulaCode).toBe(testFormulaCode); + expect(state.elements.formulaBar.getAttribute("data-testid")).toBe( + "shouldPreserve", + ); +});