@pb33f/cowboy-components 0.7.10 → 0.7.11

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.
@@ -55,6 +55,7 @@ import { ModelController } from '../../controllers/model-controller.js';
55
55
  import { DiagnosticController } from '../../controllers/diagnostic-controller.js';
56
56
  import { StateController } from '../../controllers/state-controller.js';
57
57
  import { WorkspaceController } from "../../controllers/workspace-controller";
58
+ import { WalletController } from "../../controllers/wallet-controller.js";
58
59
  export declare const GraphBag = "pb33f-doctor-graph";
59
60
  export declare const PanelStateBag = "pb33f-doctor-panel-state";
60
61
  export declare const RolodexResponseBag = "pb33f-doctor-rolodex-response";
@@ -180,6 +181,7 @@ export declare class TheDoctor extends LitElement {
180
181
  readonly diagnosticController: DiagnosticController;
181
182
  readonly stateController: StateController;
182
183
  readonly workspaceController: WorkspaceController;
184
+ readonly walletController: WalletController;
183
185
  editorMap: Map<string, SpecEditor>;
184
186
  selectedEditorTab: string;
185
187
  sidebarClosed: boolean;
@@ -219,6 +221,7 @@ export declare class TheDoctor extends LitElement {
219
221
  filterTreeModel(event: CustomEvent<ExplorerEqualizerChangedEvent>): void;
220
222
  exportRuleset(): void;
221
223
  addToastEvent(event: CustomEvent<AddToastEvent>): void;
224
+ creditStreamUpdated(event: CustomEvent): void;
222
225
  sendToast(toast: Toast): void;
223
226
  updateInspectorDivider(): void;
224
227
  updateExplorerDivider(): void;
@@ -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, ArchiveURLRequested, BuiltInRulesetChanged, CreditEmpty, CustomRulesetEnabled, DocumentReferenceClicked, EditorClicked, EditorUpdated, ExplorerEqualizerChanged, ExplorerEqualizerFiltered, ExplorerNodeClicked, ExportRuleset, LoadRenderedNodeIntoInspector, ModelTreeNodeClicked, NodeReferenceClicked, NukeWorkspaceEvent, OpenProblemDrawer, OpenSettings, ProblemClicked, Reboot, RolodexRootFileSelected, RolodexTreeNodeClicked, RuleClicked, RulesetSaved, RuleViolationClicked, StartSessionFailed, } from "../../events/doctor.js";
19
+ import { ActiveView, AddToast, ArchiveURLRequested, BuiltInRulesetChanged, CreditEmpty, CustomRulesetEnabled, DocumentReferenceClicked, EditorClicked, EditorUpdated, ExplorerEqualizerChanged, ExplorerEqualizerFiltered, ExplorerNodeClicked, ExportRuleset, LoadRenderedNodeIntoInspector, ModelTreeNodeClicked, NodeReferenceClicked, NukeWorkspaceEvent, OpenProblemDrawer, OpenSettings, ProblemClicked, Reboot, RolodexRootFileSelected, RolodexTreeNodeClicked, RuleClicked, RulesetSaved, RuleViolationClicked, StartSessionFailed, CreditStreamUpdated, } from "../../events/doctor.js";
20
20
  import { ProblemDetailsDrawer } from "../problem-list/details-drawer.js";
21
21
  import { CreateBagManager } from "@pb33f/saddlebag";
22
22
  import { LintingService } from "../../services/linting-service.js";
@@ -67,7 +67,9 @@ import { ModelController } from '../../controllers/model-controller.js';
67
67
  import { DiagnosticController } from '../../controllers/diagnostic-controller.js';
68
68
  import { StateController } from '../../controllers/state-controller.js';
69
69
  import { WorkspaceController } from "../../controllers/workspace-controller";
70
+ import { WalletController } from "../../controllers/wallet-controller.js";
70
71
  import { WorkspaceService } from '../../services/workspace-service.js';
72
+ import { WalletService } from '../../services/wallet-service.js';
71
73
  export const GraphBag = "pb33f-doctor-graph";
72
74
  export const PanelStateBag = "pb33f-doctor-panel-state";
73
75
  export const RolodexResponseBag = "pb33f-doctor-rolodex-response";
@@ -116,6 +118,7 @@ let TheDoctor = class TheDoctor extends LitElement {
116
118
  AuthService.doctorEndpoint = this.doctorEndpoint;
117
119
  TimelineService.doctorEndpoint = this.doctorEndpoint;
118
120
  WorkspaceService.doctorEndpoint = this.doctorEndpoint;
121
+ WalletService.doctorEndpoint = this.doctorEndpoint;
119
122
  this.timeVortex = new TimeVortex();
120
123
  // bus it up
121
124
  this.bus = CreateBus();
@@ -159,6 +162,7 @@ let TheDoctor = class TheDoctor extends LitElement {
159
162
  this.problemController = new ProblemController(this);
160
163
  this.docsController = new DocsController(this);
161
164
  this.diagnosticController = new DiagnosticController(this);
165
+ this.walletController = new WalletController(this);
162
166
  this.workspaceController = new WorkspaceController(this);
163
167
  this.settingsComponent = new DoctorSettings();
164
168
  this.editorMap = new Map();
@@ -205,6 +209,8 @@ let TheDoctor = class TheDoctor extends LitElement {
205
209
  if (this.authController.authenticated) {
206
210
  this.workspaceController.getWorkspaces();
207
211
  }
212
+ // update workspace view to reflect new credit balance
213
+ this.workspaceController.workspaceView.requestUpdate();
208
214
  };
209
215
  // create auth controller
210
216
  this.authController = new AuthController(this, sessionCallback, true);
@@ -226,6 +232,8 @@ let TheDoctor = class TheDoctor extends LitElement {
226
232
  this.addEventListener(RulesetSaved, this.ruleController.rulesetSaved.bind(this.ruleController));
227
233
  // @ts-ignore
228
234
  this.addEventListener(AddToast, this.addToastEvent);
235
+ // @ts-ignore
236
+ this.addEventListener(CreditStreamUpdated, this.creditStreamUpdated.bind(this));
229
237
  this.addEventListener(ExportRuleset, this.exportRuleset);
230
238
  // @ts-ignore
231
239
  this.addEventListener(RuleClicked, this.ruleController.ruleClicked.bind(this.ruleController));
@@ -363,6 +371,14 @@ let TheDoctor = class TheDoctor extends LitElement {
363
371
  addToastEvent(event) {
364
372
  this.sendToast(event.detail.toast);
365
373
  }
374
+ creditStreamUpdated(event) {
375
+ // Update session credits
376
+ if (this.authController?.session) {
377
+ this.authController.session.creditsRemaining = event.detail.credits;
378
+ }
379
+ // Trigger workspace view update directly
380
+ this.workspaceController.workspaceView.requestUpdate();
381
+ }
366
382
  sendToast(toast) {
367
383
  this.toastManager.addToastManually(toast);
368
384
  }
@@ -36,5 +36,18 @@ export default css `
36
36
  display: inline-block;
37
37
  }
38
38
 
39
+ sl-button > strong {
40
+ color: inherit;
41
+ }
42
+
43
+ strong {
44
+ font-family: var(--font-stack-bold), sans-serif;
45
+ color: var(--primary-color)
46
+ }
47
+
48
+ strong.secondary {
49
+ font-family: var(--font-stack-bold), sans-serif;
50
+ color: var(--secondary-color)
51
+ }
39
52
 
40
53
  `;
@@ -1,5 +1,6 @@
1
1
  import { TheDoctor } from "../the-doctor/the-doctor.js";
2
2
  import { WorkspaceController } from "../../controllers/workspace-controller.js";
3
+ import { WalletController } from "../../controllers/wallet-controller.js";
3
4
  import { Workspace } from "../../model/workspace.js";
4
5
  import { Formable } from "../../model/formable.js";
5
6
  import { WorkspaceForm } from "./workspace-form.js";
@@ -11,11 +12,14 @@ export declare class WorkspacesView extends Formable {
11
12
  mutateWorkspaceActive: boolean;
12
13
  workspaces: Workspace[];
13
14
  wsController: WorkspaceController;
15
+ walletController: WalletController;
14
16
  workspaceForm: WorkspaceForm;
15
17
  destroyDialog: WorkspaceDestroyView;
16
- constructor(doc: TheDoctor, wsController: WorkspaceController);
18
+ constructor(doc: TheDoctor, wsController: WorkspaceController, walletController: WalletController);
17
19
  private setupDestroyDialogEvents;
18
20
  private setupWorkspaceFormEvents;
21
+ private setupWalletEvents;
22
+ private handleDailyCreditRequest;
19
23
  private handleWorkspaceFormComplete;
20
24
  openCreateWorkspaceDialog(): void;
21
25
  selectWorkspace(workspace: Workspace): void;
@@ -9,24 +9,27 @@ import { customElement, state } from "lit/decorators.js";
9
9
  import { Formable } from "../../model/formable.js";
10
10
  import { WorkspaceForm } from "./workspace-form.js";
11
11
  import { WorkspaceDestroyView } from "./workspace-destroy-dialog.js";
12
- import { DeleteWorkspace, SwitchWorkspace, FormSubmitComplete, RefreshWorkspaces, CreateWorkspace, UpdateWorkspace } from "../../events/doctor.js";
12
+ import { DeleteWorkspace, SwitchWorkspace, FormSubmitComplete, RefreshWorkspaces, CreateWorkspace, UpdateWorkspace, DailyCreditRequested, DailyCreditApplied, DailyCreditErrorChanged } from "../../events/doctor.js";
13
13
  import { AttentionType } from "../attention-box/attention-box.js";
14
14
  import listsCss from "../../css/lists.css.js";
15
15
  import workspaceViewCss from "./workspace-view.css.js";
16
16
  import badgesCss from "../../css/badges.css.js";
17
17
  import linksCss from "../../css/links.css.js";
18
18
  import tooltipCss from "../../css/tooltip.css.js";
19
+ import hrCss from "../../css/hr.css.js";
19
20
  let WorkspacesView = class WorkspacesView extends Formable {
20
- constructor(doc, wsController) {
21
+ constructor(doc, wsController, walletController) {
21
22
  super(doc);
22
23
  this.doc = doc;
23
24
  this.wsController = wsController;
25
+ this.walletController = walletController;
24
26
  this.active = false;
25
27
  this.mutateWorkspaceActive = false;
26
28
  this.workspaceForm = new WorkspaceForm();
27
29
  this.destroyDialog = new WorkspaceDestroyView();
28
30
  this.setupDestroyDialogEvents();
29
31
  this.setupWorkspaceFormEvents();
32
+ this.setupWalletEvents();
30
33
  }
31
34
  setupDestroyDialogEvents() {
32
35
  this.destroyDialog.addEventListener('cancel', () => this.handleDestroyCancel());
@@ -51,6 +54,16 @@ let WorkspacesView = class WorkspacesView extends Formable {
51
54
  }));
52
55
  });
53
56
  }
57
+ setupWalletEvents() {
58
+ this.walletController.addEventListener(DailyCreditApplied, () => this.requestUpdate());
59
+ this.walletController.addEventListener(DailyCreditErrorChanged, () => this.requestUpdate());
60
+ }
61
+ handleDailyCreditRequest() {
62
+ this.walletController.dispatchEvent(new CustomEvent(DailyCreditRequested, {
63
+ bubbles: true,
64
+ composed: true
65
+ }));
66
+ }
54
67
  handleWorkspaceFormComplete(_) {
55
68
  // Refresh the workspace list after create/update
56
69
  this.wsController.dispatchEvent(new CustomEvent(RefreshWorkspaces, {
@@ -178,12 +191,39 @@ let WorkspacesView = class WorkspacesView extends Formable {
178
191
  `;
179
192
  })}
180
193
  </ul>
194
+
195
+ <hr/>
196
+ <h3>
197
+ <span style="color: var(--terminal-text)">$</span>
198
+ Free Daily Credit <span style="color: var(--terminal-text)">$</span>
199
+ </h3>
200
+
201
+ <p>
202
+ Every day, authenticated users get a free daily allowance that is <strong class="secondary">way higher</strong>
203
+ than anonymous users. All you have to do is <strong class="secondary">push the button.</strong>
204
+ </p>
205
+
206
+ ${this.walletController.canRequestCredit ? html `
207
+ <sl-button @click="${this.handleDailyCreditRequest}">
208
+ <strong>$</strong> Claim <strong>FREE</strong> daily credit! <strong>$</strong>
209
+ </sl-button>
210
+ ` : html `
211
+ <sl-button disabled variant="${this.walletController.dailyCreditError ? 'danger' : 'default'}">
212
+ Daily free credit claimed!
213
+ </sl-button>
214
+ `}
215
+
216
+ ${this.walletController.creditStatusText ? html `
217
+ <span style="margin-left: 10px; font-size: 0.9em;">
218
+ ${this.walletController.creditStatusText}
219
+ </span>
220
+ ` : ''}
181
221
  </div>
182
222
 
183
223
  `;
184
224
  }
185
225
  };
186
- WorkspacesView.styles = [...Formable.styles, workspaceViewCss, badgesCss, linksCss, listsCss, tooltipCss];
226
+ WorkspacesView.styles = [...Formable.styles, workspaceViewCss, badgesCss, linksCss, listsCss, tooltipCss, hrCss];
187
227
  __decorate([
188
228
  state()
189
229
  ], WorkspacesView.prototype, "active", void 0);
@@ -1,5 +1,6 @@
1
1
  import { DefaultDocument } from "../components/the-doctor/the-doctor.js";
2
2
  import { Command, CreditStreamChannel, DoctorServiceChannel, isBrokerResponse, QueuePrefix, SpecStreamChannel } from "../model/channels.js";
3
+ import { CreditStreamUpdated } from "../events/doctor.js";
3
4
  export class BrokerController extends EventTarget {
4
5
  constructor(doc) {
5
6
  super();
@@ -100,7 +101,14 @@ export class BrokerController extends EventTarget {
100
101
  creditStreamHandler() {
101
102
  return (msg) => {
102
103
  if (msg.payload?.payload != null) {
103
- this.doc.creditTicker.credits = parseInt(msg.payload.payload);
104
+ const newCredits = parseInt(msg.payload.payload);
105
+ this.doc.creditTicker.credits = newCredits;
106
+ // Dispatch event to the doctor for credit stream updates
107
+ this.doc.dispatchEvent(new CustomEvent(CreditStreamUpdated, {
108
+ bubbles: true,
109
+ composed: true,
110
+ detail: { credits: newCredits }
111
+ }));
104
112
  }
105
113
  };
106
114
  }
@@ -0,0 +1,13 @@
1
+ import { TheDoctor } from "../components/the-doctor/the-doctor.js";
2
+ import { AuthController } from "./auth-controller.js";
3
+ import { TemplateResult } from "lit";
4
+ export declare class WalletController extends EventTarget {
5
+ readonly doc: TheDoctor;
6
+ authController: AuthController | null;
7
+ dailyCreditError: boolean;
8
+ constructor(doc: TheDoctor);
9
+ get creditsRemaining(): number;
10
+ get canRequestCredit(): boolean;
11
+ get creditStatusText(): TemplateResult;
12
+ requestDailyCredit(): void;
13
+ }
@@ -0,0 +1,93 @@
1
+ import { WalletService } from "../services/wallet-service.js";
2
+ import { AddToast, DailyCreditRequested, DailyCreditApplied, DailyCreditErrorChanged } from "../events/doctor.js";
3
+ import { ToastType } from "../model/toast.js";
4
+ import { AuthController } from "./auth-controller.js";
5
+ import { html } from "lit";
6
+ export class WalletController extends EventTarget {
7
+ constructor(doc) {
8
+ super();
9
+ this.dailyCreditError = false;
10
+ this.doc = doc;
11
+ this.authController = AuthController.getInstance();
12
+ this.addEventListener(DailyCreditRequested, this.requestDailyCredit.bind(this));
13
+ }
14
+ get creditsRemaining() {
15
+ return this.authController?.session?.creditsRemaining || 0;
16
+ }
17
+ get canRequestCredit() {
18
+ return !this.dailyCreditError && this.creditsRemaining < 10;
19
+ }
20
+ get creditStatusText() {
21
+ if (this.dailyCreditError) {
22
+ return html `Free credit <strong>already claimed</strong> for today!`;
23
+ }
24
+ if (this.creditsRemaining >= 10) {
25
+ const remaining = this.creditsRemaining - 9;
26
+ return html `spend <strong>${remaining}</strong> ${remaining > 1 ? 'credits' : 'credit'} to qualify.`;
27
+ }
28
+ return html ``;
29
+ }
30
+ requestDailyCredit() {
31
+ if (!this.canRequestCredit) {
32
+ return;
33
+ }
34
+ WalletService.applyDailyCredit().then((wallet) => {
35
+ if (this.authController?.session) {
36
+ this.authController.session.creditsRemaining = wallet.balance;
37
+ }
38
+ this.doc.dispatchEvent(new CustomEvent(AddToast, {
39
+ bubbles: true,
40
+ composed: true,
41
+ detail: {
42
+ toast: {
43
+ id: crypto.randomUUID(),
44
+ title: "Daily credit applied",
45
+ type: ToastType.SUCCESS,
46
+ body: `Daily credit applied! You now have ${wallet.balance} credits.`
47
+ }
48
+ }
49
+ }));
50
+ this.dispatchEvent(new CustomEvent(DailyCreditApplied, {
51
+ bubbles: true,
52
+ composed: true,
53
+ detail: { wallet }
54
+ }));
55
+ }).catch((e) => {
56
+ if (e.status === 403) {
57
+ this.dailyCreditError = true;
58
+ // Dispatch event to trigger UI update
59
+ this.dispatchEvent(new CustomEvent(DailyCreditErrorChanged, {
60
+ bubbles: true,
61
+ composed: true,
62
+ detail: { error: true }
63
+ }));
64
+ this.doc.dispatchEvent(new CustomEvent(AddToast, {
65
+ bubbles: true,
66
+ composed: true,
67
+ detail: {
68
+ toast: {
69
+ id: crypto.randomUUID(),
70
+ title: "Unable to request free credit",
71
+ type: ToastType.ERROR,
72
+ body: e.detail || "You already claimed your daily credit. Try again tomorrow!"
73
+ }
74
+ }
75
+ }));
76
+ }
77
+ else {
78
+ this.doc.dispatchEvent(new CustomEvent(AddToast, {
79
+ bubbles: true,
80
+ composed: true,
81
+ detail: {
82
+ toast: {
83
+ id: crypto.randomUUID(),
84
+ title: "Unable to request free credit",
85
+ type: ToastType.ERROR,
86
+ body: e.detail || `Failed to apply daily credit`
87
+ }
88
+ }
89
+ }));
90
+ }
91
+ });
92
+ }
93
+ }
@@ -8,7 +8,7 @@ export class WorkspaceController extends EventTarget {
8
8
  super();
9
9
  this.doc = doc;
10
10
  this.bus = CreateBus();
11
- this.workspaceView = new WorkspacesView(this.doc, this);
11
+ this.workspaceView = new WorkspacesView(this.doc, this, this.doc.walletController);
12
12
  this.addEventListener(RefreshWorkspaces, this.getWorkspaces.bind(this));
13
13
  // @ts-ignore
14
14
  this.addEventListener(SwitchWorkspace, this.switchWorkspace.bind(this));