From e982c71c1d7c9ac427c21246c427e7d7ab06bcc2 Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 1 Oct 2026 15:03:22 +0200 Subject: [PATCH] Post work PR #3212 --- CHANGELOG.md | 1 + ...eviewModal-ertLn7DQ.js => ImagePreviewModal-DydMcOJ9.js} | 2 +- ...PreviewModal-DP4k6p_n.js => PdfPreviewModal-DXTd9gb8.js} | 4 ++-- ...odal-oZ5sN9G4.js => SpreadsheetPreviewModal-DKtg1R2F.js} | 2 +- static/assets/{index-DCzE7_R7.js => index-DXO3amin.js} | 6 +++--- static/assets/{pdf-BptzBvjW.js => pdf-BeRViiAS.js} | 2 +- static/index.html | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) rename static/assets/{ImagePreviewModal-ertLn7DQ.js => ImagePreviewModal-DydMcOJ9.js} (98%) rename static/assets/{PdfPreviewModal-DP4k6p_n.js => PdfPreviewModal-DXTd9gb8.js} (97%) rename static/assets/{SpreadsheetPreviewModal-oZ5sN9G4.js => SpreadsheetPreviewModal-DKtg1R2F.js} (98%) rename static/assets/{index-DCzE7_R7.js => index-DXO3amin.js} (98%) rename static/assets/{pdf-BptzBvjW.js => pdf-BeRViiAS.js} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87f6f6b35..f8b9f8a5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ 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 +- **Stream overlay and Cam Wall links keep working after a reload (#3204, reported and contributed by @adamspicedev in #3212)** — Opening an `/overlay/…?token=…` or `/camwall?token=…` link treated the token as a login: Bambuddy removed it from the address bar and tried to sign in with it, which a streaming token can't do. The page worked the first time, but a reload (or an OBS or Meld browser source restarting) had no token left and stayed on **Loading…**. In a browser that was already signed in, the failed attempt also signed the user out. The token now stays in the link and is used only by the overlay or Cam Wall, and an existing login is left alone. SpoolBuddy kiosk links still sign in with their API key as before. - **The File Manager reaches the bottom of the window again (#3215, reported by @koder-guy)** — On desktop the File Manager and the G-code viewer were sized as if a 64px top bar sat above them, but that bar only exists in the mobile layout, so both pages stopped 64px short and left an empty band under the file browser. They now fill the full window height. - **Progress milestones no longer send 75% at the start of a print, and 25% and 50% arrive again (#3211, reported by @BurgerKerman)** — An A1 mini reports a progress of 85% in the first moment of a print, while it is still heating the bed, and then values such as 3%, 40% and 44% through its calibration, before the first layer starts at 45%. Bambuddy read that 85% as progress and sent the 75% milestone together with Print Started, with an unknown remaining time. With 75% then on record, the 25% and 50% milestones of that print never fired. Milestones now count only from the first printed layer, and every new print starts them from zero. A printer that started layer 1 already past 25% sends the 25% milestone then. Until a print's layer count is known, the printer's preparation stage (bed preheating, homing, calibration) holds milestones back the same way. Printers that report neither behave as before. - **A printer that refuses uploads no longer empties the print queue (#3210, reported by @Leander-Vh)** — When a printer's file service would not take a file, the queue marked the job failed. That left the printer idle, so the next pass gave it the next job, which failed the same way: one P2S whose file service answered `421 There are too many connections` failed 43 queued jobs in ten minutes. When the file never reached the printer (the connection was refused, timed out or dropped), the job now stays in the queue, and the printer gets no new jobs for five minutes. The wait doubles with each refusal in a row, up to an hour, and the first upload that gets through resets it. Jobs for any printer of that model go to the others, and a job assigned to that printer shows "*printer* is not accepting files — Bambuddy will retry automatically" until it can be sent. One Job Waiting notification goes out per outage, not one per retry, and **Keep bed warm between prints** does not heat a printer that is waiting this way. A rejected access code, a full SD card (including an FTP 452 reply, which used to be read as a network error) or an upload too slow to finish still fails the job, because a retry would fail the same way. diff --git a/static/assets/ImagePreviewModal-ertLn7DQ.js b/static/assets/ImagePreviewModal-DydMcOJ9.js similarity index 98% rename from static/assets/ImagePreviewModal-ertLn7DQ.js rename to static/assets/ImagePreviewModal-DydMcOJ9.js index 8422ab24b..9134d4a8b 100644 --- a/static/assets/ImagePreviewModal-ertLn7DQ.js +++ b/static/assets/ImagePreviewModal-DydMcOJ9.js @@ -1 +1 @@ -import{i as e}from"./chunk-aKtaBQYM.js";import{a as t,c as n,d as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as f,v as p,y as m}from"./index-DCzE7_R7.js";var h=e(m(),1),g=i(),_=.4,v=8,y=1.25,b=30,x=Math.log(y)/b,S=e=>Math.min(v,Math.max(_,e)),C={x:0,y:0};function w(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function T({libraryFileId:e,filename:i,fileSize:m,onClose:_}){let{t:v}=p(),T=d(),{isFullscreen:E,toggleFullscreen:D}=T,O=(0,h.useRef)(null),k=(0,h.useRef)(null),[A,j]=(0,h.useState)(null),[M,N]=(0,h.useState)(!1),[P,F]=(0,h.useState)(null),[I,L]=(0,h.useState)(1),[R,z]=(0,h.useState)(C),[B,V]=(0,h.useState)(!1),H=(0,h.useRef)(1),U=(0,h.useRef)(C),W=(0,h.useRef)(new Map),G=(0,h.useRef)(null),K=(0,h.useCallback)((e,t)=>{let n=O.current,r=k.current;if(!n||!r)return e;let i=Math.max(0,(r.offsetWidth*t-n.clientWidth)/2),a=Math.max(0,(r.offsetHeight*t-n.clientHeight)/2);return{x:Math.min(i,Math.max(-i,e.x)),y:Math.min(a,Math.max(-a,e.y))}},[]),q=(0,h.useCallback)((e,t)=>{H.current=e,U.current=t,L(e),z(t)},[]),J=(0,h.useCallback)((e,t,n)=>{let r=H.current,i=S(r*e);if(i===r)return;let a=O.current,o=U.current,s={x:o.x*(i/r),y:o.y*(i/r)};if(a){let e=a.getBoundingClientRect(),c=(t??e.left+e.width/2)-(e.left+e.width/2),l=(n??e.top+e.height/2)-(e.top+e.height/2),u=i/r;s={x:c-(c-o.x)*u,y:l-(l-o.y)*u}}q(i,K(s,i))},[K,q]),Y=(0,h.useCallback)(()=>q(1,C),[q]);(0,h.useEffect)(()=>{let n=!1,r=null;if(j(null),N(!1),F(null),q(1,C),m>52428800){F(v(`fileManager.preview.tooLarge`,{size:a(m)}));return}let i={},o=c();return o&&(i.Authorization=`Bearer ${o}`),(async()=>{let a=await fetch(t.getLibraryFileDownloadUrl(e),{headers:i});if(!a.ok)throw Error(`HTTP ${a.status}`);let o=await a.blob();n||(r=URL.createObjectURL(o),j(r))})().catch(e=>{console.error(`[image-preview] load failed`,e),n||F(v(`fileManager.preview.error`))}),()=>{n=!0,r&&URL.revokeObjectURL(r)}},[e,m,v,q]),(0,h.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),J(y)):e.key===`-`?(e.preventDefault(),J(1/y)):e.key===`0`&&(e.preventDefault(),Y()))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[Y,J]),(0,h.useEffect)(()=>{let e=O.current;if(!e)return;let t=e=>{e.preventDefault();let t=Math.max(-30,Math.min(b,w(e)));J(Math.exp(-t*x),e.clientX,e.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[J,A]);let X=e=>{W.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),G.current=null,W.current.size===1&&H.current>1&&(e.currentTarget.setPointerCapture(e.pointerId),V(!0))},Z=e=>{W.current.delete(e.pointerId),G.current=null,W.current.size===0&&V(!1)},Q=e=>{let t=W.current,n=t.get(e.pointerId);if(!n)return;if(e.pointerType===`mouse`&&e.buttons===0){Z(e);return}if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){if(H.current<=1)return;let t={x:U.current.x+(e.clientX-n.x),y:U.current.y+(e.clientY-n.y)};q(H.current,K(t,H.current));return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=G.current;G.current=a,o!=null&&o>0&&a>0&&J(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},$=I>1;return(0,g.jsx)(s,{title:i,fullscreen:T,onClose:_,icon:(0,g.jsx)(r,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:A?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`button`,{onClick:()=>J(1/y),className:l,"aria-label":v(`fileManager.preview.zoomOut`),children:(0,g.jsx)(u,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:()=>J(y),className:l,"aria-label":v(`fileManager.preview.zoomIn`),children:(0,g.jsx)(n,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:Y,className:l,"aria-label":v(`fileManager.preview.resetZoom`),children:(0,g.jsx)(o,{className:`w-4 h-4`})})]}):void 0,children:(0,g.jsx)(`div`,{ref:O,"data-testid":`image-preview-content`,className:`relative flex-1 min-h-0 overflow-hidden bg-bambu-dark flex items-center justify-center touch-none ${E?``:`rounded-b-lg`} ${$?B?`cursor-grabbing`:`cursor-grab`:``}`,onDoubleClick:D,onPointerDown:X,onPointerMove:Q,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:P?(0,g.jsx)(`p`,{className:`text-bambu-gray text-center p-6`,children:P}):(0,g.jsxs)(g.Fragment,{children:[A&&(0,g.jsx)(`img`,{ref:k,src:A,alt:i,draggable:!1,onLoad:()=>N(!0),onError:()=>F(v(`fileManager.preview.error`)),style:{transform:`translate(${R.x}px, ${R.y}px) scale(${I})`},className:`max-w-full max-h-full object-contain select-none`}),!M&&(0,g.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,g.jsx)(f,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})})}export{T as ImagePreviewModal}; \ No newline at end of file +import{i as e}from"./chunk-aKtaBQYM.js";import{a as t,c as n,d as r,g as i,i as a,l as o,n as s,o as c,r as l,s as u,t as d,u as f,v as p,y as m}from"./index-DXO3amin.js";var h=e(m(),1),g=i(),_=.4,v=8,y=1.25,b=30,x=Math.log(y)/b,S=e=>Math.min(v,Math.max(_,e)),C={x:0,y:0};function w(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function T({libraryFileId:e,filename:i,fileSize:m,onClose:_}){let{t:v}=p(),T=d(),{isFullscreen:E,toggleFullscreen:D}=T,O=(0,h.useRef)(null),k=(0,h.useRef)(null),[A,j]=(0,h.useState)(null),[M,N]=(0,h.useState)(!1),[P,F]=(0,h.useState)(null),[I,L]=(0,h.useState)(1),[R,z]=(0,h.useState)(C),[B,V]=(0,h.useState)(!1),H=(0,h.useRef)(1),U=(0,h.useRef)(C),W=(0,h.useRef)(new Map),G=(0,h.useRef)(null),K=(0,h.useCallback)((e,t)=>{let n=O.current,r=k.current;if(!n||!r)return e;let i=Math.max(0,(r.offsetWidth*t-n.clientWidth)/2),a=Math.max(0,(r.offsetHeight*t-n.clientHeight)/2);return{x:Math.min(i,Math.max(-i,e.x)),y:Math.min(a,Math.max(-a,e.y))}},[]),q=(0,h.useCallback)((e,t)=>{H.current=e,U.current=t,L(e),z(t)},[]),J=(0,h.useCallback)((e,t,n)=>{let r=H.current,i=S(r*e);if(i===r)return;let a=O.current,o=U.current,s={x:o.x*(i/r),y:o.y*(i/r)};if(a){let e=a.getBoundingClientRect(),c=(t??e.left+e.width/2)-(e.left+e.width/2),l=(n??e.top+e.height/2)-(e.top+e.height/2),u=i/r;s={x:c-(c-o.x)*u,y:l-(l-o.y)*u}}q(i,K(s,i))},[K,q]),Y=(0,h.useCallback)(()=>q(1,C),[q]);(0,h.useEffect)(()=>{let n=!1,r=null;if(j(null),N(!1),F(null),q(1,C),m>52428800){F(v(`fileManager.preview.tooLarge`,{size:a(m)}));return}let i={},o=c();return o&&(i.Authorization=`Bearer ${o}`),(async()=>{let a=await fetch(t.getLibraryFileDownloadUrl(e),{headers:i});if(!a.ok)throw Error(`HTTP ${a.status}`);let o=await a.blob();n||(r=URL.createObjectURL(o),j(r))})().catch(e=>{console.error(`[image-preview] load failed`,e),n||F(v(`fileManager.preview.error`))}),()=>{n=!0,r&&URL.revokeObjectURL(r)}},[e,m,v,q]),(0,h.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),J(y)):e.key===`-`?(e.preventDefault(),J(1/y)):e.key===`0`&&(e.preventDefault(),Y()))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[Y,J]),(0,h.useEffect)(()=>{let e=O.current;if(!e)return;let t=e=>{e.preventDefault();let t=Math.max(-30,Math.min(b,w(e)));J(Math.exp(-t*x),e.clientX,e.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[J,A]);let X=e=>{W.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),G.current=null,W.current.size===1&&H.current>1&&(e.currentTarget.setPointerCapture(e.pointerId),V(!0))},Z=e=>{W.current.delete(e.pointerId),G.current=null,W.current.size===0&&V(!1)},Q=e=>{let t=W.current,n=t.get(e.pointerId);if(!n)return;if(e.pointerType===`mouse`&&e.buttons===0){Z(e);return}if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){if(H.current<=1)return;let t={x:U.current.x+(e.clientX-n.x),y:U.current.y+(e.clientY-n.y)};q(H.current,K(t,H.current));return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=G.current;G.current=a,o!=null&&o>0&&a>0&&J(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},$=I>1;return(0,g.jsx)(s,{title:i,fullscreen:T,onClose:_,icon:(0,g.jsx)(r,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:A?(0,g.jsxs)(g.Fragment,{children:[(0,g.jsx)(`button`,{onClick:()=>J(1/y),className:l,"aria-label":v(`fileManager.preview.zoomOut`),children:(0,g.jsx)(u,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:()=>J(y),className:l,"aria-label":v(`fileManager.preview.zoomIn`),children:(0,g.jsx)(n,{className:`w-4 h-4`})}),(0,g.jsx)(`button`,{onClick:Y,className:l,"aria-label":v(`fileManager.preview.resetZoom`),children:(0,g.jsx)(o,{className:`w-4 h-4`})})]}):void 0,children:(0,g.jsx)(`div`,{ref:O,"data-testid":`image-preview-content`,className:`relative flex-1 min-h-0 overflow-hidden bg-bambu-dark flex items-center justify-center touch-none ${E?``:`rounded-b-lg`} ${$?B?`cursor-grabbing`:`cursor-grab`:``}`,onDoubleClick:D,onPointerDown:X,onPointerMove:Q,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:P?(0,g.jsx)(`p`,{className:`text-bambu-gray text-center p-6`,children:P}):(0,g.jsxs)(g.Fragment,{children:[A&&(0,g.jsx)(`img`,{ref:k,src:A,alt:i,draggable:!1,onLoad:()=>N(!0),onError:()=>F(v(`fileManager.preview.error`)),style:{transform:`translate(${R.x}px, ${R.y}px) scale(${I})`},className:`max-w-full max-h-full object-contain select-none`}),!M&&(0,g.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,g.jsx)(f,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})})}export{T as ImagePreviewModal}; \ No newline at end of file diff --git a/static/assets/PdfPreviewModal-DP4k6p_n.js b/static/assets/PdfPreviewModal-DXTd9gb8.js similarity index 97% rename from static/assets/PdfPreviewModal-DP4k6p_n.js rename to static/assets/PdfPreviewModal-DXTd9gb8.js index d2a5a8f74..ed73486a9 100644 --- a/static/assets/PdfPreviewModal-DP4k6p_n.js +++ b/static/assets/PdfPreviewModal-DXTd9gb8.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/pdf-BptzBvjW.js","assets/index-DCzE7_R7.js","assets/chunk-aKtaBQYM.js","assets/index-CyW_3l56.css"])))=>i.map(i=>d[i]); -import{i as e}from"./chunk-aKtaBQYM.js";import{_ as t,a as n,c as r,f as i,g as a,h as o,i as s,m as c,n as l,o as u,r as d,s as ee,t as te,u as ne,v as re,y as f}from"./index-DCzE7_R7.js";var p=e(f(),1),m=a(),h=.4,g=4,_=1.25,v=30,ie=Math.log(_)/v,ae=150,y=16*1024*1024,b=`/assets/pdfjs/`,oe=e=>Math.min(g,Math.max(h,e));function x(e){let t=document.createElement(`canvas`);t.width=256,t.height=256;let n=t.getContext(`2d`);if(!n)return Promise.resolve(null);n.fillStyle=`#ffffff`,n.fillRect(0,0,256,256);let r=256/Math.max(e.width,e.height),i=e.width*r,a=e.height*r;return n.drawImage(e,(256-i)/2,(256-a)/2,i,a),new Promise(e=>t.toBlob(e,`image/png`))}function S(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function C({libraryFileId:e,filename:a,fileSize:f,onClose:h,onSnapshot:g}){let{t:C}=re(),w=te(),{panelRef:T,isFullscreen:E,toggleFullscreen:D}=w,O=(0,p.useRef)(null),k=(0,p.useRef)(null),A=(0,p.useRef)(null),j=(0,p.useRef)(!1),M=(0,p.useRef)(g);(0,p.useEffect)(()=>{M.current=g});let[N,P]=(0,p.useState)(null),[F,I]=(0,p.useState)(1),[L,R]=(0,p.useState)(1),[z,se]=(0,p.useState)(1),[B,ce]=(0,p.useState)(0),[V,H]=(0,p.useState)(null),[U,W]=(0,p.useState)(!0),G=(0,p.useRef)(L),K=(0,p.useRef)(null),q=(0,p.useRef)(null),J=(0,p.useRef)(new Map),Y=(0,p.useRef)(null),X=(0,p.useCallback)((e,t,n)=>{let r=O.current,i=k.current;if(r&&i){let e=r.getBoundingClientRect(),a=i.getBoundingClientRect(),o=t==null?e.width/2:t-e.left,s=n==null?e.height/2:n-e.top;q.current=a.width>0&&a.height>0?{fx:(o+e.left-a.left)/a.width,fy:(s+e.top-a.top)/a.height,px:o,py:s}:null}R(t=>oe(t*e))},[]);(0,p.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),X(_)):e.key===`-`?(e.preventDefault(),X(1/_)):e.key===`0`&&(e.preventDefault(),q.current=null,R(1)))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[X]),(0,p.useEffect)(()=>{let e=O.current;if(!e)return;let t=t=>{if(!k.current||!K.current||!(t.ctrlKey||t.metaKey)&&!(e.scrollHeight<=e.clientHeight&&e.scrollWidth<=e.clientWidth))return;t.preventDefault();let n=Math.max(-30,Math.min(v,S(t)));X(Math.exp(-n*ie),t.clientX,t.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[X]);let le=e=>{e.pointerType===`touch`&&(J.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),Y.current=null)},ue=e=>{if(e.pointerType!==`touch`)return;let t=J.current,n=t.get(e.pointerId);if(!n)return;if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){let t=O.current;t&&(t.scrollLeft-=e.clientX-n.x,t.scrollTop-=e.clientY-n.y);return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=Y.current;Y.current=a,o!=null&&o>0&&a>0&&X(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},Z=e=>{e.pointerType===`touch`&&(J.current.delete(e.pointerId),Y.current=null)};(0,p.useLayoutEffect)(()=>{G.current=L;let e=k.current,t=O.current,n=K.current;if(!e||!t||!n)return;e.style.width=`${n.width*L}px`,e.style.height=`${n.height*L}px`;let r=q.current;q.current=null,r&&(t.scrollLeft=e.offsetLeft+r.fx*e.offsetWidth-r.px,t.scrollTop=e.offsetTop+r.fy*e.offsetHeight-r.py)},[L]),(0,p.useEffect)(()=>{if(L===z)return;let e=window.setTimeout(()=>se(L),ae);return()=>window.clearTimeout(e)},[L,z]),(0,p.useEffect)(()=>{let e=T.current;if(!e)return;let t=new ResizeObserver(()=>ce(e=>e+1));return t.observe(e),()=>t.disconnect()},[T]),(0,p.useEffect)(()=>{let r=!1,i=null;if(P(null),H(null),I(1),W(!0),f>52428800){H(C(`fileManager.preview.tooLarge`,{size:s(f)})),W(!1);return}let a={},o=u();return o&&(a.Authorization=`Bearer ${o}`),(async()=>{let o=await fetch(n.getLibraryFileDownloadUrl(e),{headers:a});if(!o.ok)throw Error(`HTTP ${o.status}`);let s=await o.arrayBuffer(),c=await t(()=>import(`./pdf-BptzBvjW.js`),__vite__mapDeps([0,1,2,3]));if(c.GlobalWorkerOptions.workerSrc||(c.GlobalWorkerOptions.workerSrc=(await t(async()=>{let{default:e}=await import(`./pdf.worker.min-DpOxPwGP.js`);return{default:e}},[])).default),i=c.getDocument({data:new Uint8Array(s),cMapUrl:`${b}cmaps/`,iccUrl:`${b}iccs/`,standardFontDataUrl:`${b}standard_fonts/`,wasmUrl:`${b}wasm/`}),r){i.destroy();return}let l=await i.promise;r||P(l)})().catch(e=>{console.error(`[pdf-preview] load failed`,e),r||(H(C(`fileManager.preview.error`)),W(!1))}),()=>{r=!0,A.current?.cancel(),A.current=null,i?.destroy()}},[e,f,C]),(0,p.useEffect)(()=>{if(!N)return;let e=!1;return W(!0),(async()=>{let t=await N.getPage(F),n=k.current,r=O.current;if(!n||!r||e)return;let i=t.getViewport({scale:1}),a=Math.max((r.clientWidth-32)/i.width,.1),o=Math.min(window.devicePixelRatio||1,2),s=a*z*o,c=i.width*s*(i.height*s),l=c>y?s*Math.sqrt(y/c):s,u=t.getViewport({scale:l});n.width=u.width,n.height=u.height,K.current={width:i.width*a,height:i.height*a},n.style.width=`${K.current.width*G.current}px`,n.style.height=`${K.current.height*G.current}px`,A.current?.cancel();let d=t.render({canvas:n,viewport:u});if(A.current=d,await d.promise,!e&&(W(!1),F===1&&M.current&&!j.current)){j.current=!0;let t=await x(n);t&&!e&&M.current?.(t)}})().catch(t=>{!e&&t?.name!==`RenderingCancelledException`&&(console.error(`[pdf-preview] render failed`,t),H(C(`fileManager.preview.error`)),W(!1))}),()=>{e=!0}},[N,F,z,B,C]);let Q=N?.numPages??0,$=d;return(0,m.jsx)(l,{title:a,fullscreen:w,onClose:h,icon:(0,m.jsx)(i,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:(0,m.jsxs)(m.Fragment,{children:[N&&Q>1&&(0,m.jsxs)(`div`,{className:`flex items-center gap-1 mr-2`,children:[(0,m.jsx)(`button`,{onClick:()=>I(e=>Math.max(1,e-1)),disabled:F<=1,className:`${$} disabled:opacity-40 disabled:cursor-not-allowed`,"aria-label":C(`fileManager.preview.prevPage`),children:(0,m.jsx)(c,{className:`w-4 h-4`})}),(0,m.jsx)(`span`,{className:`text-sm text-bambu-gray whitespace-nowrap`,children:C(`fileManager.preview.page`,{current:F,total:Q})}),(0,m.jsx)(`button`,{onClick:()=>I(e=>Math.min(Q,e+1)),disabled:F>=Q,className:`${$} disabled:opacity-40 disabled:cursor-not-allowed`,"aria-label":C(`fileManager.preview.nextPage`),children:(0,m.jsx)(o,{className:`w-4 h-4`})})]}),N&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`button`,{onClick:()=>X(1/_),className:$,"aria-label":C(`fileManager.preview.zoomOut`),children:(0,m.jsx)(ee,{className:`w-4 h-4`})}),(0,m.jsx)(`button`,{onClick:()=>X(_),className:$,"aria-label":C(`fileManager.preview.zoomIn`),children:(0,m.jsx)(r,{className:`w-4 h-4`})})]})]}),children:(0,m.jsxs)(`div`,{ref:O,"data-testid":`pdf-preview-page`,className:`relative flex-1 min-h-0 overflow-auto bg-bambu-dark p-4 touch-none ${E?``:`rounded-b-lg`}`,onDoubleClick:D,onPointerDown:le,onPointerMove:ue,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:[V?(0,m.jsx)(`div`,{className:`h-full flex items-center justify-center`,children:(0,m.jsx)(`p`,{className:`text-bambu-gray text-center`,children:V})}):(0,m.jsx)(`div`,{className:`flex justify-center min-w-fit`,children:(0,m.jsx)(`canvas`,{ref:k,className:`shadow-lg`})}),!V&&U&&(0,m.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,m.jsx)(ne,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})}export{C as PdfPreviewModal}; \ No newline at end of file +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/pdf-BeRViiAS.js","assets/index-DXO3amin.js","assets/chunk-aKtaBQYM.js","assets/index-CyW_3l56.css"])))=>i.map(i=>d[i]); +import{i as e}from"./chunk-aKtaBQYM.js";import{_ as t,a as n,c as r,f as i,g as a,h as o,i as s,m as c,n as l,o as u,r as d,s as ee,t as te,u as ne,v as re,y as f}from"./index-DXO3amin.js";var p=e(f(),1),m=a(),h=.4,g=4,_=1.25,v=30,ie=Math.log(_)/v,ae=150,y=16*1024*1024,b=`/assets/pdfjs/`,oe=e=>Math.min(g,Math.max(h,e));function x(e){let t=document.createElement(`canvas`);t.width=256,t.height=256;let n=t.getContext(`2d`);if(!n)return Promise.resolve(null);n.fillStyle=`#ffffff`,n.fillRect(0,0,256,256);let r=256/Math.max(e.width,e.height),i=e.width*r,a=e.height*r;return n.drawImage(e,(256-i)/2,(256-a)/2,i,a),new Promise(e=>t.toBlob(e,`image/png`))}function S(e){return e.deltaMode===1?e.deltaY*16:e.deltaMode===2?e.deltaY*100:e.deltaY}function C({libraryFileId:e,filename:a,fileSize:f,onClose:h,onSnapshot:g}){let{t:C}=re(),w=te(),{panelRef:T,isFullscreen:E,toggleFullscreen:D}=w,O=(0,p.useRef)(null),k=(0,p.useRef)(null),A=(0,p.useRef)(null),j=(0,p.useRef)(!1),M=(0,p.useRef)(g);(0,p.useEffect)(()=>{M.current=g});let[N,P]=(0,p.useState)(null),[F,I]=(0,p.useState)(1),[L,R]=(0,p.useState)(1),[z,se]=(0,p.useState)(1),[B,ce]=(0,p.useState)(0),[V,H]=(0,p.useState)(null),[U,W]=(0,p.useState)(!0),G=(0,p.useRef)(L),K=(0,p.useRef)(null),q=(0,p.useRef)(null),J=(0,p.useRef)(new Map),Y=(0,p.useRef)(null),X=(0,p.useCallback)((e,t,n)=>{let r=O.current,i=k.current;if(r&&i){let e=r.getBoundingClientRect(),a=i.getBoundingClientRect(),o=t==null?e.width/2:t-e.left,s=n==null?e.height/2:n-e.top;q.current=a.width>0&&a.height>0?{fx:(o+e.left-a.left)/a.width,fy:(s+e.top-a.top)/a.height,px:o,py:s}:null}R(t=>oe(t*e))},[]);(0,p.useEffect)(()=>{let e=e=>{e.ctrlKey||e.metaKey||e.altKey||(e.key===`+`||e.key===`=`?(e.preventDefault(),X(_)):e.key===`-`?(e.preventDefault(),X(1/_)):e.key===`0`&&(e.preventDefault(),q.current=null,R(1)))};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[X]),(0,p.useEffect)(()=>{let e=O.current;if(!e)return;let t=t=>{if(!k.current||!K.current||!(t.ctrlKey||t.metaKey)&&!(e.scrollHeight<=e.clientHeight&&e.scrollWidth<=e.clientWidth))return;t.preventDefault();let n=Math.max(-30,Math.min(v,S(t)));X(Math.exp(-n*ie),t.clientX,t.clientY)};return e.addEventListener(`wheel`,t,{passive:!1}),()=>e.removeEventListener(`wheel`,t)},[X]);let le=e=>{e.pointerType===`touch`&&(J.current.set(e.pointerId,{x:e.clientX,y:e.clientY}),Y.current=null)},ue=e=>{if(e.pointerType!==`touch`)return;let t=J.current,n=t.get(e.pointerId);if(!n)return;if(t.set(e.pointerId,{x:e.clientX,y:e.clientY}),t.size===1){let t=O.current;t&&(t.scrollLeft-=e.clientX-n.x,t.scrollTop-=e.clientY-n.y);return}if(t.size!==2)return;let[r,i]=Array.from(t.values()),a=Math.hypot(r.x-i.x,r.y-i.y),o=Y.current;Y.current=a,o!=null&&o>0&&a>0&&X(a/o,(r.x+i.x)/2,(r.y+i.y)/2)},Z=e=>{e.pointerType===`touch`&&(J.current.delete(e.pointerId),Y.current=null)};(0,p.useLayoutEffect)(()=>{G.current=L;let e=k.current,t=O.current,n=K.current;if(!e||!t||!n)return;e.style.width=`${n.width*L}px`,e.style.height=`${n.height*L}px`;let r=q.current;q.current=null,r&&(t.scrollLeft=e.offsetLeft+r.fx*e.offsetWidth-r.px,t.scrollTop=e.offsetTop+r.fy*e.offsetHeight-r.py)},[L]),(0,p.useEffect)(()=>{if(L===z)return;let e=window.setTimeout(()=>se(L),ae);return()=>window.clearTimeout(e)},[L,z]),(0,p.useEffect)(()=>{let e=T.current;if(!e)return;let t=new ResizeObserver(()=>ce(e=>e+1));return t.observe(e),()=>t.disconnect()},[T]),(0,p.useEffect)(()=>{let r=!1,i=null;if(P(null),H(null),I(1),W(!0),f>52428800){H(C(`fileManager.preview.tooLarge`,{size:s(f)})),W(!1);return}let a={},o=u();return o&&(a.Authorization=`Bearer ${o}`),(async()=>{let o=await fetch(n.getLibraryFileDownloadUrl(e),{headers:a});if(!o.ok)throw Error(`HTTP ${o.status}`);let s=await o.arrayBuffer(),c=await t(()=>import(`./pdf-BeRViiAS.js`),__vite__mapDeps([0,1,2,3]));if(c.GlobalWorkerOptions.workerSrc||(c.GlobalWorkerOptions.workerSrc=(await t(async()=>{let{default:e}=await import(`./pdf.worker.min-DpOxPwGP.js`);return{default:e}},[])).default),i=c.getDocument({data:new Uint8Array(s),cMapUrl:`${b}cmaps/`,iccUrl:`${b}iccs/`,standardFontDataUrl:`${b}standard_fonts/`,wasmUrl:`${b}wasm/`}),r){i.destroy();return}let l=await i.promise;r||P(l)})().catch(e=>{console.error(`[pdf-preview] load failed`,e),r||(H(C(`fileManager.preview.error`)),W(!1))}),()=>{r=!0,A.current?.cancel(),A.current=null,i?.destroy()}},[e,f,C]),(0,p.useEffect)(()=>{if(!N)return;let e=!1;return W(!0),(async()=>{let t=await N.getPage(F),n=k.current,r=O.current;if(!n||!r||e)return;let i=t.getViewport({scale:1}),a=Math.max((r.clientWidth-32)/i.width,.1),o=Math.min(window.devicePixelRatio||1,2),s=a*z*o,c=i.width*s*(i.height*s),l=c>y?s*Math.sqrt(y/c):s,u=t.getViewport({scale:l});n.width=u.width,n.height=u.height,K.current={width:i.width*a,height:i.height*a},n.style.width=`${K.current.width*G.current}px`,n.style.height=`${K.current.height*G.current}px`,A.current?.cancel();let d=t.render({canvas:n,viewport:u});if(A.current=d,await d.promise,!e&&(W(!1),F===1&&M.current&&!j.current)){j.current=!0;let t=await x(n);t&&!e&&M.current?.(t)}})().catch(t=>{!e&&t?.name!==`RenderingCancelledException`&&(console.error(`[pdf-preview] render failed`,t),H(C(`fileManager.preview.error`)),W(!1))}),()=>{e=!0}},[N,F,z,B,C]);let Q=N?.numPages??0,$=d;return(0,m.jsx)(l,{title:a,fullscreen:w,onClose:h,icon:(0,m.jsx)(i,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),actions:(0,m.jsxs)(m.Fragment,{children:[N&&Q>1&&(0,m.jsxs)(`div`,{className:`flex items-center gap-1 mr-2`,children:[(0,m.jsx)(`button`,{onClick:()=>I(e=>Math.max(1,e-1)),disabled:F<=1,className:`${$} disabled:opacity-40 disabled:cursor-not-allowed`,"aria-label":C(`fileManager.preview.prevPage`),children:(0,m.jsx)(c,{className:`w-4 h-4`})}),(0,m.jsx)(`span`,{className:`text-sm text-bambu-gray whitespace-nowrap`,children:C(`fileManager.preview.page`,{current:F,total:Q})}),(0,m.jsx)(`button`,{onClick:()=>I(e=>Math.min(Q,e+1)),disabled:F>=Q,className:`${$} disabled:opacity-40 disabled:cursor-not-allowed`,"aria-label":C(`fileManager.preview.nextPage`),children:(0,m.jsx)(o,{className:`w-4 h-4`})})]}),N&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`button`,{onClick:()=>X(1/_),className:$,"aria-label":C(`fileManager.preview.zoomOut`),children:(0,m.jsx)(ee,{className:`w-4 h-4`})}),(0,m.jsx)(`button`,{onClick:()=>X(_),className:$,"aria-label":C(`fileManager.preview.zoomIn`),children:(0,m.jsx)(r,{className:`w-4 h-4`})})]})]}),children:(0,m.jsxs)(`div`,{ref:O,"data-testid":`pdf-preview-page`,className:`relative flex-1 min-h-0 overflow-auto bg-bambu-dark p-4 touch-none ${E?``:`rounded-b-lg`}`,onDoubleClick:D,onPointerDown:le,onPointerMove:ue,onPointerUp:Z,onPointerCancel:Z,onPointerLeave:Z,children:[V?(0,m.jsx)(`div`,{className:`h-full flex items-center justify-center`,children:(0,m.jsx)(`p`,{className:`text-bambu-gray text-center`,children:V})}):(0,m.jsx)(`div`,{className:`flex justify-center min-w-fit`,children:(0,m.jsx)(`canvas`,{ref:k,className:`shadow-lg`})}),!V&&U&&(0,m.jsx)(`div`,{className:`absolute inset-0 flex items-center justify-center pointer-events-none`,children:(0,m.jsx)(ne,{className:`w-8 h-8 text-bambu-green animate-spin`})})]})})}export{C as PdfPreviewModal}; \ No newline at end of file diff --git a/static/assets/SpreadsheetPreviewModal-oZ5sN9G4.js b/static/assets/SpreadsheetPreviewModal-DKtg1R2F.js similarity index 98% rename from static/assets/SpreadsheetPreviewModal-oZ5sN9G4.js rename to static/assets/SpreadsheetPreviewModal-DKtg1R2F.js index 4fb718ae1..bd65c45f3 100644 --- a/static/assets/SpreadsheetPreviewModal-oZ5sN9G4.js +++ b/static/assets/SpreadsheetPreviewModal-DKtg1R2F.js @@ -1,2 +1,2 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/papaparse.min-CR43Mv99.js","assets/chunk-aKtaBQYM.js"])))=>i.map(i=>d[i]); -import{i as e}from"./chunk-aKtaBQYM.js";import{_ as t,a as n,g as r,i,n as a,o,p as s,t as c,u as l,v as u,y as d}from"./index-DCzE7_R7.js";var f=e(d(),1),p=r(),m=500,h=40;function g(e){let t=``,n=e;for(;n>=0;)t=String.fromCharCode(65+n%26)+t,n=Math.floor(n/26)-1;return t}function _(e){let t=document.createElement(`canvas`);t.width=256,t.height=256;let n=t.getContext(`2d`);if(!n)return Promise.resolve(null);let r=256/5,i=256/9;n.fillStyle=`#1a1a1a`,n.fillRect(0,0,256,256),n.fillStyle=`rgba(0, 174, 66, 0.25)`,n.fillRect(0,0,256,i),n.strokeStyle=`#333333`,n.lineWidth=1;for(let e=1;e<5;e++)n.beginPath(),n.moveTo(e*r+.5,0),n.lineTo(e*r+.5,256),n.stroke();for(let e=1;e<9;e++)n.beginPath(),n.moveTo(0,e*i+.5),n.lineTo(256,e*i+.5),n.stroke();n.fillStyle=`#d4d4d4`,n.font=`11px sans-serif`,n.textBaseline=`middle`;for(let t=0;t<9;t++)for(let a=0;a<5;a++){let o=e[t]?.[a];o&&n.fillText(String(o),a*r+4,t*i+i/2,r-8)}return new Promise(e=>t.toBlob(e,`image/png`))}function v({libraryFileId:r,filename:d,fileType:v,fileSize:y,onClose:b,onSnapshot:x}){let{t:S}=u(),[C,w]=(0,f.useState)(null),[T,E]=(0,f.useState)(0),[D,O]=(0,f.useState)(null),k=(0,f.useRef)(!1),A=(0,f.useRef)(x);(0,f.useEffect)(()=>{A.current=x});let j=c(),{isFullscreen:M,toggleFullscreen:N}=j;(0,f.useEffect)(()=>{let a=!1;if(w(null),O(null),E(0),y>20971520){O(S(`fileManager.preview.tooLarge`,{size:i(y)}));return}let s={},c=o();return c&&(s.Authorization=`Bearer ${c}`),(async()=>{let i=await fetch(n.getLibraryFileDownloadUrl(r),{headers:s});if(!i.ok)throw Error(`HTTP ${i.status}`);let o=await i.arrayBuffer(),c;if(v===`csv`){let n=(await t(async()=>{let{default:t}=await import(`./papaparse.min-CR43Mv99.js`).then(t=>e(t.default,1));return{default:t}},__vite__mapDeps([0,1]))).default,r=new TextDecoder().decode(o),i=[],a=0,s=0,l=[],u=e=>{a+=1,s=Math.max(s,e.length),i.lengthe??``))};n.parse(r,{skipEmptyLines:!1,step:({data:e})=>{if(Array.isArray(e)){if(e.every(e=>!e)){l.push(e);return}l.forEach(u),l=[],u(e)}}}),c=[{name:d,rows:i,totalRows:a,totalCols:s}]}else{let e=await t(()=>import(`./xlsx-CKkngM-o.js`),[]),n=e.read(o,{dense:!0});c=n.SheetNames.map(t=>{let r=n.Sheets[t],i=r?.[`!ref`];if(!r||!i)return{name:t,rows:[],totalRows:0,totalCols:0};let a=e.utils.decode_range(i),o=a.e.r-a.s.r+1,s=a.e.c-a.s.c+1,c={s:a.s,e:{r:Math.min(a.e.r,a.s.r+m-1),c:Math.min(a.e.c,a.s.c+h-1)}};return{name:t,rows:e.utils.sheet_to_json(r,{header:1,raw:!1,defval:``,range:e.utils.encode_range(c)}),totalRows:o,totalCols:s}})}if(!a&&(w(c),A.current&&!k.current)){let e=c.find(e=>e.rows.length>0);if(e){k.current=!0;let t=await _(e.rows);t&&!a&&A.current?.(t)}}})().catch(e=>{console.error(`[sheet-preview] load failed`,e),a||O(S(`fileManager.preview.error`))}),()=>{a=!0}},[r,v,y,d,S]);let P=C?.[T]??null,F=P!=null&&P.totalRows>P.rows.length,I=P!=null&&P.totalCols>h,L=P==null?0:Math.min(P.totalCols,h);return(0,p.jsxs)(a,{title:d,fullscreen:j,onClose:b,icon:(0,p.jsx)(s,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),children:[C&&C.length>1&&(0,p.jsx)(`div`,{className:`flex gap-1 px-4 pt-2 overflow-x-auto flex-shrink-0`,children:C.map((e,t)=>(0,p.jsx)(`button`,{onClick:()=>E(t),className:`px-3 py-1.5 text-sm rounded-t whitespace-nowrap transition-colors ${t===T?`bg-bambu-dark text-white border border-b-0 border-bambu-dark-tertiary`:`text-bambu-gray hover:text-white hover:bg-bambu-dark/50`}`,children:e.name},`${e.name}-${t}`))}),(0,p.jsx)(`div`,{"data-testid":`spreadsheet-preview-content`,className:`flex-1 min-h-0 overflow-auto bg-bambu-dark ${M?``:`rounded-b-lg`}`,onDoubleClick:N,children:D?(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center p-6`,children:(0,p.jsx)(`p`,{className:`text-bambu-gray text-center`,children:D})}):C?!P||P.rows.length===0?(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center p-6`,children:(0,p.jsx)(`p`,{className:`text-bambu-gray`,children:S(`fileManager.preview.emptySheet`)})}):(0,p.jsxs)(`table`,{className:`border-collapse text-xs min-w-full`,children:[(0,p.jsx)(`thead`,{children:(0,p.jsxs)(`tr`,{children:[(0,p.jsx)(`th`,{className:`sticky top-0 bg-bambu-dark-secondary border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray font-medium w-10`}),Array.from({length:L},(e,t)=>(0,p.jsx)(`th`,{className:`sticky top-0 bg-bambu-dark-secondary border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray font-medium text-left min-w-[80px]`,children:g(t)},t))]})}),(0,p.jsx)(`tbody`,{children:P.rows.map((e,t)=>(0,p.jsxs)(`tr`,{children:[(0,p.jsx)(`td`,{className:`border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray text-right bg-bambu-dark-secondary/50`,children:t+1}),Array.from({length:L},(t,n)=>(0,p.jsx)(`td`,{className:`border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray-light whitespace-nowrap max-w-[280px] overflow-hidden text-ellipsis`,title:e[n]||void 0,children:e[n]??``},n))]},t))})]}):(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center`,children:(0,p.jsx)(l,{className:`w-8 h-8 text-bambu-green animate-spin`})})}),(F||I)&&P&&(0,p.jsxs)(`div`,{className:`px-4 py-2 border-t border-bambu-dark-tertiary text-xs text-bambu-gray flex gap-4 flex-shrink-0`,children:[F&&(0,p.jsx)(`span`,{children:S(`fileManager.preview.truncatedRows`,{shown:P.rows.length,total:P.totalRows})}),I&&(0,p.jsx)(`span`,{children:S(`fileManager.preview.truncatedCols`,{shown:L,total:P.totalCols})})]})]})}export{v as SpreadsheetPreviewModal}; \ No newline at end of file +import{i as e}from"./chunk-aKtaBQYM.js";import{_ as t,a as n,g as r,i,n as a,o,p as s,t as c,u as l,v as u,y as d}from"./index-DXO3amin.js";var f=e(d(),1),p=r(),m=500,h=40;function g(e){let t=``,n=e;for(;n>=0;)t=String.fromCharCode(65+n%26)+t,n=Math.floor(n/26)-1;return t}function _(e){let t=document.createElement(`canvas`);t.width=256,t.height=256;let n=t.getContext(`2d`);if(!n)return Promise.resolve(null);let r=256/5,i=256/9;n.fillStyle=`#1a1a1a`,n.fillRect(0,0,256,256),n.fillStyle=`rgba(0, 174, 66, 0.25)`,n.fillRect(0,0,256,i),n.strokeStyle=`#333333`,n.lineWidth=1;for(let e=1;e<5;e++)n.beginPath(),n.moveTo(e*r+.5,0),n.lineTo(e*r+.5,256),n.stroke();for(let e=1;e<9;e++)n.beginPath(),n.moveTo(0,e*i+.5),n.lineTo(256,e*i+.5),n.stroke();n.fillStyle=`#d4d4d4`,n.font=`11px sans-serif`,n.textBaseline=`middle`;for(let t=0;t<9;t++)for(let a=0;a<5;a++){let o=e[t]?.[a];o&&n.fillText(String(o),a*r+4,t*i+i/2,r-8)}return new Promise(e=>t.toBlob(e,`image/png`))}function v({libraryFileId:r,filename:d,fileType:v,fileSize:y,onClose:b,onSnapshot:x}){let{t:S}=u(),[C,w]=(0,f.useState)(null),[T,E]=(0,f.useState)(0),[D,O]=(0,f.useState)(null),k=(0,f.useRef)(!1),A=(0,f.useRef)(x);(0,f.useEffect)(()=>{A.current=x});let j=c(),{isFullscreen:M,toggleFullscreen:N}=j;(0,f.useEffect)(()=>{let a=!1;if(w(null),O(null),E(0),y>20971520){O(S(`fileManager.preview.tooLarge`,{size:i(y)}));return}let s={},c=o();return c&&(s.Authorization=`Bearer ${c}`),(async()=>{let i=await fetch(n.getLibraryFileDownloadUrl(r),{headers:s});if(!i.ok)throw Error(`HTTP ${i.status}`);let o=await i.arrayBuffer(),c;if(v===`csv`){let n=(await t(async()=>{let{default:t}=await import(`./papaparse.min-CR43Mv99.js`).then(t=>e(t.default,1));return{default:t}},__vite__mapDeps([0,1]))).default,r=new TextDecoder().decode(o),i=[],a=0,s=0,l=[],u=e=>{a+=1,s=Math.max(s,e.length),i.lengthe??``))};n.parse(r,{skipEmptyLines:!1,step:({data:e})=>{if(Array.isArray(e)){if(e.every(e=>!e)){l.push(e);return}l.forEach(u),l=[],u(e)}}}),c=[{name:d,rows:i,totalRows:a,totalCols:s}]}else{let e=await t(()=>import(`./xlsx-CKkngM-o.js`),[]),n=e.read(o,{dense:!0});c=n.SheetNames.map(t=>{let r=n.Sheets[t],i=r?.[`!ref`];if(!r||!i)return{name:t,rows:[],totalRows:0,totalCols:0};let a=e.utils.decode_range(i),o=a.e.r-a.s.r+1,s=a.e.c-a.s.c+1,c={s:a.s,e:{r:Math.min(a.e.r,a.s.r+m-1),c:Math.min(a.e.c,a.s.c+h-1)}};return{name:t,rows:e.utils.sheet_to_json(r,{header:1,raw:!1,defval:``,range:e.utils.encode_range(c)}),totalRows:o,totalCols:s}})}if(!a&&(w(c),A.current&&!k.current)){let e=c.find(e=>e.rows.length>0);if(e){k.current=!0;let t=await _(e.rows);t&&!a&&A.current?.(t)}}})().catch(e=>{console.error(`[sheet-preview] load failed`,e),a||O(S(`fileManager.preview.error`))}),()=>{a=!0}},[r,v,y,d,S]);let P=C?.[T]??null,F=P!=null&&P.totalRows>P.rows.length,I=P!=null&&P.totalCols>h,L=P==null?0:Math.min(P.totalCols,h);return(0,p.jsxs)(a,{title:d,fullscreen:j,onClose:b,icon:(0,p.jsx)(s,{className:`w-5 h-5 text-bambu-green flex-shrink-0`}),children:[C&&C.length>1&&(0,p.jsx)(`div`,{className:`flex gap-1 px-4 pt-2 overflow-x-auto flex-shrink-0`,children:C.map((e,t)=>(0,p.jsx)(`button`,{onClick:()=>E(t),className:`px-3 py-1.5 text-sm rounded-t whitespace-nowrap transition-colors ${t===T?`bg-bambu-dark text-white border border-b-0 border-bambu-dark-tertiary`:`text-bambu-gray hover:text-white hover:bg-bambu-dark/50`}`,children:e.name},`${e.name}-${t}`))}),(0,p.jsx)(`div`,{"data-testid":`spreadsheet-preview-content`,className:`flex-1 min-h-0 overflow-auto bg-bambu-dark ${M?``:`rounded-b-lg`}`,onDoubleClick:N,children:D?(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center p-6`,children:(0,p.jsx)(`p`,{className:`text-bambu-gray text-center`,children:D})}):C?!P||P.rows.length===0?(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center p-6`,children:(0,p.jsx)(`p`,{className:`text-bambu-gray`,children:S(`fileManager.preview.emptySheet`)})}):(0,p.jsxs)(`table`,{className:`border-collapse text-xs min-w-full`,children:[(0,p.jsx)(`thead`,{children:(0,p.jsxs)(`tr`,{children:[(0,p.jsx)(`th`,{className:`sticky top-0 bg-bambu-dark-secondary border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray font-medium w-10`}),Array.from({length:L},(e,t)=>(0,p.jsx)(`th`,{className:`sticky top-0 bg-bambu-dark-secondary border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray font-medium text-left min-w-[80px]`,children:g(t)},t))]})}),(0,p.jsx)(`tbody`,{children:P.rows.map((e,t)=>(0,p.jsxs)(`tr`,{children:[(0,p.jsx)(`td`,{className:`border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray text-right bg-bambu-dark-secondary/50`,children:t+1}),Array.from({length:L},(t,n)=>(0,p.jsx)(`td`,{className:`border border-bambu-dark-tertiary px-2 py-1 text-bambu-gray-light whitespace-nowrap max-w-[280px] overflow-hidden text-ellipsis`,title:e[n]||void 0,children:e[n]??``},n))]},t))})]}):(0,p.jsx)(`div`,{className:`h-full flex items-center justify-center`,children:(0,p.jsx)(l,{className:`w-8 h-8 text-bambu-green animate-spin`})})}),(F||I)&&P&&(0,p.jsxs)(`div`,{className:`px-4 py-2 border-t border-bambu-dark-tertiary text-xs text-bambu-gray flex gap-4 flex-shrink-0`,children:[F&&(0,p.jsx)(`span`,{children:S(`fileManager.preview.truncatedRows`,{shown:P.rows.length,total:P.totalRows})}),I&&(0,p.jsx)(`span`,{children:S(`fileManager.preview.truncatedCols`,{shown:L,total:P.totalCols})})]})]})}export{v as SpreadsheetPreviewModal}; \ No newline at end of file diff --git a/static/assets/index-DCzE7_R7.js b/static/assets/index-DXO3amin.js similarity index 98% rename from static/assets/index-DCzE7_R7.js rename to static/assets/index-DXO3amin.js index b8d412592..166bb3a3c 100644 --- a/static/assets/index-DCzE7_R7.js +++ b/static/assets/index-DXO3amin.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/PdfPreviewModal-DP4k6p_n.js","assets/chunk-aKtaBQYM.js","assets/SpreadsheetPreviewModal-oZ5sN9G4.js","assets/ImagePreviewModal-ertLn7DQ.js"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/PdfPreviewModal-DXTd9gb8.js","assets/chunk-aKtaBQYM.js","assets/SpreadsheetPreviewModal-DKtg1R2F.js","assets/ImagePreviewModal-DydMcOJ9.js"])))=>i.map(i=>d[i]); import{i as e,n as t,r as n,t as r}from"./chunk-aKtaBQYM.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var i=r((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var A=/\/+/g;function j(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function M(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function N(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,N(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+j(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(A,`$&/`)+`/`),N(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(A,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=i()})),o=r((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&j(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&j(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,A=k.port2;k.port1.onmessage=D,O=function(){A.postMessage(null)}}else O=function(){_(D,0)};function j(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,j(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),s=r(((e,t)=>{t.exports=o()})),c=r((e=>{var t=a();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=c()})),u=r((e=>{var t=s(),n=a(),r=l();function i(e){var t=`https://react.dev/errors/`+e;if(1B||(e.current=z[B],z[B]=null,B--)}function te(e,t){B++,z[B]=e.current,e.current=t}var ne=V(null),re=V(null),ie=V(null),ae=V(null);function oe(e,t){switch(te(ie,t),te(re,e),te(ne,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Ud(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Ud(t),e=Wd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}ee(ne),te(ne,e)}function se(){ee(ne),ee(re),ee(ie)}function H(e){e.memoizedState!==null&&te(ae,e);var t=ne.current,n=Wd(t,e.type);t!==n&&(te(re,e),te(ne,n))}function ce(e){re.current===e&&(ee(ne),ee(re)),ae.current===e&&(ee(ae),$f._currentValue=R)}var le,ue;function de(e){if(le===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);le=t&&t[1]||``,ue=-1)`:-1 Конфіденційність і безпека > Локальна мережа та переконайтеся, що Python від Bambuddy увімкнено. Інакше macOS мовчки відкидає локальні з’єднання, а оновлення Python може залишити старий дозвіл позаду.`},network_mode:{title:`Мережевий режим контейнера`,genericRuntime:`контейнері`,pass:`Працює в {{runtime}} із мережею хоста.`,warn:`Bambuddy працює в {{runtime}} із мостовою мережею. Для виявлення принтерів і роботи віртуального принтера потрібна мережа хоста — повторно створіть контейнер із мережею хоста ("network_mode: host" у docker-compose, "--network=host" для Podman).`,skip:`Не працює в контейнері — не застосовується.`,skip_unknown:`Bambuddy працює в {{runtime}}, але визначити мережевий режим не вдалося. Якщо виявлення принтерів або віртуальний принтер не працюють, повторно створіть контейнер із мережею хоста.`,skip_system_container:`Bambuddy працює в системному контейнері {{runtime}}, який перебуває в локальній мережі як віртуальна машина — не застосовується.`},subnet:{title:`Мережева підмережа`,pass:`Принтер і Bambuddy знаходяться в одній підмережі.`,warn:`Принтер ({{printer_ip}}) і Bambuddy ({{host_ip}}) перебувають у різних підмережах. Вони можуть не мати зв’язку між собою, якщо маршрутизацію між підмережами не налаштовано.`,skip:`Не вдалося визначити підмережу — пропущено.`},mqtt_auth:{title:`Облікові дані принтера`,pass:`Принтер прийняв підключення.`,fail:`Принтер доступний, але Bambuddy до нього не під’єднано. Найімовірніше, указано неправильний код доступу або серійний номер — код доступу змінюється після кожного перемикання режиму «Лише LAN» або режиму розробника, тож знову скопіюйте його з екрана принтера. Так само виглядає принтер, який перезавантажується або вже вичерпав ліміт одночасних з’єднань.`,fail_auth_rejected:`Принтер відхилив облікові дані Bambuddy. Код доступу або серійний номер неправильний — код доступу змінюється після кожного перемикання режиму «Лише LAN» або режиму розробника. Знову скопіюйте його з екрана принтера та збережіть у налаштуваннях принтера.`,skip:`Не позначено — принтер недоступний.`},developer_mode:{title:`Режим розробника LAN`,pass:`Режим розробника ввімкнено.`,fail:`Режим розробника на принтері ВИМКНЕНО. Увімкніть його в налаштуваннях LAN принтера й підтвердьте вибір кнопкою OK. Без цього друк не запускатиметься.`,skip:`Неможливо перевірити — потрібне живе підключення до принтера.`},printer_publishing:{title:`Принтер публікує дані про стан`,pass:`Принтер публікує оновлення статусу — AMS, філаменти та K-профілі правильно відображатимуться на слайсері.`,fail:`MQTT-брокер прийняв з’єднання, але принтер не опублікував жодного звіту про стан. Майже завжди причиною є неправильний серійний номер або неправильний регістр символів: тема device//report чутлива до регістру. Звірте серійний номер у налаштуваннях Bambuddy із номером на екрані принтера.`,skip:`Неможливо перевірити — потрібне живе підключення до принтера.`}}},systemHealth:{sectionTitle:`Стан системи`,sectionDescription:`Сканує останні журнали на наявність відомих проблем, які зазвичай можна вирішити самостійно, перш ніж вони перетворяться на запит у службу підтримки.`,rescan:`Пересканувати`,clean:`В останніх записах журналу {{times}} не знайдено відомих проблем.`,logUnavailable:`Журналування файлів вимкнено, тому журнали неможливо сканувати. Увімкніть журналювання файлів, щоб використовувати цю перевірку.`,learnMore:`Як виправити`,fixLabel:`Виправити:`,occurrences:`Виявлено {{times}} разів — востаннє о {{lastSeen}}`,category:{layer8:`Ви можете це виправити`,environment:`Середовище виконання`,bug:`Будь ласка, повідомте про це`},signature:{"ftp-auth-rejected":{name:`Принтер відхилив код доступу`,cause:`Принтер відмовився від входу для передавання файлів. Код доступу неправильний або він змінився після перемикання режиму розробника.`,fix:`Повторно скопіюйте код доступу з екрана принтера (налаштування LAN) і оновіть його в налаштуваннях принтера в Bambuddy.`},"ftp-connection-timeout":{name:`Час очікування з’єднання для передачі файлів минув`,cause:`Bambuddy не вдалося отримати доступ до порту передачі файлів принтера (FTPS 990). Порт заблоковано, або принтер вимкнено або знаходиться в іншій підмережі.`,fix:`Переконайтеся, що ніщо не блокує порт 990 між Bambuddy і принтером, і що обидва знаходяться в одній мережі.`},"ftp-ssl-error":{name:`Помилка захищеного з’єднання для передавання файлів`,cause:`Файлова служба принтера відповіла на порту 990 без TLS. Її файловий сервер завис — це несправність самого принтера, а не брандмауера чи прошивки.`,fix:`Перезапустіть принтер. До того часу файли друку, обкладинки та таймлапси отримати неможливо, але на сам друк це не впливає.`},"bambu-cloud-captcha":{name:`Bambu Cloud вимагає пройти CAPTCHA`,cause:`Захист Bambu від зловживань перевіряє цю мережу, тож завершити вхід у Bambu Cloud неможливо. Це пов'язано з публічною IP-адресою, а не з вашим обліковим записом чи цією інсталяцією.`,fix:`Зачекайте: зазвичай перевірка зникає за кілька годин, а повторні спроби входу її подовжують. Тим часом увійдіть за допомогою токена доступу із сеансу браузера.`},"mqtt-connection-flapping":{name:`Підключення до принтера постійно падає`,cause:`Керуюче з’єднання (MQTT 8883) неодноразово роз’єднується та знову під’єднується — зазвичай через слабкий мережевий шлях або частково заблокований порт.`,fix:`Перевірте сигнал Wi-Fi на принтері, віддайте перевагу дротовому підключенню та переконайтеся, що порт 8883 надійно доступний.`},"camera-connection-refused":{name:`Потік з камери недоступний`,cause:`Не вдається отримати доступ до камери в прямому ефірі через порт RTSPS 322. Порт заблоковано, або на принтері вимкнено камеру або LAN liveview.`,fix:`Увімкніть камеру та LAN liveview на принтері та переконайтеся, що порт 322 не заблоковано. Це не впливає на друк.`},"database-locked":{name:`Конфлікт запису до бази даних`,cause:`Під навантаженням SQLite повертає помилки "database is locked". Це часто трапляється, коли одночасно працюють кілька принтерів.`,fix:`Переключіть Bambuddy на зовнішню базу даних PostgreSQL. Див. посібник PostgreSQL у документації.`}}},vpDiagnostic:{title:`Перевірка налаштувань — {{name}}`,runButton:`Запустити перевірку налаштувань`,running:`Виконується перевірка налаштувань...`,runFailed:`Не вдалося виконати перевірку налаштувань: {{error}}`,retry:`Запустити знову`,overall:{ok:`Усі перевірки пройшли — цей віртуальний принтер налаштовано правильно.`,warnings:`Віртуальний принтер має працювати, але деякі параметри потребують уваги.`,problems:`Знайдено проблеми, які пояснюють, чому слайсер не бачить або не використовує цей віртуальний принтер.`},check:{enabled:{title:`Віртуальний принтер увімкнено`,fail:`Цей віртуальний принтер вимкнено. Увімкніть його, щоб зробити його видимим.`},running:{title:`Служби запущені`,fail:`Віртуальний принтер увімкнено, але його служби не працюють. Перевірте журнал Bambuddy — зазвичай запуску заважає конфлікт IP-адреси прив’язки або помилка дозволів.`},bind_interface:{title:`Прив’язка мережевого інтерфейсу`,fail:`Інтерфейс прив’язки не встановлено або більше не існує на цьому хості. Виберіть поточний інтерфейс у спадному списку «Інтерфейс прив’язки».`},access_code:{title:`Код доступу встановлено`,fail:`Код доступу не встановлено. У слайсері потрібно вказати той самий 8-символьний код доступу, який задано тут.`},target_printer:{title:`Цільовий принтер`,fail:`Цільовий принтер не вибрано. У режимі проксі потрібен фізичний принтер, якому пересилатимуться завдання.`,warn:`Цільовий принтер наразі не в мережі — проксі-сервер відновиться після повторного підключення.`},port_ftps:{title:`Служба вивантаження файлів (порт {{port}})`,fail:`На порту {{port}} IP-адреси прив’язки немає служби, що приймає з’єднання, тому слайсер не може вивантажити файли. Зазвичай причиною є конфлікт порту на цьому інтерфейсі.`},port_mqtt:{title:`Служба керування (порт {{port}})`,fail:`На порту {{port}} IP-адреси прив’язки немає служби, що приймає з’єднання, тому слайсер не може підключитися або показати стан.`},port_bind:{title:`Служба виявлення (порт {{port}})`,fail:`На порту {{port}} IP-адреси прив’язки немає служби, що приймає з’єднання, тому слайсер не може завершити процедуру виявлення.`},privileged_ports:{title:`Прив’язка до привілейованих портів`,fail:`Порт {{port}} нижче 1024, і цій службі не дозволено його займати — тому вище ніщо не слухає. Додайте AmbientCapabilities=CAP_NET_BIND_SERVICE до /etc/systemd/system/bambuddy.service і перезапустіть або виконайте "sudo setcap cap_net_bind_service=+ep $(readlink -f $(which python3))". У Docker додайте cap_add: [NET_BIND_SERVICE].`},certificate:{title:`Сертифікат TLS`,pass:`Сертифікат готовий. Переконайтеся, що наведений вище сертифікат центру сертифікації Bambuddy імпортовано до сховища довірених сертифікатів слайсера.`,fail:`Сертифікат TLS для цього віртуального принтера відсутній. Перевірте, чи доступний для запису каталог даних Bambuddy.`}}},bugReport:{title:`Повідомити про помилку`,description:`опис`,descriptionPlaceholder:`Що пішло не так? Будь ласка, опишіть проблему...`,email:`Електронна адреса (необов’язково)`,emailPlaceholder:`your@email.com`,emailPrivacy:`Якщо надано, вашу електронну адресу буде включено до згорнутого розділу проблеми GitHub, щоб спеціаліст із супроводу міг відслідковувати.`,screenshot:`Скріншот`,uploadOrPaste:`Вивантажити, вставити або перетягнути зображення`,dataCollectedSummary:`Які дані містяться у звіті?`,dataIncluded:`У комплекті:`,dataIncludedList:`Версія додатка, OS, архітектура, версія Python, статистика бази даних (лише для підрахунку), моделі принтерів, кількість сопел, версії прошивки, статус підключення, статус інтеграції (Spoolman, MQTT, HA), неконфіденційні налаштування, кількість мережевих інтерфейсів, Docker деталі, версії залежностей.`,dataNeverIncluded:`Ніколи не додаються:`,dataNeverIncludedList:`Назви принтерів, серійні номери, коди доступу, паролі, IP-адреси, адреси електронної пошти, ключі API, токени, URL вебхуків, імена хостів або користувачів.`,submit:`Надіслати`,startLogging:`Розпочати журнал налагодження`,stepEnableLogging:`Журнал налагодження ввімкнено`,stepReproduce:`Відтворіть проблему зараз`,stepStopLogging:`Зупинити та подати звіт`,stopAndSubmit:`Зупинити та надіслати`,maxDuration:`Автоматична зупинка через {{minutes}} хв`,stoppingLogs:`Збір журналів і надсилання...`,submitting:`Надсилання звіту про помилку...`,submittingStepConnection:`Запуск перевірки підключення принтера`,submittingStepVirtualPrinters:`Виконання перевірок налаштування віртуального принтера`,submittingStepLogScan:`Сканування останніх журналів на наявність відомих проблем`,submittingStepSubmit:`Надсилання звіту GitHub`,submitSuccess:`Звіт про помилку успішно надіслано!`,submitFailed:`Не вдалося надіслати звіт про помилку`,diagnosticChecking:`Перевірка підключень принтера...`,diagnosticHealthy:`Перевірку підключення пройдено — проблем із принтерами не виявлено.`,diagnosticSummary:`Проблеми з підключенням мають {{problems}} із {{total}} принтерів`,diagnosticIntro:`Один або кілька принтерів мають проблему з підключенням, яка може спричиняти описану несправність. Розгорніть запис принтера нижче, щоб переглянути спосіб виправлення. Усунення цієї проблеми може допомогти без створення звіту про помилку, але за потреби звіт усе одно можна надіслати нижче.`,logHealthSummary:`У ваших журналах знайдено відомі проблеми`,logHealthIntro:`Останні журнали відповідають відомим проблемам. Перегляньте наведені нижче виправлення — їх вирішення може вирішити вашу проблему без повідомлення про помилку. Ви все ще можете надіслати звіт нижче.`,thankYou:`дякую!`,submitted:`Ваш звіт про помилку надіслано.`,viewIssue:`Переглянути випуск`,closeKeepsRecording:`Ви можете закрити цю панель, поки відтворюєте проблему: запис триває, і після повторного відкриття ви повернетеся сюди.`,resumeRecording:`Триває запис звіту про помилку — {{elapsed}}. Натисніть, щоб завершити.`,resumeReport:`Продовжити звіт`,unexpectedError:`Сталася неочікувана помилка`},failureDetection:{title:`Виявлення помилок за допомогою ШІ`,description:`Відстежуйте друк за допомогою самостійно розгорнутого Obico ML API та автоматично реагуйте на виявлені помилки.`,mlUrl:`URL Obico ML API`,mlUrlHint:`База URL вашого контейнера ml_api, розміщеного на власному хості Obico (наприклад, http://192.168.1.10:3333).`,mlToken:`Токен ML API (необов'язково)`,mlTokenPlaceholder:`Залиште порожнім, якщо сервер працює без ML_API_TOKEN`,mlTokenHint:`Має збігатися зі змінною середовища ML_API_TOKEN вашого контейнера Obico ml_api. Залиште порожнім, якщо контейнер працює без токена.`,test:`Тест`,testSuccess:`ML API доступний і працює належним чином.`,testSuccessTokenUnknown:`ML API доступний і працює належним чином. Не вдалося перевірити токен.`,testFailed:`Не вдалося підключитися до ML API.`,sensitivity:`Чутливість`,sensitivityLow:`Низький (менше помилкових спрацьовувань)`,sensitivityMedium:`Середній (збалансований)`,sensitivityHigh:`Високий (виявляти рано, більше помилкових спрацьовувань)`,sensitivityHint:`Налаштовує пороги впевненості, за яких формуються попередження та фіксуються помилки.`,action:`Дія в разі виявлення помилки`,actionNotify:`Лише сповіщати`,actionPause:`Призупинити друк`,actionPauseOff:`Призупинити друк і вимкнути живлення`,pollInterval:`Інтервал опитування (секунди)`,pollIntervalHint:`Як часто перевіряти кожен принтер під час друку. Мінімум 5 с, максимум 120 с.`,externalUrlMissing:`Зовнішній URL не встановлено.`,externalUrlHint:`ML API отримує знімок із камери за URL. Укажіть зовнішній URL у загальних налаштуваннях, щоб контейнер ML API міг підключитися до Bambuddy.`,perPrinterTitle:`Принтери під спостереженням`,perPrinterHint:`Виберіть, які принтери перевіряє служба виявлення.`,monitorAll:`Відстежувати всі підключені принтери`,statusTitle:`Статус`,serviceRunning:`Служба працює`,thresholds:`Нижній / верхній поріг`,activePrinters:`Активні завдання друку`,noActivePrints:`Зараз немає роздруківок.`,historyTitle:`Останні виявлення`,noHistory:`Виявлень ще немає.`},makerworld:{title:`MakerWorld`,description:`Вставте модель MakerWorld URL, щоб імпортувати та роздрукувати її безпосередньо з Bambuddy — не виходячи з програми Bambu Handy.`,pasteUrlHeader:`Імпортувати з MakerWorld`,pasteUrlPlaceholder:`https://makerworld.com/en/models/… або вставте будь-яке посилання MakerWorld.`,resolveButton:`Розв’язати`,signInRequiredTitle:`Для завантаження потрібно увійти в Bambu Cloud`,signInRequiredBody:`Ви можете переглядати деталі моделі анонімно, але для MakerWorld потрібен обліковий запис Bambu Cloud для завантаження файлів 3MF.`,signInExpiredTitle:`Термін входу в Bambu Cloud минув`,signInExpiredBody:`Ви все ще ввійшли в Bambuddy, але Bambu Lab більше не приймає збережений токен, тому завантаження не вдасться. Знову ввійдіть у Bambu Cloud.`,openCloudSettings:`Відкрити налаштування Cloud`,untitledModel:`Модель без назви`,byCreator:`від {{name}}`,downloadsCount:`{{count}} завантажень`,licensePrefix:`Ліцензія`,alreadyImported:`Вже в бібліотеці`,openOnMakerworld:`Відкрити MakerWorld`,alreadyInLibrary:`Ця модель уже є у вашій бібліотеці — знайдіть її в Менеджері файлів → MakerWorld`,importSuccess:`Імпортовано {{filename}} — збережено в менеджері файлів → MakerWorld`,platesHeader:`Пластини ({{count}})`,plateDefaultName:`Пластина {{n}}`,materialCount:`Філаментів: {{count}}`,amsRequired:`Потрібен AMS`,slicedFor:`Нарізано для {{printer}}`,alsoCompatible:`Також позначено як сумісний: {{printers}}`,importToLibrary:`Зберегти`,sliceIn:`Зберегти й нарізати в {{slicer}}`,disclaimer:`Інтеграція MakerWorld використовує кінцеві точки API, задокументовані спільнотою. Bambuddy не є філією та не підтримується MakerWorld або Bambu Lab.`,lastImportSuccess:`Імпортовано до вашої бібліотеки`,lastImportAlreadyInLibrary:`Уже у вашій бібліотеці`,viewInLibrary:`Переглянути в менеджері файлів`,openInBambuStudio:`Відкрити в Bambu Studio`,openInOrcaSlicer:`Відкрити в OrcaSlicer`,importTo:`Імпорт до файлового менеджера`,recentImportsHeader:`Недавній імпорт`,phaseResolving:`Розв’язування`,phaseDownloading:`Завантаження`,folderAuto:`MakerWorld (за замовчуванням)`,importAll:`Імпортувати все`,importAllProgress:`Імпорт {{current}}/{{total}}`,openGallery:`Відкрити галерею зображень`,galleryPrev:`Попереднє зображення`,galleryNext:`Наступне зображення`,deleteImport:`Видалити з бібліотеки`,importDeleting:`Видалення…`,importDeleted:`Видалено з бібліотеки`,confirmDelete:`Видалити {{filename}} з бібліотеки? Локальний файл буде видалено, але цю друкарську пластину можна буде повторно імпортувати з MakerWorld.`,errors:{resolveFailed:`Не вдалося вирішити MakerWorld URL.`,downloadFailed:`Помилка завантаження. Спробуйте ще раз.`,deleteFailed:`Не вдалося видалити файл із бібліотеки.`}},gcodeViewer:{filamentSlot:`Філамент {{n}}`,loading:`Читання траєкторії…`,loadFailed:`Не вдалося завантажити G-код цього файлу.`,showTravel:`Холості переміщення`,topLayer:`Верхній шар`,bottomLayer:`Нижній шар`,noSource:`Файл для перегляду не вказано.`,view:{filament:`Філамент`,feature:`Тип`,height:`Висота`,width:`Ширина`},feature:{wall:`Стінки`,sparseInfill:`Розріджене заповнення`,solidInfill:`Суцільне заповнення`,bridge:`Міст / навис`,support:`Підтримка`,skirt:`Спідниця / облямівка`,gapFill:`Заповнення проміжків`,ironing:`Розгладжування`,primeTower:`Вежа очищення`},back:`Назад`,backToArchives:`Назад до друку архівів`,backToFiles:`Назад до файлового менеджера`,plates:`Пластини`,plateN:`Пластина {{n}}`},libraryTrash:{title:`Кошик`,headerButton:`Кошик`,headerTooltip:`Переглянути файли, переміщені в кошик`,backToFiles:`Назад до файлового менеджера`,subtitleAdmin:`Видалені файли залишаються тут протягом {{days}} днів, а потім автоматично видаляються. У цьому режимі перегляду відображаються файли, видалені в кошик для всіх користувачів.`,subtitleUser:`Видалені файли залишаються тут протягом {{days}} днів, а потім автоматично видаляються.`,loading:`Завантаження кошика…`,loadError:`Не вдалося завантажити кошик.`,empty:`Кошик порожній.`,summary:`Файлів: {{count}} · {{size}}`,emptyTrash:`Очистити кошик`,restore:`Відновити`,purgeNow:`Видалити зараз`,autoPurgeIn:`Автоматичне видалення через {{when}}`,days:`днів`,retentionLabel:`Автоматичне видалення після`,selectAll:`Вибрати все`,selectOne:`Вибрати {{filename}}`,selectionCount:`Вибрано {{count}}.`,bulkRestore:`Відновити вибране`,bulkPurge:`Видалити вибране`,col:{filename:`Файл`,folder:`Папка`,size:`Розмір`,deleted:`Переміщено в кошик`,autoPurge:`Автоматичне видалення`,owner:`Власник`,actions:`Дії`},confirm:{purgeTitle:`Видалити остаточно?`,purgeBody:`{{filename}} буде видалено з диска та не підлягає відновленню.`,emptyTitle:`Очистити кошик?`,emptyBody:`З диска буде остаточно видалено всі файли ({{count}}). Цю дію неможливо скасувати.`,bulkPurgeTitle:`Видалити вибрані файли назавжди?`,bulkPurgeBody:`Вибрані файли ({{count}}) буде видалено з диска без можливості відновлення.`,cta:`Видалити остаточно`},toast:{restored:`Файл відновлено.`,restoreFailed:`Не вдалося відновити файл.`,purged:`Файл видалено остаточно.`,purgeFailed:`Не вдалося видалити файл.`,emptied:`Файлів остаточно видалено з кошика: {{count}}.`,emptyFailed:`Не вдалося очистити кошик.`,retentionSaved:`Для автоматичного видалення встановлено {{days}} днів.`,retentionFailed:`Не вдалося зберегти налаштування збереження.`,bulkRestored:`Відновлено файлів: {{count}}`,bulkPurged:`Файлів остаточно видалено: {{count}}.`}},libraryPurge:{title:`Очистити старі файли`,headerButton:`Очищення старого`,headerTooltip:`Масове переміщення старих файлів у кошик`,description:`Однією дією перемістіть старі файли з бібліотеки до кошика. Вік файлів з історією друку визначається за датою останнього друку, а файлів, які ще не друкувалися, — за датою вивантаження.`,ageLabel:`Перемістити файли, старші за`,days:`днів`,includeNeverPrinted:`Включати файли, які ніколи не друкувались`,effectsTitle:`Що відбудеться після натискання «Очистити»`,effect1:`Відповідні файли переміщуються в кошик — вони ще не видалені з диска.`,effect2:`Ви можете будь-коли відновити їх із кошика, доки не закінчиться період зберігання.`,effect3:`Після завершення строку зберігання служба очищення кошика остаточно видалить файли з диска.`,effect4:`Файли у зовнішніх (зв’язаних) папках пропускаються — Bambuddy ніколи не видаляє байти, які йому не належать.`,previewLoading:`Перевірка кількості файлів…`,previewFailed:`Не вдалося попередньо переглянути очищення.`,previewSummary:`Файли {{count}} · {{size}} буде переміщено до кошика`,andMore:`…і {{count}} більше`,warning:`Файли в кошику й надалі займають місце, доки не завершиться строк зберігання. Щоб звільнити місце на диску негайно, після цього очистьте кошик.`,confirmCta:`Перемістити до кошика ({{count}})`,purging:`Переміщення в кошик…`,toast:{success:`Переміщено до кошика файлів: {{count}}`,failed:`Не вдалося очистити файли.`}},libraryAutoPurge:{enableLabel:`Автоматичне очищення старих файлів`,enableDescription:`Запускає очищення адміністратора один раз на день. Файли спочатку потрапляють у кошик — вони не видаляються одразу.`,ageLabel:`Автоматичне очищення файлів, старших за`,ageDescription:`Мінімум 7 днів, максимум 10 років. Використовує те саме вікове правило, що й ручна кнопка очищення.`,days:`днів`,includeNeverPrinted:`Включати файли, які ніколи не друкувались`,saveFailed:`Не вдалося зберегти налаштування автоматичного очищення.`},archivePurge:{headerButton:`Очищення старого`,headerTooltip:`Масове видалення старих архівів`,title:`Очистити старі архіви`,description:`Очистити стару історію друку. Кожен архів старіє відповідно до останнього завершення друку — повторний друк архіву оновлює його вік, тому активна робота ніколи не очищається.`,ageLabel:`Видалити архіви, не надруковані в останньому`,days:`днів`,effectsTitle:`Що відбудеться після натискання «Очистити»`,effect1:`Кожен відповідний архів приховано зі списків, а його файли видалено з диска (3MF, ескіз, покадрова зйомка, вихідний 3MF, F3D файл дизайну, фотографії).`,effect2:`Запис архіву залишиться в базі даних, тому «Швидка статистика» й надалі враховуватиме філамент, час, вартість та енергію — як і за типового видалення окремого архіву.`,effect3:`Позначте нижче «Також видалити зі статистики», щоб вилучити внесок цих архівів зі «Швидкої статистики» — так само, як у відповідній опції видалення окремого архіву. Цю дію неможливо скасувати.`,effect4:`Повторний друк архіву оновлює його віковий годинник, тому архіви, якими ви все ще користуєтеся, залишаються в безпеці.`,purgeStatsLabel:`Також видалити зі статистики`,purgeStatsHint:`Вилучає внесок відповідних архівів зі «Швидкої статистики» — філамент, час, вартість та енергію. Якщо параметр вимкнено, статистичні дані зберігаються, а з диска видаляються лише файли.`,previewLoading:`Перевірка кількості архівів, що збігаються…`,previewFailed:`Не вдалося попередньо переглянути очищення.`,previewSummary:`Архіви {{count}} · {{size}} буде видалено`,andMore:`…і {{count}} більше`,warning:`Файли видаляються з диска і не можуть бути відновлені. Перш ніж продовжити, завантажте або додайте до вибраного все, що хочете зберегти.`,confirmCta:`Видалити архіви ({{count}})`,purging:`Видалення…`,toast:{success:`Видалено архівів: {{count}}`,failed:`Не вдалося очистити архіви.`}},archiveAutoPurge:{enableLabel:`Автоматичне очищення старих архівів`,enableDescription:`Один раз на день приховує архіви зі списків і видаляє їх файли з диска, якщо вони не були надруковані в межах порогу. Повторний друк архіву скидає годинник.`,ageLabel:`Автоматичне видалення архівів, не надрукованих в останньому`,ageDescription:`Мінімум 7 днів, максимум 10 років. На основі останнього завершення друку — повторний друк архіву оновлює його вік. Видаляє 3MF, мініатюру, покадрову зйомку, джерело 3MF, F3D і фотографії.`,days:`днів`,purgeStatsLabel:`Також видалити зі статистики`,purgeStatsDescription:`Якщо параметр увімкнено, щоденне очищення також вилучатиме внесок кожного видаленого архіву зі «Швидкої статистики» — філамент, час, вартість та енергію. Типово параметр вимкнено: статистичні дані зберігаються, а з диска видаляються лише файли.`,runNow:`Очистити архіви зараз`,saveFailed:`Не вдалося зберегти налаштування автоматичного очищення.`},connectedApps:{title:`Підключені застосунки`,description:`Застосунки, у які входять з обліковим записом Bambuddy. Кожен застосунок отримує ідентифікатор клієнта й секрет, а Bambuddy надсилає вхід лише на зареєстровану тут URL-адресу зворотного виклику.`,requiresAuth:`Для підключених застосунків потрібна автентифікація Bambuddy. Спершу увімкніть її в розділі «Користувачі».`,name:`Назва застосунку`,namePlaceholder:`напр., Керування замовленнями`,callbackUrl:`URL зворотного виклику`,callbackHint:`URL зворотного виклику вказано в документації застосунку. Він має збігатися точно.`,add:`Додати застосунок`,empty:`Підключених застосунків поки немає.`,clientId:`Ідентифікатор клієнта`,clientSecret:`Секрет клієнта`,lastUsed:`Останній вхід`,disabledBadge:`Вимкнено`,enable:`Увімкнути`,disable:`Вимкнути`,rotate:`Новий секрет`,delete:`Видалити`,cancel:`Скасувати`,copy:`Копіювати`,secret:{title:`{{name}}: скопіюйте секрет зараз`,warning:`Вставте ідентифікатор клієнта й секрет у застосунок. Секрет показується лише цього разу; якщо його втрачено, створіть новий.`,dismiss:`Я зберіг`},confirmRotate:{title:`Створити новий секрет?`,body:`«{{name}}» не зможе виконувати вхід, доки ви не вставите в нього новий секрет.`},confirmDelete:{title:`Видалити цей застосунок?`,body:`Через «{{name}}» більше ніхто не зможе увійти. Якщо додати його знову, у всіх знову запитають дозвіл.`},toast:{created:`Застосунок додано`,enabled:`Застосунок увімкнено`,disabled:`Застосунок вимкнено`,rotated:`Новий секрет створено`,deleted:`Застосунок видалено`,copied:`Скопійовано в буфер обміну`,copyFailed:`Не вдалося скопіювати. Виділіть текст і скопіюйте вручну.`,failed:`Щось пішло не так`},authorize:{checking:`Перевірка запиту на вхід…`,redirecting:`Виконується вхід…`,errorTitle:`Не вдається увійти`,invalidRequest:`Цей запит на вхід недійсний: застосунок не зареєстровано, вимкнено або він використовує іншу URL-адресу зворотного виклику. Попросіть адміністратора Bambuddy перевірити «Налаштування > API-ключі > Підключені застосунки».`,authDisabled:`Вхід через Bambuddy недоступний, бо автентифікацію Bambuddy вимкнено. Використовуйте власний вхід застосунку.`,failed:`Не вдалося увійти. Поверніться до застосунку й спробуйте ще раз.`,title:`{{app}} хоче виконати ваш вхід`,signedInAs:`Вхід у Bambuddy виконано як {{username}}`,sharedData:`{{app}} бачитиме ваше ім'я користувача, адресу електронної пошти, групи та дозволи. Ваш пароль і сеанс Bambuddy застосунок не отримає.`,allow:`Дозволити`,deny:`Скасувати`}},cameraTokens:{scope:{camera_stream:`Потік камери`,camwall:`Панель камер`,overlay:`Потокове накладання`},title:`API-токени камери`,navTitle:`API-токени камери`,description:`Довгострокові токени для вбудовування потоку камери в Home Assistant, Frigate, кіоски або інші засоби, яким потрібен постійний URL. Під час створення виберіть область доступу; токен можна відкликати будь-коли.`,loading:`Завантаження…`,confirmRevoke:{title:`Відкликати цей токен?`,body:`Усі пристрої, що використовують «{{name}}», негайно втратять доступ. Цю дію неможливо скасувати.`,cancel:`Скасувати`,confirm:`Відкликати`},create:{scopeLabel:`Область застосування`,hintCameraStream:`Токен потоку камери дає змогу отримувати лише потоки з камер і знімки. Використовуйте його для Home Assistant, Frigate або іншого засобу, що вбудовує одну камеру.`,hintCamWall:`Токен Cam Wall відкриває /camwall на екрані без входу. Він може бачити назву та стан кожного принтера, а також потоки з камери. Він не може бачити імена файлів, адреси чи коди доступу.`,hintOverlay:`Токен потокового накладання відкриває /overlay/{printerId} на екрані без входу — для OBS або будь-якої прямої трансляції. Він дає змогу переглядати потік із камери одного принтера та стан друку в реальному часі, зокрема назву файлу, показану на екрані. Він не надає доступу до адрес чи кодів доступу.`,title:`Створити новий токен`,nameLabel:`Назва токена`,namePlaceholder:`напр. Home Assistant`,daysLabel:`Днів до закінчення терміну дії`,submit:`Створити`,hint:`Максимальний строк дії — 365 днів. Значення токена буде показано лише один раз під час створення — скопіюйте його зараз.`},created:{camWallUrlTitle:`Cam Wall URL для цього дисплея`,camWallUrlHint:`Відкрийте цей URL на потрібному екрані. Будь-хто, хто отримає URL, зможе переглядати панель камер, тому захищайте його як ключ доступу. Щоб негайно припинити доступ із цього екрана, відкличте токен.`,overlayUrlTitle:`Накладання URL для OBS`,overlayUrlHint:`Додайте цей URL в OBS як джерело браузера. Замініть число в /overlay/1 номером принтера з його URL на сторінці «Принтери». Будь-хто, хто отримає URL, зможе переглядати трансляцію, тому захищайте його як ключ доступу. Щоб негайно припинити доступ, відкличте токен.`,title:`Токен створено — скопіюйте його зараз`,warning:`Це єдиний раз, коли токен буде показано. Після закриття цього діалогового вікна переглянути його знову буде неможливо.`,copy:`Копіювати`,dismiss:`Я зберіг токен`},list:{scope:`Область застосування`,myTitle:`Мої токени`,allTitle:`Усі користувачі (перегляд адміністратора)`,empty:`Токенів ще немає.`,name:`Назва`,owner:`Власник`,prefix:`Префікс`,created:`Створено`,expires:`Термін дії закінчується`,lastUsed:`Останнє використання`,revoke:`Відкликати`,expired:`Термін дії минув`},toast:{created:`Токен створено`,createFailed:`Не вдалося створити токен`,revoked:`Токен відкликано`,revokeFailed:`Не вдалося відкликати токен`,loadFailed:`Не вдалося завантажити токени`,copied:`Скопійовано в буфер обміну`,copyFailed:`Не вдалося копіювати — виберіть і скопіюйте вручну`}},forecast:{title:`Прогноз`,noSpools:`Не знайдено активних котушок. Додайте котушки до свого інвентарю, щоб переглянути прогнозні дані.`,noUsageData:`Немає даних про використання — неможливо спроєктувати хронологію запасів.`,sku:`SKU`,material:`матеріал`,stock:`Запас`,dailyRate:`Добове споживання`,daysLeft:`Залишилося днів`,emptyBy:`Вичерпається до`,reorderBy:`Замовити до`,actions:`Дії`,trend:`Тренд`,estimated:`Приблизно`,noData:`Немає даних`,timeframe:`Період`,chartTitle:`Прогнозований запас — топ-5 матеріалів`,dashedLinesROP:`Пунктирні лінії — точки повторного замовлення`,stockLevel:`Рівень запасів`,reorderPoint:`Точка повторного замовлення`,safetyMargin:`Запас безпеки`,trendLegend:`Тенденція (на основі історії, рівень обслуговування 95%)`,estimatedLegend:`Розрахункова (дельта ваги)`,noDataLegend:`Немає даних`,ropLabel:`ROP`,ssLabel:`SS`,safetyStockLegend:`Страховий запас`,stockArrivalLegend:`Надходження запасів`,stockoutLegend:`Дефіцит`,alertCount_one:`Сповіщень: {{count}}`,alertCount_other:`Сповіщень: {{count}}`,order:`Замовлення`,save:`Зберегти`,cancel:`Скасувати`,settingsSaved:`Налаштування збережено`,failedSaveSettings:`Не вдалося зберегти налаштування`,globalLeadTimeSaved:`Загальний строк постачання збережено`,globalLeadTime:`Загальний строк постачання`,globalLeadTimeHint:`Мінімальний строк постачання для розрахунку точки повторного замовлення всіх SKU`,skuLeadTimeOverride:`Інший строк постачання`,skuLeadTimeHint:`0 — використовувати загальний строк. Установіть значення понад 0, щоб перевизначити його для цього SKU.`,safetyMarginLabel:`Запас безпеки`,effectiveLeadTime:`Розрахунковий строк постачання`,effectiveLeadTimeHint:`max(глобальний {{global}}d, SKU {{sku}}d)`,reorderPointHint:`d̄ × LT + страховий запас — замовляйте, коли запас досягне цього рівня`,safetyMarginHint:`Статистичний страховий запас (z=1,65 × σ × √LT) + визначений користувачем буфер`,safetyMarginHintDays:`Буфер додано поверх статистичного страхового запасу.{{approx}}`,safetyMarginHintDaysApprox:` ≈ {{g}} г за поточного рівня споживання.`,safetyMarginHintG:`Буфер фіксованої ваги, доданий поверх статистичного страхового запасу.{{approx}}`,safetyMarginHintGApprox:` ≈ {{days}} дн. за поточного рівня споживання.`,individualSpools:`Окремі котушки`,labelWeight:`Вага за етикеткою`,spoolCount_one:`Котушок: {{count}}`,spoolCount_other:`Котушок: {{count}}`,stockBreakRisk:`Ризик вичерпання запасу`,stockBreakDetail:`Залишилося {{days}} дн.; строк постачання — {{lt}} дн.`,stockBreakBefore:`Запас вичерпається до поповнення`,reorderNow:`Замовити зараз`,reorderTriggerPassed:`Дата замовлення {{date}} минула.`,shoppingList:`Список покупок`,shoppingListItems_one:`(позицій: {{count}})`,shoppingListItems_other:`(позицій: {{count}})`,shoppingListEmpty:`Список покупок порожній. Натисніть значок кошика в будь-якому рядку, щоб додати позиції.`,addToCart:`Додати до списку покупок`,alertsSnoozed:`Вимкнути сповіщення для цього SKU`,alertsEnabled:`Увімкнути сповіщення для цього SKU`,addedToCart:`Додано до списку покупок`,failedAddItem:`Не вдалося додати елемент`,listView:`Список`,logisticsView:`Логістика`,qty:`Кількість`,weight:`Вага`,leadTime:`Строк постачання`,expectedRestock:`Очікуване поповнення запасів`,status:`Статус`,note:`Примітка`,pending:`В очікуванні`,purchased:`Придбано`,received:`Отримано`,markPurchased:`Позначити як придбане`,markReceived:`Позначити як отримане — додає котушки до запасу`,resetToPending:`Скинути до очікування`,remove:`Видалити`,clearAll:`Очистити все`,downloadCsv:`CSV`,addToCartTitle:`Додати до списку покупок`,byQuantity:`За кількістю`,byDuration:`За тривалістю`,numberOfSpools:`Кількість котушок`,lastHowManyDays:`Скільки днів має тривати?`,noUsageQty:`Немає даних про використання — кількість встановлено на 1.`,noteOptional:`Примітка (необов’язково)`,notePlaceholder:`напр. для проєкту X, терміново…`,addNSpools_one:`Додати котушки ({{count}})`,addNSpools_other:`Додати котушки ({{count}})`,onArrival:`Після прибуття`,stockBreakIn:`Запас вичерпається через {{days}} дн.`,stockRunsOutBefore:`Запас закінчиться раніше, ніж мине строк постачання {{lt}} дн.`,atRate:`За споживання {{rate}} г/день потрібно`,moreSpools_one:`Додаткових котушок: {{count}}`,moreSpools_other:`Додаткових котушок: {{count}}`,bridgeGap:`щоб покрити нестачу.`,noReadAccess:`Ви не маєте дозволу переглядати прогнози запасів.`,noWriteAccess:`Ви не маєте дозволу змінювати налаштування прогнозу.`}}}},yt=[`en`,`de`,`es`,`fr`,`ja`,`it`,`ko`,`nl`,`pt-BR`,`ru`,`sv`,`tr`,`uk`,`zh-CN`,`zh-TW`],bt=`bambuddy_appliance_locale_consumed`;xe.use(_t).use(Be).init({resources:vt,fallbackLng:`en`,supportedLngs:yt,detection:{order:[`localStorage`,`navigator`,`htmlTag`],lookupLocalStorage:`bambutrack_language`,caches:[`localStorage`]},interpolation:{escapeValue:!1},react:{useSuspense:!1}});function xt(){if(typeof window>`u`||!window.localStorage)return;let e=window.localStorage;typeof e.getItem!=`function`||typeof e.setItem!=`function`||e.getItem(bt)||fetch(`/api/v1/system/appliance`).then(e=>e.ok?e.json():null).then(t=>{!t||typeof t.locale!=`string`||yt.includes(t.locale)&&(xe.changeLanguage(t.locale),e.setItem(bt,`1`))}).catch(()=>{})}xt();var St=[{code:`en`,name:`English`,nativeName:`English`},{code:`de`,name:`German`,nativeName:`Deutsch`},{code:`es`,name:`Spanish`,nativeName:`Español`},{code:`fr`,name:`French`,nativeName:`Français`},{code:`ja`,name:`Japanese`,nativeName:`日本語`},{code:`it`,name:`Italian`,nativeName:`Italiano`},{code:`ko`,name:`Korean`,nativeName:`한국어`},{code:`nl`,name:`Dutch`,nativeName:`Nederlands`},{code:`pt-BR`,name:`Portuguese (Brazil)`,nativeName:`Português (Brasil)`},{code:`zh-CN`,name:`Chinese (Simplified)`,nativeName:`简体中文`},{code:`zh-TW`,name:`Chinese (Traditional)`,nativeName:`繁體中文`},{code:`sv`,name:`Swedish`,nativeName:`Svenska`},{code:`tr`,name:`Turkish`,nativeName:`Türkçe`},{code:`ru`,name:`Russian`,nativeName:`Русский`},{code:`uk`,name:`Ukrainian`,nativeName:`Українська`}],Ct=e(l(),1),wt=`modulepreload`,Tt=function(e){return`/`+e},Et={},Dt=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=Tt(t,n),t in Et)return;Et[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:wt,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},Ot=/^(?:[a-z][a-z0-9+.-]*:|[\\/]{2})/i,kt=/^[\\/]{2}/;function At(e,t){return t+e.replace(/\\/g,`/`)}var jt=`popstate`;function Mt(e){return typeof e==`object`&&!!e&&`pathname`in e&&`search`in e&&`hash`in e&&`state`in e&&`key`in e}function Nt(e={}){function t(e,t){let n=t.state?.masked,{pathname:r,search:i,hash:a}=n||e.location;return Rt(``,{pathname:r,search:i,hash:a},t.state&&t.state.usr||null,t.state&&t.state.key||`default`,n?{pathname:e.location.pathname,search:e.location.search,hash:e.location.hash}:void 0)}function n(e,t){return typeof t==`string`?t:zt(t)}return Vt(t,n,null,e)}function Pt(e,t){if(e===!1||e==null)throw Error(t)}function Ft(e,t){if(!e){typeof console<`u`&&console.warn(t);try{throw Error(t)}catch{}}}function It(){return Math.random().toString(36).substring(2,10)}function Lt(e,t){return{usr:e.state,key:e.key,idx:t,masked:e.mask?{pathname:e.pathname,search:e.search,hash:e.hash}:void 0}}function Rt(e,t,n=null,r,i){return{pathname:typeof e==`string`?e:e.pathname,search:``,hash:``,...typeof t==`string`?Bt(t):t,state:n,key:t&&t.key||r||It(),mask:i}}function zt({pathname:e=`/`,search:t=``,hash:n=``}){return t&&t!==`?`&&(e+=t.charAt(0)===`?`?t:`?`+t),n&&n!==`#`&&(e+=n.charAt(0)===`#`?n:`#`+n),e}function Bt(e){let t={};if(e){let n=e.indexOf(`#`);n>=0&&(t.hash=e.substring(n),e=e.substring(0,n));let r=e.indexOf(`?`);r>=0&&(t.search=e.substring(r),e=e.substring(0,r)),e&&(t.pathname=e)}return t}function Vt(e,t,n,r={}){let{window:i=document.defaultView,v5Compat:a=!1}=r,o=i.history,s=`POP`,c=null,l=u();l??(l=0,o.replaceState({...o.state,idx:l},``));function u(){return(o.state||{idx:null}).idx}function d(){s=`POP`;let e=u(),t=e==null?null:e-l;l=e,c&&c({action:s,location:h.location,delta:t})}function f(e,t){s=`PUSH`;let r=Mt(e)?e:Rt(h.location,e,t);n&&n(r,e),l=u()+1;let d=Lt(r,l),f=h.createHref(r.mask||r);try{o.pushState(d,``,f)}catch(e){if(e instanceof DOMException&&e.name===`DataCloneError`)throw e;i.location.assign(f)}a&&c&&c({action:s,location:h.location,delta:1})}function p(e,t){s=`REPLACE`;let r=Mt(e)?e:Rt(h.location,e,t);n&&n(r,e),l=u();let i=Lt(r,l),d=h.createHref(r.mask||r);o.replaceState(i,``,d),a&&c&&c({action:s,location:h.location,delta:0})}function m(e){return Ht(i,e)}let h={get action(){return s},get location(){return e(i,o)},listen(e){if(c)throw Error(`A history only accepts one active listener`);return i.addEventListener(jt,d),c=e,()=>{i.removeEventListener(jt,d),c=null}},createHref(e){return t(i,e)},createURL:m,encodeLocation(e){let t=m(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:f,replace:p,go(e){return o.go(e)}};return h}function Ht(e,t,n=!1){let r=`http://localhost`;e&&(r=e.location.origin===`null`?e.location.href:e.location.origin),Pt(r,`No window.location.(origin|href) available to create URL`);let i=typeof t==`string`?t:zt(t);return i=i.replace(/ $/,`%20`),!n&&kt.test(i)&&(i=r+i),new URL(i,r)}function Ut(e,t,n=`/`){return Wt(e,t,n,!1)}function Wt(e,t,n,r,i){let a=un((typeof t==`string`?Bt(t):t).pathname||`/`,n);if(a==null)return null;let o=i??Kt(e),s=null,c=ln(a);for(let e=0;s==null&&e{let c={relativePath:s===void 0?e.path||``:s,caseSensitive:e.caseSensitive===!0,childrenIndex:a,route:e};if(c.relativePath.startsWith(`/`)){if(!c.relativePath.startsWith(r)&&o)return;Pt(c.relativePath.startsWith(r),`Absolute route path "${c.relativePath}" nested under path "${r}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),c.relativePath=c.relativePath.slice(r.length)}let l=vn([r,c.relativePath]),u=n.concat(c);e.children&&e.children.length>0&&(Pt(e.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${l}".`),qt(e.children,t,u,l,o)),!(e.path==null&&!e.index)&&t.push({path:l,score:W(l,e.index),routesMeta:u.map((e,t)=>{let[n,r]=cn(e.relativePath,e.caseSensitive,t===u.length-1);return{...e,matcher:n,compiledParams:r}})})};return e.forEach((e,t)=>{if(e.path===``||!e.path?.includes(`?`))a(e,t);else for(let n of Jt(e.path))a(e,t,!0,n)}),t}function Jt(e){let t=e.split(`/`);if(t.length===0)return[];let[n,...r]=t,i=n.endsWith(`?`),a=n.replace(/\?$/,``);if(r.length===0)return i?[a,``]:[a];let o=Jt(r.join(`/`)),s=[];return s.push(...o.map(e=>e===``?a:[a,e].join(`/`))),i&&s.push(...o),s.map(t=>e.startsWith(`/`)&&t===``?`/`:t)}function Yt(e){e.sort((e,t)=>e.score===t.score?rn(e.routesMeta.map(e=>e.childrenIndex),t.routesMeta.map(e=>e.childrenIndex)):t.score-e.score)}var Xt=/^:[\w-]+$/,Zt=3,Qt=2,$t=1,en=10,tn=-2,nn=e=>e===`*`;function W(e,t){let n=e.split(`/`),r=n.length;return n.some(nn)&&(r+=tn),t&&(r+=Qt),n.filter(e=>!nn(e)).reduce((e,t)=>e+(Xt.test(t)?Zt:t===``?$t:en),r)}function rn(e,t){return e.length===t.length&&e.slice(0,-1).every((e,n)=>e===t[n])?e[e.length-1]-t[t.length-1]:0}function an(e,t,n=!1){let{routesMeta:r}=e,i={},a=`/`,o=[];for(let e=0;e{if(t===`*`){let e=s[r]||``;o=a.slice(0,a.length-e.length).replace(/(.)\/+$/,`$1`)}let i=s[r];return n&&!i?e[t]=void 0:e[t]=(i||``).replace(/%2F/g,`/`),e},{}),pathname:a,pathnameBase:o,pattern:e}}function cn(e,t=!1,n=!0){Ft(e===`*`||!e.endsWith(`*`)||e.endsWith(`/*`),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,`/*`)}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,`/*`)}".`);let r=[],i=`^`+e.replace(/\/*\*?$/,``).replace(/^\/*/,`/`).replace(/[\\.*+^${}|()[\]]/g,`\\$&`).replace(/\/:([\w-]+)(\?)?/g,(e,t,n,i,a)=>{if(r.push({paramName:t,isOptional:n!=null}),n){let t=a.charAt(i+e.length);return t&&t!==`/`?`/([^\\/]*)`:`(?:/([^\\/]*))?`}return`/([^\\/]+)`}).replace(/\/([\w-]+)\?(\/|$)/g,`(/$1)?$2`);return e.endsWith(`*`)?(r.push({paramName:`*`}),i+=e===`*`||e===`/*`?`(.*)$`:`(?:\\/(.+)|\\/*)$`):n?i+=`\\/*$`:e!==``&&e!==`/`&&(i+=`(?:(?=\\/|$))`),[new RegExp(i,t?void 0:`i`),r]}function ln(e){try{return e.split(`/`).map(e=>decodeURIComponent(e).replace(/\//g,`%2F`)).join(`/`)}catch(t){return Ft(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function un(e,t){if(t===`/`)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith(`/`)?t.length-1:t.length,r=e.charAt(n);return r&&r!==`/`?null:e.slice(n)||`/`}function dn(e,t=`/`){let{pathname:n,search:r=``,hash:i=``}=typeof e==`string`?Bt(e):e,a;return n?(n=_n(n),a=n.startsWith(`/`)?fn(n.substring(1),`/`):fn(n,t)):a=t,{pathname:a,search:xn(r),hash:Sn(i)}}function fn(e,t){let n=yn(t).split(`/`);return e.split(`/`).forEach(e=>{e===`..`?n.length>1&&n.pop():e!==`.`&&n.push(e)}),n.length>1?n.join(`/`):`/`}function pn(e,t,n,r){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(r)}]. Please separate it out to the \`to.${n}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function mn(e){return e.filter((e,t)=>t===0||e.route.path&&e.route.path.length>0)}function hn(e){let t=mn(e);return t.map((e,n)=>n===t.length-1?e.pathname:e.pathnameBase)}function gn(e,t,n,r=!1){let i;typeof e==`string`?i=Bt(e):(i={...e},Pt(!i.pathname||!i.pathname.includes(`?`),pn(`?`,`pathname`,`search`,i)),Pt(!i.pathname||!i.pathname.includes(`#`),pn(`#`,`pathname`,`hash`,i)),Pt(!i.search||!i.search.includes(`#`),pn(`#`,`search`,`hash`,i)));let a=e===``||i.pathname===``,o=a?`/`:i.pathname,s;if(o==null)s=n;else{let e=t.length-1;if(!r&&o.startsWith(`..`)){let t=o.split(`/`);for(;t[0]===`..`;)t.shift(),--e;i.pathname=t.join(`/`)}s=e>=0?t[e]:`/`}let c=dn(i,s),l=o&&o!==`/`&&o.endsWith(`/`),u=(a||o===`.`)&&n.endsWith(`/`);return!c.pathname.endsWith(`/`)&&(l||u)&&(c.pathname+=`/`),c}var _n=e=>e.replace(/[\\/]{2,}/g,`/`),vn=e=>_n(e.join(`/`)),yn=e=>e.replace(/\/+$/,``),bn=e=>yn(e).replace(/^\/*/,`/`),xn=e=>!e||e===`?`?``:e.startsWith(`?`)?e:`?`+e,Sn=e=>!e||e===`#`?``:e.startsWith(`#`)?e:`#`+e,Cn=class{constructor(e,t,n,r=!1){this.status=e,this.statusText=t||``,this.internal=r,n instanceof Error?(this.data=n.toString(),this.error=n):this.data=n}};function wn(e){return e!=null&&typeof e.status==`number`&&typeof e.statusText==`string`&&typeof e.internal==`boolean`&&`data`in e}function Tn(e){return vn(e.map(e=>e.route.path).filter(Boolean))||`/`}var En=typeof window<`u`&&window.document!==void 0&&window.document.createElement!==void 0;function Dn(e,t){let n=e;if(typeof n!=`string`||!Ot.test(n))return{absoluteURL:void 0,isExternal:!1,to:n};let r=n,i=!1;if(En)try{let e=new URL(window.location.href),r=kt.test(n)?new URL(At(n,e.protocol)):new URL(n),a=un(r.pathname,t);r.origin===e.origin&&a!=null?n=a+r.search+r.hash:i=!0}catch{Ft(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:r,isExternal:i,to:n}}Object.getOwnPropertyNames(Object.prototype).sort().join(`\0`);var On=[`POST`,`PUT`,`PATCH`,`DELETE`];new Set(On);var kn=[`GET`,...On];new Set(kn);var An=[`about:`,`blob:`,`chrome:`,`chrome-untrusted:`,`content:`,`data:`,`devtools:`,`file:`,`filesystem:`,`javascript:`];function jn(e){try{return An.includes(new URL(e).protocol)}catch{return!1}}var Mn=f.createContext(null);Mn.displayName=`DataRouter`;var Nn=f.createContext(null);Nn.displayName=`DataRouterState`;var Pn=f.createContext(!1);function Fn(){return f.useContext(Pn)}var In=f.createContext({isTransitioning:!1});In.displayName=`ViewTransition`;var Ln=f.createContext(new Map);Ln.displayName=`Fetchers`;var Rn=f.createContext(null);Rn.displayName=`Await`;var zn=f.createContext(null);zn.displayName=`Navigation`;var Bn=f.createContext(null);Bn.displayName=`Location`;var Vn=f.createContext({outlet:null,matches:[],isDataRoute:!1});Vn.displayName=`Route`;var Hn=f.createContext(null);Hn.displayName=`RouteError`;var Un=`REACT_ROUTER_ERROR`,Wn=`REDIRECT`,Gn=`ROUTE_ERROR_RESPONSE`;function Kn(e){if(e.startsWith(`${Un}:${Wn}:{`))try{let t=JSON.parse(e.slice(28));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`&&typeof t.location==`string`&&typeof t.reloadDocument==`boolean`&&typeof t.replace==`boolean`)return t}catch{}}function qn(e){if(e.startsWith(`${Un}:${Gn}:{`))try{let t=JSON.parse(e.slice(40));if(typeof t==`object`&&t&&typeof t.status==`number`&&typeof t.statusText==`string`)return new Cn(t.status,t.statusText,t.data)}catch{}}function Jn(e,{relative:t}={}){Pt(Yn(),`useHref() may be used only in the context of a component.`);let{basename:n,navigator:r}=f.useContext(zn),{hash:i,pathname:a,search:o}=ar(e,{relative:t}),s=a;return n!==`/`&&(s=a===`/`?n:vn([n,a])),r.createHref({pathname:s,search:o,hash:i})}function Yn(){return f.useContext(Bn)!=null}function Xn(){return Pt(Yn(),`useLocation() may be used only in the context of a component.`),f.useContext(Bn).location}var Zn=`You should call navigate() in a React.useEffect(), not when your component is first rendered.`;function Qn(e){f.useContext(zn).static||f.useLayoutEffect(e)}function $n(){let{isDataRoute:e}=f.useContext(Vn);return e?wr():er()}function er(){Pt(Yn(),`useNavigate() may be used only in the context of a component.`);let e=f.useContext(Mn),{basename:t,navigator:n}=f.useContext(zn),{matches:r}=f.useContext(Vn),{pathname:i}=Xn(),a=JSON.stringify(hn(r)),o=f.useRef(!1);return Qn(()=>{o.current=!0}),f.useCallback((r,s={})=>{if(Ft(o.current,Zn),!o.current)return;if(typeof r==`number`){n.go(r);return}let c=gn(r,JSON.parse(a),i,s.relative===`path`);e==null&&t!==`/`&&(c.pathname=c.pathname===`/`?t:vn([t,c.pathname])),(s.replace?n.replace:n.push)(c,s.state,s)},[t,n,a,i,e])}var tr=f.createContext(null);function nr(){return f.useContext(tr)}function rr(e){let t=f.useContext(Vn).outlet;return f.useMemo(()=>t&&f.createElement(tr.Provider,{value:e},t),[t,e])}function ir(){let{matches:e}=f.useContext(Vn);return e[e.length-1]?.params??{}}function ar(e,{relative:t}={}){let{matches:n}=f.useContext(Vn),{pathname:r}=Xn(),i=JSON.stringify(hn(n));return f.useMemo(()=>gn(e,JSON.parse(i),r,t===`path`),[e,i,r,t])}function or(e,t){return sr(e,t)}function sr(e,t,n){Pt(Yn(),`useRoutes() may be used only in the context of a component.`);let{navigator:r}=f.useContext(zn),{matches:i}=f.useContext(Vn),a=i[i.length-1],o=a?a.params:{},s=a?a.pathname:`/`,c=a?a.pathnameBase:`/`,l=a&&a.route;{let e=l&&l.path||``;Er(s,!l||e.endsWith(`*`)||e.endsWith(`*?`),`You rendered descendant (or called \`useRoutes()\`) at "${s}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. -Please change the parent to .`)}let u=Xn(),d;if(t){let e=typeof t==`string`?Bt(t):t;Pt(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let p=d.pathname||`/`,m=p;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);m=`/`+p.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let h=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):Ut(e,{pathname:m});Ft(l||h!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),Ft(h==null||h[h.length-1].route.element!==void 0||h[h.length-1].route.Component!==void 0||h[h.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let g=mr(h&&h.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:vn([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:vn([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&g?f.createElement(Bn.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},g):g}function cr(){let e=Cr(),t=wn(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=f.createElement(f.Fragment,null,f.createElement(`p`,null,`💿 Hey developer 👋`),f.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,f.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,f.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),f.createElement(f.Fragment,null,f.createElement(`h2`,null,`Unexpected Application Error!`),f.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?f.createElement(`pre`,{style:i},n):null,o)}var lr=f.createElement(cr,null),ur=class extends f.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=qn(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:f.createElement(Vn.Provider,{value:this.props.routeContext},f.createElement(Hn.Provider,{value:e,children:this.props.component}));return this.context?f.createElement(fr,{error:e},t):t}};ur.contextType=Pn;var dr=new WeakMap;function fr({children:e,error:t}){let{basename:n}=f.useContext(zn);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=Kn(t.digest);if(e){let r=dr.get(t);if(r)throw r;let i=Dn(e.location,n),a=i.absoluteURL||i.to;if(jn(a))throw Error(`Invalid redirect location`);if(En&&!dr.get(t))if(i.isExternal||e.reloadDocument)window.location.href=a;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw dr.set(t,n),n}return f.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${a}`})}}return e}function pr({routeContext:e,match:t,children:n}){let r=f.useContext(Mn);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),f.createElement(Vn.Provider,{value:e},n)}function mr(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);Pt(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e=0?i.slice(0,s+1):[i[0]];break}}}}let c=n?.onError,l=r&&c?(e,t)=>{c(e,{location:r.location,params:r.matches?.[0]?.params??{},pattern:Tn(r.matches),errorInfo:t})}:void 0;return i.reduceRight((e,n,c)=>{let u,d=!1,p=null,m=null;r&&(u=a&&n.route.id?a[n.route.id]:void 0,p=n.route.errorElement||lr,o&&(s<0&&c===0?(Er(`route-fallback`,!1,"No `HydrateFallback` element provided to render during initial hydration"),d=!0,m=null):s===c&&(d=!0,m=n.route.hydrateFallbackElement||null)));let h=t.concat(i.slice(0,c+1)),g=()=>{let t;return t=u?p:d?m:n.route.Component?f.createElement(n.route.Component,null):n.route.element?n.route.element:e,f.createElement(pr,{match:n,routeContext:{outlet:e,matches:h,isDataRoute:r!=null},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||c===0)?f.createElement(ur,{location:r.location,revalidation:r.revalidation,component:p,error:u,children:g(),routeContext:{outlet:null,matches:h,isDataRoute:!0},onError:l}):g()},null)}function hr(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function gr(e){let t=f.useContext(Mn);return Pt(t,hr(e)),t}function _r(e){let t=f.useContext(Nn);return Pt(t,hr(e)),t}function vr(e){let t=f.useContext(Vn);return Pt(t,hr(e)),t}function yr(e){let t=vr(e),n=t.matches[t.matches.length-1];return Pt(n.route.id,`${e} can only be used on routes that contain a unique "id"`),n.route.id}function br(){return yr(`useRouteId`)}function xr(){let e=_r(`useNavigation`);return f.useMemo(()=>{let{matches:t,historyAction:n,...r}=e.navigation;return r},[e.navigation])}function Sr(){let{matches:e,loaderData:t}=_r(`useMatches`);return f.useMemo(()=>e.map(e=>Gt(e,t)),[e,t])}function Cr(){let e=f.useContext(Hn),t=_r(`useRouteError`),n=yr(`useRouteError`);return e===void 0?t.errors?.[n]:e}function wr(){let{router:e}=gr(`useNavigate`),t=yr(`useNavigate`),n=f.useRef(!1);return Qn(()=>{n.current=!0}),f.useCallback(async(r,i={})=>{Ft(n.current,Zn),n.current&&(typeof r==`number`?await e.navigate(r):await e.navigate(r,{fromRouteId:t,...i}))},[e,t])}var Tr={};function Er(e,t,n){!t&&!Tr[e]&&(Tr[e]=!0,Ft(!1,n))}f.memo(Dr);function Dr({routes:e,manifest:t,future:n,state:r,isStatic:i,onError:a}){return sr(e,void 0,{manifest:t,state:r,isStatic:i,onError:a,future:n})}function Or({to:e,replace:t,state:n,relative:r}){Pt(Yn(),` may be used only in the context of a component.`);let{static:i}=f.useContext(zn);Ft(!i,` must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.`);let{matches:a}=f.useContext(Vn),{pathname:o}=Xn(),s=$n(),c=gn(e,hn(a),o,r===`path`),l=JSON.stringify(c);return f.useEffect(()=>{s(JSON.parse(l),{replace:t,state:n,relative:r})},[s,l,r,t,n]),null}function kr(e){return rr(e.context)}function Ar(e){Pt(!1,`A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .`)}function jr({basename:e=`/`,children:t=null,location:n,navigationType:r=`POP`,navigator:i,static:a=!1,useTransitions:o}){Pt(!Yn(),`You cannot render a inside another . You should never have more than one in your app.`);let s=e.replace(/^\/*/,`/`),c=f.useMemo(()=>({basename:s,navigator:i,static:a,useTransitions:o,future:{}}),[s,i,a,o]);typeof n==`string`&&(n=Bt(n));let{pathname:l=`/`,search:u=``,hash:d=``,state:p=null,key:m=`default`,mask:h}=n,g=f.useMemo(()=>{let e=un(l,s);return e==null?null:{location:{pathname:e,search:u,hash:d,state:p,key:m,mask:h},navigationType:r}},[s,l,u,d,p,m,r,h]);return Ft(g!=null,` is not able to match the URL "${l}${u}${d}" because it does not start with the basename, so the won't render anything.`),g==null?null:f.createElement(zn.Provider,{value:c},f.createElement(Bn.Provider,{children:t,value:g}))}function Mr({children:e,location:t}){return or(Nr(e),t)}f.Component;function Nr(e,t=[]){let n=[];return f.Children.forEach(e,(e,r)=>{if(!f.isValidElement(e))return;let i=[...t,r];if(e.type===f.Fragment){n.push.apply(n,Nr(e.props.children,i));return}Pt(e.type===Ar,`[${typeof e.type==`string`?e.type:e.type.name}] is not a component. All component children of must be a or `),Pt(!e.props.index||!e.props.children,`An index route cannot have child routes.`);let a={id:e.props.id||i.join(`-`),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,middleware:e.props.middleware,loader:e.props.loader,action:e.props.action,hydrateFallbackElement:e.props.hydrateFallbackElement,HydrateFallback:e.props.HydrateFallback,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:e.props.hasErrorBoundary===!0||e.props.ErrorBoundary!=null||e.props.errorElement!=null,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(a.children=Nr(e.props.children,i)),n.push(a)}),n}var Pr=`get`,Fr=`application/x-www-form-urlencoded`;function Ir(e){return typeof HTMLElement<`u`&&e instanceof HTMLElement}function Lr(e){return Ir(e)&&e.tagName.toLowerCase()===`button`}function Rr(e){return Ir(e)&&e.tagName.toLowerCase()===`form`}function zr(e){return Ir(e)&&e.tagName.toLowerCase()===`input`}function Br(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Vr(e,t){return e.button===0&&(!t||t===`_self`)&&!Br(e)}function Hr(e=``){return new URLSearchParams(typeof e==`string`||Array.isArray(e)||e instanceof URLSearchParams?e:Object.keys(e).reduce((t,n)=>{let r=e[n];return t.concat(Array.isArray(r)?r.map(e=>[n,e]):[[n,r]])},[]))}function Ur(e,t){let n=Hr(e);return t&&t.forEach((e,r)=>{n.has(r)||t.getAll(r).forEach(e=>{n.append(r,e)})}),n}var Wr=null;function Gr(){if(Wr===null)try{new FormData(document.createElement(`form`),0),Wr=!1}catch{Wr=!0}return Wr}var Kr=new Set([`application/x-www-form-urlencoded`,`multipart/form-data`,`text/plain`]);function qr(e){return e!=null&&!Kr.has(e)?(Ft(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Fr}"`),null):e}function Jr(e,t){let n,r,i,a,o;if(Rr(e)){let o=e.getAttribute(`action`);r=o?un(o,t):null,n=e.getAttribute(`method`)||Pr,i=qr(e.getAttribute(`enctype`))||Fr,a=new FormData(e)}else if(Lr(e)||zr(e)&&(e.type===`submit`||e.type===`image`)){let o=e.form;if(o==null)throw Error(`Cannot submit a