diff --git a/web/libs/editor/src/mixins/PersistentState.js b/web/libs/editor/src/mixins/PersistentState.js index 3b8039aa0c6c..1960a059d269 100644 --- a/web/libs/editor/src/mixins/PersistentState.js +++ b/web/libs/editor/src/mixins/PersistentState.js @@ -26,7 +26,11 @@ const PersistentStateMixin = types storeValues() { const key = self.persistentValuesKey; - const obj = { ...self.persistentFingerprint, values: self.persistentValues }; + const fingerprint = self.persistentFingerprint; + + // no stable identity (e.g. preview without a task) — don't persist + if (!Object.values(fingerprint).every((value) => value !== undefined)) return; + const obj = { ...fingerprint, values: self.persistentValues }; localStorage.setItem(key, JSON.stringify(obj)); }, @@ -35,8 +39,10 @@ const PersistentStateMixin = types const stored = JSON.parse(localStorage.getItem(self.persistentValuesKey) || "{}"); if (!stored) return; - if (!Object.keys(self.persistentFingerprint).every((key) => stored[key] === self.persistentFingerprint[key])) - return; + const fingerprint = self.persistentFingerprint; + + if (!Object.values(fingerprint).every((value) => value !== undefined)) return; + if (!Object.keys(fingerprint).every((key) => stored[key] === fingerprint[key])) return; const values = stored.values || {}; diff --git a/web/libs/editor/src/tags/object/TimeSeries.jsx b/web/libs/editor/src/tags/object/TimeSeries.jsx index 4148830817d9..c6ee115290b8 100644 --- a/web/libs/editor/src/tags/object/TimeSeries.jsx +++ b/web/libs/editor/src/tags/object/TimeSeries.jsx @@ -479,7 +479,12 @@ const Model = types }, get persistentFingerprint() { - return { task: getRoot(self).task?.id }; + // tag and overviewwidth invalidate the stored view on config change + return { + task: getRoot(self).task?.id, + tag: self.name, + overviewwidth: self.overviewwidth, + }; }, states() { diff --git a/web/libs/editor/src/tags/object/__tests__/TimeSeries.test.js b/web/libs/editor/src/tags/object/__tests__/TimeSeries.test.js index 3ebcdaf4ebae..24e0ed405378 100644 --- a/web/libs/editor/src/tags/object/__tests__/TimeSeries.test.js +++ b/web/libs/editor/src/tags/object/__tests__/TimeSeries.test.js @@ -1302,6 +1302,87 @@ describe("TimeSeries persistentValues, dataSlices, and panView no-op", () => { expect(fp).toHaveProperty("task"); }); + it("persistentFingerprint includes tag name and overviewwidth", () => { + const model = TimeSeriesModel.create( + { + name: "ts1", + value: "$timeseries", + timecolumn: "time", + overviewwidth: "50%", + children: [], + }, + mockEnv, + ); + const fp = model.persistentFingerprint; + expect(fp.tag).toBe("ts1"); + expect(fp.overviewwidth).toBe("50%"); + }); + + it("does not store values when there is no task id", () => { + localStorage.clear(); + const model = TimeSeriesModel.create( + { + name: "timeseries", + value: "$timeseries", + timecolumn: "time", + children: [], + }, + mockEnv, + ); + model.setData({ time: [0, 100], value: [1, 2] }); + model.updateTR([10, 90]); + model.storeValues(); + expect(localStorage.getItem(model.persistentValuesKey)).toBeNull(); + }); + + it("does not restore stored view when overviewwidth changed", () => { + localStorage.clear(); + const MockStoreWithTask = types + .model({ timeseries: TimeSeriesModel }) + .volatile(() => ({ task: { id: 1, dataObj: {} } })); + + const m1 = TimeSeriesModel.create( + { name: "ts", value: "$timeseries", timecolumn: "time", overviewwidth: "25%", children: [] }, + mockEnv, + ); + MockStoreWithTask.create({ timeseries: m1 }, mockEnv); + m1.setData({ time: [0, 100], value: [1, 2] }); + m1.updateTR([0, 25]); + m1.storeValues(); + + const m2 = TimeSeriesModel.create( + { name: "ts", value: "$timeseries", timecolumn: "time", overviewwidth: "100%", children: [] }, + mockEnv, + ); + MockStoreWithTask.create({ timeseries: m2 }, mockEnv); + m2.restoreValues(); + expect(m2.brushRange.length).toBe(0); + }); + + it("restores stored view for the same task, tag and config", () => { + localStorage.clear(); + const MockStoreWithTask = types + .model({ timeseries: TimeSeriesModel }) + .volatile(() => ({ task: { id: 1, dataObj: {} } })); + + const m1 = TimeSeriesModel.create( + { name: "ts", value: "$timeseries", timecolumn: "time", overviewwidth: "25%", children: [] }, + mockEnv, + ); + MockStoreWithTask.create({ timeseries: m1 }, mockEnv); + m1.setData({ time: [0, 100], value: [1, 2] }); + m1.updateTR([0, 25]); + m1.storeValues(); + + const m2 = TimeSeriesModel.create( + { name: "ts", value: "$timeseries", timecolumn: "time", overviewwidth: "25%", children: [] }, + mockEnv, + ); + MockStoreWithTask.create({ timeseries: m2 }, mockEnv); + m2.restoreValues(); + expect([...m2.brushRange]).toEqual([0, 25]); + }); + it("dataSlices returns array of slices", () => { const model = TimeSeriesModel.create( {