fastapi-voyager 0.15.0__py3-none-any.whl → 0.15.2__py3-none-any.whl

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.
@@ -1,116 +1,139 @@
1
- import SchemaCodeDisplay from "./component/schema-code-display.js";
2
- import RouteCodeDisplay from "./component/route-code-display.js";
3
- import Demo from "./component/demo.js";
4
- import RenderGraph from "./component/render-graph.js";
5
- import { GraphUI } from "./graph-ui.js";
6
- import { store } from "./store.js";
7
-
8
- const { createApp, onMounted, ref, watch } = window.Vue;
1
+ import SchemaCodeDisplay from "./component/schema-code-display.js"
2
+ import RouteCodeDisplay from "./component/route-code-display.js"
3
+ import Demo from "./component/demo.js"
4
+ import RenderGraph from "./component/render-graph.js"
5
+ import { GraphUI } from "./graph-ui.js"
6
+ import { store } from "./store.js"
7
+
8
+ const { createApp, onMounted, ref, watch } = window.Vue
9
+
10
+ // Load toggle states from localStorage
11
+ function loadToggleState(key, defaultValue = false) {
12
+ if (typeof window === "undefined") return defaultValue
13
+ try {
14
+ const saved = localStorage.getItem(key)
15
+ return saved !== null ? JSON.parse(saved) : defaultValue
16
+ } catch (e) {
17
+ console.warn(`Failed to load ${key} from localStorage`, e)
18
+ return defaultValue
19
+ }
20
+ }
9
21
 
10
22
  const app = createApp({
11
23
  setup() {
12
- let graphUI = null;
13
- const allSchemaOptions = ref([]);
14
- const erDiagramLoading = ref(false);
15
- const erDiagramCache = ref("");
24
+ let graphUI = null
25
+ const allSchemaOptions = ref([])
26
+ const erDiagramLoading = ref(false)
27
+ const erDiagramCache = ref("")
28
+
29
+ // Initialize toggle states from localStorage
30
+ store.state.modeControl.pydanticResolveMetaEnabled = loadToggleState(
31
+ "pydantic_resolve_meta",
32
+ false
33
+ )
34
+ store.state.filter.hidePrimitiveRoute = loadToggleState("hide_primitive", false)
35
+ store.state.filter.brief = loadToggleState("brief_mode", false)
36
+ store.state.filter.showModule = loadToggleState("show_module_cluster", false)
16
37
 
17
38
  function initGraphUI() {
18
39
  if (graphUI) {
19
- return;
40
+ return
20
41
  }
21
42
  graphUI = new GraphUI("#graph", {
22
43
  onSchemaShiftClick: (id) => {
23
44
  if (store.state.graph.schemaKeys.has(id)) {
24
- store.state.previousTagRoute.tag = store.state.leftPanel.tag;
25
- store.state.previousTagRoute.routeId = store.state.leftPanel.routeId;
26
- store.state.previousTagRoute.hasValue = true;
27
- store.state.search.mode = true;
28
- store.state.search.schemaName = id;
29
- onSearch();
45
+ // Only save current tag/route if we're not already in search mode
46
+ // This prevents overwriting the saved state with null values
47
+ if (!store.state.previousTagRoute.hasValue && !store.state.search.mode) {
48
+ store.state.previousTagRoute.tag = store.state.leftPanel.tag
49
+ store.state.previousTagRoute.routeId = store.state.leftPanel.routeId
50
+ store.state.previousTagRoute.hasValue = true
51
+ }
52
+ store.state.search.mode = true
53
+ store.state.search.schemaName = id
54
+ onSearch()
30
55
  }
31
56
  },
32
57
  onSchemaClick: (id) => {
33
- resetDetailPanels();
58
+ resetDetailPanels()
34
59
  if (store.state.graph.schemaKeys.has(id)) {
35
- store.state.schemaDetail.schemaCodeName = id;
36
- store.state.rightDrawer.drawer = true;
60
+ store.state.schemaDetail.schemaCodeName = id
61
+ store.state.rightDrawer.drawer = true
37
62
  }
38
63
  if (id in store.state.graph.routeItems) {
39
- store.state.routeDetail.routeCodeId = id;
40
- store.state.routeDetail.show = true;
64
+ store.state.routeDetail.routeCodeId = id
65
+ store.state.routeDetail.show = true
41
66
  }
42
67
  },
43
68
  resetCb: () => {
44
- resetDetailPanels();
69
+ resetDetailPanels()
45
70
  },
46
- });
71
+ })
47
72
  }
48
73
 
49
74
  function rebuildSchemaOptions() {
50
- const dict = store.state.graph.schemaMap || {};
75
+ const dict = store.state.graph.schemaMap || {}
51
76
  const opts = Object.values(dict).map((s) => ({
52
77
  label: s.name,
53
78
  desc: s.id,
54
79
  value: s.id,
55
- }));
56
- allSchemaOptions.value = opts;
57
- store.state.search.schemaOptions = opts.slice();
58
- populateFieldOptions(store.state.search.schemaName);
80
+ }))
81
+ allSchemaOptions.value = opts
82
+ store.state.search.schemaOptions = opts.slice()
83
+ populateFieldOptions(store.state.search.schemaName)
59
84
  }
