Bladeren bron

Reprint the plate the archive printed, not plate 1 (#3395)

- Print on an archive (card and list row), Print again in the outcome
  dialog and Re-queue in the queue history open the print dialog on
  the plate that was printed, scrolled into view. They opened on
  plate 1, the only one in view in a long plate list.
- A plate the file no longer has falls back to the first plate; it
  stayed selected before, also when editing a queue item.
- The plates that will print are named above the buttons.
maziggy 2 dagen geleden
bovenliggende
commit
d9d58de1db
39 gewijzigde bestanden met toevoegingen van 229 en 15 verwijderingen
  1. 3 0
      CHANGELOG.md
  2. 14 1
      frontend/src/__tests__/components/ConfirmOutcomeDialog.test.tsx
  3. 82 0
      frontend/src/__tests__/components/PrintModal.test.tsx
  4. 34 0
      frontend/src/__tests__/pages/ArchivesPageWiring.test.tsx
  5. 23 0
      frontend/src/__tests__/pages/QueuePage.test.tsx
  6. 1 0
      frontend/src/components/ConfirmOutcomeDialog.tsx
  7. 13 0
      frontend/src/components/PrintModal/PlateSelector.tsx
  8. 41 5
      frontend/src/components/PrintModal/index.tsx
  9. 6 0
      frontend/src/components/PrintModal/types.ts
  10. 2 0
      frontend/src/pages/ArchivesPage.tsx
  11. 1 0
      frontend/src/pages/QueuePage.tsx
  12. 0 0
      static/assets/ArchivesPage-CK7FSbaj.js
  13. 0 0
      static/assets/CamWallPage-CoqULYx5.js
  14. 0 0
      static/assets/CameraPage-CNbvXWYb.js
  15. 0 0
      static/assets/ConfirmOutcomeDialog-BbGTWAed.js
  16. 0 0
      static/assets/FileManagerPage-dtGApOUu.js
  17. 0 0
      static/assets/FinancePage-C_AwsL1h.js
  18. 0 0
      static/assets/InventoryPage-BvTUoms0.js
  19. 0 0
      static/assets/Layout-DImTD50D.js
  20. 0 0
      static/assets/ModelSourcesPage-CO86duzB.js
  21. 0 0
      static/assets/PhotoGalleryModal-CvtaJKzW.js
  22. 0 0
      static/assets/PrintModal-CZIbcLUP.js
  23. 0 0
      static/assets/PrintModal-DQ-xVUNN.js
  24. 0 0
      static/assets/PrintersPage-ZxGYaSN4.js
  25. 0 0
      static/assets/ProjectDetailPage-C0ZsgE5J.js
  26. 0 0
      static/assets/ProjectsPage-CdvcmTJL.js
  27. 0 0
      static/assets/QueuePage-DdltWLWm.js
  28. 0 0
      static/assets/RichTextEditor-CXp5MDTi.js
  29. 0 0
      static/assets/SettingsPage-DpSkP1Te.js
  30. 0 0
      static/assets/SliceModal-DEnxxNHd.js
  31. 1 1
      static/assets/SpoolBuddySettingsPage-R20JKCVW.js
  32. 0 0
      static/assets/SpoolBuddyWriteTagPage-CcjmPdKZ.js
  33. 0 0
      static/assets/StatsPage-BTtbrRsG.js
  34. 1 1
      static/assets/currency-LmqFrzn3.js
  35. 1 0
      static/assets/index-Brnj6zSd.css
  36. 0 0
      static/assets/index-Bycsrcpp.js
  37. 0 1
      static/assets/index-DAazpymq.css
  38. 0 0
      static/assets/locationSensorDefaults-CZr-vqtp.js
  39. 6 6
      static/index.html

+ 3 - 0
CHANGELOG.md

@@ -193,6 +193,9 @@ All notable changes to Bambuddy will be documented in this file.
 - **The frontend build no longer warns about `path` and `crypto` being externalized for the STEP previewer (#2976)** — `occt-import-js`, the Emscripten build behind STEP previews, requires both modules, but only inside its `ENVIRONMENT_IS_NODE` branches; in the browser it loads its `.wasm` from the URL the preview worker passes and draws randomness from `crypto.getRandomValues`. Vite still externalized both and printed two warnings on every build. `vite.config.ts` now drops exactly those two warnings for that one package through `build.rolldownOptions.onLog`, so an externalization anywhere else, or of any other module, still shows.
 
 ### Fixed
+- **Reprinting a plate of a multi-plate file prints that plate, not plate 1 (#3395, reported by @Thomansky)** — **Print** on an archive, **Print again** in the print-outcome dialog and **Re-queue** in the queue history opened the print dialog with plate 1 selected, whichever plate the job had printed. In a file with many plates, plate 1 was the only one in view, so one click on Print printed the wrong part.
+  - The dialog now opens on the plate that was printed and scrolls it into view. If the file no longer has that plate, it falls back to the first one.
+  - The plates that will print are named above the buttons, e.g. "Plate 10 – Lid", so the choice stays visible however long the plate list is.
 - **SUNLU and some P1/X1 presets put the right filament in an AMS slot (#3387, reported by @Spegeli)** — Bambuddy worked out a slot's filament id from a Bambu preset's setting id by dropping the "S" after "GF". For SUNLU that gives an id no profile has: SUNLU PLA+ (`GFSNLS03_04`) went into the slot as `GFNLS03` instead of `GFSNL03`. About thirty P1/X1-series presets carry another filament's id altogether, for example Generic TPU @BBL P1P is `GFU99`, not Generic EVA's `GFR99`.
   - **Configure AMS Slot** now takes the filament id from Bambu's own preset list, which carries it for every preset.
   - **Spool assignment, in both inventory modes, and K-profile matching** know SUNLU's ids and look up the P1/X1 exceptions by full setting id.

+ 14 - 1
frontend/src/__tests__/components/ConfirmOutcomeDialog.test.tsx

@@ -12,7 +12,9 @@ import { server } from '../mocks/server';
 
 // The reject flow can hand off into the full PrintModal — not under test here.
 vi.mock('../../components/PrintModal', () => ({
-  PrintModal: () => <div data-testid="print-modal" />,
+  PrintModal: ({ preselectPlateId }: { preselectPlateId?: number | null }) => (
+    <div data-testid="print-modal" data-plate={String(preselectPlateId)} />
+  ),
 }));
 
 const baseArchive = {
@@ -145,6 +147,17 @@ describe('ConfirmOutcomeDialog', () => {
     });
   });
 
+  it('reprints the plate the archive printed (#3395)', async () => {
+    server.use(http.get('/api/v1/archives/42', () => HttpResponse.json({ ...baseArchive, plate_id: 10 })));
+    const user = userEvent.setup();
+    render(<ConfirmOutcomeDialog archiveId={42} onClose={vi.fn()} />);
+
+    await user.click(await screen.findByText('Reject'));
+    await user.click(await screen.findByText('Print again'));
+
+    expect(await screen.findByTestId('print-modal')).toHaveAttribute('data-plate', '10');
+  });
+
   it('shows the already-decided state instead of the buttons', async () => {
     server.use(
       http.get('/api/v1/archives/42', () =>

+ 82 - 0
frontend/src/__tests__/components/PrintModal.test.tsx

@@ -1167,6 +1167,88 @@ describe('PrintModal', () => {
       expect((queueRequests[1] as { plate_id: number }).plate_id).toBe(3);
     });
 
+    // #3395: a reprint opens on the plate the archive printed, not plate 1.
+    async function submitAndCollect(preselectPlateId: number | null) {
+      const queueRequests: { plate_id: number }[] = [];
+      server.use(
+        http.post('/api/v1/queue/', async ({ request }) => {
+          queueRequests.push((await request.json()) as { plate_id: number });
+          return HttpResponse.json({ id: queueRequests.length, status: 'pending' });
+        }),
+      );
+      const user = userEvent.setup();
+      render(
+        <PrintModal
+          mode="create"
+          archiveId={1}
+          archiveName="MultiPlate.3mf"
+          preselectPlateId={preselectPlateId}
+          initialSelectedPrinterIds={[1]}
+          onClose={mockOnClose}
+          onSuccess={mockOnSuccess}
+        />
+      );
+      await waitFor(() => {
+        expect(screen.getByText('Select All 3 Plates')).toBeInTheDocument();
+      });
+      await user.click(document.querySelector('button[type="submit"]') as HTMLElement);
+      await waitFor(() => {
+        expect(queueRequests.length).toBe(1);
+      });
+      return queueRequests.map((r) => r.plate_id);
+    }
+
+    it('queues the pre-selected plate only', async () => {
+      expect(await submitAndCollect(3)).toEqual([3]);
+      expect(screen.getByTestId('selected-plates-summary')).toHaveTextContent('Plate 3');
+    });
+
+    it('falls back to the first plate when the file no longer has the pre-selected one', async () => {
+      expect(await submitAndCollect(9)).toEqual([1]);
+    });
+
+    it('names the selected plates beside the buttons, with their names', async () => {
+      server.use(
+        http.get('/api/v1/archives/:id/plates', () =>
+          HttpResponse.json({
+            ...multiPlateResponse,
+            plates: multiPlateResponse.plates.map((p) => (p.index === 2 ? { ...p, name: 'Lid' } : p)),
+          }),
+        ),
+      );
+      const user = userEvent.setup();
+      render(
+        <PrintModal mode="create" archiveId={1} archiveName="MultiPlate.3mf" preselectPlateId={2} onClose={mockOnClose} />
+      );
+      await waitFor(() => {
+        expect(screen.getByTestId('selected-plates-summary')).toHaveTextContent('Plate 2 – Lid');
+      });
+      await user.click(screen.getByText('Plate 3'));
+      expect(screen.getByTestId('selected-plates-summary')).toHaveTextContent('Plate 2 – Lid, Plate 3');
+    });
+
+    it('scrolls the pre-selected plate into view', async () => {
+      const scrolled: string[] = [];
+      const original = Element.prototype.scrollIntoView;
+      Element.prototype.scrollIntoView = function (this: Element) {
+        scrolled.push(this.textContent ?? '');
+      };
+      try {
+        render(
+          <PrintModal mode="create" archiveId={1} archiveName="MultiPlate.3mf" preselectPlateId={3} onClose={mockOnClose} />
+        );
+        await waitFor(() => {
+          expect(scrolled.length).toBe(1);
+        });
+        expect(scrolled[0]).toContain('Part C');
+        // Once: picking another plate doesn't move the list again.
+        await userEvent.setup().click(screen.getByText('Plate 1'));
+        expect(scrolled.length).toBe(1);
+      } finally {
+        Element.prototype.scrollIntoView = original;
+      }
+    });
+
     it('creates one queue item per plate when submitting with select-all', async () => {
       const queueRequests: unknown[] = [];
       server.use(

+ 34 - 0
frontend/src/__tests__/pages/ArchivesPageWiring.test.tsx

@@ -513,3 +513,37 @@ describe('ArchivesPage wiring: print log', () => {
     expect(mutations()).toHaveLength(0);
   });
 });
+
+describe('ArchivesPage wiring: reprint opens on the archive plate (#3395)', () => {
+  const plate = (index: number) => ({
+    index, name: '', has_thumbnail: false, thumbnail_url: null, objects: [`Part ${index}`],
+    filaments: [], print_time_seconds: 600, filament_used_grams: 5,
+  });
+
+  beforeEach(() => {
+    server.use(
+      http.get('*/api/v1/archives/', () =>
+        HttpResponse.json(ARCHIVES.map((a) => ({ ...a, file_path: `archive/${a.id}.gcode.3mf`, plate_id: a.id === 2 ? 3 : null }))),
+      ),
+      http.get('*/api/v1/archives/:id/plates', () =>
+        HttpResponse.json({ is_multi_plate: true, plates: [plate(1), plate(2), plate(3)] }),
+      ),
+      http.get('*/api/v1/archives/:id/filament-requirements', () => HttpResponse.json({ filaments: [] })),
+    );
+  });
+
+  it.each([
+    ['card', null, (el: HTMLElement) => within(el).getByRole('button', { name: /Print/ })],
+    ['list row', 'list', (el: HTMLElement) => within(el).getByTitle('Print')],
+  ])('%s Print preselects the plate the archive printed', async (_where, mode, button) => {
+    viewMode = mode;
+    const user = userEvent.setup();
+    render(<ArchivesPage />);
+
+    await user.click(button(await cardFor(2)));
+
+    await waitFor(() => {
+      expect(screen.getByTestId('selected-plates-summary')).toHaveTextContent(/^Plate 3$/);
+    });
+  });
+});

+ 23 - 0
frontend/src/__tests__/pages/QueuePage.test.tsx

@@ -1660,6 +1660,29 @@ describe('QueuePage', () => {
       const requeueButtons = screen.getAllByTitle('Re-queue');
       expect(requeueButtons.length).toBeGreaterThan(0);
     });
+
+    it('re-queues the plate the job printed (#3395)', async () => {
+      const plate = (index: number) => ({
+        index, name: '', has_thumbnail: false, thumbnail_url: null, objects: [`Part ${index}`],
+        filaments: [], print_time_seconds: 600, filament_used_grams: 5,
+      });
+      server.use(
+        ...serveQueue(mockQueueItems.map((i) => (i.id === 3 ? { ...i, plate_id: 2 } : i))),
+        http.get('/api/v1/archives/:id/plates', () =>
+          HttpResponse.json({ is_multi_plate: true, plates: [plate(1), plate(2), plate(3)] }),
+        ),
+      );
+      const user = userEvent.setup();
+      render(<QueuePage />);
+
+      await user.click(await screen.findByRole('button', { name: /^History/ }));
+      await screen.findByText('Completed Print');
+      await user.click(screen.getAllByTitle('Re-queue')[0]);
+
+      await waitFor(() => {
+        expect(screen.getByTestId('selected-plates-summary')).toHaveTextContent(/^Plate 2$/);
+      });
+    });
   });
 
   describe('clear history', () => {

+ 1 - 0
frontend/src/components/ConfirmOutcomeDialog.tsx

@@ -85,6 +85,7 @@ export function ConfirmOutcomeDialog({ archiveId, onClose }: ConfirmOutcomeDialo
         mode="create"
         archiveId={archiveId}
         archiveName={name}
+        preselectPlateId={archive.plate_id}
         onClose={onClose}
       />
     );

+ 13 - 0
frontend/src/components/PrintModal/PlateSelector.tsx

@@ -1,3 +1,4 @@
+import { useEffect, useRef } from 'react';
 import { Layers, Check, AlertTriangle, Square, CheckSquare } from 'lucide-react';
 import { useTranslation } from 'react-i18next';
 import type { PlateSelectorProps } from './types';
@@ -24,6 +25,17 @@ export function PlateSelector({
   onQuantityChange,
 }: PlateSelectorProps) {
   const { t } = useTranslation();
+  // A plate selected on opening (a reprint of plate 10, say) can be far down a
+  // long list: bring it into view once, when the cards first appear (#3395).
+  const firstSelectedRef = useRef<HTMLDivElement | null>(null);
+  const scrolled = useRef(false);
+  const visible = isMultiPlate && plates.length > 1;
+  useEffect(() => {
+    if (scrolled.current || !visible || !firstSelectedRef.current) return;
+    scrolled.current = true;
+    firstSelectedRef.current.scrollIntoView?.({ block: 'nearest' });
+  }, [visible, selectedPlates]);
+  const firstSelected = plates.find((p) => selectedPlates.has(p.index))?.index;
 
   // Only show for multi-plate files with multiple plates
   if (!isMultiPlate || plates.length <= 1) {
@@ -73,6 +85,7 @@ export function PlateSelector({
                region stays a button beside the stepper. */
             <div
               key={plate.index}
+              ref={plate.index === firstSelected ? firstSelectedRef : undefined}
               className={`flex items-center gap-2 p-2 rounded-lg border transition-colors ${
                 isSelected
                   ? 'border-bambu-green bg-bambu-green/10'

+ 41 - 5
frontend/src/components/PrintModal/index.tsx

@@ -1,5 +1,5 @@
 import { useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
-import { AlertCircle, AlertTriangle, Loader2, Pencil, Printer, ThumbsUp, X } from 'lucide-react';
+import { AlertCircle, AlertTriangle, Layers, Loader2, Pencil, Printer, ThumbsUp, X } from 'lucide-react';
 import { useEffect, useMemo, useRef, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import type { CostCenterSummary, PrintQueueItemCreate, PrintQueueItemUpdate, SlotMaterial } from '../../api/client';
@@ -92,6 +92,7 @@ export function PrintModal({
   archiveName,
   queueItem,
   initialSelectedPrinterIds,
+  preselectPlateId,
   onClose,
   onSuccess,
   projectId,
@@ -159,6 +160,9 @@ export function PrintModal({
     if (mode === 'edit-queue-item' && queueItem?.plate_id != null) {
       return new Set([queueItem.plate_id]);
     }
+    if (mode === 'create' && !isCrossModel && preselectPlateId != null) {
+      return new Set([preselectPlateId]);
+    }
     return new Set();
   });
 
@@ -740,11 +744,19 @@ export function PrintModal({
     sharedMappingPrinterId,
   );
 
-  // Auto-select first plate when plates load (single or multi-plate)
+  // Auto-select first plate when plates load (single or multi-plate). A plate
+  // chosen before the plates were known (a reprint's or a queue item's) that
+  // the file doesn't have, because the file changed since, is dropped: left
+  // selected, it would be printed or keep the selection from ever falling
+  // back to a plate the file has (#3395).
   useEffect(() => {
-    if (platesData?.plates && platesData.plates.length >= 1 && selectedPlates.size === 0) {
-      setSelectedPlates(new Set([platesData.plates[0].index]));
-    }
+    const list = platesData?.plates;
+    if (!list?.length) return;
+    setSelectedPlates((prev) => {
+      const kept = [...prev].filter((index) => list.some((p) => p.index === index));
+      if (kept.length > 0 && kept.length === prev.size) return prev;
+      return new Set(kept.length > 0 ? kept : [list[0].index]);
+    });
   }, [platesData, selectedPlates.size]);
 
   // Cross-model: the candidate list owns plate choice, and `platesData` is the
@@ -891,6 +903,19 @@ export function PrintModal({
 
   const isMultiPlate = platesData?.is_multi_plate ?? false;
   const plates = platesData?.plates ?? [];
+  // The plates that will print, beside the buttons: in a long plate list the
+  // selected card can be far out of view (#3395).
+  const selectedPlatesSummary =
+    !isCrossModel && isMultiPlate && plates.length > 1
+      ? plates
+          .filter((p) => selectedPlates.has(p.index))
+          .map((p) => {
+            const label = t('printModal.plateN', 'Plate {{n}}', { n: p.index });
+            const name = p.name?.trim();
+            return name && name !== label ? `${label} – ${name}` : label;
+          })
+          .join(', ')
+      : '';
 
   const filamentWarningMessage = useMemo(() => {
     if (!filamentWarningItems || filamentWarningItems.length === 0) return '';
@@ -2147,6 +2172,17 @@ export function PrintModal({
               </div>
             )}
 
+            {selectedPlatesSummary && (
+              <p
+                className="flex items-center gap-1.5 text-sm text-bambu-gray min-w-0"
+                title={selectedPlatesSummary}
+                data-testid="selected-plates-summary"
+              >
+                <Layers className="w-4 h-4 flex-shrink-0" />
+                <span className="truncate">{selectedPlatesSummary}</span>
+              </p>
+            )}
+
             {/* Actions */}
             <div className="flex gap-3 pt-2">
               <Button type="button" variant="secondary" onClick={onClose} className="flex-1" disabled={isSubmitting}>

+ 6 - 0
frontend/src/components/PrintModal/types.ts

@@ -31,6 +31,12 @@ export interface PrintModalProps {
   queueItem?: PrintQueueItem;
   /** Pre-select specific printers when opening the modal */
   initialSelectedPrinterIds?: number[];
+  /**
+   * Pre-select this plate when opening the modal (create mode): a reprint of
+   * an archive, or a re-queue, prints the plate that job printed, not plate 1.
+   * A plate the file no longer has falls back to the first plate.
+   */
+  preselectPlateId?: number | null;
   /** Handler for closing the modal */
   onClose: () => void;
   /** Handler for successful operation */

+ 2 - 0
frontend/src/pages/ArchivesPage.tsx

@@ -1567,6 +1567,7 @@ function ArchiveCard({
           mode="create"
           archiveId={archive.id}
           archiveName={archive.print_name || archive.filename}
+          preselectPlateId={archive.plate_id}
           onClose={() => setShowReprint(false)}
         />
       )}
@@ -2696,6 +2697,7 @@ function ArchiveListRow({
           mode="create"
           archiveId={archive.id}
           archiveName={archive.print_name || archive.filename}
+          preselectPlateId={archive.plate_id}
           onClose={() => setShowReprint(false)}
         />
       )}

+ 1 - 0
frontend/src/pages/QueuePage.tsx

@@ -3253,6 +3253,7 @@ export function QueuePage() {
           archiveId={requeueItem.archive_id ?? undefined}
           libraryFileId={requeueItem.library_file_id ?? undefined}
           archiveName={queueItemDisplayName(requeueItem, (n) => t('common.plusNMore', { count: n }))}
+          preselectPlateId={requeueItem.plate_id}
           onClose={() => setRequeueItem(null)}
         />
       )}

File diff suppressed because it is too large
+ 0 - 0
static/assets/ArchivesPage-CK7FSbaj.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CamWallPage-CoqULYx5.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/CameraPage-CNbvXWYb.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ConfirmOutcomeDialog-BbGTWAed.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/FileManagerPage-dtGApOUu.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/FinancePage-C_AwsL1h.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/InventoryPage-BvTUoms0.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/Layout-DImTD50D.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ModelSourcesPage-CO86duzB.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PhotoGalleryModal-CvtaJKzW.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PrintModal-CZIbcLUP.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PrintModal-DQ-xVUNN.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/PrintersPage-ZxGYaSN4.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ProjectDetailPage-C0ZsgE5J.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/ProjectsPage-CdvcmTJL.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/QueuePage-DdltWLWm.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/RichTextEditor-CXp5MDTi.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SettingsPage-DpSkP1Te.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SliceModal-DEnxxNHd.js


File diff suppressed because it is too large
+ 1 - 1
static/assets/SpoolBuddySettingsPage-R20JKCVW.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/SpoolBuddyWriteTagPage-CcjmPdKZ.js


File diff suppressed because it is too large
+ 0 - 0
static/assets/StatsPage-BTtbrRsG.js


+ 1 - 1
static/assets/currency-R7oUEamO.js → static/assets/currency-LmqFrzn3.js

@@ -1 +1 @@
-var e={USD:`$`,EUR:`€`,GBP:`£`,CHF:`Fr.`,JPY:`¥`,CNY:`¥`,CAD:`$`,AUD:`$`,INR:`₹`,HKD:`HK$`,KRW:`₩`,SEK:`kr`,NOK:`kr`,DKK:`kr`,PLN:`zł`,BRL:`R$`,TWD:`NT$`,SGD:`S$`,NZD:`NZ$`,MXN:`MX$`,BZD:`BZ$`,MYR:`RM`,CZK:`Kč`,THB:`฿`,ZAR:`R`,TRY:`₺`,RUB:`₽`,HUF:`Ft`,ILS:`₪`,UAH:`₴`,IDR:`Rp`,PHP:`₱`,EGP:`E£`};function t(t){return e[t.toUpperCase()]||t}var n=Object.entries(e).map(([e,t])=>({code:e,label:`${e} (${t})`}));export{t as n,n as t};
+var e={USD:`$`,EUR:`€`,GBP:`£`,CHF:`Fr.`,JPY:`¥`,CNY:`¥`,CAD:`$`,AUD:`$`,INR:`₹`,HKD:`HK$`,KRW:`₩`,SEK:`kr`,NOK:`kr`,DKK:`kr`,PLN:`zł`,BRL:`R$`,TWD:`NT$`,SGD:`S$`,NZD:`NZ$`,MXN:`MX$`,BZD:`BZ$`,MYR:`RM`,CZK:`Kč`,THB:`฿`,ZAR:`R`,TRY:`₺`,RUB:`₽`,HUF:`Ft`,ILS:`₪`,UAH:`₴`,IDR:`Rp`,PHP:`₱`,EGP:`E£`,DOP:`RD$`};function t(t){return e[t.toUpperCase()]||t}var n=Object.entries(e).map(([e,t])=>({code:e,label:`${e} (${t})`}));export{t as n,n as t};

File diff suppressed because it is too large
+ 1 - 0
static/assets/index-Brnj6zSd.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-Bycsrcpp.js


File diff suppressed because it is too large
+ 0 - 1
static/assets/index-DAazpymq.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/locationSensorDefaults-CZr-vqtp.js


+ 6 - 6
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CnjrYt4Y.js"></script>
+    <script type="module" crossorigin src="/assets/index-Bycsrcpp.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-aKtaBQYM.js">
     <link rel="modulepreload" crossorigin href="/assets/client-Wixdakxn.js">
     <link rel="modulepreload" crossorigin href="/assets/react-dom-9hW1swN1.js">
@@ -93,13 +93,13 @@
     <link rel="modulepreload" crossorigin href="/assets/date-G9KUauqz.js">
     <link rel="modulepreload" crossorigin href="/assets/amsHelpers-DDsrDUnz.js">
     <link rel="modulepreload" crossorigin href="/assets/colors-BBBvvSKR.js">
-    <link rel="modulepreload" crossorigin href="/assets/currency-R7oUEamO.js">
+    <link rel="modulepreload" crossorigin href="/assets/currency-LmqFrzn3.js">
     <link rel="modulepreload" crossorigin href="/assets/useUiSettings-BC4O5ZgX.js">
     <link rel="modulepreload" crossorigin href="/assets/NumberInput-VlZYDJ-L.js">
     <link rel="modulepreload" crossorigin href="/assets/printer-DfB6SVGc.js">
     <link rel="modulepreload" crossorigin href="/assets/filamentSwatchHelpers-C3r5m07g.js">
     <link rel="modulepreload" crossorigin href="/assets/FilamentSwatch-CAdwRRk_.js">
-    <link rel="modulepreload" crossorigin href="/assets/PrintModal-CZIbcLUP.js">
+    <link rel="modulepreload" crossorigin href="/assets/PrintModal-DQ-xVUNN.js">
     <link rel="modulepreload" crossorigin href="/assets/download-CIYQYgRy.js">
     <link rel="modulepreload" crossorigin href="/assets/eye-DotmdMYm.js">
     <link rel="modulepreload" crossorigin href="/assets/folder-kanban-CmkoZHYI.js">
@@ -110,7 +110,7 @@
     <link rel="modulepreload" crossorigin href="/assets/trash-2-BuReB2M2.js">
     <link rel="modulepreload" crossorigin href="/assets/weight-vy0UgZLK.js">
     <link rel="modulepreload" crossorigin href="/assets/projectTree-C5LrJHZP.js">
-    <link rel="modulepreload" crossorigin href="/assets/ConfirmOutcomeDialog-C2TBZ334.js">
+    <link rel="modulepreload" crossorigin href="/assets/ConfirmOutcomeDialog-BbGTWAed.js">
     <link rel="modulepreload" crossorigin href="/assets/key-DP9lXZjm.js">
     <link rel="modulepreload" crossorigin href="/assets/log-out-DEHZfwc9.js">
     <link rel="modulepreload" crossorigin href="/assets/shield-alert-DhDK-8wS.js">
@@ -122,8 +122,8 @@
     <link rel="modulepreload" crossorigin href="/assets/useColorCatalogVersion-me0iWO3m.js">
     <link rel="modulepreload" crossorigin href="/assets/circle-minus-BWJkPGS-.js">
     <link rel="modulepreload" crossorigin href="/assets/ConnectionDiagnostic-DSIiTSOB.js">
-    <link rel="modulepreload" crossorigin href="/assets/Layout-DBQNIECX.js">
-    <link rel="stylesheet" crossorigin href="/assets/index-DAazpymq.css">
+    <link rel="modulepreload" crossorigin href="/assets/Layout-DImTD50D.js">
+    <link rel="stylesheet" crossorigin href="/assets/index-Brnj6zSd.css">
   </head>
   <body>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff