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."