|
1 | 1 | <script lang="ts"> |
2 | | - import { onMount, onDestroy } from "svelte"; |
| 2 | + import { onMount, onDestroy, tick } from "svelte"; |
3 | 3 | import { useRoute } from "@dvcol/svelte-simple-router/router"; |
4 | 4 | import { getBackendUrl } from "$lib/utils.js"; |
5 | 5 | import * as Tooltip from "$lib/components/ui/tooltip"; |
|
75 | 75 |
|
76 | 76 | let printer = $state<PrinterResponse | null>(null); |
77 | 77 |
|
| 78 | + function toPercent(value: number): number { |
| 79 | + // value [0, 15] → 0‑100 % |
| 80 | + return Math.round(value / 1.5) * 10; |
| 81 | + } |
| 82 | +
|
| 83 | + function scaleSliderToBackend(value: number, coefficient: number): number { |
| 84 | + // UI → 0‑15, Backend → 0‑255 (coefficient × value) |
| 85 | + const scaled = Math.round(value * coefficient); |
| 86 | + return Math.min(scaled, 255); |
| 87 | + } |
| 88 | +
|
| 89 | + function createSmartControl<T>(getBackendValue: () => T, onCommit: (value: T) => void) { |
| 90 | + let localValue = $state(getBackendValue()); |
| 91 | + let isInteracting = $state(false); |
| 92 | + let timeout: number; |
| 93 | + let skipNextChange = false; |
| 94 | +
|
| 95 | + $effect(() => { |
| 96 | + if (!isInteracting) { |
| 97 | + skipNextChange = true; |
| 98 | + localValue = getBackendValue(); |
| 99 | + tick().then(() => { |
| 100 | + skipNextChange = false; |
| 101 | + }); |
| 102 | + } |
| 103 | + }); |
| 104 | +
|
| 105 | + onDestroy(() => { |
| 106 | + clearTimeout(timeout); |
| 107 | + }); |
| 108 | +
|
| 109 | + return { |
| 110 | + get value() { |
| 111 | + return localValue; |
| 112 | + }, |
| 113 | +
|
| 114 | + onChange(value: T) { |
| 115 | + if (skipNextChange) { |
| 116 | + // Reset just in case several rapid backend updates happen |
| 117 | + skipNextChange = false; |
| 118 | + return; |
| 119 | + } |
| 120 | + console.log("onChange -> ", value); |
| 121 | + localValue = value; |
| 122 | + isInteracting = true; |
| 123 | + clearTimeout(timeout); |
| 124 | + }, |
| 125 | +
|
| 126 | + onCommit(value: T) { |
| 127 | + console.log("onCommit -> ", value); |
| 128 | + localValue = value; |
| 129 | + onCommit(value); |
| 130 | + timeout = setTimeout(() => { |
| 131 | + isInteracting = false; |
| 132 | + }, 7000); |
| 133 | + }, |
| 134 | + }; |
| 135 | + } |
| 136 | +
|
| 137 | + const controls = { |
| 138 | + chamberLight: createSmartControl( |
| 139 | + () => printerStatus?.lights_report?.[0]?.mode === "on", |
| 140 | + (v) => sendWsCommand(new ChamberLight(v)), |
| 141 | + ), |
| 142 | + partFan: createSmartControl( |
| 143 | + () => Number(printerStatus?.cooling_fan_speed || 0), |
| 144 | + (v) => sendWsCommand(new PartFanSpeed(scaleSliderToBackend(v, 15))), |
| 145 | + ), |
| 146 | + chamberFan: createSmartControl( |
| 147 | + () => Number(printerStatus?.big_fan2_speed || 0), |
| 148 | + (v) => sendWsCommand(new ChamberFanSpeed(scaleSliderToBackend(v, 17))), |
| 149 | + ), |
| 150 | + auxFan: createSmartControl( |
| 151 | + () => Number(printerStatus?.big_fan1_speed || 0), |
| 152 | + (v) => sendWsCommand(new AuxFanSpeed(scaleSliderToBackend(v, 17))), |
| 153 | + ), |
| 154 | + }; |
| 155 | +
|
78 | 156 | onMount(async () => { |
79 | 157 | try { |
80 | 158 | const response = await fetch(`${backendUrl}/api/printer/${printerId}`); |
|
524 | 602 | <div class="mt-6 grid grid-cols-1 gap-4"> |
525 | 603 | <div class="bg-400 rounded-lg p-3"> |
526 | 604 | <div class="mb-2 flex justify-between"> |
527 | | - <div class="flex items-center space-x-4"> |
| 605 | + <div class="flex flex-row gap-1"> |
528 | 606 | <CircleGauge /> |
529 | 607 | Speed |
530 | 608 | </div> |
|
549 | 627 | <!-- Chamber Controls (Lower Right) --> |
550 | 628 | <Card class="space-y-6"> |
551 | 629 | <CardContent class="p-4"> |
552 | | - <div class="grid grid-cols-4 gap-4"> |
553 | | - <div class="flex flex-col items-center justify-center space-y-1"> |
| 630 | + <div class="flex flex-row gap-4"> |
| 631 | + <div class="flex flex-col items-center justify-center space-y-1 w-full"> |
554 | 632 | <div class="text-400 flex items-center justify-center space-x-2 text-sm"> |
555 | 633 | <Thermometer /> Nozzle |
556 | 634 | </div> |
557 | 635 | <div class="text-center text-xl"> |
558 | 636 | {Math.round(printerStatus?.nozzle_temper ?? 0)}/{printerStatus?.nozzle_target_temper}°C |
559 | 637 | </div> |
560 | 638 | </div> |
561 | | - <div class="flex flex-col items-center justify-center space-y-1"> |
| 639 | + <div class="flex flex-col items-center justify-center space-y-1 w-full"> |
562 | 640 | <div class="text-400 flex items-center justify-center space-x-2 text-sm"> |
563 | 641 | <Thermometer /> Bed |
564 | 642 | </div> |
|
567 | 645 | </div> |
568 | 646 | </div> |
569 | 647 | {#if printer?.supports_chamber_temp} |
570 | | - <div class="flex flex-col items-center justify-center space-y-1"> |
| 648 | + <div class="flex flex-col items-center justify-center space-y-1 w-full"> |
571 | 649 | <div class="text-400 flex items-center justify-center space-x-2 text-sm"> |
572 | 650 | <Thermometer /> Chamber |
573 | 651 | </div> |
574 | 652 | <div class="text-center text-xl">{Math.round(printerStatus?.chamber_temper ?? 0)}°C</div> |
575 | 653 | </div> |
576 | 654 | {/if} |
577 | | - <div class="flex items-center justify-center space-x-2"> |
| 655 | + <div class="flex items-center justify-center space-x-2 w-full"> |
578 | 656 | <Switch |
579 | 657 | id="printer-light" |
580 | | - bind:checked={printerLightOn} |
| 658 | + checked={controls.chamberLight.value} |
581 | 659 | onCheckedChange={(enabled: boolean) => { |
582 | | - sendWsCommand(new ChamberLight(enabled)); |
| 660 | + controls.chamberLight.onChange(enabled); |
| 661 | + controls.chamberLight.onCommit(enabled); |
583 | 662 | }} |
584 | 663 | /> |
585 | 664 | <Label for="printer-light"> |
|
591 | 670 | <div class="mt-6 grid grid-cols-1 gap-4"> |
592 | 671 | <div class="bg-400 rounded-lg p-3"> |
593 | 672 | <div class="mb-2 flex justify-between"> |
594 | | - <div class="flex items-center space-x-4"> |
| 673 | + <div class="flex flex-row gap-1"> |
595 | 674 | <Fan /> |
596 | 675 | Part Cooling |
597 | 676 | </div> |
598 | | - <span>{Math.round((Number(printerStatus?.cooling_fan_speed ?? 0) / 15) * 100)}%</span> |
| 677 | + <span>{toPercent(controls.partFan.value)}%</span> |
599 | 678 | </div> |
600 | 679 | <Slider |
601 | | - onValueCommit={(value: number[]) => { |
602 | | - sendWsCommand(new PartFanSpeed(value[0])); |
603 | | - }} |
604 | | - value={[printerStatus?.cooling_fan_speed]} |
| 680 | + onValueChange={(value: number[]) => controls.partFan.onChange(Math.round(value[0]))} |
| 681 | + onValueCommit={(value: number[]) => controls.partFan.onCommit(Math.round(value[0]))} |
| 682 | + value={[controls.partFan.value]} |
605 | 683 | max={15} |
606 | | - step={1} |
| 684 | + step={1.5} |
607 | 685 | class="w-full" |
608 | 686 | /> |
609 | 687 | </div> |
610 | 688 | <div class="bg-400 rounded-lg p-3"> |
611 | 689 | <div class="mb-2 flex justify-between"> |
612 | | - <div class="flex items-center space-x-4"> |
| 690 | + <div class="flex flex-row gap-1"> |
613 | 691 | <Fan /> |
614 | 692 | Chamber |
615 | 693 | </div> |
616 | | - <span>{Math.round((Number(printerStatus?.big_fan2_speed ?? 0) / 15) * 100)}%</span> |
| 694 | + <span>{toPercent(controls.chamberFan.value)}%</span> |
617 | 695 | </div> |
618 | 696 | <Slider |
619 | | - onValueCommit={(value: number[]) => { |
620 | | - sendWsCommand(new ChamberFanSpeed(value[0])); |
621 | | - }} |
622 | | - value={[printerStatus?.big_fan2_speed || 0]} |
| 697 | + onValueChange={(value: number[]) => controls.chamberFan.onChange(Math.round(value[0]))} |
| 698 | + onValueCommit={(value: number[]) => controls.chamberFan.onCommit(Math.round(value[0]))} |
| 699 | + value={[controls.chamberFan.value]} |
623 | 700 | max={15} |
624 | | - step={1} |
| 701 | + step={1.5} |
625 | 702 | class="w-full" |
626 | 703 | /> |
627 | 704 | </div> |
628 | 705 | <div class="bg-400 rounded-lg p-3"> |
629 | 706 | <div class="mb-2 flex justify-between"> |
630 | | - <div class="flex items-center space-x-4"> |
| 707 | + <div class="flex flex-row gap-1"> |
631 | 708 | <Fan /> |
632 | 709 | Auxillary |
633 | 710 | </div> |
634 | | - <span>{Math.round((Number(printerStatus?.big_fan1_speed ?? 0) / 15) * 100)}%</span> |
| 711 | + <span>{toPercent(controls.auxFan.value)}%</span> |
635 | 712 | </div> |
636 | 713 | <Slider |
637 | | - onValueCommit={(value: number[]) => { |
638 | | - sendWsCommand(new AuxFanSpeed(value[0])); |
639 | | - }} |
640 | | - value={[printerStatus?.big_fan1_speed || 0]} |
| 714 | + onValueChange={(value: number[]) => controls.auxFan.onChange(Math.round(value[0]))} |
| 715 | + onValueCommit={(value: number[]) => controls.auxFan.onCommit(Math.round(value[0]))} |
| 716 | + value={[controls.auxFan.value]} |
641 | 717 | max={15} |
642 | | - step={1} |
| 718 | + step={1.5} |
643 | 719 | class="w-full" |
644 | 720 | /> |
645 | 721 | </div> |
|
0 commit comments