From 50d19848208a495b2c9ef4355f625a4cd549c35a Mon Sep 17 00:00:00 2001 From: maziggy Date: Fri, 22 May 2026 08:26:50 +0200 Subject: [PATCH] fix(printer): stop the File Manager polling the printer over FTPS every 30s (#1480) FileManagerModal ran its file-listing query with refetchInterval 30000, opening a fresh FTPS connection (full TLS handshake) to the printer every 30s while the modal was open. On fragile controllers like the P1S that load tipped MQTT, FTP and the camera into simultaneous timeouts. Drop the interval; the listing still refreshes on open, directory change, after upload/delete, and via the manual Refresh button. Also log STL thumbnail failures with a traceback (exc_info) so a data-specific "str / str" TypeError can be located from a support bundle. --- CHANGELOG.md | 2 ++ backend/app/services/stl_thumbnail.py | 7 ++++++- frontend/src/components/FileManagerModal.tsx | 7 ++++++- static/assets/{index-B2ysIPjO.js => index-B7aP7po9.js} | 2 +- static/index.html | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) rename static/assets/{index-B2ysIPjO.js => index-B7aP7po9.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8229a2069..4d4c4db4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ All notable changes to Bambuddy will be documented in this file. - **PyJWT CVE-2025-45768 (PYSEC-2025-183 / GHSA-65pc-fj4g-8rjx): permanently ignored in pip-audit** — Advisory is disputed by the PyJWT maintainers, with the advisory description literally noting *"this is disputed by the Supplier because the key length is chosen by the application that uses the library."* `fix_versions=[]` on the advisory confirms no PyJWT patch exists or will exist. Bambuddy is not affected: `backend/app/core/auth.py:184` auto-generates secrets via `secrets.token_urlsafe(64)` (~86 chars of entropy, far above any sane minimum) and the file-loaded path at `:177` rejects secrets shorter than 32 chars. Added a permanent `--ignore-vuln CVE-2025-45768` to `.github/workflows/security.yml` with an inline comment citing the file:line evidence so a future maintainer reviewing the ignore list sees why it's load-bearing. Also dropped the stale `--ignore-vuln CVE-2026-4539` for Pygments — Pygments has since shipped a patched version and the ignore is no longer load-bearing (verified: `pip-audit --ignore-vuln CVE-2025-45768` alone reports clean). ### Fixed +- **File Manager no longer polls the printer over FTPS every 30 seconds while open (#1480, reported by @OscarsWorldTech)** — The reporter's P1S churned through MQTT disconnect/reconnect cycles and timelapse downloads silently failed. The support bundle showed the real picture: during the churn windows, MQTT (`Connection stale - no message for 60.2s`), FTPS (`_ssl.c:1015: The handshake operation timed out`) and the camera all timed out *together* and recovered together — the P1S's embedded controller saturating, not a network fault (wifi -44 dBm, Docker host networking). A visible contributor on Bambuddy's side: `FileManagerModal.tsx` ran its `getPrinterFiles` query with `refetchInterval: 30000`, so every 30 s while the File Manager modal sat open it opened a *fresh* FTPS connection — full TLS handshake — to re-list the current directory. A printer's file list doesn't change on its own; it only changes on upload / delete (the modal's mutations already `invalidateQueries`) or when a print finishes. The blind 30 s poll was pure load, and on a fragile controller like the P1S it was enough to tip MQTT and FTP into the timeouts above. **Fix**: the `refetchInterval` is removed. The listing still refreshes on modal open, on directory / tab change (the path is in the query key), after every upload / delete, and via the existing manual Refresh button — so nothing stops updating, the printer just isn't hammered. Reduces steady-state FTPS connection load while the modal is open from one handshake every 30 s to zero. 19 FileManagerModal tests green; frontend build clean. +- **STL thumbnail generation failures now log a full traceback** — Surfaced by the #1480 support bundle: every STL in the reporter's library failed thumbnail generation with `unsupported operand type(s) for /: 'str' and 'str'`, but `generate_stl_thumbnail`'s `except` handler logged only the bare exception message — no traceback, no line number. The fault could not be reproduced from a clean STL across path shapes (`#` and spaces in the path), `str` vs `Path` arguments, or large meshes that exercise `simplify_quadric_decimation`, so it is data- or environment-specific and the message alone is not enough to locate it. `stl_thumbnail.py` now passes `exc_info=True` on that warning, so the next support bundle carries the traceback and the exact failing line. No behaviour change to thumbnail generation itself. - **Slicer: the Process / Filament dropdowns now filter by printer using the uploaded Slicer Bundles instead of guessing from preset names (#1325, reported by @IndividualGhost1905)** — After the printer-preset pre-selection landed, the reporter found the Process Profile dropdown still showed a flat mix of `@BBL X1C` and `@BBL P2S` presets with the printer set to X1C — P2S presets that should have dropped into the trailing "Other printers" group sat in the main list. **Root cause**: `frontend/src/utils/slicerPrinterMatch.ts` resolved each cloud / standard preset's printer by parsing the `@BBL ` suffix of its name against a hard-coded `KNOWN_MODEL_CODES` allow-list. That list (`X1C, X1E, X1, P1S, P1P, A1M, A1, H2D, H2S`) was missing `P2S` (and `H2C`, `X2D`), so every `@BBL P2S` preset parsed to an empty model-code set, `presetCompatibility` returned `unknown`, and the dropdown keeps `unknown` presets in the main list (only `mismatch` moves to "Other printers"). It was a maintenance trap by construction: every new Bambu model silently broke filtering until someone edited the list. **Fix**: the name-suffix heuristic and both hard-coded model tables (`KNOWN_MODEL_CODES`, `PRINTER_NAME_PATTERNS`) are removed. Compatibility is now read from ground truth — the user's uploaded Slicer Bundles (`.bbscfg`). Each bundle is scoped to one printer and lists the process / filament presets it ships, so "process P works with printer X" holds exactly when some uploaded bundle for printer X contains P. `buildCompatibilityIndex` builds a `presetName → {printer names}` index per slot from `GET /slicer/bundles` (already fetched by the modal), and `presetCompatibility` consults it — still preferring an imported preset's own `compatible_printers` list when present. A newly released Bambu model is covered the moment its bundle is uploaded, with no code change. Presets no bundle covers stay in the main list (`unknown` is never hidden), so a user with no bundle imported sees the un-filtered list rather than a wrong one. `printerPresetCode` / `presetModelCodes` are gone; `SliceModal` passes the bundle-derived index to `PresetDropdown`, `pickProcessDefault`, and `pickFilamentForSlot` in place of the old model code. **Tests**: `slicerPrinterMatch.test.ts` rewritten — 12 tests covering `buildCompatibilityIndex` (per-printer mapping, multi-bundle union, `# ` user-clone-prefix stripping, empty-printer skip) and `presetCompatibility` (imported-tier `compatible_printers` exact match, bundle-driven match / mismatch / unknown, the #1325 P2S-into-X1C repro, no-bundles and no-printer-selected cases). 32 SliceModal tests green; frontend build clean; backend ruff clean. - **PWA: Bambuddy can now be installed as an app on Android, and the font is self-hosted (#1460, reported by @Soopahfly)** — Reporter could install Bambuddy as a PWA on desktop but not on an Android phone (Pixel 9 Pro XL, failing in both Chrome and Edge). A thorough remote-DevTools trace confirmed the manifest was valid, all icons/screenshots present, and the service worker activated, running, and controlling the page — DevTools reported no installability blockers — yet no install prompt ever appeared. **Two root causes, both Bambuddy-side.** **(1) No in-app install trigger.** Chrome for Android removed the automatic install mini-infobar in Chrome 108 (2022); since then a site must either listen for `beforeinstallprompt` and surface its own button, or the user must dig into the browser's ⋮ menu. Desktop Chrome still auto-shows the omnibox install icon, which is exactly why desktop "worked" and Android didn't. Bambuddy had no `beforeinstallprompt` handler at all, so on Android there was no discoverable install path. New `frontend/src/components/InstallAppButton.tsx` captures the `beforeinstallprompt` event (calling `preventDefault()` so the button is the single predictable entry point), re-fires it on click, shows a success toast on accept, and clears the captured prompt afterwards (a prompt can only be used once). It renders nothing when there is no pending prompt — already installed, unsupported browser, or iOS Safari (no programmatic install) — so it never shows a dead button. Added to both the expanded and compact sidebar-footer rows in `Layout.tsx`, next to the GitHub link. New `nav.installApp` / `nav.installAppSuccess` i18n keys with real translations in all 9 locales (en/de/es/fr/it/ja/pt-BR/zh-CN/zh-TW). **(2) Inter font loaded from the Google Fonts CDN.** `frontend/src/index.css` pulled Inter via `@import url('https://fonts.googleapis.com/css2?...')`. For a local-first, offline-capable PWA this is wrong: it leaks a request to Google on every load, breaks the UI font when offline, and — as the reporter's trace showed — triggered CSP console errors (`connect-src` doesn't allow `fonts.googleapis.com`). The service worker made it worse: a request to `fonts.googleapis.com/css2` has path `/css2` (no `.css` extension), so it missed the CSS branch, fell through to the catch-all HTML branch, and on failure was answered with the cached `index.html` — which is why the font request came back as `text/html`. **Fix**: the two Inter variable woff2 files (latin + latin-ext, one file covers every weight via the variable axis) are now bundled in `frontend/public/fonts/` and declared with `@font-face` in `index.css`, served same-origin. The service worker now (a) skips all cross-origin requests entirely — letting the browser handle them so a failed cross-origin fetch can never be answered with `index.html` again — (b) caches the font files in `STATIC_ASSETS` and via a `.woff2` / `/fonts/` match in the cache-first branch so the UI renders offline, and (c) bumps its cache version. With Inter self-hosted, `fonts.googleapis.com` and `fonts.gstatic.com` were dropped from the SPA and gcode-viewer CSP directives in `backend/app/main.py` (the `/docs` ReDoc/Swagger CSP keeps them — that third-party UI genuinely loads Google Fonts). Frontend build clean, i18n parity green across 9 locales, backend ruff clean, 17 security-header tests green. - **Flow Calibration now actually runs when the print option is enabled (#1478, reported by @andreirusu99)** — Reporter on an H2S saw poor extrusion around corners (classic too-high K factor); the printer's flow-dynamics calibration step never appeared in the pre-print checklist even with **Flow Calibration** toggled on in the Re-print dialog, while the same 3MF printed from Bambu Studio did calibrate. **Root causes, both in the `project_file` command built by `start_print` (`backend/app/services/bambu_mqtt.py`)**: (1) `extrude_cali_flag` was hardcoded to `0`. A BambuStudio request-topic capture from a real H2D (plus X1C and P2S captures) shows BambuStudio always sends `1` (run flow-dynamics calibration) or `2` (skip, reuse the stored PA value), paired with `flow_cali`, and **never** `0` — so the printer skipped calibration regardless of the toggle. (2) An earlier revision integer-encoded the calibration/leveling fields (`timelapse`, `bed_leveling`, `flow_cali`, `vibration_cali`, `layer_inspect`) for the H2 family (H2D/H2S/H2C/X2D) on the belief that H2 firmware required `0/1`; the same H2D capture disproves this — BambuStudio sends plain JSON booleans for every model. The "integer required" claim conflated these fields with `use_ams`, which genuinely must stay boolean (H2D Pro reads an integer `use_ams` as a nozzle index — the actual #1386 cause). **Fix**: `extrude_cali_flag` is now `1 if flow_cali else 2`, and the five calibration/leveling fields are sent as JSON booleans for all models, matching BambuStudio's wire format exactly. The H-family integer-conversion branch (`is_h_family`) is removed. `use_ams` is unchanged. This affects only the outbound print command; the virtual-printer inbound coercion of slicer integer `0/1` flags (#1403) is a separate path and untouched. **Tests**: in `test_bambu_mqtt.py`, the two tests that asserted the integer format (`test_x2d_uses_integer_format_for_calibration_fields`, `test_h2s_keeps_integer_format_for_calibration_fields`) are corrected to assert booleans and renamed; all three model tests (X2D/H2S/P2S) now also assert `extrude_cali_flag` is `1` when flow cali is on and `2` when off. 381 mqtt + virtual-printer tests green; backend ruff clean. diff --git a/backend/app/services/stl_thumbnail.py b/backend/app/services/stl_thumbnail.py index 7d56e296b..d383f1aa0 100644 --- a/backend/app/services/stl_thumbnail.py +++ b/backend/app/services/stl_thumbnail.py @@ -142,5 +142,10 @@ def generate_stl_thumbnail( logger.warning("STL thumbnail generation unavailable (missing dependencies): %s", e) return None except Exception as e: - logger.warning("Failed to generate STL thumbnail for %s: %s", stl_path, e) + # Log the traceback, not just the message: a bare + # "unsupported operand type(s) for /: 'str' and 'str'" gives no clue + # which line failed, and the fault is data-/environment-specific + # enough that it can't be reproduced from a clean STL — the traceback + # in the next support bundle is what pinpoints it (#1480). + logger.warning("Failed to generate STL thumbnail for %s: %s", stl_path, e, exc_info=True) return None diff --git a/frontend/src/components/FileManagerModal.tsx b/frontend/src/components/FileManagerModal.tsx index 94d77a100..c33560a12 100644 --- a/frontend/src/components/FileManagerModal.tsx +++ b/frontend/src/components/FileManagerModal.tsx @@ -300,10 +300,15 @@ export function FileManagerModal({ printerId, printerName, onClose }: FileManage return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); + // No auto-poll: every refetch opens a fresh FTPS connection (TLS handshake + // and all) to the printer, and a 30s interval saturated fragile printer + // controllers like the P1S — MQTT, FTP and the camera all timed out + // together while this modal sat open (#1480). A printer's file list only + // changes on upload / delete (the mutations below invalidate the query) + // or when a print finishes; the manual Refresh button covers the rest. const { data, isLoading, refetch } = useQuery({ queryKey: ['printerFiles', printerId, currentPath], queryFn: () => api.getPrinterFiles(printerId, currentPath), - refetchInterval: 30000, }); const { data: storageData } = useQuery({ diff --git a/static/assets/index-B2ysIPjO.js b/static/assets/index-B7aP7po9.js similarity index 99% rename from static/assets/index-B2ysIPjO.js rename to static/assets/index-B7aP7po9.js index cb67f37f5..17c9a2988 100644 --- a/static/assets/index-B2ysIPjO.js +++ b/static/assets/index-B7aP7po9.js @@ -8437,7 +8437,7 @@ void main() { `};class zee extends Xh{constructor(e){super({type:"LineMaterial",uniforms:s4.clone(Go.line.uniforms),vertexShader:Go.line.vertexShader,fragmentShader:Go.line.fragmentShader,clipping:!0}),this.isLineMaterial=!0,this.setValues(e)}get color(){return this.uniforms.diffuse.value}set color(e){this.uniforms.diffuse.value=e}get worldUnits(){return"WORLD_UNITS"in this.defines}set worldUnits(e){e===!0?this.defines.WORLD_UNITS="":delete this.defines.WORLD_UNITS}get linewidth(){return this.uniforms.linewidth.value}set linewidth(e){this.uniforms.linewidth&&(this.uniforms.linewidth.value=e)}get dashed(){return"USE_DASH"in this.defines}set dashed(e){e===!0!==this.dashed&&(this.needsUpdate=!0),e===!0?this.defines.USE_DASH="":delete this.defines.USE_DASH}get dashScale(){return this.uniforms.dashScale.value}set dashScale(e){this.uniforms.dashScale.value=e}get dashSize(){return this.uniforms.dashSize.value}set dashSize(e){this.uniforms.dashSize.value=e}get dashOffset(){return this.uniforms.dashOffset.value}set dashOffset(e){this.uniforms.dashOffset.value=e}get gapSize(){return this.uniforms.gapSize.value}set gapSize(e){this.uniforms.gapSize.value=e}get opacity(){return this.uniforms.opacity.value}set opacity(e){this.uniforms&&(this.uniforms.opacity.value=e)}get resolution(){return this.uniforms.resolution.value}set resolution(e){this.uniforms.resolution.value.copy(e)}get alphaToCoverage(){return"USE_ALPHA_TO_COVERAGE"in this.defines}set alphaToCoverage(e){this.defines&&(e===!0!==this.alphaToCoverage&&(this.needsUpdate=!0),e===!0?(this.defines.USE_ALPHA_TO_COVERAGE="",this.extensions.derivatives=!0):(delete this.defines.USE_ALPHA_TO_COVERAGE,this.extensions.derivatives=!1))}}const jV=new yc,A1=new yt;class Uee extends KEe{constructor(){super(),this.isLineSegmentsGeometry=!0,this.type="LineSegmentsGeometry",this.setIndex([0,2,1,2,3,1,2,4,3,4,5,3,4,6,5,6,7,5]),this.setAttribute("position",new bi([-1,2,0,1,2,0,-1,1,0,1,1,0,-1,0,0,1,0,0,-1,-1,0,1,-1,0],3)),this.setAttribute("uv",new bi([-1,2,1,2,-1,1,1,1,-1,-1,1,-1,-1,-2,1,-2],2))}applyMatrix4(e){const a=this.attributes.instanceStart,r=this.attributes.instanceEnd;return a!==void 0&&(a.applyMatrix4(e),r.applyMatrix4(e),a.needsUpdate=!0),this.boundingBox!==null&&this.computeBoundingBox(),this.boundingSphere!==null&&this.computeBoundingSphere(),this}setPositions(e){let a;e instanceof Float32Array?a=e:Array.isArray(e)&&(a=new Float32Array(e));const r=new dR(a,6,1);return this.setAttribute("instanceStart",new wh(r,3,0)),this.setAttribute("instanceEnd",new wh(r,3,3)),this.computeBoundingBox(),this.computeBoundingSphere(),this}setColors(e){let a;e instanceof Float32Array?a=e:Array.isArray(e)&&(a=new Float32Array(e));const r=new dR(a,6,1);return this.setAttribute("instanceColorStart",new wh(r,3,0)),this.setAttribute("instanceColorEnd",new wh(r,3,3)),this}fromWireframeGeometry(e){return this.setPositions(e.attributes.position.array),this}fromEdgesGeometry(e){return this.setPositions(e.attributes.position.array),this}fromMesh(e){return this.fromWireframeGeometry(new BEe(e.geometry)),this}fromLineSegments(e){const a=e.geometry;return this.setPositions(a.attributes.position.array),this}computeBoundingBox(){this.boundingBox===null&&(this.boundingBox=new yc);const e=this.attributes.instanceStart,a=this.attributes.instanceEnd;e!==void 0&&a!==void 0&&(this.boundingBox.setFromBufferAttribute(e),jV.setFromBufferAttribute(a),this.boundingBox.union(jV))}computeBoundingSphere(){this.boundingSphere===null&&(this.boundingSphere=new iu),this.boundingBox===null&&this.computeBoundingBox();const e=this.attributes.instanceStart,a=this.attributes.instanceEnd;if(e!==void 0&&a!==void 0){const r=this.boundingSphere.center;this.boundingBox.getCenter(r);let i=0;for(let s=0,o=e.count;sP&&js.z>P)continue;if(As.z>P){const I=As.z-js.z,z=(As.z-P)/I;As.lerp(js,z)}else if(js.z>P){const I=js.z-As.z,z=(js.z-P)/I;js.lerp(As,z)}As.applyMatrix4(g),js.applyMatrix4(g),As.multiplyScalar(1/As.w),js.multiplyScalar(1/js.w),As.x*=v.x/2,As.y*=v.y/2,js.x*=v.x/2,js.y*=v.y/2,Ms.start.copy(As),Ms.start.z=0,Ms.end.copy(js),Ms.end.z=0;const F=Ms.closestPointToPointParameter(F2,!0);Ms.at(F,DV);const k=xee.lerp(As.z,js.z,F),E=k>=-1&&k<=1,U=F2.distanceTo(DV)<.5*sg;if(E&&U){Ms.start.fromBufferAttribute(S,T),Ms.end.fromBufferAttribute(_,T),Ms.start.applyMatrix4(b),Ms.end.applyMatrix4(b);const I=new yt,z=new yt;Fd.distanceSqToSegment(Ms.start,Ms.end,z,I),f.push({point:z,pointOnLine:I,distance:Fd.origin.distanceTo(z),object:m,face:null,faceIndex:T,uv:null,uv1:null})}}})(this,i,a)))}}class m2e extends $T{constructor(e,a,r,i,s=4473924,o=8947848){s=new La(s),o=new La(o);const l=Math.round(e/a);r=Math.round(r/i)*i/2;const c=[],d=[];let u=0;for(let p=-1*(e=l*a/2);p<=e;p+=a){c.push(p,0,-1*r,p,0,r);const f=p===0?s:o;f.toArray(d,u),u+=3,f.toArray(d,u),u+=3,f.toArray(d,u),u+=3,f.toArray(d,u),u+=3}for(let p=-1*r;p<=r;p+=i){c.push(-1*e,0,p,e,0,p);const f=p===0?s:o;f.toArray(d,u),u+=3,f.toArray(d,u),u+=3,f.toArray(d,u),u+=3,f.toArray(d,u),u+=3}const m=new co;m.setAttribute("position",new bi(c,3)),m.setAttribute("color",new bi(d,3)),super(m,new $S({vertexColors:!0,toneMapped:!1}))}}function p2e(t,e,a,r){const i=(function(o,l,c){o*=.5,l*=.5,c*=.5;const d=new co,u=[];return u.push(-o,-l,-c,-o,l,-c,-o,l,-c,o,l,-c,o,l,-c,o,-l,-c,o,-l,-c,-o,-l,-c,-o,-l,c,-o,l,c,-o,l,c,o,l,c,o,l,c,o,-l,c,o,-l,c,-o,-l,c,-o,-l,-c,-o,-l,c,-o,l,-c,-o,l,c,o,l,-c,o,l,c,o,-l,-c,o,-l,c),d.setAttribute("position",new bi(u,3)),d})(t,e,a),s=new $T(i,new qEe({color:new La(r),dashSize:3,gapSize:1}));return s.computeLineDistances(),s}var sw=function(){var t=0,e=document.createElement("div");function a(u){return e.appendChild(u.dom),u}function r(u){for(var m=0;m=s+1e3&&(l.update(1e3*o/(u-s),100),s=u,o=0,d)){var m=performance.memory;d.update(m.usedJSHeapSize/1048576,m.jsHeapSizeLimit/1048576)}return u},update:function(){i=this.end()},domElement:e,setMode:r}};sw.Panel=function(t,e,a){var r=1/0,i=0,s=Math.round,o=s(window.devicePixelRatio||1),l=80*o,c=48*o,d=3*o,u=2*o,m=3*o,p=15*o,f=74*o,g=30*o,v=document.createElement("canvas");v.width=l,v.height=c,v.style.cssText="width:80px;height:48px";var b=v.getContext("2d");return b.font="bold "+9*o+"px Helvetica,Arial,sans-serif",b.textBaseline="top",b.fillStyle=a,b.fillRect(0,0,l,c),b.fillStyle=e,b.fillText(t,d,u),b.fillRect(m,p,f,g),b.fillStyle=a,b.globalAlpha=.9,b.fillRect(m,p,f,g),{dom:v,update:function(x,S){r=Math.min(r,x),i=Math.max(i,x),b.fillStyle=a,b.globalAlpha=1,b.fillRect(0,0,l,p),b.fillStyle=e,b.fillText(s(x)+" "+t+" ("+s(r)+"-"+s(i)+")",d,u),b.drawImage(v,m+o,p,f-o,g,m,p,f-o,g),b.fillRect(m+f-o,p,o,g),b.fillStyle=a,b.globalAlpha=.9,b.fillRect(m+f-o,p,o,s((1-x/S)*g))}}};var h2e=sw;class LV{constructor(e,a){this.openFolders=[],this.watchedObject=e,this.options=a,this.gui=new NC,this.gui.title("Dev info"),this.setup()}setup(){this.loadOpenFolders(),this.options&&!this.options.renderer||this.setupRedererFolder(),this.options&&!this.options.camera||this.setupCameraFolder(),this.options&&!this.options.parser||this.setupParserFolder(),this.options&&!this.options.buildVolume||this.setupBuildVolumeFolder(),this.options&&!this.options.devHelpers||this.setupDevHelpers()}reset(){this.gui.destroy(),this.gui=new NC,this.gui.title("Dev info"),this.setup()}loadOpenFolders(){this.openFolders=JSON.parse(localStorage.getItem("dev-gui-open")||"{}").open||[]}saveOpenFolders(){this.openFolders=this.gui.foldersRecursive().filter((e=>!e._closed)).map((e=>e._title)),console.log(this.openFolders),localStorage.setItem("dev-gui-open",JSON.stringify({open:this.openFolders}))}setupRedererFolder(){const e=this.gui.addFolder("Render Info");this.openFolders.includes("Render Info")||e.close(),e.onOpenClose((()=>{this.saveOpenFolders()})),e.add(this.watchedObject.renderer.info.render,"triangles").listen(),e.add(this.watchedObject.renderer.info.render,"calls").listen(),e.add(this.watchedObject.renderer.info.render,"lines").listen(),e.add(this.watchedObject.renderer.info.render,"points").listen(),e.add(this.watchedObject.renderer.info.memory,"geometries").listen(),e.add(this.watchedObject.renderer.info.memory,"textures").listen(),e.add(this.watchedObject,"_lastRenderTime").listen()}setupCameraFolder(){const e=this.gui.addFolder("Camera");this.openFolders.includes("Camera")||e.close(),e.onOpenClose((()=>{this.saveOpenFolders()}));const a=e.addFolder("Camera position");a.add(this.watchedObject.camera.position,"x").listen(),a.add(this.watchedObject.camera.position,"y").listen(),a.add(this.watchedObject.camera.position,"z").listen();const r=e.addFolder("Camera rotation");r.add(this.watchedObject.camera.rotation,"x").listen(),r.add(this.watchedObject.camera.rotation,"y").listen(),r.add(this.watchedObject.camera.rotation,"z").listen()}setupParserFolder(){const e=this.gui.addFolder("Parser");this.openFolders.includes("Parser")||e.close(),e.onOpenClose((()=>{this.saveOpenFolders()})),e.add(this.watchedObject.parser,"curZ").listen(),e.add(this.watchedObject.parser,"maxZ").listen(),e.add(this.watchedObject.parser,"tolerance").listen(),e.add(this.watchedObject.parser.layers,"length").name("layers.count").listen(),e.add(this.watchedObject.parser.lines,"length").name("lines.count").listen()}setupBuildVolumeFolder(){const e=this.gui.addFolder("Build Volume");this.openFolders.includes("Build Volume")||e.close(),e.onOpenClose((()=>{this.saveOpenFolders()})),e.add(this.watchedObject.buildVolume,"x").min(0).max(600).listen().onChange((()=>{this.watchedObject.render()})),e.add(this.watchedObject.buildVolume,"y").min(0).max(600).listen().onChange((()=>{this.watchedObject.render()})),e.add(this.watchedObject.buildVolume,"z").min(0).max(600).listen().onChange((()=>{this.watchedObject.render()}))}setupDevHelpers(){const e=this.gui.addFolder("Dev Helpers");this.openFolders.includes("Dev Helpers")||e.close(),e.onOpenClose((()=>{this.saveOpenFolders()})),e.add(this.watchedObject,"_wireframe").listen().onChange((()=>{this.watchedObject.render()})),e.add(this.watchedObject,"render").listen(),e.add(this.watchedObject,"clear").listen()}}class f2e extends co{constructor(e=[new yt],a=.6,r=.2,i=8){super(),this.type="ExtrusionGeometry",this.parameters={points:e,lineWidth:a,lineHeight:r,radialSegments:i,closed:!1};const s=new yt,o=new yt,l=new Ga,c=[],d=[],u=[],m=[];function p(f){const[g,v,b]=(function(x){const S=e[x],_=new yt,C=new yt,P=new yt,T=new yt;_.copy(S).sub(e[x-1]||S).normalize().add((e[x+1]||S).clone().sub(S).normalize()).normalize();let A=Number.MAX_VALUE;const F=Math.abs(_.x),k=Math.abs(_.y),E=Math.abs(_.z);return F<=A&&(A=F,C.set(1,0,0)),k<=A&&(A=k,C.set(0,1,0)),E<=A&&C.set(0,0,1),T.crossVectors(_,C).normalize(),C.crossVectors(_,T),P.crossVectors(_,C),[S,C,P]})(f);for(let x=0;x<=i;x++){const S=x/i*Math.PI*2,_=Math.sin(S),C=-Math.cos(S);o.x=C*v.x+_*b.x,o.y=C*v.y+_*b.y,o.z=C*v.z+_*b.z,o.normalize(),d.push(o.x,o.y,o.z),s.x=g.x+a*o.x*.5,s.y=g.y+a*o.y*.5,s.z=g.z+r*o.z*.5,c.push(s.x,s.y,s.z)}}(function(){for(let f=0;fthis.animate())),this.controls.update(),this.renderer.render(this.scene,this.camera),(e=this.stats)===null||e===void 0||e.update()}processGCode(e){this.parser.parseGCode(e),this.render()}initScene(){for(;this.scene.children.length>0;)this.scene.remove(this.scene.children[0]);for(;this.disposables.length>0;){const e=this.disposables.pop();e&&e.dispose()}if(this.debug&&this.buildVolume){const e=new YEe(Math.max(this.buildVolume.x/2,this.buildVolume.y/2)+20);this.scene.add(e)}if(this.buildVolume&&this.drawBuildVolume(),this.renderTubes){console.warn("Volumetric rendering is experimental. It may not work as expected or change in the future.");const e=new WEe(13421772,.3*Math.PI),a=new GEe(16777215,Math.PI,void 0,.001);a.position.set(0,500,500),this.scene.add(e),this.scene.add(a)}}createGroup(e){const a=new U0;return a.name=e,a.quaternion.setFromEuler(new HS(-Math.PI/2,0,0)),this.buildVolume?a.position.set(-this.buildVolume.x/2,0,this.buildVolume.y/2):a.position.set(-100,0,100),a}render(){const e=performance.now();this.group=this.createGroup("allLayers"),this.state=ow.initial,this.initScene();for(let a=0;a0?e:1)}))}renderFrameLoop(e){return new Promise((a=>{const r=()=>{this.renderLayerIndex>this.layers.length-1?a():(this.renderFrame(e),requestAnimationFrame(r))};r()}))}renderFrame(e){this.group=this.createGroup("layer"+this.renderLayerIndex);for(let a=0;athis.maxLayerIndex)return;const u=this.layers[e],m={extrusion:[],travel:[],z:this.state.z,height:u.height};for(const p of u.commands)if(p.gcode!="g20"){if(p.gcode.startsWith("t")){this.doRenderExtrusion(m,e),m.extrusion=[];const f=p;this.state.t=f.toolIndex}else if(["g0","g00","g1","g01","g2","g02","g3","g03"].indexOf(p.gcode)>-1){const f=p,g={x:(a=f.params.x)!==null&&a!==void 0?a:this.state.x,y:(r=f.params.y)!==null&&r!==void 0?r:this.state.y,z:(i=f.params.z)!==null&&i!==void 0?i:this.state.z,r:(s=f.params.r)!==null&&s!==void 0?s:this.state.r,e:(o=f.params.e)!==null&&o!==void 0?o:this.state.e,i:(l=f.params.i)!==null&&l!==void 0?l:this.state.i,j:(c=f.params.j)!==null&&c!==void 0?c:this.state.j,t:this.state.t};if(e>=this.minLayerIndex){const v=((d=f.params.e)!==null&&d!==void 0?d:0)>0||this.nonTravelmoves.indexOf(p.gcode)>-1;(g.x!=this.state.x||g.y!=this.state.y||g.z!=this.state.z)&&(v&&this.renderExtrusion||!v&&this.renderTravel)&&(p.gcode=="g2"||p.gcode=="g3"||p.gcode=="g02"||p.gcode=="g03"?this.addArcSegment(m,this.state,g,v,p.gcode=="g2"||p.gcode=="g02"):this.addLineSegment(m,this.state,g,v))}this.state.x=g.x,this.state.y=g.y,this.state.z=g.z,this.beyondFirstMove||(this.beyondFirstMove=!0)}}else this.setInches();this.doRenderExtrusion(m,e)}doRenderExtrusion(e,a){var r,i;if(this.renderExtrusion){let s=this.currentToolColor;if(!this.singleLayerMode&&!this.renderTubes&&!this.disableGradient){const o=.1+.7*a/this.layers.length;s.getHSL(R2),s=new La().setHSL(R2.h,R2.s,o)}if(a==this.layers.length-1){const o=(r=this._topLayerColor)!==null&&r!==void 0?r:s,l=(i=this._lastSegmentColor)!==null&&i!==void 0?i:o,c=e.extrusion.splice(-3),d=e.extrusion.splice(-3);this.renderTubes?(this.addTubeLine(e.extrusion,o.getHex(),e.height),this.addTubeLine([...d,...c],l.getHex(),e.height)):(this.addLine(e.extrusion,o.getHex()),this.addLine([...d,...c],l.getHex()))}else this.renderTubes?this.addTubeLine(e.extrusion,s.getHex(),e.height):this.addLine(e.extrusion,s.getHex())}this.renderTravel&&this.addLine(e.travel,this._travelColor.getHex())}setInches(){this.beyondFirstMove?console.warn("Switching units after movement is already made is discouraged and is not supported."):this.inches=!0}drawBuildVolume(){if(!this.buildVolume)return;this.scene.add(new m2e(this.buildVolume.x,10,this.buildVolume.y,10));const e=p2e(this.buildVolume.x,this.buildVolume.z,this.buildVolume.y,8947848);e.position.setY(this.buildVolume.z/2),this.scene.add(e)}clear(){this.resetState(),this.parser=new CC(this.minLayerThreshold)}resetState(){var e;this.startLayer=1,this.endLayer=1/0,this.singleLayerMode=!1,this.beyondFirstMove=!1,this.state=ow.initial,(e=this.devGui)===null||e===void 0||e.reset(),this._geometries={}}resize(){const[e,a]=[this.canvas.offsetWidth,this.canvas.offsetHeight];this.camera.aspect=e/a,this.camera.updateProjectionMatrix(),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(e,a,!1)}addLineSegment(e,a,r,i){(i?e.extrusion:e.travel).push(a.x,a.y,a.z,r.x,r.y,r.z)}addArcSegment(e,a,r,i,s){const o=i?e.extrusion:e.travel,l=a.x,c=a.y,d=a.z,u=r.x,m=r.y,p=r.z;let f=r.r,g=r.i,v=r.j;if(f){const Z=u-l,ae=m-c,Q=Math.sqrt(Math.pow(Z/2,2)+Math.pow(ae/2,2));f=Math.max(f,Q);const oe=Math.pow(Z,2)+Math.pow(ae,2),ue=Math.pow(f,2)-oe/4;let pe=Math.sqrt(ue/oe);(s&&f<0||!s&&f>0)&&(pe=-pe),g=Z/2+ae*pe,v=ae/2-Z*pe}const b=l==u&&c==m,x=l+g,S=c+v,_=Math.sqrt(g*g+v*v),C=Math.atan2(-v,-g),P=Math.atan2(m-S,u-x);let T;b?T=2*Math.PI:(T=s?C-P:P-C,T<0&&(T+=2*Math.PI));let A=_*T/1.8;this.inches&&(A*=25),A<1&&(A=1);let F=T/A;F*=s?-1:1;const k=[];k.push({x:l,y:c,z:d});const E=(d-p)/A;let U=l,I=c,z=d,L=C;for(let Z=0;Z0)return void this.addThickLine(e,a);const i=new co;i.setAttribute("position",new bi(e,3)),this.disposables.push(i);const s=new $S({color:a});this.disposables.push(s);const o=new $T(i,s);(r=this.group)===null||r===void 0||r.add(o)}addTubeLine(e,a,r=.2){let i=[];const s=[];for(let o=0;o{var l;const c=new f2e(o,this.extrusionWidth,this.lineHeight||r,4);(l=this._geometries)[a]||(l[a]=[]),this._geometries[a].push(c)}))}addThickLine(e,a){var r;if(!e.length||!this.lineWidth)return;const i=new Uee;this.disposables.push(i);const s=new zee({color:a,linewidth:this.lineWidth/(1e3*window.devicePixelRatio)});this.disposables.push(s),i.setPositions(e);const o=new u2e(i,s);(r=this.group)===null||r===void 0||r.add(o)}dispose(){this.disposables.forEach((e=>e.dispose())),this.disposables=[],this.controls.dispose(),this.renderer.dispose(),this.cancelAnimation()}cancelAnimation(){this.animationFrameId!==void 0&&cancelAnimationFrame(this.animationFrameId),this.animationFrameId=void 0}_enableDropHandler(){console.warn("Drag and drop is deprecated as a library feature. See the demo how to implement your own."),this.canvas.addEventListener("dragover",(e=>{e.stopPropagation(),e.preventDefault(),e.dataTransfer&&(e.dataTransfer.dropEffect="copy"),this.canvas.classList.add("dragging")})),this.canvas.addEventListener("dragleave",(e=>{e.stopPropagation(),e.preventDefault(),this.canvas.classList.remove("dragging")})),this.canvas.addEventListener("drop",(e=>E2(this,void 0,void 0,(function*(){var a,r;e.stopPropagation(),e.preventDefault(),this.canvas.classList.remove("dragging");const i=((r=(a=e.dataTransfer)===null||a===void 0?void 0:a.files)!==null&&r!==void 0?r:[])[0];this.clear(),yield this._readFromStream(i.stream()),this.render()}))))}batchGeometries(){if(this._geometries)for(const e in this._geometries){const a=this.createBatchMesh(parseInt(e));for(;this._geometries[e].length>0;){const r=this._geometries[e].pop();a.addGeometry(r)}}}createBatchMesh(e){var a;const r=this._geometries[e],i=new HEe({color:e,wireframe:this._wireframe});this.disposables.push(i);const s=r.reduce(((l,c)=>3*c.attributes.position.count+l),0),o=new zEe(r.length,s,void 0,i);return this.disposables.push(o),(a=this.group)===null||a===void 0||a.add(o),o}_readFromStream(e){var a,r;return E2(this,void 0,void 0,(function*(){const i=e.getReader();let s,o="",l=0;do{console.debug("reading from stream"),s=yield i.read(),l+=(r=(a=s.value)===null||a===void 0?void 0:a.length)!==null&&r!==void 0?r:0;const d=(c=s.value,new TextDecoder("utf-8").decode(c)),u=d.lastIndexOf(` `),m=d.slice(0,u);this.parser.parseGCode(o+m),o=d.slice(u)}while(!s.done);var c;console.debug("read from stream",l)}))}initGui(){typeof this.devMode=="boolean"&&this.devMode===!0?this.devGui=new LV(this):typeof this.devMode=="object"&&(this.devGui=new LV(this,this.devMode))}initStats(){var e;this.stats&&(typeof this.devMode=="object"&&(this.statsContainer=this.devMode.statsContainer),((e=this.statsContainer)!==null&&e!==void 0?e:document.body).appendChild(this.stats.dom),this.stats.dom.classList.add("stats"),this.initGui())}}cy.defaultExtrusionColor=new La("hotpink");function Bee({gcodeUrl:t,buildVolume:e={x:256,y:256,z:256},filamentColors:a,className:r=""}){const i=y.useRef(null),s=y.useRef(null),o=y.useRef(null),l=y.useRef(!1),[c,d]=y.useState(!0),[u,m]=y.useState(null),[p,f]=y.useState(!1),[g,v]=y.useState(0),[b,x]=y.useState(0),S=y.useMemo(()=>JSON.stringify(a),[a]);y.useEffect(()=>{if(!i.current||l.current)return;l.current=!0;const P=i.current,T=P.parentElement?.getBoundingClientRect();T&&(P.width=T.width,P.height=T.height);const A=a&&a.length>1,F=a?.[0]||"#00ae42",k=new cy({canvas:P,buildVolume:e,backgroundColor:1710618,extrusionColor:A?a:F,disableGradient:!0,lineHeight:.2,lineWidth:2,renderTravel:!1,renderExtrusion:!0});s.current=k;const E={},U=Zm();U&&(E.Authorization=`Bearer ${U}`),fetch(t,{headers:E}).then(async z=>{if(!z.ok)throw z.status===404&&(await z.json().catch(()=>({}))).detail?.includes("sliced")?(f(!0),new Error("not_sliced")):new Error("Failed to load G-code");return z.text()}).then(z=>{const L=new Set,Z=/^(\s*)T(\d+)(\s*;.*)?$/gim;let ae;for(;(ae=Z.exec(z))!==null;){const le=parseInt(ae[2],10);le<=15&&L.add(le)}const Q=new Map,oe=Array.from(L).sort((le,me)=>le-me);oe.forEach((le,me)=>{Q.set(le,me%8)});const ue=[];oe.forEach((le,me)=>{const de=a?.[le]||"#00ae42";ue[me%8]=de});const pe=z.split(` `).map(le=>{const me=le.match(/^(\s*)T(\d+)(\s*;.*)?$/i);if(me){const de=parseInt(me[2],10);if(de>15)return`; FILTERED: ${le.trim()}`;const we=Q.get(de)??0;return`${me[1]}T${we}${me[3]||""}`}return le}).join(` -`);ue.length>0&&(k.extrusionColor=ue),k.processGCode(pe);const X=k.layers?.length||0;x(X),v(X),k.render(),d(!1)}).catch(z=>{z.message!=="not_sliced"&&m(z.message),d(!1)});const I=()=>{if(P.parentElement&&s.current){const z=P.parentElement.getBoundingClientRect();P.width=z.width,P.height=z.height,s.current.resize()}};return window.addEventListener("resize",I),()=>{window.removeEventListener("resize",I),o.current&&cancelAnimationFrame(o.current),s.current&&(s.current.dispose(),s.current=null),l.current=!1}},[t,S]);const _=y.useCallback(P=>{if(!s.current)return;const T=Math.max(1,Math.min(P,b));v(T),o.current&&cancelAnimationFrame(o.current),o.current=requestAnimationFrame(()=>{s.current&&(s.current.endLayer=T,s.current.render())})},[b]),C=P=>{_(parseInt(P.target.value,10))};return n.jsxs("div",{className:`relative flex flex-col h-full ${r}`,children:[n.jsxs("div",{className:"flex-1 relative bg-bambu-dark rounded-lg overflow-hidden",children:[n.jsx("canvas",{ref:i,className:"w-full h-full"}),c&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsxs("div",{className:"text-center",children:[n.jsx(pt,{className:"w-8 h-8 animate-spin text-bambu-green mx-auto mb-2"}),n.jsx("p",{className:"text-bambu-gray text-sm",children:"Loading G-code..."})]})}),p&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsxs("div",{className:"text-center max-w-sm px-4",children:[n.jsx(Nbe,{className:"w-12 h-12 text-bambu-gray mx-auto mb-3"}),n.jsx("p",{className:"text-white font-medium mb-2",children:"G-code not available"}),n.jsx("p",{className:"text-bambu-gray text-sm",children:"This file hasn't been sliced yet. G-code preview is only available after slicing in Bambu Studio or Orca Slicer."})]})}),u&&!p&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsx("div",{className:"text-center text-red-400",children:n.jsx("p",{className:"text-sm",children:u})})})]}),!c&&!u&&!p&&b>0&&n.jsx("div",{className:"mt-4 px-2",children:n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx(cr,{className:"w-4 h-4 text-bambu-gray flex-shrink-0"}),n.jsx("button",{onClick:()=>_(g-1),disabled:g<=1,className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-30 disabled:cursor-not-allowed",children:n.jsx(tl,{className:"w-4 h-4"})}),n.jsx("input",{type:"range",min:1,max:b,value:g,onChange:C,className:"flex-1 h-2 bg-bambu-dark-tertiary rounded-lg appearance-none cursor-pointer accent-bambu-green"}),n.jsx("button",{onClick:()=>_(g+1),disabled:g>=b,className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-30 disabled:cursor-not-allowed",children:n.jsx(qr,{className:"w-4 h-4"})}),n.jsxs("span",{className:"text-sm text-bambu-gray min-w-[80px] text-right",children:[g," / ",b]})]})})]})}function g2e({printerId:t,filePath:e,filename:a,onClose:r}){const[i,s]=y.useState(null),[o,l]=y.useState([]),[c,d]=y.useState(!1),[u,m]=y.useState(null),p=a.toLowerCase().split(".").pop()||"",f=p==="3mf"||p==="stl",g=p==="gcode"||p==="3mf";y.useEffect(()=>{s(f?"3d":g?"gcode":null)},[f,g]),y.useEffect(()=>{l([]),m(null),f&&(d(!0),re.getPrinterFilePlates(t,e).then(x=>l(x.plates||[])).catch(()=>l([])).finally(()=>d(!1)))},[e,f,t]);const v=o.length>1,b=u==null?null:o.find(x=>x.index===u)??null;return n.jsx("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-6",onClick:r,children:n.jsxs("div",{className:"bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary w-full max-w-4xl h-[80vh] flex flex-col",onClick:x=>x.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between px-6 py-4 border-b border-bambu-dark-tertiary",children:[n.jsx("h2",{className:"text-lg font-semibold text-white truncate flex-1 mr-4",children:a}),n.jsx(je,{variant:"ghost",size:"sm",onClick:r,children:n.jsx($t,{className:"w-5 h-5"})})]}),n.jsxs("div",{className:"flex border-b border-bambu-dark-tertiary",children:[n.jsxs("button",{onClick:()=>f&&s("3d"),disabled:!f,className:`flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors ${i==="3d"?"text-bambu-green border-b-2 border-bambu-green":f?"text-bambu-gray hover:text-white":"text-bambu-gray/30 cursor-not-allowed"}`,children:[n.jsx(ji,{className:"w-4 h-4"}),"3D Model",!f&&n.jsx("span",{className:"text-xs",children:"(not available)"})]}),n.jsxs("button",{onClick:()=>g&&s("gcode"),disabled:!g,className:`flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors ${i==="gcode"?"text-bambu-green border-b-2 border-bambu-green":g?"text-bambu-gray hover:text-white":"text-bambu-gray/30 cursor-not-allowed"}`,children:[n.jsx(Bs,{className:"w-4 h-4"}),"G-code Preview",!g&&n.jsx("span",{className:"text-xs",children:"(not sliced)"})]})]}),n.jsx("div",{className:"flex-1 overflow-hidden p-4",children:i==="3d"&&f?n.jsxs("div",{className:"w-full h-full flex flex-col gap-3",children:[v&&n.jsxs("div",{className:"rounded-lg border border-bambu-dark-tertiary bg-bambu-dark p-3",children:[n.jsxs("div",{className:"flex items-center gap-2 text-sm text-bambu-gray mb-2",children:[n.jsx(ji,{className:"w-4 h-4"}),"Plates",c&&n.jsx(pt,{className:"w-3 h-3 animate-spin"})]}),n.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2",children:[n.jsxs("button",{type:"button",onClick:()=>m(null),className:`flex items-center gap-2 rounded-lg border p-2 text-left transition-colors ${u==null?"border-bambu-green bg-bambu-green/10":"border-bambu-dark-tertiary bg-bambu-dark-secondary hover:border-bambu-gray"}`,children:[n.jsx("div",{className:"w-10 h-10 rounded bg-bambu-dark-tertiary flex items-center justify-center",children:n.jsx(ji,{className:"w-5 h-5 text-bambu-gray"})}),n.jsxs("div",{className:"min-w-0 flex-1",children:[n.jsx("p",{className:"text-sm text-white font-medium truncate",children:"All Plates"}),n.jsxs("p",{className:"text-xs text-bambu-gray truncate",children:[o.length," plate",o.length!==1?"s":""]})]}),u==null&&n.jsx(ws,{className:"w-4 h-4 text-bambu-green flex-shrink-0"})]}),o.map(x=>n.jsxs("button",{type:"button",onClick:()=>m(x.index),className:`flex items-center gap-2 rounded-lg border p-2 text-left transition-colors ${u===x.index?"border-bambu-green bg-bambu-green/10":"border-bambu-dark-tertiary bg-bambu-dark-secondary hover:border-bambu-gray"}`,children:[x.has_thumbnail?n.jsx("img",{src:re.getPrinterFilePlateThumbnail(t,x.index,e),alt:`Plate ${x.index}`,className:"w-10 h-10 rounded object-cover bg-bambu-dark-tertiary"}):n.jsx("div",{className:"w-10 h-10 rounded bg-bambu-dark-tertiary flex items-center justify-center",children:n.jsx(ji,{className:"w-5 h-5 text-bambu-gray"})}),n.jsxs("div",{className:"min-w-0 flex-1",children:[n.jsx("p",{className:"text-sm text-white font-medium truncate",children:x.name||`Plate ${x.index}`}),n.jsx("p",{className:"text-xs text-bambu-gray truncate",children:x.objects.length>0?x.objects.slice(0,2).join(", ")+(x.objects.length>2?"…":""):`${x.filaments.length} filament${x.filaments.length!==1?"s":""}`})]}),u===x.index&&n.jsx(ws,{className:"w-4 h-4 text-bambu-green flex-shrink-0"})]},x.index))]}),b&&n.jsxs("div",{className:"mt-3 text-xs text-bambu-gray flex flex-wrap gap-x-4 gap-y-1",children:[n.jsxs("span",{children:["Plate ",b.index]}),b.print_time_seconds!=null&&n.jsxs("span",{children:["ETA ",Math.round(b.print_time_seconds/60)," min"]}),b.filament_used_grams!=null&&n.jsxs("span",{children:[b.filament_used_grams.toFixed(1)," g"]}),b.filaments.length>0&&n.jsxs("span",{children:[b.filaments.length," filament",b.filaments.length!==1?"s":""]})]})]}),n.jsx("div",{className:"flex-1",children:n.jsx(iee,{url:re.getPrinterFileDownloadUrl(t,e),fileType:p,selectedPlateId:u,className:"w-full h-full"})})]}):i==="gcode"&&g?n.jsx(Bee,{gcodeUrl:re.getPrinterFileGcodeUrl(t,e),className:"w-full h-full"}):n.jsx("div",{className:"w-full h-full flex items-center justify-center text-bambu-gray",children:"No preview available for this file"})})]})})}function RV(t){if(t===0)return"0 GB";const e=t/(1024*1024*1024);return e>=1?`${e.toFixed(1)} GB`:`${(t/(1024*1024)).toFixed(0)} MB`}function b2e(t,e){if(e)return VZ;switch(t.toLowerCase().split(".").pop()||""){case"3mf":return Th;case"gcode":return Bs;case"mp4":case"avi":return Ah;case"png":case"jpg":case"jpeg":return cu;default:return jT}}const x2e=[{value:"name-asc",label:"Name (A-Z)"},{value:"name-desc",label:"Name (Z-A)"},{value:"size-asc",label:"Size (smallest)"},{value:"size-desc",label:"Size (largest)"},{value:"date-asc",label:"Date (oldest)"},{value:"date-desc",label:"Date (newest)"}];function y2e({printerId:t,printerName:e,onClose:a}){const{t:r}=St(),{showToast:i}=ra(),s=ta(),[o,l]=y.useState("/"),[c,d]=y.useState(new Set),[u,m]=y.useState(""),[p,f]=y.useState([]),[g,v]=y.useState("name-asc"),[b,x]=y.useState(null),[S,_]=y.useState(null);y.useEffect(()=>{const Q=oe=>{oe.key==="Escape"&&a()};return window.addEventListener("keydown",Q),()=>window.removeEventListener("keydown",Q)},[a]);const{data:C,isLoading:P,refetch:T}=Ye({queryKey:["printerFiles",t,o],queryFn:()=>re.getPrinterFiles(t,o),refetchInterval:3e4}),{data:A}=Ye({queryKey:["printerStorage",t],queryFn:()=>re.getPrinterStorage(t),staleTime:3e4}),F=lt({mutationFn:async Q=>{for(const oe of Q)await re.deletePrinterFile(t,oe)},onSuccess:()=>{i(r("printerFiles.toast.filesDeleted",{count:p.length})),s.invalidateQueries({queryKey:["printerFiles",t]}),d(new Set),f([])},onError:Q=>{i(r("printerFiles.toast.deleteFailed",{error:Q.message}),"error")}}),k=Q=>{l(Q),d(new Set)},E=()=>{if(o==="/")return;const Q=o.split("/").filter(Boolean);Q.pop(),l(Q.length?"/"+Q.join("/"):"/"),d(new Set)},U=(Q,oe)=>{oe.stopPropagation(),d(ue=>{const pe=new Set(ue);return pe.has(Q)?pe.delete(Q):pe.add(Q),pe})},I=()=>{if(!C?.files)return;const Q=C.files.filter(oe=>!oe.is_directory&&(!u||oe.name.toLowerCase().includes(u.toLowerCase()))).map(oe=>oe.path);d(new Set(Q))},z=()=>{d(new Set)},L=async()=>{if(c.size===0)return;const Q=Array.from(c);if(Q.length===1){re.downloadPrinterFile(t,Q[0]).catch(oe=>{console.error("Printer file download failed:",oe)}),d(new Set);return}x({current:0,total:Q.length});try{const oe=await re.downloadPrinterFilesAsZip(t,Q),ue=URL.createObjectURL(oe),pe=document.createElement("a");pe.href=ue,pe.download=`${e.replace(/[^a-zA-Z0-9]/g,"_")}-files.zip`,document.body.appendChild(pe),pe.click(),document.body.removeChild(pe),URL.revokeObjectURL(ue),i(`Downloaded ${Q.length} files as ZIP`),d(new Set)}catch(oe){i(`Download failed: ${oe instanceof Error?oe.message:"Unknown error"}`,"error")}finally{x(null)}},Z=()=>{c.size!==0&&f(Array.from(c))},ae=[{path:"/",label:"Root"},{path:"/cache",label:"Cache"},{path:"/model",label:"Models"},{path:"/timelapse",label:"Timelapse"}];return n.jsxs("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4",onClick:a,children:[n.jsxs("div",{className:"w-full max-w-3xl max-h-[85vh] flex flex-col bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary overflow-hidden",onClick:Q=>Q.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between p-4 border-b border-bambu-dark-tertiary flex-shrink-0",children:[n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx(cb,{className:"w-5 h-5 text-bambu-green"}),n.jsxs("div",{children:[n.jsx("h2",{className:"text-lg font-semibold text-white",children:r("printerFiles.title")}),n.jsx("p",{className:"text-sm text-bambu-gray",children:e})]})]}),n.jsxs("div",{className:"flex items-center gap-4",children:[A&&(A.used_bytes!=null||A.free_bytes!=null)&&n.jsxs("div",{className:"text-sm text-bambu-gray flex items-center gap-2",children:[A.used_bytes!=null&&n.jsxs("span",{children:[r("printerFiles.storageUsed")," ",RV(A.used_bytes)]}),A.used_bytes!=null&&A.free_bytes!=null&&n.jsx("span",{className:"text-bambu-dark-tertiary",children:"|"}),A.free_bytes!=null&&n.jsxs("span",{children:[r("printerFiles.storageFree")," ",RV(A.free_bytes)]})]}),n.jsx("button",{onClick:a,className:"text-bambu-gray hover:text-white transition-colors",title:"Close file manager","aria-label":"Close file manager",children:n.jsx($t,{className:"w-5 h-5"})})]})]}),n.jsxs("div",{className:"flex items-center gap-2 p-3 border-b border-bambu-dark-tertiary bg-bambu-dark/50 flex-shrink-0",children:[ae.map(Q=>n.jsx("button",{onClick:()=>{k(Q.path),m("")},className:`px-3 py-1 text-sm rounded-full transition-colors ${o===Q.path?"bg-bambu-green text-white":"bg-bambu-dark-tertiary text-bambu-gray hover:text-white"}`,children:Q.label},Q.path)),n.jsx("div",{className:"flex-1"}),n.jsxs("div",{className:"relative",children:[n.jsx(gn,{className:"absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray"}),n.jsx("input",{type:"text",placeholder:r("printerFiles.filterPlaceholder"),value:u,onChange:Q=>m(Q.target.value),className:"w-40 pl-8 pr-3 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:border-bambu-green focus:outline-none"})]}),n.jsxs("div",{className:"relative flex items-center gap-1",children:[n.jsx(TT,{className:"w-4 h-4 text-bambu-gray"}),n.jsx("select",{value:g,onChange:Q=>v(Q.target.value),className:"appearance-none bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm py-1.5 pl-2 pr-6 focus:border-bambu-green focus:outline-none cursor-pointer",title:"Sort files","aria-label":"Sort files",children:x2e.map(Q=>n.jsx("option",{value:Q.value,children:Q.label},Q.value))})]}),n.jsx(je,{variant:"secondary",size:"sm",onClick:()=>T(),disabled:P,children:n.jsx(en,{className:`w-4 h-4 ${P?"animate-spin":""}`})})]}),n.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 bg-bambu-dark text-sm flex-shrink-0",children:[n.jsx("button",{onClick:E,disabled:o==="/",className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-50 disabled:cursor-not-allowed",title:"Go to parent folder","aria-label":"Go to parent folder",children:n.jsx(tl,{className:"w-4 h-4"})}),n.jsx("span",{className:"text-bambu-gray font-mono",children:o})]}),n.jsx("div",{className:"flex-1 overflow-y-auto p-2 min-h-0",children:P?n.jsx("div",{className:"flex items-center justify-center py-12",children:n.jsx(pt,{className:"w-8 h-8 text-bambu-green animate-spin"})}):C?.files?.length?n.jsx("div",{className:"space-y-1",children:[...C.files].filter(Q=>!u||Q.name.toLowerCase().includes(u.toLowerCase())).sort((Q,oe)=>{if(Q.is_directory&&!oe.is_directory)return-1;if(!Q.is_directory&&oe.is_directory)return 1;switch(g){case"name-asc":return Q.name.localeCompare(oe.name);case"name-desc":return oe.name.localeCompare(Q.name);case"size-asc":return Q.size-oe.size;case"size-desc":return oe.size-Q.size;case"date-asc":{const ue=Q.mtime?Qa(Q.mtime)?.getTime()??0:0,pe=oe.mtime?Qa(oe.mtime)?.getTime()??0:0;return ue-pe}case"date-desc":{const ue=Q.mtime?Qa(Q.mtime)?.getTime()??0:0;return(oe.mtime?Qa(oe.mtime)?.getTime()??0:0)-ue}default:return Q.name.localeCompare(oe.name)}}).map(Q=>{const oe=b2e(Q.name,Q.is_directory),ue=c.has(Q.path);return n.jsxs("div",{className:`flex items-center gap-3 p-2 rounded-lg cursor-pointer transition-colors ${ue?"bg-bambu-green/20 border border-bambu-green/50":"hover:bg-bambu-dark-tertiary"}`,onClick:()=>{Q.is_directory&&k(Q.path)},children:[Q.is_directory?null:n.jsx("button",{onClick:pe=>U(Q.path,pe),className:"flex-shrink-0 text-bambu-gray hover:text-white",children:ue?n.jsx(ws,{className:"w-5 h-5 text-bambu-green"}):n.jsx(oo,{className:"w-5 h-5"})}),n.jsx(oe,{className:`w-5 h-5 flex-shrink-0 ${Q.is_directory?"text-bambu-green":"text-bambu-gray"}`}),n.jsx("span",{className:"flex-1 text-white truncate",children:Q.name}),!Q.is_directory&&n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx("span",{className:"text-sm text-bambu-gray",children:ps(Q.size)}),(Q.name.toLowerCase().endsWith(".3mf")||Q.name.toLowerCase().endsWith(".gcode")||Q.name.toLowerCase().endsWith(".stl"))&&n.jsx("button",{onClick:pe=>{pe.stopPropagation(),_({path:Q.path,name:Q.name})},className:"p-1 rounded hover:bg-bambu-dark text-bambu-gray hover:text-bambu-green",title:"3D View",children:n.jsx(ji,{className:"w-4 h-4"})})]}),Q.is_directory&&n.jsx(tl,{className:"w-4 h-4 text-bambu-gray rotate-180"})]},Q.path)})}):n.jsx("div",{className:"text-center py-12 text-bambu-gray",children:"No files in this directory"})}),n.jsxs("div",{className:"flex items-center justify-between p-4 border-t border-bambu-dark-tertiary bg-bambu-dark/50 flex-shrink-0",children:[n.jsxs("div",{className:"flex items-center gap-4",children:[n.jsx("div",{className:"text-sm text-bambu-gray",children:c.size>0?`${c.size} selected`:u?`${C?.files?.filter(Q=>Q.name.toLowerCase().includes(u.toLowerCase())).length||0} of ${C?.files?.length||0} items`:`${C?.files?.length||0} items`}),C?.files?.some(Q=>!Q.is_directory)&&n.jsx("div",{className:"flex items-center gap-2",children:c.size>0?n.jsxs("button",{onClick:z,className:"flex items-center gap-1 text-xs text-bambu-gray hover:text-white transition-colors",children:[n.jsx(eve,{className:"w-4 h-4"}),"Deselect All"]}):n.jsxs("button",{onClick:I,className:"flex items-center gap-1 text-xs text-bambu-gray hover:text-white transition-colors",children:[n.jsx(ws,{className:"w-4 h-4"}),"Select All"]})})]}),n.jsxs("div",{className:"flex gap-2",children:[n.jsx(je,{variant:"secondary",disabled:c.size===0||b!==null,onClick:L,children:b?n.jsxs(n.Fragment,{children:[n.jsx(pt,{className:"w-4 h-4 animate-spin"}),b.current,"/",b.total]}):n.jsxs(n.Fragment,{children:[n.jsx($n,{className:"w-4 h-4"}),"Download",c.size>1?` (${c.size})`:""]})}),n.jsxs(je,{variant:"secondary",disabled:c.size===0||F.isPending,onClick:Z,className:"text-red-400 hover:text-red-300",children:[F.isPending?n.jsx(pt,{className:"w-4 h-4 animate-spin"}):n.jsx(ea,{className:"w-4 h-4"}),r("printerFiles.deleteButton"),c.size>1?` (${c.size})`:""]})]})]})]}),p.length>0&&n.jsx(va,{title:p.length>1?r("printerFiles.deleteFiles",{count:p.length}):r("fileManager.deleteFile"),message:p.length>1?r("printerFiles.deleteFilesConfirm",{count:p.length}):r("printerFiles.deleteFileConfirm",{name:p[0].split("/").pop()}),confirmText:r("common.delete"),variant:"danger",onConfirm:()=>{F.mutate(p)},onCancel:()=>f([])}),S&&n.jsx(g2e,{printerId:t,filePath:S.path,filename:S.name,onClose:()=>_(null)})]})}function m4({on:t,className:e="w-5 h-5"}){const a=t?"#facc15":"none",r=t?"#78350f":"currentColor",i=t?1:0;return n.jsxs("svg",{viewBox:"0 0 32 32",fill:"none",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:e,children:[n.jsxs("g",{stroke:r,opacity:i,children:[n.jsx("line",{x1:"16",y1:"2",x2:"16",y2:"6"}),n.jsx("line",{x1:"6.1",y1:"6.1",x2:"8.9",y2:"8.9"}),n.jsx("line",{x1:"25.9",y1:"6.1",x2:"23.1",y2:"8.9"}),n.jsx("line",{x1:"2",y1:"16",x2:"6",y2:"16"}),n.jsx("line",{x1:"30",y1:"16",x2:"26",y2:"16"})]}),n.jsx("path",{d:"M12 24v-2.3c0-.9-.4-1.7-1-2.3C9.2 17.6 8 15.4 8 13c0-4.4 3.6-8 8-8s8 3.6 8 8c0 2.4-1.2 4.6-3 6.4-.6.6-1 1.4-1 2.3V24",fill:a,stroke:r}),n.jsx("path",{d:"M12 24h8",stroke:r}),n.jsx("path",{d:"M12 27h8",stroke:r}),n.jsx("path",{d:"M13 30h6",stroke:r})]})}const VT=({className:t})=>n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:t,children:[n.jsx("rect",{x:"2",y:"15",width:"5",height:"5",rx:"0.5"}),n.jsx("rect",{x:"9.5",y:"15",width:"5",height:"5",rx:"0.5",fill:"currentColor",opacity:"0.3"}),n.jsx("rect",{x:"17",y:"15",width:"5",height:"5",rx:"0.5"}),n.jsx("path",{d:"M4 12 C4 6, 14 6, 14 12"}),n.jsx("polyline",{points:"12,10 14,12 12,14"})]});function p4({printerId:t,isOpen:e,onClose:a}){const{t:r}=St(),{showToast:i}=ra(),{hasPermission:s}=un(),[o,l]=y.useState(null),[c,d]=y.useState(!1),{data:u}=Ye({queryKey:["printerStatus",t],queryFn:()=>re.getPrinterStatus(t),refetchInterval:3e4,enabled:e}),{data:m,refetch:p}=Ye({queryKey:["printableObjects",t],queryFn:()=>re.getPrintableObjects(t),enabled:e,refetchInterval:e?5e3:!1}),f=lt({mutationFn:g=>re.skipObjects(t,g),onSuccess:g=>{i(g.message||r("printers.skipObjects.objectsSkipped")),l(null),p()},onError:g=>i(g.message||r("printers.toast.failedToSkipObjects"),"error")});return e?n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center",onClick:a,onKeyDown:g=>{g.key==="Escape"&&(c?d(!1):a())},tabIndex:-1,ref:g=>g?.focus(),children:[n.jsx("div",{className:"absolute inset-0 bg-black/50 z-0"}),n.jsxs("div",{className:"relative z-10 bg-white dark:bg-bambu-dark border border-gray-200 dark:border-bambu-dark-tertiary rounded-xl shadow-2xl w-[560px] max-h-[85vh] flex flex-col overflow-hidden",onClick:g=>g.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-bambu-dark-tertiary bg-gray-50 dark:bg-bambu-dark",children:[n.jsxs("div",{className:"flex items-center gap-2",children:[n.jsx(VT,{className:"w-4 h-4 text-bambu-green"}),n.jsx("span",{className:"text-sm font-medium text-gray-900 dark:text-white",children:r("printers.skipObjects.title")})]}),n.jsx("button",{onClick:a,className:"p-1 text-gray-500 dark:text-bambu-gray hover:text-gray-900 dark:hover:text-white rounded transition-colors",children:n.jsx($t,{className:"w-4 h-4"})})]}),m?m.objects.length===0?n.jsxs("div",{className:"text-center py-8 px-4 text-bambu-gray",children:[n.jsx("p",{className:"text-sm",children:r("printers.noObjectsFound")}),n.jsx("p",{className:"text-xs mt-1 opacity-70",children:r("printers.objectsLoadedOnPrintStart")})]}):n.jsxs("div",{className:"flex flex-col overflow-hidden",children:[n.jsxs("div",{className:"flex items-center gap-3 px-4 py-2.5 bg-blue-50 dark:bg-blue-500/10 border-b border-gray-200 dark:border-bambu-dark-tertiary",children:[n.jsx("div",{className:"flex-shrink-0 w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-500/20 flex items-center justify-center",children:n.jsx(AO,{className:"w-4 h-4 text-blue-500 dark:text-blue-400"})}),n.jsxs("div",{className:"flex-1 min-w-0",children:[n.jsx("p",{className:"text-xs text-blue-600 dark:text-blue-300",children:r("printers.skipObjects.matchIdsInfo")}),n.jsx("p",{className:"text-[10px] text-blue-500/70 dark:text-blue-300/60",children:r("printers.skipObjects.printerShowsIds")})]}),n.jsxs("div",{className:"flex-shrink-0 text-xs text-gray-500 dark:text-bambu-gray",children:[m.skipped_count,"/",m.total," ",r("printers.skipObjects.skipped")]})]}),(u?.layer_num??0)<=1&&n.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 bg-amber-50 dark:bg-amber-500/10 border-b border-gray-200 dark:border-bambu-dark-tertiary",children:[n.jsx(Hr,{className:"w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0"}),n.jsx("p",{className:"text-xs text-amber-600 dark:text-amber-400",children:r("printers.skipObjects.waitForLayer",{layer:u?.layer_num??0})})]}),n.jsxs("div",{className:"flex flex-1 overflow-hidden",children:[n.jsx("div",{className:"w-52 flex-shrink-0 p-4 border-r border-gray-200 dark:border-bambu-dark-tertiary bg-gray-50 dark:bg-bambu-dark-secondary overflow-y-auto",children:n.jsxs("div",{className:"relative cursor-pointer group",onClick:()=>d(!0),children:[u?.cover_url?n.jsx("img",{src:Fr(`${u.cover_url}?view=top`),alt:r("printers.printPreview"),className:"w-full aspect-square object-contain rounded-lg bg-gray-900 dark:bg-gray-900 border border-gray-300 dark:border-gray-600"}):n.jsx("div",{className:"w-full aspect-square rounded-lg bg-gray-100 dark:bg-bambu-dark flex items-center justify-center",children:n.jsx(ji,{className:"w-8 h-8 text-gray-300 dark:text-bambu-gray/30"})}),n.jsx("div",{className:"absolute top-2 right-2 p-1 bg-black/60 rounded opacity-0 group-hover:opacity-100 transition-opacity",children:n.jsx(MT,{className:"w-3.5 h-3.5 text-white"})}),m.objects.length>0&&n.jsx("div",{className:"absolute inset-0 pointer-events-none",children:m.objects.map((g,v)=>{let b,x;if(g.x!=null&&g.y!=null&&m.bbox_all){const[S,_,C,P]=m.bbox_all,T=C-S,A=P-_,F=8,k=100-F*2;b=F+(g.x-S)/T*k,x=F+(P-g.y)/A*k,b=Math.max(5,Math.min(95,b)),x=Math.max(5,Math.min(95,x))}else if(g.x!=null&&g.y!=null)b=g.x/256*100,x=100-g.y/256*100,b=Math.max(5,Math.min(95,b)),x=Math.max(5,Math.min(95,x));else{const S=Math.ceil(Math.sqrt(m.objects.length)),_=Math.floor(v/S),C=v%S,P=Math.ceil(m.objects.length/S);b=15+C*(70/S)+35/S,x=15+_*(70/P)+35/P}return n.jsx("div",{className:`absolute flex items-center justify-center w-6 h-6 rounded-full text-[10px] font-bold shadow-lg ${g.skipped?"bg-red-500 text-white line-through":"bg-bambu-green text-black"}`,style:{left:`${b}%`,top:`${x}%`,transform:"translate(-50%, -50%)"},title:g.name,children:g.id},g.id)})}),n.jsx("div",{className:"absolute bottom-2 right-2 px-2 py-1 bg-white/90 dark:bg-black/80 rounded text-[10px] text-gray-700 dark:text-white shadow-sm",children:r("printers.skipObjects.activeCount",{count:m.objects.filter(g=>!g.skipped).length})})]})}),n.jsx("div",{className:"flex-1 min-w-0 overflow-y-auto",children:m.objects.map(g=>n.jsxs("div",{className:` +`);ue.length>0&&(k.extrusionColor=ue),k.processGCode(pe);const X=k.layers?.length||0;x(X),v(X),k.render(),d(!1)}).catch(z=>{z.message!=="not_sliced"&&m(z.message),d(!1)});const I=()=>{if(P.parentElement&&s.current){const z=P.parentElement.getBoundingClientRect();P.width=z.width,P.height=z.height,s.current.resize()}};return window.addEventListener("resize",I),()=>{window.removeEventListener("resize",I),o.current&&cancelAnimationFrame(o.current),s.current&&(s.current.dispose(),s.current=null),l.current=!1}},[t,S]);const _=y.useCallback(P=>{if(!s.current)return;const T=Math.max(1,Math.min(P,b));v(T),o.current&&cancelAnimationFrame(o.current),o.current=requestAnimationFrame(()=>{s.current&&(s.current.endLayer=T,s.current.render())})},[b]),C=P=>{_(parseInt(P.target.value,10))};return n.jsxs("div",{className:`relative flex flex-col h-full ${r}`,children:[n.jsxs("div",{className:"flex-1 relative bg-bambu-dark rounded-lg overflow-hidden",children:[n.jsx("canvas",{ref:i,className:"w-full h-full"}),c&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsxs("div",{className:"text-center",children:[n.jsx(pt,{className:"w-8 h-8 animate-spin text-bambu-green mx-auto mb-2"}),n.jsx("p",{className:"text-bambu-gray text-sm",children:"Loading G-code..."})]})}),p&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsxs("div",{className:"text-center max-w-sm px-4",children:[n.jsx(Nbe,{className:"w-12 h-12 text-bambu-gray mx-auto mb-3"}),n.jsx("p",{className:"text-white font-medium mb-2",children:"G-code not available"}),n.jsx("p",{className:"text-bambu-gray text-sm",children:"This file hasn't been sliced yet. G-code preview is only available after slicing in Bambu Studio or Orca Slicer."})]})}),u&&!p&&n.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-bambu-dark/80",children:n.jsx("div",{className:"text-center text-red-400",children:n.jsx("p",{className:"text-sm",children:u})})})]}),!c&&!u&&!p&&b>0&&n.jsx("div",{className:"mt-4 px-2",children:n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx(cr,{className:"w-4 h-4 text-bambu-gray flex-shrink-0"}),n.jsx("button",{onClick:()=>_(g-1),disabled:g<=1,className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-30 disabled:cursor-not-allowed",children:n.jsx(tl,{className:"w-4 h-4"})}),n.jsx("input",{type:"range",min:1,max:b,value:g,onChange:C,className:"flex-1 h-2 bg-bambu-dark-tertiary rounded-lg appearance-none cursor-pointer accent-bambu-green"}),n.jsx("button",{onClick:()=>_(g+1),disabled:g>=b,className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-30 disabled:cursor-not-allowed",children:n.jsx(qr,{className:"w-4 h-4"})}),n.jsxs("span",{className:"text-sm text-bambu-gray min-w-[80px] text-right",children:[g," / ",b]})]})})]})}function g2e({printerId:t,filePath:e,filename:a,onClose:r}){const[i,s]=y.useState(null),[o,l]=y.useState([]),[c,d]=y.useState(!1),[u,m]=y.useState(null),p=a.toLowerCase().split(".").pop()||"",f=p==="3mf"||p==="stl",g=p==="gcode"||p==="3mf";y.useEffect(()=>{s(f?"3d":g?"gcode":null)},[f,g]),y.useEffect(()=>{l([]),m(null),f&&(d(!0),re.getPrinterFilePlates(t,e).then(x=>l(x.plates||[])).catch(()=>l([])).finally(()=>d(!1)))},[e,f,t]);const v=o.length>1,b=u==null?null:o.find(x=>x.index===u)??null;return n.jsx("div",{className:"fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-6",onClick:r,children:n.jsxs("div",{className:"bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary w-full max-w-4xl h-[80vh] flex flex-col",onClick:x=>x.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between px-6 py-4 border-b border-bambu-dark-tertiary",children:[n.jsx("h2",{className:"text-lg font-semibold text-white truncate flex-1 mr-4",children:a}),n.jsx(je,{variant:"ghost",size:"sm",onClick:r,children:n.jsx($t,{className:"w-5 h-5"})})]}),n.jsxs("div",{className:"flex border-b border-bambu-dark-tertiary",children:[n.jsxs("button",{onClick:()=>f&&s("3d"),disabled:!f,className:`flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors ${i==="3d"?"text-bambu-green border-b-2 border-bambu-green":f?"text-bambu-gray hover:text-white":"text-bambu-gray/30 cursor-not-allowed"}`,children:[n.jsx(ji,{className:"w-4 h-4"}),"3D Model",!f&&n.jsx("span",{className:"text-xs",children:"(not available)"})]}),n.jsxs("button",{onClick:()=>g&&s("gcode"),disabled:!g,className:`flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors ${i==="gcode"?"text-bambu-green border-b-2 border-bambu-green":g?"text-bambu-gray hover:text-white":"text-bambu-gray/30 cursor-not-allowed"}`,children:[n.jsx(Bs,{className:"w-4 h-4"}),"G-code Preview",!g&&n.jsx("span",{className:"text-xs",children:"(not sliced)"})]})]}),n.jsx("div",{className:"flex-1 overflow-hidden p-4",children:i==="3d"&&f?n.jsxs("div",{className:"w-full h-full flex flex-col gap-3",children:[v&&n.jsxs("div",{className:"rounded-lg border border-bambu-dark-tertiary bg-bambu-dark p-3",children:[n.jsxs("div",{className:"flex items-center gap-2 text-sm text-bambu-gray mb-2",children:[n.jsx(ji,{className:"w-4 h-4"}),"Plates",c&&n.jsx(pt,{className:"w-3 h-3 animate-spin"})]}),n.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 gap-2",children:[n.jsxs("button",{type:"button",onClick:()=>m(null),className:`flex items-center gap-2 rounded-lg border p-2 text-left transition-colors ${u==null?"border-bambu-green bg-bambu-green/10":"border-bambu-dark-tertiary bg-bambu-dark-secondary hover:border-bambu-gray"}`,children:[n.jsx("div",{className:"w-10 h-10 rounded bg-bambu-dark-tertiary flex items-center justify-center",children:n.jsx(ji,{className:"w-5 h-5 text-bambu-gray"})}),n.jsxs("div",{className:"min-w-0 flex-1",children:[n.jsx("p",{className:"text-sm text-white font-medium truncate",children:"All Plates"}),n.jsxs("p",{className:"text-xs text-bambu-gray truncate",children:[o.length," plate",o.length!==1?"s":""]})]}),u==null&&n.jsx(ws,{className:"w-4 h-4 text-bambu-green flex-shrink-0"})]}),o.map(x=>n.jsxs("button",{type:"button",onClick:()=>m(x.index),className:`flex items-center gap-2 rounded-lg border p-2 text-left transition-colors ${u===x.index?"border-bambu-green bg-bambu-green/10":"border-bambu-dark-tertiary bg-bambu-dark-secondary hover:border-bambu-gray"}`,children:[x.has_thumbnail?n.jsx("img",{src:re.getPrinterFilePlateThumbnail(t,x.index,e),alt:`Plate ${x.index}`,className:"w-10 h-10 rounded object-cover bg-bambu-dark-tertiary"}):n.jsx("div",{className:"w-10 h-10 rounded bg-bambu-dark-tertiary flex items-center justify-center",children:n.jsx(ji,{className:"w-5 h-5 text-bambu-gray"})}),n.jsxs("div",{className:"min-w-0 flex-1",children:[n.jsx("p",{className:"text-sm text-white font-medium truncate",children:x.name||`Plate ${x.index}`}),n.jsx("p",{className:"text-xs text-bambu-gray truncate",children:x.objects.length>0?x.objects.slice(0,2).join(", ")+(x.objects.length>2?"…":""):`${x.filaments.length} filament${x.filaments.length!==1?"s":""}`})]}),u===x.index&&n.jsx(ws,{className:"w-4 h-4 text-bambu-green flex-shrink-0"})]},x.index))]}),b&&n.jsxs("div",{className:"mt-3 text-xs text-bambu-gray flex flex-wrap gap-x-4 gap-y-1",children:[n.jsxs("span",{children:["Plate ",b.index]}),b.print_time_seconds!=null&&n.jsxs("span",{children:["ETA ",Math.round(b.print_time_seconds/60)," min"]}),b.filament_used_grams!=null&&n.jsxs("span",{children:[b.filament_used_grams.toFixed(1)," g"]}),b.filaments.length>0&&n.jsxs("span",{children:[b.filaments.length," filament",b.filaments.length!==1?"s":""]})]})]}),n.jsx("div",{className:"flex-1",children:n.jsx(iee,{url:re.getPrinterFileDownloadUrl(t,e),fileType:p,selectedPlateId:u,className:"w-full h-full"})})]}):i==="gcode"&&g?n.jsx(Bee,{gcodeUrl:re.getPrinterFileGcodeUrl(t,e),className:"w-full h-full"}):n.jsx("div",{className:"w-full h-full flex items-center justify-center text-bambu-gray",children:"No preview available for this file"})})]})})}function RV(t){if(t===0)return"0 GB";const e=t/(1024*1024*1024);return e>=1?`${e.toFixed(1)} GB`:`${(t/(1024*1024)).toFixed(0)} MB`}function b2e(t,e){if(e)return VZ;switch(t.toLowerCase().split(".").pop()||""){case"3mf":return Th;case"gcode":return Bs;case"mp4":case"avi":return Ah;case"png":case"jpg":case"jpeg":return cu;default:return jT}}const x2e=[{value:"name-asc",label:"Name (A-Z)"},{value:"name-desc",label:"Name (Z-A)"},{value:"size-asc",label:"Size (smallest)"},{value:"size-desc",label:"Size (largest)"},{value:"date-asc",label:"Date (oldest)"},{value:"date-desc",label:"Date (newest)"}];function y2e({printerId:t,printerName:e,onClose:a}){const{t:r}=St(),{showToast:i}=ra(),s=ta(),[o,l]=y.useState("/"),[c,d]=y.useState(new Set),[u,m]=y.useState(""),[p,f]=y.useState([]),[g,v]=y.useState("name-asc"),[b,x]=y.useState(null),[S,_]=y.useState(null);y.useEffect(()=>{const Q=oe=>{oe.key==="Escape"&&a()};return window.addEventListener("keydown",Q),()=>window.removeEventListener("keydown",Q)},[a]);const{data:C,isLoading:P,refetch:T}=Ye({queryKey:["printerFiles",t,o],queryFn:()=>re.getPrinterFiles(t,o)}),{data:A}=Ye({queryKey:["printerStorage",t],queryFn:()=>re.getPrinterStorage(t),staleTime:3e4}),F=lt({mutationFn:async Q=>{for(const oe of Q)await re.deletePrinterFile(t,oe)},onSuccess:()=>{i(r("printerFiles.toast.filesDeleted",{count:p.length})),s.invalidateQueries({queryKey:["printerFiles",t]}),d(new Set),f([])},onError:Q=>{i(r("printerFiles.toast.deleteFailed",{error:Q.message}),"error")}}),k=Q=>{l(Q),d(new Set)},E=()=>{if(o==="/")return;const Q=o.split("/").filter(Boolean);Q.pop(),l(Q.length?"/"+Q.join("/"):"/"),d(new Set)},U=(Q,oe)=>{oe.stopPropagation(),d(ue=>{const pe=new Set(ue);return pe.has(Q)?pe.delete(Q):pe.add(Q),pe})},I=()=>{if(!C?.files)return;const Q=C.files.filter(oe=>!oe.is_directory&&(!u||oe.name.toLowerCase().includes(u.toLowerCase()))).map(oe=>oe.path);d(new Set(Q))},z=()=>{d(new Set)},L=async()=>{if(c.size===0)return;const Q=Array.from(c);if(Q.length===1){re.downloadPrinterFile(t,Q[0]).catch(oe=>{console.error("Printer file download failed:",oe)}),d(new Set);return}x({current:0,total:Q.length});try{const oe=await re.downloadPrinterFilesAsZip(t,Q),ue=URL.createObjectURL(oe),pe=document.createElement("a");pe.href=ue,pe.download=`${e.replace(/[^a-zA-Z0-9]/g,"_")}-files.zip`,document.body.appendChild(pe),pe.click(),document.body.removeChild(pe),URL.revokeObjectURL(ue),i(`Downloaded ${Q.length} files as ZIP`),d(new Set)}catch(oe){i(`Download failed: ${oe instanceof Error?oe.message:"Unknown error"}`,"error")}finally{x(null)}},Z=()=>{c.size!==0&&f(Array.from(c))},ae=[{path:"/",label:"Root"},{path:"/cache",label:"Cache"},{path:"/model",label:"Models"},{path:"/timelapse",label:"Timelapse"}];return n.jsxs("div",{className:"fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4",onClick:a,children:[n.jsxs("div",{className:"w-full max-w-3xl max-h-[85vh] flex flex-col bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary overflow-hidden",onClick:Q=>Q.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between p-4 border-b border-bambu-dark-tertiary flex-shrink-0",children:[n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx(cb,{className:"w-5 h-5 text-bambu-green"}),n.jsxs("div",{children:[n.jsx("h2",{className:"text-lg font-semibold text-white",children:r("printerFiles.title")}),n.jsx("p",{className:"text-sm text-bambu-gray",children:e})]})]}),n.jsxs("div",{className:"flex items-center gap-4",children:[A&&(A.used_bytes!=null||A.free_bytes!=null)&&n.jsxs("div",{className:"text-sm text-bambu-gray flex items-center gap-2",children:[A.used_bytes!=null&&n.jsxs("span",{children:[r("printerFiles.storageUsed")," ",RV(A.used_bytes)]}),A.used_bytes!=null&&A.free_bytes!=null&&n.jsx("span",{className:"text-bambu-dark-tertiary",children:"|"}),A.free_bytes!=null&&n.jsxs("span",{children:[r("printerFiles.storageFree")," ",RV(A.free_bytes)]})]}),n.jsx("button",{onClick:a,className:"text-bambu-gray hover:text-white transition-colors",title:"Close file manager","aria-label":"Close file manager",children:n.jsx($t,{className:"w-5 h-5"})})]})]}),n.jsxs("div",{className:"flex items-center gap-2 p-3 border-b border-bambu-dark-tertiary bg-bambu-dark/50 flex-shrink-0",children:[ae.map(Q=>n.jsx("button",{onClick:()=>{k(Q.path),m("")},className:`px-3 py-1 text-sm rounded-full transition-colors ${o===Q.path?"bg-bambu-green text-white":"bg-bambu-dark-tertiary text-bambu-gray hover:text-white"}`,children:Q.label},Q.path)),n.jsx("div",{className:"flex-1"}),n.jsxs("div",{className:"relative",children:[n.jsx(gn,{className:"absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray"}),n.jsx("input",{type:"text",placeholder:r("printerFiles.filterPlaceholder"),value:u,onChange:Q=>m(Q.target.value),className:"w-40 pl-8 pr-3 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:border-bambu-green focus:outline-none"})]}),n.jsxs("div",{className:"relative flex items-center gap-1",children:[n.jsx(TT,{className:"w-4 h-4 text-bambu-gray"}),n.jsx("select",{value:g,onChange:Q=>v(Q.target.value),className:"appearance-none bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm py-1.5 pl-2 pr-6 focus:border-bambu-green focus:outline-none cursor-pointer",title:"Sort files","aria-label":"Sort files",children:x2e.map(Q=>n.jsx("option",{value:Q.value,children:Q.label},Q.value))})]}),n.jsx(je,{variant:"secondary",size:"sm",onClick:()=>T(),disabled:P,children:n.jsx(en,{className:`w-4 h-4 ${P?"animate-spin":""}`})})]}),n.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 bg-bambu-dark text-sm flex-shrink-0",children:[n.jsx("button",{onClick:E,disabled:o==="/",className:"p-1 rounded hover:bg-bambu-dark-tertiary disabled:opacity-50 disabled:cursor-not-allowed",title:"Go to parent folder","aria-label":"Go to parent folder",children:n.jsx(tl,{className:"w-4 h-4"})}),n.jsx("span",{className:"text-bambu-gray font-mono",children:o})]}),n.jsx("div",{className:"flex-1 overflow-y-auto p-2 min-h-0",children:P?n.jsx("div",{className:"flex items-center justify-center py-12",children:n.jsx(pt,{className:"w-8 h-8 text-bambu-green animate-spin"})}):C?.files?.length?n.jsx("div",{className:"space-y-1",children:[...C.files].filter(Q=>!u||Q.name.toLowerCase().includes(u.toLowerCase())).sort((Q,oe)=>{if(Q.is_directory&&!oe.is_directory)return-1;if(!Q.is_directory&&oe.is_directory)return 1;switch(g){case"name-asc":return Q.name.localeCompare(oe.name);case"name-desc":return oe.name.localeCompare(Q.name);case"size-asc":return Q.size-oe.size;case"size-desc":return oe.size-Q.size;case"date-asc":{const ue=Q.mtime?Qa(Q.mtime)?.getTime()??0:0,pe=oe.mtime?Qa(oe.mtime)?.getTime()??0:0;return ue-pe}case"date-desc":{const ue=Q.mtime?Qa(Q.mtime)?.getTime()??0:0;return(oe.mtime?Qa(oe.mtime)?.getTime()??0:0)-ue}default:return Q.name.localeCompare(oe.name)}}).map(Q=>{const oe=b2e(Q.name,Q.is_directory),ue=c.has(Q.path);return n.jsxs("div",{className:`flex items-center gap-3 p-2 rounded-lg cursor-pointer transition-colors ${ue?"bg-bambu-green/20 border border-bambu-green/50":"hover:bg-bambu-dark-tertiary"}`,onClick:()=>{Q.is_directory&&k(Q.path)},children:[Q.is_directory?null:n.jsx("button",{onClick:pe=>U(Q.path,pe),className:"flex-shrink-0 text-bambu-gray hover:text-white",children:ue?n.jsx(ws,{className:"w-5 h-5 text-bambu-green"}):n.jsx(oo,{className:"w-5 h-5"})}),n.jsx(oe,{className:`w-5 h-5 flex-shrink-0 ${Q.is_directory?"text-bambu-green":"text-bambu-gray"}`}),n.jsx("span",{className:"flex-1 text-white truncate",children:Q.name}),!Q.is_directory&&n.jsxs("div",{className:"flex items-center gap-3",children:[n.jsx("span",{className:"text-sm text-bambu-gray",children:ps(Q.size)}),(Q.name.toLowerCase().endsWith(".3mf")||Q.name.toLowerCase().endsWith(".gcode")||Q.name.toLowerCase().endsWith(".stl"))&&n.jsx("button",{onClick:pe=>{pe.stopPropagation(),_({path:Q.path,name:Q.name})},className:"p-1 rounded hover:bg-bambu-dark text-bambu-gray hover:text-bambu-green",title:"3D View",children:n.jsx(ji,{className:"w-4 h-4"})})]}),Q.is_directory&&n.jsx(tl,{className:"w-4 h-4 text-bambu-gray rotate-180"})]},Q.path)})}):n.jsx("div",{className:"text-center py-12 text-bambu-gray",children:"No files in this directory"})}),n.jsxs("div",{className:"flex items-center justify-between p-4 border-t border-bambu-dark-tertiary bg-bambu-dark/50 flex-shrink-0",children:[n.jsxs("div",{className:"flex items-center gap-4",children:[n.jsx("div",{className:"text-sm text-bambu-gray",children:c.size>0?`${c.size} selected`:u?`${C?.files?.filter(Q=>Q.name.toLowerCase().includes(u.toLowerCase())).length||0} of ${C?.files?.length||0} items`:`${C?.files?.length||0} items`}),C?.files?.some(Q=>!Q.is_directory)&&n.jsx("div",{className:"flex items-center gap-2",children:c.size>0?n.jsxs("button",{onClick:z,className:"flex items-center gap-1 text-xs text-bambu-gray hover:text-white transition-colors",children:[n.jsx(eve,{className:"w-4 h-4"}),"Deselect All"]}):n.jsxs("button",{onClick:I,className:"flex items-center gap-1 text-xs text-bambu-gray hover:text-white transition-colors",children:[n.jsx(ws,{className:"w-4 h-4"}),"Select All"]})})]}),n.jsxs("div",{className:"flex gap-2",children:[n.jsx(je,{variant:"secondary",disabled:c.size===0||b!==null,onClick:L,children:b?n.jsxs(n.Fragment,{children:[n.jsx(pt,{className:"w-4 h-4 animate-spin"}),b.current,"/",b.total]}):n.jsxs(n.Fragment,{children:[n.jsx($n,{className:"w-4 h-4"}),"Download",c.size>1?` (${c.size})`:""]})}),n.jsxs(je,{variant:"secondary",disabled:c.size===0||F.isPending,onClick:Z,className:"text-red-400 hover:text-red-300",children:[F.isPending?n.jsx(pt,{className:"w-4 h-4 animate-spin"}):n.jsx(ea,{className:"w-4 h-4"}),r("printerFiles.deleteButton"),c.size>1?` (${c.size})`:""]})]})]})]}),p.length>0&&n.jsx(va,{title:p.length>1?r("printerFiles.deleteFiles",{count:p.length}):r("fileManager.deleteFile"),message:p.length>1?r("printerFiles.deleteFilesConfirm",{count:p.length}):r("printerFiles.deleteFileConfirm",{name:p[0].split("/").pop()}),confirmText:r("common.delete"),variant:"danger",onConfirm:()=>{F.mutate(p)},onCancel:()=>f([])}),S&&n.jsx(g2e,{printerId:t,filePath:S.path,filename:S.name,onClose:()=>_(null)})]})}function m4({on:t,className:e="w-5 h-5"}){const a=t?"#facc15":"none",r=t?"#78350f":"currentColor",i=t?1:0;return n.jsxs("svg",{viewBox:"0 0 32 32",fill:"none",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:e,children:[n.jsxs("g",{stroke:r,opacity:i,children:[n.jsx("line",{x1:"16",y1:"2",x2:"16",y2:"6"}),n.jsx("line",{x1:"6.1",y1:"6.1",x2:"8.9",y2:"8.9"}),n.jsx("line",{x1:"25.9",y1:"6.1",x2:"23.1",y2:"8.9"}),n.jsx("line",{x1:"2",y1:"16",x2:"6",y2:"16"}),n.jsx("line",{x1:"30",y1:"16",x2:"26",y2:"16"})]}),n.jsx("path",{d:"M12 24v-2.3c0-.9-.4-1.7-1-2.3C9.2 17.6 8 15.4 8 13c0-4.4 3.6-8 8-8s8 3.6 8 8c0 2.4-1.2 4.6-3 6.4-.6.6-1 1.4-1 2.3V24",fill:a,stroke:r}),n.jsx("path",{d:"M12 24h8",stroke:r}),n.jsx("path",{d:"M12 27h8",stroke:r}),n.jsx("path",{d:"M13 30h6",stroke:r})]})}const VT=({className:t})=>n.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:t,children:[n.jsx("rect",{x:"2",y:"15",width:"5",height:"5",rx:"0.5"}),n.jsx("rect",{x:"9.5",y:"15",width:"5",height:"5",rx:"0.5",fill:"currentColor",opacity:"0.3"}),n.jsx("rect",{x:"17",y:"15",width:"5",height:"5",rx:"0.5"}),n.jsx("path",{d:"M4 12 C4 6, 14 6, 14 12"}),n.jsx("polyline",{points:"12,10 14,12 12,14"})]});function p4({printerId:t,isOpen:e,onClose:a}){const{t:r}=St(),{showToast:i}=ra(),{hasPermission:s}=un(),[o,l]=y.useState(null),[c,d]=y.useState(!1),{data:u}=Ye({queryKey:["printerStatus",t],queryFn:()=>re.getPrinterStatus(t),refetchInterval:3e4,enabled:e}),{data:m,refetch:p}=Ye({queryKey:["printableObjects",t],queryFn:()=>re.getPrintableObjects(t),enabled:e,refetchInterval:e?5e3:!1}),f=lt({mutationFn:g=>re.skipObjects(t,g),onSuccess:g=>{i(g.message||r("printers.skipObjects.objectsSkipped")),l(null),p()},onError:g=>i(g.message||r("printers.toast.failedToSkipObjects"),"error")});return e?n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center",onClick:a,onKeyDown:g=>{g.key==="Escape"&&(c?d(!1):a())},tabIndex:-1,ref:g=>g?.focus(),children:[n.jsx("div",{className:"absolute inset-0 bg-black/50 z-0"}),n.jsxs("div",{className:"relative z-10 bg-white dark:bg-bambu-dark border border-gray-200 dark:border-bambu-dark-tertiary rounded-xl shadow-2xl w-[560px] max-h-[85vh] flex flex-col overflow-hidden",onClick:g=>g.stopPropagation(),children:[n.jsxs("div",{className:"flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-bambu-dark-tertiary bg-gray-50 dark:bg-bambu-dark",children:[n.jsxs("div",{className:"flex items-center gap-2",children:[n.jsx(VT,{className:"w-4 h-4 text-bambu-green"}),n.jsx("span",{className:"text-sm font-medium text-gray-900 dark:text-white",children:r("printers.skipObjects.title")})]}),n.jsx("button",{onClick:a,className:"p-1 text-gray-500 dark:text-bambu-gray hover:text-gray-900 dark:hover:text-white rounded transition-colors",children:n.jsx($t,{className:"w-4 h-4"})})]}),m?m.objects.length===0?n.jsxs("div",{className:"text-center py-8 px-4 text-bambu-gray",children:[n.jsx("p",{className:"text-sm",children:r("printers.noObjectsFound")}),n.jsx("p",{className:"text-xs mt-1 opacity-70",children:r("printers.objectsLoadedOnPrintStart")})]}):n.jsxs("div",{className:"flex flex-col overflow-hidden",children:[n.jsxs("div",{className:"flex items-center gap-3 px-4 py-2.5 bg-blue-50 dark:bg-blue-500/10 border-b border-gray-200 dark:border-bambu-dark-tertiary",children:[n.jsx("div",{className:"flex-shrink-0 w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-500/20 flex items-center justify-center",children:n.jsx(AO,{className:"w-4 h-4 text-blue-500 dark:text-blue-400"})}),n.jsxs("div",{className:"flex-1 min-w-0",children:[n.jsx("p",{className:"text-xs text-blue-600 dark:text-blue-300",children:r("printers.skipObjects.matchIdsInfo")}),n.jsx("p",{className:"text-[10px] text-blue-500/70 dark:text-blue-300/60",children:r("printers.skipObjects.printerShowsIds")})]}),n.jsxs("div",{className:"flex-shrink-0 text-xs text-gray-500 dark:text-bambu-gray",children:[m.skipped_count,"/",m.total," ",r("printers.skipObjects.skipped")]})]}),(u?.layer_num??0)<=1&&n.jsxs("div",{className:"flex items-center gap-2 px-4 py-2 bg-amber-50 dark:bg-amber-500/10 border-b border-gray-200 dark:border-bambu-dark-tertiary",children:[n.jsx(Hr,{className:"w-4 h-4 text-amber-500 dark:text-amber-400 flex-shrink-0"}),n.jsx("p",{className:"text-xs text-amber-600 dark:text-amber-400",children:r("printers.skipObjects.waitForLayer",{layer:u?.layer_num??0})})]}),n.jsxs("div",{className:"flex flex-1 overflow-hidden",children:[n.jsx("div",{className:"w-52 flex-shrink-0 p-4 border-r border-gray-200 dark:border-bambu-dark-tertiary bg-gray-50 dark:bg-bambu-dark-secondary overflow-y-auto",children:n.jsxs("div",{className:"relative cursor-pointer group",onClick:()=>d(!0),children:[u?.cover_url?n.jsx("img",{src:Fr(`${u.cover_url}?view=top`),alt:r("printers.printPreview"),className:"w-full aspect-square object-contain rounded-lg bg-gray-900 dark:bg-gray-900 border border-gray-300 dark:border-gray-600"}):n.jsx("div",{className:"w-full aspect-square rounded-lg bg-gray-100 dark:bg-bambu-dark flex items-center justify-center",children:n.jsx(ji,{className:"w-8 h-8 text-gray-300 dark:text-bambu-gray/30"})}),n.jsx("div",{className:"absolute top-2 right-2 p-1 bg-black/60 rounded opacity-0 group-hover:opacity-100 transition-opacity",children:n.jsx(MT,{className:"w-3.5 h-3.5 text-white"})}),m.objects.length>0&&n.jsx("div",{className:"absolute inset-0 pointer-events-none",children:m.objects.map((g,v)=>{let b,x;if(g.x!=null&&g.y!=null&&m.bbox_all){const[S,_,C,P]=m.bbox_all,T=C-S,A=P-_,F=8,k=100-F*2;b=F+(g.x-S)/T*k,x=F+(P-g.y)/A*k,b=Math.max(5,Math.min(95,b)),x=Math.max(5,Math.min(95,x))}else if(g.x!=null&&g.y!=null)b=g.x/256*100,x=100-g.y/256*100,b=Math.max(5,Math.min(95,b)),x=Math.max(5,Math.min(95,x));else{const S=Math.ceil(Math.sqrt(m.objects.length)),_=Math.floor(v/S),C=v%S,P=Math.ceil(m.objects.length/S);b=15+C*(70/S)+35/S,x=15+_*(70/P)+35/P}return n.jsx("div",{className:`absolute flex items-center justify-center w-6 h-6 rounded-full text-[10px] font-bold shadow-lg ${g.skipped?"bg-red-500 text-white line-through":"bg-bambu-green text-black"}`,style:{left:`${b}%`,top:`${x}%`,transform:"translate(-50%, -50%)"},title:g.name,children:g.id},g.id)})}),n.jsx("div",{className:"absolute bottom-2 right-2 px-2 py-1 bg-white/90 dark:bg-black/80 rounded text-[10px] text-gray-700 dark:text-white shadow-sm",children:r("printers.skipObjects.activeCount",{count:m.objects.filter(g=>!g.skipped).length})})]})}),n.jsx("div",{className:"flex-1 min-w-0 overflow-y-auto",children:m.objects.map(g=>n.jsxs("div",{className:` flex items-center gap-3 px-4 py-3 border-b border-gray-200 dark:border-bambu-dark-tertiary/50 last:border-0 ${g.skipped?"bg-red-50 dark:bg-red-500/10":"hover:bg-gray-50 dark:hover:bg-bambu-dark/50"} `,children:[n.jsxs("div",{className:` diff --git a/static/index.html b/static/index.html index 8e8f5c372..2fbf11687 100644 --- a/static/index.html +++ b/static/index.html @@ -26,7 +26,7 @@ - +