60
85
 
61
86
  function populateFieldOptions(schemaId) {
62
87
  if (!schemaId) {
63
- store.state.search.fieldOptions = [];
64
- store.state.search.fieldName = null;
65
- return;
88
+ store.state.search.fieldOptions = []
89
+ store.state.search.fieldName = null
90
+ return
66
91
  }
67
- const schema = store.state.graph.schemaMap?.[schemaId];
92
+ const schema = store.state.graph.schemaMap?.[schemaId]
68
93
  if (!schema) {
69
- store.state.search.fieldOptions = [];
70
- store.state.search.fieldName = null;
71
- return;
72
- }
73
- const fields = Array.isArray(schema.fields)
74
- ? schema.fields.map((f) => f.name)
75
- : [];
76
- store.state.search.fieldOptions = fields;
94
+ store.state.search.fieldOptions = []
95
+ store.state.search.fieldName = null
96
+ return
97
+ }
98
+ const fields = Array.isArray(schema.fields) ? schema.fields.map((f) => f.name) : []
99
+ store.state.search.fieldOptions = fields
77
100
  if (!fields.includes(store.state.search.fieldName)) {
78
- store.state.search.fieldName = null;
101
+ store.state.search.fieldName = null
79
102
  }
80
103
  }
81
104
 
82
105
  function filterSearchSchemas(val, update) {
83
- const needle = (val || "").toLowerCase();
106
+ const needle = (val || "").toLowerCase()
84
107
  update(() => {
85
108
  if (!needle) {
86
- store.state.search.schemaOptions = allSchemaOptions.value.slice();
87
- return;
109
+ store.state.search.schemaOptions = allSchemaOptions.value.slice()
110
+ return
88
111
  }
89
112
  store.state.search.schemaOptions = allSchemaOptions.value.filter((option) =>
90
113
  option.label.toLowerCase().includes(needle)
91
- );
92
- });
114
+ )
115
+ })
93
116
  }
94
117
 
95
118
  function onSearchSchemaChange(val) {
96
- store.state.search.schemaName = val;
97
- store.state.search.mode = false;
119
+ store.state.search.schemaName = val
120
+ store.state.search.mode = false
98
121
  if (!val) {
99
122
  // Clearing the select should only run resetSearch via @clear
100
- return;
123
+ return
101
124
  }
102
- onSearch();
125
+ onSearch()
103
126
  }
104
127
 
105
128
  function readQuerySelection() {
106
129
  if (typeof window === "undefined") {
107
- return { tag: null, route: null };
130
+ return { tag: null, route: null }
108
131
  }
109
- const params = new URLSearchParams(window.location.search);
132
+ const params = new URLSearchParams(window.location.search)
110
133
  return {
111
134
  tag: params.get("tag") || null,
112
135
  route: params.get("route") || null,
113
- };
136
+ }
114
137
  }
115
138
 
116
139
  function findTagByRoute(routeId) {
@@ -118,82 +141,92 @@ const app = createApp({
118
141
  store.state.leftPanel.tags.find((tag) =>
119
142
  (tag.routes || []).some((route) => route.id === routeId)
120
143
  )?.name || null
121
- );
144
+ )
122
145
  }
123
146
 
