livedesk 0.1.688 → 0.1.689
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hub/src/control-presentation-borrow-contract.test.mjs +32 -4
- package/package.json +1 -1
- package/web/dist/app.html +1 -1
- package/web/dist/app.webmanifest +1 -1
- package/web/dist/assets/{AgentSettingsTab-KqhCzxB2.js → AgentSettingsTab-DqcDj-on.js} +1 -1
- package/web/dist/assets/{AgentsPage-CKEnSTh-.js → AgentsPage-DtUpg-_-.js} +1 -1
- package/web/dist/assets/{CaptureGallery-DKz8QtoW.js → CaptureGallery-BDTCKOQU.js} +1 -1
- package/web/dist/assets/HubApp-oCD8fLqO.js +182 -0
- package/web/dist/assets/{MonitorStackIcon-cTkKvZN8.js → MonitorStackIcon-4RlZK1aS.js} +1 -1
- package/web/dist/assets/{SettingsPage-Bczqk0_A.js → SettingsPage-CDQjf6HM.js} +1 -1
- package/web/dist/assets/{ShareFilesPage-Bp3tzbpP.js → ShareFilesPage-DbPDQMhc.js} +1 -1
- package/web/dist/assets/{SupportPage-BSryZiae.js → SupportPage-DaPaJB2u.js} +1 -1
- package/web/dist/assets/{VuvoDeskApp-B8VUwBZP.js → VuvoDeskApp-DBUI3UMT.js} +1 -1
- package/web/dist/assets/{app-BmosrH7b.js → app-D8_G4B_m.js} +2 -2
- package/web/dist/assets/{main-DoTeXSBC.js → main-BwD1pIhw.js} +2 -2
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +2 -2
- package/web/dist/vuvodesk-build-evidence.json +45 -45
- package/web/dist/assets/HubApp-B7wrc0on.js +0 -182
|
@@ -479,7 +479,7 @@ function parseBorrowLifecycleFramePacket(data) {
|
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
test('
|
|
482
|
+
test('Hub and phone Wall borrowers follow Control without stopping or retaining each other', {
|
|
483
483
|
timeout: 20_000
|
|
484
484
|
}, async () => {
|
|
485
485
|
const {
|
|
@@ -563,7 +563,7 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
563
563
|
).toString('base64')
|
|
564
564
|
});
|
|
565
565
|
|
|
566
|
-
const connectBorrower = async label => {
|
|
566
|
+
const connectBorrower = async (label, options = {}) => {
|
|
567
567
|
const socket = new WebSocket(
|
|
568
568
|
`ws://127.0.0.1:${httpPort}/api/remote/frames/ws?devices=${encodeURIComponent(deviceId)}`,
|
|
569
569
|
{ headers: { Origin: baseUrl }, perMessageDeflate: false }
|
|
@@ -603,7 +603,8 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
603
603
|
maxWidth: 960,
|
|
604
604
|
maxHeight: 540,
|
|
605
605
|
quality: 55,
|
|
606
|
-
monitorIndex: 0
|
|
606
|
+
monitorIndex: 0,
|
|
607
|
+
...options
|
|
607
608
|
}));
|
|
608
609
|
return { socket, observed, subscribeJsonIndex };
|
|
609
610
|
};
|
|
@@ -799,6 +800,20 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
799
800
|
assert.equal(firstFrame.streamPurpose, 'control');
|
|
800
801
|
assert.equal(firstFrame.monitorIndex, 0);
|
|
801
802
|
|
|
803
|
+
const hubBorrower = await connectBorrower('desktop Hub', {
|
|
804
|
+
fps: 30, maxWidth: 1920, maxHeight: 1080
|
|
805
|
+
});
|
|
806
|
+
await waitForBorrowStart(hubBorrower.observed, initialControl.commandId,
|
|
807
|
+
hubBorrower.subscribeJsonIndex, 'desktop Hub borrows the same Control owner');
|
|
808
|
+
sendControlFrame(initialControl, 4, true);
|
|
809
|
+
await waitForExactReady(hubBorrower.observed, initialControl, 0, 'desktop Hub key readiness');
|
|
810
|
+
const otherMonitor = await connectBorrower('other monitor', { monitorIndex: 1 });
|
|
811
|
+
await waitForBorrowLifecycle(() => otherMonitor.observed.json.some(message =>
|
|
812
|
+
JSON.stringify(message).includes('CONTROL_CAPTURE_OWNS_DEVICE')),
|
|
813
|
+
'different monitor reports ownership conflict');
|
|
814
|
+
assert.equal(otherMonitor.observed.frames.length, 0, 'never substitute a different monitor');
|
|
815
|
+
otherMonitor.socket.close();
|
|
816
|
+
|
|
802
817
|
const firstStopCommandIndex = agentMessages.length;
|
|
803
818
|
const stopInitialPromise = postJson(
|
|
804
819
|
`/api/remote/devices/${encodeURIComponent(deviceId)}/live/stop`,
|
|
@@ -855,6 +870,8 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
855
870
|
const replacementFrameIndex = firstBorrower.observed.frames.length;
|
|
856
871
|
sendOpen(replacementControl);
|
|
857
872
|
sendControlFrame(replacementControl, 1, true);
|
|
873
|
+
await waitForBorrowStart(hubBorrower.observed, replacementControl.commandId,
|
|
874
|
+
0, 'desktop Hub follows the replacement generation');
|
|
858
875
|
const replacementBorrowStart = await waitForBorrowStart(
|
|
859
876
|
firstBorrower.observed,
|
|
860
877
|
replacementControl.commandId,
|
|
@@ -878,6 +895,8 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
878
895
|
await new Promise(resolveWait => setTimeout(resolveWait, 60));
|
|
879
896
|
assert.equal(firstBorrower.observed.frames.length, replacementFrameIndex);
|
|
880
897
|
sendControlFrame(replacementControl, 3, true);
|
|
898
|
+
await waitForExactReady(hubBorrower.observed, replacementControl, 0,
|
|
899
|
+
'desktop Hub replacement key readiness');
|
|
881
900
|
await waitForExactReady(
|
|
882
901
|
firstBorrower.observed,
|
|
883
902
|
replacementControl,
|
|
@@ -915,6 +934,10 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
915
934
|
stopCountBeforeBorrowerClose,
|
|
916
935
|
'Closing a read-only borrower must never stop the native Control owner.'
|
|
917
936
|
);
|
|
937
|
+
sendControlFrame(replacementControl, 4, true);
|
|
938
|
+
await waitForBorrowLifecycle(() => hubBorrower.observed.frames.some(frame =>
|
|
939
|
+
frame.commandId === replacementControl.commandId && frame.frameSeq === 4),
|
|
940
|
+
'desktop Hub keeps receiving after phone borrower closes');
|
|
918
941
|
|
|
919
942
|
const fallbackBorrower = await connectBorrower('fallback borrower');
|
|
920
943
|
await waitForBorrowStart(
|
|
@@ -924,7 +947,7 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
924
947
|
'fallback borrower Control binding'
|
|
925
948
|
);
|
|
926
949
|
const fallbackReadyIndex = fallbackBorrower.observed.json.length;
|
|
927
|
-
sendControlFrame(replacementControl,
|
|
950
|
+
sendControlFrame(replacementControl, 5, true);
|
|
928
951
|
await waitForExactReady(
|
|
929
952
|
fallbackBorrower.observed,
|
|
930
953
|
replacementControl,
|
|
@@ -984,6 +1007,11 @@ test('the browser Wall borrower follows exact Control key frames without owning
|
|
|
984
1007
|
'ordinary Wall browser binding after confirmed Control stop'
|
|
985
1008
|
);
|
|
986
1009
|
assert.equal(fallbackWallStart.started[0].presentationPurpose, '');
|
|
1010
|
+
await waitForBorrowLifecycle(() => hubBorrower.observed.json.some(message =>
|
|
1011
|
+
message.type === 'RemoteFrameLiveAutoStart'
|
|
1012
|
+
&& message.started?.some(item => item.commandId === fallbackWallCommand.message.commandId
|
|
1013
|
+
&& item.streamPurpose === 'wall' && item.readOnlyControlBorrow !== true)),
|
|
1014
|
+
'desktop Hub and phone both restore the ordinary Wall owner');
|
|
987
1015
|
} catch (error) {
|
|
988
1016
|
error.message += hubOutput.length > 0
|
|
989
1017
|
? `\nIsolated Hub output:\n${hubOutput.join('\n')}`
|
package/package.json
CHANGED
package/web/dist/app.html
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
.vuvodesk-static-boot [hidden] { display: none; }
|
|
53
53
|
@keyframes vuvodesk-static-boot-spin { to { transform: rotate(360deg); } }
|
|
54
54
|
</style>
|
|
55
|
-
<script type="module" crossorigin src="/assets/app-
|
|
55
|
+
<script type="module" crossorigin src="/assets/app-D8_G4B_m.js" data-vuvodesk-entry></script>
|
|
56
56
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DD1OZmLC.js">
|
|
57
57
|
<link rel="modulepreload" crossorigin href="/assets/pwa-bootstrap-ljOQTt2e.js">
|
|
58
58
|
</head>
|
package/web/dist/app.webmanifest
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as c,d as A,S as q,T as I,e as z}from"./icons-CuXJn-Hz.js";import{h as x}from"./MonitorStackIcon-
|
|
1
|
+
import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as c,d as A,S as q,T as I,e as z}from"./icons-CuXJn-Hz.js";import{h as x}from"./MonitorStackIcon-4RlZK1aS.js";import"./pwa-bootstrap-ljOQTt2e.js";const E={enabled:!0};function w(i){return{enabled:i?.enabled===!0}}async function L(i,p,f){let l=null;for(let s=0;s<2;s+=1){const g=await x("/api/settings",{signal:f},p);if(g.settings.agent?.enabled===i)return;try{await x("/api/settings",{method:"PATCH",signal:f,body:JSON.stringify({revision:g.revision,agent:{enabled:i}})},p);return}catch(a){if(l=a,s!==0||!String(a instanceof Error?a.message:a).startsWith("409 "))throw a}}throw l}function H({hubUrl:i,showToast:p}){const[f,l]=c.useState(E),[s,g]=c.useState(null),[a,m]=c.useState(""),[j,v]=c.useState(""),[y,d]=c.useState(""),b=c.useRef(0),u=c.useRef(null);c.useEffect(()=>{let e=!0;u.current?.abort();const n=new AbortController;u.current=n;const h=++b.current;return g(null),l(E),m(""),d(""),v(""),x("/api/settings/agent",{signal:n.signal},i).then(o=>{!e||n.signal.aborted||h!==b.current||(g(o.settings),l(w(o.settings)))}).catch(o=>{e&&!n.signal.aborted&&h===b.current&&d(o instanceof Error?o.message:String(o))}),()=>{e=!1,n.abort(),u.current===n&&(u.current=null)}},[i]);const k=(e,n)=>l(h=>({...h,[e]:n})),C=e=>{g(e),l(w(e))},S=()=>({generation:b.current,controller:u.current}),r=e=>e.generation===b.current&&e.controller!==null&&e.controller===u.current&&!e.controller.signal.aborted,R=async()=>{if(!s){d("Agent settings are not available yet.");return}const e=S();if(r(e)){m("save"),d(""),v("");try{if(await L(f.enabled,i,e.controller.signal),!r(e))return;const n=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(n.settings),v("Codex Agent settings saved.")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},T=async()=>{const e=S();if(r(e)){m("test"),d(""),v("");try{const n=await x("/api/settings/agent/test",{method:"POST",signal:e.controller.signal,body:JSON.stringify({})},i);if(!r(e))return;const h=await x("/api/settings/agent",{signal:e.controller.signal},i);if(!r(e))return;C(h.settings);const o=Number.isFinite(n.connection?.latencyMs)?Math.max(0,Math.round(n.connection.latencyMs)):null;p(`Codex Agent connection verified${o!==null?` in ${o} ms.`:"."}`,"success")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},D=s?.codexInstallation==="installed"&&s.codexAuth==="signed-in",N=s!==null;return t.jsxs("div",{className:"settings-tab-content agent-settings-content",children:[t.jsxs("section",{className:"settings-section-card agent-enable-card",children:[t.jsxs("label",{className:"settings-toggle agent-enable-toggle",children:[t.jsx("input",{type:"checkbox",checked:f.enabled,onChange:e=>k("enabled",e.target.checked),disabled:!N||a!==""}),t.jsx("span",{children:"Enable Codex Agent"})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," Enabled by default. Agent commands use only the Codex account signed in on this Hub."]})]}),y&&t.jsx("div",{className:"agent-settings-alert error",role:"alert",children:y}),j&&t.jsx("div",{className:"agent-settings-alert success",role:"status",children:j}),t.jsxs("section",{className:"settings-section-card",children:[t.jsxs("div",{className:"settings-section-heading",children:[t.jsxs("div",{children:[t.jsx("span",{children:"Connection"}),t.jsx("h3",{children:"Codex SDK / CLI"})]}),t.jsx(q,{size:20})]}),t.jsxs("div",{className:"agent-connection-row",children:[t.jsx("span",{className:`agent-connection-dot ${D?"connected":s?.codexAuth==="not-signed-in"?"unavailable":""}`}),t.jsx("span",{children:s?.codexInstallation==="not-installed"?"Codex SDK not installed":s?.codexAuth==="signed-in"?"Codex CLI signed in":s?.codexAuth==="not-signed-in"?"Codex CLI sign-in required":"Codex status not tested"}),t.jsxs("button",{className:"settings-reset",onClick:()=>{T()},disabled:a!=="",type:"button",children:[t.jsx(I,{size:15})," ",a==="test"?"Testing":"Test connection"]})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," The Hub uses the installed Codex SDK / CLI to plan approved commands and run them through VuvoDesk Agent tools on selected computers. VuvoDesk never asks for separate credentials."]})]}),t.jsx("div",{className:"settings-action-row agent-settings-actions",children:t.jsxs("button",{className:"text-button primary",onClick:()=>{R()},disabled:!N||a!=="",type:"button",children:[t.jsx(z,{size:15})," ",a==="save"?"Saving":"Save Agent settings"]})})]})}export{H as AgentSettingsTab};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as d,ae as ht,af as Ce,z as yt,G as ee,X as xe,ag as vt,ah as wt,ai as Ze,aj as kt,ak as bt,al as Se,b as H,am as Ct,p as xt,an as _e,o as St,ao as jt,S as Je,ap as At,a9 as Rt,c as Nt,aq as Pt,ar as It,A as Tt,as as ue,M as Xe}from"./icons-CuXJn-Hz.js";import{r as Et,h as F}from"./MonitorStackIcon-
|
|
1
|
+
import{j as t}from"./pwa-startup-DvbrYEQi.js";import{r as d,ae as ht,af as Ce,z as yt,G as ee,X as xe,ag as vt,ah as wt,ai as Ze,aj as kt,ak as bt,al as Se,b as H,am as Ct,p as xt,an as _e,o as St,ao as jt,S as Je,ap as At,a9 as Rt,c as Nt,aq as Pt,ar as It,A as Tt,as as ue,M as Xe}from"./icons-CuXJn-Hz.js";import{r as Et,h as F}from"./MonitorStackIcon-4RlZK1aS.js";import"./pwa-bootstrap-ljOQTt2e.js";const Ot=d.forwardRef(function({value:s,onChange:n,onSubmit:r,onOpenSuggestedTasks:i,suggestedOpen:f=!1,suggestedTaskCount:c,disabled:v=!1,placeholder:S="Ask VuvoDesk to work across your machines...",compact:g=!1},x){return t.jsxs("form",{className:"agents-composer",onSubmit:w=>{w.preventDefault(),r()},children:[t.jsx("button",{className:`agents-suggested-trigger ${f?"active":""}`,type:"button","aria-label":c===void 0?"Open Suggested tasks":`Open Suggested tasks (${c} available)`,"aria-expanded":f,onClick:i,disabled:v,children:t.jsx(ht,{size:17})}),t.jsx("textarea",{ref:x,value:s,onChange:w=>n(w.target.value.slice(0,4e3)),placeholder:S,"aria-label":"Ask VuvoDesk",rows:g?1:2,disabled:v}),t.jsxs("button",{className:"agents-run-button",type:"submit",disabled:v||!s.trim(),children:[v?t.jsx(Ce,{className:"spin",size:16}):t.jsx(yt,{size:16}),v?"Running":"Run"]})]})}),$t=["read","processControl","serviceControl","applicationControl","fileRead","fileWrite","fileDelete","shell","script","softwareInstall","network","systemPower","systemConfiguration","userAccount"];function de(e){return e.deviceName?.trim()||e.hostname?.trim()||e.deviceId}function pe(e){return e.connected!==!0?!1:e.synthetic===!0?!0:!e.channels||e.channels.control===!0}function Dt(e){return e==="running"||e==="completed"||e==="failed"||e==="cancelled"?e:"queued"}function _t(e){switch(e){case"process.list":return"Process list";case"system.health":return"System health";case"gpu.status":return"GPU status";case"disk.status":return"Disk status";case"service.status":return"Service status";case"diagnostics.collect":return"Diagnostics";case"process.control":return"Process control";case"service.control":return"Service control";case"application.launch":return"Launch application";case"application.close":return"Close application";case"file.read":return"Read file";case"file.write":return"Write file";case"file.delete":return"Delete file";case"file.list":return"List directory";case"command.run":return"Run command";case"script.run":return"Run script";case"software.install":return"Install software";case"network.status":return"Network status";case"system.power":return"Power action";case"system.configure":return"System configuration";case"logs.collect":return"Collect logs"}}const Mt=64*1024,Me="[Earlier log output hidden by PWA display limit.]",Qe=new TextEncoder,zt=new TextDecoder("utf-8",{fatal:!0}),ze=16*1024,Lt=200,Le="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Ft="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Bt="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|credential|password|secret|cookie)",qt=/\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:AKIA|ASIA)[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|npm_[A-Za-z0-9]{8,}|xox[a-z]-[A-Za-z0-9-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,})\b/g;function Ut(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Fe(e){return Qe.encode(e).byteLength}function Be(e,s){const n=Qe.encode(e);if(n.byteLength<=s)return{value:e,truncated:!1};let r=n.byteLength-s;for(;r<n.byteLength&&(n[r]&192)===128;)r+=1;let i=zt.decode(n.subarray(r));const f=i.indexOf(`
|
|
2
2
|
`);return f>=0&&f<i.length-1&&(i=i.slice(f+1)),{value:i,truncated:!0}}function et(e){return e.replace(/-----BEGIN [^-\r\n]*PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]*PRIVATE KEY-----/gi,"[REDACTED PRIVATE KEY]").replace(/\b(?:proxy-)?authorization\s*[:=]\s*[^\r\n]*|\b(?:set-)?cookie\s*[:=]\s*[^\r\n]*/gi,"credential-header=[REDACTED]").replace(/\bBearer\s+(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,"Bearer [REDACTED]").replace(new RegExp(`("${Le}"\\s*:\\s*")[^"\\r\\n]*(")`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`('${Le}'\\s*:\\s*')[^'\\r\\n]*(')`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`((?:[A-Za-z0-9.-]+[_-])?${Ft}\\s*[:=]\\s*)(?!\\[REDACTED\\])[^\\s,;&]+`,"gi"),"$1[REDACTED]").replace(new RegExp(`([?&]${Bt}=)[^&\\s]+`,"gi"),"$1[REDACTED]").replace(/\b(?:eyJ|[A-Za-z0-9_-]{8,}\.)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,"[REDACTED TOKEN]").replace(qt,"[REDACTED KEY]")}function Gt(e){return typeof e!="string"?"source unknown":et(e).replace(/[\u0000-\u001f\u007f]/g," ").trim().slice(0,64)||"source unknown"}function qe(e){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=0?Math.floor(e):null}function Vt(e){if(typeof e!="string"||e.length===0)return{output:"",displayTruncated:!1};const s=Be(e.replace(/\r\n?/g,`
|
|
3
3
|
`),Mt);let n=et(s.value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g," ").trim(),r=s.truncated;const i=n?n.split(`
|
|
4
4
|
`):[];if(i.length>Lt-1&&(n=i.slice(-199).join(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e}from"./pwa-startup-DvbrYEQi.js";import{r as i,R as x,E as j,D as u,f as g,h as f}from"./icons-CuXJn-Hz.js";import{l as b,c as d,d as y}from"./HubApp-
|
|
1
|
+
import{j as e}from"./pwa-startup-DvbrYEQi.js";import{r as i,R as x,E as j,D as u,f as g,h as f}from"./icons-CuXJn-Hz.js";import{l as b,c as d,d as y}from"./HubApp-oCD8fLqO.js";import"./pwa-bootstrap-ljOQTt2e.js";import"./preload-helper-DD1OZmLC.js";import"./MonitorStackIcon-4RlZK1aS.js";import"./supabase-C7qjtLN3.js";import"./frame-lifecycle-D91nchvm.js";function z({hubUrl:s,onToast:n}){const[o,m]=i.useState([]),[h,c]=i.useState(!0),r=i.useCallback(async()=>{c(!0);try{m((await b(s)).captures||[])}catch(t){n(t instanceof Error?t.message:String(t))}finally{c(!1)}},[s,n]);i.useEffect(()=>{r()},[r]);const p=async t=>{const a=d(t.id,s);if(navigator.share)try{await navigator.share({title:t.fileName,url:a});return}catch{}window.open(a,"_blank","noopener,noreferrer")};return e.jsxs("section",{className:"capture-gallery",children:[e.jsxs("header",{children:[e.jsxs("div",{children:[e.jsx("span",{className:"settings-eyebrow",children:"Capture Gallery"}),e.jsx("h2",{children:"Saved captures"})]}),e.jsxs("button",{className:"text-button",onClick:()=>{r()},type:"button",children:[e.jsx(x,{size:15})," Refresh"]})]}),h?e.jsx("p",{className:"settings-help",children:"Loading captures..."}):o.length===0?e.jsx("p",{className:"settings-help",children:"No captures saved yet."}):e.jsx("div",{className:"capture-gallery-grid",children:o.map(t=>{const a=d(t.id,s);return e.jsxs("article",{className:"capture-gallery-card",children:[t.mimeType.startsWith("image/")?e.jsx("img",{src:a,alt:t.target.label}):e.jsx("video",{src:a,controls:!0,preload:"metadata"}),e.jsxs("div",{className:"capture-gallery-card-body",children:[e.jsx("strong",{children:t.fileName}),e.jsxs("small",{children:[t.target.label," · ",new Date(t.createdAt).toLocaleString()," · ",Math.ceil(t.sizeBytes/1024)," KB"]}),e.jsxs("div",{children:[e.jsx("button",{className:"icon-button",title:"Open","aria-label":"Open",onClick:()=>window.open(a,"_blank","noopener,noreferrer"),type:"button",children:e.jsx(j,{size:15})}),e.jsx("a",{className:"icon-button",title:"Download","aria-label":"Download",href:`${a}?download=1`,download:t.fileName,children:e.jsx(u,{size:15})}),e.jsx("button",{className:"icon-button",title:"Share","aria-label":"Share",onClick:()=>{p(t)},type:"button",children:e.jsx(g,{size:15})}),e.jsx("button",{className:"icon-button danger",title:"Delete","aria-label":"Delete",onClick:()=>{window.confirm(`Delete ${t.fileName}?`)&&y(t.id,s).then(()=>r()).catch(l=>n(l instanceof Error?l.message:String(l)))},type:"button",children:e.jsx(f,{size:15})})]})]})]},t.id)})})]})}export{z as CaptureGallery};
|