@pb33f/cowboy-components 0.3.4 → 0.4.0

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 (96) hide show
  1. package/dist/assets/css.worker-CTSQecos.js +84 -0
  2. package/dist/assets/editor.worker-CYC0jP-p.js +12 -0
  3. package/dist/assets/equalizer.worker-CdIiiqfH.js +1 -0
  4. package/dist/assets/html.worker-C1BIaUKh.js +461 -0
  5. package/dist/assets/json.worker-BCyBlh8h.js +49 -0
  6. package/dist/assets/rule-documentation.worker-B7xOWY5M.js +1 -0
  7. package/dist/components/credit-ticker/credit-ticker.css.d.ts +2 -0
  8. package/dist/components/credit-ticker/credit-ticker.css.js +36 -0
  9. package/dist/components/credit-ticker/credit-ticker.d.ts +14 -0
  10. package/dist/components/credit-ticker/credit-ticker.js +88 -0
  11. package/dist/components/editor/editor-breadcrumb.css.d.ts +2 -0
  12. package/dist/components/editor/editor-breadcrumb.css.js +49 -0
  13. package/dist/components/editor/editor-breadcrumb.d.ts +9 -0
  14. package/dist/components/editor/editor-breadcrumb.js +60 -0
  15. package/dist/components/editor/editor.css.d.ts +2 -0
  16. package/dist/components/editor/editor.css.js +62 -0
  17. package/dist/components/editor/editor.d.ts +25 -4
  18. package/dist/components/editor/editor.js +412 -76
  19. package/dist/components/manage-ruleset/manage-ruleset.css.js +5 -0
  20. package/dist/components/manage-ruleset/manage-ruleset.js +8 -6
  21. package/dist/components/model-icon/model-icon.d.ts +7 -0
  22. package/dist/components/model-icon/model-icon.js +56 -7
  23. package/dist/components/model-renderer/clickable-ref.d.ts +2 -2
  24. package/dist/components/model-renderer/clickable-ref.js +5 -4
  25. package/dist/components/model-renderer/header.js +10 -10
  26. package/dist/components/model-renderer/operation.js +2 -2
  27. package/dist/components/model-renderer/rendered-node.css.js +1 -1
  28. package/dist/components/model-renderer/schema.js +1 -1
  29. package/dist/components/model-tree/tree.css.js +1 -0
  30. package/dist/components/model-tree/tree.d.ts +8 -0
  31. package/dist/components/model-tree/tree.js +232 -24
  32. package/dist/components/paginator/paginator.css.js +6 -2
  33. package/dist/components/paginator/paginator.d.ts +1 -0
  34. package/dist/components/paginator/paginator.js +8 -4
  35. package/dist/components/problem-list/details-drawer.css.js +1 -1
  36. package/dist/components/problem-list/problem-item.js +6 -3
  37. package/dist/components/problem-list/problem-label-view-filter.d.ts +2 -1
  38. package/dist/components/problem-list/problem-label-view-filter.js +1 -0
  39. package/dist/components/problem-list/problem-list.d.ts +1 -0
  40. package/dist/components/problem-list/problem-list.js +19 -1
  41. package/dist/components/problem-list/problem-mainview.css.js +1 -1
  42. package/dist/components/problem-list/problem-mainview.js +6 -0
  43. package/dist/components/problem-list/problem-sort-filter.d.ts +2 -1
  44. package/dist/components/problem-list/problem-sort-filter.js +1 -0
  45. package/dist/components/problems-overview/diagnostic-evaluation.css.js +14 -4
  46. package/dist/components/problems-overview/diagnostic-evaluation.js +42 -1
  47. package/dist/components/problems-overview/problems-overview.css.js +1 -0
  48. package/dist/components/the-doctor/nuke-workspace.d.ts +13 -0
  49. package/dist/components/the-doctor/nuke-workspace.js +70 -0
  50. package/dist/components/the-doctor/sparks.d.ts +1 -0
  51. package/dist/components/the-doctor/sparks.js +11 -2
  52. package/dist/components/the-doctor/status-bar.css.js +6 -5
  53. package/dist/components/the-doctor/the-doctor.css.js +117 -16
  54. package/dist/components/the-doctor/the-doctor.d.ts +70 -21
  55. package/dist/components/the-doctor/the-doctor.js +1026 -169
  56. package/dist/components/the-doctor/upload-archive.css.d.ts +2 -0
  57. package/dist/components/the-doctor/upload-archive.css.js +98 -0
  58. package/dist/components/the-doctor/upload-archive.d.ts +33 -0
  59. package/dist/components/the-doctor/upload-archive.js +281 -0
  60. package/dist/components/visualizer/equalizer.d.ts +3 -1
  61. package/dist/components/visualizer/equalizer.js +55 -31
  62. package/dist/components/visualizer/explorer.js +6 -5
  63. package/dist/cowboy-components.umd.cjs +1846 -1273
  64. package/dist/css/hr.css.d.ts +2 -0
  65. package/dist/css/hr.css.js +12 -0
  66. package/dist/css/pb33f-theme.css +1 -0
  67. package/dist/events/doctor.d.ts +19 -1
  68. package/dist/events/doctor.js +5 -0
  69. package/dist/model/channels.d.ts +1 -0
  70. package/dist/model/channels.js +1 -0
  71. package/dist/model/graph.d.ts +17 -0
  72. package/dist/model/graph.js +17 -1
  73. package/dist/model/link.d.ts +8 -0
  74. package/dist/model/node_type.d.ts +22 -1
  75. package/dist/model/node_type.js +22 -0
  76. package/dist/model/panel-state.d.ts +5 -0
  77. package/dist/model/panel-state.js +1 -0
  78. package/dist/model/problem.d.ts +3 -1
  79. package/dist/model/problem.js +4 -2
  80. package/dist/model/rolodex.d.ts +34 -0
  81. package/dist/model/rolodex.js +1 -0
  82. package/dist/services/linting-service.d.ts +1 -1
  83. package/dist/services/linting-service.js +23 -11
  84. package/dist/services/model-service.d.ts +8 -1
  85. package/dist/services/model-service.js +127 -0
  86. package/dist/style.css +1 -1
  87. package/dist/workers/equalizer.worker.js +4 -3
  88. package/dist/workers/rule-documentation.worker.d.ts +2 -2
  89. package/dist/workers/rule-documentation.worker.js +26 -15
  90. package/package.json +3 -3
  91. package/dist/assets/css.worker-B_qZXUzt.js +0 -84
  92. package/dist/assets/editor.worker-HEmB0D7P.js +0 -11
  93. package/dist/assets/equalizer.worker-icLzyXQ7.js +0 -1
  94. package/dist/assets/html.worker-D3WUrk8Q.js +0 -458
  95. package/dist/assets/json.worker-CAhUaBo4.js +0 -42
  96. package/dist/assets/rule-documentation.worker-D39NS8Lx.js +0 -1
@@ -16,7 +16,7 @@ import '@shoelace-style/shoelace/dist/components/avatar/avatar.js';
16
16
  import { customElement, property, query, state } from "lit/decorators.js";
17
17
  import { html, LitElement } from "lit";
18
18
  import { SpecEditor } from "../editor/editor.js";
19
- import { ActiveView, AddToast, CustomRulesetEnabled, EditorClicked, EditorUpdated, ExportRuleset, OpenProblemDrawer, ProblemClicked, ProblemRuleFilterChangedManual, RuleViolationClicked, RulesetSaved, RuleClicked, ModelTreeNodeClicked, BuiltInRulesetChanged, ExplorerNodeClicked, NodeReferenceClicked, ExplorerEqualizerChanged, ExplorerEqualizerFiltered, } from "../../events/doctor.js";
19
+ import { ActiveView, AddToast, ArchiveURLRequested, BuiltInRulesetChanged, CustomRulesetEnabled, DocumentReferenceClicked, EditorClicked, EditorUpdated, ExplorerEqualizerChanged, ExplorerEqualizerFiltered, ExplorerNodeClicked, ExportRuleset, ModelTreeNodeClicked, NodeReferenceClicked, OpenProblemDrawer, ProblemClicked, ProblemRuleFilterChangedManual, RolodexRootFileSelected, RolodexTreeNodeClicked, RuleClicked, RulesetSaved, RuleViolationClicked, NukeWorkspaceEvent } from "../../events/doctor.js";
20
20
  import { ProblemDetailsDrawer, ProblemDrawerEventType } from "../problem-list/details-drawer.js";
21
21
  import { CreateBagManager } from "@pb33f/saddlebag";
22
22
  import { LintingService } from "../../services/linting-service.js";
@@ -40,19 +40,30 @@ import buttonCss from "../../css/button.css.js";
40
40
  import radioGroupsCss from "../../css/radiogroups.css.js";
41
41
  import { MarkerSeverity } from "monaco-editor";
42
42
  import { ModelService } from "../../services/model-service.js";
43
+ import { SearchNodeTreeForPath } from "../../model/graph.js";
43
44
  import { ModelTree } from "../model-tree/tree.js";
44
45
  import { ExplorerComponent } from "../visualizer/explorer.js";
45
46
  import { RenderedNodeComponent } from "../model-renderer/rendered-node.js";
46
47
  import tabsCss from "../../css/tabs.css.js";
47
48
  import { CreateBus } from "@pb33f/ranch";
48
- import { Command, DoctorServiceChannel, isBrokerResponse, QueuePrefix, SpecStreamChannel } from "../../model/channels";
49
+ import { Command, CreditStreamChannel, DoctorServiceChannel, isBrokerResponse, QueuePrefix, SpecStreamChannel, } from "../../model/channels";
49
50
  import formsCss from "../../css/forms.css";
50
- import { PixelSparks } from "./sparks.js";
51
51
  import spinnerCss from "../../css/spinner.css";
52
+ import { UploadArchiveComponent } from "./upload-archive";
53
+ import { NodeType } from "../../model/node_type";
54
+ import tooltipCss from "../../css/tooltip.css";
55
+ import { NukeWorkspaceComponent } from "./nuke-workspace";
56
+ import { CreditTicker } from "../credit-ticker/credit-ticker";
52
57
  export const GraphBag = "pb33f-doctor-graph";
58
+ export const PanelStateBag = "pb33f-doctor-panel-state";
59
+ export const RolodexResponseBag = "pb33f-doctor-rolodex-response";
60
+ export const RolodexFilesBag = "pb33f-doctor-rolodex-files";
61
+ export const RolodexStateBag = "pb33f-doctor-rolodex-state";
62
+ export const ReferenceMapBag = "pb33f-doctor-reference-map";
53
63
  export const DoctorDocumentBag = "pb33f-doctor-editor";
54
64
  export const HowToFixBag = "pb33f-doctor-howtofix";
55
65
  export const FunctionDocumentationBag = "pb33f-doctor-funcdocs";
66
+ export const DocumentationExpirationBag = "pb33f-doctor-doc-expiration";
56
67
  export const RuleDocumentationBag = "pb33f-doctor-ruledocs";
57
68
  export const DiagnosticBag = "pb33f-doctor-diagnostic";
58
69
  export const DefaultRulesetBag = "pb33f-doctor-default-ruleset";
@@ -63,8 +74,8 @@ export const FunctionsSchemaBag = "pb33f-doctor-function-schema";
63
74
  export const CustomRulesetBag = "pb33f-doctor-custom-ruleset";
64
75
  export const RuleConfigurationBag = "pb33f-doctor-rule-configuration";
65
76
  export const SessionRulesetMapBag = "pb33f-doctor-session-rulesetmap";
