fastapi-voyager 0.10.3__tar.gz → 0.10.5__tar.gz

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.
Files changed (48) hide show
  1. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/PKG-INFO +16 -2
  2. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/README.md +15 -1
  3. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/render.py +1 -1
  4. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/version.py +1 -1
  5. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/schema-field-filter.js +5 -6
  6. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graph-ui.js +56 -25
  7. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/index.html +41 -44
  8. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/vue-main.js +55 -52
  9. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/demo.py +5 -1
  10. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/.gitignore +0 -0
  11. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/.python-version +0 -0
  12. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/LICENSE +0 -0
  13. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/pyproject.toml +0 -0
  14. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/__init__.py +0 -0
  15. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/cli.py +0 -0
  16. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/filter.py +0 -0
  17. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/module.py +0 -0
  18. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/server.py +0 -0
  19. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/type.py +0 -0
  20. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/type_helper.py +0 -0
  21. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/voyager.py +0 -0
  22. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  23. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  24. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  25. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  26. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  27. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  28. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  29. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  30. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  31. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  32. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  33. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  34. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/quasar.min.css +0 -0
  35. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/src/fastapi_voyager/web/quasar.min.js +0 -0
  36. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/__init__.py +0 -0
  37. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/demo_anno.py +0 -0
  38. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/programatic.py +0 -0
  39. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/service/__init__.py +0 -0
  40. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/service/schema.py +0 -0
  41. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_analysis.py +0 -0
  42. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_filter.py +0 -0
  43. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_generic.py +0 -0
  44. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_import.py +0 -0
  45. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_module.py +0 -0
  46. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/tests/test_type_helper.py +0 -0
  47. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/uv.lock +0 -0
  48. {fastapi_voyager-0.10.3 → fastapi_voyager-0.10.5}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.10.3
3
+ Version: 0.10.5
4
4
  Summary: Visualize FastAPI application's routing tree and dependencies
5
5
  Project-URL: Homepage, https://github.com/allmonday/fastapi-voyager
6
6
  Project-URL: Source, https://github.com/allmonday/fastapi-voyager
@@ -32,7 +32,9 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  > This repo is still in early stage, it supports pydantic v2 only
34
34
 
