@marimo-team/frontend 0.15.1-dev14 → 0.15.1-dev16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ConnectedDataExplorerComponent-BBUI6vmA.js → ConnectedDataExplorerComponent-BPfZgm5R.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-CX4CDvS_.js → ImageComparisonComponent-DN7EJDoX.js} +1 -1
- package/dist/assets/{VegaLite-BtjbyLAA.js → VegaLite-CUBSrB9h.js} +1 -1
- package/dist/assets/{_baseEach-Ca2O9Wne.js → _baseEach-COj8UYrB.js} +1 -1
- package/dist/assets/_baseMap-CzIRdvop.js +1 -0
- package/dist/assets/{_baseUniq-D4iU71YJ.js → _baseUniq-smyj1abf.js} +1 -1
- package/dist/assets/{_createAggregator-QKQ5gj2H.js → _createAggregator-BAk0hBur.js} +1 -1
- package/dist/assets/{any-language-editor-Pcus9OFi.js → any-language-editor-D4fMF9FA.js} +1 -1
- package/dist/assets/{architectureDiagram-KFL7JDKH-DwsPlBM_.js → architectureDiagram-KFL7JDKH-DBOzElPL.js} +1 -1
- package/dist/assets/{blockDiagram-ZYB65J3Q-B65IkzKX.js → blockDiagram-ZYB65J3Q-u8cQEOZT.js} +1 -1
- package/dist/assets/{c4Diagram-AAMF2YG6-DFmoIQ6u.js → c4Diagram-AAMF2YG6-DwbICH2A.js} +1 -1
- package/dist/assets/channel-KTUeCTOf.js +1 -0
- package/dist/assets/{chunk-ANTBXLJU-BPOzSwai.js → chunk-ANTBXLJU-Dh4bjrG_.js} +1 -1
- package/dist/assets/{chunk-FHKO5MBM-BbM8E9B5.js → chunk-FHKO5MBM-CcxXcZ33.js} +1 -1
- package/dist/assets/{chunk-GLLZNHP4-BqFTg_51.js → chunk-GLLZNHP4-Dmy2T6iT.js} +1 -1
- package/dist/assets/{chunk-JBRWN2VN-DMbINMTT.js → chunk-JBRWN2VN-DF6eV4wI.js} +1 -1
- package/dist/assets/{chunk-LXBSTHXV-C6VjboaW.js → chunk-LXBSTHXV-BZK5AZPH.js} +1 -1
- package/dist/assets/{chunk-NRVI72HA-CQ-LV0Dy.js → chunk-NRVI72HA-BSUK3njt.js} +1 -1
- package/dist/assets/{chunk-OMD6QJNC-Cwl2YrS3.js → chunk-OMD6QJNC-DQeyHllN.js} +1 -1
- package/dist/assets/{chunk-WVR4S24B-B9Jid-bk.js → chunk-WVR4S24B-BrcfqJzw.js} +1 -1
- package/dist/assets/{circle-play-BZUxpncG.js → circle-play-yBw8lfzY.js} +1 -1
- package/dist/assets/classDiagram-3BZAVTQC-DPHwWa4N.js +1 -0
- package/dist/assets/classDiagram-v2-QTMF73CY-DPHwWa4N.js +1 -0
- package/dist/assets/clone-BmLPfKbO.js +1 -0
- package/dist/assets/{compile-yJZ0Xdaw.js → compile-Bec0k3mh.js} +1 -1
- package/dist/assets/{dagre-2BBEFEWP-D8ZiA7Qh.js → dagre-2BBEFEWP-P-g9NZ2K.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DU4SCMnn.js → data-grid-overlay-editor-COTyuTA0.js} +1 -1
- package/dist/assets/{diagram-4IRLE6MV-n4YVHjql.js → diagram-4IRLE6MV-BSJp0gka.js} +1 -1
- package/dist/assets/{diagram-GUPCWM2R-Bo5QTdQd.js → diagram-GUPCWM2R-DRjfoI14.js} +1 -1
- package/dist/assets/{diagram-RP2FKANI-Dfj2_fkX.js → diagram-RP2FKANI-CdIkZYD2.js} +1 -1
- package/dist/assets/{edit-page-DKcm_3qI.js → edit-page-DZr7-GvB.js} +56 -56
- package/dist/assets/{erDiagram-HZWUO2LU-DZzYtx3L.js → erDiagram-HZWUO2LU-DTHN9I8P.js} +1 -1
- package/dist/assets/{flowDiagram-THRYKUMA-BGysw2Yl.js → flowDiagram-THRYKUMA-BTCjKp92.js} +1 -1
- package/dist/assets/{ganttDiagram-WV7ZQ7D5-bevExrPn.js → ganttDiagram-WV7ZQ7D5-DRi68HWD.js} +1 -1
- package/dist/assets/{gitGraphDiagram-OJR772UL-Cz-LKk6z.js → gitGraphDiagram-OJR772UL-DRj-3FPQ.js} +1 -1
- package/dist/assets/{glide-data-editor-_ErWjEGN.js → glide-data-editor-GC2Tm7eO.js} +4 -4
- package/dist/assets/{graph-BotwWgC_.js → graph-R7CWosbg.js} +1 -1
- package/dist/assets/{home-page-Gm-HyQW-.js → home-page-CyNI8llk.js} +1 -1
- package/dist/assets/{index-bFhhN-yc.js → index-Bpq6foom.js} +1 -1
- package/dist/assets/{index-BJQhAWxG.js → index-C2E3cOJc.js} +1 -1
- package/dist/assets/{index-u0nnWJen.js → index-CD2ivQTR.js} +1 -1
- package/dist/assets/{index-Bdm26Lv3.js → index-CG3NSOEk.js} +1 -1
- package/dist/assets/{index-LsvpkwND.js → index-COYAjgyj.js} +1 -1
- package/dist/assets/{index-C2JHPfAE.js → index-Cv0VD6q8.js} +1 -1
- package/dist/assets/{index-B8DEeCJ-.js → index-D-HWwsQB.js} +1 -1
- package/dist/assets/{index-B8Xj7x_9.js → index-DF0A4qiq.js} +1 -1
- package/dist/assets/{index-C4NF73JD.js → index-DKNnr1M-.js} +1 -1
- package/dist/assets/{index-D7PpFqo7.js → index-DTug6sja.js} +1 -1
- package/dist/assets/{index-BcvK_4kl.js → index-DUFKeg2p.js} +1 -1
- package/dist/assets/{index-boRDRVH5.js → index-DcmLW1fG.js} +1 -1
- package/dist/assets/{index-D31E2jjI.js → index-DePLe-OM.js} +1 -1
- package/dist/assets/{index-DQYkp82V.js → index-EZIz7w08.js} +1 -1
- package/dist/assets/{index-BvXaqQ0m.js → index-El7wdsft.js} +1 -1
- package/dist/assets/{index-KDj__fqX.js → index-P9NNDpC1.js} +1 -1
- package/dist/assets/{index-BhSNFJlD.js → index-Tc_0hlVK.js} +131 -131
- package/dist/assets/{index-BAML-avM.js → index-ZK30DKfs.js} +1 -1
- package/dist/assets/{index-D_fBaBkI.js → index-kC7oVlSz.js} +1 -1
- package/dist/assets/{index-D22p-NeS.js → index-sPITtia-.js} +1 -1
- package/dist/assets/{infoDiagram-6WOFNB3A-EUOwUSFP.js → infoDiagram-6WOFNB3A-7y7TIB48.js} +1 -1
- package/dist/assets/{journeyDiagram-FFXJYRFH-CB9jZPwI.js → journeyDiagram-FFXJYRFH-C6FV9piB.js} +1 -1
- package/dist/assets/{kanban-definition-KOZQBZVT-BHbgzVC3.js → kanban-definition-KOZQBZVT-BGil-OkO.js} +1 -1
- package/dist/assets/{layout-BZvWSid2.js → layout-D7woCajw.js} +1 -1
- package/dist/assets/{linear-EdVDlbvK.js → linear-CqdRUecd.js} +1 -1
- package/dist/assets/{links-CO7cA2Fg.js → links-a9aaa35o.js} +1 -1
- package/dist/assets/{mermaid-DsKZ0k6o.js → mermaid-Axj__7kG.js} +4 -4
- package/dist/assets/{min-CnCkyEM4.js → min-B893_0jy.js} +1 -1
- package/dist/assets/{mindmap-definition-LNHGMQRG-Js0ddZCg.js → mindmap-definition-LNHGMQRG-CZUWGNhT.js} +1 -1
- package/dist/assets/{number-overlay-editor-sEOE1Lxn.js → number-overlay-editor-DroE0_Ko.js} +1 -1
- package/dist/assets/{pieDiagram-DBDJKBY4-6mOM4izU.js → pieDiagram-DBDJKBY4-CbR4R3Rc.js} +1 -1
- package/dist/assets/{quadrantDiagram-YPSRARAO-BGEtnc0_.js → quadrantDiagram-YPSRARAO-CeooR17T.js} +1 -1
- package/dist/assets/{react-plotly-J0l9XhwH.js → react-plotly-CuC-he6i.js} +1 -1
- package/dist/assets/{requirementDiagram-EGVEC5DT-CmouXJCf.js → requirementDiagram-EGVEC5DT-BkimxR14.js} +1 -1
- package/dist/assets/{run-page-Kbd0Syqg.js → run-page-BVwqPRN6.js} +1 -1
- package/dist/assets/{sankeyDiagram-HRAUVNP4-B8B8bCqh.js → sankeyDiagram-HRAUVNP4-CTZKv6rj.js} +1 -1
- package/dist/assets/{sequenceDiagram-WFGC7UMF-BCnoyrnn.js → sequenceDiagram-WFGC7UMF-CItujBr8.js} +1 -1
- package/dist/assets/{slides-component-C7vRb0EU.js → slides-component-CFYgEqEh.js} +1 -1
- package/dist/assets/{sortBy-MlLjgbjX.js → sortBy-DxJYl_MG.js} +1 -1
- package/dist/assets/{stateDiagram-UUKSUZ4H-BqpQEZpf.js → stateDiagram-UUKSUZ4H-CVflrtzd.js} +1 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-CaL-mSFo.js +1 -0
- package/dist/assets/{storage-K_hnzEm3.js → storage-Cnne9zKF.js} +3 -3
- package/dist/assets/{terminal-YyumG7Qd.js → terminal-cp6Ptqd0.js} +1 -1
- package/dist/assets/{time-D6Mn02Nz.js → time-DGreD2Zc.js} +1 -1
- package/dist/assets/{timeline-definition-3HZDQTIS-CoishIBE.js → timeline-definition-3HZDQTIS-V85f9_hM.js} +1 -1
- package/dist/assets/{tracing-NWwbuXzA.js → tracing-Cznlg8Fk.js} +2 -2
- package/dist/assets/{trash-CljYMg5D.js → trash-B3RKvBUN.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-BrZyfDQ2.js → treemap-75Q7IDZK-cKRL_h4o.js} +1 -1
- package/dist/assets/{vega-component-DfKv95vf.js → vega-component-CzgePQFb.js} +1 -1
- package/dist/assets/{xychartDiagram-FDP5SA34-Hi8NAr2N.js → xychartDiagram-FDP5SA34-D71Ck33W.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/__tests__/chat-utils.test.ts +50 -36
- package/src/components/chat/chat-panel.tsx +91 -68
- package/src/components/chat/markdown-renderer.tsx +29 -16
- package/src/core/ai/chat-utils.ts +13 -16
- package/src/core/ai/state.ts +30 -10
- package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -9
- package/src/core/codemirror/language/languages/sql/utils.ts +13 -1
- package/src/utils/__tests__/storage.test.ts +144 -0
- package/src/utils/storage.ts +38 -0
- package/dist/assets/_baseMap-CeNM_WhA.js +0 -1
- package/dist/assets/channel-fQDJ54fB.js +0 -1
- package/dist/assets/classDiagram-3BZAVTQC-CFX5hd8N.js +0 -1
- package/dist/assets/classDiagram-v2-QTMF73CY-CFX5hd8N.js +0 -1
- package/dist/assets/clone-sKX7VVYQ.js +0 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-CQ0TxHAR.js +0 -1
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Message as AIMessage } from "@ai-sdk/react";
|
|
4
4
|
import { Logger } from "@/utils/Logger";
|
|
5
|
-
import type { ChatState } from "./state";
|
|
5
|
+
import type { ChatId, ChatState } from "./state";
|
|
6
6
|
|
|
7
7
|
export const addMessageToChat = (
|
|
8
8
|
chatState: ChatState,
|
|
9
|
-
chatId:
|
|
9
|
+
chatId: ChatId | null,
|
|
10
10
|
messageId: string,
|
|
11
11
|
role: "user" | "assistant",
|
|
12
12
|
content: string,
|
|
@@ -16,26 +16,23 @@ export const addMessageToChat = (
|
|
|
16
16
|
Logger.warn("No active chat");
|
|
17
17
|
return chatState;
|
|
18
18
|
}
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
if (activeChatIndex === -1) {
|
|
19
|
+
const chat = chatState.chats.get(chatId);
|
|
20
|
+
if (!chat) {
|
|
23
21
|
Logger.warn("No active chat");
|
|
24
22
|
return chatState;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
const chat = chatState.chats[activeChatIndex];
|
|
28
25
|
const messageIndex = chat.messages.findIndex(
|
|
29
26
|
(message) => message.id === messageId,
|
|
30
27
|
);
|
|
31
28
|
|
|
32
29
|
// Create copy of chats to modify
|
|
33
|
-
const newChats =
|
|
34
|
-
const
|
|
30
|
+
const newChats = new Map(chatState.chats);
|
|
31
|
+
const timestamp = Date.now();
|
|
35
32
|
|
|
36
33
|
if (messageIndex === -1) {
|
|
37
34
|
// Handle new message
|
|
38
|
-
newChats
|
|
35
|
+
newChats.set(chatId, {
|
|
39
36
|
...chat,
|
|
40
37
|
messages: [
|
|
41
38
|
...chat.messages,
|
|
@@ -43,12 +40,12 @@ export const addMessageToChat = (
|
|
|
43
40
|
id: messageId,
|
|
44
41
|
role,
|
|
45
42
|
content,
|
|
46
|
-
timestamp:
|
|
43
|
+
timestamp: timestamp,
|
|
47
44
|
parts,
|
|
48
45
|
},
|
|
49
46
|
],
|
|
50
|
-
updatedAt:
|
|
51
|
-
};
|
|
47
|
+
updatedAt: timestamp,
|
|
48
|
+
});
|
|
52
49
|
} else {
|
|
53
50
|
// Handle update message
|
|
54
51
|
const newMessages = [...chat.messages];
|
|
@@ -57,11 +54,11 @@ export const addMessageToChat = (
|
|
|
57
54
|
content,
|
|
58
55
|
parts,
|
|
59
56
|
};
|
|
60
|
-
newChats
|
|
57
|
+
newChats.set(chat.id, {
|
|
61
58
|
...chat,
|
|
62
59
|
messages: newMessages,
|
|
63
|
-
updatedAt:
|
|
64
|
-
};
|
|
60
|
+
updatedAt: timestamp,
|
|
61
|
+
});
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
return {
|
package/src/core/ai/state.ts
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
import type { Message as AIMessage } from "@ai-sdk/react";
|
|
4
4
|
import { atom } from "jotai";
|
|
5
5
|
import { atomWithStorage } from "jotai/utils";
|
|
6
|
+
import { adaptForLocalStorage } from "@/utils/storage";
|
|
7
|
+
import type { TypedString } from "@/utils/typed";
|
|
6
8
|
import type { CellId } from "../cells/ids";
|
|
7
9
|
|
|
8
|
-
const KEY = "marimo:ai:chatState:
|
|
10
|
+
const KEY = "marimo:ai:chatState:v4";
|
|
11
|
+
|
|
12
|
+
export type ChatId = TypedString<"ChatId">;
|
|
9
13
|
|
|
10
14
|
export const aiCompletionCellAtom = atom<{
|
|
11
15
|
cellId: CellId;
|
|
@@ -27,7 +31,7 @@ export interface Message {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export interface Chat {
|
|
30
|
-
id:
|
|
34
|
+
id: ChatId;
|
|
31
35
|
title: string;
|
|
32
36
|
messages: Message[];
|
|
33
37
|
createdAt: number;
|
|
@@ -35,21 +39,37 @@ export interface Chat {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export interface ChatState {
|
|
38
|
-
chats: Chat
|
|
39
|
-
activeChatId:
|
|
42
|
+
chats: Map<ChatId, Chat>;
|
|
43
|
+
activeChatId: ChatId | null;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export const chatStateAtom = atomWithStorage<ChatState>(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
export const chatStateAtom = atomWithStorage<ChatState>(
|
|
47
|
+
KEY,
|
|
48
|
+
{
|
|
49
|
+
chats: new Map(),
|
|
50
|
+
activeChatId: null,
|
|
51
|
+
},
|
|
52
|
+
adaptForLocalStorage({
|
|
53
|
+
toSerializable: (value: ChatState) => ({
|
|
54
|
+
chats: [...value.chats.entries()],
|
|
55
|
+
activeChatId: value.activeChatId,
|
|
56
|
+
}),
|
|
57
|
+
fromSerializable: (value) => ({
|
|
58
|
+
chats: new Map(value.chats),
|
|
59
|
+
activeChatId: value.activeChatId,
|
|
60
|
+
}),
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
46
63
|
|
|
47
64
|
export const activeChatAtom = atom(
|
|
48
65
|
(get) => {
|
|
49
66
|
const state = get(chatStateAtom);
|
|
50
|
-
|
|
67
|
+
if (!state.activeChatId) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return state.chats.get(state.activeChatId);
|
|
51
71
|
},
|
|
52
|
-
(get, set, chatId:
|
|
72
|
+
(get, set, chatId: ChatId | null) => {
|
|
53
73
|
set(chatStateAtom, (prev) => ({
|
|
54
74
|
...prev,
|
|
55
75
|
activeChatId: chatId,
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
type SQLConfig,
|
|
5
|
-
type SQLDialect,
|
|
6
|
-
StandardSQL,
|
|
7
|
-
} from "@codemirror/lang-sql";
|
|
3
|
+
import type { SQLConfig, SQLDialect } from "@codemirror/lang-sql";
|
|
8
4
|
import { isSchemaless } from "@/components/datasources/utils";
|
|
9
5
|
import { dataConnectionsMapAtom } from "@/core/datasets/data-source-connections";
|
|
10
6
|
import type { ConnectionName } from "@/core/datasets/engines";
|
|
@@ -12,7 +8,7 @@ import { datasetTablesAtom } from "@/core/datasets/state";
|
|
|
12
8
|
import type { DataSourceConnection } from "@/core/kernel/messages";
|
|
13
9
|
import { store } from "@/core/state/jotai";
|
|
14
10
|
import { LRUCache } from "@/utils/lru";
|
|
15
|
-
import { guessDialect } from "./utils";
|
|
11
|
+
import { guessDialect, ModifiedStandardSQL } from "./utils";
|
|
16
12
|
|
|
17
13
|
type TableToCols = Record<string, string[]>;
|
|
18
14
|
type Schemas = Record<string, TableToCols>;
|
|
@@ -134,9 +130,9 @@ class SQLCompletionStore {
|
|
|
134
130
|
getDialect(connectionName: ConnectionName): SQLDialect {
|
|
135
131
|
const connection = this.getConnection(connectionName);
|
|
136
132
|
if (!connection) {
|
|
137
|
-
return
|
|
133
|
+
return ModifiedStandardSQL;
|
|
138
134
|
}
|
|
139
|
-
return guessDialect(connection) ??
|
|
135
|
+
return guessDialect(connection) ?? ModifiedStandardSQL;
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
getCompletionSource(connectionName: ConnectionName): SQLConfig | null {
|
|
@@ -160,7 +156,7 @@ class SQLCompletionStore {
|
|
|
160
156
|
const schema = this.cache.getOrCreate(connection);
|
|
161
157
|
|
|
162
158
|
return {
|
|
163
|
-
dialect: guessDialect(connection),
|
|
159
|
+
dialect: guessDialect(connection) ?? ModifiedStandardSQL,
|
|
164
160
|
schema: schema.shouldAddLocalTables
|
|
165
161
|
? { ...schema.schema, ...getTablesMap() }
|
|
166
162
|
: schema.schema,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import type { SQLDialect } from "@codemirror/lang-sql";
|
|
4
3
|
import {
|
|
5
4
|
Cassandra,
|
|
6
5
|
MariaSQL,
|
|
@@ -8,7 +7,10 @@ import {
|
|
|
8
7
|
MySQL,
|
|
9
8
|
PLSQL,
|
|
10
9
|
PostgreSQL,
|
|
10
|
+
SQLDialect,
|
|
11
|
+
type SQLDialectSpec,
|
|
11
12
|
SQLite,
|
|
13
|
+
StandardSQL,
|
|
12
14
|
} from "@codemirror/lang-sql";
|
|
13
15
|
import { DuckDBDialect } from "@marimo-team/codemirror-sql/dialects";
|
|
14
16
|
import type { DataSourceConnection } from "@/core/kernel/messages";
|
|
@@ -40,3 +42,13 @@ export function guessDialect(
|
|
|
40
42
|
return undefined;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
const OpinionatedStandardSQL: SQLDialectSpec = {
|
|
47
|
+
...StandardSQL,
|
|
48
|
+
// Upper-case identifiers do not need to be quoted most of the time
|
|
49
|
+
caseInsensitiveIdentifiers: true,
|
|
50
|
+
// Encase identifiers in single quotes instead of \
|
|
51
|
+
identifierQuotes: "'",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const ModifiedStandardSQL = SQLDialect.define(OpinionatedStandardSQL);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { adaptForLocalStorage } from "../storage";
|
|
5
|
+
|
|
6
|
+
// Mock localStorage
|
|
7
|
+
const localStorageMock = {
|
|
8
|
+
getItem: vi.fn(),
|
|
9
|
+
setItem: vi.fn(),
|
|
10
|
+
removeItem: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
Object.defineProperty(global, "localStorage", {
|
|
14
|
+
value: localStorageMock,
|
|
15
|
+
writable: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Mock Logger
|
|
19
|
+
vi.mock("../Logger", () => ({
|
|
20
|
+
Logger: {
|
|
21
|
+
warn: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
interface TestValue {
|
|
26
|
+
id: string;
|
|
27
|
+
data: Map<string, number>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SerializableValue {
|
|
31
|
+
id: string;
|
|
32
|
+
data: Array<[string, number]>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("adaptForLocalStorage", () => {
|
|
36
|
+
const storage = adaptForLocalStorage<TestValue, SerializableValue>({
|
|
37
|
+
toSerializable: (v) => ({
|
|
38
|
+
id: v.id,
|
|
39
|
+
data: [...v.data.entries()],
|
|
40
|
+
}),
|
|
41
|
+
fromSerializable: (s) => ({
|
|
42
|
+
id: s.id,
|
|
43
|
+
data: new Map(s.data),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const testValue: TestValue = {
|
|
48
|
+
id: "test",
|
|
49
|
+
data: new Map([
|
|
50
|
+
["key1", 1],
|
|
51
|
+
["key2", 2],
|
|
52
|
+
]),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const initialValue: TestValue = {
|
|
56
|
+
id: "initial",
|
|
57
|
+
data: new Map(),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.clearAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("getItem", () => {
|
|
65
|
+
it("should return parsed value when localStorage contains valid data", () => {
|
|
66
|
+
const serialized = JSON.stringify({
|
|
67
|
+
id: "test",
|
|
68
|
+
data: [
|
|
69
|
+
["key1", 1],
|
|
70
|
+
["key2", 2],
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
localStorageMock.getItem.mockReturnValue(serialized);
|
|
74
|
+
|
|
75
|
+
const result = storage.getItem("test-key", initialValue);
|
|
76
|
+
|
|
77
|
+
expect(localStorageMock.getItem).toHaveBeenCalledWith("test-key");
|
|
78
|
+
expect(result.id).toBe("test");
|
|
79
|
+
expect(result.data.get("key1")).toBe(1);
|
|
80
|
+
expect(result.data.get("key2")).toBe(2);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should return initial value when localStorage returns null", () => {
|
|
84
|
+
localStorageMock.getItem.mockReturnValue(null);
|
|
85
|
+
|
|
86
|
+
const result = storage.getItem("test-key", initialValue);
|
|
87
|
+
|
|
88
|
+
expect(result).toBe(initialValue);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should return initial value when localStorage returns empty string", () => {
|
|
92
|
+
localStorageMock.getItem.mockReturnValue("");
|
|
93
|
+
|
|
94
|
+
const result = storage.getItem("test-key", initialValue);
|
|
95
|
+
|
|
96
|
+
expect(result).toBe(initialValue);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should return initial value and log warning when JSON parsing fails", () => {
|
|
100
|
+
localStorageMock.getItem.mockReturnValue("invalid-json");
|
|
101
|
+
|
|
102
|
+
const result = storage.getItem("test-key", initialValue);
|
|
103
|
+
|
|
104
|
+
expect(result).toBe(initialValue);
|
|
105
|
+
// Logger.warn should have been called but we're not testing the exact call
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return initial value and log warning when deserialization fails", () => {
|
|
109
|
+
// This JSON is parseable but will cause the Map constructor to throw
|
|
110
|
+
localStorageMock.getItem.mockReturnValue(
|
|
111
|
+
'{"id": "test", "data": "not-an-array"}',
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const result = storage.getItem("test-key", initialValue);
|
|
115
|
+
|
|
116
|
+
expect(result).toEqual(initialValue);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("setItem", () => {
|
|
121
|
+
it("should serialize and store value in localStorage", () => {
|
|
122
|
+
storage.setItem("test-key", testValue);
|
|
123
|
+
|
|
124
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
125
|
+
"test-key",
|
|
126
|
+
JSON.stringify({
|
|
127
|
+
id: "test",
|
|
128
|
+
data: [
|
|
129
|
+
["key1", 1],
|
|
130
|
+
["key2", 2],
|
|
131
|
+
],
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("removeItem", () => {
|
|
138
|
+
it("should call localStorage.removeItem with the correct key", () => {
|
|
139
|
+
storage.removeItem("test-key");
|
|
140
|
+
|
|
141
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith("test-key");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { SyncStorage } from "jotai/vanilla/utils/atomWithStorage";
|
|
4
|
+
import { Logger } from "./Logger";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Converts a value to and from a serializable format for storage.
|
|
8
|
+
* Useful for data structures that are not natively supported by localStorage (e.g., Maps)
|
|
9
|
+
*
|
|
10
|
+
* @param opts - The options for the storage adapter.
|
|
11
|
+
* @returns - The storage adapter.
|
|
12
|
+
*/
|
|
13
|
+
export function adaptForLocalStorage<Value, Serializable>(opts: {
|
|
14
|
+
toSerializable: (v: Value) => Serializable;
|
|
15
|
+
fromSerializable: (s: Serializable) => Value;
|
|
16
|
+
}): SyncStorage<Value> {
|
|
17
|
+
return {
|
|
18
|
+
getItem(key, initialValue) {
|
|
19
|
+
try {
|
|
20
|
+
const value = localStorage.getItem(key);
|
|
21
|
+
if (!value) {
|
|
22
|
+
return initialValue;
|
|
23
|
+
}
|
|
24
|
+
const parsed = JSON.parse(value) as Serializable;
|
|
25
|
+
return opts.fromSerializable(parsed);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
Logger.warn(`Error getting ${key} from storage`, error);
|
|
28
|
+
return initialValue;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
setItem: (key, value): void => {
|
|
32
|
+
localStorage.setItem(key, JSON.stringify(opts.toSerializable(value)));
|
|
33
|
+
},
|
|
34
|
+
removeItem: (key: string): void => {
|
|
35
|
+
localStorage.removeItem(key);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as m}from"./_baseEach-Ca2O9Wne.js";import{x as s}from"./index-BhSNFJlD.js";function e(r,o){var a=-1,t=s(r)?Array(r.length):[];return m(r,function(n,f,i){t[++a]=o(n,f,i)}),t}export{e as b};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as s,C as o}from"./mermaid-DsKZ0k6o.js";const n=(a,r)=>s.lang.round(o.parse(a)[r]);export{n as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-DMbINMTT.js";import{_ as e}from"./mermaid-DsKZ0k6o.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-BqFTg_51.js";import"./chunk-WVR4S24B-B9Jid-bk.js";import"./chunk-NRVI72HA-CQ-LV0Dy.js";import"./index-BhSNFJlD.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-DMbINMTT.js";import{_ as e}from"./mermaid-DsKZ0k6o.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-BqFTg_51.js";import"./chunk-WVR4S24B-B9Jid-bk.js";import"./chunk-NRVI72HA-CQ-LV0Dy.js";import"./index-BhSNFJlD.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as n}from"./_baseUniq-D4iU71YJ.js";function o(r){return n(r,4)}export{o as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as t,b as a,a as e,S as o}from"./chunk-LXBSTHXV-C6VjboaW.js";import{_ as s}from"./mermaid-DsKZ0k6o.js";import"./transform-B8bpuzxV.js";import"./chunk-WVR4S24B-B9Jid-bk.js";import"./chunk-NRVI72HA-CQ-LV0Dy.js";import"./index-BhSNFJlD.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:e,get db(){return new o(2)},renderer:a,styles:t,init:s(r=>{r.state||(r.state={}),r.state.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|