124
147
  function syncSelectionToUrl() {
125
148
  if (typeof window === "undefined") {
126
- return;
149
+ return
127
150
  }
128
- const params = new URLSearchParams(window.location.search);
151
+ const params = new URLSearchParams(window.location.search)
129
152
  if (store.state.leftPanel.tag) {
130
- params.set("tag", store.state.leftPanel.tag);
153
+ params.set("tag", store.state.leftPanel.tag)
131
154
  } else {
132
- params.delete("tag");
155
+ params.delete("tag")
133
156
  }
134
157
  if (store.state.leftPanel.routeId) {
135
- params.set("route", store.state.leftPanel.routeId);
158
+ params.set("route", store.state.leftPanel.routeId)
136
159
  } else {
137
- params.delete("route");
160
+ params.delete("route")
138
161
  }
139
- const hash = window.location.hash || "";
140
- const search = params.toString();
141
- const base = window.location.pathname;
142
- const newUrl = search ? `${base}?${search}${hash}` : `${base}${hash}`;
143
- window.history.replaceState({}, "", newUrl);
162
+ const hash = window.location.hash || ""
163
+ const search = params.toString()
164
+ const base = window.location.pathname
165
+ const newUrl = search ? `${base}?${search}${hash}` : `${base}${hash}`
166
+ window.history.replaceState({}, "", newUrl)
144
167
  }
145
168
 
146
169
  function applySelectionFromQuery(selection) {
147
- let applied = false;
148
- if (
149
- selection.tag &&
150
- store.state.leftPanel.tags.some((tag) => tag.name === selection.tag)
151
- ) {
152
- store.state.leftPanel.tag = selection.tag;
153
- store.state.leftPanel._tag = selection.tag;
154
- applied = true;
170
+ let applied = false
171
+ if (selection.tag && store.state.leftPanel.tags.some((tag) => tag.name === selection.tag)) {
172
+ store.state.leftPanel.tag = selection.tag
173
+ store.state.leftPanel._tag = selection.tag
174
+ applied = true
155
175
  }
156
176
  if (selection.route && store.state.graph.routeItems?.[selection.route]) {
157
- store.state.leftPanel.routeId = selection.route;
158
- applied = true;
159
- const inferredTag = findTagByRoute(selection.route);
177
+ store.state.leftPanel.routeId = selection.route
178
+ applied = true
179
+ const inferredTag = findTagByRoute(selection.route)
160
180
  if (inferredTag) {
161
- store.state.leftPanel.tag = inferredTag;
162
- store.state.leftPanel._tag = inferredTag;
181
+ store.state.leftPanel.tag = inferredTag
182
+ store.state.leftPanel._tag = inferredTag
163
183
  }
164
184
  }
165
- return applied;
185
+ return applied
166
186
  }
167
187
 
168
188
  async function resetSearch() {
169
- store.state.search.mode = false;
170
- console.log(store.state.previousTagRoute.hasValue)
171
- console.log(store.state.previousTagRoute)
172
- if (store.state.previousTagRoute.hasValue) {
173
- store.state.leftPanel.tag = store.state.previousTagRoute.tag;
174
- store.state.leftPanel._tag = store.state.previousTagRoute.tag;
175
- store.state.leftPanel.routeId = store.state.previousTagRoute.routeId;
176
- store.state.previousTagRoute.hasValue = false;
189
+ store.state.search.mode = false
190
+ const hadPreviousValue = store.state.previousTagRoute.hasValue
191
+
192
+ if (hadPreviousValue) {
193
+ store.state.leftPanel.tag = store.state.previousTagRoute.tag
194
+ store.state.leftPanel._tag = store.state.previousTagRoute.tag
195
+ store.state.leftPanel.routeId = store.state.previousTagRoute.routeId
196
+ store.state.previousTagRoute.hasValue = false
177
197
  } else {
178
- store.state.leftPanel.tag = null;
179
- store.state.leftPanel._tag = null;
180
- store.state.leftPanel.routeId = null;
198
+ store.state.leftPanel.tag = null
199
+ store.state.leftPanel._tag = null
200
+ store.state.leftPanel.routeId = null
181
201
  }
182
202
 
183
203
  syncSelectionToUrl()
184
- await loadSearchedTags();
185
- renderBasedOnInitialPolicy()
204
+
205
+ // Load the full tags from cache (not search results) since we're resetting search
206
+ loadFullTags()
207
+
208
+ // If we restored a previous tag/route, generate with it
209
+ // Otherwise, fall back to initial policy
210
+ if (hadPreviousValue) {
211
+ onGenerate()
212
+ } else {
213
+ renderBasedOnInitialPolicy()
214
+ }
215
+ }
216
+
217
+ function loadFullTags() {
218
+ // Restore from cache (set by loadInitial)
219
+ store.state.leftPanel.tags = store.state.leftPanel.fullTagsCache
186
220
  }
187
221
 
188
222
  async function onSearch() {
189
- console.log('start search')
190
- store.state.search.mode = true;
191
- store.state.leftPanel.tag = null;
192
- store.state.leftPanel._tag = null;
193
- store.state.leftPanel.routeId = null;
223
+ store.state.search.mode = true
224
+ store.state.leftPanel.tag = null
225
+ store.state.leftPanel._tag = null
226
+ store.state.leftPanel.routeId = null
194
227
  syncSelectionToUrl()
195
- await loadSearchedTags();
196
- await onGenerate();
228
+ await loadSearchedTags()
229
+ await onGenerate()
197
230
  }
198
231
  async function loadSearchedTags() {
199
232
  try {
@@ -204,56 +237,58 @@ const app = createApp({
204
237
  brief: store.state.filter.brief,
205
238
  hide_primitive_route: store.state.filter.hidePrimitiveRoute,
206
239
  show_module: store.state.filter.showModule,
207
- };
240
+ }
208
241
  const res = await fetch("dot-search", {
209
242
  method: "POST",
210
243
  headers: { "Content-Type": "application/json" },
211
244
  body: JSON.stringify(payload),
212
- });
245
+ })
213
246
  if (res.ok) {
214
- const data = await res.json();
215
- const tags = Array.isArray(data.tags) ? data.tags : [];
216
- store.state.leftPanel.tags = tags;
247
+ const data = await res.json()
248
+ const tags = Array.isArray(data.tags) ? data.tags : []
249
+ store.state.leftPanel.tags = tags
217
250
  }
218
251
  } catch (err) {
219
- console.error("dot-search failed", err);
252
+ console.error("dot-search failed", err)
220
253
  }
221
254
  }