35
- Inspect your API interactively!
35
+ Inspect your API interactively!
36
+
37
+ [visit online demo](https://www.newsyeah.fun/voyager/) of project: [composition oriented development pattern](https://github.com/allmonday/composition-oriented-development-pattern)
36
38
 
37
39
  <p align="center"><img src="./voyager.jpg" alt="" /></p>
38
40
  <p align="center"><a target="_blank" rel="" href="https://www.youtube.com/watch?v=PGlbQq1M-n8"><img src="http://img.youtube.com/vi/PGlbQq1M-n8/0.jpg" alt="" style="max-width: 100%;"></a></p>
@@ -170,6 +172,8 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
170
172
  - [ ] optimize static resource (allow manually config url)
171
173
  - [ ] improve search dialog
172
174
  - [ ] add route/tag list
175
+ - [ ] type alias should not be kept as node instead of compiling to original type
176
+ - [ ] how to correctly handle the generic type ?
173
177
 
174
178
  ### in analysis
175
179
  - [ ] click field to highlight links
@@ -240,6 +244,11 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
240
244
  - [x] fix focus in brief-mode
241
245
  - [x] ui: adjust focus position
242
246
  - [x] refactor naming
247
+ - 0.10.4
248
+ - [x] fix: when focus is on, should ensure changes from other params not broken.
249
+ - 0.10.5
250
+ - [x] double click to show details, and highlight as tomato
251
+
243
252
 
244
253
  #### 0.11
245
254
  - [ ] enable/disable module cluster (to save space)
@@ -248,6 +257,11 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
248
257
  - [ ] support opening route in swagger
249
258
  - config docs path
250
259
  - [ ] add tests
260
+ - [ ] fix: focus should reset zoom
261
+ - [ ] sort field name
262
+ - [ ] set max limit for fields
263
+ - [ ] add info icon alone with schema node
264
+ - [ ] add loading for field detail panel
251
265
 
252
266
  #### 0.12
253
267
  - [ ] integration with pydantic-resolve
@@ -5,7 +5,9 @@
5
5
 
6
6
  > This repo is still in early stage, it supports pydantic v2 only
7
7
 
8
- Inspect your API interactively!
8
+ Inspect your API interactively!
9
+
10
+ [visit online demo](https://www.newsyeah.fun/voyager/) of project: [composition oriented development pattern](https://github.com/allmonday/composition-oriented-development-pattern)
9
11
 
10
12
  <p align="center"><img src="./voyager.jpg" alt="" /></p>
11
13
  <p align="center"><a target="_blank" rel="" href="https://www.youtube.com/watch?v=PGlbQq1M-n8"><img src="http://img.youtube.com/vi/PGlbQq1M-n8/0.jpg" alt="" style="max-width: 100%;"></a></p>
@@ -143,6 +145,8 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
143
145
  - [ ] optimize static resource (allow manually config url)
144
146
  - [ ] improve search dialog
145
147
  - [ ] add route/tag list
148
+ - [ ] type alias should not be kept as node instead of compiling to original type
149
+ - [ ] how to correctly handle the generic type ?
146
150
 
147
151
  ### in analysis
148
152
  - [ ] click field to highlight links
@@ -213,6 +217,11 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
213
217
  - [x] fix focus in brief-mode
214
218
  - [x] ui: adjust focus position
215
219
  - [x] refactor naming
220
+ - 0.10.4
221
+ - [x] fix: when focus is on, should ensure changes from other params not broken.
222
+ - 0.10.5
223
+ - [x] double click to show details, and highlight as tomato
224
+
216
225
 
217
226
  #### 0.11
218
227
  - [ ] enable/disable module cluster (to save space)
@@ -221,6 +230,11 @@ or you can open router_viz.dot with vscode extension `graphviz interactive previ
221
230
  - [ ] support opening route in swagger
222
231
  - config docs path
223
232
  - [ ] add tests
233
+ - [ ] fix: focus should reset zoom
234
+ - [ ] sort field name
235
+ - [ ] set max limit for fields
236
+ - [ ] add info icon alone with schema node
237
+ - [ ] add loading for field detail panel
224
238
 
225
239
  #### 0.12
226
240
  - [ ] integration with pydantic-resolve
@@ -37,7 +37,7 @@ class Renderer:
37
37
  fields_parts.append(field_str)
38
38
 
39
39
  header_color = 'tomato' if node.id == self.schema else '#009485'
40
- header = f"""<tr><td cellpadding="1.5" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font> </td> </tr>"""
40
+ header = f"""<tr><td cellpadding="6" bgcolor="{header_color}" align="center" colspan="1" port="{PK}"> <font color="white"> {node.name} </font></td> </tr>"""
41
41
  field_content = ''.join(fields_parts) if fields_parts else ''
42
42
  return f"""<<table border="1" cellborder="0" cellpadding="0" bgcolor="white"> {header} {field_content} </table>>"""
43
43
 
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.10.3"
2
+ __version__ = "0.10.5"
@@ -42,11 +42,12 @@ export default defineComponent({
42
42
  async function loadSchemas() {
43
43
  // Use externally provided props.schemas dict directly; no network call.
44
44
  state.error = null;
45
- const dict = props.schemas && typeof props.schemas === "object" ? props.schemas : {};
45
+ const dict =
46
+ props.schemas && typeof props.schemas === "object" ? props.schemas : {};
46
47
  // Flatten to array for local operations
47
48
  state.schemas = Object.values(dict);
48
49
  state.schemaOptions = state.schemas.map((s) => ({
49
- label: `${s.name} (${s.id})`,
50
+ label: `${s.name} - ${s.id}`,
50
51
  value: s.id,
51
52
  }));
52
53
  // Maintain compatibility: loadingSchemas flag toggled quickly (no async work)
@@ -57,7 +58,7 @@ export default defineComponent({
57
58
  const needle = (val || "").toLowerCase();
58
59
  update(() => {
59
60
  let opts = state.schemas.map((s) => ({
60
- label: `${s.name} (${s.id})`,
61
+ label: `${s.name} - ${s.id}`,
61
62
  value: s.id,
62
63
  }));
63
64
  if (needle) {
@@ -127,7 +128,6 @@ export default defineComponent({
127
128
  }
128
129
  });
129
130
 
130
-
131
131
  function close() {
132
132
  emit("close");
133
133
  }
@@ -178,13 +178,12 @@ export default defineComponent({
178
178
  :disable="!state.schemaFullname"
179
179
  :loading="state.querying"
180
180
  @click="onQuery" />
181
- </div>
182
181
  <q-btn
183
182
  flat dense round icon="close"
184
183
  aria-label="Close"
185
184
  @click="close"
186
- style="position:absolute; top:6px; right:6px; z-index:11; background:rgba(255,255,255,0.85);"
187
185
  />
186
+ </div>
188
187
  <div v-if="state.error" style="position:absolute; top:52px; left:8px; z-index:10; color:#c10015; font-size:12px;">{{ state.error }}</div>
189
188
  <div id="graph-schema-field" style="width:100%; height:100%; overflow:auto; background:#fafafa"></div>
190
189
  </div>
@@ -57,6 +57,13 @@ export class GraphUI {
57
57
  return $result;
58
58
  }
59
59
 
60
+ highlightSchemaBanner(node) {
61
+ const ele = node.querySelector("polygon[fill='#009485']")
62
+ if (ele) {
63
+ ele.setAttribute('fill', 'tomato');
64
+ }
65
+ }
66
+
60
67
  _init() {
61
68
  const self = this;
62
69
  $(this.selector).graphviz({
@@ -65,6 +72,26 @@ export class GraphUI {
65
72
  ready: function () {
66
73
  self.gv = this;
67
74
 
75
+ self.gv.nodes().dblclick(function (event) {
76
+ event.stopPropagation();
77
+ try {
78
+ self.highlightSchemaBanner(this)
79
+ } catch(e) {
80
+ console.log(e)
81
+ }
82
+ const set = $();
83
+ set.push(this);
84
+ // const obj = { set, direction: "bidirectional" };
85
+ const schemaName = event.currentTarget.dataset.name;
86
+ // self.currentSelection = [obj];
87
+ if (schemaName) {
88
+ try {
89
+ self.options.onSchemaClick(schemaName);
90
+ } catch (e) {
91
+ console.warn("onSchemaClick callback failed", e);
92
+ }
93
+ }
94
+ });
68
95
  self.gv.nodes().click(function (event) {
69
96
  const set = $();
70
97
  set.push(this);
@@ -83,12 +110,10 @@ export class GraphUI {
83
110
  } else {
84
111
  self.currentSelection = [obj];
85
112
  self._highlight();
86
- if (schemaName) {
87
- try {
88
- self.options.onSchemaClick(schemaName);
89
- } catch (e) {
90
- console.warn("onSchemaClick callback failed", e);
91
- }
113
+ try {
114
+ self.options.resetCb();
115
+ } catch (e) {
116
+ console.warn("resetCb callback failed", e);
92
117
  }
93
118
  }
94
119
  });
@@ -107,28 +132,34 @@ export class GraphUI {
107
132
 
108
133
  // svg 背景点击高亮清空
109
134
 
110
- $(document).off('click.graphui').on('click.graphui', function (evt) {
111
- // 如果点击目标不在 graph 容器内,直接退出
112
- const graphContainer = $(self.selector)[0];
113
- if (!graphContainer || !evt.target || !graphContainer.contains(evt.target)) {
114
- return;
115
- }
135
+ $(document)
136
+ .off("click.graphui")
137
+ .on("click.graphui", function (evt) {
138
+ // 如果点击目标不在 graph 容器内,直接退出
139
+ const graphContainer = $(self.selector)[0];
140
+ if (
141
+ !graphContainer ||
142
+ !evt.target ||
143
+ !graphContainer.contains(evt.target)
144
+ ) {
145
+ return;
146
+ }
116
147
 
117
- let isNode = false;
118
- const $nodes = self.gv.nodes();
119
- const node = evt.target.parentNode;
120
- $nodes.each(function () {
121
- if (this === node) {
122
- isNode = true;
148
+ let isNode = false;
149
+ const $nodes = self.gv.nodes();
150
+ const node = evt.target.parentNode;
151
+ $nodes.each(function () {
152
+ if (this === node) {
153
+ isNode = true;
154
+ }
155
+ });
156
+ if (!isNode && self.gv) {
157
+ self.gv.highlight();
158
+ if (self.options.resetCb) {
159
+ self.options.resetCb();
160
+ }
123
161
  }
124
162
  });
125
- if (!isNode && self.gv) {
126
- self.gv.highlight();
127
- if (self.options.resetCb) {
128
- self.options.resetCb();
129
- }
130
- }
131
- });
132
163
 
133
164
  $(document).on("keydown.graphui", function (evt) {
134
165
  if (evt.keyCode === 27 && self.gv) {
@@ -64,7 +64,7 @@
64
64
  <div id="q-app">
65
65
  <q-layout view="hHh lpR fff">
66
66
  <q-header bordered class="bg-primary text-white">
67
- <q-toolbar class="row text-grey-9 bg-white" style="width: 100%">
67
+ <q-toolbar class="row text-grey-9 bg-white" style="width: 100%; border-bottom: 2px solid #009485;">
68
68
  <div
69
69
  class="col-auto text-primary"
70
70
  style="font-size: 18px; font-weight: bold; display: flex; align-items: baseline;"
@@ -86,39 +86,21 @@
86
86
  />
87
87
  </div>
88
88
  <div class="col-auto q-ml-auto">
89
- <q-toggle
90
- v-if="state.enableBriefMode"
91
- class="q-mr-md"
92
- v-model="state.brief"
93
- label="Brief Mode"
94
- @update:model-value="(val) => toggleBrief(val)"
95
- title="skip middle classes, config module_prefix to enable it"
96
- dense
97
- />
98
- </div>
99
- <div class="col-auto">
100
- <q-toggle
101
- v-model="state.hidePrimitiveRoute"
102
- @update:model-value="(val) => toggleHidePrimitiveRoute(val)"
103
- label="Hide Primitive"
104
- title="hide routes return primitive"
105
- class="q-mr-md"
106
- dense
107
- />
108
- </div>
109
- <div class="col-auto">
110
89
  <q-btn
111
90
  outline
112
91
  @click="onReset"
113
92
  title="may be very slow"
114
- class="q-mr-md"
115
- label="Show All"
93
+ icon="border_all"
94
+ flat
95
+ class="q-mr-sm"
96
+ label="Full Graph"
116
97
  />
117
98
  </div>
118
99
  <div class="col-auto">
119
100
  <q-btn
120
101
  outline
121
102
  icon="search"
103
+ flat
122
104
  label="Search"
123
105
  class="q-mr-md"
124
106
  @click="showDialog()"
@@ -131,7 +113,7 @@
131
113
  flat
132
114
  icon="help_outline"
133
115
  aria-label="Help"
134
- class="q-mr-sm"
116
+ style="margin-right: 40px"
135
117
  >
136
118
  <q-tooltip
137
119
  anchor="bottom middle"
@@ -145,18 +127,16 @@
145
127
  <ul>
146
128
  <li>scroll to zoom in/out</li>
147
129
  <li>
148
- click node to check highlight related nodes on the
149
- chart, esc to unselect
130
+ double click node to view details.
150
131
  </li>
151
132
  <li>
152
- shift + click to see schema's dependencies without
153
- unrelated nodes
133
+ shift + click to see schema's dependencies without unrelated nodes.
154
134
  </li>
155
135
  </ul>
156
136
  </div>
157
137
  </q-tooltip>
158
138
  </q-btn>
159
- </div>
139
+ <a href="https://github.com/allmonday/fastapi-voyager" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="52" height="52" viewBox="0 0 250 250" style="fill:#009485; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"/><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"/></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style></div>
160
140
  <!-- <q-toolbar-title class="text-primary row">
161
141
  </q-toolbar-title> -->
162
142
  </q-toolbar>
@@ -167,7 +147,6 @@
167
147
  :width="state.drawerWidth"
168
148
  side="right"
169
149
  style="border-left: 1px solid #888;"
170
- overlay
171
150
  bordered
172
151
  >
173
152
  <!-- 可拖拽的调整栏 -->
@@ -232,14 +211,13 @@
232
211
  content-class="q-pa-none"
233
212
  >
234
213
  <template #header>
235
- <div class="row items-center" style="width: 100%">
236
- <div class="row items-center">
214
+ <div class="" style="white-space: nowrap; width: 100%">
237
215
  <q-icon
216
+ style="vertical-align: top;"
238
217
  class="q-mr-sm"
239
218
  :name="state.tag == tag.name ? 'folder' : 'folder_open'"
240
219
  ></q-icon>
241
- <span>{{ tag.name }} <q-chip class="q-ml-sm" dense>{{ tag.routes.length }}</q-chip></span>
242
- </div>
220
+ <span>{{ tag.name }} <q-chip style="position:relative; top: -1px;" class="q-ml-sm" dense>{{ tag.routes.length }}</q-chip></span>
243
221
  </div>
244
222
  </template>
245
223
  <q-list separator style="overflow: auto; max-height: 60vh;">
@@ -280,15 +258,34 @@
280
258
  <template #after>
281
259
  <div style="position: relative; width: 100%; height: 100%;">
282
260
  <div id="graph" class="adjust-fit"></div>
283
- <q-toggle
284
- v-model="state.focus"
285
- v-show="schemaCodeName"
286
- @update:model-value="val => onFocusChange(val)"
287
- label="Focus"
288
- title="pick a schema and toggle focus on to display related nodes only"
289
- style="position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(255,255,255,0.85); border-radius: 4px; padding: 2px 8px;"
290
- size="sm"
291
- />
261
+ <div style="position: absolute; left: 8px; bottom: 8px; z-index: 10; background: rgba(255,255,255,0.85); border-radius: 4px; padding: 2px 8px;">
262
+ <div>
263
+ <q-toggle
264
+ v-model="state.focus"
265
+ v-show="schemaCodeName"
266
+ @update:model-value="val => onFocusChange(val)"
267
+ label="Focus"
268
+ title="pick a schema and toggle focus on to display related nodes only"
269
+ />
270
+ </div>
271
+ <div>
272
+ <q-toggle
273
+ v-if="state.enableBriefMode"
274
+ v-model="state.brief"
275
+ label="Brief Mode"
276
+ @update:model-value="(val) => toggleBrief(val)"
277
+ title="skip middle classes, config module_prefix to enable it"
278
+ />
279
+ </div>
280
+ <div>
281
+ <q-toggle
282
+ v-model="state.hidePrimitiveRoute"
283
+ @update:model-value="(val) => toggleHidePrimitiveRoute(val)"
284
+ label="Hide Primitive"
285
+ title="hide routes return primitive"
286
+ />
287
+ </div>
288
+ </div>
292
289
  </div>
293
290
  </template>
294
291
  </q-splitter>
@@ -48,6 +48,7 @@ const app = createApp({
48
48
  const schemaCodeName = ref("");
49
49
  const routeCodeId = ref("");
50
50
  const showRouteDetail = ref(false);
51
+ let graphUI = null;
51
52
 
52
53
  function openDetail() {
53
54
  showDetail.value = true;
@@ -88,17 +89,18 @@ const app = createApp({
88
89
 
89
90
  async function onFocusChange(val) {
90
91
  if (val) {
91
- await onGenerate(false, schemaCodeName.value)
92
+ await onGenerate(true); // target could be out of view when switchingfrom big to small
92
93
  } else {
93
- await onGenerate(false, null)
94
+ await onGenerate(false);
94
95
  setTimeout(() => {
95
- const ele = $(`[data-name='${schemaCodeName.value}'] polygon`)
96
- ele.click()
97
- }, 1)
96
+ const ele = $(`[data-name='${schemaCodeName.value}'] polygon`);
97
+ ele.dblclick();
98
+ }, 1);
98
99
  }
99
100
  }
100
101
 
101
- async function onGenerate(resetZoom = true, schema_name = null) {
102
+ async function onGenerate(resetZoom = true) {
103
+ const schema_name = state.focus ? schemaCodeName.value : null;
102
104
  state.generating = true;
103
105
  try {
104
106
  const payload = {
@@ -117,30 +119,31 @@ const app = createApp({
117
119
  const dotText = await res.text();
118
120
 
119
121
  // create graph instance once
120
- const graphUI = new GraphUI("#graph", {
121
- onSchemaShiftClick: (id) => {
122
- if (state.rawSchemas.has(id)) {
123
- resetDetailPanels()
124
- schemaFieldFilterSchema.value = id;
125
- showSchemaFieldFilter.value = true;
126
- }
127
- },
128
- onSchemaClick: (id) => {
129
- resetDetailPanels()
130
- if (state.rawSchemas.has(id)) {
131
- schemaCodeName.value = id;
132
- state.detailDrawer = true;
133
- }
134
- if (id in state.routeItems) {
135
- routeCodeId.value = id;
136
- showRouteDetail.value = true;
137
- }
138
- },
139
- resetCb: () => {
140
- resetDetailPanels()
141
- }
142
- });
143
-
122
+ if (!graphUI) {
123
+ graphUI = new GraphUI("#graph", {
124
+ onSchemaShiftClick: (id) => {
125
+ if (state.rawSchemas.has(id)) {
126
+ resetDetailPanels();
127
+ schemaFieldFilterSchema.value = id;
128
+ showSchemaFieldFilter.value = true;
129
+ }
130
+ },
131
+ onSchemaClick: (id) => {
132
+ resetDetailPanels();
133
+ if (state.rawSchemas.has(id)) {
134
+ schemaCodeName.value = id;
135
+ state.detailDrawer = true;
136
+ }
137
+ if (id in state.routeItems) {
138
+ routeCodeId.value = id;
139
+ showRouteDetail.value = true;
140
+ }
141
+ },
142
+ resetCb: () => {
143
+ resetDetailPanels();
144
+ },
145
+ });
146
+ }
144
147
  await graphUI.render(dotText, resetZoom);
145
148
  } catch (e) {
146
149
  console.error("Generate failed", e);
@@ -212,9 +215,9 @@ const app = createApp({
212
215
  }
213
216
 
214
217
  function resetDetailPanels() {
215
- state.detailDrawer = false
218
+ state.detailDrawer = false;
216
219
  showRouteDetail.value = false;
217
- schemaCodeName.value = ''
220
+ schemaCodeName.value = "";
218
221
  }
219
222
 
220
223
  async function onReset() {
@@ -223,8 +226,8 @@ const app = createApp({
223
226
  state.schemaId = null;
224
227
  // state.showFields = "object";
225
228
  state.brief = false;
226
- state.focus = false
227
- schemaCodeName.value = ''
229
+ state.focus = false;
230
+ schemaCodeName.value = "";
228
231
  onGenerate();
229
232
  }
230
233
 
@@ -233,11 +236,11 @@ const app = createApp({
233
236
  state._tag = tagName;
234
237
  state.tag = tagName;
235
238
  state.routeId = "";
236
- state.focus = false
237
- schemaCodeName.value = ''
239
+ state.focus = false;
240
+ schemaCodeName.value = "";
238
241
  onGenerate();
239
242
  } else {
240
- state._tag = null
243
+ state._tag = null;
241
244
  }
242
245
 
243
246
  state.detailDrawer = false;
@@ -252,8 +255,8 @@ const app = createApp({
252
255
  }
253
256
  state.detailDrawer = false;
254
257
  showRouteDetail.value = false;
255
- state.focus = false
256
- schemaCodeName.value = ''
258
+ state.focus = false;
259
+ schemaCodeName.value = "";
257
260
  onGenerate();
258
261
  }
259
262
 
@@ -275,24 +278,24 @@ const app = createApp({
275
278
  function startDragDrawer(e) {
276
279
  const startX = e.clientX;
277
280
  const startWidth = state.drawerWidth;
278
-
281
+
279
282
  function onMouseMove(moveEvent) {
280
- const deltaX = startX - moveEvent.clientX;
283
+ const deltaX = startX - moveEvent.clientX;
281
284
  const newWidth = Math.max(300, Math.min(800, startWidth + deltaX));
282
285
  state.drawerWidth = newWidth;
283
286
  }
284
-
287
+
285
288
  function onMouseUp() {
286
- document.removeEventListener('mousemove', onMouseMove);
287
- document.removeEventListener('mouseup', onMouseUp);
288
- document.body.style.cursor = '';
289
- document.body.style.userSelect = '';
289
+ document.removeEventListener("mousemove", onMouseMove);
290
+ document.removeEventListener("mouseup", onMouseUp);
291
+ document.body.style.cursor = "";
292
+ document.body.style.userSelect = "";
290
293
  }
291
-
292
- document.addEventListener('mousemove', onMouseMove);
293
- document.addEventListener('mouseup', onMouseUp);
294
- document.body.style.cursor = 'col-resize';
295
- document.body.style.userSelect = 'none';
294
+
295
+ document.addEventListener("mousemove", onMouseMove);
296
+ document.addEventListener("mouseup", onMouseUp);
297
+ document.body.style.cursor = "col-resize";
298
+ document.body.style.userSelect = "none";
296
299
  e.preventDefault();
297
300
  }
298
301
 
@@ -332,7 +335,7 @@ const app = createApp({
332
335
  renderCoreData,
333
336
  toggleShowField,
334
337
  startDragDrawer,
335
- onFocusChange
338
+ onFocusChange,
336
339
  };
337
340
  },
338
341
  });
@@ -98,6 +98,10 @@ def get_page_test_2():
98
98
  def get_page_test_3_long_long_long_name():
99
99
  return True
100
100
 
101
- @app.get("/page_test_4/", tags=['for-page', 'group_b'])
101
+ @app.get("/page_test_4/", tags=['for-page'])
102
+ def get_page_test_3_no_response_model():
103
+ return True
104
+
105
+ @app.get("/page_test_5/", tags=['long_long_long_tag_name', 'group_b'])
102
106
  def get_page_test_3_no_response_model():
103
107
  return True