/* global BLI */ if (window.__BLI_BUILDER_DEFINED__) { console.warn('[BLI] builder.js already loaded – skipping duplicate init'); } else { window.__BLI_BUILDER_DEFINED__ = true; console.log('[BLI] builder.js loaded'); window.BLIBuilder = function () { return { drag: false, items: [], papers: [], sizes: [], busy: false, async init() { try { await Promise.all([this.loadPapers(), this.loadSizes()]); await this.loadDraft(); if (!this.items.length && Number(BLI.hydrateFromCart)) { await this.loadFromCart(); } window.addEventListener('beforeunload', () => this.saveDraftSync()); } catch (e) { console.error('[BLI] init error', e); } }, money(v) { if (!v) v = 0; const currency = (BLI && BLI.currency) ? BLI.currency : 'USD'; return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(v); }, _fd(action) { const f = new FormData(); f.append('action', action); f.append('_wpnonce', BLI.ajaxNonce); return f; }, _sleep(ms){ return new Promise(r=>setTimeout(r,ms)); }, async loadPapers() { const res = await fetch(BLI.rest + 'papers', { headers: { 'X-WP-Nonce': (BLI.restNonce || BLI.nonce) } }); this.papers = await res.json(); }, async loadSizes() { const res = await fetch(BLI.rest + 'sizes', { headers: { 'X-WP-Nonce': (BLI.restNonce || BLI.nonce) } }); this.sizes = await res.json(); }, _dedupeIncoming(rows) { const seen = new Set(this.items.map(x => x.attachmentId).filter(Boolean)); const out = []; for (const r of rows || []) { const aid = r.attachmentId || r.attachment_id; if (!aid || seen.has(aid)) continue; seen.add(aid); out.push(r); } return out; }, async loadDraft() { try { const f = this._fd('bli_get_draft'); const res = await fetch(BLI.ajax, { method: 'POST', body: f }); const data = await res.json(); const rows = (data && data.success && data.data && data.data.rows) ? data.data.rows : (data.rows || []); const toAdd = this._dedupeIncoming(rows); for (const r of toAdd) { const it = { localId: 'loc_' + Math.random().toString(36).slice(2), name: r.name || 'file', file: null, status: 'complete', progress: 100, attachmentId: r.attachmentId || r.attachment_id, thumb: r.thumb || null, paperId: String(r.paperId || ''), sizeId: String(r.sizeId || ''), border: r.border || 'Full Bleed', qty: Number(r.qty || 1), unit: 0 }; this.items.push(it); } for (const it of this.items) { if (it.paperId && it.sizeId) await this.reprice(it); } this.items = [...this.items]; } catch(e) { console.warn('[BLI] loadDraft failed', e); } }, async loadFromCart() { try { const f = this._fd('bli_cart_rows'); const res = await fetch(BLI.ajax, { method: 'POST', body: f }); const data = await res.json(); const rows = (data && data.success && data.data && data.data.rows) ? data.data.rows : (data.rows || []); const toAdd = this._dedupeIncoming(rows); for (const r of toAdd) { const it = { localId: 'loc_' + Math.random().toString(36).slice(2), name: r.name || 'file', file: null, status: 'complete', progress: 100, attachmentId: r.attachmentId, thumb: r.thumb || null, paperId: String(r.paperId || ''), sizeId: String(r.sizeId || ''), border: r.border || 'Full Bleed', qty: Number(r.qty || 1), unit: 0 }; this.items.push(it); } for (const it of this.items) { if (it.paperId && it.sizeId) await this.reprice(it); } this.items = [...this.items]; } catch(e) { console.warn('[BLI] loadFromCart failed', e); } }, handleDrop(e) { this.drag = false; const files = Array.from(e.dataTransfer.files || []); if (files.length) this.uploadBatch(files); }, handleFileChoose(e) { const files = Array.from(e.target.files || []); if (files.length) this.uploadBatch(files); e.target.value = ''; }, async uploadBatch(files) { for (const f of files) await this.uploadOne(f); }, async uploadOne(file) { const maxBytes = (Number(BLI.maxSizeMB || 100)) * 1024 * 1024; if (file.size > maxBytes) { alert('File too large'); return; } const local = { localId: 'loc_' + Math.random().toString(36).slice(2), name: file.name, file, status: 'uploading', progress: 0, attachmentId: null, thumb: null, paperId: '', sizeId: '', border: 'Full Bleed', qty: 1, unit: 0 }; this.items.push(local); this.items = [...this.items]; const fd = this._fd('bli_upload_file'); fd.append('file', file); try { const res = await fetch(BLI.ajax, { method:'POST', body: fd }); const data = await res.json(); if (!data || data.success === false) throw new Error((data && data.message) || 'Upload failed'); local.status = 'complete'; local.progress = 100; local.attachmentId = data.attachment_id; local.thumb = data.thumb || null; local.name = data.name || local.name; this.items = [...this.items]; this.saveDraft(); } catch (err) { console.error(err); local.status = 'error'; this.items = [...this.items]; } }, async reprice(it) { if (!it.paperId || !it.sizeId) { it.unit = 0; return; } const url = BLI.rest + 'price?paper_id=' + it.paperId + '&size_id=' + it.sizeId + '&border=' + encodeURIComponent(it.border || 'Full Bleed'); const res = await fetch(url, { headers: { 'X-WP-Nonce': (BLI.restNonce || BLI.nonce) } }); const data = await res.json(); it.unit = parseFloat(data.price || 0); this.items = [...this.items]; }, async onChange(it){ await this.reprice(it); this.saveDraft(); }, subtotal() { return this.items.reduce((s,it)=> s + (Number(it.unit||0) * Number(it.qty||0)), 0); }, async remove(idx) { const it = this.items[idx]; this.items.splice(idx,1); this.items = [...this.items]; if (it && it.attachmentId && it.status === 'complete') { try { const f = this._fd('bli_delete_temp'); f.append('attachment_id', it.attachmentId); await fetch(BLI.ajax, { method:'POST', body:f }); } catch(e){} } this.saveDraft(); }, _saveTimer: null, saveDraft() { clearTimeout(this._saveTimer); this._saveTimer = setTimeout(()=>this.saveDraftSync(), 250); }, async saveDraftSync() { const rows = this.items .filter(it => it.attachmentId) .map(it => ({ attachmentId: it.attachmentId, thumb: it.thumb || '', name: it.name || '', paperId: Number(it.paperId || 0), sizeId: Number(it.sizeId || 0), border: it.border || 'Full Bleed', qty: Number(it.qty || 1) })); const f = this._fd('bli_save_draft'); f.append('rows', JSON.stringify(rows)); try { await fetch(BLI.ajax, { method:'POST', body:f }); } catch(e){} }, async addAllToCart() { if (!this.items.length) return; try { this.busy = true; await this.saveDraftSync(); const rows = this.items.filter(it=>it.attachmentId).map(it => ({ file_id: it.attachmentId, paper_id: Number(it.paperId || 0), size_id: Number(it.sizeId || 0), border: it.border || 'Full Bleed', qty: Number(it.qty || 1) })); const f = this._fd('bli_add_to_cart'); f.append('rows', JSON.stringify(rows)); const res = await fetch(BLI.ajax, { method:'POST', body:f }); const data = await res.json(); if (!data || data.success === false) throw new Error((data && data.message) || 'Add to cart failed'); try { const cf = this._fd('bli_clear_draft'); await fetch(BLI.ajax, { method:'POST', body: cf }); } catch(e){} window.location.href = data.cart_url; } catch (err) { alert(err.message); } finally { this.busy = false; } } }; }; } https://www.blacklabimaging.com/page-sitemap.xml 2025-08-17T02:51:10+00:00 https://www.blacklabimaging.com/jet-menu-sitemap.xml 2024-09-20T05:40:28+00:00 https://www.blacklabimaging.com/jet-popup-sitemap.xml 2025-06-29T03:02:39+00:00 https://www.blacklabimaging.com/jet-woo-builder-sitemap.xml 2025-06-29T03:32:41+00:00 https://www.blacklabimaging.com/product-sitemap.xml 2025-08-16T23:24:17+00:00 https://www.blacklabimaging.com/product-sitemap2.xml 2025-06-29T05:22:29+00:00 https://www.blacklabimaging.com/product-sitemap3.xml 2025-08-15T10:43:28+00:00 https://www.blacklabimaging.com/product-sitemap4.xml 2025-08-16T23:24:17+00:00 https://www.blacklabimaging.com/pp_video_block-sitemap.xml 2024-12-04T20:38:31+00:00 https://www.blacklabimaging.com/jet-theme-core-sitemap.xml 2025-07-15T23:04:37+00:00 https://www.blacklabimaging.com/fine-art-print-price-sitemap.xml 2025-08-11T23:52:43+00:00 https://www.blacklabimaging.com/paper-type-sitemap.xml 2025-08-12T03:22:51+00:00 https://www.blacklabimaging.com/condition-sitemap.xml 2025-01-19T12:54:51+00:00 https://www.blacklabimaging.com/drop-off-locations-sitemap.xml 2024-10-21T02:21:31+00:00 https://www.blacklabimaging.com/post_tag-sitemap.xml https://www.blacklabimaging.com/wc_square_synced-sitemap.xml 2025-08-16T23:24:17+00:00 https://www.blacklabimaging.com/product_brand-sitemap.xml 2025-08-16T18:28:19+00:00 https://www.blacklabimaging.com/product_cat-sitemap.xml 2025-08-16T23:24:17+00:00 https://www.blacklabimaging.com/product_tag-sitemap.xml 2025-08-16T23:24:17+00:00 https://www.blacklabimaging.com/product_shipping_class-sitemap.xml 2025-08-15T10:44:19+00:00 https://www.blacklabimaging.com/pa_color-camera-sitemap.xml 2025-08-15T10:44:45+00:00 https://www.blacklabimaging.com/pa_dimensions-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_disposable-camera-type-sitemap.xml 2025-08-16T22:10:41+00:00 https://www.blacklabimaging.com/pa_film-base-sitemap.xml 2025-08-16T22:17:36+00:00 https://www.blacklabimaging.com/pa_film-color-balance-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_film-format-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_film-layer-thickness-sitemap.xml 2025-08-16T22:17:36+00:00 https://www.blacklabimaging.com/pa_film-processing-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_film-resolution-sitemap.xml 2025-08-16T22:17:36+00:00 https://www.blacklabimaging.com/pa_films-sitemap.xml 2025-08-16T22:16:11+00:00 https://www.blacklabimaging.com/pa_filmspeed-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_filmtype-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_lens-angleview-sitemap.xml 2025-08-15T10:43:59+00:00 https://www.blacklabimaging.com/pa_lens-blades-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-filtersize-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-focallength-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-focustype-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-format-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-marcorepro-sitemap.xml 2020-11-21T16:10:24+00:00 https://www.blacklabimaging.com/pa_lens-maxaperture-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-maxmag-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-minaperture-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-minfocus-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-opticaldesign-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lens-stabilization-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_lensmount-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/pa_number-of-exposures-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_processing-film-type-sitemap.xml 2025-06-29T05:19:02+00:00 https://www.blacklabimaging.com/pa_rolls-sitemap.xml 2025-08-16T22:22:07+00:00 https://www.blacklabimaging.com/pa_weight-sitemap.xml 2025-08-16T22:27:12+00:00 https://www.blacklabimaging.com/yith_product_brand-sitemap.xml 2025-08-16T23:24:17+00:00