222
255
 
223
256
  async function loadInitial() {
224
- store.state.initializing = true;
257
+ store.state.initializing = true
225
258
  try {
226
- const res = await fetch("dot");
227
- const data = await res.json();
228
- store.state.leftPanel.tags = Array.isArray(data.tags) ? data.tags : [];
229
-
230
- const schemasArr = Array.isArray(data.schemas) ? data.schemas : [];
259
+ const res = await fetch("dot")
260
+ const data = await res.json()
261
+ const tags = Array.isArray(data.tags) ? data.tags : []
262
+ store.state.leftPanel.tags = tags
263
+ // Cache the full tags for later use (e.g., resetSearch)
264
+ store.state.leftPanel.fullTagsCache = tags
265
+
266
+ const schemasArr = Array.isArray(data.schemas) ? data.schemas : []
231
267
  // Build dict keyed by id for faster lookups and simpler prop passing
232
- const schemaMap = Object.fromEntries(schemasArr.map((s) => [s.id, s]));
233
- store.state.graph.schemaMap = schemaMap;
234
- store.state.graph.schemaKeys = new Set(Object.keys(schemaMap));
268
+ const schemaMap = Object.fromEntries(schemasArr.map((s) => [s.id, s]))
269
+ store.state.graph.schemaMap = schemaMap
270
+ store.state.graph.schemaKeys = new Set(Object.keys(schemaMap))
235
271
  store.state.graph.routeItems = data.tags
236
272
  .map((t) => t.routes)
237
273
  .flat()
238
274
  .reduce((acc, r) => {
239
- acc[r.id] = r;
240
- return acc;
241
- }, {});
242
- store.state.modeControl.briefModeEnabled =
243
- data.enable_brief_mode || false;
244
- store.state.version = data.version || "";
245
- store.state.swagger.url = data.swagger_url || null;
246
- store.state.config.has_er_diagram = data.has_er_diagram || false;
247
- store.state.config.enable_pydantic_resolve_meta = data.enable_pydantic_resolve_meta || false;
248
-
249
- rebuildSchemaOptions();
250
-
251
- const querySelection = readQuerySelection();
252
- const restoredFromQuery = applySelectionFromQuery(querySelection);
275
+ acc[r.id] = r
276
+ return acc
277
+ }, {})
278
+ store.state.modeControl.briefModeEnabled = data.enable_brief_mode || false
279
+ store.state.version = data.version || ""
280
+ store.state.swagger.url = data.swagger_url || null
281
+ store.state.config.has_er_diagram = data.has_er_diagram || false
282
+ store.state.config.enable_pydantic_resolve_meta = data.enable_pydantic_resolve_meta || false
283
+
284
+ rebuildSchemaOptions()
285
+
286
+ const querySelection = readQuerySelection()
287
+ const restoredFromQuery = applySelectionFromQuery(querySelection)
253
288
  if (restoredFromQuery) {
254
- syncSelectionToUrl();
255
- onGenerate();
256
- return;
289
+ syncSelectionToUrl()
290
+ onGenerate()
291
+ return
257
292
  } else {
258
293
  store.state.config.initial_page_policy = data.initial_page_policy
259
294
  renderBasedOnInitialPolicy()
@@ -261,50 +296,44 @@ const app = createApp({
261
296
 
262
297
  // default route options placeholder
263
298
  } catch (e) {
264
- console.error("Initial load failed", e);
299
+ console.error("Initial load failed", e)
265
300
  } finally {
266
- store.state.initializing = false;
301
+ store.state.initializing = false
267
302
  }
268
303
  }
269
304
 
270
305
  async function renderBasedOnInitialPolicy() {
271
- switch (store.state.config.initial_page_policy) {
272
- case "full":
273
- onGenerate();
274
- return;
275
- case "empty":
276
- return;
277
- case "first":
278
- store.state.leftPanel.tag =
279
- store.state.leftPanel.tags.length > 0
280
- ? store.state.leftPanel.tags[0].name
281
- : null;
282
- store.state.leftPanel._tag = store.state.leftPanel.tag;
283
- syncSelectionToUrl()
284
- onGenerate();
285
- return;
286
- }
306
+ switch (store.state.config.initial_page_policy) {
307
+ case "full":
308
+ onGenerate()
309
+ return
310
+ case "empty":
311
+ return
312
+ case "first":
313
+ store.state.leftPanel.tag =
314
+ store.state.leftPanel.tags.length > 0 ? store.state.leftPanel.tags[0].name : null
315
+ store.state.leftPanel._tag = store.state.leftPanel.tag
316
+ syncSelectionToUrl()
317
+ onGenerate()
318
+ return
319
+ }
287
320
  }
288
321
 
289
322
  async function onGenerate(resetZoom = true) {
290
323
  switch (store.state.mode) {
291
324
  case "voyager":
292
- await renderVoyager(resetZoom);
293
- break;
325
+ await renderVoyager(resetZoom)
326
+ break
294
327
  case "er-diagram":
295
- await renderErDiagram(resetZoom);
296
- break;
328
+ await renderErDiagram(resetZoom)
329
+ break
297
330
  }
298
331
  }
299
332
 
300
333
  async function renderVoyager(resetZoom = true) {
301
- const activeSchema = store.state.search.mode
302
- ? store.state.search.schemaName
303
- : null;
304
- const activeField = store.state.search.mode
305
- ? store.state.search.fieldName
306
- : null;
307
- store.state.generating = true;
334
+ const activeSchema = store.state.search.mode ? store.state.search.schemaName : null
335
+ const activeField = store.state.search.mode ? store.state.search.fieldName : null
336
+ store.state.generating = true
308
337
  try {
309
338
  const payload = {
310
339
  tags: store.state.leftPanel.tag ? [store.state.leftPanel.tag] : null,
@@ -315,212 +344,248 @@ const app = createApp({
315
344
  brief: store.state.filter.brief,
316
345
  hide_primitive_route: store.state.filter.hidePrimitiveRoute,
317
346
  show_module: store.state.filter.showModule,
318
- show_pydantic_resolve_meta: store.state.modeControl.pydanticResolveMetaEnabled
319
- };
320
- initGraphUI();
347
+ show_pydantic_resolve_meta: store.state.modeControl.pydanticResolveMetaEnabled,
348
+ }
349
+ initGraphUI()
321
350
  const res = await fetch("dot", {
322
351
  method: "POST",
323
352
  headers: { "Content-Type": "application/json" },
324
353
  body: JSON.stringify(payload),
325
- });
326
- const dotText = await res.text();
354
+ })
355
+ const dotText = await res.text()
327
356
 
328
- await graphUI.render(dotText, resetZoom);
357
+ await graphUI.render(dotText, resetZoom)
329
358
  } catch (e) {
330
- console.error("Generate failed", e);
359
+ console.error("Generate failed", e)
331
360
  } finally {
332
- store.state.generating = false;
361
+ store.state.generating = false
333
362
  }
334
363
  }
