Spaces:
Running
Running
Download runtime-browser.js from johnpitteera/elog-book: direct link, hf CLI and curl.
- Browser
- Download file 8.63 kB
-
https://huggingface.co/spaces/johnpitteera/elog-book/resolve/main/runtime-browser.js
- Command line
-
hf download hf://spaces/johnpitteera/elog-book/runtime-browser.js
-
curl -L -o runtime-browser.js https://huggingface.co/spaces/johnpitteera/elog-book/resolve/main/runtime-browser.js
8.63 kB
| // eLOG Book browser demo runtime: implements window.claude.use(...) with data kept in this | |
| // browser's localStorage, plus demo "people" the visitor can switch between. No server. | |
| (function () { | |
| 'use strict'; | |
| const KEY = 'elog-demo-db-v1', WHO = 'elog-demo-person'; | |
| const J = o => JSON.parse(JSON.stringify(o)); | |
| const PEOPLE = [ | |
| { id: 'u_demo_admin', name: 'Demo Administrator', roles: ['Administrator'], admin: true }, | |
| { id: 'u_demo_op', name: 'Asha Operator', roles: ['Operator'], supervisor: 'u_demo_sup', admin: false }, | |
| { id: 'u_demo_sup', name: 'Ravi Supervisor', roles: ['Supervisor'], admin: false }, | |
| { id: 'u_demo_qa', name: 'Meera QA', roles: ['QA'], admin: false }, | |
| { id: 'u_demo_eng', name: 'Karan Engineering', roles: ['Engineering'], admin: false }, | |
| ]; | |
| const me = () => PEOPLE.find(p => p.id === (safeGet(WHO) || 'u_demo_op')) || PEOPLE[1]; | |
| function safeGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } } | |
| function safeSet(k, v) { try { localStorage.setItem(k, v); return true; } catch (e) { return false; } } | |
| // ---- store | |
| let store = new Map(); | |
| function load() { try { const o = JSON.parse(safeGet(KEY) || 'null'); store = new Map(o ? Object.entries(o) : []); } catch (e) { store = new Map(); } if (!store.size) seed(); } | |
| function persist() { if (!safeSet(KEY, JSON.stringify(Object.fromEntries(store)))) { console.warn('eLOG demo: browser storage is full or blocked'); } } | |
| function seed() { | |
| const S = window.ELOG_DEMO_SEED || {}; | |
| store.set('cfg/main', { siteName: 'Hugging Face browser demo', demoMode: false, roles: [] }); | |
| (S.assets || []).forEach(a => store.set('cfg/main/assets/' + a.id, a)); | |
| (S.masters || []).forEach(m => store.set('cfg/main/masters/' + m.id, m)); | |
| store.set('cfg/main/schedules/s-env-rm105', { tpl: 'ENV', assetId: 'RM-105', freq: 'Per shift' }); | |
| store.set('cfg/main/schedules/s-bal-in07', { tpl: 'BAL', assetId: 'IN-BAL-07', freq: 'Daily' }); | |
| PEOPLE.forEach(p => store.set('cfg/main/roles/' + p.id, { roles: p.roles, supervisor: p.supervisor || '', active: true, dept: 'Production - Oral Solids' })); | |
| persist(); | |
| } | |
| load(); | |
| const listeners = []; | |
| const colOf = p => p.split('/').slice(0, -1).join('/'); | |
| const META = { fromCache: false, hasPendingWrites: false }; | |
| const snapDoc = p => { const e = store.get(p); return { id: p.split('/').pop(), exists: !!e, data: () => e ? J(e) : undefined, metadata: META }; }; | |
| function runQuery(q) { | |
| let docs = Array.from(store.keys()).filter(p => colOf(p) === q.path).sort().map(snapDoc); | |
| for (const [f, op, v] of q.wh) docs = docs.filter(d => { const x = d.data()[f]; switch (op) { case '==': return x === v; case '!=': return x !== v; case '<': return x < v; case '<=': return x <= v; case '>': return x > v; case '>=': return x >= v; case 'in': return (v || []).includes(x); case 'not-in': return !(v || []).includes(x); case 'array-contains': return Array.isArray(x) && x.includes(v); } return true; }); | |
| if (q.ob) { const [f, dir] = q.ob; const has = docs.filter(d => d.data()[f] != null), no = docs.filter(d => d.data()[f] == null); has.sort((a, b) => { const x = a.data()[f], y = b.data()[f]; const c = x < y ? -1 : x > y ? 1 : 0; return dir === 'desc' ? -c : c; }); docs = has.concat(no); } | |
| if (q.lim) docs = docs.slice(0, q.lim); | |
| return { docs, size: docs.length, empty: !docs.length, docChanges: () => [], metadata: META }; | |
| } | |
| function notify() { setTimeout(() => listeners.forEach(l => { if (!l.dead) l.next(l.kind === 'doc' ? snapDoc(l.path) : runQuery(l.q)); }), 0); } | |
| window.addEventListener('storage', e => { if (e.key === KEY) { load(); notify(); } }); | |
| function write(fn) { return new Promise((res, rej) => setTimeout(() => { try { fn(); } catch (e) { rej({ code: 'invalid_argument', message: String(e.message || e) }); return; } persist(); notify(); res(); }, 0)); } | |
| function merge(a, b) { for (const k in b) { if (b[k] && typeof b[k] === 'object' && !Array.isArray(b[k]) && a[k] && typeof a[k] === 'object' && !Array.isArray(a[k])) merge(a[k], b[k]); else a[k] = J(b[k]); } } | |
| const SEG = /^(?!\.\.?$)[A-Za-z0-9_\-.~:@+]{1,200}$/; | |
| function check(p, even) { const s = String(p || '').split('/'); if (!p || !s.every(x => SEG.test(x)) || (s.length % 2 === 0) !== even) throw new TypeError('Bad path: ' + p); } | |
| const canWriteCfg = () => me().admin; | |
| function guard(path) { if ((path.startsWith('cfg/') || path === 'cfg/main') && !canWriteCfg()) throw new Error('Only the Demo Administrator can change setup. Switch person at the top of the page.'); if (path.startsWith('delegations/') && path.split('/')[1] !== me().id && !me().admin) throw new Error('You can only set your own delegation.'); } | |
| const newId = () => 'd' + Math.random().toString(36).slice(2, 12) + Date.now().toString(36).slice(-4); | |
| function docRef(path) { | |
| check(path, true); | |
| return { id: path.split('/').pop(), path, | |
| get: () => Promise.resolve(snapDoc(path)), | |
| set: d => write(() => { guard(path); store.set(path, J(d)); }), | |
| update: d => write(() => { guard(path); if (!store.has(path)) throw new Error('missing document'); const x = J(store.get(path)); merge(x, d); store.set(path, x); }), | |
| delete: () => write(() => { guard(path); store.delete(path); }), | |
| acquire: async () => ({ acquired: true }), | |
| onSnapshot: next => { const l = { kind: 'doc', path, next }; listeners.push(l); setTimeout(() => !l.dead && next(snapDoc(path)), 0); return () => { l.dead = true; }; }, | |
| collection: sub => colRef(path + '/' + sub) }; | |
| } | |
| function query(q) { | |
| return { where: (f, op, v) => query(Object.assign({}, q, { wh: q.wh.concat([[f, op, v]]) })), orderBy: (f, d) => query(Object.assign({}, q, { ob: [f, d || 'asc'] })), limit: n => query(Object.assign({}, q, { lim: n })), | |
| get: () => Promise.resolve(runQuery(q)), | |
| onSnapshot: next => { const l = { kind: 'col', q, next }; listeners.push(l); setTimeout(() => !l.dead && next(runQuery(q)), 0); return () => { l.dead = true; }; } }; | |
| } | |
| function colRef(path) { check(path, false); return Object.assign(query({ path, wh: [], ob: null, lim: 0 }), { path, doc: id => docRef(path + '/' + (id || newId())), add: d => { const r = docRef(path + '/' + newId()); return r.set(d).then(() => r); } }); } | |
| const db = Object.freeze({ doc: docRef, collection: colRef }); | |
| // ---- people | |
| const COLORS = ['#1B5E8C', '#1D7549', '#8F5B00', '#7A3E9D', '#AE2A21']; | |
| function avatar(p) { const i = PEOPLE.indexOf(p); const ini = p.name.split(' ').map(w => w[0]).join('').slice(0, 2); return 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" rx="32" fill="' + COLORS[(i + 5) % 5] + '"/><text x="32" y="41" font-family="Arial,sans-serif" font-size="24" font-weight="700" fill="#fff" text-anchor="middle">' + ini + '</text></svg>'); } | |
| const prof = (id) => { const p = PEOPLE.find(x => x.id === id); return { id, name: p ? p.name : '', avatarUrl: p ? avatar(p) : '', color: '#888', email: null, isMe: id === me().id, guest: false }; }; | |
| const user = Object.freeze({ | |
| isOwner: async () => me().admin, canEdit: async () => me().admin, can: async n => n === 'data.write' ? true : n === 'files.write' ? me().admin : false, | |
| me: async () => { const p = me(); return { id: p.id, name: p.name, avatarUrl: avatar(p), color: '#888', email: null, isOwner: p.admin, canEdit: p.admin }; }, | |
| id: async () => me().id, name: async () => me().name, avatarUrl: async () => avatar(me()), email: async () => null, | |
| profiles: async ids => { const o = {}; [].concat(ids || []).forEach(i => { o[i] = prof(i); }); return o; }, | |
| search: async q => PEOPLE.filter(p => !q || p.name.toLowerCase().includes(String(q).toLowerCase())).map(p => prof(p.id)), | |
| }); | |
| const downloads = Object.freeze({ save: async r => { const b = r.data instanceof Blob ? r.data : new Blob([r.data]); const u = URL.createObjectURL(b); const a = document.createElement('a'); a.href = u; a.download = r.filename || 'download'; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(u), 10000); return { status: 'saved' }; } }); | |
| window.ELOG_DEMO = { | |
| people: PEOPLE.map(p => ({ id: p.id, name: p.name, roles: p.roles })), | |
| current: () => me().id, | |
| switchTo: id => { safeSet(WHO, id); location.reload(); }, | |
| reset: () => { try { localStorage.removeItem(KEY); } catch (e) {} location.reload(); }, | |
| }; | |
| const caps = { db, user, downloads }; | |
| window.claude = Object.freeze({ use: name => Promise.resolve(caps[name] || null) }); | |
| })(); | |