66
- export const DefaultDocument = "document";
67
- export const DocumentProblems = "problems";
77
+ export const DefaultDocument = "pb33f-doctor-document";
78
+ export const DocumentProblems = "pb33f-doctor-problems";
68
79
  export const DoctorEndpoint = "doctor-endpoint";
69
80
  let TheDoctor = class TheDoctor extends LitElement {
70
81
  constructor(doctorEndpoint = "https://doctor.pb33f.io") {
@@ -73,19 +84,27 @@ let TheDoctor = class TheDoctor extends LitElement {
73
84
  this.debounceTimeRuleset = 900;
74
85
  this.bounceId = 0;
75
86
  this.useTLS = false;
87
+ // rolodex divider state (because it may not exist)
88
+ this.rolodexDividerPosition = 40;
89
+ this._firstRun = true;
76
90
  // bus it up
77
91
  this.bus = CreateBus();
78
92
  this.doctorServiceChannel = this.bus.createChannel(DoctorServiceChannel);
79
93
  this.specStreamChannel = this.bus.createChannel(SpecStreamChannel);
94
+ this.creditStreamChannel = this.bus.createChannel(CreditStreamChannel);
80
95
  this.bus.mapChannelToBrokerDestination(QueuePrefix + DoctorServiceChannel, DoctorServiceChannel);
81
96
  this.bus.mapChannelToBrokerDestination(QueuePrefix + SpecStreamChannel, SpecStreamChannel);
97
+ this.bus.mapChannelToBrokerDestination(QueuePrefix + CreditStreamChannel, CreditStreamChannel);
82
98
  this.doctorChannelSubscription = this.doctorServiceChannel.subscribe(this.doctorServiceHandler());
83
99
  this.specChannelSubscription = this.specStreamChannel.subscribe(this.specStreamHandler());
100
+ this.creditChannelSubscription = this.creditStreamChannel.subscribe(this.creditStreamHandler());
84
101
  // create a stateful bag manager
85
102
  this.bagManager = CreateBagManager(true);
86
103
  this.bagManager.loadStatefulBags().then(this.loadState.bind(this));
87
104
  this.editor = new SpecEditor();
88
- this.rulesetEditor = new SpecEditor('ruleset');
105
+ this.uploadArchive = new UploadArchiveComponent();
106
+ this.nukeWorkspace = new NukeWorkspaceComponent();
107
+ this.rulesetEditor = new SpecEditor('ruleset-editor');
89
108
  this.problemList = new ProblemList();
90
109
  this.detailsDrawer = new ProblemDetailsDrawer();
91
110
  this.problemsOverview = new ProblemsOverview();
@@ -97,21 +116,32 @@ let TheDoctor = class TheDoctor extends LitElement {
97
116
  this.activitySpinner = new ActivitySpinner();
98
117
  this.manageRuleset = new ManageRuleset();
99
118
  this.toastManager = new ToastManager();
119
+ this.creditTicker = new CreditTicker();
100
120
  this.editorMap = new Map();
101
121
  this.editorMap.set("spec", this.editor);
102
- this.editorMap.set("ruleset", this.rulesetEditor);
122
+ this.editorMap.set("ruleset-editor", this.rulesetEditor);
103
123
  this.selectedEditorTab = "spec";
104
124
  this.sidebarClosed = false;
105
125
  this.explorerVisible = false;
106
126
  this.explorerBooted = false;
127
+ this.rolodexNeedsReset = false;
128
+ this.rolodexProblemMap = new Map();
129
+ this.rolodexRootHash = "";
130
+ this.rolodexRootPath = "";
131
+ this.importDisabled = false;
132
+ this.pendingLine = 0;
107
133
  this.nodeMap = new Map();
108
134
  this.nodeIdMap = new Map();
109
135
  this.nodeIdHashMap = new Map();
110
136
  this.renderedNodeMap = new Map();
111
137
  this.filteredNodes = new Map();
112
138
  this.modelTree = new ModelTree(this.nodeMap, this.nodeIdMap, this.nodeIdHashMap);
139
+ this.rolodexTree = new ModelTree(this.nodeMap, this.nodeIdMap, this.nodeIdHashMap);
140
+ this.rolodexActiveHash = "";
113
141
  this.explorer = new ExplorerComponent();
114
142
  this.renderedNode = new RenderedNodeComponent();
143
+ this.references = [];
144
+ this.minimapVisible = true;
115
145
  this.nodeLimit = 150;
116
146
  // extract the doctor endpoint from session storage.
117
147
  const sessionEndpoint = sessionStorage.getItem(DoctorEndpoint);
@@ -122,7 +152,7 @@ let TheDoctor = class TheDoctor extends LitElement {
122
152
  this.doctorEndpoint = doctorEndpoint;
123
153
  }
124
154
  // TODO: make this configurable
125
- this.editor.language = "yaml";
155
+ //this.editor.language = "yaml";
126
156
  //@ts-ignore
127
157
  this.addEventListener(EditorUpdated, this.specChanged);
128
158
  //@ts-ignore
@@ -147,23 +177,32 @@ let TheDoctor = class TheDoctor extends LitElement {
147
177
  // @ts-ignore
148
178
  this.addEventListener(ModelTreeNodeClicked, this.modelTreeNodeClicked);
149
179
  // @ts-ignore
180
+ this.addEventListener(RolodexTreeNodeClicked, this.rolodexTreeNodeClicked);
181
+ // @ts-ignore
182
+ this.addEventListener(RolodexRootFileSelected, this.rolodexRootFileSelected);
183
+ // @ts-ignore
150
184
  this.addEventListener(NodeReferenceClicked, this.explorerReferenceClicked);
151
185
  // @ts-ignore
152
186
  this.addEventListener(ExplorerNodeClicked, this.explorerNodeClicked.bind(this));
187
+ // @ts-ignore
188
+ this.addEventListener(ArchiveURLRequested, this.fetchUrl.bind(this));
189
+ // @ts-ignore
190
+ this.addEventListener(DocumentReferenceClicked, this.documentReferenceClicked.bind(this));
153
191
  //@ts-ignore
154
192
  this.explorer.equalizer.addEventListener(ExplorerEqualizerChanged, this.filterTreeModel.bind(this));
155
193
  //@ts-ignore
156
194
  this.explorer.equalizer.addEventListener(ExplorerEqualizerFiltered, this.filterTreeModel.bind(this));
195
+ this.nukeWorkspace.addEventListener(NukeWorkspaceEvent, this.nukeWorkspaceHandler.bind(this));
157
196
  // extract port from session storage.
158
- this.busPort = localStorage.getItem("pb33f-doctor-port");
159
- this.busHost = localStorage.getItem("pb33f-doctor-host");
197
+ this.busPort = sessionStorage.getItem("pb33f-doctor-port");
198
+ this.busHost = sessionStorage.getItem("pb33f-doctor-host");
160
199
  if (!this.busPort) {
161
- this.busPort = "9090"; // default port
200
+ this.busPort = "443"; // default port
162
201
  }
163
202
  if (!this.busHost) {
164
- this.busHost = "localhost"; // default host
203
+ this.busHost = "https://doctor.pb33f.io"; // default host
165
204
  }
166
- const useTLS = localStorage.getItem("pb33f-doctor-tls");
205
+ const useTLS = sessionStorage.getItem("pb33f-doctor-tls");
167
206
  if (useTLS && useTLS == 'true') {
168
207
  this.useTLS = true;
169
208
  }
@@ -179,9 +218,33 @@ let TheDoctor = class TheDoctor extends LitElement {
179
218
  }
180
219
  }));
181
220
  }
221
+ if (state.ref) {
222
+ this.documentReferenceClicked(new CustomEvent(DocumentReferenceClicked, {
223
+ detail: {
224
+ jsonPath: state.ref,
225
+ noState: true
226
+ }
227
+ }));
228
+ }
182
229
  }
183
230
  });
184
231
  }