335
364
 
336
365
  function resetDetailPanels() {
337
- store.state.rightDrawer.drawer = false;
338
- store.state.routeDetail.show = false;
339
- store.state.schemaDetail.schemaCodeName = "";
366
+ store.state.rightDrawer.drawer = false
367
+ store.state.routeDetail.show = false
368
+ store.state.schemaDetail.schemaCodeName = ""
340
369
  }
341
370
 
342
371
  async function onReset() {
343
- store.state.leftPanel.tag = null;
344
- store.state.leftPanel._tag = null;
345
- store.state.leftPanel.routeId = null;
346
- syncSelectionToUrl()
347
- onGenerate()
372
+ store.state.leftPanel.tag = null
373
+ store.state.leftPanel._tag = null
374
+ store.state.leftPanel.routeId = null
375
+ syncSelectionToUrl()
376
+ onGenerate()
348
377
  }
349
378
 
350
379
  async function togglePydanticResolveMeta(val) {
351
- store.state.modeControl.pydanticResolveMetaEnabled = val;
352
- onGenerate();
380
+ store.state.modeControl.pydanticResolveMetaEnabled = val
381
+ try {
382
+ localStorage.setItem("pydantic_resolve_meta", JSON.stringify(val))
383
+ } catch (e) {
384
+ console.warn("Failed to save pydantic_resolve_meta to localStorage", e)
385
+ }
386
+ onGenerate()
353
387
  }
