Просмотр исходного кода

Stop the drying badge inventing a temperature on a uniform AMS (#2759)

    The follow-up to the same report: a second AMS 2 Pro, no aux power,
    loaded entirely with PLA and drying at the 45C the reporter picked,
    showed 45C and then switched to 55C.

    Bambu never echoes back a cycle's filament or temperature, so both come
    from the target cached when the command went out, and the fallback for
    a missing cache reads the loaded trays. The first pass narrowed that
    fallback to units whose spools agree on a filament, which fixed the
    mixed-unit case in the original report but left the uniform case
    answering with the spools' RFID-recommended drying_temp -- 55C here.
    Agreement across slots is evidence of what is being dried, because the
    dryer heats all of them. It is no evidence of the temperature, which is
    picked freely in the popover, so the recommendation was never more than
    a guess wearing the same confident "PLA @ 55C" as a known target.

    uniform_tray_drying_hint therefore becomes uniform_tray_filament_hint
    and returns the filament alone. The badge names a temperature only when
    we sent it, and otherwise shows the filament and the countdown.

    Both status builders also stopped filling the two fields independently.
    Entering the fallback when either was missing let a cached filament pair
    with a guessed temperature and render as though both were known; the
    temperature now simply has no fallback to reach.

    The badge required both fields before rendering anything, so dropping
    the temperature would have blanked it rather than shortening it -- the
    frontend now renders each on its own terms. No new translation key: the
    filament type is a passthrough.

    This changes what is shown when the cached target is missing, not why
    it goes missing. If the reporter was on the fixed build, the falling-
    edge gate is still letting a zero through on an unpowered unit, which
    needs a log covering the start of the cycle.
maziggy 3 недель назад
Родитель
Сommit
4335ee3814
87 измененных файлов с 214 добавлено и 790 удалено
  1. 7 13
      backend/app/api/routes/printers.py
  2. 26 30
      backend/app/services/printer_manager.py
  3. 32 13
      backend/tests/unit/services/test_printer_manager.py
  4. 137 0
      frontend/src/__tests__/pages/PrintersPageDryingBadge.test.tsx
  5. 12 4
      frontend/src/pages/PrintersPage.tsx
  6. 0 1
      static/assets/index-B3jj6-fz.css
  7. 0 0
      static/assets/index-BkwFYHam.js
  8. BIN
      static/fonts/inter-latin-ext.woff2
  9. BIN
      static/fonts/inter-latin.woff2
  10. BIN
      static/icons/ams-ht.png
  11. 0 1
      static/icons/ams-settings.svg
  12. 0 9
      static/icons/ams-wiring-center.svg
  13. 0 17
      static/icons/ams-wiring-left.svg
  14. 0 17
      static/icons/ams-wiring-right.svg
  15. BIN
      static/icons/ams.png
  16. 0 1
      static/icons/chamber.svg
  17. BIN
      static/icons/dual-extruder-left.png
  18. BIN
      static/icons/dual-extruder-right.png
  19. BIN
      static/icons/dual-extruder-right_sav.png
  20. BIN
      static/icons/dual-extruder.png
  21. BIN
      static/icons/extruder-change-filament.png
  22. BIN
      static/icons/extruder-left-right.png
  23. 0 51
      static/icons/eye.svg
  24. 0 1
      static/icons/heatbed.svg
  25. 0 44
      static/icons/home.svg
  26. 0 6
      static/icons/hotend.svg
  27. 0 4
      static/icons/humidity-empty.svg
  28. 0 4
      static/icons/humidity-full.svg
  29. 0 4
      static/icons/humidity-half.svg
  30. BIN
      static/icons/jogpad.png
  31. 0 5
      static/icons/jogpad.svg
  32. 0 4
      static/icons/lamp.svg
  33. 0 12
      static/icons/micro-sd.svg
  34. 0 1
      static/icons/reload.svg
  35. 0 0
      static/icons/settings.svg
  36. BIN
      static/icons/single-extruder1.png
  37. BIN
      static/icons/single-extruder2.png
  38. 0 1
      static/icons/skip-objects.svg
  39. 0 53
      static/icons/snowflake.svg
  40. 0 6
      static/icons/speed.svg
  41. 0 4
      static/icons/temperature.svg
  42. 0 6
      static/icons/ventilation.svg
  43. 0 1
      static/icons/video-camera.svg
  44. 0 2
      static/icons/water.svg
  45. 0 73
      static/icons/webcam.svg
  46. BIN
      static/img/android-chrome-192x192.png
  47. BIN
      static/img/android-chrome-512x512.png
  48. BIN
      static/img/apple-touch-icon.png
  49. BIN
      static/img/bambuddy_logo_dark.png
  50. BIN
      static/img/bambuddy_logo_dark_transparent.png
  51. BIN
      static/img/bambuddy_logo_light.png
  52. BIN
      static/img/bed/bed_cool.png
  53. BIN
      static/img/bed/bed_cool_supertack.png
  54. BIN
      static/img/bed/bed_engineering.png
  55. BIN
      static/img/bed/bed_high_templ.png
  56. BIN
      static/img/bed/bed_pei.png
  57. BIN
      static/img/bed/bed_pei_cool.png
  58. BIN
      static/img/favicon-16x16.png
  59. BIN
      static/img/favicon-32x32.png
  60. BIN
      static/img/favicon.png
  61. BIN
      static/img/printers/a1.png
  62. BIN
      static/img/printers/a1f.png
  63. BIN
      static/img/printers/a1mini.png
  64. BIN
      static/img/printers/a2l.png
  65. BIN
      static/img/printers/default.png
  66. BIN
      static/img/printers/h2c.png
  67. BIN
      static/img/printers/h2d.png
  68. BIN
      static/img/printers/h2dpro.png
  69. BIN
      static/img/printers/o1c.png
  70. BIN
      static/img/printers/o1e.png
  71. BIN
      static/img/printers/o1s.png
  72. BIN
      static/img/printers/p1p.png
  73. BIN
      static/img/printers/p1s.png
  74. BIN
      static/img/printers/printer_placeholder.png
  75. BIN
      static/img/printers/x1c.png
  76. BIN
      static/img/printers/x1e.png
  77. BIN
      static/img/printers/x2d.png
  78. BIN
      static/img/screenshot-desktop.png
  79. BIN
      static/img/screenshot-mobile.png
  80. BIN
      static/img/spoolbuddy_logo_dark.png
  81. BIN
      static/img/spoolbuddy_logo_dark_small.png
  82. 0 40
      static/index.html
  83. 0 95
      static/manifest.json
  84. BIN
      static/spoolbuddy_logo_dark.png
  85. 0 37
      static/sw-register.js
  86. 0 229
      static/sw.js
  87. 0 1
      static/vite.svg

+ 7 - 13
backend/app/api/routes/printers.py

@@ -62,7 +62,7 @@ from backend.app.services.printer_manager import (
     supports_chamber_temp,
     supports_drying,
     supports_drying_while_printing,
-    uniform_tray_drying_hint,
+    uniform_tray_filament_hint,
 )
 from backend.app.utils.filament_ids import filament_id_to_setting_id
 from backend.app.utils.http import build_content_disposition
@@ -578,18 +578,12 @@ async def get_printer_status(
                     dry_target_temp = None
             if target_fil_val:
                 dry_filament = str(target_fil_val)
-            # Fallback: derive from the loaded trays when there is no cached
-            # target (drying started in a previous backend session, or the
-            # cache wasn't seeded), and only when they agree on a filament
-            # type. See uniform_tray_drying_hint.
-            if dry_target_temp is None or not dry_filament:
-                hint_filament, hint_temp = uniform_tray_drying_hint(
-                    [(tray.tray_type or "", tray.drying_temp) for tray in trays]
-                )
-                if not dry_filament:
-                    dry_filament = hint_filament
-                if dry_target_temp is None:
-                    dry_target_temp = hint_temp
+            # Fallback: name the filament from the loaded trays when there is no
+            # cached target (drying started in a previous backend session, or
+            # the cache wasn't seeded), and only when they agree. The
+            # temperature has no fallback — see uniform_tray_filament_hint.
+            if not dry_filament:
+                dry_filament = uniform_tray_filament_hint([tray.tray_type or "" for tray in trays])
 
             ams_units.append(
                 AMSUnit(

+ 26 - 30
backend/app/services/printer_manager.py

@@ -280,8 +280,8 @@ def display_temperatures(temperatures: dict | None, model: str | None) -> dict[s
     return out
 
 
-def uniform_tray_drying_hint(loaded_trays: list[tuple[str, object]]) -> tuple[str | None, int | None]:
-    """Guess an active cycle's filament + target temperature from the loaded trays.
+def uniform_tray_filament_hint(loaded_types: list[str]) -> str | None:
+    """Guess an active cycle's filament from the loaded trays.
 
     Bambu never echoes back which filament or temperature a drying cycle is
     running, so the badge normally reads the target we cached when we sent the
@@ -291,30 +291,31 @@ def uniform_tray_drying_hint(loaded_trays: list[tuple[str, object]]) -> tuple[st
     It answers only when every loaded tray holds the same filament type. On a
     mixed unit the first tray is evidence of nothing: an AMS holding two PETG
     and two PLA spools, drying PLA at the 45°C the user picked, was labelled
-    "PETG @ 65°C" purely because slot 1 happened to be PETG (#2759). Saying
-    nothing and letting the badge show just the countdown beats stating a
-    temperature the cycle isn't using.
+    "PETG @ 65°C" purely because slot 1 happened to be PETG (#2759).
+
+    Deliberately no temperature. The RFID-recommended ``drying_temp`` used to be
+    returned alongside a uniform filament, which narrowed #2759 to units whose
+    spools disagree but left the uniform case stating a temperature just as
+    invented: a unit loaded entirely with PLA, drying at the 45°C the user
+    picked, read "PLA @ 55°C" the moment the cached target went missing. The
+    filament type is real evidence — every spool in the unit agrees on it, and
+    the dryer heats all of them — but the temperature is a free choice in the
+    popover, so a recommendation is never evidence of what is running. The badge
+    shows the filament and the countdown, and names a temperature only when we
+    actually sent it.
 
     Args:
-        loaded_trays: ``(tray_type, drying_temp)`` for each tray, in slot order.
-            Empty slots (falsy tray_type) are ignored. ``drying_temp`` is the
-            RFID-recommended value and may be None or unparseable.
+        loaded_types: ``tray_type`` for each tray, in slot order. Empty slots
+            (falsy) are ignored.
 
     Returns:
-        ``(filament, temp)``, either of which may be None.
+        The shared filament type, or None if the loaded trays disagree or the
+        unit is empty.
     """
-    types = {str(tray_type) for tray_type, _ in loaded_trays if tray_type}
+    types = {str(tray_type) for tray_type in loaded_types if tray_type}
     if len(types) != 1:
-        return None, None
-    filament = next(iter(types))
-    for tray_type, drying_temp in loaded_trays:
-        if not tray_type or not drying_temp:
-            continue
-        try:
-            return filament, int(drying_temp)
-        except (TypeError, ValueError):
-            continue
-    return filament, None
+        return None
+    return next(iter(types))
 
 
 def supports_drying(model: str | None, firmware: str | None) -> bool:
@@ -1333,8 +1334,9 @@ def printer_state_to_dict(
             # per-tick AMS push, so prefer the cached target from the last
             # ``send_drying_command``. When we have no record (drying
             # started in a previous backend lifetime, or the cache was
-            # never seeded), fall back to the loaded trays — but only when
-            # they agree on a filament type. See uniform_tray_drying_hint.
+            # never seeded), the loaded trays can still name the filament
+            # if they agree — but never the temperature, which only the
+            # cache knows. See uniform_tray_filament_hint.
             ams_id_int = int(ams_data.get("id", 0))
             target = (drying_targets or {}).get(ams_id_int)
             dry_target_temp: int | None = None
@@ -1349,14 +1351,8 @@ def printer_state_to_dict(
                         dry_target_temp = None
                 if fil_val:
                     dry_filament = str(fil_val)
-            if dry_target_temp is None or not dry_filament:
-                hint_filament, hint_temp = uniform_tray_drying_hint(
-                    [(tray.get("tray_type") or "", tray.get("drying_temp")) for tray in trays]
-                )
-                if not dry_filament:
-                    dry_filament = hint_filament
-                if dry_target_temp is None:
-                    dry_target_temp = hint_temp
+            if not dry_filament:
+                dry_filament = uniform_tray_filament_hint([tray.get("tray_type") or "" for tray in trays])
 
             ams_units.append(
                 {

+ 32 - 13
backend/tests/unit/services/test_printer_manager.py

@@ -1378,9 +1378,10 @@ class TestDryingTargetExposure:
         assert result["ams"][0]["dry_filament"] == "PETG"
         assert result["ams"][0]["dry_target_temp"] == 65
 
-    def test_falls_back_to_loaded_tray_when_no_cache(self):
-        """No cached target → derive from the loaded trays' tray_type +
-        RFID-recommended drying_temp when they agree on a filament."""
+    def test_falls_back_to_loaded_tray_filament_when_no_cache(self):
+        """No cached target → name the filament from the loaded trays when they
+        agree on a type. The temperature stays unknown: only the cache records
+        what we actually sent."""
         state = self._state_with_ams(
             {
                 "id": 0,
@@ -1392,7 +1393,7 @@ class TestDryingTargetExposure:
         )
         result = printer_state_to_dict(state, drying_targets=None)
         assert result["ams"][0]["dry_filament"] == "ABS"
-        assert result["ams"][0]["dry_target_temp"] == 70
+        assert result["ams"][0]["dry_target_temp"] is None
 
     def test_returns_none_when_no_cache_and_empty_trays(self):
         """No cache + no loaded tray with tray_type → both fields are None."""
@@ -1443,8 +1444,8 @@ class TestDryingTargetExposure:
         assert result["ams"][0]["dry_target_temp"] is None
 
     def test_fallback_survives_multiple_trays_of_one_type(self):
-        """Agreement across slots is still evidence — a unit loaded entirely
-        with PLA keeps the fallback the mixed case gives up."""
+        """Agreement across slots is still evidence of the filament — a unit
+        loaded entirely with PLA keeps the name the mixed case gives up."""
         state = self._state_with_ams(
             {
                 "id": 0,
@@ -1458,24 +1459,42 @@ class TestDryingTargetExposure:
         )
         result = printer_state_to_dict(state, drying_targets={})
         assert result["ams"][0]["dry_filament"] == "PLA"
-        assert result["ams"][0]["dry_target_temp"] == 45
 
-    def test_fallback_takes_temp_from_a_later_tray_when_slot_one_has_none(self):
-        """Only Bambu spools carry an RFID drying_temp. A third-party spool in
-        slot 1 alongside a genuine one of the same type should not cost us the
-        temperature."""
+    def test_uniform_unit_never_invents_a_temperature(self):
+        """#2759 follow-up — the reporter's second AMS held only PLA and was
+        drying at the 45°C they picked, but with no cached target the badge
+        answered with the RFID recommendation and read "PLA @ 55°C". Every
+        spool agreeing tells us the filament; it tells us nothing about a
+        temperature the user chose freely in the popover."""
         state = self._state_with_ams(
             {
                 "id": 0,
                 "dry_time": 719,
                 "tray": [
-                    {"id": 0, "tray_type": "PLA", "state": 11},
-                    {"id": 1, "tray_type": "PLA", "drying_temp": 45, "state": 11},
+                    {"id": 0, "tray_type": "PLA", "drying_temp": 55, "state": 11},
+                    {"id": 1, "tray_type": "PLA", "drying_temp": 55, "state": 11},
                 ],
             }
         )
         result = printer_state_to_dict(state, drying_targets={})
         assert result["ams"][0]["dry_filament"] == "PLA"
+        assert result["ams"][0]["dry_target_temp"] is None
+
+    def test_cached_temp_survives_a_unit_whose_trays_disagree(self):
+        """The cache is authoritative for both fields. A mixed unit costs us the
+        filament fallback but must not touch a target we actually sent."""
+        state = self._state_with_ams(
+            {
+                "id": 0,
+                "dry_time": 719,
+                "tray": [
+                    {"id": 0, "tray_type": "PETG", "drying_temp": 65, "state": 11},
+                    {"id": 1, "tray_type": "PLA", "drying_temp": 55, "state": 11},
+                ],
+            }
+        )
+        result = printer_state_to_dict(state, drying_targets={0: {"filament": "PLA", "temp": 45}})
+        assert result["ams"][0]["dry_filament"] == "PLA"
         assert result["ams"][0]["dry_target_temp"] == 45
 
 

+ 137 - 0
frontend/src/__tests__/pages/PrintersPageDryingBadge.test.tsx

@@ -0,0 +1,137 @@
+/**
+ * The active-cycle badge on the AMS card (#2759).
+ *
+ * Bambu never echoes back which filament or temperature a drying cycle is
+ * running, so the backend hands us two independent fields: `dry_filament`,
+ * which it can also infer from a uniformly loaded unit, and `dry_target_temp`,
+ * which it only knows from the target it cached when sending the command. The
+ * temperature can therefore go missing while the filament survives, and the
+ * badge has to render that pairing rather than dropping both.
+ *
+ * The reporter's second AMS held only PLA and was drying at the 45°C they
+ * picked; with no cached target the badge previously showed the RFID
+ * recommendation and read "PLA @ 55°C".
+ */
+import { describe, it, expect, beforeEach } from 'vitest';
+import { screen, waitFor } from '@testing-library/react';
+import { render } from '../utils';
+import { PrintersPage } from '../../pages/PrintersPage';
+import { http, HttpResponse } from 'msw';
+import { server } from '../mocks/server';
+
+const mockPrinter = {
+  id: 1,
+  name: 'X1C',
+  ip_address: '192.168.1.100',
+  serial_number: '01P00A000000001',
+  access_code: '12345678',
+  model: 'X1C',
+  enabled: true,
+  nozzle_diameter: 0.4,
+  nozzle_type: 'stainless_steel',
+  location: 'Workshop',
+  auto_archive: true,
+  created_at: '2024-01-01T00:00:00Z',
+  updated_at: '2024-01-01T00:00:00Z',
+};
+
+const baseTray = {
+  tray_color: 'FF0000FF',
+  tray_type: 'PLA',
+  tray_sub_brands: 'PLA Basic',
+  tray_id_name: 'A00-R0',
+  tray_info_idx: 'GFA00',
+  remain: 80,
+  k: 0.02,
+  cali_idx: null,
+  tag_uid: null,
+  tray_uuid: null,
+  nozzle_temp_min: 190,
+  nozzle_temp_max: 230,
+  drying_temp: 55,
+  drying_time: 8,
+  state: 3,
+};
+
+/** An AMS 2 Pro twelve hours into a cycle, with the badge fields under test. */
+function makeStatus(target: { dry_filament: string | null; dry_target_temp: number | null }) {
+  return {
+    connected: true,
+    state: 'IDLE',
+    progress: 0,
+    layer_num: 0,
+    total_layers: 0,
+    temperatures: { nozzle: 25, bed: 25, chamber: 25 },
+    remaining_time: 0,
+    filename: null,
+    wifi_signal: -29,
+    speed_level: 2,
+    supports_drying: true,
+    drying_screen_only: false,
+    vt_tray: [],
+    ams: [
+      {
+        id: 0,
+        humidity: 30,
+        temp: 33,
+        is_ams_ht: false,
+        serial_number: 'AMS00',
+        sw_ver: '03.00.21.29',
+        dry_time: 719,
+        dry_status: 2,
+        dry_sub_status: 0,
+        dry_sf_reason: [],
+        module_type: 'n3f',
+        ...target,
+        tray: [0, 1, 2, 3].map((id) => ({ id, ...baseTray })),
+      },
+    ],
+  };
+}
+
+function renderWith(target: { dry_filament: string | null; dry_target_temp: number | null }) {
+  server.use(
+    http.get('/api/v1/printers/', () => HttpResponse.json([mockPrinter])),
+    http.get('/api/v1/printers/:id/status', () => HttpResponse.json(makeStatus(target))),
+    http.get('/api/v1/queue/', () => HttpResponse.json([])),
+  );
+  render(<PrintersPage />);
+}
+
+describe('PrintersPage — AMS drying badge (#2759)', () => {
+  beforeEach(() => {
+    server.use(http.get('/api/v1/queue/', () => HttpResponse.json([])));
+  });
+
+  it('names the filament and the temperature when the cycle target is known', async () => {
+    renderWith({ dry_filament: 'PLA', dry_target_temp: 45 });
+
+    await waitFor(() => {
+      expect(screen.getAllByText('PLA @ 45°C').length).toBeGreaterThan(0);
+    });
+  });
+
+  it('still names the filament when only the temperature is unknown', async () => {
+    renderWith({ dry_filament: 'PLA', dry_target_temp: null });
+
+    // The filament survives on its own — dropping it too would leave the badge
+    // showing a bare countdown for a cycle we can still describe.
+    await waitFor(() => {
+      expect(screen.getAllByText('PLA').length).toBeGreaterThan(0);
+    });
+    // And it must not fall back to the trays' RFID recommendation (55°C here),
+    // which is what the user's chosen 45°C was being overwritten with. Scoped
+    // to the badge's own "<filament> @ <temp>°C" shape — the card carries
+    // unrelated nozzle and bed readings in °C.
+    expect(screen.queryByText(/@ \d+°C/)).toBeNull();
+  });
+
+  it('shows the countdown alone when the unit gives no evidence at all', async () => {
+    renderWith({ dry_filament: null, dry_target_temp: null });
+
+    await waitFor(() => {
+      expect(screen.getAllByText(/11h 59m/).length).toBeGreaterThan(0);
+    });
+    expect(screen.queryByText(/@ \d+°C/)).toBeNull();
+  });
+});

+ 12 - 4
frontend/src/pages/PrintersPage.tsx

@@ -4911,9 +4911,15 @@ function PrinterCard({
                               <div className="flex items-center gap-2 rounded-lg bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-[length:var(--pc-t9,9px)]">
                                 <Flame className="w-[var(--pc-i3,0.75rem)] h-[var(--pc-i3,0.75rem)] text-amber-600 dark:text-amber-400 shrink-0" />
                                 <span className="text-amber-700 dark:text-amber-400 font-medium">{t('printers.drying.active')}</span>
-                                {ams.dry_filament && ams.dry_target_temp != null && (
+                                {/* The temperature is only ever known from the target we
+                                    cached when sending the command — the filament can also
+                                    be read off a uniformly loaded unit, so it can outlive
+                                    the temperature (#2759). */}
+                                {ams.dry_filament && (
                                   <span className="text-amber-700/80 dark:text-amber-300/70">
-                                    {t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })}
+                                    {ams.dry_target_temp != null
+                                      ? t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })
+                                      : ams.dry_filament}
                                   </span>
                                 )}
                                 <span className="text-amber-700/80 dark:text-amber-300/70">
@@ -5453,9 +5459,11 @@ function PrinterCard({
                             {ams.dry_time > 0 && (
                               <div className="flex items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-lg bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-[length:var(--pc-t9,9px)]">
                                 <Flame className="w-[var(--pc-i3,0.75rem)] h-[var(--pc-i3,0.75rem)] text-amber-600 dark:text-amber-400 shrink-0" />
-                                {ams.dry_filament && ams.dry_target_temp != null && (
+                                {ams.dry_filament && (
                                   <span className="text-amber-700/80 dark:text-amber-300/70 text-[length:var(--pc-t8,8px)] truncate">
-                                    {t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })}
+                                    {ams.dry_target_temp != null
+                                      ? t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })
+                                      : ams.dry_filament}
                                   </span>
                                 )}
                                 <span className="text-amber-700/80 dark:text-amber-300/70 text-[length:var(--pc-t8,8px)] truncate">

Разница между файлами не показана из-за своего большого размера
+ 0 - 1
static/assets/index-B3jj6-fz.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-BkwFYHam.js


BIN
static/fonts/inter-latin-ext.woff2


BIN
static/fonts/inter-latin.woff2


BIN
static/icons/ams-ht.png


Разница между файлами не показана из-за своего большого размера
+ 0 - 1
static/icons/ams-settings.svg


+ 0 - 9
static/icons/ams-wiring-center.svg

@@ -1,9 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 50">
-  <!-- Left wire: horizontal from left edge, then down to extruder left inlet -->
-  <line x1="0" y1="0" x2="10" y2="0" stroke="#909090" stroke-width="2" />
-  <line x1="10" y1="0" x2="10" y2="50" stroke="#909090" stroke-width="2" />
-
-  <!-- Right wire: horizontal from right edge, then down to extruder right inlet -->
-  <line x1="40" y1="0" x2="30" y2="0" stroke="#909090" stroke-width="2" />
-  <line x1="30" y1="0" x2="30" y2="50" stroke="#909090" stroke-width="2" />
-</svg>

+ 0 - 17
static/icons/ams-wiring-left.svg

@@ -1,17 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 50">
-  <!-- Vertical lines from slots down to horizontal bar -->
-  <line x1="28" y1="0" x2="28" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="82" y1="0" x2="82" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="138" y1="0" x2="138" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="192" y1="0" x2="192" y2="14" stroke="#909090" stroke-width="2" />
-
-  <!-- Horizontal bar across all slots -->
-  <line x1="28" y1="14" x2="192" y2="14" stroke="#909090" stroke-width="2" />
-
-  <!-- Center hub box -->
-  <rect x="96" y="8" width="28" height="12" rx="2" fill="#c0c0c0" stroke="#909090" stroke-width="1" />
-
-  <!-- Wire from hub: down, then right to edge (at same level as hub horizontal bar) -->
-  <line x1="110" y1="20" x2="110" y2="35" stroke="#909090" stroke-width="2" />
-  <line x1="110" y1="35" x2="220" y2="35" stroke="#909090" stroke-width="2" />
-</svg>

+ 0 - 17
static/icons/ams-wiring-right.svg

@@ -1,17 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 50">
-  <!-- Vertical lines from slots down to horizontal bar -->
-  <line x1="28" y1="0" x2="28" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="82" y1="0" x2="82" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="138" y1="0" x2="138" y2="14" stroke="#909090" stroke-width="2" />
-  <line x1="192" y1="0" x2="192" y2="14" stroke="#909090" stroke-width="2" />
-
-  <!-- Horizontal bar across all slots -->
-  <line x1="28" y1="14" x2="192" y2="14" stroke="#909090" stroke-width="2" />
-
-  <!-- Center hub box -->
-  <rect x="96" y="8" width="28" height="12" rx="2" fill="#c0c0c0" stroke="#909090" stroke-width="1" />
-
-  <!-- Wire from hub: down, then left to edge (at same level as hub horizontal bar) -->
-  <line x1="110" y1="20" x2="110" y2="35" stroke="#909090" stroke-width="2" />
-  <line x1="0" y1="35" x2="110" y2="35" stroke="#909090" stroke-width="2" />
-</svg>

BIN
static/icons/ams.png


+ 0 - 1
static/icons/chamber.svg

@@ -1 +0,0 @@
-<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m15 12h-6c-1.103 0-2 .897-2 2v4c0 1.103.897 2 2 2h6c1.103 0 2-.897 2-2v-4c0-1.103-.897-2-2-2zm1 6c0 .552-.448 1-1 1h-6c-.551 0-1-.448-1-1v-4c0-.552.449-1 1-1h6c.552 0 1 .448 1 1zm-2.5-2.5c0 .276-.224.5-.5.5h-2c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h2c.276 0 .5.224.5.5zm6-13.5h-1.5v-1.5c0-.276-.224-.5-.5-.5s-.5.224-.5.5v1.5h-10v-1.5c0-.276-.224-.5-.5-.5s-.5.224-.5.5v1.5h-1.5c-2.481 0-4.5 2.019-4.5 4.5v13c0 2.481 2.019 4.5 4.5 4.5h15c2.481 0 4.5-2.019 4.5-4.5v-13c0-2.481-2.019-4.5-4.5-4.5zm-15 1h15c1.93 0 3.5 1.57 3.5 3.5v1.5h-22v-1.5c0-1.93 1.57-3.5 3.5-3.5zm15 20h-15c-1.93 0-3.5-1.57-3.5-3.5v-10.5h22v10.5c0 1.93-1.57 3.5-3.5 3.5z"/></svg>

BIN
static/icons/dual-extruder-left.png


BIN
static/icons/dual-extruder-right.png


BIN
static/icons/dual-extruder-right_sav.png


BIN
static/icons/dual-extruder.png


BIN
static/icons/extruder-change-filament.png


BIN
static/icons/extruder-left-right.png


+ 0 - 51
static/icons/eye.svg

@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 511.999 511.999" style="enable-background:new 0 0 511.999 511.999;" xml:space="preserve">
-<g>
-	<g>
-		<path d="M508.745,246.041c-4.574-6.257-113.557-153.206-252.748-153.206S7.818,239.784,3.249,246.035
-			c-4.332,5.936-4.332,13.987,0,19.923c4.569,6.257,113.557,153.206,252.748,153.206s248.174-146.95,252.748-153.201
-			C513.083,260.028,513.083,251.971,508.745,246.041z M255.997,385.406c-102.529,0-191.33-97.533-217.617-129.418
-			c26.253-31.913,114.868-129.395,217.617-129.395c102.524,0,191.319,97.516,217.617,129.418
-			C447.361,287.923,358.746,385.406,255.997,385.406z"/>
-	</g>
-</g>
-<g>
-	<g>
-		<path d="M255.997,154.725c-55.842,0-101.275,45.433-101.275,101.275s45.433,101.275,101.275,101.275
-			s101.275-45.433,101.275-101.275S311.839,154.725,255.997,154.725z M255.997,323.516c-37.23,0-67.516-30.287-67.516-67.516
-			s30.287-67.516,67.516-67.516s67.516,30.287,67.516,67.516S293.227,323.516,255.997,323.516z"/>
-	</g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-</svg>

+ 0 - 1
static/icons/heatbed.svg

@@ -1 +0,0 @@
-<svg id="Layer_1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><g fill="rgb(0,0,0)"><path d="m6.3 3.9c.54-.4 1.2-.9 1.2-1.9 0-.28-.22-.5-.5-.5s-.5.22-.5.5c0 .48-.29.71-.8 1.1-.53.4-1.2.9-1.2 1.9s.67 1.5 1.2 1.9c.51.38.8.62.8 1.1s-.29.72-.8 1.1c-.54.4-1.2.9-1.2 1.9 0 .28.22.5.5.5s.5-.22.5-.5c0-.48.29-.72.8-1.1.54-.4 1.2-.9 1.2-1.9s-.67-1.5-1.2-1.9c-.51-.38-.8-.62-.8-1.1s.29-.72.8-1.1z"/><path d="m12.3 3.9c.54-.4 1.2-.9 1.2-1.9 0-.28-.22-.5-.5-.5s-.5.22-.5.5c0 .48-.29.71-.8 1.1-.53.4-1.2.9-1.2 1.9s.67 1.5 1.2 1.9c.51.38.8.62.8 1.1s-.29.72-.8 1.1c-.54.4-1.2.9-1.2 1.9 0 .28.22.5.5.5s.5-.22.5-.5c0-.48.29-.72.8-1.1.54-.4 1.2-.9 1.2-1.9s-.67-1.5-1.2-1.9c-.51-.38-.8-.62-.8-1.1s.29-.72.8-1.1z"/><path d="m18.3 3.9c.54-.4 1.2-.9 1.2-1.9 0-.28-.22-.5-.5-.5s-.5.22-.5.5c0 .48-.29.71-.8 1.1-.53.4-1.2.9-1.2 1.9s.67 1.5 1.2 1.9c.51.38.8.62.8 1.1s-.29.72-.8 1.1c-.54.4-1.2.9-1.2 1.9 0 .28.22.5.5.5s.5-.22.5-.5c0-.48.29-.72.8-1.1.54-.4 1.2-.9 1.2-1.9s-.67-1.5-1.2-1.9c-.51-.38-.8-.62-.8-1.1s.29-.72.8-1.1z"/><path d="m22 13.5c-1.07 0-1.61.65-2.05 1.18-.44.52-.71.82-1.29.82s-.85-.3-1.29-.82c-.44-.53-.98-1.18-2.05-1.18s-1.61.65-2.05 1.18c-.44.52-.71.82-1.28.82s-.85-.3-1.28-.82c-.44-.53-.98-1.18-2.05-1.18s-1.61.65-2.05 1.18c-.44.52-.71.82-1.28.82s-.84-.3-1.28-.82c-.44-.53-.98-1.18-2.05-1.18-.28 0-.5.22-.5.5s.22.5.5.5c.57 0 .84.3 1.28.82.44.53.98 1.18 2.05 1.18s1.61-.65 2.05-1.18c.44-.52.71-.82 1.28-.82s.85.3 1.28.82c.44.53.98 1.18 2.05 1.18s1.61-.65 2.05-1.18c.44-.52.71-.82 1.28-.82s.85.3 1.29.82c.44.53.98 1.18 2.05 1.18s1.61-.65 2.05-1.18c.44-.52.71-.82 1.29-.82.28 0 .5-.22.5-.5s-.22-.5-.5-.5z"/><path d="m21 18.5h-18c-.83 0-1.5.67-1.5 1.5v1c0 .83.67 1.5 1.5 1.5h18c.83 0 1.5-.67 1.5-1.5v-1c0-.83-.67-1.5-1.5-1.5zm.5 2.5c0 .28-.22.5-.5.5h-18c-.28 0-.5-.22-.5-.5v-1c0-.28.22-.5.5-.5h18c.28 0 .5.22.5.5z"/></g></svg>

+ 0 - 44
static/icons/home.svg

@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 476.912 476.912" style="enable-background:new 0 0 476.912 476.912;" xml:space="preserve">
-<g>
-	<g>
-		<path d="M461.776,209.408L249.568,4.52c-6.182-6.026-16.042-6.026-22.224,0L15.144,209.4c-3.124,3.015-4.888,7.17-4.888,11.512
-			c0,8.837,7.164,16,16,16h28.2v224c0,8.837,7.163,16,16,16h112c8.837,0,16-7.163,16-16v-128h80v128c0,8.837,7.163,16,16,16h112
-			c8.837,0,16-7.163,16-16v-224h28.2c4.338,0,8.489-1.761,11.504-4.88C468.301,225.678,468.129,215.549,461.776,209.408z
-			 M422.456,220.912c-8.837,0-16,7.163-16,16v224h-112v-128c0-8.837-7.163-16-16-16h-80c-8.837,0-16,7.163-16,16v128h-112v-224
-			c0-8.837-7.163-16-16-16h-28.2l212.2-204.88l212.28,204.88H422.456z"/>
-	</g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-</svg>

+ 0 - 6
static/icons/hotend.svg

@@ -1,6 +0,0 @@
-<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M12.4759 7.06822H13.7562L8.74263 11.2268C8.514 11.416 8.10431 11.416 7.87554 11.227C7.87545 11.2269 7.87536 11.2268 7.87528 11.2268L2.86167 7.06822H4.142C4.54877 7.06822 4.93613 6.94338 5.23267 6.71974C5.52893 6.49633 5.76004 6.14967 5.76004 5.72506V1.84316C5.76004 1.80403 5.78049 1.72911 5.88953 1.64688C5.99827 1.56488 6.16993 1.5 6.37809 1.5H10.2398C10.448 1.5 10.6196 1.56488 10.7284 1.64688C10.8374 1.72911 10.8579 1.80403 10.8579 1.84316V5.72506C10.8579 6.14967 11.089 6.49633 11.3852 6.71974C11.6818 6.94338 12.0691 7.06822 12.4759 7.06822ZM2.36979 7.09452C2.36773 7.09555 2.36658 7.096 2.36652 7.09597C2.36645 7.09594 2.36748 7.09542 2.36979 7.09452ZM14.2475 7.09456C14.2498 7.09545 14.2508 7.09596 14.2507 7.096C14.2507 7.09603 14.2495 7.09558 14.2475 7.09456Z" stroke="#6B6B6B"/>
-<path d="M3.80389 10.668C3.58699 10.7742 3.42822 10.9007 3.42822 11.0895C3.42822 11.673 4.95994 11.673 4.95994 12.2548C4.95994 12.8383 3.42822 12.8383 3.42822 13.42C3.42822 14.0035 4.95994 14.0035 4.95994 14.587C4.95994 15.1704 3.42822 15.1704 3.42822 15.7539" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
-<path d="M8.63467 14.1348C8.88288 14.2477 9.07518 14.381 9.07518 14.5867C9.07518 15.1702 7.54346 15.1702 7.54346 15.7536" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
-<path d="M11.893 11.4316C12.3223 11.7065 13.1899 11.8161 13.1899 12.2546C13.1899 12.838 11.6582 12.838 11.6582 13.4198C11.6582 14.0033 13.1899 14.0033 13.1899 14.5867C13.1899 15.1702 11.6582 15.1702 11.6582 15.7537" stroke="#6B6B6B" stroke-miterlimit="10" stroke-linecap="round"/>
-</svg>

+ 0 - 4
static/icons/humidity-empty.svg

@@ -1,4 +0,0 @@
-<svg width="36" height="54" viewBox="0 0 36 54" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M17.8131 0.00537678C18.4463 -0.150913 20.3648 3.14642 20.8264 3.84781C25.4187 10.816 35.3089 26.9368 35.9383 34.8694C37.4182 53.5822 11.882 61.3357 2.53721 45.3789C-1.73471 38.0791 0.016016 32.2049 3.178 25.0232C6.99221 16.3662 12.6411 7.90372 17.8131 0.00537678ZM18.3738 7.24807L17.5881 7.48441C14.4452 12.9431 10.917 18.2341 8.19369 23.9368C4.6808 31.29 1.18317 38.5479 7.69403 45.5657C17.3058 55.9228 34.9847 46.8808 31.4604 32.8681C29.2558 24.0969 22.4207 15.2913 18.3776 7.24807H18.3738Z" fill="#D0D0D0"/>
-<path d="M8 46C12 48 24 48 28 46C26 50 22 52 18 52C14 52 10 50 8 46Z" fill="#1F8FEB"/>
-</svg>

+ 0 - 4
static/icons/humidity-full.svg

@@ -1,4 +0,0 @@
-<svg width="36" height="54" viewBox="0 0 36 54" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M17.9625 4.48059L4.77216 26.3154L2.08228 40.2175L10.0224 50.8414H23.1594L33.3246 42.1693V30.2455L17.9625 4.48059Z" fill="#1F8FEB"/>
-<path d="M17.7948 0.00537678C18.4273 -0.150913 20.3438 3.14642 20.8048 3.84781C25.3921 10.816 35.2715 26.9368 35.9001 34.8694C37.3784 53.5822 11.8702 61.3357 2.53562 45.3789C-1.73163 38.0829 0.0133678 32.2087 3.1757 25.027C6.98574 16.3662 12.6284 7.90372 17.7948 0.00537678ZM18.3549 7.24807L17.57 7.48441C14.4306 12.9431 10.9063 18.2341 8.1859 23.9368C4.67686 31.29 1.18305 38.5479 7.68679 45.5657C17.2881 55.9228 34.9476 46.8808 31.4271 32.8681C29.2249 24.0969 22.3974 15.2913 18.3587 7.24807H18.3549Z" fill="#D0D0D0"/>
-</svg>

+ 0 - 4
static/icons/humidity-half.svg

@@ -1,4 +0,0 @@
-<svg width="35" height="53" viewBox="0 0 35 53" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M17.3165 0.00379674C17.932 -0.149588 19.7971 3.08645 20.2458 3.77481C24.7103 10.6135 34.3251 26.4346 34.937 34.2198C36.3757 52.5848 11.5505 60.1942 2.46584 44.534C-1.68714 37.3735 0.0148377 31.6085 3.08879 24.5603C6.79681 16.0605 12.2884 7.75907 17.3165 0.00379674ZM17.8615 7.11561L17.0977 7.34755C14.0423 12.7048 10.6124 17.8974 7.96483 23.4941C4.54975 30.7107 1.14949 37.8337 7.47908 44.721C16.8233 54.8856 34.01 46.0117 30.5838 32.2595C28.4405 23.6512 21.7957 15.0093 17.8652 7.11561H17.8615Z" fill="#D0D0D0"/>
-<path d="M5.03547 30.112C9.64453 30.4936 11.632 35.7985 16.4154 35.791C19.6339 35.7873 20.2161 33.2283 22.3853 31.6197C31.6776 24.7286 33.5835 37.4894 27.9881 44.4254C18.1878 56.5653 -1.16063 44.6013 5.03917 30.1158L5.03547 30.112Z" fill="#1F8FEB"/>
-</svg>

BIN
static/icons/jogpad.png


Разница между файлами не показана из-за своего большого размера
+ 0 - 5
static/icons/jogpad.svg


+ 0 - 4
static/icons/lamp.svg

@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
-  <path d="m6.443,4.08L4.304.567,5.157.048l2.14,3.513-.854.52Zm13.557,7.92c0,2.323-1.01,4.528-2.771,6.051-.781.674-1.229,1.641-1.229,2.653v3.296h-8v-3.295c0-1.007-.456-1.982-1.252-2.675-2.062-1.796-3.058-4.497-2.661-7.227.512-3.521,3.457-6.36,7.003-6.753,2.307-.256,4.527.45,6.245,1.987,1.693,1.517,2.665,3.689,2.665,5.962Zm-5,8.704c0-.239.04-.471.077-.704h-6.156c.038.233.078.467.078.705v2.295h6v-2.296Zm4-8.704c0-1.988-.85-3.89-2.332-5.217-1.502-1.344-3.438-1.964-5.469-1.738-3.1.343-5.675,2.825-6.122,5.903-.348,2.391.522,4.757,2.327,6.328.553.481.963,1.076,1.234,1.724h2.861v-5.551c-1.14-.232-2-1.242-2-2.449h1c0,.827.673,1.5,1.5,1.5s1.5-.673,1.5-1.5h1c0,1.208-.86,2.217-2,2.449v5.551h2.856c.268-.645.672-1.234,1.218-1.706,1.542-1.332,2.426-3.262,2.426-5.294Zm.696-11.433l-.854-.52-2.14,3.513.854.52,2.14-3.513Zm3.86,4.342l-3.536,1.597.412.912,3.536-1.597-.412-.912ZM.031,5.821l3.536,1.597.412-.912L.443,4.909l-.412.912Z"/>
-</svg>

+ 0 - 12
static/icons/micro-sd.svg

@@ -1,12 +0,0 @@
-<?xml version='1.0' encoding='utf-8'?>
-<!-- Generator: imaengine 6.0   -->
-<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0,0,512,512" style="enable-background:new 0 0 512 512;" version="1.1">
-<defs/>
-<g id="layer0">
-<path d="M-0.00100857,91.001L-0.000996768,361.001C-0.000995864,381.679 16.821,398.501 37.499,398.501L154.393,398.501L212.196,456.305C213.603,457.711 215.51,458.501 217.5,458.501L277.5,458.501C281.643,458.501 285,455.144 285,451.001L285,428.501L334.394,428.501L362.197,456.305C363.604,457.711 365.511,458.501 367.501,458.501L474.501,458.501C495.179,458.501 512.001,441.679 512.001,421.001L512.001,91.001C512.001,70.323 495.179,53.501 474.501,53.501L37.501,53.501C16.821,53.501 -0.00100947,70.323 -0.00100857,91.001L-0.00100857,91.001ZM496.999,91.001L496.999,421.001C496.999,433.407 486.905,443.501 474.499,443.501L436.999,443.501L436.999,121.001C436.999,116.858 433.642,113.501 429.499,113.501C425.356,113.501 421.999,116.858 421.999,121.001L421.999,443.501L370.605,443.501L342.802,415.697C341.395,414.291 339.488,413.501 337.498,413.501L277.498,413.501C273.355,413.501 269.998,416.858 269.998,421.001L269.998,443.501L220.604,443.501L162.801,385.697C161.394,384.291 159.487,383.501 157.497,383.501L37.497,383.501C25.091,383.501 14.997,373.407 14.997,361.001L14.997,91.001C14.997,78.595 25.091,68.501 37.497,68.501L421.999,68.501L421.999,91.001C421.999,95.144 425.356,98.501 429.499,98.501C433.642,98.501 436.999,95.144 436.999,91.001L436.999,68.501L474.499,68.501C486.905,68.501 496.999,78.595 496.999,91.001L496.999,91.001Z" fill="#000000"/>
-<path d="M29.999,316.001L29.999,361.001C29.999,365.144 33.356,368.501 37.499,368.501L157.499,368.501C161.642,368.501 164.999,365.144 164.999,361.001L164.999,316.001C164.999,311.858 161.642,308.501 157.499,308.501L37.499,308.501C33.356,308.501 29.999,311.858 29.999,316.001L29.999,316.001ZM149.999,323.501L149.999,353.501L44.999,353.501L44.999,323.501L149.999,323.501Z" fill="#000000"/>
-<path d="M29.999,241.001L29.999,286.001C29.999,290.144 33.356,293.501 37.499,293.501L157.499,293.501C161.642,293.501 164.999,290.144 164.999,286.001L164.999,241.001C164.999,236.858 161.642,233.501 157.499,233.501L37.499,233.501C33.356,233.501 29.999,236.858 29.999,241.001L29.999,241.001ZM149.999,248.501L149.999,278.501L44.999,278.501L44.999,248.501L149.999,248.501Z" fill="#000000"/>
-<path d="M29.999,166.001L29.999,211.001C29.999,215.144 33.356,218.501 37.499,218.501L157.499,218.501C161.642,218.501 164.999,215.144 164.999,211.001L164.999,166.001C164.999,161.858 161.642,158.501 157.499,158.501L37.499,158.501C33.356,158.501 29.999,161.858 29.999,166.001L29.999,166.001ZM149.999,173.501L149.999,203.501L44.999,203.501L44.999,173.501L149.999,173.501Z" fill="#000000"/>
-<path d="M157.499,83.501L37.499,83.501C33.356,83.501 29.999,86.858 29.999,91.001L29.999,136.001C29.999,140.144 33.356,143.501 37.499,143.501L157.499,143.501C161.642,143.501 164.999,140.144 164.999,136.001L164.999,91.001C164.999,86.858 161.642,83.501 157.499,83.501L157.499,83.501ZM149.999,98.501L149.999,128.501L44.999,128.501L44.999,98.501L149.999,98.501Z" fill="#000000"/>
-</g>
-</svg>

+ 0 - 1
static/icons/reload.svg

@@ -1 +0,0 @@
-<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m22 11a1 1 0 0 0 -1 1 9 9 0 1 1 -9-9 8.9 8.9 0 0 1 4.42 1.166l-1.127 1.127a1 1 0 0 0 .707 1.707h4a1 1 0 0 0 1-1v-4a1 1 0 0 0 -1.707-.707l-1.411 1.407a10.9 10.9 0 0 0 -5.882-1.7 11 11 0 1 0 11 11 1 1 0 0 0 -1-1z"/></svg>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/icons/settings.svg


BIN
static/icons/single-extruder1.png


BIN
static/icons/single-extruder2.png


+ 0 - 1
static/icons/skip-objects.svg

@@ -1 +0,0 @@
-<svg id="Layer_1" height="512" viewBox="0 0 32 32" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m30 17-3 3-3-3h2c0-6.0654-4.9355-11-11-11s-11 4.9346-11 11h-2c0-7.168 5.832-13 13-13s13 5.832 13 13zm0 5h-6v2h6zm-10 0h-2v2h2zm-16 0h-2v2h2zm4 0h-2v2h2zm4 0h-2v2h2zm4 0h-2v2h2z"/></svg>

+ 0 - 53
static/icons/snowflake.svg

@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 412.8 412.8" style="enable-background:new 0 0 412.8 412.8;" xml:space="preserve">
-<g>
-	<g>
-		<path d="M378.4,225.6L304,251.2L274,234v-27.6v-27.2l30-17.2l74.4,25.6c5.2,2,11.2-1.2,12.8-6.4c2-5.2-1.2-11.2-6.4-12.8
-			l-57.6-19.6l54-31.2c4.8-2.8,6.4-9.2,3.6-14c-2.8-4.8-9.2-6.4-14-3.6l-54,31.2l11.6-59.6c1.2-5.6-2.4-10.8-8-12
-			c-5.6-1.2-10.8,2.4-12,8l-15.2,77.2l-30,17.2l-22.8-13.2l-0.4-0.4l-23.2-13.6v-34.4L276,48.8c4.4-3.6,4.8-10,0.8-14.4
-			c-3.6-4.4-10-4.8-14.4-0.8l-45.6,40V10.4c0-5.6-4.4-10.4-10.4-10.4C200.8,0,196,4.4,196,10.4v62.4l-45.6-39.6
-			C146,29.6,139.6,30,136,34c-3.6,4.4-3.2,10.8,0.8,14.4L196,100v34.4L172.8,148l-23.2,13.6l-30-17.2l-15.2-77.2
-			c-1.2-5.6-6.4-9.2-12-8c-5.6,1.2-9.2,6.4-8,12L96,130.8L42,99.6c-4.8-2.8-11.2-1.2-14,3.6s-1.2,11.2,3.6,14l54,31.2L28,168
-			c-5.2,2-8.4,7.6-6.4,12.8s7.6,8.4,12.8,6.4l74.4-25.6l30,17.2v27.6v27.2h0.4l-30,17.2l-74.4-25.6c-5.2-2-11.2,1.2-12.8,6.4
-			c-2,5.2,1.2,11.2,6.4,12.8L86,264l-54,31.2c-4.8,2.8-6.4,9.2-3.6,14c2.8,4.8,9.2,6.4,14,3.6l54-31.2l-11.6,59.6
-			c-1.2,5.6,2.4,10.8,8,12c5.6,1.2,10.8-2.4,12-8L120,268l30-17.2l23.6,13.6l23.2,13.6v34.4L137.6,364c-4.4,3.6-4.8,10-0.8,14.4
-			c3.6,4.4,10,4.8,14.4,0.8l45.6-40v63.2c0,5.6,4.4,10.4,10.4,10.4c5.6,0,10.4-4.4,10.4-10.4V340l45.6,40c4.4,3.6,10.8,3.2,14.4-0.8
-			c3.6-4.4,3.2-10.8-0.8-14.4l-60-52v-34.4l23.2-13.6l23.2-13.6l30,17.2l15.2,77.2c1.2,5.6,6.4,9.2,12,8c5.6-1.2,9.2-6.4,8-12
-			L316.8,282l54,31.2c4.8,2.8,11.2,1.2,14-3.6c2.8-4.8,1.2-11.2-3.6-14l-54-31.2l57.6-19.6c5.2-2,8.4-7.6,6.4-12.8
-			C389.2,226.8,383.6,223.6,378.4,225.6z M252.4,206.4v27.2l-23.2,13.6l-22.8,13.2l-23.6-13.6l-23.2-13.6v-26.8v-27.2l23.2-13.6
-			L206,152l23.2,13.6l0.4,0.4l22.8,13.2V206.4z"/>
-	</g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-</svg>

+ 0 - 6
static/icons/speed.svg

@@ -1,6 +0,0 @@
-<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M17.4005 12.7763C17.649 12.8443 17.907 12.6982 17.9606 12.4463C18.1963 11.3405 18.1993 10.1959 17.9674 9.08583C17.7037 7.8233 17.1438 6.64152 16.3337 5.63787C15.5236 4.63422 14.4866 3.83744 13.3082 3.3132C12.1298 2.78897 10.8436 2.55228 9.55579 2.62265C8.26794 2.69302 7.01524 3.06843 5.90095 3.71795C4.78665 4.36746 3.84266 5.27248 3.14678 6.35842C2.45089 7.44436 2.02303 8.68012 1.89846 9.96387C1.78893 11.0926 1.91663 12.23 2.27134 13.3036C2.35217 13.5482 2.62452 13.6653 2.8641 13.5706V13.5706C3.10368 13.4759 3.21959 13.2053 3.14058 12.9601C2.83915 12.0245 2.73177 11.0355 2.82702 10.054C2.93731 8.91737 3.31613 7.82324 3.93226 6.86177C4.54839 5.9003 5.38418 5.09901 6.37076 4.52394C7.35733 3.94887 8.46645 3.61649 9.60669 3.55418C10.7469 3.49188 11.8857 3.70144 12.929 4.16559C13.9724 4.62974 14.8905 5.33519 15.6077 6.2238C16.3249 7.11242 16.8207 8.15875 17.0542 9.27658C17.2558 10.2419 17.2569 11.2367 17.0592 12.1995C17.0073 12.4519 17.152 12.7083 17.4005 12.7763V12.7763Z" fill="#323A3D"/>
-<path d="M15.4157 8.78647C15.593 8.71313 15.6782 8.50941 15.5948 8.33658C15.1923 7.50231 14.6033 6.76987 13.8715 6.19699C13.0484 5.5526 12.0726 5.13199 11.0389 4.97602C10.0053 4.82004 8.94883 4.93399 7.9722 5.30681C7.10396 5.63825 6.32502 6.16427 5.6943 6.84264C5.56365 6.98317 5.58494 7.20296 5.73272 7.32535V7.32535C5.88051 7.44775 6.09873 7.42629 6.23043 7.28674C6.78398 6.70015 7.46373 6.2447 8.22002 5.956C9.08471 5.62591 10.0201 5.52502 10.9353 5.66312C11.8504 5.80122 12.7144 6.17362 13.4432 6.74415C14.0806 7.24316 14.5957 7.8789 14.9515 8.60271C15.0362 8.77491 15.2383 8.85981 15.4157 8.78647V8.78647Z" fill="#323A3D"/>
-<path d="M13.7871 9.75159L10.6357 14.0831" stroke="#323A3D" stroke-width="1.04628" stroke-linecap="round"/>
-<circle cx="10.1926" cy="14.5735" r="1.32935" fill="#323A3D"/>
-</svg>

+ 0 - 4
static/icons/temperature.svg

@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24">
-  <path d="m12.5,15.051V5h-1v10.051c-1.14.232-2,1.242-2,2.449,0,1.379,1.121,2.5,2.5,2.5s2.5-1.121,2.5-2.5c0-1.208-.86-2.217-2-2.449Zm-.5,3.949c-.827,0-1.5-.673-1.5-1.5s.673-1.5,1.5-1.5,1.5.673,1.5,1.5-.673,1.5-1.5,1.5Zm4.5-6.181V4.5c0-2.481-2.019-4.5-4.5-4.5s-4.5,2.019-4.5,4.5v8.319c-1.627,1.561-2.32,3.805-1.859,6.049.508,2.472,2.506,4.476,4.972,4.987.459.096.92.143,1.376.143,1.495,0,2.942-.503,4.111-1.454,1.525-1.241,2.4-3.08,2.4-5.044,0-1.763-.727-3.456-2-4.681Zm-1.031,8.949c-1.292,1.05-2.989,1.454-4.653,1.108-2.081-.432-3.767-2.124-4.194-4.21-.405-1.968.235-3.933,1.713-5.258l.166-.148V4.5c0-1.93,1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5v8.761l.166.148c1.166,1.046,1.834,2.537,1.834,4.091,0,1.662-.74,3.218-2.031,4.269Z"/>
-</svg>

+ 0 - 6
static/icons/ventilation.svg

@@ -1,6 +0,0 @@
-<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.6444 8.72819C12.3737 7.93152 12.8358 6.88333 12.8815 5.7196C12.9537 3.86391 11.9465 2.21641 10.4205 1.38122C10.2905 1.31022 10.1557 1.24523 10.0186 1.18506C9.74417 1.07796 9.44933 1.01417 9.13884 1.00214C7.64057 0.943171 6.37816 2.1105 6.3204 3.60878C6.2867 4.48608 6.67301 5.28155 7.29879 5.80264C7.34813 5.83754 7.39507 5.87485 7.442 5.91336C7.92097 6.31169 8.25913 6.87129 8.37587 7.5043C8.67793 7.40923 9.00045 7.3635 9.335 7.37673C10.317 7.41765 11.1642 7.95077 11.6444 8.72819Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
-<path d="M6.3842 10.1074C6.39743 9.77525 6.46723 9.45995 6.58276 9.16752C5.57669 8.98339 4.50443 9.12179 3.52844 9.63445C1.88455 10.4997 0.961517 12.1954 1.00123 13.9343C1.00484 14.0823 1.01567 14.2316 1.03252 14.3808C1.07705 14.6708 1.16971 14.9597 1.31412 15.234C2.01212 16.5602 3.65481 17.0705 4.98099 16.3713C5.75721 15.9621 6.25422 15.2304 6.39142 14.4265C6.39743 14.3664 6.40586 14.3062 6.41669 14.2472C6.53583 13.5564 6.91972 12.9174 7.52024 12.4926C6.79938 11.9523 6.34689 11.0774 6.3842 10.1074Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
-<path d="M16.7327 11.0132C15.9902 10.545 15.1081 10.4813 14.3427 10.7641C14.2874 10.7881 14.232 10.811 14.1754 10.8327C13.4895 11.0854 12.7097 11.0601 12.0213 10.7147C11.8083 11.9254 10.8347 12.8592 9.62646 13.0313C9.94778 14.0747 10.6277 15.0134 11.6242 15.6416C13.1959 16.632 15.1262 16.5839 16.6124 15.6801C16.7388 15.6031 16.8627 15.5188 16.9831 15.4298C17.2129 15.2457 17.4163 15.0218 17.5812 14.7595C18.3815 13.4898 18.0012 11.8135 16.7327 11.0132Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
-<path d="M9.20391 11.3717C9.76553 11.3717 10.2208 10.9164 10.2208 10.3548C10.2208 9.79317 9.76553 9.33789 9.20391 9.33789C8.64229 9.33789 8.18701 9.79317 8.18701 10.3548C8.18701 10.9164 8.64229 11.3717 9.20391 11.3717Z" stroke="#00AE42" stroke-miterlimit="10" stroke-linecap="round"/>
-</svg>

+ 0 - 1
static/icons/video-camera.svg

@@ -1 +0,0 @@
-<svg height="472pt" viewBox="0 -87 472 472" width="472pt" xmlns="http://www.w3.org/2000/svg"><path d="m467.101562 26.527344c-3.039062-1.800782-6.796874-1.871094-9.898437-.179688l-108.296875 59.132813v-35.480469c-.03125-27.601562-22.398438-49.96875-50-50h-248.90625c-27.601562.03125-49.96875 22.398438-50 50v197.421875c.03125 27.601563 22.398438 49.96875 50 50h248.90625c27.601562-.03125 49.96875-22.398437 50-50v-34.835937l108.300781 59.132812c3.097657 1.691406 6.859375 1.625 9.894531-.175781 3.039063-1.804688 4.898438-5.074219 4.898438-8.601563v-227.816406c0-3.53125-1.863281-6.796875-4.898438-8.597656zm-138.203124 220.898437c-.015626 16.5625-13.4375 29.980469-30 30h-248.898438c-16.5625-.019531-29.980469-13.4375-30-30v-197.425781c.019531-16.558594 13.4375-29.980469 30-30h248.90625c16.558594.019531 29.980469 13.441406 30 30zm123.101562-1.335937-103.09375-56.289063v-81.535156l103.09375-56.285156zm0 0"/></svg>

+ 0 - 2
static/icons/water.svg

@@ -1,2 +0,0 @@
-<?xml version="1.0"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="512" height="512"><g id="Water"><path d="M24,46A16.0183,16.0183,0,0,1,8,30C8,16.0942,22.708,2.8125,23.3345,2.2539a.9983.9983,0,0,1,1.331,0C25.292,2.8125,40,16.0942,40,30A16.0183,16.0183,0,0,1,24,46ZM24,4.3721C21.1333,7.1372,10,18.6118,10,30a14,14,0,0,0,28,0C38,18.6118,26.8667,7.1372,24,4.3721Z"/><path d="M18.4976,40.5273a.9946.9946,0,0,1-.5-.1342A12.0449,12.0449,0,0,1,12,30a1,1,0,0,1,2,0,10.0373,10.0373,0,0,0,5,8.6616,1,1,0,0,1-.5019,1.8657Z"/></g></svg>

+ 0 - 73
static/icons/webcam.svg

@@ -1,73 +0,0 @@
-<?xml version="1.0" encoding="iso-8859-1"?>
-<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
-<g>
-	<g>
-		<path d="M256,40c-5.52,0-10,4.48-10,10s4.48,10,10,10s10-4.48,10-10S261.52,40,256,40z"/>
-	</g>
-</g>
-<g>
-	<g>
-		<path d="M466,210C466,94.206,371.794,0,256,0S46,94.206,46,210c0,96.488,66.579,180.855,159.516,203.859
-			c-1.591,14.119-6.958,31.441-13.568,38.051l-0.131,0.131c-18.899,0.353-32.638,3.149-42.999,8.73
-			C133.677,468.949,126,482.82,126,502c0,5.522,4.478,10,10,10h240c5.522,0,10-4.478,10-10c0-19.187-7.68-33.058-22.824-41.229
-			c-10.344-5.58-24.082-8.378-42.992-8.731l-0.132-0.132c-6.61-6.609-11.977-23.931-13.568-38.05
-			C399.423,390.853,466,306.486,466,210z M316,472c33.23,0,45.303,7.689,48.794,20H147.226c2.172-7.762,6.862-11.345,11.087-13.626
-			C166.274,474.085,178.603,472,196,472H316z M215.517,452c5.068-10.601,8.238-23.466,9.638-34.27
-			C235.326,419.232,245.658,420,256,420c10.342,0,20.674-0.768,30.845-2.27c1.401,10.804,4.57,23.67,9.638,34.27H215.517z
-			 M294.015,396.179c-0.019,0.004-0.037,0.007-0.056,0.011c-24.788,5.056-51.127,5.057-75.922-0.001
-			c-0.017-0.004-0.035-0.007-0.052-0.01C129.918,378.227,66,299.929,66,210c0-104.767,85.233-190,190-190s190,85.233,190,190
-			C446,299.929,382.082,378.227,294.015,396.179z"/>
-	</g>
-</g>
-<g>
-	<g>
-		<path d="M389.606,104.994c-23.072-29.303-55.544-50.505-91.434-59.701c-5.355-1.374-10.799,1.855-12.17,7.205
-			c-1.37,5.35,1.855,10.798,7.205,12.169c31.66,8.112,60.314,26.828,80.686,52.7c3.426,4.352,9.716,5.077,14.043,1.67
-			C392.275,115.621,393.023,109.333,389.606,104.994z"/>
-	</g>
-</g>
-<g>
-	<g>
-		<path d="M256,100c-60.654,0-110,49.346-110,110s49.346,110,110,110s110-49.346,110-110S316.654,100,256,100z M256,300
-			c-49.626,0-90-40.374-90-90c0-49.626,40.374-90,90-90c49.626,0,90,40.374,90,90C346,259.626,305.626,300,256,300z"/>
-	</g>
-</g>
-<g>
-	<g>
-		<path d="M256,140c-38.598,0-70,31.402-70,70c0,38.598,31.402,70,70,70c38.598,0,70-31.402,70-70C326,171.402,294.598,140,256,140z
-			 M256,260c-27.57,0-50-22.43-50-50s22.43-50,50-50s50,22.43,50,50S283.57,260,256,260z"/>
-	</g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-<g>
-</g>
-</svg>

BIN
static/img/android-chrome-192x192.png


BIN
static/img/android-chrome-512x512.png


BIN
static/img/apple-touch-icon.png


BIN
static/img/bambuddy_logo_dark.png


BIN
static/img/bambuddy_logo_dark_transparent.png


BIN
static/img/bambuddy_logo_light.png


BIN
static/img/bed/bed_cool.png


BIN
static/img/bed/bed_cool_supertack.png


BIN
static/img/bed/bed_engineering.png


BIN
static/img/bed/bed_high_templ.png


BIN
static/img/bed/bed_pei.png


BIN
static/img/bed/bed_pei_cool.png


BIN
static/img/favicon-16x16.png


BIN
static/img/favicon-32x32.png


BIN
static/img/favicon.png


BIN
static/img/printers/a1.png


BIN
static/img/printers/a1f.png


BIN
static/img/printers/a1mini.png


BIN
static/img/printers/a2l.png


BIN
static/img/printers/default.png


BIN
static/img/printers/h2c.png


BIN
static/img/printers/h2d.png


BIN
static/img/printers/h2dpro.png


BIN
static/img/printers/o1c.png


BIN
static/img/printers/o1e.png


BIN
static/img/printers/o1s.png


BIN
static/img/printers/p1p.png


BIN
static/img/printers/p1s.png


BIN
static/img/printers/printer_placeholder.png


BIN
static/img/printers/x1c.png


BIN
static/img/printers/x1e.png


BIN
static/img/printers/x2d.png


BIN
static/img/screenshot-desktop.png


BIN
static/img/screenshot-mobile.png


BIN
static/img/spoolbuddy_logo_dark.png


BIN
static/img/spoolbuddy_logo_dark_small.png


+ 0 - 40
static/index.html

@@ -1,40 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
-    <!-- L-4: Restrict Referer header to origin-only on cross-origin navigation so
-         sensitive tokens in query parameters are not leaked to third-party servers. -->
-    <meta name="referrer" content="strict-origin-when-cross-origin" />
-    <title>Bambuddy</title>
-
-    <!-- PWA Meta Tags -->
-    <meta name="description" content="Monitor and manage your Bambu Lab 3D printers" />
-    <meta name="theme-color" content="#00ae42" />
-    <meta name="mobile-web-app-capable" content="yes" />
-    <meta name="apple-mobile-web-app-capable" content="yes" />
-    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
-    <meta name="apple-mobile-web-app-title" content="Bambuddy" />
-
-    <!-- Manifest -->
-    <link rel="manifest" href="/manifest.json" />
-
-    <!-- Favicons -->
-    <link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
-    <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
-    <link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
-
-    <!-- Splash screens for iOS -->
-    <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BkwFYHam.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-B3jj6-fz.css">
-  </head>
-  <body>
-    <div id="root"></div>
-
-    <!-- Service Worker Registration (skip on SpoolBuddy kiosk).
-         Kept as an external file so the CSP `script-src 'self'` covers it
-         without needing 'unsafe-inline' or per-build hashes. -->
-    <script src="/sw-register.js"></script>
-  </body>
-</html>

+ 0 - 95
static/manifest.json

@@ -1,95 +0,0 @@
-{
-  "id": "/",
-  "name": "Bambuddy",
-  "short_name": "Bambuddy",
-  "description": "Monitor and manage your Bambu Lab 3D printers",
-  "start_url": "/",
-  "display": "standalone",
-  "background_color": "#1a1a1a",
-  "theme_color": "#00ae42",
-  "orientation": "any",
-  "scope": "/",
-  "icons": [
-    {
-      "src": "/img/favicon-16x16.png",
-      "sizes": "16x16",
-      "type": "image/png"
-    },
-    {
-      "src": "/img/favicon-32x32.png",
-      "sizes": "32x32",
-      "type": "image/png"
-    },
-    {
-      "src": "/img/android-chrome-192x192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/img/android-chrome-512x512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/img/android-chrome-192x192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "maskable"
-    },
-    {
-      "src": "/img/android-chrome-512x512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "maskable"
-    }
-  ],
-  "screenshots": [
-    {
-      "src": "/img/screenshot-mobile.png",
-      "sizes": "1080x1920",
-      "type": "image/png",
-      "form_factor": "narrow",
-      "label": "Bambuddy on mobile"
-    },
-    {
-      "src": "/img/screenshot-desktop.png",
-      "sizes": "1920x1080",
-      "type": "image/png",
-      "form_factor": "wide",
-      "label": "Bambuddy on desktop"
-    }
-  ],
-  "categories": ["utilities", "productivity"],
-  "shortcuts": [
-    {
-      "name": "Printers",
-      "short_name": "Printers",
-      "description": "View your printers",
-      "url": "/",
-      "icons": [{ "src": "/img/android-chrome-192x192.png", "sizes": "192x192" }]
-    },
-    {
-      "name": "Archives",
-      "short_name": "Archives",
-      "description": "View print archives",
-      "url": "/archives",
-      "icons": [{ "src": "/img/android-chrome-192x192.png", "sizes": "192x192" }]
-    },
-    {
-      "name": "Queue",
-      "short_name": "Queue",
-      "description": "View print queue",
-      "url": "/queue",
-      "icons": [{ "src": "/img/android-chrome-192x192.png", "sizes": "192x192" }]
-    },
-    {
-      "name": "Projects",
-      "short_name": "Projects",
-      "description": "View print projects",
-      "url": "/projects",
-      "icons": [{ "src": "/img/android-chrome-192x192.png", "sizes": "192x192" }]
-    }
-  ]
-}

BIN
static/spoolbuddy_logo_dark.png


+ 0 - 37
static/sw-register.js

@@ -1,37 +0,0 @@
-if ('serviceWorker' in navigator) {
-  if (location.pathname.startsWith('/spoolbuddy')) {
-    navigator.serviceWorker.getRegistrations().then((regs) => {
-      if (regs.length > 0) {
-        Promise.all([
-          ...regs.map((r) => r.unregister()),
-          caches.keys().then((names) => Promise.all(names.map((n) => caches.delete(n)))),
-        ]).then(() => location.reload());
-      }
-    });
-  } else {
-    // Capture controller state at script-load. Used to decide whether a
-    // subsequent `controllerchange` is a deploy-pickup (had a prior SW →
-    // reload so the new bundle takes over) or a first install (no prior SW →
-    // skip the reload; the in-flight React mount would otherwise race the
-    // forced navigation, leaving the page wedged on a spinner. The previous
-    // approach — `client.navigate(client.url)` from the SW's activate
-    // handler — exhibited that race in Chromium and a waitUntil hang in
-    // Firefox, both surfaced on every fresh demo subdomain).
-    const hadController = !!navigator.serviceWorker.controller;
-    let reloading = false;
-    navigator.serviceWorker.addEventListener('controllerchange', () => {
-      if (!hadController || reloading) return;
-      reloading = true;
-      location.reload();
-    });
-    window.addEventListener('load', () => {
-      navigator.serviceWorker.register('/sw.js')
-        .then((registration) => {
-          console.log('SW registered:', registration.scope);
-        })
-        .catch((error) => {
-          console.log('SW registration failed:', error);
-        });
-    });
-  }
-}

+ 0 - 229
static/sw.js

@@ -1,229 +0,0 @@
-// Bambuddy Service Worker
-const CACHE_NAME = 'bambuddy-v30';
-const STATIC_CACHE = 'bambuddy-static-v29';
-
-// Static assets to cache on install
-const STATIC_ASSETS = [
-  '/',
-  '/manifest.json',
-  '/img/favicon.png',
-  '/img/favicon-16x16.png',
-  '/img/favicon-32x32.png',
-  '/img/android-chrome-192x192.png',
-  '/img/android-chrome-512x512.png',
-  '/img/apple-touch-icon.png',
-  '/img/bambuddy_logo_dark.png',
-  // Self-hosted Inter font (#1460) - cached so the UI renders offline.
-  '/fonts/inter-latin.woff2',
-  '/fonts/inter-latin-ext.woff2',
-];
-
-// Install event - cache static assets
-self.addEventListener('install', (event) => {
-  console.log('[SW] Installing service worker...');
-  event.waitUntil(
-    caches.open(STATIC_CACHE).then((cache) => {
-      console.log('[SW] Caching static assets');
-      return cache.addAll(STATIC_ASSETS);
-    })
-  );
-  // Activate immediately
-  self.skipWaiting();
-});
-
-// Activate event - clean up old caches and claim existing clients.
-//
-// The forced reload that picks up a new bundle on already-open clients (the
-// kiosk deploy-pickup scenario) lives in sw-register.js via a
-// `controllerchange` listener, gated on whether the page already had a SW
-// controller at load time. That gate distinguishes first-install (where a
-// reload would race the in-flight React mount — observed on every fresh
-// *.demo.bambuddy.cool subdomain, and in Firefox the activate's waitUntil
-// hung on `client.navigate` until the document load was aborted with a
-// Corrupted-Content error) from upgrade-on-existing-client (where the reload
-// is wanted).
-self.addEventListener('activate', (event) => {
-  console.log('[SW] Activating service worker...');
-  event.waitUntil(
-    (async () => {
-      const cacheNames = await caches.keys();
-      await Promise.all(
-        cacheNames
-          .filter((name) => name !== CACHE_NAME && name !== STATIC_CACHE)
-          .map((name) => {
-            console.log('[SW] Deleting old cache:', name);
-            return caches.delete(name);
-          }),
-      );
-      await self.clients.claim();
-    })(),
-  );
-});
-
-// Fetch event - network-first for API, cache-first for static
-self.addEventListener('fetch', (event) => {
-  const { request } = event;
-  const url = new URL(request.url);
-
-  // Skip non-GET requests
-  if (request.method !== 'GET') {
-    return;
-  }
-
-  // Skip cross-origin requests - let the browser handle them directly.
-  // Without this the catch-all HTML branch below would answer a failed
-  // cross-origin request with our cached index.html, so e.g. a blocked
-  // Google Fonts request came back as text/html (#1460).
-  if (url.origin !== self.location.origin) {
-    return;
-  }
-
-  // Skip WebSocket connections
-  if (url.protocol === 'ws:' || url.protocol === 'wss:') {
-    return;
-  }
-
-  // Skip camera stream/snapshot requests - Safari has issues with streaming through SW
-  if (url.pathname.includes('/camera/stream') || url.pathname.includes('/camera/snapshot')) {
-    return;
-  }
-
-  // API requests - network first, no cache (real-time data is critical)
-  if (url.pathname.startsWith('/api/')) {
-    event.respondWith(
-      fetch(request).catch(() => {
-        // Return offline response for API failures
-        return new Response(
-          JSON.stringify({ error: 'offline', message: 'You are currently offline' }),
-          {
-            status: 503,
-            headers: { 'Content-Type': 'application/json' },
-          }
-        );
-      })
-    );
-    return;
-  }
-
-  // Static assets - cache first, then network
-  if (
-    url.pathname.startsWith('/img/') ||
-    url.pathname.startsWith('/icons/') ||
-    url.pathname.startsWith('/fonts/') ||
-    url.pathname.endsWith('.png') ||
-    url.pathname.endsWith('.jpg') ||
-    url.pathname.endsWith('.svg') ||
-    url.pathname.endsWith('.ico') ||
-    url.pathname.endsWith('.woff2')
-  ) {
-    event.respondWith(
-      caches.match(request).then((cached) => {
-        if (cached) {
-          return cached;
-        }
-        return fetch(request).then((response) => {
-          // Cache successful responses
-          if (response.ok) {
-            const clone = response.clone();
-            caches.open(STATIC_CACHE).then((cache) => {
-              cache.put(request, clone);
-            });
-          }
-          return response;
-        });
-      })
-    );
-    return;
-  }
-
-  // JS/CSS assets - network first (Vite content-hashes filenames, so
-  // cache-busting is built in; network-first ensures new builds load immediately)
-  if (
-    url.pathname.startsWith('/assets/') ||
-    url.pathname.endsWith('.js') ||
-    url.pathname.endsWith('.css')
-  ) {
-    event.respondWith(
-      fetch(request)
-        .then((response) => {
-          if (response.ok) {
-            const clone = response.clone();
-            caches.open(CACHE_NAME).then((cache) => {
-              cache.put(request, clone);
-            });
-          }
-          return response;
-        })
-        .catch(() => {
-          return caches.match(request);
-        })
-    );
-    return;
-  }
-
-  // HTML pages - network first, fall back to cache
-  event.respondWith(
-    fetch(request)
-      .then((response) => {
-        if (response.ok) {
-          const clone = response.clone();
-          caches.open(CACHE_NAME).then((cache) => {
-            cache.put(request, clone);
-          });
-        }
-        return response;
-      })
-      .catch(() => {
-        return caches.match(request).then((cached) => {
-          if (cached) {
-            return cached;
-          }
-          // Return cached index for SPA navigation
-          return caches.match('/');
-        });
-      })
-  );
-});
-
-// Handle push notifications (for future use)
-self.addEventListener('push', (event) => {
-  if (!event.data) return;
-
-  const data = event.data.json();
-  const options = {
-    body: data.body || 'New notification from Bambuddy',
-    icon: '/img/android-chrome-192x192.png',
-    badge: '/img/favicon-32x32.png',
-    vibrate: [100, 50, 100],
-    data: {
-      url: data.url || '/',
-    },
-  };
-
-  event.waitUntil(
-    self.registration.showNotification(data.title || 'Bambuddy', options)
-  );
-});
-
-// Handle notification clicks
-self.addEventListener('notificationclick', (event) => {
-  event.notification.close();
-
-  const url = event.notification.data?.url || '/';
-
-  event.waitUntil(
-    clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => {
-      // Check if there's already a window open
-      for (const client of windowClients) {
-        if (client.url.includes(self.location.origin) && 'focus' in client) {
-          client.navigate(url);
-          return client.focus();
-        }
-      }
-      // Open a new window if none exists
-      if (clients.openWindow) {
-        return clients.openWindow(url);
-      }
-    })
-  );
-});

+ 0 - 1
static/vite.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Некоторые файлы не были показаны из-за большого количества измененных файлов