| … | … | @@ -20,6 +20,47 @@ |
| 20 | 20 | var cfg = function () { try { return JSON.parse(localStorage.getItem('mirall:config') || '{}') } catch (e) { return {} } } |
| 21 | 21 | var setCfg = function (patch) { var c = Object.assign(cfg(), patch); localStorage.setItem('mirall:config', JSON.stringify(c)); return c } |
| 22 | 22 | |
| 23 | // Folder picker: replaces Electron's native dialog. Browses the DAEMON's filesystem via |
| 24 | // /fs/list — and since the daemon is LOCAL, that's your own machine. Resolves the chosen |
| 25 | // absolute path (string), or null on cancel. Matches browseShareFolder(): Promise<string|null>. |
| 26 | function pickFolder () { |
| 27 | return new Promise(function (resolve) { |
| 28 | var cur = null |
| 29 | var ov = document.createElement('div') |
| 30 | ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:2147483647;display:flex;align-items:center;justify-content:center;font-family:system-ui,sans-serif' |
| 31 | var box = document.createElement('div') |
| 32 | box.style.cssText = 'background:#fff;color:#111;width:min(560px,92vw);max-height:78vh;border-radius:12px;box-shadow:0 12px 48px rgba(0,0,0,.4);display:flex;flex-direction:column;overflow:hidden' |
| 33 | if (document.documentElement.classList.contains('dark')) { box.style.background = '#1b1b1f'; box.style.color = '#eee' } |
| 34 | var title = document.createElement('div'); title.textContent = 'Choose a folder'; title.style.cssText = 'padding:14px 16px;font-weight:600;border-bottom:1px solid rgba(128,128,128,.25)' |
| 35 | var pathEl = document.createElement('div'); pathEl.style.cssText = 'padding:8px 16px;font-size:13px;opacity:.75;word-break:break-all' |
| 36 | var list = document.createElement('div'); list.style.cssText = 'overflow:auto;flex:1;min-height:140px;border-top:1px solid rgba(128,128,128,.15)' |
| 37 | var footer = document.createElement('div'); footer.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;padding:12px 16px;border-top:1px solid rgba(128,128,128,.25)' |
| 38 | var cancel = document.createElement('button'); cancel.textContent = 'Cancel'; cancel.style.cssText = 'padding:8px 14px;border-radius:8px;border:1px solid rgba(128,128,128,.4);background:transparent;color:inherit;cursor:pointer' |
| 39 | var sel = document.createElement('button'); sel.textContent = 'Select this folder'; sel.style.cssText = 'padding:8px 14px;border-radius:8px;border:0;background:#2563eb;color:#fff;cursor:pointer' |
| 40 | footer.appendChild(cancel); footer.appendChild(sel) |
| 41 | box.appendChild(title); box.appendChild(pathEl); box.appendChild(list); box.appendChild(footer); ov.appendChild(box); document.body.appendChild(ov) |
| 42 | function done (v) { try { document.body.removeChild(ov) } catch (e) {} resolve(v) } |
| 43 | cancel.onclick = function () { done(null) } |
| 44 | sel.onclick = function () { done(cur) } |
| 45 | ov.onclick = function (e) { if (e.target === ov) done(null) } |
| 46 | function row (label, isDir, onClick) { |
| 47 | var r = document.createElement('div') |
| 48 | r.textContent = (isDir ? '📁 ' : '📄 ') + label |
| 49 | r.style.cssText = 'padding:9px 16px;font-size:14px;' + (isDir ? 'cursor:pointer;' : 'opacity:.4;') |
| 50 | if (onClick) { r.onmouseenter = function () { r.style.background = 'rgba(128,128,128,.15)' }; r.onmouseleave = function () { r.style.background = '' }; r.onclick = onClick } |
| 51 | list.appendChild(r) |
| 52 | } |
| 53 | function load (p) { |
| 54 | fetch('/fs/list' + (p ? '?path=' + encodeURIComponent(p) : '')).then(function (r) { return r.json() }).then(function (d) { |
| 55 | cur = d.path; pathEl.textContent = d.path; list.innerHTML = '' |
| 56 | if (d.parent) row('.. (up)', true, function () { load(d.parent) }) |
| 57 | d.entries.forEach(function (e) { row(e.name, e.dir, e.dir ? function () { load(d.path + '/' + e.name) } : null) }) |
| 58 | }).catch(function () { pathEl.textContent = '(cannot read this folder)' }) |
| 59 | } |
| 60 | load(null) |
| 61 | }) |
| 62 | } |
| 63 | |
| 23 | 64 | window.bridge = { |
| 24 | 65 | // ── LOAD-BEARING: the raw NDJSON worker pipe ── |
| 25 | 66 | startWorker: function () { return Promise.resolve() }, // daemon already runs the worker |
| … | … | @@ -57,8 +98,8 @@ |
| 57 | 98 | // ── downloads / folders (LOCAL machine — fs-browse picker replaces these, see notes) ── |
| 58 | 99 | getDownloadFolder: function () { return Promise.resolve(cfg().downloadFolder || '') }, |
| 59 | 100 | setDownloadFolder: function (f) { return Promise.resolve(setCfg({ downloadFolder: f })) }, |
| 60 | | browseDownloadFolder: function () { return Promise.resolve(null) }, // TODO fs:list-dir picker |
| 61 | | browseShareFolder: function () { return Promise.resolve(null) }, // TODO fs:list-dir picker |
| 101 | browseDownloadFolder: function () { return pickFolder() }, |
| 102 | browseShareFolder: function () { return pickFolder() }, |
| 62 | 103 | startOwnedFolderWatcher: function () { return Promise.resolve() }, stopOwnedFolderWatcher: function () { return Promise.resolve() }, |
| 63 | 104 | |
| 64 | 105 | // ── notifications → browser Notification API ── |