354
388
 
355
389
  async function renderErDiagram(resetZoom = true) {
356
- initGraphUI();
357
- erDiagramLoading.value = true;
390
+ initGraphUI()
391
+ erDiagramLoading.value = true
358
392
  const payload = {
359
393
  show_fields: store.state.filter.showFields,
360
394
  show_module: store.state.filter.showModule,
361
- };
395
+ }
362
396
  try {
363
- const res = await fetch("er-diagram", {
397
+ const res = await fetch("er-diagram", {
364
398
  method: "POST",
365
399
  headers: { "Content-Type": "application/json" },
366
400
  body: JSON.stringify(payload),
367
- });
401
+ })
368
402
  if (!res.ok) {
369
- throw new Error(`failed with status ${res.status}`);
403
+ throw new Error(`failed with status ${res.status}`)
370
404
  }
371
- const dot = await res.text();
372
- erDiagramCache.value = dot;
373
- await graphUI.render(dot, resetZoom);
405
+ const dot = await res.text()
406
+ erDiagramCache.value = dot
407
+ await graphUI.render(dot, resetZoom)
374
408
  } catch (err) {
375
409
  console.error(err)
376
410
  } finally {
377
- erDiagramLoading.value = false;
411
+ erDiagramLoading.value = false
378
412
  }
379
413
  }
380
414
 
381
415
  async function onModeChange(val) {
382
416
  if (val === "er-diagram") {
383
- // clear search
417
+ // clear search
384
418
  store.state.search.schemaName = null
385
419
  store.state.search.fieldName = null
386
420
  store.state.search.invisible = true
387
421
 
388
422
  if (store.state.leftPanel.width > 0) {
389
- store.state.leftPanel.previousWidth = store.state.leftPanel.width;
423
+ store.state.leftPanel.previousWidth = store.state.leftPanel.width
390
424
  }
391
- store.state.leftPanel.width = 0;
392
- await renderErDiagram();
425
+ store.state.leftPanel.width = 0
426
+ await renderErDiagram()
393
427
  } else {
394
428
  store.state.search.invisible = false
395
-
396
- const fallbackWidth = store.state.leftPanel.previousWidth || 300;
397
- store.state.leftPanel.width = fallbackWidth;
398
- await onGenerate();
429
+
430
+ const fallbackWidth = store.state.leftPanel.previousWidth || 300
431
+ store.state.leftPanel.width = fallbackWidth
432
+ await onGenerate()
399
433
  }
400
434
  }
401
435
 
402
436
  function toggleTag(tagName, expanded = null) {
403
437
  if (expanded === true || store.state.search.mode === true) {
404
- store.state.leftPanel._tag = tagName;
405
- store.state.leftPanel.tag = tagName;
406
- store.state.leftPanel.routeId = "";
438
+ store.state.leftPanel._tag = tagName
439
+ store.state.leftPanel.tag = tagName
440
+ store.state.leftPanel.routeId = ""
407
441
 
408
- store.state.schemaDetail.schemaCodeName = "";
409
- onGenerate();
442
+ store.state.schemaDetail.schemaCodeName = ""
443
+ onGenerate()
410
444
  } else {
411
- store.state.leftPanel._tag = null;
445
+ store.state.leftPanel._tag = null
412
446
  }
413
447
 
414
- store.state.rightDrawer.drawer = false;
415
- store.state.routeDetail.show = false;
416
- syncSelectionToUrl();
448
+ store.state.rightDrawer.drawer = false
449
+ store.state.routeDetail.show = false
450
+ syncSelectionToUrl()
451
+ }
452
+
453
+ function toggleTagNavigatorCollapse() {
454
+ if (store.state.leftPanel.collapsed) {
455
+ // Expand: restore previous width
456
+ const fallbackWidth = store.state.leftPanel.previousWidth || 300
457
+ store.state.leftPanel.width = fallbackWidth
458
+ store.state.leftPanel.collapsed = false
459
+ } else {
460
+ // Collapse: save current width and set to 0
461
+ if (store.state.leftPanel.width > 0) {
462
+ store.state.leftPanel.previousWidth = store.state.leftPanel.width
463
+ }
464
+ store.state.leftPanel.width = 0
465
+ store.state.leftPanel.collapsed = true
466
+ }
417
467
  }