232
+ nukeWorkspaceHandler() {
233
+ this.bagManager.resetBags();
234
+ ModelService.resetWorkspace().then(() => {
235
+ window.location.reload();
236
+ });
237
+ }
238
+ minimapToggled() {
239
+ this.minimapVisible = !this.minimapVisible;
240
+ this.editor.toggleMinimap();
241
+ this.rulesetEditor.toggleMinimap();
242
+ }
243
+ firstUpdated() {
244
+ // https://github.com/microsoft/monaco-editor/issues/3409
245
+ this.editor = this.querySelector('pb33f-editor#spec-editor');
246
+ this.rulesetEditor = this.querySelector('pb33f-editor#ruleset-editor');
247
+ }
185
248
  whoAmI() {
186
249
  this.bus.publish({
187
250
  destination: "/p/q/" + DoctorServiceChannel,
@@ -203,23 +266,31 @@ let TheDoctor = class TheDoctor extends LitElement {
203
266
  this.whoAmI();
204
267
  }
205
268
  };
269
+ // TODO re-enable
206
270
  this.bus.connectToBroker(config);
271
+ //this.startTheDoctor()
207
272
  }
208
273
  addClickTrack(node) {
209
274
  history.pushState({ activeNode: node.idHash }, "", `?view=explore&node=${node.idHash}`);
210
275
  }
276
+ addRefTrack(ref) {
277
+ history.pushState({ ref: ref }, "", `?view=spec&ref=${ref}`);
278
+ }
211
279
  doctorServiceHandler() {
212
280
  return (msg) => {
213
281
  if (msg.payload?.payload != null) {
214
282
  if (isBrokerResponse(msg.payload.payload)) {
215
283
  this.brokerConnectionId = msg.payload.payload.broker;
216
- console.log("💊 Welcome patient %c" + this.brokerConnectionId, 'color: #62C4FFFF; font-weight: bold');
217
- this.boostrap();
218
- this.loadingOverlay.hide();
284
+ console.log("💊 Welcome to the clinic, the %cdoctor %cis ready to see you.", 'color: #62C4FFFF; font-weight: bold', 'color: default');
285
+ this.startTheDoctor();
219
286
  }
220
287
  }
221
288
  };
222
289
  }
290
+ startTheDoctor() {
291
+ this.boostrap();
292
+ this.loadingOverlay.hide();
293
+ }
223
294
  specStreamHandler() {
224
295
  return (msg) => {
225
296
  if (msg.payload?.payload != null) {
@@ -233,6 +304,13 @@ let TheDoctor = class TheDoctor extends LitElement {
233
304
  }
234
305
  };
235
306
  }
307
+ creditStreamHandler() {
308
+ return (msg) => {
309
+ if (msg.payload?.payload != null) {
310
+ this.creditTicker.credits = parseInt(msg.payload.payload);
311
+ }
312
+ };
313
+ }
236
314
  filterTreeModel(event) {
237
315
  this.filteredNodes = new Map();
238
316
  event.detail.graph.nodes.forEach((node) => {
@@ -241,6 +319,76 @@ let TheDoctor = class TheDoctor extends LitElement {
241
319
  this.modelTree.filteredNodes = this.filteredNodes;
242
320
  this.modelTree.renderedNodes = this.renderedNodeMap;
243
321
  }
322
+ documentReferenceClicked(evt) {
323
+ let location = '';
324
+ let line, col = 1;
325
+ let path = evt.detail.jsonPath;
326
+ let fullPath = '';
327
+ let file = '';
328
+ let rolodexId = '';
329
+ if (evt.detail.jsonPath.includes('||')) {
330
+ location = evt.detail.jsonPath.split('||')[1];
331
+ path = evt.detail.jsonPath.split('||')[0];
332
+ // location is formatted line:col-endCol
333
+ line = parseInt(location.split(':')[0]);
334
+ col = parseInt(location.split(':')[1]);
335
+ fullPath = evt.detail.jsonPath.split('||')[2];
336
+ if (fullPath != '') {
337
+ file = fullPath.split('#')[0];
338
+ }
339
+ rolodexId = evt.detail.jsonPath.split('||')[3];
340
+ }
341
+ this.editor.editor?.setPosition({ lineNumber: line, column: col });
342
+ this.editor.editor?.revealLineInCenter(line);
343
+ if (this.nodeIdMap.has(path)) {
344
+ const node = this.nodeIdMap.get(path);
345
+ if (node) {
346
+ if (this.nodeIdHashMap.has(node.idHash)) {
347
+ const renderedNode = this.renderedNodeMap.get(node.idHash);
348
+ if (renderedNode) {
349
+ this.renderedNode.node = renderedNode;
350
+ this.modelTree.explorerClicked(node.idHash);
351
+ // add a ref click
352
+ if (!evt.detail.noState) {
353
+ this.addRefTrack(evt.detail.jsonPath);
354
+ }
355
+ }
356
+ }
357
+ }
358
+ else {
359
+ this.toastManager.addToastManually({
360
+ id: crypto.randomUUID(),
361
+ type: ToastType.INFO,
362
+ title: "Reference not found",
363
+ body: `Something went wrong, '${path}' not found`
364
+ });
365
+ }
366
+ }
367
+ else {
368
+ if (this.rolodexActivePath != undefined && this.rolodexActivePath != file) {
369
+ ModelService.queryRolodex(rolodexId, file).then((result) => {
370
+ this.editor.showBreadcrumb = true;
371
+ this.rolodexActivePath = file;
372
+ this.editor.clearDecorations();
373
+ this.editor.setValue(result.rolodexRoot.instance, true);
374
+ this.editor.editor?.setPosition({ lineNumber: line, column: 1 });
375
+ this.editor.editor?.revealLineInCenter(line);
376
+ this.rolodexTree.explorerClicked(rolodexId);
377
+ this.editor.setCurrentPath(file);
378
+ this.lintSpec(result.rolodexRoot.instance);
379
+ // apply problems
380
+ if (this.rolodexProblemMap.has(this.rolodexActivePath)) {
381
+ const probs = this.rolodexProblemMap.get(this.rolodexActivePath);
382
+ if (probs) {
383
+ this.editor.setMarkers(probs);
384
+ }
385
+ }
386
+ }).catch((e) => {
387
+ console.error('rolodex query failed', e);
388
+ });
389
+ }
390
+ }
391
+ }
244
392
  explorerReferenceClicked(evt) {
245
393
  let hashId = '';
246
394
  this.explorer.nodeComponents.forEach((node) => {
@@ -267,10 +415,189 @@ let TheDoctor = class TheDoctor extends LitElement {
267
415
  this.explorer.requestUpdate();
268
416
  this.viewerPanel.click();
269
417
  }
418
+ if (evt.detail.nodePath) {
419
+ let p = evt.detail.nodePath;
420
+ if (!p.startsWith('/') && !p.includes('#')) {
421
+ const dir = this.rolodexRootPath.substring(0, this.rolodexRootPath.lastIndexOf('/') + 1);
422
+ p = dir + p;
423
+ }
424
+ if (!p.startsWith('#/')) {
425
+ this.rolodexTreeNodeClicked(new CustomEvent(RolodexTreeNodeClicked, {
426
+ detail: {
427
+ path: p,
428
+ }
429
+ }));
430
+ }
431
+ }
432
+ }
433
+ rolodexTreeNodeClicked(evt) {
434
+ const fileBag = this.rolodexFilesBag?.get(RolodexFilesBag);
435
+ const handleRolodexResponse = (result) => {
436
+ this.rolodexActivePath = evt.detail.path;
437
+ this.rolodexActiveHash = evt.detail.nodeHashId;
438
+ this.editor.setValue(result.instance, true);
439
+ this.editor.setCurrentPath(this.rolodexActivePath);
440
+ const probs = this.rolodexProblemMap.get(this.rolodexActivePath);
441
+ if (probs) {
442
+ this.editor.setMarkers(probs);
443
+ }
444
+ // set the editor file type
445
+ let language = 'yaml';
446
+ const ext = evt.detail?.path?.split('.').pop();
447
+ if (ext) {
448
+ switch (ext) {
449
+ case NodeType.JSON:
450
+ language = NodeType.JSON;
451
+ break;
452
+ case NodeType.GO:
453
+ language = NodeType.GO;
454
+ break;
455
+ case NodeType.PY:
456
+ language = 'python';
457
+ break;
458
+ case NodeType.JS:
459
+ language = 'javascript';
460
+ break;
461
+ case NodeType.TS:
462
+ language = 'typescript';
463
+ break;
464
+ case NodeType.PHP:
465
+ language = NodeType.PHP;
466
+ break;
467
+ case NodeType.XML:
468
+ language = NodeType.XML;
469
+ break;
470
+ case NodeType.JAVA:
471
+ language = NodeType.JAVA;
472
+ break;
473
+ case NodeType.RB:
474
+ language = 'ruby';
475
+ break;
476
+ case NodeType.RS:
477
+ language = 'rust';
478
+ break;
479
+ case NodeType.C:
480
+ language = NodeType.C;
481
+ break;
482
+ case NodeType.CPP:
483
+ language = NodeType.CPP;
484
+ break;
485
+ case NodeType.CS:
486
+ language = 'csharp';
487
+ break;
488
+ case NodeType.MD:
489
+ language = 'markdown';
490
+ break;
491
+ }
492
+ }
493
+ const existingState = this.rolodexStateBag?.get(RolodexStateBag);
494
+ if (existingState) {
495
+ existingState.activePath = evt.detail.path;
496
+ existingState.activeHash = evt.detail.nodeHashId;
497
+ existingState.activeLanguage = language;
498
+ existingState.activeNode = result;
499
+ existingState.rootPath = this.rolodexRootPath;
500
+ this.rolodexStateBag?.set(RolodexStateBag, existingState);
501
+ }
502
+ else {
503
+ const existingState = {
504
+ activePath: evt.detail.path,
505
+ activeHash: evt.detail.nodeHashId,
506
+ activeLanguage: language,
507
+ activeNode: result,
508
+ rootHash: this.rolodexRootHash,
509
+ rootPath: this.rolodexRootPath
510
+ };
511
+ this.rolodexStateBag?.set(RolodexStateBag, existingState);
512
+ }
513
+ this.editor.switchLanguage(language);
514
+ let line = 1;
515
+ let col = 0;
516
+ if (evt.detail.line) {
517
+ line = evt.detail.line;
518
+ }
519
+ if (evt.detail.column) {
520
+ col = evt.detail.column;
521
+ }
522
+ this.editor.editor?.setPosition({ lineNumber: line, column: col });
523
+ this.editor.editor?.revealLineInCenter(line, col);
524
+ this.lintSpec(result.instance);
525
+ if (evt.detail.path) {
526
+ this.rolodexTree.openNodeByPath(evt.detail.path);
527
+ }
528
+ return language;
529
+ };
530
+ ModelService.queryRolodex(evt.detail.nodeHashId, evt.detail.path).then((result) => {
531
+ this.editor.showBreadcrumb = true;
532
+ // clear all references
533
+ this.referenceMapBag?.reset();
534
+ handleRolodexResponse(result.rolodexRoot);
535
+ if (evt.detail.path) {
536
+ if (fileBag && !fileBag.files[evt.detail.path]) {
537
+ fileBag.files[evt.detail.path] = result.rolodexRoot;
538
+ this.rolodexFilesBag?.set(RolodexFilesBag, fileBag);
539
+ }
540
+ if (!fileBag) {
541
+ const rf = {
542
+ files: { [evt.detail.path]: result.rolodexRoot }
543
+ };
544
+ this.rolodexFilesBag?.set(RolodexFilesBag, rf);
545
+ }
546
+ }
547
+ });
548
+ }
549
+ rolodexRootFileSelected(evt) {
550
+ this.rolodexActivePath = evt.detail.path;
551
+ this.rolodexActiveHash = evt.detail.nodeHashId;
552
+ this.rolodexRootPath = evt.detail.path;
553
+ this.importDisabled = true;
554
+ const existingState = this.rolodexStateBag?.get(RolodexStateBag);
555
+ if (existingState) {
556
+ existingState.activePath = evt.detail.path;
557
+ existingState.activeHash = evt.detail.nodeHashId;
558
+ existingState.rootPath = evt.detail.path;
559
+ existingState.rootHash = evt.detail.nodeHashId;
560
+ this.rolodexStateBag?.set(RolodexStateBag, existingState);
561
+ }
562
+ else {
563
+ const existingState = {
564
+ activePath: evt.detail.path,
565
+ activeHash: evt.detail.nodeHashId,
566
+ rootPath: evt.detail.path,
567
+ rootHash: evt.detail.nodeHashId
568
+ };
569
+ this.rolodexStateBag?.set(RolodexStateBag, existingState);
570
+ }
571
+ if (evt.detail.nodeHashId) {
572
+ this.rolodexRootHash = evt.detail.nodeHashId;
573
+ }
574
+ if (evt.detail.content) {
575
+ this.rolodexNeedsReset = true;
576
+ this.editor.setValue(evt.detail.content, true);
577
+ this.lintSpec(evt.detail.content);
578
+ }
270
579
  }
271
580
  modelTreeNodeClicked(evt) {
272
581
  const node = this.nodeIdHashMap.get(evt.detail.nodeHashId);
273
582
  if (node) {
583
+ if (node.origin && node.origin != this.rolodexActivePath && node.origin != '/root.yaml') {
584
+ // extract the rolodex id using the path
585
+ let hashId = '';
586
+ const fb = this.rolodexFilesBag?.get(RolodexFilesBag);
587
+ if (fb) {
588
+ if (fb.files[node.origin]) {
589
+ hashId = fb.files[node.origin].idHash;
590
+ }
591
+ }
592
+ // we need to fire a rolodex tree node change event.
593
+ this.rolodexTreeNodeClicked(new CustomEvent(RolodexTreeNodeClicked, {
594
+ detail: {
595
+ nodeHashId: hashId,
596
+ path: node.origin
597
+ }
598
+ }));
599
+ return;
600
+ }
274
601
  this.editor.editor?.setPosition({ lineNumber: node.keyLine, column: 0 });
275
602
  this.editor.editor?.revealLineInCenter(node.keyLine);
276
603
  if (this.explorerVisible) {
@@ -288,15 +615,21 @@ let TheDoctor = class TheDoctor extends LitElement {
288
615
  evt.detail.noState = true;
289
616
  }
290
617
  this.explorerNodeClicked(evt);
291
- this.viewerPanel.click();
618
+ if (this._firstRun) {
619
+ this._firstRun = false;
620
+ }
621
+ else {
622
+ this.viewerPanel.click();
623
+ }
292
624
  }
293
625
  }
294
626
  explorerNodeClicked(evt) {
295
627
  let found = false;
628
+ let foundRenderedNode;
296
629
  this.explorer.nodeComponents.forEach((node) => {
297
630
  if (node.id === evt.detail.nodeHashId) {
298
631
  const renderedNode = this.renderedNodeMap.get(node.id);
299
- if (renderedNode) {
632
+ if (renderedNode && !this._firstRun) {
300
633
  this.renderedNode.node = renderedNode;
301
634
  }
302
635
  this.selectedNodeHashId = node.body.node.idHash;
@@ -304,6 +637,7 @@ let TheDoctor = class TheDoctor extends LitElement {
304
637
  this.activeNode = node.body.node;
305
638
  this.explorer.activeNode = node.body.node;
306
639
  found = true;
640
+ foundRenderedNode = renderedNode;
307
641
  if (!evt.detail.noState) {
308
642
  this.addClickTrack(node.body.node);
309
643
  }
@@ -322,8 +656,14 @@ let TheDoctor = class TheDoctor extends LitElement {
322
656
  this.explorer.equalizer.activeNode = node;
323
657
  }
324
658
  }
659
+ if (foundRenderedNode) {
660
+ // when jumping back to the spec view, we need to set the line
661
+ this.pendingLine = foundRenderedNode.keyLine;
662
+ }
325
663
  this.modelTree.explorerClicked(evt.detail.nodeHashId);
326
- this.viewerPanel.click();
664
+ if (!this._firstRun) {
665
+ this.viewerPanel.click();
666
+ }
327
667
  this.explorer.requestUpdate();
328
668
  }
329
669
  exportRuleset() {
@@ -412,17 +752,17 @@ let TheDoctor = class TheDoctor extends LitElement {
412
752
  // lint spec
413
753
  if (this.docBag) {
414
754
  const doc = this.docBag.get(DefaultDocument);
415
- const config = this.RuleConfigBag?.get(RuleConfigurationBag);
755
+ const config = this.ruleConfigBag?.get(RuleConfigurationBag);
416
756
  if (evt.detail.rules) {
417
- this.CustomRulesetBag?.set(CustomRulesetBag, evt.detail.rules);
757
+ this.customRulesetBag?.set(CustomRulesetBag, evt.detail.rules);
418
758
  // get ruleset config and disable everything,
419
759
  // then enable the custom rules.
420
760
  if (config) {
421
761
  // extract config from the event
422
762
  const newConfig = evt.detail.config;
423
- if (newConfig && this.RuleConfigBag) {
763
+ if (newConfig && this.ruleConfigBag) {
424
764
  newConfig.returnedRuleset = evt.detail.returnedRules;
425
- this.RuleConfigBag?.set(RuleConfigurationBag, newConfig);
765
+ this.ruleConfigBag?.set(RuleConfigurationBag, newConfig);
426
766
  }
427
767
  }
428
768
  RulesetService.getSessionRulesetAsYAML().then((yaml) => {
@@ -433,42 +773,267 @@ let TheDoctor = class TheDoctor extends LitElement {
433
773
  this.fetchRulesetMap();
434
774
  });
435
775
  // lint the spec
436
- this.lintSpec(doc);
776
+ this.lintSpec(doc, '');
437
777
  }
438
778
  }
439
779
  }
780
+ buildRolodexResultMap(results) {
781
+ this.rolodexProblemMap.clear();
782
+ results.forEach((problem) => {
783
+ // check if the source location is '/root.yaml'
784
+ if (problem.sourceLocation === '/root.yaml') {
785
+ problem.sourceLocation = "root";
786
+ }
787
+ if (this.rolodexProblemMap.has(problem.sourceLocation)) {
788
+ const problems = this.rolodexProblemMap.get(problem.sourceLocation);
789
+ if (problems) {
790
+ problems.push(problem);
791
+ if (problem.sourceLocation) {
792
+ this.rolodexProblemMap.set(problem.sourceLocation, problems);
793
+ }
794
+ else {
795
+ if (!this.rolodexRootPath) {
796
+ this.rolodexProblemMap.set("root", problems);
797
+ }
798
+ else {
799
+ this.rolodexProblemMap.set(this.rolodexRootPath, problems);
800
+ }
801
+ }
802
+ }
803
+ }
804
+ else {
805
+ if (problem.sourceLocation) {
806
+ this.rolodexProblemMap.set(problem.sourceLocation, [problem]);
807
+ }
808
+ else {
809
+ if (!this.rolodexRootPath) {
810
+ this.rolodexProblemMap.set("root", [problem]);
811
+ }
812
+ else {
813
+ this.rolodexProblemMap.set(this.rolodexRootPath, [problem]);
814
+ }
815
+ }
816
+ }
817
+ });
818
+ return this.rolodexProblemMap;
819
+ }
820
+ /* LINT SPEC <---------------------------------------------------------------------------------------------
821
+ *
822
+ * This is our main function, man we need to clean this all up, it's bloated to fuck
823
+ *
824
+ * <--------------------------------------------------------------------------------------------------------
825
+ */
440
826
  lintSpec(value, url) {
441
827
  this.activitySpinner.show();
828
+ this.editor.breadcumb.isInvalid = false;
442
829
  if (url) {
830
+ if (url === 'root') {
831
+ url = '';
832
+ }
833
+ else {
834
+ this.urlProblem.style.display = 'none';
835
+ this.urlOverlay.style.display = "block";
836
+ this.urlSpinner.style.display = "block";
837
+ this.referenceMapBag?.reset(); // wipe out refs.
838
+ }
839
+ }
840
+ else {
443
841
  this.urlProblem.style.display = 'none';
444
- this.urlOverlay.style.display = "block";
445
- this.urlSpinner.style.display = "block";
842
+ this.urlOverlay.style.display = "none";
843
+ this.urlSpinner.style.display = "none";
844
+ }
845
+ let replaceResults = false;
846
+ let currentPath = this.rolodexActivePath;
847
+ if (this.rolodexActivePath === this.rolodexRootPath || this.rolodexActivePath === 'root') {
848
+ replaceResults = true;
849
+ currentPath = '';
850
+ this.editor.showBreadcrumb = false;
851
+ }
852
+ if (url && url != '') {
853
+ try {
854
+ const parsedUrl = new URL(url);
855
+ if (parsedUrl) {
856
+ currentPath = parsedUrl.pathname;
857
+ this.rolodexActivePath = currentPath;
858
+ this.rolodexRootPath = currentPath;
859
+ replaceResults = true;
860
+ }
861
+ }
862
+ catch (e) {
863
+ // do nothing for now.
864
+ }
446
865
  }
447
- LintingService.lintFile(value, this.brokerConnectionId, url).then((result) => {
866
+ const fetchRefMap = (dead = false) => {
867
+ const currentRefs = this.referenceMapBag?.export().size;
868
+ ModelService.fetchReferenceMap().then((result) => {
869
+ if (dead) {
870
+ this.editor.clearDecorations();
871
+ this.editor.clearAllMarkers();
872
+ this.editor.breadcumb.isInvalid = true;
873
+ this.editor.dead();
874
+ this.problemList.isInvalid = true;
875
+ this.editor.requestUpdate();
876
+ }
877
+ else {
878
+ this.references = result;
879
+ this.editor.links = result;
880
+ this.editor.applyLinkDecorations();
881
+ }
882
+ // add references to our stateful bag
883
+ this.updateRefmapBag(currentPath, result);
884
+ }).catch(() => {
885
+ this.editor.clearDecorations();
886
+ this.editor.clearAllMarkers();
887
+ this.toastManager.addToastManually({
888
+ id: crypto.randomUUID(),
889
+ type: ToastType.INFO,
890
+ title: "File not analyzed",
891
+ body: "This file is not a JSON or YAML file, it has not been analyzed."
892
+ });
893
+ });
894
+ };
895
+ let revive = true;
896
+ LintingService.lintFile(value, this.brokerConnectionId, url, currentPath).then((result) => {
897
+ //if (replaceResults) {
898
+ const map = this.buildRolodexResultMap(result);
448
899
  if (url) {
449
900
  this.urlOverlay.style.display = "none";
450
901
  this.urlSpinner.style.display = "none";
451
902
  this.urlProblem.style.display = 'none';
452
903
  }
904
+ // extract empty location problems as we are replacing the root results
905
+ let rootProblems = this.rolodexProblemMap.get("root");
906
+ if (!this.rolodexRoot) {
907
+ rootProblems = [];
908
+ }
453
909
  if (result && !Array.isArray(result)) {
454
- this.editor.setMarkers([result]);
455
- this.problemBag?.set(DocumentProblems, [result]);
456
- this.problemList.problems = [result];
457
- this.problems = [result];
910
+ const r = [result];
911
+ if (this.rolodexProblemMap.size > 0 && this.rolodexProblemMap.has(this.rolodexActivePath)) {
912
+ const probs = this.rolodexProblemMap.get(this.rolodexActivePath);
913
+ if (probs) {
914
+ if (rootProblems) {
915
+ probs.push(...rootProblems);
916
+ }
917
+ this.editor.setMarkers(probs);
918
+ }
919
+ else {
920
+ if (rootProblems) {
921
+ r.push(...rootProblems);
922
+ }
923
+ this.editor.setMarkers(r);
924
+ }
925
+ }
926
+ else {
927
+ if (rootProblems) {
928
+ r.push(...rootProblems);
929
+ }
930
+ this.editor.setMarkers(r);
931
+ }
932
+ this.problemBag?.set(DocumentProblems, r);
933
+ this.problemList.problems = r;
934
+ this.problems = r;
458
935
  this.problemsOverview.problems = this.problemList.problemItems;
459
936
  }
460
937
  if (result && Array.isArray(result)) {
461
- this.editor.setMarkers(result);
938
+ if (result.length == 0) {
939
+ this.editor.clearAllMarkers();
940
+ this.editor.breadcumb.isInvalid = false;
941
+ this.editor.showBreadcrumb = false;
942
+ revive = true;
943
+ this.problemList.isInvalid = false;
944
+ }
945
+ // check if this problem is 'unable to parse'
946
+ if (result.length == 1 && result[0].message.startsWith('unable to parse')) {
947
+ // short circuit, we are dead.
948
+ this.editor.clearDecorations();
949
+ this.editor.clearAllMarkers();
950
+ this.editor.setMarkers(result);
951
+ this.editor.breadcumb.isInvalid = true;
952
+ this.editor.showBreadcrumb = true;
953
+ this.editor.dead();
954
+ this.problemList.isInvalid = true;
955
+ revive = false;
956
+ }
957
+ // extract empty location problems as we are replacing the root results
958
+ if (revive && this.rolodexProblemMap.size > 0 && this.rolodexProblemMap.has(this.rolodexActivePath)) {
959
+ const probs = this.rolodexProblemMap.get(this.rolodexActivePath);
960
+ if (probs) {
961
+ if (rootProblems) {
962
+ probs.push(...rootProblems);
963
+ }
964
+ this.editor.setMarkers(probs);
965
+ }
966
+ else {
967
+ if (rootProblems) {
968
+ result.push(...rootProblems);
969
+ }
970
+ this.editor.setMarkers(result);
971
+ }
972
+ }
973
+ else {
974
+ if (replaceResults) {
975
+ this.editor.setMarkers(result);
976
+ }
977
+ else {
978
+ fetchRefMap();
979
+ return;
980
+ }
981
+ }
462
982
  this.problemBag?.set(DocumentProblems, result);
463
983
  this.problemList.problems = result;
464
984
  this.problems = result;
465
985
  this.problemsOverview.problems = this.problemList.problemItems;
986
+ if (this.problemsOverview.statistics) {
987
+ if (result.length == 1) {
988
+ this.problemsOverview.statistics.statistics.totalErrors = 1;
989
+ this.problemsOverview.statistics.statistics.overallScore = 0;
990
+ this.problemsOverview.statistics.evaluation = 'Useless';
991
+ this.problemsOverview.statistics.diagnosis = '<strong>Specification cannot be used</strong>: <br/><br/>' + result[0].message;
992
+ }
993
+ }
994
+ }
995
+ // enable pb33f theme.
996
+ if (revive) {
997
+ this.editor.revive();
998
+ }
999
+ else {
1000
+ return;
466
1001
  }
467
1002
  // fetch graph
468
- // TODO: This needs work
469
- ModelService.createGraph().then((result) => {
470
- this.extractGraph(result);
471
- });
1003
+ if (this.rolodexRootPath == this.rolodexActivePath || this.rolodexActivePath == 'root') {
1004
+ ModelService.createGraph().then((result) => {
1005
+ this.extractGraph(result);
1006
+ });
1007
+ }
1008
+ fetchRefMap();
1009
+ // TODO: rolodex query should happen elsewhere too!
1010
+ if (this.rolodexNeedsReset) {
1011
+ ModelService.queryRolodex().then((result) => {
1012
+ this.importDisabled = true;
1013
+ this.editor.showBreadcrumb = true;
1014
+ this.rolodexNeedsReset = false;
1015
+ result.rolodexRoot.treeExpanded = true;
1016
+ this.rolodexTree.isRolodex = true;
1017
+ this.rolodexTree.node = result.rolodexRoot;
1018
+ this.rolodexRoot = result.rolodexRoot;
1019
+ if (this.rolodexActiveHash) {
1020
+ this.rolodexTree.pendingNavigationHash = this.rolodexActiveHash;
1021
+ }
1022
+ if (this.rolodexActivePath) {
1023
+ this.rolodexTree.pendingNavigationPath = this.rolodexActivePath;
1024
+ }
1025
+ this.rolodexResponseBag?.set(RolodexResponseBag, result);
1026
+ const existingState = this.rolodexStateBag?.get(RolodexStateBag);
1027
+ if (!existingState) {
1028
+ const existingState = {
1029
+ activePath: this.rolodexActivePath,
1030
+ rootPath: this.rolodexRootPath,
1031
+ };
1032
+ this.rolodexStateBag?.set(RolodexStateBag, existingState);
1033
+ }
1034
+ this.requestUpdate();
1035
+ });
1036
+ }
472
1037
  // update the overview statistics
473
1038
  LintingService.fetchStatistics().then((result) => {
474
1039
  let oldScore = 0;
@@ -485,32 +1050,37 @@ let TheDoctor = class TheDoctor extends LitElement {
485
1050
  }
486
1051
  // determine if the score went up or down and toast it!
487
1052
  this.problemsOverview.statistics = result;
488
- if (this.problemsOverview.statistics) {
489
- const newScore = result.statistics.overallScore;
490
- if (oldScore > newScore) {
491
- this.sendToast({
492
- id: crypto.randomUUID(),
493
- type: ToastType.SCOREDOWN,
494
- body: "Your score has decreased. It is now " + newScore + "%",
495
- title: "Score went down by " + (oldScore - newScore) + "%"
496
- });
497
- }
498
- if (oldScore < newScore) {
499
- this.sendToast({
500
- id: crypto.randomUUID(),
501
- type: ToastType.SCOREUP,
502
- body: "Your score has increased to " + newScore + "%",
503
- title: "Score went up by " + (newScore - oldScore) + "%"
504
- });
505
- }
506
- }
1053
+ // TODO: re-evaluate these toasts, they get annoying, fucking fast.
1054
+ // if (this.problemsOverview.statistics) {
1055
+ // const newScore = result.statistics.overallScore;
1056
+ // if (oldScore > newScore) {
1057
+ // this.sendToast({
1058
+ // id: crypto.randomUUID(),
1059
+ // type: ToastType.SCOREDOWN,
1060
+ // body: "Your score has decreased. It is now " + newScore + "%",
1061
+ // title: "Score went down by " + (oldScore - newScore) + "%"
1062
+ // });
1063
+ // }
1064
+ // if (oldScore < newScore) {
1065
+ // this.sendToast({
1066
+ // id: crypto.randomUUID(),
1067
+ // type: ToastType.SCOREUP,
1068
+ // body: "Your score has increased to " + newScore + "%",
1069
+ // title: "Score went up by " + (newScore - oldScore) + "%"
1070
+ // });
1071
+ // }
1072
+ // }
507
1073
  }).catch((e) => {
508
1074
  console.error("statistics service is down", e);
509
1075
  this.platformUnavailable(e);
510
1076
  });
1077
+ if (!this.explorerBooted) {
1078
+ this.explorer.equalizer.initializeEqualizer();
1079
+ this.explorerBooted = true;
1080
+ }
511
1081
  }).catch((e) => {
512
1082
  if (!url) {
513
- this.platformUnavailable(e);
1083
+ //this.platformUnavailable(e);
514
1084
  console.error("so sorry, the doctor cannot see you right now, the clinic is closed.");
515
1085
  if (e) {
516
1086
  console.error(e.detail);
@@ -531,6 +1101,50 @@ let TheDoctor = class TheDoctor extends LitElement {
531
1101
  }
532
1102
  });
533
1103
  }
1104
+ updateInspectorDivider() {
1105
+ const panelState = this.panelStateBag?.get(PanelStateBag);
1106
+ if (panelState) {
1107
+ panelState.inspectorPanel = this.splitPanelInspector.position;
1108
+ this.panelStateBag?.set(PanelStateBag, panelState);
1109
+ }
1110
+ else {
1111
+ this.panelStateBag?.set(PanelStateBag, { inspectorPanel: this.splitPanelInspector.position });
1112
+ }
1113
+ }
1114
+ updateExplorerDivider() {
1115
+ const panelState = this.panelStateBag?.get(PanelStateBag);
1116
+ if (panelState) {
1117
+ panelState.explorerPanel = this.splitPanelExplorer.position;
1118
+ this.panelStateBag?.set(PanelStateBag, panelState);
1119
+ }
1120
+ else {
1121
+ this.panelStateBag?.set(PanelStateBag, { explorerPanel: this.splitPanelExplorer.position });
1122
+ }
1123
+ }
1124
+ updateRolodexDivider() {
1125
+ const panelState = this.panelStateBag?.get(PanelStateBag);
1126
+ if (panelState) {
1127
+ panelState.rolodexPanel = this.splitPanelRolodex.position;
1128
+ this.panelStateBag?.set(PanelStateBag, panelState);
1129
+ }
1130
+ else {
1131
+ this.panelStateBag?.set(PanelStateBag, { rolodexPanel: this.splitPanelRolodex.position });
1132
+ }
1133
+ this.rolodexDividerPosition = this.splitPanelRolodex.position;
1134
+ }
1135
+ updateRefmapBag(currentPath, result) {
1136
+ let refMap = this.referenceMapBag?.get(ReferenceMapBag);
1137
+ let cp = currentPath;
1138
+ if (refMap) {
1139
+ refMap.references[cp] = result;
1140
+ }
1141
+ else {
1142
+ refMap = {
1143
+ references: { [cp]: result }
1144
+ };
1145
+ }
1146
+ this.referenceMapBag?.set(ReferenceMapBag, refMap);
1147
+ }
534
1148
  platformUnavailable(error) {
535
1149
  if (!this.unavailable) {
536
1150
  this.loadingOverlay.hide();
@@ -610,6 +1224,28 @@ let TheDoctor = class TheDoctor extends LitElement {
610
1224
  this.editorTabGroup.show("spec");
611
1225
  this.selectedEditorTab = "spec";
612
1226
  }
1227
+ let sourceLocation = event.detail.problem.problemObject.sourceLocation;
1228
+ // no source? use root
1229
+ if (sourceLocation == '' || sourceLocation == undefined || sourceLocation == 'root') {
1230
+ sourceLocation = this.rolodexRootPath;
1231
+ }
1232
+ // locate the node in rolodex root
1233
+ const node = SearchNodeTreeForPath(this.rolodexRoot, sourceLocation);
1234
+ if (node) {
1235
+ // trigger a rolodex load
1236
+ this.rolodexTreeNodeClicked(new CustomEvent(RolodexTreeNodeClicked, {
1237
+ detail: {
1238
+ nodeHashId: node.idHash,
1239
+ path: sourceLocation,
1240
+ line: event.detail.problem.problemObject.startLineNumber,
1241
+ column: event.detail.problem.problemObject.startColumn
1242
+ }
1243
+ }));
1244
+ this.detailsDrawer.close();
1245
+ // make sure we select the file in the rolodex
1246
+ this.rolodexTree.openNodeByPath(sourceLocation);
1247
+ return;
1248
+ }
613
1249
  this.controlTabGroup.show(ActiveView.Problems);
614
1250
  this.editor.editor?.revealLineInCenter(event.detail.problem.line);
615
1251
  this.editor.editor?.setPosition({
@@ -629,7 +1265,94 @@ let TheDoctor = class TheDoctor extends LitElement {
629
1265
  this.connectToBroker();
630
1266
  this.graphBag = this.bagManager.getBag(GraphBag);
631
1267
  this.docBag = this.bagManager.getBag(DoctorDocumentBag);
632
- if (this.docBag) {
1268
+ this.rolodexResponseBag = this.bagManager.getBag(RolodexResponseBag);
1269
+ this.rolodexFilesBag = this.bagManager.getBag(RolodexFilesBag);
1270
+ this.rolodexStateBag = this.bagManager.getBag(RolodexStateBag);
1271
+ this.referenceMapBag = this.bagManager.getBag(ReferenceMapBag);
1272
+ this.docExpirationBag = this.bagManager.getBag(DocumentationExpirationBag);
1273
+ this.panelStateBag = this.bagManager.getBag(PanelStateBag);
1274
+ // load the rolodex
1275
+ if (this.rolodexResponseBag) {
1276
+ const response = this.rolodexResponseBag.get(RolodexResponseBag);
1277
+ if (response) {
1278
+ this.rolodexRoot = response.rolodexRoot;
1279
+ this.rolodexTree.isRolodex = true;
1280
+ this.rolodexTree.node = response.rolodexRoot;
1281
+ response.rolodexRoot.treeExpanded = true;
1282
+ this.requestUpdate();
1283
+ }
1284
+ }
1285
+ let skipDocBag = false;
1286
+ const checkRefMap = (key) => {
1287
+ let refMap = this.referenceMapBag?.get(ReferenceMapBag);
1288
+ if (refMap) {
1289
+ // TODO: replace with scehduled task to refresh the reference map
1290
+ setTimeout(() => {
1291
+ let refs = refMap?.references[key];
1292
+ if (!refs) {
1293
+ refs = refMap?.references[""]; // single file
1294
+ }
1295
+ if (refs) {
1296
+ this.references = refs;
1297
+ this.editor.links = refs;
1298
+ this.editor.applyLinkDecorations();
1299
+ }
1300
+ }, 5); // editor has not loaded yet, give it a second to breathe.
1301
+ }
1302
+ else {
1303
+ ModelService.fetchReferenceMap().then((result) => {
1304
+ this.references = result;
1305
+ this.editor.links = result;
1306
+ this.editor.applyLinkDecorations();
1307
+ }).catch(() => {
1308
+ this.editor.clearDecorations();
1309
+ this.editor.clearAllMarkers();
1310
+ });
1311
+ }
1312
+ };
1313
+ if (this.rolodexStateBag) {
1314
+ const state = this.rolodexStateBag.get(RolodexStateBag);
1315
+ if (state) {
1316
+ this.rolodexActivePath = state.activePath;
1317
+ this.rolodexActiveHash = state.activeHash;
1318
+ // load the root.
1319
+ if (state.rootHash) {
1320
+ this.rolodexTree.pendingNavigationHash = state.rootHash;
1321
+ }
1322
+ if (state.rootPath) {
1323
+ this.rolodexRootPath = state.rootPath;
1324
+ if (state.activePath) {
1325
+ checkRefMap(state.activePath);
1326
+ }
1327
+ // ensure the session has a root selected
1328
+ ModelService.selectRootPath(state.rootPath).then(() => {
1329
+ // nothing to do.
1330
+ }).catch((e) => {
1331
+ console.error('could not select the root path on boot bro', e);
1332
+ });
1333
+ }
1334
+ if (state.rootHash)
1335
+ this.rolodexRootHash = state.rootHash;
1336
+ if (state.activePath) {
1337
+ this.rolodexTree.pendingNavigationPath = state.activePath;
1338
+ this.rolodexTree.requestUpdate();
1339
+ }
1340
+ if (state.activeNode) {
1341
+ // load the content of the active node into the editor
1342
+ if (state.activeLanguage) {
1343
+ this.editor.switchLanguage(state.activeLanguage);
1344
+ }
1345
+ this.editor.setValue(state.activeNode.instance, true);
1346
+ skipDocBag = true;
1347
+ }
1348
+ }
1349
+ else {
1350
+ this.rolodexActivePath = "root";
1351
+ // refresh the refmap.
1352
+ checkRefMap(this.rolodexActivePath);
1353
+ }
1354
+ }
1355
+ if (this.docBag && !skipDocBag) {
633
1356
  const doc = this.docBag.get(DefaultDocument);
634
1357
  if (doc) {
635
1358
  this.editor.setValue(doc, true);
@@ -640,10 +1363,16 @@ let TheDoctor = class TheDoctor extends LitElement {
640
1363
  const markers = this.problemBag.get(DocumentProblems);
641
1364
  if (markers) {
642
1365
  this.problems = markers;
643
- this.editor.setMarkers(markers);
644
1366
  for (let i = 0; i < markers.length; i++) {
645
1367
  markers[i] = Problem.reconstruct(markers[i]);
646
1368
  }
1369
+ this.buildRolodexResultMap(markers);
1370
+ if (this.rolodexProblemMap.size > 0 && this.rolodexProblemMap.has(this.rolodexActivePath)) {
1371
+ const probs = this.rolodexProblemMap.get(this.rolodexActivePath);
1372
+ if (probs) {
1373
+ this.editor.setMarkers(probs);
1374
+ }
1375
+ }
647
1376
  this.problemList.problems = markers;
648
1377
  this.problemsOverview.problems = this.problemList.problemItems;
649
1378
  }
@@ -663,11 +1392,33 @@ let TheDoctor = class TheDoctor extends LitElement {
663
1392
  this.extractGraph(graph);
664
1393
  }
665
1394
  }
1395
+ // extract panel state
1396
+ if (this.panelStateBag) {
1397
+ const panelState = this.panelStateBag.get(PanelStateBag);
1398
+ if (panelState && panelState.explorerPanel) {
1399
+ this.splitPanelExplorer.position = panelState.explorerPanel;
1400
+ }
1401
+ if (panelState && panelState.rolodexPanel) {
1402
+ if (this.splitPanelRolodex) {
1403
+ this.splitPanelRolodex.position = panelState.rolodexPanel;
1404
+ }
1405
+ this.rolodexDividerPosition = panelState.rolodexPanel;
1406
+ }
1407
+ if (panelState && panelState.inspectorPanel) {
1408
+ this.splitPanelInspector.position = panelState.inspectorPanel;
1409
+ }
1410
+ }
1411
+ if (!this.rolodexRoot) {
1412
+ this.importDisabled = false;
1413
+ }
1414
+ else {
1415
+ this.importDisabled = true;
1416
+ }
666
1417
  // extract rulesets from bags
667
1418
  const promises = [];
668
- this.DefaultRulesetBag = this.bagManager.getBag(DefaultRulesetBag);
669
- if (this.DefaultRulesetBag) {
670
- const ruleset = this.DefaultRulesetBag.get(DefaultRulesetBag);
1419
+ this.defaultRulesetBag = this.bagManager.getBag(DefaultRulesetBag);
1420
+ if (this.defaultRulesetBag) {
1421
+ const ruleset = this.defaultRulesetBag.get(DefaultRulesetBag);
671
1422
  if (ruleset) {
672
1423
  this.defaultRuleset = ruleset;
673
1424
  }
@@ -687,21 +1438,21 @@ let TheDoctor = class TheDoctor extends LitElement {
687
1438
  }
688
1439
  }
689
1440
  // All ruleset
690
- this.AllRulesetBag = this.bagManager.getBag(AllRulesetBag);
691
- if (this.AllRulesetBag) {
692
- const ruleset = this.AllRulesetBag.get(AllRulesetBag);
1441
+ this.allRulesetBag = this.bagManager.getBag(AllRulesetBag);
1442
+ if (this.allRulesetBag) {
1443
+ const ruleset = this.allRulesetBag.get(AllRulesetBag);
693
1444
  if (ruleset) {
694
- this.AllRuleset = ruleset;
1445
+ this.allRuleset = ruleset;
695
1446
  }
696
1447
  else {
697
1448
  promises.push(this.fetchAllRuleset());
698
1449
  }
699
1450
  }
700
1451
  // functions
701
- this.FunctionsBag = this.bagManager.getBag(FunctionsBag);
702
- this.FunctionSchemaBag = this.bagManager.getBag(FunctionsSchemaBag);
703
- if (this.FunctionsBag) {
704
- const functions = this.FunctionsBag.get(FunctionsBag);
1452
+ this.functionsBag = this.bagManager.getBag(FunctionsBag);
1453
+ this.functionSchemaBag = this.bagManager.getBag(FunctionsSchemaBag);
1454
+ if (this.functionsBag) {
1455
+ const functions = this.functionsBag.get(FunctionsBag);
705
1456
  if (functions) {
706
1457
  this.functions = functions;
707
1458
  }
@@ -710,15 +1461,15 @@ let TheDoctor = class TheDoctor extends LitElement {
710
1461
  }
711
1462
  }
712
1463
  // custom ruleset
713
- this.CustomRulesetBag = this.bagManager.getBag(CustomRulesetBag);
714
- if (this.CustomRulesetBag) {
715
- const ruleset = this.CustomRulesetBag.get(CustomRulesetBag);
1464
+ this.customRulesetBag = this.bagManager.getBag(CustomRulesetBag);
1465
+ if (this.customRulesetBag) {
1466
+ const ruleset = this.customRulesetBag.get(CustomRulesetBag);
716
1467
  if (ruleset) {
717
- this.CustomRuleset = ruleset;
1468
+ this.customRuleset = ruleset;
718
1469
  }
719
1470
  }
720
1471
  // create rule configuration bag
721
- this.RuleConfigBag = this.bagManager.getBag(RuleConfigurationBag);
1472
+ this.ruleConfigBag = this.bagManager.getBag(RuleConfigurationBag);
722
1473
  // create rule configuration bag
723
1474
  this.sessionRulesetMapBag = this.bagManager.getBag(SessionRulesetMapBag);
724
1475
  // fire off all network requests, then configure the ruleset management.
@@ -726,9 +1477,9 @@ let TheDoctor = class TheDoctor extends LitElement {
726
1477
  // configure rule management
727
1478
  this.manageRuleset.defaultRuleset = this.defaultRuleset;
728
1479
  this.manageRuleset.owaspRuleset = this.OWASPRuleset;
729
- this.manageRuleset.allRuleset = this.AllRuleset;
1480
+ this.manageRuleset.allRuleset = this.allRuleset;
730
1481
  this.manageRuleset.functions = this.functions;
731
- const config = this.RuleConfigBag?.get(RuleConfigurationBag);
1482
+ const config = this.ruleConfigBag?.get(RuleConfigurationBag);
732
1483
  if (config) {
733
1484
  this.manageRuleset.rulesetConfig = config;
734
1485
  }
@@ -737,8 +1488,8 @@ let TheDoctor = class TheDoctor extends LitElement {
737
1488
  }
738
1489
  setTimeout(() => {
739
1490
  this.manageRuleset.buildRulesets();
740
- if (this.CustomRuleset && this.CustomRuleset.rules.size > 0) {
741
- this.manageRuleset.customRuleset = this.CustomRuleset;
1491
+ if (this.customRuleset && this.customRuleset.rules.size > 0) {
1492
+ this.manageRuleset.customRuleset = this.customRuleset;
742
1493
  }
743
1494
  this.fetchSessionRulesetAsYaml().then((result) => {
744
1495
  this.rulesetEditor?.setValue(result, true);
@@ -770,14 +1521,14 @@ let TheDoctor = class TheDoctor extends LitElement {
770
1521
  this.nodeIdMap.clear();
771
1522
  this.nodeIdHashMap.clear();
772
1523
  const renderedNodes = new Map();
773
- graph.nodesRendered.forEach((node) => {
1524
+ graph?.nodesRendered?.forEach((node) => {
774
1525
  this.explorer.renderedNodeMap.set(node.id, node);
775
1526
  renderedNodes.set(node.idHash, node);
776
1527
  });
777
- for (let i = 0; i < graph.nodesRendered.length; i++) {
1528
+ for (let i = 0; i < graph?.nodesRendered?.length; i++) {
778
1529
  this.renderedNodeMap.set(graph.nodesRendered[i].idHash, graph.nodesRendered[i]);
779
1530
  }
780
- graph.nodes.forEach((node) => {
1531
+ graph?.nodes?.forEach((node) => {
781
1532
  this.nodeMap.set('model-' + node.idHash, node);
782
1533
  this.nodeIdMap.set(node.id, node);
783
1534
  this.nodeIdHashMap.set(node.idHash, node);
@@ -793,9 +1544,15 @@ let TheDoctor = class TheDoctor extends LitElement {
793
1544
  });
794
1545
  this.graphBag?.set(GraphBag, graph);
795
1546
  this.explorer.updateGraphResponse(graph);
796
- this.modelTree.node = graph.nodes[0]; // update tree
797
- this.explorer.equalizer.runEQ();
798
- //this.explorer.buildLayout();
1547
+ if (graph && graph.nodes) {
1548
+ this.modelTree.node = graph?.nodes[0]; // update tree
1549
+ }
1550
+ if (!this.explorer.equalizer.isInitialized()) {
1551
+ this.explorer.equalizer.initializeEqualizer();
1552
+ }
1553
+ else {
1554
+ this.explorer.equalizer.runEQ(true);
1555
+ }
799
1556
  }
800
1557
  customRulesetEnabled(event) {
801
1558
  const customRS = { rules: new Map() };
@@ -808,12 +1565,12 @@ let TheDoctor = class TheDoctor extends LitElement {
808
1565
  customRS.rules.set(rule.ruleId, rule.rule);
809
1566
  });
810
1567
  // write state
811
- this.CustomRulesetBag?.set(CustomRulesetBag, customRS);
1568
+ this.customRulesetBag?.set(CustomRulesetBag, customRS);
812
1569
  if (event.detail.ruleConfig) {
813
- this.RuleConfigBag?.set(RuleConfigurationBag, event.detail.ruleConfig);
1570
+ this.ruleConfigBag?.set(RuleConfigurationBag, event.detail.ruleConfig);
814
1571
  }
815
1572
  else {
816
- this.RuleConfigBag?.reset();
1573
+ this.ruleConfigBag?.reset();
817
1574
  // reset session ruleset
818
1575
  RulesetService.resetSessionRuleset(event.detail.rulesetReset).then(() => {
819
1576
  this.sendToast({
@@ -824,15 +1581,14 @@ let TheDoctor = class TheDoctor extends LitElement {
824
1581
  });
825
1582
  if (this.docBag) {
826
1583
  const doc = this.docBag.get(DefaultDocument);
827
- this.lintSpec(doc);
1584
+ let path = this.rolodexActivePath;
1585
+ if (path == 'root') {
1586
+ path = "";
1587
+ }
1588
+ this.lintSpec(doc, this.rolodexActivePath);
828
1589
  }
829
1590
  this.fetchSessionRulesetAsYaml().then((result) => {
830
1591
  this.rulesetEditor?.setValue(result, true);
831
- // if(event.detail.rulesetReset) {
832
- // this.manageRuleset.switchRuleset(event.detail.rulesetReset);
833
- // } else {
834
- // this.manageRuleset.switchRuleset("default");
835
- // }
836
1592
  });
837
1593
  }).catch((e) => {
838
1594
  this.sendToast({
@@ -858,7 +1614,7 @@ let TheDoctor = class TheDoctor extends LitElement {
858
1614
  return new Promise(async (resolve) => {
859
1615
  RulesetService.getFunctions().then((result) => {
860
1616
  this.functions = result;
861
- this.FunctionsBag?.set(FunctionsBag, result);
1617
+ this.functionsBag?.set(FunctionsBag, result);
862
1618
  // extract schemas for each function
863
1619
  const promises = [];
864
1620
  result.forEach((functionId) => {
@@ -873,12 +1629,12 @@ let TheDoctor = class TheDoctor extends LitElement {
873
1629
  async fetchFunctionSchema(functionId) {
874
1630
  return new Promise(async (resolve) => {
875
1631
  RulesetService.getFunctionSchema(functionId).then((result) => {
876
- let m = this.FunctionSchemaBag?.get(FunctionsSchemaBag);
1632
+ let m = this.functionSchemaBag?.get(FunctionsSchemaBag);
877
1633
  if (!m) {
878
1634
  m = new Map();
879
1635
  }
880
1636
  m.set(functionId, result);
881
- this.FunctionSchemaBag?.set(FunctionsSchemaBag, m);
1637
+ this.functionSchemaBag?.set(FunctionsSchemaBag, m);
882
1638
  resolve(result);
883
1639
  });
884
1640
  });
@@ -887,7 +1643,7 @@ let TheDoctor = class TheDoctor extends LitElement {
887
1643
  return new Promise(async (resolve) => {
888
1644
  RulesetService.getDefaultRuleset().then((result) => {
889
1645
  this.defaultRuleset = result;
890
- this.DefaultRulesetBag?.set(DefaultRulesetBag, result);
1646
+ this.defaultRulesetBag?.set(DefaultRulesetBag, result);
891
1647
  resolve(result);
892
1648
  });
893
1649
  });
@@ -904,8 +1660,8 @@ let TheDoctor = class TheDoctor extends LitElement {
904
1660
  async fetchAllRuleset() {
905
1661
  return new Promise(async (resolve) => {
906
1662
  RulesetService.getAllRuleset().then((result) => {
907
- this.AllRuleset = result;
908
- this.AllRulesetBag?.set(AllRulesetBag, result);
1663
+ this.allRuleset = result;
1664
+ this.allRulesetBag?.set(AllRulesetBag, result);
909
1665
  resolve(result);
910
1666
  });
911
1667
  });
@@ -915,10 +1671,13 @@ let TheDoctor = class TheDoctor extends LitElement {
915
1671
  // the first this we need to do is start a session
916
1672
  LintingService.startSession().then((session) => {
917
1673
  this.session = session;
918
- // bootstrap async
919
- // setTimeout(() => {
920
- // this.boostrap();
921
- // });
1674
+ if (this.session.creditsRemaining) {
1675
+ this.creditTicker.credits = this.session.creditsRemaining;
1676
+ }
1677
+ else {
1678
+ this.creditTicker.credits = this.session.creditsRemaining;
1679
+ }
1680
+ this.creditTicker.visible = true;
922
1681
  LintingService.fetchAllHowToFix().then((result) => {
923
1682
  if (result) {
924
1683
  result.forEach((howToFix) => {
@@ -935,6 +1694,7 @@ let TheDoctor = class TheDoctor extends LitElement {
935
1694
  this.ruleDocsWorker.addEventListener("message", (event) => {
936
1695
  const data = event.data;
937
1696
  if (data) {
1697
+ this.docExpirationBag?.set(DocumentationExpirationBag, new Date().toISOString());
938
1698
  data.forEach((doc) => {
939
1699
  if (doc.ruleId) {
940
1700
  this.ruleDocsBag?.set(doc.ruleId, doc);
@@ -946,7 +1706,43 @@ let TheDoctor = class TheDoctor extends LitElement {
946
1706
  this.activitySpinner.hide();
947
1707
  }
948
1708
  });
949
- this.ruleDocsWorker.postMessage({ start: true, endpoint: this.doctorEndpoint });
1709
+ let ruleDocs = [];
1710
+ let functionDocs = [];
1711
+ if (this.ruleDocsBag) {
1712
+ ruleDocs = Array.from(this.ruleDocsBag.export().keys());
1713
+ }
1714
+ if (this.functionDocsBag) {
1715
+ functionDocs = Array.from(this.functionDocsBag.export().keys());
1716
+ }
1717
+ const fetchDocs = () => {
1718
+ this.ruleDocsWorker.postMessage({
1719
+ start: true,
1720
+ endpoint: this.doctorEndpoint,
1721
+ existingRules: ruleDocs,
1722
+ existingFunctions: functionDocs
1723
+ });
1724
+ };
1725
+ // check expiration
1726
+ if (this.docExpirationBag) {
1727
+ const expiration = this.docExpirationBag.get(DocumentationExpirationBag);
1728
+ // if the docs are older than 15 days, refresh them.
1729
+ if (expiration) {
1730
+ const now = new Date().getTime();
1731
+ const then = new Date(expiration).getTime();
1732
+ if (now - then > 1296000000) {
1733
+ fetchDocs();
1734
+ }
1735
+ else {
1736
+ this.activitySpinner.hide();
1737
+ }
1738
+ }
1739
+ else {
1740
+ fetchDocs();
1741
+ }
1742
+ }
1743
+ else {
1744
+ fetchDocs();
1745
+ }
950
1746
  }).catch((e) => {
951
1747
  this.platformUnavailable(e);
952
1748
  console.error("cannot start session");
@@ -997,8 +1793,8 @@ let TheDoctor = class TheDoctor extends LitElement {
997
1793
  rule.id = id; // set the id to the key
998
1794
  });
999
1795
  // write state
1000
- this.CustomRulesetBag?.set(CustomRulesetBag, customRS);
1001
- this.CustomRuleset = customRS;
1796
+ this.customRulesetBag?.set(CustomRulesetBag, customRS);
1797
+ this.customRuleset = customRS;
1002
1798
  if (customRS.rules.size > 0) {
1003
1799
  // rebuild the ruleset management
1004
1800
  this.manageRuleset.customRulesetManual = customRS;
@@ -1039,7 +1835,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1039
1835
  if (rule.id != id)
1040
1836
  rule.id = id; // set the id to the key
1041
1837
  });
1042
- this.CustomRuleset = customRS;
1838
+ this.customRuleset = customRS;
1043
1839
  if (customRS.rules.size > 0) {
1044
1840
  // rebuild the ruleset management
1045
1841
  this.manageRuleset.customRulesetManual = customRS;
@@ -1059,7 +1855,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1059
1855
  }
1060
1856
  specChanged(event) {
1061
1857
  const editor = this.editorMap.get(event.detail.id);
1062
- if (editor && event.detail.id === 'ruleset') {
1858
+ if (editor && event.detail.id === 'ruleset-editor') {
1063
1859
  this.rulesetManuallyChanged();
1064
1860
  return;
1065
1861
  }
@@ -1077,12 +1873,14 @@ let TheDoctor = class TheDoctor extends LitElement {
1077
1873
  const urlParam = url.searchParams.get('url');
1078
1874
  if (urlParam) {
1079
1875
  this.urlInput.value = urlParam;
1876
+ this.activeURL = urlParam;
1080
1877
  this.lintSpec('', urlParam);
1081
1878
  return;
1082
1879
  }
1083
- if (this.editor.getValue() === '') {
1880
+ if (this.editor.getValue() === '' || !this.problems) {
1084
1881
  LintingService.bootstrapEditor().then((result) => {
1085
1882
  this.editor.setValue(result, true);
1883
+ this.rolodexActivePath = "";
1086
1884
  this.specChanged(new CustomEvent(EditorUpdated, {
1087
1885
  detail: {
1088
1886
  content: result,
@@ -1094,8 +1892,8 @@ let TheDoctor = class TheDoctor extends LitElement {
1094
1892
  }
1095
1893
  exportJSON() {
1096
1894
  let jsonRS;
1097
- if (this.RuleConfigBag) {
1098
- const config = this.RuleConfigBag.get(RuleConfigurationBag);
1895
+ if (this.ruleConfigBag) {
1896
+ const config = this.ruleConfigBag.get(RuleConfigurationBag);
1099
1897
  if (config && config.returnedRuleset) {
1100
1898
  jsonRS = config.returnedRuleset;
1101
1899
  }
@@ -1188,17 +1986,21 @@ let TheDoctor = class TheDoctor extends LitElement {
1188
1986
  this.sidebarClosed = !this.sidebarClosed;
1189
1987
  }
1190
1988
  toggleExplorer() {
1191
- this.explorerVisible = true;
1192
- if (this.explorerVisible) {
1989
+ this.explorerVisible = !this.explorerVisible;
1990
+ if (this.explorerVisible && this.activeNode) {
1193
1991
  this.explorer.moveToNode(this.activeNode);
1194
1992
  }
1195
- if (!this.explorerBooted) {
1196
- this.explorer.equalizer.initializeEqualizer();
1197
- this.explorerBooted = true;
1198
- }
1993
+ this.explorerBooted = true;
1199
1994
  }
1200
1995
  closeExplorer() {
1201
1996
  this.explorerVisible = false;
1997
+ setTimeout(() => {
1998
+ if (this.pendingLine > 0) {
1999
+ this.editor.editor?.setPosition({ lineNumber: this.pendingLine, column: 1 });
2000
+ this.editor.editor?.revealLineInCenter(this.pendingLine);
2001
+ this.pendingLine = 0;
2002
+ }
2003
+ }, 20);
1202
2004
  }
1203
2005
  render() {
1204
2006
  let overlay = html ``;
@@ -1224,6 +2026,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1224
2026
  <sl-badge pulse></sl-badge>`;
1225
2027
  }
1226
2028
  let mainPanelView = html `
2029
+
1227
2030
  <div class="problems" slot="end">
1228
2031
  <div class="problems-data">
1229
2032
  <sl-tab-group class="tab-group" id="manager-controls"
@@ -1244,8 +2047,25 @@ let TheDoctor = class TheDoctor extends LitElement {
1244
2047
  <sl-icon-button class="collapse-side" name="chevron-bar-right"
1245
2048
  @click="${this.toggleSidebar}"></sl-icon-button>
1246
2049
  </div>`;
1247
- let sparks = new PixelSparks();
2050
+ let modelTree = html `${this.modelTree}`;
2051
+ if (this.rolodexTree.node && this.rolodexTree.node.nodes?.length > 0) {
2052
+ modelTree = html `
2053
+ <sl-split-panel id="rolodex-split-panel" vertical class="split-panel-rolodex"
2054
+ position="${this.rolodexDividerPosition}" @sl-reposition="${this.updateRolodexDivider}"
2055
+ style="height: calc(100% - 60px);">
2056
+ <sl-icon id="split-rolodex-tree" slot="divider" name="grip-horizontal"
2057
+ class="divider-horiz"></sl-icon>
2058
+ <div slot="start" class="model-tree">
2059
+ ${this.modelTree}
2060
+ </div>
2061
+ <div slot="end" class="model-tree">
2062
+ ${this.rolodexTree}
2063
+ </div>
2064
+ </sl-split-panel>`;
2065
+ }
1248
2066
  return html `
2067
+ ${this.uploadArchive}
2068
+ ${this.nukeWorkspace}
1249
2069
  ${welcomeBox}
1250
2070
  ${this.toastManager}
1251
2071
  <sl-dialog open id="loading-overlay" class="dialog-overview"
@@ -1276,15 +2096,46 @@ let TheDoctor = class TheDoctor extends LitElement {
1276
2096
  ${this.errorBanner}
1277
2097
  ${overlay}
1278
2098
 
1279
- <sl-split-panel class="split-panel-explorer" position="12">
1280
- <sl-icon id="split-divider-tree" slot="divider" name="grip-vertical"></sl-icon>
2099
+ <sl-split-panel id="explorer-split-panel" class="split-panel-explorer" position="12"
2100
+ @sl-reposition="${this.updateExplorerDivider}">
2101
+ <sl-icon id="split-divider-tree" slot="divider" name="grip-vertical" class="divider-vert"></sl-icon>
1281
2102
  <div slot="start" class="model-tree">
1282
- ${this.modelTree}
2103
+ <div class="controls">
2104
+
2105
+ <sl-tooltip content="Upload zip file or tar.gz (tarball)" placement="top">
2106
+ <sl-button ?disabled=${this.importDisabled} class="import-button" variant="text"
2107
+ size="small" @click="${() => {
2108
+ this.uploadArchive.show();
2109
+ }}">
2110
+ Import
2111
+ </sl-button>
2112
+ </sl-tooltip>
2113
+ <sl-tooltip content="Nuke / reset workspace" placement="top">
2114
+ <sl-icon-button style="${!this.importDisabled ? 'display: none' : ''}"
2115
+ class="nuke-session-button" name="radioactive" @click="${() => {
2116
+ this.nukeWorkspace.show();
2117
+ }}">
2118
+ </sl-icon-button>
2119
+ </sl-tooltip>
2120
+
2121
+ </div>
2122
+ ${modelTree}
2123
+ ${this.creditTicker}
1283
2124
  </div>
1284
2125
  <div slot="end">
1285
- <sl-split-panel class="split-panel ${this.unavailable ? 'unavailable' : ''}" position="60">
1286
- <sl-icon id="split-divider" slot="divider" name="grip-vertical"></sl-icon>
2126
+ <sl-split-panel id="inspector-split-panel"
2127
+ @sl-reposition="${this.updateInspectorDivider}"
2128
+ class="split-panel ${this.unavailable ? 'unavailable' : ''}">
2129
+ <sl-icon id="split-divider" slot="divider" name="grip-vertical"
2130
+ class="divider-vert"></sl-icon>
1287
2131
  <div class="editor" slot="start">
2132
+
2133
+ <sl-tooltip class="minimap-tip" content="Toggle source mini-map" hoist>
2134
+ <sl-icon-button @click="${this.minimapToggled}"
2135
+ name="map"
2136
+ class="minimap-toggle ${this.minimapVisible ? 'active' : ''}"></sl-icon-button>
2137
+ </sl-tooltip>
2138
+
1288
2139
  ${this.detailsDrawer}
1289
2140
  <sl-tab-group class="tab-group" @sl-tab-show="${this.selectEditorTab}"
1290
2141
  id="editor-controls">
@@ -1302,26 +2153,18 @@ let TheDoctor = class TheDoctor extends LitElement {
1302
2153
  </sl-tab>
1303
2154
 
1304
2155
  <sl-tab-panel name="spec" class="tab-panel">
1305
-
1306
- <div class="controls">
1307
- <div style="margin-top: 8px; margin-right: 5px;">URL:</div>
1308
- <sl-input id="url-input" class="url-input"
1309
- placeholder="https://api.pb33f.io/train-travel.yaml" size="small"
1310
- @keydown="${this.validateUrl}"
1311
- @keyup="${this.validateUrl}"></sl-input>
1312
- <sl-button id="url-input-button" class="url-input-button close-button"
1313
- size="small" variant="neutral" disabled
1314
- @click="${this.fetchUrl}">Fetch
1315
- </sl-button>
1316
- </div>
1317
2156
  <div class="main-view">
2157
+ ${this.statusBar}
1318
2158
  <div id="editor-url-overlay" class="editor-url-overlay">
2159
+
2160
+
1319
2161
  <div id="url-spinner">
1320
- <div class="pb33f-loader">
1321
- <div class="spin"></div>
1322
- Fetching from URL '<strong>${this.activeURL}</strong>'...
1323
- </div>
2162
+ <sl-icon name="arrow-repeat" class="url-spinner-icon"></sl-icon>
2163
+ <h3>Fetching URL</h3>
2164
+ <p>${this.activeURL}</p>
1324
2165
  </div>
2166
+
2167
+
1325
2168
  <div id="url-problem" class="url-problem">
1326
2169
  <pb33f-attention-box type="warning" headerText="Problem with URL">
1327
2170
  <h4>Error: ${this.urlErrorCode}</h4>
@@ -1331,10 +2174,19 @@ let TheDoctor = class TheDoctor extends LitElement {
1331
2174
  </pb33f-attention-box>
1332
2175
  </div>
1333
2176
  </div>
1334
- ${this.editor}
2177
+
2178
+
2179
+ <!-- EDITOR -->
2180
+ <slot name="spec-editor"></slot>
2181
+ <!-- EDITOR -->
1335
2182
  </div>
1336
2183
  </sl-tab-panel>
1337
- <sl-tab-panel name="ruleset" class="tab-panel">${this.rulesetEditor}</sl-tab-panel>
2184
+ <sl-tab-panel name="ruleset" class="tab-panel">
2185
+
2186
+ <!-- RULESET EDITOR -->
2187
+ <slot name="ruleset-editor"></slot>
2188
+ <!-- RULESET EDITOR -->
2189
+ </sl-tab-panel>
1338
2190
  <sl-tab-panel name="explorer" class="tab-panel"
1339
2191
  @mouseleave="${this.ungrabExplorer}">${this.explorer}
1340
2192
  </sl-tab-panel>
@@ -1345,8 +2197,9 @@ let TheDoctor = class TheDoctor extends LitElement {
1345
2197
  </sl-split-panel>
1346
2198
  </div>
1347
2199
  </sl-split-panel>
1348
- ${this.statusBar}
2200
+
1349
2201
  </div>`;
2202
+ //
1350
2203
  // docs <iframe src="/docs.html" width="100%" height="100%"
1351
2204
  // style="border: none"></iframe>
1352
2205
  // <sl-tab slot="nav" panel="docs" class="tab" id="docs">
@@ -1355,14 +2208,17 @@ let TheDoctor = class TheDoctor extends LitElement {
1355
2208
  // <sl-tab-panel name="docs" class="tab-panel">
1356
2209
  // </sl-tab-panel>
1357
2210
  }
1358
- fetchUrl() {
1359
- this.activeURL = this.urlInput.value;
1360
- this.lintSpec('', this.urlInput.value);
2211
+ fetchUrl(event) {
2212
+ this.activeURL = event.detail.url;
2213
+ this.rolodexNeedsReset = true;
2214
+ this.rolodexActivePath = "/";
2215
+ this.lintSpec('', event.detail.url);
1361
2216
  }
1362
2217
  hideUrlError() {
1363
2218
  this.urlProblem.style.display = 'none';
1364
2219
  this.urlOverlay.style.display = 'none';
1365
2220
  this.urlSpinner.style.display = 'none';
2221
+ this.editor.toggleMinimap();
1366
2222
  }
1367
2223
  showUrlError(e) {
1368
2224
  this.urlErrorCode = e.status;
@@ -1370,27 +2226,14 @@ let TheDoctor = class TheDoctor extends LitElement {
1370
2226
  this.urlSpinner.style.display = 'none';
1371
2227
  this.urlOverlay.style.display = 'block';
1372
2228
  this.urlProblem.style.display = 'block';
2229
+ this.editor.toggleMinimap();
1373
2230
  this.requestUpdate();
1374
2231
  }
1375
- validateUrl(evt) {
1376
- if (evt.key === "Enter") {
1377
- this.fetchUrl();
1378
- }
1379
- const urlValue = this.urlInput.value;
1380
- if (urlValue.match(urlRegex)) {
1381
- this.urlInputButton.disabled = false;
1382
- this.urlInputButton.classList.remove('close-button');
1383
- }
1384
- else {
1385
- this.urlInputButton.disabled = true;
1386
- this.urlInputButton.classList.add('close-button');
1387
- }
1388
- }
1389
2232
  ungrabExplorer() {
1390
2233
  this.explorer.grabbed = false;
1391
2234
  }
1392
2235
  };
1393
- TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss, formsCss, spinnerCss];
2236
+ TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss, formsCss, spinnerCss, tooltipCss];
1394
2237
  __decorate([
1395
2238
  query('#overviewPanel')
1396
2239
  ], TheDoctor.prototype, "overviewPanel", void 0);
@@ -1433,6 +2276,15 @@ __decorate([
1433
2276
  __decorate([
1434
2277
  property({ type: Boolean })
1435
2278
  ], TheDoctor.prototype, "unavailable", void 0);
2279
+ __decorate([
2280
+ query('#editor-url-overlay')
2281
+ ], TheDoctor.prototype, "urlOverlay", void 0);
2282
+ __decorate([
2283
+ query('#url-problem')
2284
+ ], TheDoctor.prototype, "urlProblem", void 0);
2285
+ __decorate([
2286
+ query('#url-spinner')
2287
+ ], TheDoctor.prototype, "urlSpinner", void 0);
1436
2288
  __decorate([
1437
2289
  property()
1438
2290
  ], TheDoctor.prototype, "doctorEndpoint", void 0);
@@ -1448,6 +2300,15 @@ __decorate([
1448
2300
  __decorate([
1449
2301
  query('sl-split-panel.split-panel')
1450
2302
  ], TheDoctor.prototype, "splitPanel", void 0);
2303
+ __decorate([
2304
+ query('#explorer-split-panel')
2305
+ ], TheDoctor.prototype, "splitPanelExplorer", void 0);
2306
+ __decorate([
2307
+ query('#rolodex-split-panel')
2308
+ ], TheDoctor.prototype, "splitPanelRolodex", void 0);
2309
+ __decorate([
2310
+ query('#inspector-split-panel')
2311
+ ], TheDoctor.prototype, "splitPanelInspector", void 0);
1451
2312
  __decorate([
1452
2313
  state()
1453
2314
  ], TheDoctor.prototype, "rulesetPulse", void 0);
@@ -1456,18 +2317,14 @@ __decorate([
1456
2317
  ], TheDoctor.prototype, "explorerVisible", void 0);
1457
2318
  __decorate([
1458
2319
  state()
1459
- ], TheDoctor.prototype, "activeURL", void 0);
1460
- __decorate([
1461
- query('#editor-url-overlay')
1462
- ], TheDoctor.prototype, "urlOverlay", void 0);
2320
+ ], TheDoctor.prototype, "minimapVisible", void 0);
1463
2321
  __decorate([
1464
- query('#url-problem')
1465
- ], TheDoctor.prototype, "urlProblem", void 0);
2322
+ state()
2323
+ ], TheDoctor.prototype, "activeURL", void 0);
1466
2324
  __decorate([
1467
- query('#url-spinner')
1468
- ], TheDoctor.prototype, "urlSpinner", void 0);
2325
+ state()
2326
+ ], TheDoctor.prototype, "rolodexNeedsReset", void 0);
1469
2327
  TheDoctor = __decorate([
1470
2328
  customElement("pb33f-doctor")
1471
2329
  ], TheDoctor);
1472
2330
  export { TheDoctor };
1473
- const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;