@energy8platform/shell 0.7.2 → 0.9.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.
@@ -362,8 +362,20 @@ export interface SpinDiscOpts {
362
362
  ticker: Ticker;
363
363
  onSpin: () => void;
364
364
  onStop: () => void;
365
+ /** Tapped in the halted-autoplay state — resume the run with the count still on the disc. */
366
+ onResume?: () => void;
365
367
  }
366
368
 
369
+ /**
370
+ * What the disc is showing:
371
+ * - `off` — an ordinary SPIN disc;
372
+ * - `running` — STOP glyph over the live countdown;
373
+ * - `paused` — a run halted with spins still owed (a lost connection). The count stays put and a
374
+ * tap resumes; clearing it is the autoplay button's job. Certification asks for exactly this:
375
+ * the run stops, and after reconnection the counter is still there and still correct.
376
+ */
377
+ export type SpinAutoplayMode = 'off' | 'running' | 'paused';
378
+
367
379
  export class SpinDisc extends Container implements Sizable {
368
380
  private size: number;
369
381
  private glyphSize: number;
@@ -375,13 +387,14 @@ export class SpinDisc extends Container implements Sizable {
375
387
  private glyph: IconView;
376
388
  private dim = new Graphics();
377
389
  private countText?: Text;
378
- private mode: 'spin' | 'stop' = 'spin';
390
+ private mode: 'spin' | 'stop' | 'resume' = 'spin';
379
391
  private _busy = false;
380
392
  private _disabled = false;
381
393
  private hovering = false;
382
394
  private rotTick?: (t: Ticker) => void;
383
395
  private onSpin: () => void;
384
396
  private onStop: () => void;
397
+ private onResume: () => void;
385
398
 
386
399
  constructor(opts: SpinDiscOpts) {
387
400
  super();
@@ -391,6 +404,7 @@ export class SpinDisc extends Container implements Sizable {
391
404
  this.ticker = opts.ticker;
392
405
  this.onSpin = opts.onSpin;
393
406
  this.onStop = opts.onStop;
407
+ this.onResume = opts.onResume ?? opts.onSpin;
394
408
  this.disc = new Graphics();
395
409
  this.glyph = makeIcon('spin', this.glyphSize, this.tokens.btnInk);
396
410
  this.glyph.position.set((this.size - this.glyphSize) / 2, (this.size - this.glyphSize) / 2);
@@ -409,6 +423,7 @@ export class SpinDisc extends Container implements Sizable {
409
423
  attachPress(this, 0.94, () => {
410
424
  if (this._disabled) return;
411
425
  if (this.mode === 'stop') this.onStop();
426
+ else if (this.mode === 'resume') this.onResume();
412
427
  else this.onSpin();
413
428
  });
414
429
  }
@@ -420,8 +435,10 @@ export class SpinDisc extends Container implements Sizable {
420
435
  drawDisc(this.disc, this.size, this.tokens.btn, 4);
421
436
  const glyphColor = hot ? this.tokens.accent : this.tokens.btnInk;
422
437
  this.glyph.setColor(glyphColor);
423
- // the count sits ON the solid (btnInk) STOP square, so it must be light to read
424
- if (this.countText) this.countText.style.fill = '#ffffff';
438
+ // the count sits ON the solid (btnInk) STOP square, so it must be light to read; halted, it
439
+ // sits on the bare disc instead and takes the disc's own ink.
440
+ if (this.countText)
441
+ this.countText.style.fill = this.mode === 'resume' ? this.tokens.btnInk : '#ffffff';
425
442
  // Disabled (mid-spin / can't spin): darken OPAQUELY (≈ filter:grayscale(.4) brightness(.62))
426
443
  // with a dark veil over the disc — not alpha, which would let the bright board show through and
427
444
  // read as a translucent/missing button (what looked "transparent" while spinning).
@@ -431,31 +448,44 @@ export class SpinDisc extends Container implements Sizable {
431
448
  }
432
449
  }
433
450
 
434
- /** STOP glyph + remaining-count, when autoplay runs. */
435
- setAutoplay(active: boolean, remaining: number): void {
436
- if (active) {
437
- this.mode = 'stop';
438
- this.stopRotation();
439
- // STOP glyph at the disc's full size (like SPIN); the count is centred on top of it.
440
- this.glyph.visible = false;
441
- this.stopGlyph();
442
- if (!this.countText) {
443
- this.countText = makeText('', { size: 22, weight: '800', color: '#ffffff', align: 'center', family: NUM_FONT_FAMILY });
444
- this.addChild(this.countText); // added after the STOP glyph → renders on top of it
445
- }
446
- const label = Number.isFinite(remaining) ? String(remaining) : '∞';
447
- setText(this.countText, label);
448
- this.countText.position.set((this.size - this.countText.width) / 2, (this.size - this.countText.height) / 2);
449
- } else {
451
+ /** STOP glyph + remaining-count while autoplay runs; auto glyph + the same count once it halts. */
452
+ setAutoplay(mode: SpinAutoplayMode, remaining: number): void {
453
+ if (mode === 'off') {
450
454
  this.mode = 'spin';
451
455
  this.glyph.visible = true;
452
456
  this.removeStopGlyph();
457
+ this.removeAutoGlyph();
453
458
  if (this.countText) {
454
459
  this.removeChild(this.countText);
455
460
  this.countText.destroy();
456
461
  this.countText = undefined;
457
462
  }
463
+ this.paint();
464
+ return;
465
+ }
466
+ const running = mode === 'running';
467
+ this.mode = running ? 'stop' : 'resume';
468
+ this.stopRotation();
469
+ this.glyph.visible = false;
470
+ // Running: a solid STOP square at the disc's full size (like SPIN), count centred on top of it.
471
+ // Halted: the auto glyph, smaller and lifted, with the count under it — no dark square, because
472
+ // nothing is running to stop, and a white count would have nothing to read against.
473
+ if (running) {
474
+ this.removeAutoGlyph();
475
+ this.stopGlyph();
476
+ } else {
477
+ this.removeStopGlyph();
478
+ this.autoGlyph();
458
479
  }
480
+ if (!this.countText) {
481
+ this.countText = makeText('', { size: 22, weight: '800', color: '#ffffff', align: 'center', family: NUM_FONT_FAMILY });
482
+ this.addChild(this.countText); // added after the glyph → renders on top of it
483
+ }
484
+ setText(this.countText, Number.isFinite(remaining) ? String(remaining) : '∞');
485
+ const cy = running
486
+ ? (this.size - this.countText.height) / 2
487
+ : this.size * 0.56; // sits under the lifted auto glyph
488
+ this.countText.position.set((this.size - this.countText.width) / 2, cy);
459
489
  this.paint();
460
490
  }
461
491
 
@@ -475,6 +505,22 @@ export class SpinDisc extends Container implements Sizable {
475
505
  }
476
506
  }
477
507
 
508
+ private autoGlyphView?: IconView;
509
+ private autoGlyph(): void {
510
+ if (this.autoGlyphView) return;
511
+ const size = this.glyphSize * 0.42;
512
+ this.autoGlyphView = makeIcon('autoplay', size, this.tokens.btnInk);
513
+ this.autoGlyphView.position.set((this.size - size) / 2, this.size * 0.2);
514
+ this.addChild(this.autoGlyphView);
515
+ }
516
+ private removeAutoGlyph(): void {
517
+ if (this.autoGlyphView) {
518
+ this.removeChild(this.autoGlyphView);
519
+ this.autoGlyphView.destroy();
520
+ this.autoGlyphView = undefined;
521
+ }
522
+ }
523
+
478
524
  setBusy(busy: boolean): void {
479
525
  this._busy = busy;
480
526
  if (busy && this.mode === 'spin') this.startRotation();