418
468
 
419
469
  function selectRoute(routeId) {
420
470
  // find belonging tag
421
- const belongingTag = findTagByRoute(routeId);
471
+ const belongingTag = findTagByRoute(routeId)
422
472
  if (belongingTag) {
423
- store.state.leftPanel.tag = belongingTag;
424
- store.state.leftPanel._tag = belongingTag;
473
+ store.state.leftPanel.tag = belongingTag
474
+ store.state.leftPanel._tag = belongingTag
425
475
  }
426
476
 
427
477
  if (store.state.leftPanel.routeId === routeId) {
428
- store.state.leftPanel.routeId = "";
478
+ store.state.leftPanel.routeId = ""
429
479
  } else {
430
- store.state.leftPanel.routeId = routeId;
480
+ store.state.leftPanel.routeId = routeId
431
481
  }
432
482
 
433
- store.state.rightDrawer.drawer = false;
434
- store.state.routeDetail.show = false;
435
- store.state.schemaDetail.schemaCodeName = "";
436
- syncSelectionToUrl();
437
- onGenerate();
483
+ store.state.rightDrawer.drawer = false
484
+ store.state.routeDetail.show = false
485
+ store.state.schemaDetail.schemaCodeName = ""
486
+ syncSelectionToUrl()
487
+ onGenerate()
438
488
  }
439
489
 
440
490
  function toggleShowModule(val) {
441
- store.state.filter.showModule = val;
442
- onGenerate();
491
+ store.state.filter.showModule = val
492
+ try {
493
+ localStorage.setItem("show_module_cluster", JSON.stringify(val))
494
+ } catch (e) {
495
+ console.warn("Failed to save show_module_cluster to localStorage", e)
496
+ }
497
+ onGenerate()
443
498
  }
444
499
 
445
500
  function toggleShowField(field) {
446
- store.state.filter.showFields = field;
447
- onGenerate(false);
501
+ store.state.filter.showFields = field
502
+ onGenerate(false)
448
503
  }
449
504
 
450
505
  function toggleBrief(val) {
451
- store.state.filter.brief = val;
452
- onGenerate();
506
+ store.state.filter.brief = val
507
+ try {
508
+ localStorage.setItem("brief_mode", JSON.stringify(val))
509
+ } catch (e) {
510
+ console.warn("Failed to save brief_mode to localStorage", e)
511
+ }
512
+ onGenerate()
453
513
  }
454
514
 
455
515
  function toggleHidePrimitiveRoute(val) {
456
- store.state.filter.hidePrimitiveRoute = val;
457
- onGenerate(false);
516
+ store.state.filter.hidePrimitiveRoute = val
517
+ try {
518
+ localStorage.setItem("hide_primitive", JSON.stringify(val))
519
+ } catch (e) {
520
+ console.warn("Failed to save hide_primitive to localStorage", e)
521
+ }
522
+ onGenerate(false)
458
523
  }
459
524
 
