@pb33f/cowboy-components 0.3.2 → 0.3.4

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 (31) hide show
  1. package/dist/components/manage-ruleset/manage-ruleset.css.js +14 -3
  2. package/dist/components/manage-ruleset/manage-ruleset.d.ts +3 -0
  3. package/dist/components/manage-ruleset/manage-ruleset.js +85 -2
  4. package/dist/components/manage-ruleset/rule-input.js +8 -1
  5. package/dist/components/manage-ruleset/rule.css.js +3 -1
  6. package/dist/components/manage-ruleset/rule.js +42 -3
  7. package/dist/components/model-renderer/schema.js +10 -0
  8. package/dist/components/paginator/paginator-navigator.js +3 -0
  9. package/dist/components/paginator/paginator.css.js +5 -2
  10. package/dist/components/paginator/paginator.d.ts +1 -0
  11. package/dist/components/paginator/paginator.js +9 -3
  12. package/dist/components/problems-overview/diagnostic-evaluation.js +0 -3
  13. package/dist/components/problems-overview/problem-overview-group.d.ts +2 -0
  14. package/dist/components/problems-overview/problem-overview-group.js +15 -2
  15. package/dist/components/problems-overview/problems-overview.js +8 -2
  16. package/dist/components/the-doctor/sparks.d.ts +17 -0
  17. package/dist/components/the-doctor/sparks.js +81 -0
  18. package/dist/components/the-doctor/the-doctor.css.js +74 -26
  19. package/dist/components/the-doctor/the-doctor.d.ts +31 -1
  20. package/dist/components/the-doctor/the-doctor.js +234 -32
  21. package/dist/cowboy-components.umd.cjs +1793 -1642
  22. package/dist/events/doctor.d.ts +6 -2
  23. package/dist/events/doctor.js +1 -0
  24. package/dist/model/channels.d.ts +11 -0
  25. package/dist/model/channels.js +11 -0
  26. package/dist/model/errors.d.ts +1 -1
  27. package/dist/services/header-service.d.ts +5 -0
  28. package/dist/services/header-service.js +16 -0
  29. package/dist/services/linting-service.d.ts +1 -1
  30. package/dist/services/linting-service.js +19 -9
  31. package/package.json +1 -1
@@ -44,6 +44,11 @@ import { ModelTree } from "../model-tree/tree.js";
44
44
  import { ExplorerComponent } from "../visualizer/explorer.js";
45
45
  import { RenderedNodeComponent } from "../model-renderer/rendered-node.js";
46
46
  import tabsCss from "../../css/tabs.css.js";
47
+ import { CreateBus } from "@pb33f/ranch";
48
+ import { Command, DoctorServiceChannel, isBrokerResponse, QueuePrefix, SpecStreamChannel } from "../../model/channels";
49
+ import formsCss from "../../css/forms.css";
50
+ import { PixelSparks } from "./sparks.js";
51
+ import spinnerCss from "../../css/spinner.css";
47
52
  export const GraphBag = "pb33f-doctor-graph";
48
53
  export const DoctorDocumentBag = "pb33f-doctor-editor";
49
54
  export const HowToFixBag = "pb33f-doctor-howtofix";
