From 9a86a37cef1eb831ba4b204dc76d87bd989907e5 Mon Sep 17 00:00:00 2001 From: ManuelW77 Date: Tue, 11 Aug 2026 12:23:02 +0200 Subject: [PATCH] Add FilaMan spool card FilaMan is an NFC/RFID filament tracking system with its own Moonraker component (the separate filaman Bespok3d plugin), independent of Spoolman. Adds a small card, bottom right corner, showing the filament name and colour assigned to each extruder (falling back to the bare spool id if that lookup fails), a button to clear the assignment, and a repush button that resends every assigned spool to the printer, the same thing that already happens once at boot. The card polls the filaman component's own REST endpoints directly (GET .../status, POST .../proxy for spool detail, POST .../repush), so it needs no Vuex or dialog patch, only a script tag and one new file. Deliberately no spool picker: assigning a spool to an extruder stays FilaMan's own job (an NFC tap, or its own app). Inert on a printer without the filaman plugin installed: the card's first request fails and it never appears. The repush button needs filaman 0.2.0 or newer (its /repush endpoint); on an older filaman it silently does nothing. Fluidd stays v1.37.3, version 0.1.5 to 0.1.6. Signed-off-by: ManuelW77 --- fluidd/doc/CHANGELOG.md | 14 ++ fluidd/doc/README.md | 19 ++ fluidd/files/fluidd/b3d-filaman-card.js | 259 ++++++++++++++++++++++++ fluidd/files/fluidd/index.html | 2 +- fluidd/files/fluidd/sw.js | 2 +- fluidd/manifest.json | 6 +- fluidd/patches/filaman-card.js | 259 ++++++++++++++++++++++++ fluidd/scripts/patch-fluidd.sh | 15 +- 8 files changed, 570 insertions(+), 6 deletions(-) create mode 100644 fluidd/files/fluidd/b3d-filaman-card.js create mode 100644 fluidd/patches/filaman-card.js diff --git a/fluidd/doc/CHANGELOG.md b/fluidd/doc/CHANGELOG.md index 466e55e..6b77359 100644 --- a/fluidd/doc/CHANGELOG.md +++ b/fluidd/doc/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.1.6 + +- New FilaMan spool card, bottom right corner, showing the filament name and colour assigned to + each extruder (falling back to the bare spool id if the filament lookup fails), a button to + clear that assignment, and a button to manually repeat the startup repush instead of restarting + Moonraker. No spool picker: assigning a spool to an extruder stays FilaMan's own job. FilaMan is + an NFC/RFID filament tracking system with its own Moonraker component (the separate filaman + Bespok3d plugin), independent of Spoolman, so the card polls that component's own endpoints + directly rather than reading Fluidd's Spoolman panel or its Vuex store. Adds one script tag and + one new file to the vendored bundle; no Vuex or dialog patch, and no manifest or config change. + Inert on a printer without the filaman plugin installed: the card's first request fails and it + never appears. The repush button needs filaman 0.2.0 or newer (its `/repush` endpoint); on an + older filaman it silently does nothing. Fluidd stays v1.37.3. + ## 0.1.5 - Vendored Fluidd bumped v1.37.2 to v1.37.3. Upstream adds an AFC print-start dialog, a diff --git a/fluidd/doc/README.md b/fluidd/doc/README.md index 1a6322d..be7a77a 100644 --- a/fluidd/doc/README.md +++ b/fluidd/doc/README.md @@ -9,6 +9,7 @@ Swaps the Snapmaker-shipped Fluidd web UI for the current upstream release. - Tool-colour preview. - A modern bed-mesh visualizer. - A print-start dialog that maps the file's tools onto your AFC lanes before the print begins. +- A small FilaMan spool card, if the separate filaman Moonraker plugin is installed. See below. ## Mapping lanes for a print sent from the slicer @@ -34,6 +35,24 @@ printer actually has. Nothing is removed from the printer: the tools are still t still call them. The count comes from the printer, so a machine with a different number of lanes shows its own. Reload the page after changing the setting. +## FilaMan spool card + +FilaMan is an NFC/RFID filament tracking system with its own Moonraker component, independent of +Spoolman. With the separate filaman Bespok3d plugin installed, a small card in the bottom right +corner shows the filament assigned to each extruder (name and colour, falling back to the bare +spool id if that lookup fails), a button to clear the assignment, and a **repush** button that +resends every assigned spool to the printer, the same thing that already happens once at boot. +Use it if a channel still shows as unknown after startup, instead of restarting Moonraker. + +There is no spool picker on this card: assigning a spool to an extruder is FilaMan's own job (an +NFC tap, or its own app), not something this card does for you. + +The card talks directly to the filaman component's own endpoints; it does not use Fluidd's +Spoolman panel. The repush button needs filaman 0.2.0 or newer; on an older filaman it silently +does nothing. + +Nothing appears without that plugin installed: the card's first request fails and it never shows. + ## Configuration - **Fluidd port** (default `80`): the port Fluidd is served on. diff --git a/fluidd/files/fluidd/b3d-filaman-card.js b/fluidd/files/fluidd/b3d-filaman-card.js new file mode 100644 index 0000000..c930313 --- /dev/null +++ b/fluidd/files/fluidd/b3d-filaman-card.js @@ -0,0 +1,259 @@ +// A small floating card showing each extruder's assigned FilaMan spool, with a button to clear it +// and a button to manually repeat the startup repush. +// +// FilaMan is an NFC/RFID filament tracking system with its own Moonraker component (the filaman +// Bespok3d plugin), independent of Spoolman. It talks to Fluidd's built in Spoolman panel not at +// all, since that panel only lights up for a component actually named spoolman. This card is its +// own thing instead: it polls the filaman component's own REST endpoints directly and renders +// itself, without reading or writing anything in Fluidd's Vuex store. Deliberately no spool +// picker here: assigning a spool to an extruder is FilaMan's own job (an NFC tap, or its own +// app), this card only shows and clears what is already assigned. +// +// Inert on a printer without the filaman Moonraker component: the first status request 404s (or +// otherwise fails), the card is never created, and nothing polls again until the next page load. +// +// This file is copied into the vendored bundle by scripts/patch-fluidd.sh, which also adds the +// script tag that loads it. Re-vendoring re-applies both. + +(function bespok3dFilamanCard() { + var STATUS_PATH = '/server/filaman/status' + var SPOOL_ID_PATH = '/server/filaman/spool_id' + var REPUSH_PATH = '/server/filaman/repush' + var PROXY_PATH = '/server/filaman/proxy' + var POLL_INTERVAL_MS = 5000 + var CARD_ELEMENT_ID = 'b3d-filaman-card' + + var spoolDetailsById = {} + var spoolDetailFetchesInFlight = {} + + function requestJson(path, requestInit) { + return window.fetch(path, requestInit).then(function readBody(response) { + if (!response.ok) throw new Error('filaman request failed: ' + response.status) + return response.json() + }) + } + + function unwrapMoonrakerResult(body) { + return body.result || body + } + + function fetchStatus() { + return requestJson(STATUS_PATH, { credentials: 'same-origin' }).then(unwrapMoonrakerResult) + } + + function clearExtruderSpool(extruderName) { + var query = '?extruder=' + encodeURIComponent(extruderName) + return requestJson(SPOOL_ID_PATH + query, { method: 'POST', credentials: 'same-origin' }) + } + + function repushAssignedSpools() { + return requestJson(REPUSH_PATH, { method: 'POST', credentials: 'same-origin' }) + } + + // FilaMan's REST API nests a spool's consumption log under /api/v1/spools/{id}/consumptions, + // so the spool itself is expected at /api/v1/spools/{id}. Routed through the filaman + // component's own proxy endpoint, which already holds the credentials this card does not have. + function fetchSpoolDetail(spoolId) { + return requestJson(PROXY_PATH, { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + request_method: 'GET', + path: '/api/v1/spools/' + spoolId, + use_v2_response: true + }) + }) + .then(unwrapMoonrakerResult) + .then(function readProxyResult(proxyResult) { + if (proxyResult.error) throw new Error(proxyResult.error.message || 'proxy error') + return spoolDetailFromApiSpool(proxyResult.response) + }) + } + + function normalizedColorHex(hex) { + return hex.charAt(0) === '#' ? hex : '#' + hex + } + + function spoolDetailFromApiSpool(apiSpool) { + var filament = apiSpool.filament || {} + var colorEntry = (filament.colors || [])[0] + var initialTotalWeightG = filament.initial_total_weight_g != null + ? filament.initial_total_weight_g + : apiSpool.initial_total_weight_g + var emptySpoolWeightG = apiSpool.empty_spool_weight_g + var initialWeightG = (initialTotalWeightG != null && emptySpoolWeightG != null) + ? Math.max(initialTotalWeightG - emptySpoolWeightG, 0) + : filament.raw_material_weight_g + + return { + name: filament.designation || null, + colorHex: colorEntry && colorEntry.color && colorEntry.color.hex_code + ? normalizedColorHex(colorEntry.color.hex_code) + : null, + remainingWeightG: apiSpool.remaining_weight_g != null ? apiSpool.remaining_weight_g : null, + initialWeightG: initialWeightG != null ? initialWeightG : null + } + } + + function spoolDetail(spoolId) { + var cached = spoolDetailsById[spoolId] + if (cached) return cached + + if (!spoolDetailFetchesInFlight[spoolId]) { + spoolDetailFetchesInFlight[spoolId] = true + fetchSpoolDetail(spoolId).then(function cacheDetail(detail) { + delete spoolDetailFetchesInFlight[spoolId] + spoolDetailsById[spoolId] = detail + poll() + }, function keepShowingSpoolId() { + // Detail lookup failed (backend unreachable, or this FilaMan version has no per-spool + // GET). Not fatal: the row below falls back to the bare spool id. Retried next poll. + delete spoolDetailFetchesInFlight[spoolId] + }) + } + return null + } + + function cardElement() { + var existing = document.getElementById(CARD_ELEMENT_ID) + if (existing) return existing + + var card = document.createElement('div') + card.id = CARD_ELEMENT_ID + card.style.cssText = + 'position:fixed;right:12px;bottom:12px;z-index:9999;' + + 'background:#1e1e20;color:#fff;border:1px solid #3a3a3d;border-radius:8px;' + + 'padding:10px 12px;font:13px/1.4 sans-serif;min-width:200px;max-width:300px;' + + 'box-shadow:0 2px 10px rgba(0,0,0,.5)' + document.body.appendChild(card) + return card + } + + function removeCardElement() { + var existing = document.getElementById(CARD_ELEMENT_ID) + if (existing) existing.remove() + } + + function iconButtonElement(label, onClick) { + var button = document.createElement('button') + button.type = 'button' + button.textContent = label + button.style.cssText = + 'background:none;border:1px solid #555;border-radius:4px;' + + 'color:#fff;cursor:pointer;font-size:11px;padding:1px 6px' + button.addEventListener('click', onClick) + return button + } + + function headingElement() { + var heading = document.createElement('div') + heading.style.cssText = + 'display:flex;align-items:center;justify-content:space-between;margin-bottom:4px' + + var title = document.createElement('span') + title.style.cssText = 'font-weight:600' + title.textContent = 'FilaMan' + heading.appendChild(title) + + heading.appendChild(iconButtonElement('repush', function onRepushClicked() { + repushAssignedSpools().then(poll, poll) + })) + return heading + } + + function colorDotElement(colorHex) { + var dot = document.createElement('span') + dot.style.cssText = + 'display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;' + + 'background:' + colorHex + ';border:1px solid rgba(255,255,255,.3)' + return dot + } + + function weightFractionText(detail) { + if (detail.remainingWeightG == null || detail.initialWeightG == null) return null + return Math.round(detail.remainingWeightG) + 'g / ' + Math.round(detail.initialWeightG) + 'g' + } + + function spoolRowElement(extruderName, spoolId) { + var row = document.createElement('div') + row.style.cssText = 'margin:4px 0' + + var mainLine = document.createElement('div') + mainLine.style.cssText = 'display:flex;align-items:center;justify-content:space-between' + + var nameCell = document.createElement('span') + nameCell.style.cssText = 'display:flex;align-items:center;overflow:hidden' + + var extruderLabel = document.createElement('strong') + extruderLabel.style.cssText = 'margin-right:6px' + extruderLabel.textContent = extruderName + nameCell.appendChild(extruderLabel) + + var detail = spoolId == null ? null : spoolDetail(spoolId) + var nameText = document.createElement('span') + nameText.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap' + if (spoolId == null) { + nameText.textContent = 'no spool' + nameText.style.opacity = '.7' + } else { + if (detail && detail.colorHex) nameCell.appendChild(colorDotElement(detail.colorHex)) + nameText.textContent = (detail && detail.name) || ('#' + spoolId) + } + nameCell.appendChild(nameText) + mainLine.appendChild(nameCell) + + if (spoolId != null) { + mainLine.appendChild(iconButtonElement('clear', function onClearClicked() { + clearExtruderSpool(extruderName).then(poll, poll) + })) + } + row.appendChild(mainLine) + + var weightText = detail ? weightFractionText(detail) : null + if (weightText) { + var weightLine = document.createElement('div') + weightLine.style.cssText = 'opacity:.7;font-size:11px' + weightLine.textContent = weightText + row.appendChild(weightLine) + } + + return row + } + + function renderStatus(status) { + var extruderSpools = status.extruder_spools || {} + var extruderNames = Object.keys(extruderSpools) + var card = cardElement() + card.textContent = '' + card.appendChild(headingElement()) + + if (!extruderNames.length) { + var emptyState = document.createElement('div') + emptyState.style.cssText = 'opacity:.7' + emptyState.textContent = 'no extruders reported' + card.appendChild(emptyState) + return + } + extruderNames.forEach(function appendRow(extruderName) { + card.appendChild(spoolRowElement(extruderName, extruderSpools[extruderName])) + }) + } + + function poll() { + fetchStatus().then(renderStatus, removeCardElement) + } + + function whenDocumentIsReady(callback) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback) + return + } + callback() + } + + whenDocumentIsReady(function start() { + poll() + window.setInterval(poll, POLL_INTERVAL_MS) + }) +})() diff --git a/fluidd/files/fluidd/index.html b/fluidd/files/fluidd/index.html index 5c056c2..1b4b044 100644 --- a/fluidd/files/fluidd/index.html +++ b/fluidd/files/fluidd/index.html @@ -58,7 +58,7 @@ -
+
diff --git a/fluidd/files/fluidd/sw.js b/fluidd/files/fluidd/sw.js index 089daac..b9d9aab 100644 --- a/fluidd/files/fluidd/sw.js +++ b/fluidd/files/fluidd/sw.js @@ -1 +1 @@ -try{self[`workbox:core:7.4.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.4.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function ee(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function te(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=ee()?i.body:await i.blob();return new Response(s,o)}var ne=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function f(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function re(e,t,n,r){let i=f(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===f(t.url,n))return e.match(t,r)}var ie=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},ae=new Set;async function oe(){for(let e of ae)await e()}function se(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.4.0`]&&_()}catch{}function p(e){return typeof e==`string`?new Request(e):e}var m=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new ie,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=p(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=p(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=p(e);await se(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:ne(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await re(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await oe(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=p(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},h=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new m(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},g=class e extends h{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};g.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},g.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await te(e):e}};var ce=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new g({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},v,y=()=>(v||=new ce,v);try{self[`workbox:routing:7.4.0`]&&_()}catch{}var b=e=>e&&typeof e==`object`?e:{handle:e},x=class{constructor(e,t,n=`GET`){this.handler=b(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=b(e)}},le=class extends x{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},ue=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},S,de=()=>(S||(S=new ue,S.addFetchListener(),S.addCacheListener()),S);function C(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new x(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new le(e,n,r);else if(typeof e==`function`)i=new x(e,n,r);else if(e instanceof x)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return de().registerRoute(i),i}function w(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*T(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=w(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var E=class extends x{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of T(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function D(e){C(new E(y(),e))}var O=`-precache-`,k=async(e,t=O)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function A(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(k(t).then(e=>{}))}))}function j(e){return y().createHandlerBoundToURL(e)}function M(e){y().precache(e)}function N(e,t){M(e),D(t)}var P=class extends x{constructor(e,{allowlist:t=[/./],denylist:n=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=n}_match({url:e,request:t}){if(t&&t.mode!==`navigate`)return!1;let n=e.pathname+e.search;for(let e of this._denylist)if(e.test(n))return!1;return!!this._allowlist.some(e=>e.test(n))}},F={cacheWillUpdate:async({response:e})=>e.status===200||e.status===0?e:null},I=class extends h{constructor(e={}){super(e),this.plugins.some(e=>`cacheWillUpdate`in e)||this.plugins.unshift(F)}async _handle(e,n){let r=n.fetchAndCachePut(e).catch(()=>{});n.waitUntil(r);let i=await n.cacheMatch(e),a;if(!i)try{i=await r}catch(e){e instanceof Error&&(a=e)}if(!i)throw new t(`no-response`,{url:e.url,error:a});return i}};try{self[`workbox:cacheable-response:7.4.0`]&&_()}catch{}var L=(e,t)=>t.some(t=>e instanceof t),R,z;function fe(){return R||=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]}function B(){return z||=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey]}var V=new WeakMap,H=new WeakMap,U=new WeakMap,W=new WeakMap,G=new WeakMap;function pe(e){let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`success`,i),e.removeEventListener(`error`,a)},i=()=>{t(q(e.result)),r()},a=()=>{n(e.error),r()};e.addEventListener(`success`,i),e.addEventListener(`error`,a)});return t.then(t=>{t instanceof IDBCursor&&V.set(t,e)}).catch(()=>{}),G.set(t,e),t}function me(e){if(H.has(e))return;let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`complete`,i),e.removeEventListener(`error`,a),e.removeEventListener(`abort`,a)},i=()=>{t(),r()},a=()=>{n(e.error||new DOMException(`AbortError`,`AbortError`)),r()};e.addEventListener(`complete`,i),e.addEventListener(`error`,a),e.addEventListener(`abort`,a)});H.set(e,t)}var K={get(e,t,n){if(e instanceof IDBTransaction){if(t===`done`)return H.get(e);if(t===`objectStoreNames`)return e.objectStoreNames||U.get(e);if(t===`store`)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return q(e[t])},set(e,t,n){return e[t]=n,!0},has(e,t){return e instanceof IDBTransaction&&(t===`done`||t===`store`)||t in e}};function he(e){K=e(K)}function ge(e){return e===IDBDatabase.prototype.transaction&&!(`objectStoreNames`in IDBTransaction.prototype)?function(t,...n){let r=e.call(J(this),t,...n);return U.set(r,t.sort?t.sort():[t]),q(r)}:B().includes(e)?function(...t){return e.apply(J(this),t),q(V.get(this))}:function(...t){return q(e.apply(J(this),t))}}function _e(e){return typeof e==`function`?ge(e):(e instanceof IDBTransaction&&me(e),L(e,fe())?new Proxy(e,K):e)}function q(e){if(e instanceof IDBRequest)return pe(e);if(W.has(e))return W.get(e);let t=_e(e);return t!==e&&(W.set(e,t),G.set(t,e)),t}var J=e=>G.get(e),ve=[`get`,`getKey`,`getAll`,`getAllKeys`,`count`],Y=[`put`,`add`,`delete`,`clear`],X=new Map;function Z(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t==`string`))return;if(X.get(t))return X.get(t);let n=t.replace(/FromIndex$/,``),r=t!==n,i=Y.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!(i||ve.includes(n)))return;let a=async function(e,...t){let a=this.transaction(e,i?`readwrite`:`readonly`),o=a.store;return r&&(o=o.index(t.shift())),(await Promise.all([o[n](...t),i&&a.done]))[0]};return X.set(t,a),a}he(e=>({...e,get:(t,n,r)=>Z(t,n)||e.get(t,n,r),has:(t,n)=>!!Z(t,n)||e.has(t,n)}));try{self[`workbox:expiration:7.4.0`]&&_()}catch{}try{self[`workbox:recipes:7.4.0`]&&_()}catch{}function ye(e){self.addEventListener(`install`,t=>{let n=e.urls.map(n=>e.strategy.handleAll({event:t,request:new Request(n)})[1]);t.waitUntil(Promise.all(n))})}self.addEventListener(`message`,e=>{e.data&&e.data.type===`SKIP_WAITING`&&self.skipWaiting()}),N([{"revision":"b3d-index-html-1","url":"index.html"},{"revision":null,"url":"assets/zh-HK-_kY26ddi.js"},{"revision":null,"url":"assets/zh-CN-Ci04HV7N.js"},{"revision":null,"url":"assets/workbox-window.prod.es5-Bd17z0YL.js"},{"revision":null,"url":"assets/vue.runtime.esm-uXCn7sOw.js"},{"revision":null,"url":"assets/vue-echarts-chunk-BZd0vyDY.js"},{"revision":null,"url":"assets/virtual_pwa-register-VxpNN5l1.js"},{"revision":null,"url":"assets/v4-DDdyfk2q.js"},{"revision":null,"url":"assets/uk-CrpPMn14.js"},{"revision":null,"url":"assets/tr-BjhqQozH.js"},{"revision":null,"url":"assets/toString-BN36OUXd.js"},{"revision":null,"url":"assets/th-DOwCrGXH.js"},{"revision":null,"url":"assets/ta-C3zJZejv.js"},{"revision":null,"url":"assets/sv-C7gGT_f4.js"},{"revision":null,"url":"assets/state-D95nW690.js"},{"revision":null,"url":"assets/state-D5EbeaNv.js"},{"revision":"b3d-cachebust-1","url":"assets/socketActions-D7Vpckss.js"},{"revision":null,"url":"assets/sleep-CEQq2wgp.js"},{"revision":null,"url":"assets/sl-BKmRYTmo.js"},{"revision":null,"url":"assets/setupMonaco-BsspMbCi.js"},{"revision":null,"url":"assets/services-DkE0LGL2.js"},{"revision":null,"url":"assets/sandboxedEval.worker-CMwFLJDA.js"},{"revision":null,"url":"assets/ru-CX6SFs1w.js"},{"revision":null,"url":"assets/rolldown-runtime-CNC7AqOf.js"},{"revision":null,"url":"assets/roboto-vietnamese-700-normal-kpkdMgbf.woff"},{"revision":null,"url":"assets/roboto-vietnamese-700-normal-BEVeWqJt.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-500-normal-BgoYVz-9.woff"},{"revision":null,"url":"assets/roboto-vietnamese-500-normal-B3ncpOoB.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-400-normal-D2PTxGxD.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-400-normal-Bf76hAzZ.woff"},{"revision":null,"url":"assets/roboto-vietnamese-300-normal-CYjuQheQ.woff"},{"revision":null,"url":"assets/roboto-vietnamese-300-normal-BPvXm_f1.woff2"},{"revision":null,"url":"assets/roboto-symbols-700-normal-C59U6HqI.woff"},{"revision":null,"url":"assets/roboto-symbols-700-normal-BiFDindJ.woff2"},{"revision":null,"url":"assets/roboto-symbols-500-normal-DFnofPUt.woff"},{"revision":null,"url":"assets/roboto-symbols-500-normal-B_CZKVJS.woff2"},{"revision":null,"url":"assets/roboto-symbols-400-normal-CB1Ce4Gk.woff2"},{"revision":null,"url":"assets/roboto-symbols-400-normal-C7tGlxgb.woff"},{"revision":null,"url":"assets/roboto-symbols-300-normal-DDU7avhj.woff2"},{"revision":null,"url":"assets/roboto-symbols-300-normal-CBIeSvs3.woff"},{"revision":null,"url":"assets/roboto-math-700-normal-DbhUef31.woff"},{"revision":null,"url":"assets/roboto-math-700-normal-B8YqGHVc.woff2"},{"revision":null,"url":"assets/roboto-math-500-normal-C4NU9gLX.woff2"},{"revision":null,"url":"assets/roboto-math-500-normal-BNGXE_xU.woff"},{"revision":null,"url":"assets/roboto-math-400-normal-D6sS4O5l.woff"},{"revision":null,"url":"assets/roboto-math-400-normal-BEFej5gc.woff2"},{"revision":null,"url":"assets/roboto-math-300-normal-CkwMvXpj.woff"},{"revision":null,"url":"assets/roboto-math-300-normal-5dF_7mZP.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-700-normal-DSBUz0N1.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-700-normal-BDePAh3g.woff"},{"revision":null,"url":"assets/roboto-latin-ext-500-normal-pMCM9Ixg.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-500-normal-C1cK9xmS.woff"},{"revision":null,"url":"assets/roboto-latin-ext-400-normal-CIveymTr.woff"},{"revision":null,"url":"assets/roboto-latin-ext-400-normal-C3tdtHj3.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-300-normal-B90pq-BC.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-300-normal-B0-sTZUp.woff"},{"revision":null,"url":"assets/roboto-latin-700-normal-YuyVweIx.woff"},{"revision":null,"url":"assets/roboto-latin-700-normal-BZpUvMxY.woff2"},{"revision":null,"url":"assets/roboto-latin-500-normal-fsTFPL3E.woff"},{"revision":null,"url":"assets/roboto-latin-500-normal-7RbcRiD8.woff2"},{"revision":null,"url":"assets/roboto-latin-400-normal-D27F5sTu.woff"},{"revision":null,"url":"assets/roboto-latin-400-normal-BqEyEoaF.woff2"},{"revision":null,"url":"assets/roboto-latin-300-normal-Dqn3NW0Y.woff"},{"revision":null,"url":"assets/roboto-latin-300-normal-CCzlftfr.woff2"},{"revision":null,"url":"assets/roboto-greek-700-normal-xQsEWSIA.woff"},{"revision":null,"url":"assets/roboto-greek-700-normal-0aHWxGLu.woff2"},{"revision":null,"url":"assets/roboto-greek-500-normal-XBIQOowA.woff"},{"revision":null,"url":"assets/roboto-greek-500-normal-C9AnhcmC.woff2"},{"revision":null,"url":"assets/roboto-greek-400-normal-ai2Z1K3C.woff2"},{"revision":null,"url":"assets/roboto-greek-400-normal-D904r64h.woff"},{"revision":null,"url":"assets/roboto-greek-300-normal-xS8CB6LN.woff"},{"revision":null,"url":"assets/roboto-greek-300-normal-DJEM9B4Z.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-700-normal-DmFxo5wj.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-700-normal-BWOtdzfy.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-500-normal-SCgicl0l.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-500-normal-BWC_xYeb.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-400-normal-qHufge6k.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-400-normal-DhDk0Xwi.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-300-normal-DIxttMbC.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-300-normal-C0UYUNTV.woff"},{"revision":null,"url":"assets/roboto-cyrillic-700-normal-D8g9KsRf.woff"},{"revision":null,"url":"assets/roboto-cyrillic-700-normal-C2o7G-SM.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-500-normal-CLao9AfR.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-500-normal-C8ZOEKN4.woff"},{"revision":null,"url":"assets/roboto-cyrillic-400-normal-CBPI_iaY.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-400-normal-BepgbcUK.woff"},{"revision":null,"url":"assets/roboto-cyrillic-300-normal-DzUz0kzv.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-300-normal-D0x1uCa7.woff"},{"revision":null,"url":"assets/raleway-vietnamese-400-normal-CTw6K1Xj.woff2"},{"revision":null,"url":"assets/raleway-vietnamese-400-normal-CTqj18iX.woff"},{"revision":null,"url":"assets/raleway-latin-ext-400-normal-DoUy7GWe.woff"},{"revision":null,"url":"assets/raleway-latin-ext-400-normal-B4d0sYmR.woff2"},{"revision":null,"url":"assets/raleway-latin-400-normal-sMcq1OIP.woff"},{"revision":null,"url":"assets/raleway-latin-400-normal-C5eIEfLm.woff2"},{"revision":null,"url":"assets/raleway-cyrillic-ext-400-normal-zbv6uFvq.woff2"},{"revision":null,"url":"assets/raleway-cyrillic-ext-400-normal-QD38Acpa.woff"},{"revision":null,"url":"assets/raleway-cyrillic-400-normal-BOk4FNQ-.woff"},{"revision":null,"url":"assets/raleway-cyrillic-400-normal-B1ZxqHSH.woff2"},{"revision":null,"url":"assets/qr-scanner-worker.min-CJaXo3yu.js"},{"revision":null,"url":"assets/pt_BR-KiT3ATS9.js"},{"revision":null,"url":"assets/pt-BaBQRXg8.js"},{"revision":null,"url":"assets/preload-helper-HclGiUj8.js"},{"revision":null,"url":"assets/pl-Cf61CMqC.js"},{"revision":null,"url":"assets/parseGcode.worker-BtTjVWHs.js"},{"revision":null,"url":"assets/nl-6huzaAKa.js"},{"revision":null,"url":"assets/monacoFoldingRangesWorker-D4J5SrDs.js"},{"revision":null,"url":"assets/monacoDocumentSymbolsWorker-B1RvxTw6.js"},{"revision":null,"url":"assets/monacoCodeLensWorker-CeGXjSxG.js"},{"revision":null,"url":"assets/module-DQ2W7G90.js"},{"revision":null,"url":"assets/mjpegStream.worker-CkFbgHTR.js"},{"revision":null,"url":"assets/markdown-Cimd5fb3.js"},{"revision":null,"url":"assets/lspLanguageFeatures-D_J5eBm-.js"},{"revision":null,"url":"assets/lib-Cm30cL1j.css"},{"revision":null,"url":"assets/lib-BPv9EiKI.js"},{"revision":null,"url":"assets/ko-Djr-uWtG.js"},{"revision":null,"url":"assets/jsonMode-C5YSZh6U.js"},{"revision":null,"url":"assets/json.worker-Oa9UrkKE.js"},{"revision":null,"url":"assets/ja-mDyLPXjN.js"},{"revision":null,"url":"assets/it-ODE9PgDu.js"},{"revision":null,"url":"assets/isArray-BfeOG1E2.js"},{"revision":null,"url":"assets/is-key-of-eCsGvpyb.js"},{"revision":null,"url":"assets/index-Dtw9dMof.css"},{"revision":null,"url":"assets/index-Bmc36AIW.js"},{"revision":null,"url":"assets/hu-BQMFte4p.js"},{"revision":null,"url":"assets/get-DprfYscH.js"},{"revision":null,"url":"assets/fr-CNLXHjU0.js"},{"revision":null,"url":"assets/files-m6EQBGUo.js"},{"revision":null,"url":"assets/file-data-transfer-DJxdvQaG.js"},{"revision":null,"url":"assets/escapeRegExp-DBAZVHfy.js"},{"revision":null,"url":"assets/es-CqxCkQAW.js"},{"revision":null,"url":"assets/en-DPc-vhny.js"},{"revision":null,"url":"assets/editor.worker-tytyn86m.js"},{"revision":null,"url":"assets/editor.api2-BmVe3eMZ.js"},{"revision":null,"url":"assets/editor-CGi5ri4_.css"},{"revision":null,"url":"assets/dynamicImports-Bq7bSVRn.js"},{"revision":null,"url":"assets/download-url-BCj14lMx.js"},{"revision":null,"url":"assets/de-FpedikYx.js"},{"revision":null,"url":"assets/cssMode-DyBLRVXG.js"},{"revision":null,"url":"assets/css.worker-yKcq13f2.js"},{"revision":null,"url":"assets/css-DIMkf-bt.js"},{"revision":null,"url":"assets/cs-Cl_UFq10.js"},{"revision":null,"url":"assets/codicon-ngg6Pgfi.ttf"},{"revision":null,"url":"assets/camera-C1rIv-Ce.js"},{"revision":null,"url":"assets/browser-B-0GUgJn.js"},{"revision":null,"url":"assets/browser-1D2tZwAR.js"},{"revision":null,"url":"assets/ar-DQkWG67W.js"},{"revision":null,"url":"assets/af-Cda-q-lJ.js"},{"revision":null,"url":"assets/_plugin-vue2_normalizer-BwvlkTga.js"},{"revision":null,"url":"assets/_createCompounder-BuI_-PJV.js"},{"revision":null,"url":"assets/_baseMerge-Cl1mD_Z3.js"},{"revision":null,"url":"assets/_baseFor-CmFSrpl9.js"},{"revision":null,"url":"assets/_MapCache-CODy_DtT.js"},{"revision":null,"url":"assets/WebrtcMediamtxCamera-wTTng3ln.js"},{"revision":null,"url":"assets/WebrtcGo2RtcCamera-_JDK7Wwy.js"},{"revision":null,"url":"assets/WebrtcCamerastreamerCamera-D4g-tIt7.js"},{"revision":null,"url":"assets/Watch-BsFMHUzZ.js"},{"revision":null,"url":"assets/VModel-EJJ5Ow2-.js"},{"revision":null,"url":"assets/Uv4LMjpegCamera-DL2tgzVZ.js"},{"revision":null,"url":"assets/Tune-YfFfOyfD.css"},{"revision":null,"url":"assets/Tune-D08ana1m.js"},{"revision":null,"url":"assets/TimelapseRenderSettingsDialog-Di8rM4OH.js"},{"revision":null,"url":"assets/Timelapse-CFheisPX.js"},{"revision":null,"url":"assets/Timelapse-C8nYrEGX.css"},{"revision":null,"url":"assets/System-DYxqWSEH.js"},{"revision":null,"url":"assets/Settings-mEV_Qz1v.js"},{"revision":null,"url":"assets/Settings-Dzk6IFO-.css"},{"revision":null,"url":"assets/NotFound-C79IZiYh.js"},{"revision":null,"url":"assets/MjpegstreamerCamera-DwDhWLeS.js"},{"revision":null,"url":"assets/MjpegstreamerAdaptiveCamera-EG3hE4Lv.js"},{"revision":null,"url":"assets/MacroCategorySettings-Dcqx2nqs.js"},{"revision":null,"url":"assets/Jobs-B3qigtOA.js"},{"revision":null,"url":"assets/JobQueueCard-wD83NPR2.css"},{"revision":null,"url":"assets/JobQueueCard-BMZyzBSF.js"},{"revision":null,"url":"assets/JobHistoryItemStatus-DHTogHUO.js"},{"revision":null,"url":"assets/IpstreamCamera-BQWSINzQ.js"},{"revision":null,"url":"assets/IframeCamera-DYmK-HOt.js"},{"revision":null,"url":"assets/Icons-DmS4BGhS.js"},{"revision":null,"url":"assets/HlsstreamCamera-CVXKb6Jl.js"},{"revision":null,"url":"assets/History-WA13spqe.js"},{"revision":null,"url":"assets/History-IqaLA2bn.css"},{"revision":null,"url":"assets/GcodePreviewCard-Dm_G6Z5X.js"},{"revision":null,"url":"assets/GcodePreviewCard-BvZ1P1X7.css"},{"revision":null,"url":"assets/GcodePreview-ZPSXRabe.js"},{"revision":null,"url":"assets/FullscreenCamera-CBFuXR8d.js"},{"revision":null,"url":"assets/FileSystem-B_trKwTi.css"},{"revision":null,"url":"assets/FileSystem-BTtS7WFc.js"},{"revision":null,"url":"assets/DiskUsageCard-2IC2soK7.js"},{"revision":null,"url":"assets/Diagnostics-Bjs4vM0F.js"},{"revision":null,"url":"assets/Diagnostics-BftVXYRb.css"},{"revision":null,"url":"assets/DeviceCamera-CEgRSNHn.js"},{"revision":"b3d-dashboard-1","url":"assets/Dashboard-DNbt7e59.js"},{"revision":null,"url":"assets/Dashboard-BCHPrNmB.css"},{"revision":null,"url":"assets/ConsoleCard-wznOIkYo.css"},{"revision":null,"url":"assets/ConsoleCard-CN6vy4vu.js"},{"revision":null,"url":"assets/Console-BxEnb97p.js"},{"revision":null,"url":"assets/Configure-jpJLplJh.css"},{"revision":null,"url":"assets/Configure-C4NMMziF.js"},{"revision":null,"url":"assets/BeaconCard-DZFsnDpA.js"},{"revision":null,"url":"assets/BeaconCard-CEcIMqLQ.css"},{"revision":null,"url":"assets/AppTextField-DCwJYWKN.js"},{"revision":null,"url":"assets/AppSettingsNav-BbBJCrdi.js"},{"revision":null,"url":"assets/AppNamedSlider-wRcBqpIw.js"},{"revision":null,"url":"assets/AppInlineChart-tU2ZraJ1.js"},{"revision":null,"url":"assets/AppInlineChart-CLkbYLy9.css"},{"revision":null,"url":"assets/AppDragIcon-DWfQuZat.css"},{"revision":null,"url":"assets/AppDragIcon-D9uUFP8Y.js"},{"revision":null,"url":"assets/AppColorPicker-Dnf0G-eH.js"},{"revision":null,"url":"assets/AppColorPicker-Ce6V2ct0.css"},{"revision":null,"url":"assets/AppChart-DWG65OLt.js"},{"revision":null,"url":"assets/AppChart-Bz44aqTu.css"},{"revision":null,"url":"assets/AppBtnCollapseGroup-D8f14tsT.js"},{"revision":null,"url":"assets/AppBtn-CrmEnE1u.js"},{"revision":null,"url":"assets/AfcPrintStartDialogTool-CZT4oN3n.js"},{"revision":"80ae0fbdf558c18f367ffcc02e3d8347","url":"favicon.ico"},{"revision":"8055ad16f14a0cc45be2db6c80b2023b","url":"logo_annex.svg"},{"revision":"1104eca767b2732a30020ebb8508ec10","url":"logo_btt.svg"},{"revision":"4c680e58e549e7583b2f57778e8c81f1","url":"logo_cocoapress.svg"},{"revision":"7f6f0c2b20d7ccbbd9752869e4fbbeb9","url":"logo_eva.svg"},{"revision":"24dbb1c5fbcedbb3a79b8e82903a3e6a","url":"logo_fluidd.svg"},{"revision":"ef5609db979c1455b836c9c12a5a6d68","url":"logo_hevort.svg"},{"revision":"ef809038e55088e9ea69528836a4da11","url":"logo_kingroon.svg"},{"revision":"8de08f112a5c44763a7896e707943823","url":"logo_klipper.svg"},{"revision":"b5a0d5b187fcb74378f917290edccef4","url":"logo_ldo.svg"},{"revision":"56651032b64a8408430837a0e737cb12","url":"logo_mellow.svg"},{"revision":"976594b4d247e8c022d05698212da711","url":"logo_micron.svg"},{"revision":"6008c6d8ff53cf07c1643cd7c1696e6e","url":"logo_peopoly.svg"},{"revision":"fbe628a634f51daf01a9ad2328212fa6","url":"logo_pfa.svg"},{"revision":"67c1bcad44a46a0c7ca7ad40a8f0b216","url":"logo_prusa.svg"},{"revision":"44d11864d102a7bc2b8d1cb802480010","url":"logo_qidi.svg"},{"revision":"0cb0e785d366c0231d4b0b5b3c2381f0","url":"logo_ratrig.svg"},{"revision":"b8090cbe3d148a55b6d2effcff714fe2","url":"logo_salad_fork.svg"},{"revision":"b383227c1bc5ee91f3cb04020be29c31","url":"logo_siboor.svg"},{"revision":"bfdfa0822f1b319366f0ddabc912a29d","url":"logo_snakeoil.svg"},{"revision":"cec01512b6a01816be40b93e2b68daa2","url":"logo_voron.svg"},{"revision":"06d4317bb99817b26381a3c8e08cfaf3","url":"logo_vzbot.svg"},{"revision":"c511ff00a84a5cc574a7bacd0180e468","url":"logo_z-bolt.svg"},{"revision":"ae2f1f9549247b98edc7948db7d2bf59","url":"logo_zerog.svg"},{"revision":"4afa8d256affc5e6ceff693ac261c0ee","url":"img/mmu/mmu_3MS.svg"},{"revision":"ce391a4e774f19c780165b000cc30b84","url":"img/mmu/mmu_AngryBeaver.svg"},{"revision":"ac815c6dc361b5277d19fc907623e35a","url":"img/mmu/mmu_BoxTurtle.svg"},{"revision":"8793cba70eb86fb472fdaaa227affba3","url":"img/mmu/mmu_EMU.svg"},{"revision":"f052341643ea0191de50c9411b3f92c5","url":"img/mmu/mmu_ERCF.svg"},{"revision":"caa34f208228120f4997db628a2a2e0f","url":"img/mmu/mmu_HappyHare.svg"},{"revision":"35fcf9e123a5760d871584dc8f0eadfe","url":"img/mmu/mmu_KMS.svg"},{"revision":"6b394816a5a10303cbcf19892c10e13e","url":"img/mmu/mmu_MMX.svg"},{"revision":"677ee2adf07447fdc59be927c65c5422","url":"img/mmu/mmu_NightOwl.svg"},{"revision":"245b90afd86535c0757e56b4108c732b","url":"img/mmu/mmu_QuattroBox.svg"},{"revision":"6808075d93579c5979158c9827ea8123","url":"img/mmu/mmu_Tradrack.svg"},{"revision":"ec8b84cc0c9a6a270c52330fd52aaa70","url":"img/mmu/mmu_VVD.svg"},{"revision":"6ea1e9fde2682dd8d0d1ea08f6624e9f","url":"img/icons/android-chrome-192x192.png"},{"revision":"db3b74c0e8a1fec2025f202d28f612f9","url":"img/icons/android-chrome-512x512.png"},{"revision":"b355fe6957e72037f1bc6fb3bad3a78d","url":"img/icons/android-chrome-maskable-192x192.png"},{"revision":"a351c8d619180fe28d1b9ae02b3d9066","url":"img/icons/android-chrome-maskable-512x512.png"},{"revision":"ec48f367f52f03862cee7cec3d01ad07","url":"img/icons/apple-touch-icon-120x120.png"},{"revision":"bc8f75876a747950735260adc634a81b","url":"img/icons/apple-touch-icon-152x152.png"},{"revision":"23e6410e45ff58896d23b4f4ef4514bd","url":"img/icons/apple-touch-icon-180x180.png"},{"revision":"27ab6d467f78011d71362fb060a98cf9","url":"img/icons/apple-touch-icon-60x60.png"},{"revision":"4af08cd1f1e8ad8b510a8b79847d1b5a","url":"img/icons/apple-touch-icon-76x76.png"},{"revision":"23e6410e45ff58896d23b4f4ef4514bd","url":"img/icons/apple-touch-icon.png"},{"revision":"d5ad46f18f3207b4073c1f8e734302d7","url":"img/icons/favicon-16x16.png"},{"revision":"3de1cf2d2204e73c6c5a622749f0f2f4","url":"img/icons/favicon-32x32.png"},{"revision":"80ae0fbdf558c18f367ffcc02e3d8347","url":"img/icons/favicon.ico"},{"revision":"4cc0223d744bd99a649837825b82c06e","url":"img/icons/msapplication-icon-144x144.png"},{"revision":"98c08c8393ca7732e4916440e52ae08f","url":"img/icons/mstile-150x150.png"},{"revision":"434c939dafecb59048ee942db3c109d7","url":"img/icons/safari-pinned-tab.svg"},{"revision":"603dcda2c2942700dcec8b8e9aad766c","url":"img/icons/shortcut-configuration-96x96.png"},{"revision":"808c09c0275277dbc4d9dc43429221ac","url":"img/icons/shortcut-settings-96x96.png"},{"revision":"b60c2a0a66f6e9a3d8557db9302c93f9","url":"manifest.webmanifest"}]),A();var Q=new URL(`config.json`,self.location.href).pathname,$=new I({cacheName:`config`,fetchOptions:{cache:`no-cache`}});ye({urls:[Q],strategy:$}),C(Q,$,`GET`),C(new P(j(`index.html`),{allowlist:void 0,denylist:[/\/websocket/,/\/(printer|api|access|machine|server)\//,/\/webcam[2-4]?\//]})); \ No newline at end of file +try{self[`workbox:core:7.4.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.4.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function ee(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function te(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=ee()?i.body:await i.blob();return new Response(s,o)}var ne=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function f(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function re(e,t,n,r){let i=f(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===f(t.url,n))return e.match(t,r)}var ie=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},ae=new Set;async function oe(){for(let e of ae)await e()}function se(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.4.0`]&&_()}catch{}function p(e){return typeof e==`string`?new Request(e):e}var m=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new ie,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=p(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=p(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=p(e);await se(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:ne(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await re(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await oe(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=p(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},h=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new m(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},g=class e extends h{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};g.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},g.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await te(e):e}};var ce=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new g({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},v,y=()=>(v||=new ce,v);try{self[`workbox:routing:7.4.0`]&&_()}catch{}var b=e=>e&&typeof e==`object`?e:{handle:e},x=class{constructor(e,t,n=`GET`){this.handler=b(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=b(e)}},le=class extends x{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},ue=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,b(e))}setCatchHandler(e){this._catchHandler=b(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},S,de=()=>(S||(S=new ue,S.addFetchListener(),S.addCacheListener()),S);function C(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new x(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new le(e,n,r);else if(typeof e==`function`)i=new x(e,n,r);else if(e instanceof x)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return de().registerRoute(i),i}function w(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*T(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=w(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var E=class extends x{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of T(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function D(e){C(new E(y(),e))}var O=`-precache-`,k=async(e,t=O)=>{let n=(await self.caches.keys()).filter(n=>n.includes(t)&&n.includes(self.registration.scope)&&n!==e);return await Promise.all(n.map(e=>self.caches.delete(e))),n};function A(){self.addEventListener(`activate`,(e=>{let t=a.getPrecacheName();e.waitUntil(k(t).then(e=>{}))}))}function j(e){return y().createHandlerBoundToURL(e)}function M(e){y().precache(e)}function N(e,t){M(e),D(t)}var P=class extends x{constructor(e,{allowlist:t=[/./],denylist:n=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=n}_match({url:e,request:t}){if(t&&t.mode!==`navigate`)return!1;let n=e.pathname+e.search;for(let e of this._denylist)if(e.test(n))return!1;return!!this._allowlist.some(e=>e.test(n))}},F={cacheWillUpdate:async({response:e})=>e.status===200||e.status===0?e:null},I=class extends h{constructor(e={}){super(e),this.plugins.some(e=>`cacheWillUpdate`in e)||this.plugins.unshift(F)}async _handle(e,n){let r=n.fetchAndCachePut(e).catch(()=>{});n.waitUntil(r);let i=await n.cacheMatch(e),a;if(!i)try{i=await r}catch(e){e instanceof Error&&(a=e)}if(!i)throw new t(`no-response`,{url:e.url,error:a});return i}};try{self[`workbox:cacheable-response:7.4.0`]&&_()}catch{}var L=(e,t)=>t.some(t=>e instanceof t),R,z;function fe(){return R||=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction]}function B(){return z||=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey]}var V=new WeakMap,H=new WeakMap,U=new WeakMap,W=new WeakMap,G=new WeakMap;function pe(e){let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`success`,i),e.removeEventListener(`error`,a)},i=()=>{t(q(e.result)),r()},a=()=>{n(e.error),r()};e.addEventListener(`success`,i),e.addEventListener(`error`,a)});return t.then(t=>{t instanceof IDBCursor&&V.set(t,e)}).catch(()=>{}),G.set(t,e),t}function me(e){if(H.has(e))return;let t=new Promise((t,n)=>{let r=()=>{e.removeEventListener(`complete`,i),e.removeEventListener(`error`,a),e.removeEventListener(`abort`,a)},i=()=>{t(),r()},a=()=>{n(e.error||new DOMException(`AbortError`,`AbortError`)),r()};e.addEventListener(`complete`,i),e.addEventListener(`error`,a),e.addEventListener(`abort`,a)});H.set(e,t)}var K={get(e,t,n){if(e instanceof IDBTransaction){if(t===`done`)return H.get(e);if(t===`objectStoreNames`)return e.objectStoreNames||U.get(e);if(t===`store`)return n.objectStoreNames[1]?void 0:n.objectStore(n.objectStoreNames[0])}return q(e[t])},set(e,t,n){return e[t]=n,!0},has(e,t){return e instanceof IDBTransaction&&(t===`done`||t===`store`)||t in e}};function he(e){K=e(K)}function ge(e){return e===IDBDatabase.prototype.transaction&&!(`objectStoreNames`in IDBTransaction.prototype)?function(t,...n){let r=e.call(J(this),t,...n);return U.set(r,t.sort?t.sort():[t]),q(r)}:B().includes(e)?function(...t){return e.apply(J(this),t),q(V.get(this))}:function(...t){return q(e.apply(J(this),t))}}function _e(e){return typeof e==`function`?ge(e):(e instanceof IDBTransaction&&me(e),L(e,fe())?new Proxy(e,K):e)}function q(e){if(e instanceof IDBRequest)return pe(e);if(W.has(e))return W.get(e);let t=_e(e);return t!==e&&(W.set(e,t),G.set(t,e)),t}var J=e=>G.get(e),ve=[`get`,`getKey`,`getAll`,`getAllKeys`,`count`],Y=[`put`,`add`,`delete`,`clear`],X=new Map;function Z(e,t){if(!(e instanceof IDBDatabase&&!(t in e)&&typeof t==`string`))return;if(X.get(t))return X.get(t);let n=t.replace(/FromIndex$/,``),r=t!==n,i=Y.includes(n);if(!(n in(r?IDBIndex:IDBObjectStore).prototype)||!(i||ve.includes(n)))return;let a=async function(e,...t){let a=this.transaction(e,i?`readwrite`:`readonly`),o=a.store;return r&&(o=o.index(t.shift())),(await Promise.all([o[n](...t),i&&a.done]))[0]};return X.set(t,a),a}he(e=>({...e,get:(t,n,r)=>Z(t,n)||e.get(t,n,r),has:(t,n)=>!!Z(t,n)||e.has(t,n)}));try{self[`workbox:expiration:7.4.0`]&&_()}catch{}try{self[`workbox:recipes:7.4.0`]&&_()}catch{}function ye(e){self.addEventListener(`install`,t=>{let n=e.urls.map(n=>e.strategy.handleAll({event:t,request:new Request(n)})[1]);t.waitUntil(Promise.all(n))})}self.addEventListener(`message`,e=>{e.data&&e.data.type===`SKIP_WAITING`&&self.skipWaiting()}),N([{"revision":"b3d-index-html-2","url":"index.html"},{"revision":null,"url":"assets/zh-HK-_kY26ddi.js"},{"revision":null,"url":"assets/zh-CN-Ci04HV7N.js"},{"revision":null,"url":"assets/workbox-window.prod.es5-Bd17z0YL.js"},{"revision":null,"url":"assets/vue.runtime.esm-uXCn7sOw.js"},{"revision":null,"url":"assets/vue-echarts-chunk-BZd0vyDY.js"},{"revision":null,"url":"assets/virtual_pwa-register-VxpNN5l1.js"},{"revision":null,"url":"assets/v4-DDdyfk2q.js"},{"revision":null,"url":"assets/uk-CrpPMn14.js"},{"revision":null,"url":"assets/tr-BjhqQozH.js"},{"revision":null,"url":"assets/toString-BN36OUXd.js"},{"revision":null,"url":"assets/th-DOwCrGXH.js"},{"revision":null,"url":"assets/ta-C3zJZejv.js"},{"revision":null,"url":"assets/sv-C7gGT_f4.js"},{"revision":null,"url":"assets/state-D95nW690.js"},{"revision":null,"url":"assets/state-D5EbeaNv.js"},{"revision":"b3d-cachebust-1","url":"assets/socketActions-D7Vpckss.js"},{"revision":null,"url":"assets/sleep-CEQq2wgp.js"},{"revision":null,"url":"assets/sl-BKmRYTmo.js"},{"revision":null,"url":"assets/setupMonaco-BsspMbCi.js"},{"revision":null,"url":"assets/services-DkE0LGL2.js"},{"revision":null,"url":"assets/sandboxedEval.worker-CMwFLJDA.js"},{"revision":null,"url":"assets/ru-CX6SFs1w.js"},{"revision":null,"url":"assets/rolldown-runtime-CNC7AqOf.js"},{"revision":null,"url":"assets/roboto-vietnamese-700-normal-kpkdMgbf.woff"},{"revision":null,"url":"assets/roboto-vietnamese-700-normal-BEVeWqJt.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-500-normal-BgoYVz-9.woff"},{"revision":null,"url":"assets/roboto-vietnamese-500-normal-B3ncpOoB.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-400-normal-D2PTxGxD.woff2"},{"revision":null,"url":"assets/roboto-vietnamese-400-normal-Bf76hAzZ.woff"},{"revision":null,"url":"assets/roboto-vietnamese-300-normal-CYjuQheQ.woff"},{"revision":null,"url":"assets/roboto-vietnamese-300-normal-BPvXm_f1.woff2"},{"revision":null,"url":"assets/roboto-symbols-700-normal-C59U6HqI.woff"},{"revision":null,"url":"assets/roboto-symbols-700-normal-BiFDindJ.woff2"},{"revision":null,"url":"assets/roboto-symbols-500-normal-DFnofPUt.woff"},{"revision":null,"url":"assets/roboto-symbols-500-normal-B_CZKVJS.woff2"},{"revision":null,"url":"assets/roboto-symbols-400-normal-CB1Ce4Gk.woff2"},{"revision":null,"url":"assets/roboto-symbols-400-normal-C7tGlxgb.woff"},{"revision":null,"url":"assets/roboto-symbols-300-normal-DDU7avhj.woff2"},{"revision":null,"url":"assets/roboto-symbols-300-normal-CBIeSvs3.woff"},{"revision":null,"url":"assets/roboto-math-700-normal-DbhUef31.woff"},{"revision":null,"url":"assets/roboto-math-700-normal-B8YqGHVc.woff2"},{"revision":null,"url":"assets/roboto-math-500-normal-C4NU9gLX.woff2"},{"revision":null,"url":"assets/roboto-math-500-normal-BNGXE_xU.woff"},{"revision":null,"url":"assets/roboto-math-400-normal-D6sS4O5l.woff"},{"revision":null,"url":"assets/roboto-math-400-normal-BEFej5gc.woff2"},{"revision":null,"url":"assets/roboto-math-300-normal-CkwMvXpj.woff"},{"revision":null,"url":"assets/roboto-math-300-normal-5dF_7mZP.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-700-normal-DSBUz0N1.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-700-normal-BDePAh3g.woff"},{"revision":null,"url":"assets/roboto-latin-ext-500-normal-pMCM9Ixg.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-500-normal-C1cK9xmS.woff"},{"revision":null,"url":"assets/roboto-latin-ext-400-normal-CIveymTr.woff"},{"revision":null,"url":"assets/roboto-latin-ext-400-normal-C3tdtHj3.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-300-normal-B90pq-BC.woff2"},{"revision":null,"url":"assets/roboto-latin-ext-300-normal-B0-sTZUp.woff"},{"revision":null,"url":"assets/roboto-latin-700-normal-YuyVweIx.woff"},{"revision":null,"url":"assets/roboto-latin-700-normal-BZpUvMxY.woff2"},{"revision":null,"url":"assets/roboto-latin-500-normal-fsTFPL3E.woff"},{"revision":null,"url":"assets/roboto-latin-500-normal-7RbcRiD8.woff2"},{"revision":null,"url":"assets/roboto-latin-400-normal-D27F5sTu.woff"},{"revision":null,"url":"assets/roboto-latin-400-normal-BqEyEoaF.woff2"},{"revision":null,"url":"assets/roboto-latin-300-normal-Dqn3NW0Y.woff"},{"revision":null,"url":"assets/roboto-latin-300-normal-CCzlftfr.woff2"},{"revision":null,"url":"assets/roboto-greek-700-normal-xQsEWSIA.woff"},{"revision":null,"url":"assets/roboto-greek-700-normal-0aHWxGLu.woff2"},{"revision":null,"url":"assets/roboto-greek-500-normal-XBIQOowA.woff"},{"revision":null,"url":"assets/roboto-greek-500-normal-C9AnhcmC.woff2"},{"revision":null,"url":"assets/roboto-greek-400-normal-ai2Z1K3C.woff2"},{"revision":null,"url":"assets/roboto-greek-400-normal-D904r64h.woff"},{"revision":null,"url":"assets/roboto-greek-300-normal-xS8CB6LN.woff"},{"revision":null,"url":"assets/roboto-greek-300-normal-DJEM9B4Z.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-700-normal-DmFxo5wj.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-700-normal-BWOtdzfy.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-500-normal-SCgicl0l.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-500-normal-BWC_xYeb.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-400-normal-qHufge6k.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-400-normal-DhDk0Xwi.woff"},{"revision":null,"url":"assets/roboto-cyrillic-ext-300-normal-DIxttMbC.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-ext-300-normal-C0UYUNTV.woff"},{"revision":null,"url":"assets/roboto-cyrillic-700-normal-D8g9KsRf.woff"},{"revision":null,"url":"assets/roboto-cyrillic-700-normal-C2o7G-SM.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-500-normal-CLao9AfR.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-500-normal-C8ZOEKN4.woff"},{"revision":null,"url":"assets/roboto-cyrillic-400-normal-CBPI_iaY.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-400-normal-BepgbcUK.woff"},{"revision":null,"url":"assets/roboto-cyrillic-300-normal-DzUz0kzv.woff2"},{"revision":null,"url":"assets/roboto-cyrillic-300-normal-D0x1uCa7.woff"},{"revision":null,"url":"assets/raleway-vietnamese-400-normal-CTw6K1Xj.woff2"},{"revision":null,"url":"assets/raleway-vietnamese-400-normal-CTqj18iX.woff"},{"revision":null,"url":"assets/raleway-latin-ext-400-normal-DoUy7GWe.woff"},{"revision":null,"url":"assets/raleway-latin-ext-400-normal-B4d0sYmR.woff2"},{"revision":null,"url":"assets/raleway-latin-400-normal-sMcq1OIP.woff"},{"revision":null,"url":"assets/raleway-latin-400-normal-C5eIEfLm.woff2"},{"revision":null,"url":"assets/raleway-cyrillic-ext-400-normal-zbv6uFvq.woff2"},{"revision":null,"url":"assets/raleway-cyrillic-ext-400-normal-QD38Acpa.woff"},{"revision":null,"url":"assets/raleway-cyrillic-400-normal-BOk4FNQ-.woff"},{"revision":null,"url":"assets/raleway-cyrillic-400-normal-B1ZxqHSH.woff2"},{"revision":null,"url":"assets/qr-scanner-worker.min-CJaXo3yu.js"},{"revision":null,"url":"assets/pt_BR-KiT3ATS9.js"},{"revision":null,"url":"assets/pt-BaBQRXg8.js"},{"revision":null,"url":"assets/preload-helper-HclGiUj8.js"},{"revision":null,"url":"assets/pl-Cf61CMqC.js"},{"revision":null,"url":"assets/parseGcode.worker-BtTjVWHs.js"},{"revision":null,"url":"assets/nl-6huzaAKa.js"},{"revision":null,"url":"assets/monacoFoldingRangesWorker-D4J5SrDs.js"},{"revision":null,"url":"assets/monacoDocumentSymbolsWorker-B1RvxTw6.js"},{"revision":null,"url":"assets/monacoCodeLensWorker-CeGXjSxG.js"},{"revision":null,"url":"assets/module-DQ2W7G90.js"},{"revision":null,"url":"assets/mjpegStream.worker-CkFbgHTR.js"},{"revision":null,"url":"assets/markdown-Cimd5fb3.js"},{"revision":null,"url":"assets/lspLanguageFeatures-D_J5eBm-.js"},{"revision":null,"url":"assets/lib-Cm30cL1j.css"},{"revision":null,"url":"assets/lib-BPv9EiKI.js"},{"revision":null,"url":"assets/ko-Djr-uWtG.js"},{"revision":null,"url":"assets/jsonMode-C5YSZh6U.js"},{"revision":null,"url":"assets/json.worker-Oa9UrkKE.js"},{"revision":null,"url":"assets/ja-mDyLPXjN.js"},{"revision":null,"url":"assets/it-ODE9PgDu.js"},{"revision":null,"url":"assets/isArray-BfeOG1E2.js"},{"revision":null,"url":"assets/is-key-of-eCsGvpyb.js"},{"revision":null,"url":"assets/index-Dtw9dMof.css"},{"revision":null,"url":"assets/index-Bmc36AIW.js"},{"revision":null,"url":"assets/hu-BQMFte4p.js"},{"revision":null,"url":"assets/get-DprfYscH.js"},{"revision":null,"url":"assets/fr-CNLXHjU0.js"},{"revision":null,"url":"assets/files-m6EQBGUo.js"},{"revision":null,"url":"assets/file-data-transfer-DJxdvQaG.js"},{"revision":null,"url":"assets/escapeRegExp-DBAZVHfy.js"},{"revision":null,"url":"assets/es-CqxCkQAW.js"},{"revision":null,"url":"assets/en-DPc-vhny.js"},{"revision":null,"url":"assets/editor.worker-tytyn86m.js"},{"revision":null,"url":"assets/editor.api2-BmVe3eMZ.js"},{"revision":null,"url":"assets/editor-CGi5ri4_.css"},{"revision":null,"url":"assets/dynamicImports-Bq7bSVRn.js"},{"revision":null,"url":"assets/download-url-BCj14lMx.js"},{"revision":null,"url":"assets/de-FpedikYx.js"},{"revision":null,"url":"assets/cssMode-DyBLRVXG.js"},{"revision":null,"url":"assets/css.worker-yKcq13f2.js"},{"revision":null,"url":"assets/css-DIMkf-bt.js"},{"revision":null,"url":"assets/cs-Cl_UFq10.js"},{"revision":null,"url":"assets/codicon-ngg6Pgfi.ttf"},{"revision":null,"url":"assets/camera-C1rIv-Ce.js"},{"revision":null,"url":"assets/browser-B-0GUgJn.js"},{"revision":null,"url":"assets/browser-1D2tZwAR.js"},{"revision":null,"url":"assets/ar-DQkWG67W.js"},{"revision":null,"url":"assets/af-Cda-q-lJ.js"},{"revision":null,"url":"assets/_plugin-vue2_normalizer-BwvlkTga.js"},{"revision":null,"url":"assets/_createCompounder-BuI_-PJV.js"},{"revision":null,"url":"assets/_baseMerge-Cl1mD_Z3.js"},{"revision":null,"url":"assets/_baseFor-CmFSrpl9.js"},{"revision":null,"url":"assets/_MapCache-CODy_DtT.js"},{"revision":null,"url":"assets/WebrtcMediamtxCamera-wTTng3ln.js"},{"revision":null,"url":"assets/WebrtcGo2RtcCamera-_JDK7Wwy.js"},{"revision":null,"url":"assets/WebrtcCamerastreamerCamera-D4g-tIt7.js"},{"revision":null,"url":"assets/Watch-BsFMHUzZ.js"},{"revision":null,"url":"assets/VModel-EJJ5Ow2-.js"},{"revision":null,"url":"assets/Uv4LMjpegCamera-DL2tgzVZ.js"},{"revision":null,"url":"assets/Tune-YfFfOyfD.css"},{"revision":null,"url":"assets/Tune-D08ana1m.js"},{"revision":null,"url":"assets/TimelapseRenderSettingsDialog-Di8rM4OH.js"},{"revision":null,"url":"assets/Timelapse-CFheisPX.js"},{"revision":null,"url":"assets/Timelapse-C8nYrEGX.css"},{"revision":null,"url":"assets/System-DYxqWSEH.js"},{"revision":null,"url":"assets/Settings-mEV_Qz1v.js"},{"revision":null,"url":"assets/Settings-Dzk6IFO-.css"},{"revision":null,"url":"assets/NotFound-C79IZiYh.js"},{"revision":null,"url":"assets/MjpegstreamerCamera-DwDhWLeS.js"},{"revision":null,"url":"assets/MjpegstreamerAdaptiveCamera-EG3hE4Lv.js"},{"revision":null,"url":"assets/MacroCategorySettings-Dcqx2nqs.js"},{"revision":null,"url":"assets/Jobs-B3qigtOA.js"},{"revision":null,"url":"assets/JobQueueCard-wD83NPR2.css"},{"revision":null,"url":"assets/JobQueueCard-BMZyzBSF.js"},{"revision":null,"url":"assets/JobHistoryItemStatus-DHTogHUO.js"},{"revision":null,"url":"assets/IpstreamCamera-BQWSINzQ.js"},{"revision":null,"url":"assets/IframeCamera-DYmK-HOt.js"},{"revision":null,"url":"assets/Icons-DmS4BGhS.js"},{"revision":null,"url":"assets/HlsstreamCamera-CVXKb6Jl.js"},{"revision":null,"url":"assets/History-WA13spqe.js"},{"revision":null,"url":"assets/History-IqaLA2bn.css"},{"revision":null,"url":"assets/GcodePreviewCard-Dm_G6Z5X.js"},{"revision":null,"url":"assets/GcodePreviewCard-BvZ1P1X7.css"},{"revision":null,"url":"assets/GcodePreview-ZPSXRabe.js"},{"revision":null,"url":"assets/FullscreenCamera-CBFuXR8d.js"},{"revision":null,"url":"assets/FileSystem-B_trKwTi.css"},{"revision":null,"url":"assets/FileSystem-BTtS7WFc.js"},{"revision":null,"url":"assets/DiskUsageCard-2IC2soK7.js"},{"revision":null,"url":"assets/Diagnostics-Bjs4vM0F.js"},{"revision":null,"url":"assets/Diagnostics-BftVXYRb.css"},{"revision":null,"url":"assets/DeviceCamera-CEgRSNHn.js"},{"revision":"b3d-dashboard-1","url":"assets/Dashboard-DNbt7e59.js"},{"revision":null,"url":"assets/Dashboard-BCHPrNmB.css"},{"revision":null,"url":"assets/ConsoleCard-wznOIkYo.css"},{"revision":null,"url":"assets/ConsoleCard-CN6vy4vu.js"},{"revision":null,"url":"assets/Console-BxEnb97p.js"},{"revision":null,"url":"assets/Configure-jpJLplJh.css"},{"revision":null,"url":"assets/Configure-C4NMMziF.js"},{"revision":null,"url":"assets/BeaconCard-DZFsnDpA.js"},{"revision":null,"url":"assets/BeaconCard-CEcIMqLQ.css"},{"revision":null,"url":"assets/AppTextField-DCwJYWKN.js"},{"revision":null,"url":"assets/AppSettingsNav-BbBJCrdi.js"},{"revision":null,"url":"assets/AppNamedSlider-wRcBqpIw.js"},{"revision":null,"url":"assets/AppInlineChart-tU2ZraJ1.js"},{"revision":null,"url":"assets/AppInlineChart-CLkbYLy9.css"},{"revision":null,"url":"assets/AppDragIcon-DWfQuZat.css"},{"revision":null,"url":"assets/AppDragIcon-D9uUFP8Y.js"},{"revision":null,"url":"assets/AppColorPicker-Dnf0G-eH.js"},{"revision":null,"url":"assets/AppColorPicker-Ce6V2ct0.css"},{"revision":null,"url":"assets/AppChart-DWG65OLt.js"},{"revision":null,"url":"assets/AppChart-Bz44aqTu.css"},{"revision":null,"url":"assets/AppBtnCollapseGroup-D8f14tsT.js"},{"revision":null,"url":"assets/AppBtn-CrmEnE1u.js"},{"revision":null,"url":"assets/AfcPrintStartDialogTool-CZT4oN3n.js"},{"revision":"80ae0fbdf558c18f367ffcc02e3d8347","url":"favicon.ico"},{"revision":"8055ad16f14a0cc45be2db6c80b2023b","url":"logo_annex.svg"},{"revision":"1104eca767b2732a30020ebb8508ec10","url":"logo_btt.svg"},{"revision":"4c680e58e549e7583b2f57778e8c81f1","url":"logo_cocoapress.svg"},{"revision":"7f6f0c2b20d7ccbbd9752869e4fbbeb9","url":"logo_eva.svg"},{"revision":"24dbb1c5fbcedbb3a79b8e82903a3e6a","url":"logo_fluidd.svg"},{"revision":"ef5609db979c1455b836c9c12a5a6d68","url":"logo_hevort.svg"},{"revision":"ef809038e55088e9ea69528836a4da11","url":"logo_kingroon.svg"},{"revision":"8de08f112a5c44763a7896e707943823","url":"logo_klipper.svg"},{"revision":"b5a0d5b187fcb74378f917290edccef4","url":"logo_ldo.svg"},{"revision":"56651032b64a8408430837a0e737cb12","url":"logo_mellow.svg"},{"revision":"976594b4d247e8c022d05698212da711","url":"logo_micron.svg"},{"revision":"6008c6d8ff53cf07c1643cd7c1696e6e","url":"logo_peopoly.svg"},{"revision":"fbe628a634f51daf01a9ad2328212fa6","url":"logo_pfa.svg"},{"revision":"67c1bcad44a46a0c7ca7ad40a8f0b216","url":"logo_prusa.svg"},{"revision":"44d11864d102a7bc2b8d1cb802480010","url":"logo_qidi.svg"},{"revision":"0cb0e785d366c0231d4b0b5b3c2381f0","url":"logo_ratrig.svg"},{"revision":"b8090cbe3d148a55b6d2effcff714fe2","url":"logo_salad_fork.svg"},{"revision":"b383227c1bc5ee91f3cb04020be29c31","url":"logo_siboor.svg"},{"revision":"bfdfa0822f1b319366f0ddabc912a29d","url":"logo_snakeoil.svg"},{"revision":"cec01512b6a01816be40b93e2b68daa2","url":"logo_voron.svg"},{"revision":"06d4317bb99817b26381a3c8e08cfaf3","url":"logo_vzbot.svg"},{"revision":"c511ff00a84a5cc574a7bacd0180e468","url":"logo_z-bolt.svg"},{"revision":"ae2f1f9549247b98edc7948db7d2bf59","url":"logo_zerog.svg"},{"revision":"4afa8d256affc5e6ceff693ac261c0ee","url":"img/mmu/mmu_3MS.svg"},{"revision":"ce391a4e774f19c780165b000cc30b84","url":"img/mmu/mmu_AngryBeaver.svg"},{"revision":"ac815c6dc361b5277d19fc907623e35a","url":"img/mmu/mmu_BoxTurtle.svg"},{"revision":"8793cba70eb86fb472fdaaa227affba3","url":"img/mmu/mmu_EMU.svg"},{"revision":"f052341643ea0191de50c9411b3f92c5","url":"img/mmu/mmu_ERCF.svg"},{"revision":"caa34f208228120f4997db628a2a2e0f","url":"img/mmu/mmu_HappyHare.svg"},{"revision":"35fcf9e123a5760d871584dc8f0eadfe","url":"img/mmu/mmu_KMS.svg"},{"revision":"6b394816a5a10303cbcf19892c10e13e","url":"img/mmu/mmu_MMX.svg"},{"revision":"677ee2adf07447fdc59be927c65c5422","url":"img/mmu/mmu_NightOwl.svg"},{"revision":"245b90afd86535c0757e56b4108c732b","url":"img/mmu/mmu_QuattroBox.svg"},{"revision":"6808075d93579c5979158c9827ea8123","url":"img/mmu/mmu_Tradrack.svg"},{"revision":"ec8b84cc0c9a6a270c52330fd52aaa70","url":"img/mmu/mmu_VVD.svg"},{"revision":"6ea1e9fde2682dd8d0d1ea08f6624e9f","url":"img/icons/android-chrome-192x192.png"},{"revision":"db3b74c0e8a1fec2025f202d28f612f9","url":"img/icons/android-chrome-512x512.png"},{"revision":"b355fe6957e72037f1bc6fb3bad3a78d","url":"img/icons/android-chrome-maskable-192x192.png"},{"revision":"a351c8d619180fe28d1b9ae02b3d9066","url":"img/icons/android-chrome-maskable-512x512.png"},{"revision":"ec48f367f52f03862cee7cec3d01ad07","url":"img/icons/apple-touch-icon-120x120.png"},{"revision":"bc8f75876a747950735260adc634a81b","url":"img/icons/apple-touch-icon-152x152.png"},{"revision":"23e6410e45ff58896d23b4f4ef4514bd","url":"img/icons/apple-touch-icon-180x180.png"},{"revision":"27ab6d467f78011d71362fb060a98cf9","url":"img/icons/apple-touch-icon-60x60.png"},{"revision":"4af08cd1f1e8ad8b510a8b79847d1b5a","url":"img/icons/apple-touch-icon-76x76.png"},{"revision":"23e6410e45ff58896d23b4f4ef4514bd","url":"img/icons/apple-touch-icon.png"},{"revision":"d5ad46f18f3207b4073c1f8e734302d7","url":"img/icons/favicon-16x16.png"},{"revision":"3de1cf2d2204e73c6c5a622749f0f2f4","url":"img/icons/favicon-32x32.png"},{"revision":"80ae0fbdf558c18f367ffcc02e3d8347","url":"img/icons/favicon.ico"},{"revision":"4cc0223d744bd99a649837825b82c06e","url":"img/icons/msapplication-icon-144x144.png"},{"revision":"98c08c8393ca7732e4916440e52ae08f","url":"img/icons/mstile-150x150.png"},{"revision":"434c939dafecb59048ee942db3c109d7","url":"img/icons/safari-pinned-tab.svg"},{"revision":"603dcda2c2942700dcec8b8e9aad766c","url":"img/icons/shortcut-configuration-96x96.png"},{"revision":"808c09c0275277dbc4d9dc43429221ac","url":"img/icons/shortcut-settings-96x96.png"},{"revision":"b60c2a0a66f6e9a3d8557db9302c93f9","url":"manifest.webmanifest"}]),A();var Q=new URL(`config.json`,self.location.href).pathname,$=new I({cacheName:`config`,fetchOptions:{cache:`no-cache`}});ye({urls:[Q],strategy:$}),C(Q,$,`GET`),C(new P(j(`index.html`),{allowlist:void 0,denylist:[/\/websocket/,/\/(printer|api|access|machine|server)\//,/\/webcam[2-4]?\//]})); \ No newline at end of file diff --git a/fluidd/manifest.json b/fluidd/manifest.json index 43ea467..7c65764 100644 --- a/fluidd/manifest.json +++ b/fluidd/manifest.json @@ -1,17 +1,17 @@ { "name": "fluidd", "title": "Fluidd", - "version": "0.1.5", + "version": "0.1.6", "sw_version": "1.37.3", "author": "bespoked", - "description": "Swaps SM's outdated Fluidd for the current upstream release. You get input-shaper graphs, integrated config/G-code editors, tool-colour preview, modern mesh viz. Uninstall restores the SM-shipped copy automatically; no nginx config touched.", + "description": "Swaps SM's outdated Fluidd for the current upstream release. You get input-shaper graphs, integrated config/G-code editors, tool-colour preview, modern mesh viz, and a FilaMan spool card when the filaman plugin is installed. Uninstall restores the SM-shipped copy automatically; no nginx config touched.", "tagline": "Modern Fluidd v1.37.3: replaces the stock SM build.", "category": "ui", "channel": "stable", "printer_specific": true, "changelog": "doc/CHANGELOG.md", "published_at": "2025-10-12", - "updated_at": "2026-08-01", + "updated_at": "2026-08-11", "source": "https://github.com/Bespok3d/fluidd-plugin", "homepage": "https://github.com/Bespok3d/fluidd-plugin", "publisher": "PLACEHOLDER", diff --git a/fluidd/patches/filaman-card.js b/fluidd/patches/filaman-card.js new file mode 100644 index 0000000..c930313 --- /dev/null +++ b/fluidd/patches/filaman-card.js @@ -0,0 +1,259 @@ +// A small floating card showing each extruder's assigned FilaMan spool, with a button to clear it +// and a button to manually repeat the startup repush. +// +// FilaMan is an NFC/RFID filament tracking system with its own Moonraker component (the filaman +// Bespok3d plugin), independent of Spoolman. It talks to Fluidd's built in Spoolman panel not at +// all, since that panel only lights up for a component actually named spoolman. This card is its +// own thing instead: it polls the filaman component's own REST endpoints directly and renders +// itself, without reading or writing anything in Fluidd's Vuex store. Deliberately no spool +// picker here: assigning a spool to an extruder is FilaMan's own job (an NFC tap, or its own +// app), this card only shows and clears what is already assigned. +// +// Inert on a printer without the filaman Moonraker component: the first status request 404s (or +// otherwise fails), the card is never created, and nothing polls again until the next page load. +// +// This file is copied into the vendored bundle by scripts/patch-fluidd.sh, which also adds the +// script tag that loads it. Re-vendoring re-applies both. + +(function bespok3dFilamanCard() { + var STATUS_PATH = '/server/filaman/status' + var SPOOL_ID_PATH = '/server/filaman/spool_id' + var REPUSH_PATH = '/server/filaman/repush' + var PROXY_PATH = '/server/filaman/proxy' + var POLL_INTERVAL_MS = 5000 + var CARD_ELEMENT_ID = 'b3d-filaman-card' + + var spoolDetailsById = {} + var spoolDetailFetchesInFlight = {} + + function requestJson(path, requestInit) { + return window.fetch(path, requestInit).then(function readBody(response) { + if (!response.ok) throw new Error('filaman request failed: ' + response.status) + return response.json() + }) + } + + function unwrapMoonrakerResult(body) { + return body.result || body + } + + function fetchStatus() { + return requestJson(STATUS_PATH, { credentials: 'same-origin' }).then(unwrapMoonrakerResult) + } + + function clearExtruderSpool(extruderName) { + var query = '?extruder=' + encodeURIComponent(extruderName) + return requestJson(SPOOL_ID_PATH + query, { method: 'POST', credentials: 'same-origin' }) + } + + function repushAssignedSpools() { + return requestJson(REPUSH_PATH, { method: 'POST', credentials: 'same-origin' }) + } + + // FilaMan's REST API nests a spool's consumption log under /api/v1/spools/{id}/consumptions, + // so the spool itself is expected at /api/v1/spools/{id}. Routed through the filaman + // component's own proxy endpoint, which already holds the credentials this card does not have. + function fetchSpoolDetail(spoolId) { + return requestJson(PROXY_PATH, { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + request_method: 'GET', + path: '/api/v1/spools/' + spoolId, + use_v2_response: true + }) + }) + .then(unwrapMoonrakerResult) + .then(function readProxyResult(proxyResult) { + if (proxyResult.error) throw new Error(proxyResult.error.message || 'proxy error') + return spoolDetailFromApiSpool(proxyResult.response) + }) + } + + function normalizedColorHex(hex) { + return hex.charAt(0) === '#' ? hex : '#' + hex + } + + function spoolDetailFromApiSpool(apiSpool) { + var filament = apiSpool.filament || {} + var colorEntry = (filament.colors || [])[0] + var initialTotalWeightG = filament.initial_total_weight_g != null + ? filament.initial_total_weight_g + : apiSpool.initial_total_weight_g + var emptySpoolWeightG = apiSpool.empty_spool_weight_g + var initialWeightG = (initialTotalWeightG != null && emptySpoolWeightG != null) + ? Math.max(initialTotalWeightG - emptySpoolWeightG, 0) + : filament.raw_material_weight_g + + return { + name: filament.designation || null, + colorHex: colorEntry && colorEntry.color && colorEntry.color.hex_code + ? normalizedColorHex(colorEntry.color.hex_code) + : null, + remainingWeightG: apiSpool.remaining_weight_g != null ? apiSpool.remaining_weight_g : null, + initialWeightG: initialWeightG != null ? initialWeightG : null + } + } + + function spoolDetail(spoolId) { + var cached = spoolDetailsById[spoolId] + if (cached) return cached + + if (!spoolDetailFetchesInFlight[spoolId]) { + spoolDetailFetchesInFlight[spoolId] = true + fetchSpoolDetail(spoolId).then(function cacheDetail(detail) { + delete spoolDetailFetchesInFlight[spoolId] + spoolDetailsById[spoolId] = detail + poll() + }, function keepShowingSpoolId() { + // Detail lookup failed (backend unreachable, or this FilaMan version has no per-spool + // GET). Not fatal: the row below falls back to the bare spool id. Retried next poll. + delete spoolDetailFetchesInFlight[spoolId] + }) + } + return null + } + + function cardElement() { + var existing = document.getElementById(CARD_ELEMENT_ID) + if (existing) return existing + + var card = document.createElement('div') + card.id = CARD_ELEMENT_ID + card.style.cssText = + 'position:fixed;right:12px;bottom:12px;z-index:9999;' + + 'background:#1e1e20;color:#fff;border:1px solid #3a3a3d;border-radius:8px;' + + 'padding:10px 12px;font:13px/1.4 sans-serif;min-width:200px;max-width:300px;' + + 'box-shadow:0 2px 10px rgba(0,0,0,.5)' + document.body.appendChild(card) + return card + } + + function removeCardElement() { + var existing = document.getElementById(CARD_ELEMENT_ID) + if (existing) existing.remove() + } + + function iconButtonElement(label, onClick) { + var button = document.createElement('button') + button.type = 'button' + button.textContent = label + button.style.cssText = + 'background:none;border:1px solid #555;border-radius:4px;' + + 'color:#fff;cursor:pointer;font-size:11px;padding:1px 6px' + button.addEventListener('click', onClick) + return button + } + + function headingElement() { + var heading = document.createElement('div') + heading.style.cssText = + 'display:flex;align-items:center;justify-content:space-between;margin-bottom:4px' + + var title = document.createElement('span') + title.style.cssText = 'font-weight:600' + title.textContent = 'FilaMan' + heading.appendChild(title) + + heading.appendChild(iconButtonElement('repush', function onRepushClicked() { + repushAssignedSpools().then(poll, poll) + })) + return heading + } + + function colorDotElement(colorHex) { + var dot = document.createElement('span') + dot.style.cssText = + 'display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:5px;' + + 'background:' + colorHex + ';border:1px solid rgba(255,255,255,.3)' + return dot + } + + function weightFractionText(detail) { + if (detail.remainingWeightG == null || detail.initialWeightG == null) return null + return Math.round(detail.remainingWeightG) + 'g / ' + Math.round(detail.initialWeightG) + 'g' + } + + function spoolRowElement(extruderName, spoolId) { + var row = document.createElement('div') + row.style.cssText = 'margin:4px 0' + + var mainLine = document.createElement('div') + mainLine.style.cssText = 'display:flex;align-items:center;justify-content:space-between' + + var nameCell = document.createElement('span') + nameCell.style.cssText = 'display:flex;align-items:center;overflow:hidden' + + var extruderLabel = document.createElement('strong') + extruderLabel.style.cssText = 'margin-right:6px' + extruderLabel.textContent = extruderName + nameCell.appendChild(extruderLabel) + + var detail = spoolId == null ? null : spoolDetail(spoolId) + var nameText = document.createElement('span') + nameText.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap' + if (spoolId == null) { + nameText.textContent = 'no spool' + nameText.style.opacity = '.7' + } else { + if (detail && detail.colorHex) nameCell.appendChild(colorDotElement(detail.colorHex)) + nameText.textContent = (detail && detail.name) || ('#' + spoolId) + } + nameCell.appendChild(nameText) + mainLine.appendChild(nameCell) + + if (spoolId != null) { + mainLine.appendChild(iconButtonElement('clear', function onClearClicked() { + clearExtruderSpool(extruderName).then(poll, poll) + })) + } + row.appendChild(mainLine) + + var weightText = detail ? weightFractionText(detail) : null + if (weightText) { + var weightLine = document.createElement('div') + weightLine.style.cssText = 'opacity:.7;font-size:11px' + weightLine.textContent = weightText + row.appendChild(weightLine) + } + + return row + } + + function renderStatus(status) { + var extruderSpools = status.extruder_spools || {} + var extruderNames = Object.keys(extruderSpools) + var card = cardElement() + card.textContent = '' + card.appendChild(headingElement()) + + if (!extruderNames.length) { + var emptyState = document.createElement('div') + emptyState.style.cssText = 'opacity:.7' + emptyState.textContent = 'no extruders reported' + card.appendChild(emptyState) + return + } + extruderNames.forEach(function appendRow(extruderName) { + card.appendChild(spoolRowElement(extruderName, extruderSpools[extruderName])) + }) + } + + function poll() { + fetchStatus().then(renderStatus, removeCardElement) + } + + function whenDocumentIsReady(callback) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback) + return + } + callback() + } + + whenDocumentIsReady(function start() { + poll() + window.setInterval(poll, POLL_INTERVAL_MS) + }) +})() diff --git a/fluidd/scripts/patch-fluidd.sh b/fluidd/scripts/patch-fluidd.sh index 9ce4d47..0862cfb 100755 --- a/fluidd/scripts/patch-fluidd.sh +++ b/fluidd/scripts/patch-fluidd.sh @@ -273,10 +273,23 @@ apply_patch "Tool row hides unused tools" "$DASHBOARD_CHUNK" \ # Bump the revision strings whenever the patches above change. apply_patch "Patched page served fresh" "$PLUGIN_DIR/files/fluidd/sw.js" \ '{"revision":"4b86906913a0847d07c33e3a67f2094d","url":"index.html"}' \ - '{"revision":"b3d-index-html-1","url":"index.html"}' + '{"revision":"b3d-index-html-2","url":"index.html"}' apply_patch "Patched tool row served fresh" "$PLUGIN_DIR/files/fluidd/sw.js" \ "{\"revision\":null,\"url\":\"assets/$(basename "$DASHBOARD_CHUNK")\"}" \ "{\"revision\":\"b3d-dashboard-1\",\"url\":\"assets/$(basename "$DASHBOARD_CHUNK")\"}" +# 11. A small dashboard card for FilaMan, an NFC/RFID filament tracking system with its own +# Moonraker component, independent of Spoolman. The card polls that component's own REST +# endpoints and renders itself, so it needs no store or Vuex patch, only a script tag. Inert +# on a printer without the filaman Moonraker component: its first request fails, the card is +# never created, and it stops polling. +install_file "FilaMan card" \ + "$PLUGIN_DIR/patches/filaman-card.js" \ + "$PLUGIN_DIR/files/fluidd/b3d-filaman-card.js" + +apply_patch "FilaMan card loaded" "$PLUGIN_DIR/files/fluidd/index.html" \ + '' \ + '' + echo "Done. All Bespok3d patches are present."