460
525
  function startDragDrawer(e) {
461
- const startX = e.clientX;
462
- const startWidth = store.state.rightDrawer.width;
526
+ const startX = e.clientX
527
+ const startWidth = store.state.rightDrawer.width
463
528
 
464
529
  function onMouseMove(moveEvent) {
465
- const deltaX = startX - moveEvent.clientX;
466
- const newWidth = Math.max(300, Math.min(800, startWidth + deltaX));
467
- store.state.rightDrawer.width = newWidth;
530
+ const deltaX = startX - moveEvent.clientX
531
+ const newWidth = Math.max(300, Math.min(800, startWidth + deltaX))
532
+ store.state.rightDrawer.width = newWidth
468
533
  }
469
534
 
470
535
  function onMouseUp() {
471
- document.removeEventListener("mousemove", onMouseMove);
472
- document.removeEventListener("mouseup", onMouseUp);
473
- document.body.style.cursor = "";
474
- document.body.style.userSelect = "";
536
+ document.removeEventListener("mousemove", onMouseMove)
537
+ document.removeEventListener("mouseup", onMouseUp)
538
+ document.body.style.cursor = ""
539
+ document.body.style.userSelect = ""
475
540
  }
476
541
 
477
- document.addEventListener("mousemove", onMouseMove);
478
- document.addEventListener("mouseup", onMouseUp);
479
- document.body.style.cursor = "col-resize";
480
- document.body.style.userSelect = "none";
481
- e.preventDefault();
542
+ document.addEventListener("mousemove", onMouseMove)
543
+ document.addEventListener("mouseup", onMouseUp)
544
+ document.body.style.cursor = "col-resize"
545
+ document.body.style.userSelect = "none"
546
+ e.preventDefault()
482
547
  }
483
548
 
484
549
  watch(
485
550
  () => store.state.graph.schemaMap,
486
551
  () => {
487
- rebuildSchemaOptions();
552
+ rebuildSchemaOptions()
488
553
  },
489
554
  { deep: false }
490
- );
555
+ )
491
556
 
492
557
  watch(
493
558
  () => store.state.leftPanel.width,
494
559
  (val) => {
495
560
  if (store.state.mode === "voyager" && typeof val === "number" && val > 0) {
496
- store.state.leftPanel.previousWidth = val;
561
+ store.state.leftPanel.previousWidth = val
497
562
  }
498
563
  }
499
- );
564
+ )
500
565
 
501
566
  watch(
502
567
  () => store.state.mode,
503
568
  (mode) => {
504
- onModeChange(mode);
569
+ onModeChange(mode)
505
570
  }
506
- );
571
+ )
507
572
 
508
573
  watch(
509
574
  () => store.state.search.schemaName,
510
575
  (schemaId) => {
511
- store.state.search.schemaOptions = allSchemaOptions.value.slice();
512
- populateFieldOptions(schemaId);
576
+ store.state.search.schemaOptions = allSchemaOptions.value.slice()
577
+ populateFieldOptions(schemaId)
513
578
  if (!schemaId) {
514
- store.state.search.mode = false;
579
+ store.state.search.mode = false
515
580
  }
516
581
  }
517
- );
582
+ )
518
583
 
519
584
  onMounted(async () => {
520
- document.body.classList.remove("app-loading");
521
- await loadInitial();
585
+ document.body.classList.remove("app-loading")
586
+ await loadInitial()
522
587
  // Reveal app content only after initial JS/data is ready
523
- });
588
+ })
524
589
 
525
590
  return {
526
591
  store,
@@ -529,6 +594,7 @@ const app = createApp({
529
594
  filterSearchSchemas,
530
595
  onSearchSchemaChange,
531
596
  toggleTag,
597
+ toggleTagNavigatorCollapse,
532
598
  toggleBrief,
533
599
  toggleHidePrimitiveRoute,
534
600
  selectRoute,
@@ -539,21 +605,21 @@ const app = createApp({
539
605
  toggleShowModule,
540
606
  onModeChange,
541
607
  renderErDiagram,
542
- togglePydanticResolveMeta
543
- };
608
+ togglePydanticResolveMeta,
609
+ }
544
610
  },
545
- });
611
+ })
546
612
 
547
- app.use(window.Quasar);
613
+ app.use(window.Quasar)
548
614
 
549
615
  // Set Quasar primary theme color to green
550
616
  if (window.Quasar && typeof window.Quasar.setCssVar === "function") {
551
- window.Quasar.setCssVar("primary", "#009485");
617
+ window.Quasar.setCssVar("primary", "#009485")
552
618
  }
553
619
 
554
- app.component("schema-code-display", SchemaCodeDisplay); // double click to see node details
555
- app.component("route-code-display", RouteCodeDisplay); // double click to see route details
556
- app.component("render-graph", RenderGraph); // for debug, render pasted dot content
557
- app.component("demo-component", Demo);
620
+ app.component("schema-code-display", SchemaCodeDisplay) // double click to see node details
621
+ app.component("route-code-display", RouteCodeDisplay) // double click to see route details
622
+ app.component("render-graph", RenderGraph) // for debug, render pasted dot content
623
+ app.component("demo-component", Demo)
558
624
 
559
- app.mount("#q-app");
625
+ app.mount("#q-app")