@@ -67,6 +72,15 @@ let TheDoctor = class TheDoctor extends LitElement {
67
72
  this.debounceTime = 400;
68
73
  this.debounceTimeRuleset = 900;
69
74
  this.bounceId = 0;
75
+ this.useTLS = false;
76
+ // bus it up
77
+ this.bus = CreateBus();
78
+ this.doctorServiceChannel = this.bus.createChannel(DoctorServiceChannel);
79
+ this.specStreamChannel = this.bus.createChannel(SpecStreamChannel);
80
+ this.bus.mapChannelToBrokerDestination(QueuePrefix + DoctorServiceChannel, DoctorServiceChannel);
81
+ this.bus.mapChannelToBrokerDestination(QueuePrefix + SpecStreamChannel, SpecStreamChannel);
82
+ this.doctorChannelSubscription = this.doctorServiceChannel.subscribe(this.doctorServiceHandler());
83
+ this.specChannelSubscription = this.specStreamChannel.subscribe(this.specStreamHandler());
70
84
  // create a stateful bag manager
71
85
  this.bagManager = CreateBagManager(true);
72
86
  this.bagManager.loadStatefulBags().then(this.loadState.bind(this));
@@ -140,21 +154,84 @@ let TheDoctor = class TheDoctor extends LitElement {
140
154
  this.explorer.equalizer.addEventListener(ExplorerEqualizerChanged, this.filterTreeModel.bind(this));
141
155
  //@ts-ignore
142
156
  this.explorer.equalizer.addEventListener(ExplorerEqualizerFiltered, this.filterTreeModel.bind(this));
157
+ // extract port from session storage.
158
+ this.busPort = localStorage.getItem("pb33f-doctor-port");
159
+ this.busHost = localStorage.getItem("pb33f-doctor-host");
160
+ if (!this.busPort) {
161
+ this.busPort = "9090"; // default port
162
+ }
163
+ if (!this.busHost) {
164
+ this.busHost = "localhost"; // default host
165
+ }
166
+ const useTLS = localStorage.getItem("pb33f-doctor-tls");
167
+ if (useTLS && useTLS == 'true') {
168
+ this.useTLS = true;
169
+ }
143
170
  // hijack navigation buttons.
144
171
  window.addEventListener('popstate', (e) => {
145
172
  const state = e.state;
146
173
  if (state) {
147
- switch (state.view) {
148
- case ActiveView.Problems:
149
- this.controlTabGroup.show(ActiveView.Problems);
150
- break;
151
- case ActiveView.Overview:
152
- this.controlTabGroup.show(ActiveView.Overview);
153
- break;
174
+ if (state.activeNode) {
175
+ this.modelTreeNodeClicked(new CustomEvent(ExplorerNodeClicked, {
176
+ detail: {
177
+ nodeHashId: state.activeNode,
178
+ noState: true
179
+ }
180
+ }));
154
181
  }
155
182
  }
156
183
  });
157
- //history.pushState({view: ActiveView.Overview}, "", ActiveView.Overview);
184
+ }
185
+ whoAmI() {
186
+ this.bus.publish({
187
+ destination: "/p/q/" + DoctorServiceChannel,
188
+ body: JSON.stringify({ request: Command.WhoAmI }),
189
+ });
190
+ }
191
+ connectToBroker() {
192
+ let protocol = "ws://";
193
+ if (this.useTLS) {
194
+ protocol = "wss://";
195
+ }
196
+ // configure wiretap broker.
197
+ const config = {
198
+ brokerURL: protocol + this.busHost + ':' + this.busPort + '/ranch',
199
+ heartbeatIncoming: 0,
200
+ heartbeatOutgoing: 0,
201
+ onConnect: () => {
202
+ console.log("💊 Connected to the %cOpenAPI Doctor%c, we are ready to communicate.", 'background: #0d1117; color: #62C4FFFF; font-weight: bold', 'color: default');
203
+ this.whoAmI();
204
+ }
205
+ };
206
+ this.bus.connectToBroker(config);
207
+ }
208
+ addClickTrack(node) {
209
+ history.pushState({ activeNode: node.idHash }, "", `?view=explore&node=${node.idHash}`);
210
+ }
211
+ doctorServiceHandler() {
212
+ return (msg) => {
213
+ if (msg.payload?.payload != null) {
214
+ if (isBrokerResponse(msg.payload.payload)) {
215
+ 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();
219
+ }
220
+ }
221
+ };
222
+ }
223
+ specStreamHandler() {
224
+ return (msg) => {
225
+ if (msg.payload?.payload != null) {
226
+ // base64 decode the payload and update the editor!
227
+ const decoded = atob(msg.payload.payload);
228
+ if (this.docBag) {
229
+ this.docBag.set(DefaultDocument, decoded);
230
+ }
231
+ this.editor?.setValue(decoded, true);
232
+ this.requestUpdate();
233
+ }
234
+ };
158
235
  }
159
236
  filterTreeModel(event) {
160
237
  this.filteredNodes = new Map();
@@ -206,6 +283,10 @@ let TheDoctor = class TheDoctor extends LitElement {
206
283
  if (renderedNode) {
207
284
  this.renderedNode.node = renderedNode;
208
285
  }
286
+ if (!evt.detail.noState) {
287
+ this.addClickTrack(node);
288
+ evt.detail.noState = true;
289
+ }
209
290
  this.explorerNodeClicked(evt);
210
291
  this.viewerPanel.click();
211
292
  }
@@ -223,6 +304,9 @@ let TheDoctor = class TheDoctor extends LitElement {
223
304
  this.activeNode = node.body.node;
224
305
  this.explorer.activeNode = node.body.node;
225
306
  found = true;
307
+ if (!evt.detail.noState) {
308
+ this.addClickTrack(node.body.node);
309
+ }
226
310
  }
227
311
  else {
228
312
  node.body.active = false;
@@ -353,9 +437,19 @@ let TheDoctor = class TheDoctor extends LitElement {
353
437
  }
354
438
  }
355
439
  }
356
- lintSpec(value) {
440
+ lintSpec(value, url) {
357
441
  this.activitySpinner.show();
358
- LintingService.lintFile(value).then((result) => {
442
+ if (url) {
443
+ this.urlProblem.style.display = 'none';
444
+ this.urlOverlay.style.display = "block";
445
+ this.urlSpinner.style.display = "block";
446
+ }
447
+ LintingService.lintFile(value, this.brokerConnectionId, url).then((result) => {
448
+ if (url) {
449
+ this.urlOverlay.style.display = "none";
450
+ this.urlSpinner.style.display = "none";
451
+ this.urlProblem.style.display = 'none';
452
+ }
359
453
  if (result && !Array.isArray(result)) {
360
454
  this.editor.setMarkers([result]);
361
455
  this.problemBag?.set(DocumentProblems, [result]);
@@ -415,21 +509,26 @@ let TheDoctor = class TheDoctor extends LitElement {
415
509
  this.platformUnavailable(e);
416
510
  });
417
511
  }).catch((e) => {
418
- this.platformUnavailable(e);
419
- console.error("so sorry, the doctor cannot see you right now, the clinic is closed.");
420
- if (e) {
421
- console.error(e.detail);
422
- if (e.instance === 'https://pb33f.io/errors/no-credit-remaining') {
423
- this.statusBar.callsRemaining = 0;
424
- this.statusBar.visible = true;
425
- this.sendToast({
426
- id: crypto.randomUUID(),
427
- type: ToastType.ERROR,
428
- body: "Run out of credit, please authenticate for more or wait 24 hours.",
429
- title: "Credit exhausted!",
430
- });
512
+ if (!url) {
513
+ this.platformUnavailable(e);
514
+ console.error("so sorry, the doctor cannot see you right now, the clinic is closed.");
515
+ if (e) {
516
+ console.error(e.detail);
517
+ if (e.instance === 'https://pb33f.io/errors/no-credit-remaining') {
518
+ this.statusBar.callsRemaining = 0;
519
+ this.statusBar.visible = true;
520
+ this.sendToast({
521
+ id: crypto.randomUUID(),
522
+ type: ToastType.ERROR,
523
+ body: "Run out of credit, please authenticate for more or wait 24 hours.",
524
+ title: "Credit exhausted!",
525
+ });
526
+ }
431
527
  }
432
528
  }
529
+ else {
530
+ this.showUrlError(e);
531
+ }
433
532
  });
434
533
  }
435
534
  platformUnavailable(error) {
@@ -527,6 +626,7 @@ let TheDoctor = class TheDoctor extends LitElement {
527
626
  FeedbackService.doctorEndpoint = this.doctorEndpoint;
528
627
  RulesetService.doctorEndpoint = this.doctorEndpoint;
529
628
  ModelService.doctorEndpoint = this.doctorEndpoint;
629
+ this.connectToBroker();
530
630
  this.graphBag = this.bagManager.getBag(GraphBag);
531
631
  this.docBag = this.bagManager.getBag(DoctorDocumentBag);
532
632
  if (this.docBag) {
@@ -635,7 +735,6 @@ let TheDoctor = class TheDoctor extends LitElement {
635
735
  else {
636
736
  this.manageRuleset.rulesetConfig = { ruleMapping: new Map(), allRulesSwitch: true };
637
737
  }
638
- this.loadingOverlay.hide();
639
738
  setTimeout(() => {
640
739
  this.manageRuleset.buildRulesets();
641
740
  if (this.CustomRuleset && this.CustomRuleset.rules.size > 0) {
@@ -817,9 +916,9 @@ let TheDoctor = class TheDoctor extends LitElement {
817
916
  LintingService.startSession().then((session) => {
818
917
  this.session = session;
819
918
  // bootstrap async
820
- setTimeout(() => {
821
- this.boostrap();
822
- });
919
+ // setTimeout(() => {
920
+ // this.boostrap();
921
+ // });
823
922
  LintingService.fetchAllHowToFix().then((result) => {
824
923
  if (result) {
825
924
  result.forEach((howToFix) => {
@@ -973,6 +1072,14 @@ let TheDoctor = class TheDoctor extends LitElement {
973
1072
  }, this.debounceTime);
974
1073
  }
975
1074
  boostrap() {
1075
+ // if the url is set in the query string, fetch it and run a lint with the URL
1076
+ const url = new URL(window.location.href);
1077
+ const urlParam = url.searchParams.get('url');
1078
+ if (urlParam) {
1079
+ this.urlInput.value = urlParam;
1080
+ this.lintSpec('', urlParam);
1081
+ return;
1082
+ }
976
1083
  if (this.editor.getValue() === '') {
977
1084
  LintingService.bootstrapEditor().then((result) => {
978
1085
  this.editor.setValue(result, true);
@@ -1137,6 +1244,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1137
1244
  <sl-icon-button class="collapse-side" name="chevron-bar-right"
1138
1245
  @click="${this.toggleSidebar}"></sl-icon-button>
1139
1246
  </div>`;
1247
+ let sparks = new PixelSparks();
1140
1248
  return html `
1141
1249
  ${welcomeBox}
1142
1250
  ${this.toastManager}
@@ -1167,7 +1275,7 @@ let TheDoctor = class TheDoctor extends LitElement {
1167
1275
  ${this.activitySpinner}
1168
1276
  ${this.errorBanner}
1169
1277
  ${overlay}
1170
-
1278
+
1171
1279
  <sl-split-panel class="split-panel-explorer" position="12">
1172
1280
  <sl-icon id="split-divider-tree" slot="divider" name="grip-vertical"></sl-icon>
1173
1281
  <div slot="start" class="model-tree">
@@ -1180,10 +1288,10 @@ let TheDoctor = class TheDoctor extends LitElement {
1180
1288
  ${this.detailsDrawer}
1181
1289
  <sl-tab-group class="tab-group" @sl-tab-show="${this.selectEditorTab}"
1182
1290
  id="editor-controls">
1291
+
1183
1292
  <sl-tab slot="nav" panel="spec" class="tab" id="spec"
1184
1293
  @click="${this.closeExplorer}">
1185
- OpenAPI
1186
- Spec
1294
+ OpenAPI Spec
1187
1295
  </sl-tab>
1188
1296
  <sl-tab slot="nav" panel="explorer" class="tab" id="explorer"
1189
1297
  @click="${this.toggleExplorer}">Explorer
@@ -1192,11 +1300,45 @@ let TheDoctor = class TheDoctor extends LitElement {
1192
1300
  @click="${this.closeExplorer}">
1193
1301
  Ruleset ${rulesetPulsePill}
1194
1302
  </sl-tab>
1195
- <sl-tab-panel name="spec" class="tab-panel">${this.editor}</sl-tab-panel>
1303
+
1304
+ <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
+ <div class="main-view">
1318
+ <div id="editor-url-overlay" class="editor-url-overlay">
1319
+ <div id="url-spinner">
1320
+ <div class="pb33f-loader">
1321
+ <div class="spin"></div>
1322
+ Fetching from URL '<strong>${this.activeURL}</strong>'...
1323
+ </div>
1324
+ </div>
1325
+ <div id="url-problem" class="url-problem">
1326
+ <pb33f-attention-box type="warning" headerText="Problem with URL">
1327
+ <h4>Error: ${this.urlErrorCode}</h4>
1328
+ <p>${this.urlErrorMessage}</p>
1329
+ <sl-button @click="${this.hideUrlError}">OK</sl-button>
1330
+ <p></p>
1331
+ </pb33f-attention-box>
1332
+ </div>
1333
+ </div>
1334
+ ${this.editor}
1335
+ </div>
1336
+ </sl-tab-panel>
1196
1337
  <sl-tab-panel name="ruleset" class="tab-panel">${this.rulesetEditor}</sl-tab-panel>
1197
1338
  <sl-tab-panel name="explorer" class="tab-panel"
1198
1339
  @mouseleave="${this.ungrabExplorer}">${this.explorer}
1199
1340
  </sl-tab-panel>
1341
+
1200
1342
  </sl-tab-group>
1201
1343
  </div>
1202
1344
  ${mainPanelView}
@@ -1205,12 +1347,50 @@ let TheDoctor = class TheDoctor extends LitElement {
1205
1347
  </sl-split-panel>
1206
1348
  ${this.statusBar}
1207
1349
  </div>`;
1350
+ // docs <iframe src="/docs.html" width="100%" height="100%"
1351
+ // style="border: none"></iframe>
1352
+ // <sl-tab slot="nav" panel="docs" class="tab" id="docs">
1353
+ // Docs
1354
+ // </sl-tab>
1355
+ // <sl-tab-panel name="docs" class="tab-panel">
1356
+ // </sl-tab-panel>
1357
+ }
1358
+ fetchUrl() {
1359
+ this.activeURL = this.urlInput.value;
1360
+ this.lintSpec('', this.urlInput.value);
1361
+ }
1362
+ hideUrlError() {
1363
+ this.urlProblem.style.display = 'none';
1364
+ this.urlOverlay.style.display = 'none';
1365
+ this.urlSpinner.style.display = 'none';
1366
+ }
1367
+ showUrlError(e) {
1368
+ this.urlErrorCode = e.status;
1369
+ this.urlErrorMessage = e.detail;
1370
+ this.urlSpinner.style.display = 'none';
1371
+ this.urlOverlay.style.display = 'block';
1372
+ this.urlProblem.style.display = 'block';
1373
+ this.requestUpdate();
1374
+ }
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
+ }
1208
1388
  }
1209
1389
  ungrabExplorer() {
1210
1390
  this.explorer.grabbed = false;
1211
1391
  }
1212
1392
  };
1213
- TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss];
1393
+ TheDoctor.styles = [theDoctorCss, linksCss, dialogCss, buttonCss, radioGroupsCss, tabsCss, formsCss, spinnerCss];
1214
1394
  __decorate([
1215
1395
  query('#overviewPanel')
1216
1396
  ], TheDoctor.prototype, "overviewPanel", void 0);
@@ -1220,6 +1400,9 @@ __decorate([
1220
1400
  __decorate([
1221
1401
  query('#viewerPanel')
1222
1402
  ], TheDoctor.prototype, "viewerPanel", void 0);
1403
+ __decorate([
1404
+ query('#explorer')
1405
+ ], TheDoctor.prototype, "explorerPanel", void 0);
1223
1406
  __decorate([
1224
1407
  query('sl-tab-group#manager-controls')
1225
1408
  ], TheDoctor.prototype, "controlTabGroup", void 0);
@@ -1241,6 +1424,12 @@ __decorate([
1241
1424
  __decorate([
1242
1425
  query('div.problems-data')
1243
1426
  ], TheDoctor.prototype, "problemsDataDiv", void 0);
1427
+ __decorate([
1428
+ query('#url-input-button')
1429
+ ], TheDoctor.prototype, "urlInputButton", void 0);
1430
+ __decorate([
1431
+ query('#url-input')
1432
+ ], TheDoctor.prototype, "urlInput", void 0);
1244
1433
  __decorate([
1245
1434
  property({ type: Boolean })
1246
1435
  ], TheDoctor.prototype, "unavailable", void 0);
@@ -1265,7 +1454,20 @@ __decorate([
1265
1454
  __decorate([
1266
1455
  state()
1267
1456
  ], TheDoctor.prototype, "explorerVisible", void 0);
1457
+ __decorate([
1458
+ state()
1459
+ ], TheDoctor.prototype, "activeURL", void 0);
1460
+ __decorate([
1461
+ query('#editor-url-overlay')
1462
+ ], TheDoctor.prototype, "urlOverlay", void 0);
1463
+ __decorate([
1464
+ query('#url-problem')
1465
+ ], TheDoctor.prototype, "urlProblem", void 0);
1466
+ __decorate([
1467
+ query('#url-spinner')
1468
+ ], TheDoctor.prototype, "urlSpinner", void 0);
1268
1469
  TheDoctor = __decorate([
1269
1470
  customElement("pb33f-doctor")
1270
1471
  ], TheDoctor);
1271
1472
  export { TheDoctor };
1473
+ const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;