@gkd-kit/inspect 0.0.1790160668392 → 0.0.1790527631913
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/dist/404.html +4 -4
- package/dist/assets/DevicePage-saBa_BwH.js +47 -0
- package/dist/assets/{DevicePage-DdDIL_wF.js.map → DevicePage-saBa_BwH.js.map} +1 -1
- package/dist/assets/HomePage-D-EKtxNC.js +7 -0
- package/dist/assets/HomePage-D-EKtxNC.js.map +1 -0
- package/dist/assets/Icon-BeujAbjV.js +172 -0
- package/dist/assets/Icon-BeujAbjV.js.map +1 -0
- package/dist/assets/LogPage-Bvep01pK.js +448 -0
- package/dist/assets/LogPage-Bvep01pK.js.map +1 -0
- package/dist/assets/Select-DG4uMgKj.js +515 -0
- package/dist/assets/Select-DG4uMgKj.js.map +1 -0
- package/dist/assets/SelectorLibraryDialog-C9J8G1jl.js +15 -0
- package/dist/assets/{SelectorLibraryDialog-CZzrCfvD.js.map → SelectorLibraryDialog-C9J8G1jl.js.map} +1 -1
- package/dist/assets/SelectorLibraryPage-BtPdT0Ul.css +1 -0
- package/dist/assets/SelectorLibraryPage-DdS3BHVA.js +7 -0
- package/dist/assets/SelectorLibraryPage-DdS3BHVA.js.map +1 -0
- package/dist/assets/{SettingsModal-Dft6fET-.js → SettingsModal-C4i6pb9I.js} +2 -2
- package/dist/assets/{SettingsModal-Dft6fET-.js.map → SettingsModal-C4i6pb9I.js.map} +1 -1
- package/dist/assets/SnapshotActionCard-D9SflO-E.js +2 -0
- package/dist/assets/{SnapshotActionCard-BOwtDKFo.js.map → SnapshotActionCard-D9SflO-E.js.map} +1 -1
- package/dist/assets/{SnapshotPage-BIlLhrcn.js → SnapshotPage-DTVxifNL.js} +4 -4
- package/dist/assets/{SnapshotPage-BIlLhrcn.js.map → SnapshotPage-DTVxifNL.js.map} +1 -1
- package/dist/assets/{Spin-CuHVDm4k.js → Spin-BVf_t8U-.js} +2 -2
- package/dist/assets/{Spin-CuHVDm4k.js.map → Spin-BVf_t8U-.js.map} +1 -1
- package/dist/assets/Tree-C7OLf_HB.js +163 -0
- package/dist/assets/{Tree-CVREMlDA.js.map → Tree-C7OLf_HB.js.map} +1 -1
- package/dist/assets/{bootstrap-CM5sQjzI.js → bootstrap-B4t3ePiM.js} +7 -7
- package/dist/assets/{bootstrap-CM5sQjzI.js.map → bootstrap-B4t3ePiM.js.map} +1 -1
- package/dist/assets/bootstrap-CB2lmq-e.css +2 -0
- package/dist/assets/chunk-CvjC-kdB.js +1 -1
- package/dist/assets/directives-rrsn-IHX.js +652 -0
- package/dist/assets/{directives-BwFfIXON.js.map → directives-rrsn-IHX.js.map} +1 -1
- package/dist/assets/index-BTkiXNBG.js +3 -0
- package/dist/assets/{index-CWtvMKq9.js.map → index-BTkiXNBG.js.map} +1 -1
- package/dist/assets/light-CDzV4g-8.js +2 -0
- package/dist/assets/{light-Cbk2lPQ4.js.map → light-CDzV4g-8.js.map} +1 -1
- package/dist/assets/{light-bufx0XpP.js → light-CyFi9Vk9.js} +2 -2
- package/dist/assets/light-CyFi9Vk9.js.map +1 -0
- package/dist/assets/light-Dq01-xj_.js +2 -0
- package/dist/assets/light-Dq01-xj_.js.map +1 -0
- package/dist/assets/{sql-wasm-browser-CF5WuyTk.js → sql-wasm-browser--LxQcKnR.js} +2 -2
- package/dist/assets/{sql-wasm-browser-CF5WuyTk.js.map → sql-wasm-browser--LxQcKnR.js.map} +1 -1
- package/dist/assets/store-CYZXdakH.js +2 -0
- package/dist/assets/store-CYZXdakH.js.map +1 -0
- package/dist/assets/{useBatchActions-BBWgyV6q.js → useBatchActions-DapF58OP.js} +2 -2
- package/dist/assets/{useBatchActions-BBWgyV6q.js.map → useBatchActions-DapF58OP.js.map} +1 -1
- package/dist/assets/useTheme-C49pY19-.js +2 -0
- package/dist/assets/{useTheme-A-hAYD1B.js.map → useTheme-C49pY19-.js.map} +1 -1
- package/dist/assets/{worker-entry-retrace.worker-Cu60Yuxe.js → worker-entry-retrace.worker-BS3SOdaF.js} +2 -2
- package/dist/assets/{worker-entry-retrace.worker-Cu60Yuxe.js.map → worker-entry-retrace.worker-BS3SOdaF.js.map} +1 -1
- package/dist/assets/{worker-entry-sqlite.worker-O_zial1j.js → worker-entry-sqlite.worker-BMkH7qJT.js} +2 -2
- package/dist/assets/{worker-entry-sqlite.worker-O_zial1j.js.map → worker-entry-sqlite.worker-BMkH7qJT.js.map} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/dist/assets/DevicePage-DdDIL_wF.js +0 -47
- package/dist/assets/HomePage-CqAZi53t.js +0 -7
- package/dist/assets/HomePage-CqAZi53t.js.map +0 -1
- package/dist/assets/Icon-BXreHcPu.js +0 -276
- package/dist/assets/Icon-BXreHcPu.js.map +0 -1
- package/dist/assets/LogPage-dnCz6dfL.js +0 -448
- package/dist/assets/LogPage-dnCz6dfL.js.map +0 -1
- package/dist/assets/Select-90siHmBu.js +0 -411
- package/dist/assets/Select-90siHmBu.js.map +0 -1
- package/dist/assets/SelectorLibraryDialog-CZzrCfvD.js +0 -15
- package/dist/assets/SelectorLibraryPage-BzXTN4cm.js +0 -7
- package/dist/assets/SelectorLibraryPage-BzXTN4cm.js.map +0 -1
- package/dist/assets/SelectorLibraryPage-DHtJivqo.css +0 -1
- package/dist/assets/SnapshotActionCard-BOwtDKFo.js +0 -2
- package/dist/assets/Tree-CVREMlDA.js +0 -163
- package/dist/assets/bootstrap-5ARAYXrx.css +0 -2
- package/dist/assets/directives-BwFfIXON.js +0 -652
- package/dist/assets/index-CWtvMKq9.js +0 -3
- package/dist/assets/light-Cbk2lPQ4.js +0 -2
- package/dist/assets/light-bufx0XpP.js.map +0 -1
- package/dist/assets/light-hXa8RC0f.js +0 -2
- package/dist/assets/light-hXa8RC0f.js.map +0 -1
- package/dist/assets/store-DwD72dps.js +0 -2
- package/dist/assets/store-DwD72dps.js.map +0 -1
- package/dist/assets/useTheme-A-hAYD1B.js +0 -2
package/dist/404.html
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="zh">
|
|
3
3
|
<head>
|
|
4
|
-
<script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.
|
|
4
|
+
<script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1790527631913/files/dist/assets/polyfills-C0QHzDpU.js"></script>
|
|
5
5
|
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<link
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
13
13
|
<meta name="referrer" content="no-referrer" />
|
|
14
14
|
<title>GKD</title>
|
|
15
|
-
<script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.
|
|
16
|
-
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.
|
|
17
|
-
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.
|
|
15
|
+
<script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1790527631913/files/dist/assets/index-BTkiXNBG.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1790527631913/files/dist/assets/preload-helper-xBbMyY7u.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1790527631913/files/dist/assets/globalError-2ZW3trxX.js">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
</body>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import{i as e}from"./chunk-62oNxeRG.js";import{It as t,h as n}from"./dist-XEDp5Bio.js";import{t as r}from"./Tooltip-D6FVnTfP.js";import{n as i,t as a}from"./Select-DG4uMgKj.js";import{Dt as o,F as s,J as c,M as l,at as u,d,dt as f,f as p,g as m,h,i as g,it as _,l as v,p as y,st as b,u as x,v as S}from"./runtime-core.esm-bundler-a08HMgVk.js";import{a as C}from"./light-B7F9SyNv.js";import{n as w,t as T}from"./GkSvg-FXttD_NY.js";import{i as E,n as ee}from"./directives-rrsn-IHX.js";import{r as D}from"./feedback-giCpIT9k.js";import{g as O,m as k,p as A,t as j}from"./SnapshotActionCard-D9SflO-E.js";import{o as M}from"./vue-router-CoqVkmtB.js";import{t as N}from"./check-DTBwMmUb.js";import{i as P,r as F}from"./chunk-CvjC-kdB.js";import{n as te}from"./async-Dypb0trs.js";import{i as I,n as L,t as R}from"./SelectorLibraryDialog-C9J8G1jl.js";import{i as z,t as B}from"./storage-BGTBd1nm.js";import{i as ne,n as re,t as ie}from"./useBatchActions-DapF58OP.js";import{t as ae}from"./PageBackButton-Dh-ryevs.js";var oe=e=>{let t=b(e),n=b(),r=new AbortController,i=()=>{r.abort(),r=new AbortController,t.value=void 0,n.value=void 0},a=async(e,n,i)=>{if(!t.value)throw Error(`origin must exist`);let a=await P(new URL(`/api/`+e,t.value),{...n,signal:r.signal},i).catch(t=>{throw t instanceof DOMException&&t.name==`AbortError`||D.error(`网络错误:`+e),t});if(!a.ok)throw D.error(`接口错误:`+e+`:`+a.status),a;if(a.headers.get(`Content-Type`)?.includes(`application/json`)){let e=await a.clone().json();if(e.__error)throw D.error(e.message),a}return a},o=async(e,t={},n)=>a(e,{method:`post`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)},n),s=async(e,t={})=>o(e,t).then(e=>e.json()),c=async(e,t={})=>o(e,t,e=>(e.responseType=`arraybuffer`,e)).then(e=>e.arrayBuffer());return{origin:t,api:{getServerInfo:async()=>s(`getServerInfo`),getSnapshot:async e=>n.value?.gkdAppInfo?.versionName===`1.10.4`?a(`snapshot?id=`+e.id).then(e=>e.json()):s(`getSnapshot`,e),getScreenshot:async e=>c(`getScreenshot`,e),captureSnapshot:async()=>s(`captureSnapshot`),getSnapshots:async()=>s(`getSnapshots`),updateSubscription:async e=>s(`updateSubscription`,{...e,id:-1,name:`内存订阅`,version:0}),execSelector:async e=>(e=structuredClone(e),e.action||=void 0,s(`execSelector`,e)),deleteSnapshot:async e=>s(`deleteSnapshot`,e)},serverInfo:n,disconnect:i}},V=e(L(),1),H={class:`mt-8px flex justify-end`},U={flex:``,"gap-10px":``,"flex-items-center":``},se=S({__name:`DeviceActionDialogs`,props:{api:{},selectorShow:{type:Boolean},subscriptionShow:{type:Boolean}},emits:[`update:selectorShow`,`update:subscriptionShow`],setup(e,{emit:t}){let r=e,o=t,l=b(``),d=u({selector:``,action:`click`,quickFind:!1}),p=e=>{l.value=e},_=e=>{d.selector=e},v=e=>{d.quickFind=e},S=e=>{d.action=e},T=b(!1),E=()=>{T.value=!0},ee=e=>{T.value=e},A=e=>{_(e)},j=[{label:`仅查询`,value:``},{value:`click`,label:`click`},{value:`clickNode`,label:`clickNode`},{value:`clickCenter`,label:`clickCenter`},{value:`back`,label:`back`},{value:`longClick`,label:`longClick`},{value:`longClickNode`,label:`longClickNode`},{value:`longClickCenter`,label:`longClickCenter`}],M=k(async()=>{let e=I(()=>V.default.parse(l.value.trim()));if(e){if(e.categories||e.globalGroups||e.apps)await r.api.updateSubscription(e);else if(typeof e.id==`string`)await r.api.updateSubscription({apps:[e]});else if(Array.isArray(e)&&typeof e[0].id==`string`)await r.api.updateSubscription({apps:e});else if(typeof e.key==`number`)await r.api.updateSubscription({globalGroups:[e]});else if(Array.isArray(e)&&typeof e[0].key==`number`)await r.api.updateSubscription({globalGroups:e});else{D.error(`无法识别的订阅文本`);return}D.success(`修改成功`)}}),N=k(async()=>{let e=await r.api.execSelector({...d,fastQuery:d.quickFind});if(e.message){D.success(`操作成功:`+e.message);return}e.action?D.success((e.result?`操作成功:`:`操作失败`)+e.action):e.result&&D.success(`查询成功`)});return(t,r)=>{let u=w,b=C,D=n,k=i,P=O,F=a;return s(),y(g,null,[m(b,{show:e.subscriptionShow,preset:`dialog`,style:{width:`800px`},title:`修改内存订阅`,maskClosable:!1,positiveText:`确认`,positiveButtonProps:{loading:f(M).loading,onClick:f(M).invoke},"onUpdate:show":r[0]||=e=>o(`update:subscriptionShow`,e)},{default:c(()=>[m(u,{value:f(l),disabled:f(M).loading,type:`textarea`,class:`gkd_code`,autosize:{minRows:20,maxRows:25},placeholder:f(`请输入订阅文本(JSON5语法):
|
|
2
|
+
示例1-更新单个应用的规则:
|
|
3
|
+
{
|
|
4
|
+
id: 'appId',
|
|
5
|
+
groups: []
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
示例2-更新多个应用的规则:
|
|
9
|
+
[
|
|
10
|
+
{
|
|
11
|
+
id: 'appId1',
|
|
12
|
+
groups: []
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'appId2',
|
|
16
|
+
groups: []
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
示例3-更新全局规则(1.7.0):
|
|
21
|
+
{
|
|
22
|
+
name: '全局规则-1',
|
|
23
|
+
key: 0,
|
|
24
|
+
rules: []
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
示例3-更新多个全局规则(1.7.0):
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
name: '全局规则-1',
|
|
31
|
+
key: 0,
|
|
32
|
+
rules: []
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: '全局规则-2',
|
|
36
|
+
key: 1,
|
|
37
|
+
rules: []
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
示例4-更新整个订阅(1.7.0):
|
|
42
|
+
{
|
|
43
|
+
apps: [],
|
|
44
|
+
globalGroups: [],
|
|
45
|
+
categories: [],
|
|
46
|
+
}`),"onUpdate:value":p},null,8,[`value`,`disabled`,`placeholder`])]),_:1},8,[`show`,`positiveButtonProps`]),m(b,{show:e.selectorShow,preset:`dialog`,style:{width:`800px`},title:`执行选择器`,maskClosable:!1,positiveText:`确认`,positiveButtonProps:{loading:f(N).loading,onClick:f(N).invoke},"onUpdate:show":r[1]||=e=>o(`update:selectorShow`,e)},{default:c(()=>[m(u,{value:f(d).selector,disabled:f(N).loading,type:`textarea`,class:`gkd_code`,autosize:{minRows:4,maxRows:10},placeholder:`请输入合法的选择器`,"onUpdate:value":_},null,8,[`value`,`disabled`]),x(`div`,H,[m(D,{secondary:``,onClick:E},{default:c(()=>[...r[2]||=[h(`选择器库`,-1)]]),_:1})]),r[6]||=x(`div`,{"h-15px":``},null,-1),m(P,null,{default:c(()=>[m(k,{checked:f(d).quickFind,"onUpdate:checked":v},{default:c(()=>[...r[3]||=[h(` 快速查找 `,-1)]]),_:1},8,[`checked`]),r[4]||=x(`a`,{href:`https://gkd.li/api/interfaces/RawCommonProps.html#quickfind`,target:`_blank`,rel:`noopener noreferrer`},` 查找说明 `,-1)]),_:1}),r[7]||=x(`div`,{"h-10px":``},null,-1),x(`div`,U,[m(F,{value:f(d).action,options:j,class:`w-150px`,"onUpdate:value":S},null,8,[`value`]),r[5]||=x(`a`,{href:`https://gkd.li/api/interfaces/RawRuleProps#action`,target:`_blank`,rel:`noopener noreferrer`},` 操作说明 `,-1)])]),_:1},8,[`show`,`positiveButtonProps`]),m(R,{show:f(T),onUse:A,"onUpdate:show":ee},null,8,[`show`])],64)}}}),ce={"page-size":``,flex:``,"flex-col":``,"p-10px":``,"gap-10px":``},le={flex:``,"items-center":``,"gap-24px":``},ue={key:1,gkd_code:``,"pl-16px":``,"whitespace-nowrap":``,flex:``,"items-center":``},W=S({__name:`DeviceControlView`,setup(e){let i=M(),{api:a,origin:S,serverInfo:C,disconnect:O}=oe(),P=b(localStorage.getItem(`device_link`)||``),L=b([]),R=0,V=e=>{P.value=e},H=async(e=R)=>{let t=await a.getSnapshots();e==R&&(t.sort((e,t)=>t.id-e.id),L.value=t)},U=k(async()=>{if(!P.value)return;let e=++R;S.value=I(()=>new URL(P.value.trim()),()=>`非法设备地址`).origin,P.value=S.value,localStorage.setItem(`device_link`,P.value);let t=await a.getServerInfo();e==R&&(C.value=t,document.title=W.value,await H(e))}),W=v(()=>{if(!C.value)return`未连接设备`;let e=C.value.device,t=C.value.gkdAppInfo;return`${e.manufacturer} Android${e.release} - GKD${t.versionName}`});l(async()=>{await te(500),N(P.value)&&U.invoke()});let G=k(async()=>{let e=await a.captureSnapshot(),t=await a.getScreenshot({id:e.id});await z.setItem(e.id,e),await B.setItem(e.id,t),D.success(`捕获并保存快照成功`),await H()}),K=k(async()=>{let e=(await a.getSnapshots()).map(e=>e.id),t=new Set((await B.keys()).map(e=>parseInt(e))),n=e.filter(e=>!t.has(e));if(n.length==0){D.success(`没有新记录可导入`);return}let r=0,i=F(3);await Promise.all(n.map(e=>i(async()=>{let[t,n]=await Promise.all([a.getSnapshot({id:e}),a.getScreenshot({id:e})]);t.nodes&&(await Promise.all([z.setItem(e,t),B.setItem(e,n)]),r++)}))),D.success(`导入${r}条新记录`)}),{activityIdCol:de,appIdCol:fe,appNameCol:pe,ctimeCol:q,appVersionCodeCol:me,appVersionNameCol:he,resetColWidth:J}=ne(),ge=e=>{e.columnKey==q.key&&(q.sortOrder=e.order)},Y=A(async e=>{if(!await z.hasItem(e.id)){let t=await a.getSnapshot({id:e.id});await z.setItem(e.id,t)}if(!await B.hasItem(e.id)){let t=await a.getScreenshot({id:e.id});await B.setItem(e.id,t)}window.open(i.resolve({name:`snapshot`,params:{snapshotId:e.id}}).href)},e=>e.id),_e=[{type:`selection`},q,pe,fe,me,he,de,{key:`actions`,title:`操作`,fixed:`right`,width:`160px`,render(e){return m(j,{snapshot:e,showExport:!1,showShare:!1,deleteConfirmText:`是否确认删除? 此操作不可恢复!\n快照ID:${e.id}`,onPreview:()=>Y.invoke(e),previewLoading:Y.loading[e.id],onBeforeDelete:async()=>await a.deleteSnapshot({id:e.id}),onDelete:()=>{L.value=L.value.filter(t=>t.id!==e.id)}},null)}}],X=_([]),{batchDelete:ve}=ie(X,{beforeDeleteItem:async e=>await a.deleteSnapshot({id:e}),onAfterDelete:async()=>{await H()}}),Z=u({page:1,pageSize:50,showSizePicker:!0,pageSizes:[50,100],onChange:e=>{Z.page=e,J()},onUpdatePageSize:e=>{Z.pageSize=e,Z.page=1,J()}}),ye=b(!1),be=b(!1),Q=e=>{ye.value=e},$=e=>{be.value=e},xe=e=>{X.value=e.filter(e=>typeof e==`number`)},Se=()=>{R++,O(),L.value=[],X.value=[],Q(!1),$(!1),document.title=`连接设备`};return(e,i)=>{let l=n,u=r,_=w,v=E,b=ee;return s(),y(g,null,[(s(),d(se,{key:f(S)?.toString(),api:f(a),subscriptionShow:f(ye),selectorShow:f(be),"onUpdate:subscriptionShow":Q,"onUpdate:selectorShow":$},null,8,[`api`,`subscriptionShow`,`selectorShow`])),x(`div`,ce,[x(`div`,le,[m(ae,{class:`ml-12px`}),m(v,null,{default:c(()=>[m(_,{value:f(P),placeholder:`请输入设备地址`,class:`gkd_code`,style:{width:`240px`},onKeyup:t(f(U).invoke,[`enter`]),"onUpdate:value":V},{suffix:c(()=>[m(u,null,{trigger:c(()=>[m(l,{text:``,style:{"--n-icon-size":`20px`},loading:f(U).loading,onClick:f(U).invoke},{icon:c(()=>[m(T,{name:`refresh`})]),_:1},8,[`loading`,`onClick`])]),default:c(()=>[i[2]||=h(` 刷新连接 `,-1)]),_:1})]),_:1},8,[`value`,`onKeyup`]),f(U).loading?(s(),d(l,{key:0,secondary:``,type:`error`,onClick:Se},{default:c(()=>[...i[3]||=[h(` 停止 `,-1)]]),_:1})):p(``,!0),f(C)?(s(),y(`div`,ue,o(f(W)),1)):p(``,!0)]),_:1}),f(C)?(s(),y(g,{key:0},[m(re,{checkedCount:f(X).length,batchDelete:f(ve)},null,8,[`checkedCount`,`batchDelete`]),m(u,null,{trigger:c(()=>[m(l,{text:``,style:{"--n-icon-size":`24px`},loading:f(G).loading,onClick:f(G).invoke},{icon:c(()=>[m(T,{name:`Snapshot`})]),_:1},8,[`loading`,`onClick`])]),default:c(()=>[i[4]||=h(` 捕获快照 `,-1)]),_:1}),m(u,null,{trigger:c(()=>[m(l,{text:``,style:{"--n-icon-size":`24px`},loading:f(K).loading,onClick:f(K).invoke},{icon:c(()=>[m(T,{name:`Down-all`})]),_:1},8,[`loading`,`onClick`])]),default:c(()=>[i[5]||=h(` 下载所有快照 `,-1)]),_:1}),m(u,null,{trigger:c(()=>[m(l,{text:``,style:{"--n-icon-size":`24px`},onClick:i[0]||=e=>Q(!0)},{icon:c(()=>[m(T,{name:`CacheSub`})]),_:1})]),default:c(()=>[i[6]||=h(` 修改内存订阅 `,-1)]),_:1}),m(u,null,{trigger:c(()=>[m(l,{text:``,style:{"--n-icon-size":`24px`},onClick:i[1]||=e=>$(!0)},{icon:c(()=>[m(T,{name:`Exe-Sel`})]),_:1})]),default:c(()=>[i[7]||=h(` 执行选择器 `,-1)]),_:1})],64)):p(``,!0)]),m(b,{checkedRowKeys:f(X),striped:``,"flex-height":``,data:f(L),columns:_e,pagination:f(Z),rowKey:e=>e.id,size:`small`,class:`flex-1`,scrollX:1200,"onUpdate:checkedRowKeys":xe,"onUpdate:sorter":ge},null,8,[`checkedRowKeys`,`data`,`pagination`,`rowKey`])])],64)}}}),G=S({__name:`DevicePage`,setup(e){return(e,t)=>(s(),d(W))}});export{G as default};
|
|
47
|
+
//# sourceMappingURL=DevicePage-saBa_BwH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevicePage-DdDIL_wF.js","names":[],"sources":["../../src/features/device-control/api.ts","../../src/features/device-control/DeviceActionDialogs.vue","../../src/features/device-control/DeviceActionDialogs.vue","../../src/features/device-control/DeviceControlView.vue","../../src/features/device-control/DeviceControlView.vue","../../src/pages/device/DevicePage.vue","../../src/pages/device/DevicePage.vue"],"sourcesContent":["import type { Snapshot } from '@/entities/snapshot/types';\nimport type { RpcError, ServerInfo } from '@/features/device-control/types';\nimport { message } from '@/shared/services/feedback';\nimport { enhanceFetch } from '@/features/network-access/enhanceFetch';\nimport type { GmXhrOptions } from '@/shared/api/gm';\n\ninterface Reqid {\n id: number;\n}\n\nexport const useDeviceApi = (initOrigin?: string) => {\n const origin = shallowRef(initOrigin);\n const serverInfo = shallowRef<ServerInfo>();\n let requestController = new AbortController();\n const disconnect = () => {\n requestController.abort();\n requestController = new AbortController();\n origin.value = undefined;\n serverInfo.value = undefined;\n };\n const request = async (\n name: string,\n init?: RequestInit,\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n if (!origin.value) {\n throw new Error(`origin must exist`);\n }\n const u = new URL(`/api/` + name, origin.value);\n const response = await enhanceFetch(\n u,\n { ...init, signal: requestController.signal },\n xhrDetails,\n ).catch((e) => {\n if (!(e instanceof DOMException && e.name == 'AbortError')) {\n message.error(`网络错误:` + name);\n }\n throw e;\n });\n if (!response.ok) {\n message.error(`接口错误:` + name + `:` + response.status);\n throw response;\n }\n if (response.headers.get(`Content-Type`)?.includes(`application/json`)) {\n const error = (await response.clone().json()) as RpcError;\n if (error.__error) {\n message.error(error.message);\n throw response;\n }\n }\n return response;\n };\n const baseRpc = async (\n name: string,\n data: object = {},\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n return request(\n name,\n {\n method: 'post',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n },\n xhrDetails,\n );\n };\n const jsonRpc = async <T>(name: string, data: object = {}): Promise<T> => {\n return baseRpc(name, data).then((r) => r.json());\n };\n const bfRpc = async (\n name: string,\n data: object = {},\n ): Promise<ArrayBuffer> => {\n return baseRpc(name, data, (v) => {\n // must set responseType to get binary data\n v.responseType = 'arraybuffer';\n return v;\n }).then((r) => r.arrayBuffer());\n };\n const api = {\n getServerInfo: async () => jsonRpc<ServerInfo>(`getServerInfo`),\n getSnapshot: async (data: Reqid): Promise<Snapshot> => {\n if (serverInfo.value?.gkdAppInfo?.versionName === '1.10.4') {\n // 兼容旧版本 BUG\n // https://github.com/gkd-kit/gkd/blob/v1.10.4/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt#L198\n return request('snapshot?id=' + data.id).then((r) => r.json());\n }\n return jsonRpc(`getSnapshot`, data);\n },\n getScreenshot: async (data: Reqid) => {\n return bfRpc(`getScreenshot`, data);\n },\n captureSnapshot: async () => jsonRpc<Snapshot>(`captureSnapshot`),\n getSnapshots: async () => jsonRpc<Snapshot[]>(`getSnapshots`),\n updateSubscription: async (data: any) => {\n return jsonRpc(`updateSubscription`, {\n ...data,\n id: -1,\n name: '内存订阅',\n version: 0,\n });\n },\n execSelector: async (data: {\n selector: string;\n action?: string;\n fastQuery?: boolean;\n }) => {\n data = structuredClone(data);\n data.action ||= undefined;\n return jsonRpc<{ message: string; action: string; result: boolean }>(\n `execSelector`,\n data,\n );\n },\n deleteSnapshot: async (data: Reqid) => {\n return jsonRpc<{ message: string }>(`deleteSnapshot`, data);\n },\n };\n return { origin, api, serverInfo, disconnect };\n};\n\nexport type DeviceApi = ReturnType<typeof useDeviceApi>['api'];\n","<script setup lang=\"ts\">\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useTask } from '@/shared/lib/task';\nimport JSON5 from 'json5';\nimport type { DeviceApi } from './api';\nimport SelectorLibraryDialog from '@/features/selector-library/ui/SelectorLibraryDialog.vue';\n\nconst props = defineProps<{\n api: DeviceApi;\n selectorShow: boolean;\n subscriptionShow: boolean;\n}>();\n\nconst emit = defineEmits<{\n 'update:selectorShow': [show: boolean];\n 'update:subscriptionShow': [show: boolean];\n}>();\n\nconst subscriptionText = shallowRef(``);\nconst clickAction = shallowReactive({\n selector: ``,\n action: 'click',\n quickFind: false,\n});\nconst updateSubscriptionText = (value: string) => {\n subscriptionText.value = value;\n};\nconst updateSelector = (value: string) => {\n clickAction.selector = value;\n};\nconst updateQuickFind = (value: boolean) => {\n clickAction.quickFind = value;\n};\nconst updateAction = (value: string) => {\n clickAction.action = value;\n};\nconst selectorLibraryShow = shallowRef(false);\nconst openSelectorLibrary = () => {\n selectorLibraryShow.value = true;\n};\nconst setSelectorLibraryVisible = (show: boolean) => {\n selectorLibraryShow.value = show;\n};\nconst useLibrarySelector = (selector: string) => {\n updateSelector(selector);\n};\n\nconst actionOptions: { value?: string; label: string }[] = [\n { label: '仅查询', value: `` },\n { value: 'click', label: 'click' },\n { value: 'clickNode', label: 'clickNode' },\n { value: 'clickCenter', label: 'clickCenter' },\n { value: 'back', label: 'back' },\n { value: 'longClick', label: 'longClick' },\n { value: 'longClickNode', label: 'longClickNode' },\n { value: 'longClickCenter', label: 'longClickCenter' },\n];\n\nconst updateSubscription = useTask(async () => {\n const data = errorWrap(() => JSON5.parse(subscriptionText.value.trim()));\n if (!data) return;\n if (data.categories || data.globalGroups || data.apps) {\n await props.api.updateSubscription(data);\n } else if (typeof data.id == 'string') {\n await props.api.updateSubscription({ apps: [data] });\n } else if (Array.isArray(data) && typeof data[0].id == 'string') {\n await props.api.updateSubscription({ apps: data });\n } else if (typeof data.key == 'number') {\n await props.api.updateSubscription({ globalGroups: [data] });\n } else if (Array.isArray(data) && typeof data[0].key == 'number') {\n await props.api.updateSubscription({ globalGroups: data });\n } else {\n message.error(`无法识别的订阅文本`);\n return;\n }\n message.success(`修改成功`);\n});\n\nconst executeSelector = useTask(async () => {\n const result = await props.api.execSelector({\n ...clickAction,\n fastQuery: clickAction.quickFind,\n });\n if (result.message) {\n message.success(`操作成功:` + result.message);\n return;\n }\n if (result.action) {\n message.success((result.result ? `操作成功:` : `操作失败`) + result.action);\n } else if (result.result) {\n message.success(`查询成功`);\n }\n});\n\nconst subscriptionPlaceholder = `\n请输入订阅文本(JSON5语法):\n示例1-更新单个应用的规则:\n{\n id: 'appId',\n groups: []\n}\n\n示例2-更新多个应用的规则:\n[\n {\n id: 'appId1',\n groups: []\n },\n {\n id: 'appId2',\n groups: []\n }\n]\n\n示例3-更新全局规则(1.7.0):\n{\n name: '全局规则-1',\n key: 0,\n rules: []\n}\n\n示例3-更新多个全局规则(1.7.0):\n[\n {\n name: '全局规则-1',\n key: 0,\n rules: []\n },\n {\n name: '全局规则-2',\n key: 1,\n rules: []\n }\n]\n\n示例4-更新整个订阅(1.7.0):\n{\n apps: [],\n globalGroups: [],\n categories: [],\n}\n`.trim();\n</script>\n\n<template>\n <NModal\n :show=\"subscriptionShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"修改内存订阅\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: updateSubscription.loading,\n onClick: updateSubscription.invoke,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 20, maxRows: 25 }\"\n :placeholder=\"subscriptionPlaceholder\"\n @update:value=\"updateSubscriptionText\"\n />\n </NModal>\n\n <NModal\n :show=\"selectorShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"执行选择器\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: executeSelector.loading,\n onClick: executeSelector.invoke,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 4, maxRows: 10 }\"\n placeholder=\"请输入合法的选择器\"\n @update:value=\"updateSelector\"\n />\n <div class=\"mt-8px flex justify-end\">\n <NButton secondary @click=\"openSelectorLibrary\">选择器库</NButton>\n </div>\n <div h-15px />\n <NSpace>\n <NCheckbox\n :checked=\"clickAction.quickFind\"\n @update:checked=\"updateQuickFind\"\n >\n 快速查找\n </NCheckbox>\n <a\n href=\"https://gkd.li/api/interfaces/RawCommonProps.html#quickfind\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 查找说明\n </a>\n </NSpace>\n <div h-10px />\n <div flex gap-10px flex-items-center>\n <NSelect\n :value=\"clickAction.action\"\n :options=\"actionOptions\"\n class=\"w-150px\"\n @update:value=\"updateAction\"\n />\n <a\n href=\"https://gkd.li/api/interfaces/RawRuleProps#action\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 操作说明\n </a>\n </div>\n </NModal>\n <SelectorLibraryDialog\n :show=\"selectorLibraryShow\"\n @use=\"useLibrarySelector\"\n @update:show=\"setSelectorLibraryVisible\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useTask } from '@/shared/lib/task';\nimport JSON5 from 'json5';\nimport type { DeviceApi } from './api';\nimport SelectorLibraryDialog from '@/features/selector-library/ui/SelectorLibraryDialog.vue';\n\nconst props = defineProps<{\n api: DeviceApi;\n selectorShow: boolean;\n subscriptionShow: boolean;\n}>();\n\nconst emit = defineEmits<{\n 'update:selectorShow': [show: boolean];\n 'update:subscriptionShow': [show: boolean];\n}>();\n\nconst subscriptionText = shallowRef(``);\nconst clickAction = shallowReactive({\n selector: ``,\n action: 'click',\n quickFind: false,\n});\nconst updateSubscriptionText = (value: string) => {\n subscriptionText.value = value;\n};\nconst updateSelector = (value: string) => {\n clickAction.selector = value;\n};\nconst updateQuickFind = (value: boolean) => {\n clickAction.quickFind = value;\n};\nconst updateAction = (value: string) => {\n clickAction.action = value;\n};\nconst selectorLibraryShow = shallowRef(false);\nconst openSelectorLibrary = () => {\n selectorLibraryShow.value = true;\n};\nconst setSelectorLibraryVisible = (show: boolean) => {\n selectorLibraryShow.value = show;\n};\nconst useLibrarySelector = (selector: string) => {\n updateSelector(selector);\n};\n\nconst actionOptions: { value?: string; label: string }[] = [\n { label: '仅查询', value: `` },\n { value: 'click', label: 'click' },\n { value: 'clickNode', label: 'clickNode' },\n { value: 'clickCenter', label: 'clickCenter' },\n { value: 'back', label: 'back' },\n { value: 'longClick', label: 'longClick' },\n { value: 'longClickNode', label: 'longClickNode' },\n { value: 'longClickCenter', label: 'longClickCenter' },\n];\n\nconst updateSubscription = useTask(async () => {\n const data = errorWrap(() => JSON5.parse(subscriptionText.value.trim()));\n if (!data) return;\n if (data.categories || data.globalGroups || data.apps) {\n await props.api.updateSubscription(data);\n } else if (typeof data.id == 'string') {\n await props.api.updateSubscription({ apps: [data] });\n } else if (Array.isArray(data) && typeof data[0].id == 'string') {\n await props.api.updateSubscription({ apps: data });\n } else if (typeof data.key == 'number') {\n await props.api.updateSubscription({ globalGroups: [data] });\n } else if (Array.isArray(data) && typeof data[0].key == 'number') {\n await props.api.updateSubscription({ globalGroups: data });\n } else {\n message.error(`无法识别的订阅文本`);\n return;\n }\n message.success(`修改成功`);\n});\n\nconst executeSelector = useTask(async () => {\n const result = await props.api.execSelector({\n ...clickAction,\n fastQuery: clickAction.quickFind,\n });\n if (result.message) {\n message.success(`操作成功:` + result.message);\n return;\n }\n if (result.action) {\n message.success((result.result ? `操作成功:` : `操作失败`) + result.action);\n } else if (result.result) {\n message.success(`查询成功`);\n }\n});\n\nconst subscriptionPlaceholder = `\n请输入订阅文本(JSON5语法):\n示例1-更新单个应用的规则:\n{\n id: 'appId',\n groups: []\n}\n\n示例2-更新多个应用的规则:\n[\n {\n id: 'appId1',\n groups: []\n },\n {\n id: 'appId2',\n groups: []\n }\n]\n\n示例3-更新全局规则(1.7.0):\n{\n name: '全局规则-1',\n key: 0,\n rules: []\n}\n\n示例3-更新多个全局规则(1.7.0):\n[\n {\n name: '全局规则-1',\n key: 0,\n rules: []\n },\n {\n name: '全局规则-2',\n key: 1,\n rules: []\n }\n]\n\n示例4-更新整个订阅(1.7.0):\n{\n apps: [],\n globalGroups: [],\n categories: [],\n}\n`.trim();\n</script>\n\n<template>\n <NModal\n :show=\"subscriptionShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"修改内存订阅\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: updateSubscription.loading,\n onClick: updateSubscription.invoke,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 20, maxRows: 25 }\"\n :placeholder=\"subscriptionPlaceholder\"\n @update:value=\"updateSubscriptionText\"\n />\n </NModal>\n\n <NModal\n :show=\"selectorShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"执行选择器\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: executeSelector.loading,\n onClick: executeSelector.invoke,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 4, maxRows: 10 }\"\n placeholder=\"请输入合法的选择器\"\n @update:value=\"updateSelector\"\n />\n <div class=\"mt-8px flex justify-end\">\n <NButton secondary @click=\"openSelectorLibrary\">选择器库</NButton>\n </div>\n <div h-15px />\n <NSpace>\n <NCheckbox\n :checked=\"clickAction.quickFind\"\n @update:checked=\"updateQuickFind\"\n >\n 快速查找\n </NCheckbox>\n <a\n href=\"https://gkd.li/api/interfaces/RawCommonProps.html#quickfind\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 查找说明\n </a>\n </NSpace>\n <div h-10px />\n <div flex gap-10px flex-items-center>\n <NSelect\n :value=\"clickAction.action\"\n :options=\"actionOptions\"\n class=\"w-150px\"\n @update:value=\"updateAction\"\n />\n <a\n href=\"https://gkd.li/api/interfaces/RawRuleProps#action\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 操作说明\n </a>\n </div>\n </NModal>\n <SelectorLibraryDialog\n :show=\"selectorLibraryShow\"\n @use=\"useLibrarySelector\"\n @update:show=\"setSelectorLibraryVisible\"\n />\n</template>\n","<script setup lang=\"tsx\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { delay } from '@/shared/lib/async';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useBatchTask, useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { RowKey, SortState } from 'naive-ui/es/data-table/src/interface';\nimport pLimit from 'p-limit';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport DeviceActionDialogs from './DeviceActionDialogs.vue';\nimport PageBackButton from '@/features/navigation/PageBackButton.vue';\n\nconst router = useRouter();\nconst { api, origin, serverInfo, disconnect } = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nlet connectionVersion = 0;\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst loadSnapshots = async (version = connectionVersion) => {\n const result = await api.getSnapshots();\n if (version != connectionVersion) return;\n result.sort((a, b) => b.id - a.id);\n snapshots.value = result;\n};\nconst connect = useTask(async () => {\n if (!link.value) return;\n const version = ++connectionVersion;\n origin.value = errorWrap(\n () => new URL(link.value.trim()),\n () => `非法设备地址`,\n ).origin;\n link.value = origin.value;\n localStorage.setItem(`device_link`, link.value);\n const result = await api.getServerInfo();\n if (version != connectionVersion) return;\n serverInfo.value = result;\n document.title = serverTitle.value;\n await loadSnapshots(version);\n});\n\nconst serverTitle = computed(() => {\n if (!serverInfo.value) return '未连接设备';\n const d = serverInfo.value.device;\n const g = serverInfo.value.gkdAppInfo;\n return `${d.manufacturer} Android${d.release} - GKD${g.versionName}`;\n});\n\nonMounted(async () => {\n await delay(500);\n if (toValidURL(link.value)) {\n connect.invoke();\n }\n});\n\nconst captureSnapshot = useTask(async () => {\n const snapshot = await api.captureSnapshot();\n const screenshot = await api.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n await loadSnapshots();\n});\nconst downloadAllSnapshot = useTask(async () => {\n const snapshotIds = (await api.getSnapshots()).map((s) => s.id);\n const existSnapshotIds = new Set(\n (await screenshotStorage.keys()).map((s) => parseInt(s)),\n );\n const unimportSnapshotIds = snapshotIds.filter(\n (k) => !existSnapshotIds.has(k),\n );\n if (unimportSnapshotIds.length == 0) {\n message.success(`没有新记录可导入`);\n return;\n }\n let r = 0;\n const limit = pLimit(3);\n await Promise.all(\n unimportSnapshotIds.map((snapshotId) =>\n limit(async () => {\n const [newSnapshot, newScreenshot] = await Promise.all([\n api.getSnapshot({ id: snapshotId }),\n api.getScreenshot({ id: snapshotId }),\n ] as const);\n if (!newSnapshot.nodes) return;\n await Promise.all([\n snapshotStorage.setItem(snapshotId, newSnapshot),\n screenshotStorage.setItem(snapshotId, newScreenshot),\n ]);\n r++;\n }),\n ),\n );\n message.success(`导入${r}条新记录`);\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\nconst handleSorterChange = (sorter: SortState) => {\n if (sorter.columnKey == ctimeCol.key) {\n ctimeCol.sortOrder = sorter.order;\n }\n};\nconst previewSnapshot = useBatchTask(\n async (row: Snapshot) => {\n if (!(await snapshotStorage.hasItem(row.id))) {\n const obj = await api.getSnapshot({ id: row.id });\n await snapshotStorage.setItem(row.id, obj);\n }\n if (!(await screenshotStorage.hasItem(row.id))) {\n const bf = await api.getScreenshot({ id: row.id });\n await screenshotStorage.setItem(row.id, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: row.id },\n }).href,\n );\n },\n (r) => r.id,\n);\n\nconst columns: DataTableColumns<Snapshot> = [\n {\n type: 'selection',\n },\n ctimeCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return (\n <SnapshotActionCard\n snapshot={row}\n showExport={false}\n showShare={false}\n deleteConfirmText={`是否确认删除? 此操作不可恢复!\\n快照ID:${row.id}`}\n onPreview={() => previewSnapshot.invoke(row)}\n previewLoading={previewSnapshot.loading[row.id]}\n onBeforeDelete={async () => await api.deleteSnapshot({ id: row.id })}\n onDelete={() => {\n snapshots.value = snapshots.value.filter((s) => s.id !== row.id);\n }}\n />\n );\n },\n },\n];\n\nconst checkedRowKeys = ref<number[]>([]);\nconst { batchDelete } = useBatchActions(checkedRowKeys, {\n beforeDeleteItem: async (id) => await api.deleteSnapshot({ id }),\n onAfterDelete: async () => {\n await loadSnapshots();\n },\n});\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst showSubsModel = shallowRef(false);\nconst showSelectorModel = shallowRef(false);\nconst setSubscriptionDialogVisible = (show: boolean) => {\n showSubsModel.value = show;\n};\nconst setSelectorDialogVisible = (show: boolean) => {\n showSelectorModel.value = show;\n};\nconst updateCheckedRowKeys = (keys: RowKey[]) => {\n checkedRowKeys.value = keys.filter(\n (key): key is number => typeof key == 'number',\n );\n};\nconst stopConnection = () => {\n connectionVersion++;\n disconnect();\n snapshots.value = [];\n checkedRowKeys.value = [];\n setSubscriptionDialogVisible(false);\n setSelectorDialogVisible(false);\n document.title = '连接设备';\n};\n</script>\n<template>\n <DeviceActionDialogs\n :key=\"origin?.toString()\"\n :api=\"api\"\n :subscriptionShow=\"showSubsModel\"\n :selectorShow=\"showSelectorModel\"\n @update:subscriptionShow=\"setSubscriptionDialogVisible\"\n @update:selectorShow=\"setSelectorDialogVisible\"\n />\n <div page-size flex flex-col p-10px gap-10px>\n <div flex items-center gap-24px>\n <PageBackButton class=\"ml-12px\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"gkd_code\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connect.invoke\"\n @update:value=\"updateLink\"\n >\n <template #suffix>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 20px\"\n :loading=\"connect.loading\"\n @click=\"connect.invoke\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connect.loading\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n gkd_code\n pl-16px\n whitespace-nowrap\n flex\n items-center\n >\n {{ serverTitle }}\n </div>\n </NInputGroup>\n <template v-if=\"serverInfo\">\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n />\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"captureSnapshot.loading\"\n @click=\"captureSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Snapshot\" /></template>\n </NButton>\n </template>\n 捕获快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"downloadAllSnapshot.loading\"\n @click=\"downloadAllSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Down-all\" /></template>\n </NButton>\n </template>\n 下载所有快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSubscriptionDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"CacheSub\" /></template>\n </NButton>\n </template>\n 修改内存订阅\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSelectorDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"Exe-Sel\" /></template>\n </NButton>\n </template>\n 执行选择器\n </NTooltip>\n </template>\n </div>\n <NDataTable\n :checkedRowKeys=\"checkedRowKeys\"\n striped\n flex-height\n :data=\"snapshots\"\n :columns=\"columns\"\n :pagination=\"pagination\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n :scrollX=\"1200\"\n @update:checkedRowKeys=\"updateCheckedRowKeys\"\n @update:sorter=\"handleSorterChange\"\n />\n </div>\n</template>\n","<script setup lang=\"tsx\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { delay } from '@/shared/lib/async';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useBatchTask, useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { RowKey, SortState } from 'naive-ui/es/data-table/src/interface';\nimport pLimit from 'p-limit';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport DeviceActionDialogs from './DeviceActionDialogs.vue';\nimport PageBackButton from '@/features/navigation/PageBackButton.vue';\n\nconst router = useRouter();\nconst { api, origin, serverInfo, disconnect } = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nlet connectionVersion = 0;\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst loadSnapshots = async (version = connectionVersion) => {\n const result = await api.getSnapshots();\n if (version != connectionVersion) return;\n result.sort((a, b) => b.id - a.id);\n snapshots.value = result;\n};\nconst connect = useTask(async () => {\n if (!link.value) return;\n const version = ++connectionVersion;\n origin.value = errorWrap(\n () => new URL(link.value.trim()),\n () => `非法设备地址`,\n ).origin;\n link.value = origin.value;\n localStorage.setItem(`device_link`, link.value);\n const result = await api.getServerInfo();\n if (version != connectionVersion) return;\n serverInfo.value = result;\n document.title = serverTitle.value;\n await loadSnapshots(version);\n});\n\nconst serverTitle = computed(() => {\n if (!serverInfo.value) return '未连接设备';\n const d = serverInfo.value.device;\n const g = serverInfo.value.gkdAppInfo;\n return `${d.manufacturer} Android${d.release} - GKD${g.versionName}`;\n});\n\nonMounted(async () => {\n await delay(500);\n if (toValidURL(link.value)) {\n connect.invoke();\n }\n});\n\nconst captureSnapshot = useTask(async () => {\n const snapshot = await api.captureSnapshot();\n const screenshot = await api.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n await loadSnapshots();\n});\nconst downloadAllSnapshot = useTask(async () => {\n const snapshotIds = (await api.getSnapshots()).map((s) => s.id);\n const existSnapshotIds = new Set(\n (await screenshotStorage.keys()).map((s) => parseInt(s)),\n );\n const unimportSnapshotIds = snapshotIds.filter(\n (k) => !existSnapshotIds.has(k),\n );\n if (unimportSnapshotIds.length == 0) {\n message.success(`没有新记录可导入`);\n return;\n }\n let r = 0;\n const limit = pLimit(3);\n await Promise.all(\n unimportSnapshotIds.map((snapshotId) =>\n limit(async () => {\n const [newSnapshot, newScreenshot] = await Promise.all([\n api.getSnapshot({ id: snapshotId }),\n api.getScreenshot({ id: snapshotId }),\n ] as const);\n if (!newSnapshot.nodes) return;\n await Promise.all([\n snapshotStorage.setItem(snapshotId, newSnapshot),\n screenshotStorage.setItem(snapshotId, newScreenshot),\n ]);\n r++;\n }),\n ),\n );\n message.success(`导入${r}条新记录`);\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\nconst handleSorterChange = (sorter: SortState) => {\n if (sorter.columnKey == ctimeCol.key) {\n ctimeCol.sortOrder = sorter.order;\n }\n};\nconst previewSnapshot = useBatchTask(\n async (row: Snapshot) => {\n if (!(await snapshotStorage.hasItem(row.id))) {\n const obj = await api.getSnapshot({ id: row.id });\n await snapshotStorage.setItem(row.id, obj);\n }\n if (!(await screenshotStorage.hasItem(row.id))) {\n const bf = await api.getScreenshot({ id: row.id });\n await screenshotStorage.setItem(row.id, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: row.id },\n }).href,\n );\n },\n (r) => r.id,\n);\n\nconst columns: DataTableColumns<Snapshot> = [\n {\n type: 'selection',\n },\n ctimeCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return (\n <SnapshotActionCard\n snapshot={row}\n showExport={false}\n showShare={false}\n deleteConfirmText={`是否确认删除? 此操作不可恢复!\\n快照ID:${row.id}`}\n onPreview={() => previewSnapshot.invoke(row)}\n previewLoading={previewSnapshot.loading[row.id]}\n onBeforeDelete={async () => await api.deleteSnapshot({ id: row.id })}\n onDelete={() => {\n snapshots.value = snapshots.value.filter((s) => s.id !== row.id);\n }}\n />\n );\n },\n },\n];\n\nconst checkedRowKeys = ref<number[]>([]);\nconst { batchDelete } = useBatchActions(checkedRowKeys, {\n beforeDeleteItem: async (id) => await api.deleteSnapshot({ id }),\n onAfterDelete: async () => {\n await loadSnapshots();\n },\n});\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst showSubsModel = shallowRef(false);\nconst showSelectorModel = shallowRef(false);\nconst setSubscriptionDialogVisible = (show: boolean) => {\n showSubsModel.value = show;\n};\nconst setSelectorDialogVisible = (show: boolean) => {\n showSelectorModel.value = show;\n};\nconst updateCheckedRowKeys = (keys: RowKey[]) => {\n checkedRowKeys.value = keys.filter(\n (key): key is number => typeof key == 'number',\n );\n};\nconst stopConnection = () => {\n connectionVersion++;\n disconnect();\n snapshots.value = [];\n checkedRowKeys.value = [];\n setSubscriptionDialogVisible(false);\n setSelectorDialogVisible(false);\n document.title = '连接设备';\n};\n</script>\n<template>\n <DeviceActionDialogs\n :key=\"origin?.toString()\"\n :api=\"api\"\n :subscriptionShow=\"showSubsModel\"\n :selectorShow=\"showSelectorModel\"\n @update:subscriptionShow=\"setSubscriptionDialogVisible\"\n @update:selectorShow=\"setSelectorDialogVisible\"\n />\n <div page-size flex flex-col p-10px gap-10px>\n <div flex items-center gap-24px>\n <PageBackButton class=\"ml-12px\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"gkd_code\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connect.invoke\"\n @update:value=\"updateLink\"\n >\n <template #suffix>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 20px\"\n :loading=\"connect.loading\"\n @click=\"connect.invoke\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connect.loading\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n gkd_code\n pl-16px\n whitespace-nowrap\n flex\n items-center\n >\n {{ serverTitle }}\n </div>\n </NInputGroup>\n <template v-if=\"serverInfo\">\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n />\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"captureSnapshot.loading\"\n @click=\"captureSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Snapshot\" /></template>\n </NButton>\n </template>\n 捕获快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"downloadAllSnapshot.loading\"\n @click=\"downloadAllSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Down-all\" /></template>\n </NButton>\n </template>\n 下载所有快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSubscriptionDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"CacheSub\" /></template>\n </NButton>\n </template>\n 修改内存订阅\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSelectorDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"Exe-Sel\" /></template>\n </NButton>\n </template>\n 执行选择器\n </NTooltip>\n </template>\n </div>\n <NDataTable\n :checkedRowKeys=\"checkedRowKeys\"\n striped\n flex-height\n :data=\"snapshots\"\n :columns=\"columns\"\n :pagination=\"pagination\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n :scrollX=\"1200\"\n @update:checkedRowKeys=\"updateCheckedRowKeys\"\n @update:sorter=\"handleSorterChange\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport DeviceControlView from '@/features/device-control/DeviceControlView.vue';\n</script>\n\n<template>\n <DeviceControlView />\n</template>\n","<script setup lang=\"ts\">\nimport DeviceControlView from '@/features/device-control/DeviceControlView.vue';\n</script>\n\n<template>\n <DeviceControlView />\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DevicePage-saBa_BwH.js","names":[],"sources":["../../src/features/device-control/api.ts","../../src/features/device-control/DeviceActionDialogs.vue","../../src/features/device-control/DeviceActionDialogs.vue","../../src/features/device-control/DeviceControlView.vue","../../src/features/device-control/DeviceControlView.vue","../../src/pages/device/DevicePage.vue","../../src/pages/device/DevicePage.vue"],"sourcesContent":["import type { Snapshot } from '@/entities/snapshot/types';\nimport type { RpcError, ServerInfo } from '@/features/device-control/types';\nimport { message } from '@/shared/services/feedback';\nimport { enhanceFetch } from '@/features/network-access/enhanceFetch';\nimport type { GmXhrOptions } from '@/shared/api/gm';\n\ninterface Reqid {\n id: number;\n}\n\nexport const useDeviceApi = (initOrigin?: string) => {\n const origin = shallowRef(initOrigin);\n const serverInfo = shallowRef<ServerInfo>();\n let requestController = new AbortController();\n const disconnect = () => {\n requestController.abort();\n requestController = new AbortController();\n origin.value = undefined;\n serverInfo.value = undefined;\n };\n const request = async (\n name: string,\n init?: RequestInit,\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n if (!origin.value) {\n throw new Error(`origin must exist`);\n }\n const u = new URL(`/api/` + name, origin.value);\n const response = await enhanceFetch(\n u,\n { ...init, signal: requestController.signal },\n xhrDetails,\n ).catch((e) => {\n if (!(e instanceof DOMException && e.name == 'AbortError')) {\n message.error(`网络错误:` + name);\n }\n throw e;\n });\n if (!response.ok) {\n message.error(`接口错误:` + name + `:` + response.status);\n throw response;\n }\n if (response.headers.get(`Content-Type`)?.includes(`application/json`)) {\n const error = (await response.clone().json()) as RpcError;\n if (error.__error) {\n message.error(error.message);\n throw response;\n }\n }\n return response;\n };\n const baseRpc = async (\n name: string,\n data: object = {},\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n return request(\n name,\n {\n method: 'post',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n },\n xhrDetails,\n );\n };\n const jsonRpc = async <T>(name: string, data: object = {}): Promise<T> => {\n return baseRpc(name, data).then((r) => r.json());\n };\n const bfRpc = async (\n name: string,\n data: object = {},\n ): Promise<ArrayBuffer> => {\n return baseRpc(name, data, (v) => {\n // must set responseType to get binary data\n v.responseType = 'arraybuffer';\n return v;\n }).then((r) => r.arrayBuffer());\n };\n const api = {\n getServerInfo: async () => jsonRpc<ServerInfo>(`getServerInfo`),\n getSnapshot: async (data: Reqid): Promise<Snapshot> => {\n if (serverInfo.value?.gkdAppInfo?.versionName === '1.10.4') {\n // 兼容旧版本 BUG\n // https://github.com/gkd-kit/gkd/blob/v1.10.4/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt#L198\n return request('snapshot?id=' + data.id).then((r) => r.json());\n }\n return jsonRpc(`getSnapshot`, data);\n },\n getScreenshot: async (data: Reqid) => {\n return bfRpc(`getScreenshot`, data);\n },\n captureSnapshot: async () => jsonRpc<Snapshot>(`captureSnapshot`),\n getSnapshots: async () => jsonRpc<Snapshot[]>(`getSnapshots`),\n updateSubscription: async (data: any) => {\n return jsonRpc(`updateSubscription`, {\n ...data,\n id: -1,\n name: '内存订阅',\n version: 0,\n });\n },\n execSelector: async (data: {\n selector: string;\n action?: string;\n fastQuery?: boolean;\n }) => {\n data = structuredClone(data);\n data.action ||= undefined;\n return jsonRpc<{ message: string; action: string; result: boolean }>(\n `execSelector`,\n data,\n );\n },\n deleteSnapshot: async (data: Reqid) => {\n return jsonRpc<{ message: string }>(`deleteSnapshot`, data);\n },\n };\n return { origin, api, serverInfo, disconnect };\n};\n\nexport type DeviceApi = ReturnType<typeof useDeviceApi>['api'];\n","<script setup lang=\"ts\">\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useTask } from '@/shared/lib/task';\nimport JSON5 from 'json5';\nimport type { DeviceApi } from './api';\nimport SelectorLibraryDialog from '@/features/selector-library/ui/SelectorLibraryDialog.vue';\n\nconst props = defineProps<{\n api: DeviceApi;\n selectorShow: boolean;\n subscriptionShow: boolean;\n}>();\n\nconst emit = defineEmits<{\n 'update:selectorShow': [show: boolean];\n 'update:subscriptionShow': [show: boolean];\n}>();\n\nconst subscriptionText = shallowRef(``);\nconst clickAction = shallowReactive({\n selector: ``,\n action: 'click',\n quickFind: false,\n});\nconst updateSubscriptionText = (value: string) => {\n subscriptionText.value = value;\n};\nconst updateSelector = (value: string) => {\n clickAction.selector = value;\n};\nconst updateQuickFind = (value: boolean) => {\n clickAction.quickFind = value;\n};\nconst updateAction = (value: string) => {\n clickAction.action = value;\n};\nconst selectorLibraryShow = shallowRef(false);\nconst openSelectorLibrary = () => {\n selectorLibraryShow.value = true;\n};\nconst setSelectorLibraryVisible = (show: boolean) => {\n selectorLibraryShow.value = show;\n};\nconst useLibrarySelector = (selector: string) => {\n updateSelector(selector);\n};\n\nconst actionOptions: { value?: string; label: string }[] = [\n { label: '仅查询', value: `` },\n { value: 'click', label: 'click' },\n { value: 'clickNode', label: 'clickNode' },\n { value: 'clickCenter', label: 'clickCenter' },\n { value: 'back', label: 'back' },\n { value: 'longClick', label: 'longClick' },\n { value: 'longClickNode', label: 'longClickNode' },\n { value: 'longClickCenter', label: 'longClickCenter' },\n];\n\nconst updateSubscription = useTask(async () => {\n const data = errorWrap(() => JSON5.parse(subscriptionText.value.trim()));\n if (!data) return;\n if (data.categories || data.globalGroups || data.apps) {\n await props.api.updateSubscription(data);\n } else if (typeof data.id == 'string') {\n await props.api.updateSubscription({ apps: [data] });\n } else if (Array.isArray(data) && typeof data[0].id == 'string') {\n await props.api.updateSubscription({ apps: data });\n } else if (typeof data.key == 'number') {\n await props.api.updateSubscription({ globalGroups: [data] });\n } else if (Array.isArray(data) && typeof data[0].key == 'number') {\n await props.api.updateSubscription({ globalGroups: data });\n } else {\n message.error(`无法识别的订阅文本`);\n return;\n }\n message.success(`修改成功`);\n});\n\nconst executeSelector = useTask(async () => {\n const result = await props.api.execSelector({\n ...clickAction,\n fastQuery: clickAction.quickFind,\n });\n if (result.message) {\n message.success(`操作成功:` + result.message);\n return;\n }\n if (result.action) {\n message.success((result.result ? `操作成功:` : `操作失败`) + result.action);\n } else if (result.result) {\n message.success(`查询成功`);\n }\n});\n\nconst subscriptionPlaceholder = `\n请输入订阅文本(JSON5语法):\n示例1-更新单个应用的规则:\n{\n id: 'appId',\n groups: []\n}\n\n示例2-更新多个应用的规则:\n[\n {\n id: 'appId1',\n groups: []\n },\n {\n id: 'appId2',\n groups: []\n }\n]\n\n示例3-更新全局规则(1.7.0):\n{\n name: '全局规则-1',\n key: 0,\n rules: []\n}\n\n示例3-更新多个全局规则(1.7.0):\n[\n {\n name: '全局规则-1',\n key: 0,\n rules: []\n },\n {\n name: '全局规则-2',\n key: 1,\n rules: []\n }\n]\n\n示例4-更新整个订阅(1.7.0):\n{\n apps: [],\n globalGroups: [],\n categories: [],\n}\n`.trim();\n</script>\n\n<template>\n <NModal\n :show=\"subscriptionShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"修改内存订阅\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: updateSubscription.loading,\n onClick: updateSubscription.invoke,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 20, maxRows: 25 }\"\n :placeholder=\"subscriptionPlaceholder\"\n @update:value=\"updateSubscriptionText\"\n />\n </NModal>\n\n <NModal\n :show=\"selectorShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"执行选择器\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: executeSelector.loading,\n onClick: executeSelector.invoke,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 4, maxRows: 10 }\"\n placeholder=\"请输入合法的选择器\"\n @update:value=\"updateSelector\"\n />\n <div class=\"mt-8px flex justify-end\">\n <NButton secondary @click=\"openSelectorLibrary\">选择器库</NButton>\n </div>\n <div h-15px />\n <NSpace>\n <NCheckbox\n :checked=\"clickAction.quickFind\"\n @update:checked=\"updateQuickFind\"\n >\n 快速查找\n </NCheckbox>\n <a\n href=\"https://gkd.li/api/interfaces/RawCommonProps.html#quickfind\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 查找说明\n </a>\n </NSpace>\n <div h-10px />\n <div flex gap-10px flex-items-center>\n <NSelect\n :value=\"clickAction.action\"\n :options=\"actionOptions\"\n class=\"w-150px\"\n @update:value=\"updateAction\"\n />\n <a\n href=\"https://gkd.li/api/interfaces/RawRuleProps#action\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 操作说明\n </a>\n </div>\n </NModal>\n <SelectorLibraryDialog\n :show=\"selectorLibraryShow\"\n @use=\"useLibrarySelector\"\n @update:show=\"setSelectorLibraryVisible\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useTask } from '@/shared/lib/task';\nimport JSON5 from 'json5';\nimport type { DeviceApi } from './api';\nimport SelectorLibraryDialog from '@/features/selector-library/ui/SelectorLibraryDialog.vue';\n\nconst props = defineProps<{\n api: DeviceApi;\n selectorShow: boolean;\n subscriptionShow: boolean;\n}>();\n\nconst emit = defineEmits<{\n 'update:selectorShow': [show: boolean];\n 'update:subscriptionShow': [show: boolean];\n}>();\n\nconst subscriptionText = shallowRef(``);\nconst clickAction = shallowReactive({\n selector: ``,\n action: 'click',\n quickFind: false,\n});\nconst updateSubscriptionText = (value: string) => {\n subscriptionText.value = value;\n};\nconst updateSelector = (value: string) => {\n clickAction.selector = value;\n};\nconst updateQuickFind = (value: boolean) => {\n clickAction.quickFind = value;\n};\nconst updateAction = (value: string) => {\n clickAction.action = value;\n};\nconst selectorLibraryShow = shallowRef(false);\nconst openSelectorLibrary = () => {\n selectorLibraryShow.value = true;\n};\nconst setSelectorLibraryVisible = (show: boolean) => {\n selectorLibraryShow.value = show;\n};\nconst useLibrarySelector = (selector: string) => {\n updateSelector(selector);\n};\n\nconst actionOptions: { value?: string; label: string }[] = [\n { label: '仅查询', value: `` },\n { value: 'click', label: 'click' },\n { value: 'clickNode', label: 'clickNode' },\n { value: 'clickCenter', label: 'clickCenter' },\n { value: 'back', label: 'back' },\n { value: 'longClick', label: 'longClick' },\n { value: 'longClickNode', label: 'longClickNode' },\n { value: 'longClickCenter', label: 'longClickCenter' },\n];\n\nconst updateSubscription = useTask(async () => {\n const data = errorWrap(() => JSON5.parse(subscriptionText.value.trim()));\n if (!data) return;\n if (data.categories || data.globalGroups || data.apps) {\n await props.api.updateSubscription(data);\n } else if (typeof data.id == 'string') {\n await props.api.updateSubscription({ apps: [data] });\n } else if (Array.isArray(data) && typeof data[0].id == 'string') {\n await props.api.updateSubscription({ apps: data });\n } else if (typeof data.key == 'number') {\n await props.api.updateSubscription({ globalGroups: [data] });\n } else if (Array.isArray(data) && typeof data[0].key == 'number') {\n await props.api.updateSubscription({ globalGroups: data });\n } else {\n message.error(`无法识别的订阅文本`);\n return;\n }\n message.success(`修改成功`);\n});\n\nconst executeSelector = useTask(async () => {\n const result = await props.api.execSelector({\n ...clickAction,\n fastQuery: clickAction.quickFind,\n });\n if (result.message) {\n message.success(`操作成功:` + result.message);\n return;\n }\n if (result.action) {\n message.success((result.result ? `操作成功:` : `操作失败`) + result.action);\n } else if (result.result) {\n message.success(`查询成功`);\n }\n});\n\nconst subscriptionPlaceholder = `\n请输入订阅文本(JSON5语法):\n示例1-更新单个应用的规则:\n{\n id: 'appId',\n groups: []\n}\n\n示例2-更新多个应用的规则:\n[\n {\n id: 'appId1',\n groups: []\n },\n {\n id: 'appId2',\n groups: []\n }\n]\n\n示例3-更新全局规则(1.7.0):\n{\n name: '全局规则-1',\n key: 0,\n rules: []\n}\n\n示例3-更新多个全局规则(1.7.0):\n[\n {\n name: '全局规则-1',\n key: 0,\n rules: []\n },\n {\n name: '全局规则-2',\n key: 1,\n rules: []\n }\n]\n\n示例4-更新整个订阅(1.7.0):\n{\n apps: [],\n globalGroups: [],\n categories: [],\n}\n`.trim();\n</script>\n\n<template>\n <NModal\n :show=\"subscriptionShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"修改内存订阅\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: updateSubscription.loading,\n onClick: updateSubscription.invoke,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 20, maxRows: 25 }\"\n :placeholder=\"subscriptionPlaceholder\"\n @update:value=\"updateSubscriptionText\"\n />\n </NModal>\n\n <NModal\n :show=\"selectorShow\"\n preset=\"dialog\"\n style=\"width: 800px\"\n title=\"执行选择器\"\n :maskClosable=\"false\"\n positiveText=\"确认\"\n :positiveButtonProps=\"{\n loading: executeSelector.loading,\n onClick: executeSelector.invoke,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading\"\n type=\"textarea\"\n class=\"gkd_code\"\n :autosize=\"{ minRows: 4, maxRows: 10 }\"\n placeholder=\"请输入合法的选择器\"\n @update:value=\"updateSelector\"\n />\n <div class=\"mt-8px flex justify-end\">\n <NButton secondary @click=\"openSelectorLibrary\">选择器库</NButton>\n </div>\n <div h-15px />\n <NSpace>\n <NCheckbox\n :checked=\"clickAction.quickFind\"\n @update:checked=\"updateQuickFind\"\n >\n 快速查找\n </NCheckbox>\n <a\n href=\"https://gkd.li/api/interfaces/RawCommonProps.html#quickfind\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 查找说明\n </a>\n </NSpace>\n <div h-10px />\n <div flex gap-10px flex-items-center>\n <NSelect\n :value=\"clickAction.action\"\n :options=\"actionOptions\"\n class=\"w-150px\"\n @update:value=\"updateAction\"\n />\n <a\n href=\"https://gkd.li/api/interfaces/RawRuleProps#action\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n 操作说明\n </a>\n </div>\n </NModal>\n <SelectorLibraryDialog\n :show=\"selectorLibraryShow\"\n @use=\"useLibrarySelector\"\n @update:show=\"setSelectorLibraryVisible\"\n />\n</template>\n","<script setup lang=\"tsx\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { delay } from '@/shared/lib/async';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useBatchTask, useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { RowKey, SortState } from 'naive-ui/es/data-table/src/interface';\nimport pLimit from 'p-limit';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport DeviceActionDialogs from './DeviceActionDialogs.vue';\nimport PageBackButton from '@/features/navigation/PageBackButton.vue';\n\nconst router = useRouter();\nconst { api, origin, serverInfo, disconnect } = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nlet connectionVersion = 0;\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst loadSnapshots = async (version = connectionVersion) => {\n const result = await api.getSnapshots();\n if (version != connectionVersion) return;\n result.sort((a, b) => b.id - a.id);\n snapshots.value = result;\n};\nconst connect = useTask(async () => {\n if (!link.value) return;\n const version = ++connectionVersion;\n origin.value = errorWrap(\n () => new URL(link.value.trim()),\n () => `非法设备地址`,\n ).origin;\n link.value = origin.value;\n localStorage.setItem(`device_link`, link.value);\n const result = await api.getServerInfo();\n if (version != connectionVersion) return;\n serverInfo.value = result;\n document.title = serverTitle.value;\n await loadSnapshots(version);\n});\n\nconst serverTitle = computed(() => {\n if (!serverInfo.value) return '未连接设备';\n const d = serverInfo.value.device;\n const g = serverInfo.value.gkdAppInfo;\n return `${d.manufacturer} Android${d.release} - GKD${g.versionName}`;\n});\n\nonMounted(async () => {\n await delay(500);\n if (toValidURL(link.value)) {\n connect.invoke();\n }\n});\n\nconst captureSnapshot = useTask(async () => {\n const snapshot = await api.captureSnapshot();\n const screenshot = await api.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n await loadSnapshots();\n});\nconst downloadAllSnapshot = useTask(async () => {\n const snapshotIds = (await api.getSnapshots()).map((s) => s.id);\n const existSnapshotIds = new Set(\n (await screenshotStorage.keys()).map((s) => parseInt(s)),\n );\n const unimportSnapshotIds = snapshotIds.filter(\n (k) => !existSnapshotIds.has(k),\n );\n if (unimportSnapshotIds.length == 0) {\n message.success(`没有新记录可导入`);\n return;\n }\n let r = 0;\n const limit = pLimit(3);\n await Promise.all(\n unimportSnapshotIds.map((snapshotId) =>\n limit(async () => {\n const [newSnapshot, newScreenshot] = await Promise.all([\n api.getSnapshot({ id: snapshotId }),\n api.getScreenshot({ id: snapshotId }),\n ] as const);\n if (!newSnapshot.nodes) return;\n await Promise.all([\n snapshotStorage.setItem(snapshotId, newSnapshot),\n screenshotStorage.setItem(snapshotId, newScreenshot),\n ]);\n r++;\n }),\n ),\n );\n message.success(`导入${r}条新记录`);\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\nconst handleSorterChange = (sorter: SortState) => {\n if (sorter.columnKey == ctimeCol.key) {\n ctimeCol.sortOrder = sorter.order;\n }\n};\nconst previewSnapshot = useBatchTask(\n async (row: Snapshot) => {\n if (!(await snapshotStorage.hasItem(row.id))) {\n const obj = await api.getSnapshot({ id: row.id });\n await snapshotStorage.setItem(row.id, obj);\n }\n if (!(await screenshotStorage.hasItem(row.id))) {\n const bf = await api.getScreenshot({ id: row.id });\n await screenshotStorage.setItem(row.id, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: row.id },\n }).href,\n );\n },\n (r) => r.id,\n);\n\nconst columns: DataTableColumns<Snapshot> = [\n {\n type: 'selection',\n },\n ctimeCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return (\n <SnapshotActionCard\n snapshot={row}\n showExport={false}\n showShare={false}\n deleteConfirmText={`是否确认删除? 此操作不可恢复!\\n快照ID:${row.id}`}\n onPreview={() => previewSnapshot.invoke(row)}\n previewLoading={previewSnapshot.loading[row.id]}\n onBeforeDelete={async () => await api.deleteSnapshot({ id: row.id })}\n onDelete={() => {\n snapshots.value = snapshots.value.filter((s) => s.id !== row.id);\n }}\n />\n );\n },\n },\n];\n\nconst checkedRowKeys = ref<number[]>([]);\nconst { batchDelete } = useBatchActions(checkedRowKeys, {\n beforeDeleteItem: async (id) => await api.deleteSnapshot({ id }),\n onAfterDelete: async () => {\n await loadSnapshots();\n },\n});\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst showSubsModel = shallowRef(false);\nconst showSelectorModel = shallowRef(false);\nconst setSubscriptionDialogVisible = (show: boolean) => {\n showSubsModel.value = show;\n};\nconst setSelectorDialogVisible = (show: boolean) => {\n showSelectorModel.value = show;\n};\nconst updateCheckedRowKeys = (keys: RowKey[]) => {\n checkedRowKeys.value = keys.filter(\n (key): key is number => typeof key == 'number',\n );\n};\nconst stopConnection = () => {\n connectionVersion++;\n disconnect();\n snapshots.value = [];\n checkedRowKeys.value = [];\n setSubscriptionDialogVisible(false);\n setSelectorDialogVisible(false);\n document.title = '连接设备';\n};\n</script>\n<template>\n <DeviceActionDialogs\n :key=\"origin?.toString()\"\n :api=\"api\"\n :subscriptionShow=\"showSubsModel\"\n :selectorShow=\"showSelectorModel\"\n @update:subscriptionShow=\"setSubscriptionDialogVisible\"\n @update:selectorShow=\"setSelectorDialogVisible\"\n />\n <div page-size flex flex-col p-10px gap-10px>\n <div flex items-center gap-24px>\n <PageBackButton class=\"ml-12px\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"gkd_code\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connect.invoke\"\n @update:value=\"updateLink\"\n >\n <template #suffix>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 20px\"\n :loading=\"connect.loading\"\n @click=\"connect.invoke\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connect.loading\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n gkd_code\n pl-16px\n whitespace-nowrap\n flex\n items-center\n >\n {{ serverTitle }}\n </div>\n </NInputGroup>\n <template v-if=\"serverInfo\">\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n />\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"captureSnapshot.loading\"\n @click=\"captureSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Snapshot\" /></template>\n </NButton>\n </template>\n 捕获快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"downloadAllSnapshot.loading\"\n @click=\"downloadAllSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Down-all\" /></template>\n </NButton>\n </template>\n 下载所有快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSubscriptionDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"CacheSub\" /></template>\n </NButton>\n </template>\n 修改内存订阅\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSelectorDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"Exe-Sel\" /></template>\n </NButton>\n </template>\n 执行选择器\n </NTooltip>\n </template>\n </div>\n <NDataTable\n :checkedRowKeys=\"checkedRowKeys\"\n striped\n flex-height\n :data=\"snapshots\"\n :columns=\"columns\"\n :pagination=\"pagination\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n :scrollX=\"1200\"\n @update:checkedRowKeys=\"updateCheckedRowKeys\"\n @update:sorter=\"handleSorterChange\"\n />\n </div>\n</template>\n","<script setup lang=\"tsx\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { delay } from '@/shared/lib/async';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useBatchTask, useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { RowKey, SortState } from 'naive-ui/es/data-table/src/interface';\nimport pLimit from 'p-limit';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport DeviceActionDialogs from './DeviceActionDialogs.vue';\nimport PageBackButton from '@/features/navigation/PageBackButton.vue';\n\nconst router = useRouter();\nconst { api, origin, serverInfo, disconnect } = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nlet connectionVersion = 0;\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst loadSnapshots = async (version = connectionVersion) => {\n const result = await api.getSnapshots();\n if (version != connectionVersion) return;\n result.sort((a, b) => b.id - a.id);\n snapshots.value = result;\n};\nconst connect = useTask(async () => {\n if (!link.value) return;\n const version = ++connectionVersion;\n origin.value = errorWrap(\n () => new URL(link.value.trim()),\n () => `非法设备地址`,\n ).origin;\n link.value = origin.value;\n localStorage.setItem(`device_link`, link.value);\n const result = await api.getServerInfo();\n if (version != connectionVersion) return;\n serverInfo.value = result;\n document.title = serverTitle.value;\n await loadSnapshots(version);\n});\n\nconst serverTitle = computed(() => {\n if (!serverInfo.value) return '未连接设备';\n const d = serverInfo.value.device;\n const g = serverInfo.value.gkdAppInfo;\n return `${d.manufacturer} Android${d.release} - GKD${g.versionName}`;\n});\n\nonMounted(async () => {\n await delay(500);\n if (toValidURL(link.value)) {\n connect.invoke();\n }\n});\n\nconst captureSnapshot = useTask(async () => {\n const snapshot = await api.captureSnapshot();\n const screenshot = await api.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n await loadSnapshots();\n});\nconst downloadAllSnapshot = useTask(async () => {\n const snapshotIds = (await api.getSnapshots()).map((s) => s.id);\n const existSnapshotIds = new Set(\n (await screenshotStorage.keys()).map((s) => parseInt(s)),\n );\n const unimportSnapshotIds = snapshotIds.filter(\n (k) => !existSnapshotIds.has(k),\n );\n if (unimportSnapshotIds.length == 0) {\n message.success(`没有新记录可导入`);\n return;\n }\n let r = 0;\n const limit = pLimit(3);\n await Promise.all(\n unimportSnapshotIds.map((snapshotId) =>\n limit(async () => {\n const [newSnapshot, newScreenshot] = await Promise.all([\n api.getSnapshot({ id: snapshotId }),\n api.getScreenshot({ id: snapshotId }),\n ] as const);\n if (!newSnapshot.nodes) return;\n await Promise.all([\n snapshotStorage.setItem(snapshotId, newSnapshot),\n screenshotStorage.setItem(snapshotId, newScreenshot),\n ]);\n r++;\n }),\n ),\n );\n message.success(`导入${r}条新记录`);\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\nconst handleSorterChange = (sorter: SortState) => {\n if (sorter.columnKey == ctimeCol.key) {\n ctimeCol.sortOrder = sorter.order;\n }\n};\nconst previewSnapshot = useBatchTask(\n async (row: Snapshot) => {\n if (!(await snapshotStorage.hasItem(row.id))) {\n const obj = await api.getSnapshot({ id: row.id });\n await snapshotStorage.setItem(row.id, obj);\n }\n if (!(await screenshotStorage.hasItem(row.id))) {\n const bf = await api.getScreenshot({ id: row.id });\n await screenshotStorage.setItem(row.id, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: row.id },\n }).href,\n );\n },\n (r) => r.id,\n);\n\nconst columns: DataTableColumns<Snapshot> = [\n {\n type: 'selection',\n },\n ctimeCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return (\n <SnapshotActionCard\n snapshot={row}\n showExport={false}\n showShare={false}\n deleteConfirmText={`是否确认删除? 此操作不可恢复!\\n快照ID:${row.id}`}\n onPreview={() => previewSnapshot.invoke(row)}\n previewLoading={previewSnapshot.loading[row.id]}\n onBeforeDelete={async () => await api.deleteSnapshot({ id: row.id })}\n onDelete={() => {\n snapshots.value = snapshots.value.filter((s) => s.id !== row.id);\n }}\n />\n );\n },\n },\n];\n\nconst checkedRowKeys = ref<number[]>([]);\nconst { batchDelete } = useBatchActions(checkedRowKeys, {\n beforeDeleteItem: async (id) => await api.deleteSnapshot({ id }),\n onAfterDelete: async () => {\n await loadSnapshots();\n },\n});\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst showSubsModel = shallowRef(false);\nconst showSelectorModel = shallowRef(false);\nconst setSubscriptionDialogVisible = (show: boolean) => {\n showSubsModel.value = show;\n};\nconst setSelectorDialogVisible = (show: boolean) => {\n showSelectorModel.value = show;\n};\nconst updateCheckedRowKeys = (keys: RowKey[]) => {\n checkedRowKeys.value = keys.filter(\n (key): key is number => typeof key == 'number',\n );\n};\nconst stopConnection = () => {\n connectionVersion++;\n disconnect();\n snapshots.value = [];\n checkedRowKeys.value = [];\n setSubscriptionDialogVisible(false);\n setSelectorDialogVisible(false);\n document.title = '连接设备';\n};\n</script>\n<template>\n <DeviceActionDialogs\n :key=\"origin?.toString()\"\n :api=\"api\"\n :subscriptionShow=\"showSubsModel\"\n :selectorShow=\"showSelectorModel\"\n @update:subscriptionShow=\"setSubscriptionDialogVisible\"\n @update:selectorShow=\"setSelectorDialogVisible\"\n />\n <div page-size flex flex-col p-10px gap-10px>\n <div flex items-center gap-24px>\n <PageBackButton class=\"ml-12px\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"gkd_code\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connect.invoke\"\n @update:value=\"updateLink\"\n >\n <template #suffix>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 20px\"\n :loading=\"connect.loading\"\n @click=\"connect.invoke\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connect.loading\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n gkd_code\n pl-16px\n whitespace-nowrap\n flex\n items-center\n >\n {{ serverTitle }}\n </div>\n </NInputGroup>\n <template v-if=\"serverInfo\">\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n />\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"captureSnapshot.loading\"\n @click=\"captureSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Snapshot\" /></template>\n </NButton>\n </template>\n 捕获快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n :loading=\"downloadAllSnapshot.loading\"\n @click=\"downloadAllSnapshot.invoke\"\n >\n <template #icon><GkSvg name=\"Down-all\" /></template>\n </NButton>\n </template>\n 下载所有快照\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSubscriptionDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"CacheSub\" /></template>\n </NButton>\n </template>\n 修改内存订阅\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <NButton\n text\n style=\"--n-icon-size: 24px\"\n @click=\"setSelectorDialogVisible(true)\"\n >\n <template #icon><GkSvg name=\"Exe-Sel\" /></template>\n </NButton>\n </template>\n 执行选择器\n </NTooltip>\n </template>\n </div>\n <NDataTable\n :checkedRowKeys=\"checkedRowKeys\"\n striped\n flex-height\n :data=\"snapshots\"\n :columns=\"columns\"\n :pagination=\"pagination\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n :scrollX=\"1200\"\n @update:checkedRowKeys=\"updateCheckedRowKeys\"\n @update:sorter=\"handleSorterChange\"\n />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport DeviceControlView from '@/features/device-control/DeviceControlView.vue';\n</script>\n\n<template>\n <DeviceControlView />\n</template>\n","<script setup lang=\"ts\">\nimport DeviceControlView from '@/features/device-control/DeviceControlView.vue';\n</script>\n\n<template>\n <DeviceControlView />\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{i as e}from"./chunk-62oNxeRG.js";import{It as t,Lt as n,h as r,o as i}from"./dist-XEDp5Bio.js";import{n as a,t as o}from"./Tooltip-D6FVnTfP.js";import{a as s,n as c}from"./Select-DG4uMgKj.js";import{$ as l,A as u,Dt as d,Et as f,F as p,J as m,L as h,M as g,Tt as ee,at as _,d as v,dt as y,f as b,g as x,h as S,i as C,it as w,l as T,nt as E,ot as D,p as O,st as k,tt as A,u as j,v as M,z as N}from"./runtime-core.esm-bundler-a08HMgVk.js";import{a as P}from"./light-B7F9SyNv.js";import{n as F,t as I}from"./GkSvg-FXttD_NY.js";import{i as te,n as ne}from"./directives-rrsn-IHX.js";import{i as re,n as ie,r as ae,t as oe}from"./Spin-BVf_t8U-.js";import{g as L,h as R,m as z,t as se}from"./SnapshotActionCard-D9SflO-E.js";import{t as B}from"./SettingsModal-C4i6pb9I.js";import{r as ce}from"./FileSaver.min-D5QkYycf.js";import{t as V}from"./check-DTBwMmUb.js";import{n as H,r as le}from"./node-DMl6TIu_.js";import{r as U,t as W,u as ue}from"./storage-BGTBd1nm.js";import{i as de,n as fe,r as pe,t as me}from"./useBatchActions-DapF58OP.js";import{n as he,t as G}from"./import-CZTd6a9_.js";import{t as ge}from"./files-I4E0NXbP.js";import{t as K}from"./_plugin-vue_export-helper-DjoU_TXQ.js";var q=e(R(),1),J=(e,t)=>{let n=new Map;for(let t of e){let e=t.appId||t.appInfo?.id||`(unknown)`,r=t.activityId||`(unknown)`,i=n.get(e)??new Map,a=i.get(r)??[];a.push(t),i.set(r,a),n.set(e,i)}return[...n.entries()].map(([e,n])=>{let r=[...n.entries()].map(([e,n])=>({activityId:e,snapshots:[...n].sort((e,n)=>(t[String(n.id)]??n.id)-(t[String(e.id)]??e.id))})).sort((e,t)=>t.snapshots.length-e.snapshots.length||e.activityId.localeCompare(t.activityId)),i=r.flatMap(e=>e.snapshots);return{appId:e,appName:i.map(e=>H(e).name).find(Boolean)||e,activities:r,snapshotCount:i.length}}).sort((e,t)=>t.snapshotCount-e.snapshotCount||e.appId.localeCompare(t.appId))},Y=e=>{let t=Math.max(1,e.maxEntries??12),n=_({}),r=_({}),i=_({}),a=new Map,o=[],s=!1,c=e=>{let t=o.indexOf(e);t>=0&&o.splice(t,1)},l=e=>{a.set(e,(a.get(e)??0)+1);let t=n[e];t&&URL.revokeObjectURL(t),delete n[e],delete r[e],delete i[e],c(e)},d=e=>{for(c(e),o.push(e);o.length>t;){let e=o[0];if(e===void 0)break;l(e)}},f=async t=>{if(s)return;if(n[t]){d(t);return}if(r[t])return;let o=(a.get(t)??0)+1;a.set(t,o),r[t]=!0,delete i[t];try{let r=await e.getScreenshot(t);if(s||a.get(t)!==o)return;if(!r){i[t]=`暂无预览图`;return}let c=URL.createObjectURL(new Blob([r]));if(s||a.get(t)!==o){URL.revokeObjectURL(c);return}n[t]=c,d(t)}catch{!s&&a.get(t)===o&&(i[t]=`预览加载失败`)}finally{!s&&a.get(t)===o&&delete r[t]}},p=()=>{s||(s=!0,[...o].forEach(l),a.clear())};return u(p),{urls:D(n),loading:D(r),errors:D(i),loadPreview:f,clearPreview:l,dispose:p}},_e={key:0,"py-40px":``,"text-center":``,"opacity-70":``},ve={flex:``,"items-center":``,"gap-8px":``},ye={class:`gkd_code font-600`},be={flex:``,"items-center":``,"gap-8px":``},xe={class:`gkd_code`},Se={flex:``,"flex-col":``,"gap-6px":``},Ce=[`onMouseenter`,`onMouseleave`],X={flex:``,"flex-wrap":``,"items-center":``,"gap-6px":``},we={class:`font-600`},Te={"mt-3px":``,"text-12px":``,"opacity-65":``},Ee={key:0,"py-6px":``,"text-center":``},De=[`src`],Oe={key:1,"py-20px":``,"text-center":``,"opacity-70":``},ke=300,Ae=K(M({__name:`SnapshotGroupView`,props:{snapshots:{},checkedRowKeys:{},loading:{type:Boolean},updateSnapshots:{type:Function}},emits:[`updateCheckedRowKeys`],setup(e,{emit:t}){let o=e,u=t,{snapshotImportTime:g,snapshotViewedTime:w}=ue(),E=T(()=>J(o.snapshots,g)),D=T(()=>new Set(o.checkedRowKeys)),M=k([]),N=k([]),P=_({}),F=Y({getScreenshot:e=>W.getItem(e),maxEntries:12}),I=k(),te=k(0),ne=k(0),re=k(`right-start`),L=k(1),R=k(1),z=0,B=0,V,H=0,U=()=>{B&&window.clearTimeout(B),B=0,V=void 0};A(U);let de=e=>{let t=window.innerWidth-e-10-12,n=e-10-12,r=t>=n,i=r?t:n;re.value=r?`right-start`:`left-start`,te.value=Math.min(window.innerWidth-12,Math.max(12,e+(r?10:-10))),L.value=Math.max(1,Math.min(window.innerWidth*.8,i-32))},fe=()=>{de(z),R.value=Math.max(1,Math.min(360,window.innerHeight-48))},pe=(e,t)=>{let n=t.currentTarget;U(),V=e,z=t.clientX,H=n instanceof HTMLElement?n.getBoundingClientRect().top:t.clientY,fe(),B=window.setTimeout(()=>{V?.id==e.id&&(B=0,V=void 0,ne.value=H,I.value=e,F.loadPreview(e.id))},ke)},me=e=>{z=e.clientX,de(z)},he=e=>{V?.id==e&&U(),I.value?.id==e&&(I.value=void 0)};i(window,`resize`,()=>{I.value&&fe()});let G=(e,t)=>`${e}::${t}`,ge=(e,t)=>P[G(e,t)]??50,K=(e,t)=>{let n=G(e,t);P[n]=ge(e,t)+50},Ae=(e,t)=>{let n=new Set(o.checkedRowKeys);e.forEach(e=>t?n.add(e):n.delete(e)),u(`updateCheckedRowKeys`,[...n])},Z=e=>{let t=e.filter(e=>D.value.has(e)).length;return{checked:e.length>0&&t===e.length,indeterminate:t>0&&t<e.length}},Q=e=>e.map(e=>e.id),$=e=>e.flatMap(e=>Q(e.snapshots)),je=e=>{let t=le(e);return`${t.manufacturer} Android ${t.release||``}`.trim()};return(t,i)=>{let o=c,u=s,_=r,T=ie,k=ae,A=ce,P=a,z=oe;return p(),v(z,{show:e.loading,class:`min-h-0 flex-1 overflow-hidden`,contentClass:`h-full min-h-0 overflow-hidden`},{default:m(()=>[!e.loading&&!y(E).length?(p(),O(`div`,_e,` 未找到匹配快照 `)):(p(),v(A,{key:1,class:`h-full min-h-0`},{default:m(()=>[x(k,{expandedNames:y(M),"onUpdate:expandedNames":i[3]||=e=>l(M)?M.value=e:null,accordion:!1},{default:m(()=>[(p(!0),O(C,null,h(y(E),t=>(p(),v(T,{key:t.appId,name:t.appId},{header:m(()=>[j(`div`,ve,[x(o,{checked:Z($(t.activities)).checked,indeterminate:Z($(t.activities)).indeterminate,onClick:i[0]||=n(()=>{},[`stop`]),"onUpdate:checked":e=>Ae($(t.activities),e)},null,8,[`checked`,`indeterminate`,`onUpdate:checked`]),x(u,{type:`info`,size:`small`},{default:m(()=>[...i[4]||=[S(`应用`,-1)]]),_:1}),j(`span`,ye,d(`${t.appName} (${t.appId})`),1),x(u,{size:`small`},{default:m(()=>[S(d(t.snapshotCount)+` 个快照`,1)]),_:2},1024)])]),default:m(()=>[x(k,{expandedNames:y(N),"onUpdate:expandedNames":i[2]||=e=>l(N)?N.value=e:null,accordion:!1},{default:m(()=>[(p(!0),O(C,null,h(t.activities,r=>(p(),v(T,{key:G(t.appId,r.activityId),name:G(t.appId,r.activityId)},{header:m(()=>[j(`div`,be,[x(o,{checked:Z(Q(r.snapshots)).checked,indeterminate:Z(Q(r.snapshots)).indeterminate,onClick:i[1]||=n(()=>{},[`stop`]),"onUpdate:checked":e=>Ae(Q(r.snapshots),e)},null,8,[`checked`,`indeterminate`,`onUpdate:checked`]),x(u,{type:`success`,size:`small`},{default:m(()=>[...i[5]||=[S(`Activity`,-1)]]),_:1}),j(`span`,xe,d(r.activityId),1),x(u,{size:`small`},{default:m(()=>[S(d(r.snapshots.length)+` 个快照 `,1)]),_:2},1024)])]),default:m(()=>[j(`div`,Se,[(p(!0),O(C,null,h(r.snapshots.slice(0,ge(t.appId,r.activityId)),t=>(p(),O(`div`,{key:t.id,class:ee([`snapshot-group-row flex items-start gap-10px rounded-8px border border-solid px-10px py-7px`,{"snapshot-group-row-viewed":y(w)[t.id]}])},[x(o,{checked:y(D).has(t.id),"onUpdate:checked":e=>Ae([t.id],e)},null,8,[`checked`,`onUpdate:checked`]),j(`div`,{"min-w-0":``,"flex-1":``,"cursor-default":``,onMouseenter:e=>pe(t,e),onMousemove:me,onMouseleave:e=>he(t.id)},[j(`div`,X,[x(u,{size:`small`,type:`warning`},{default:m(()=>[S(d(y(q.default)(t.id).format(`MM-DD HH:mm:ss`)),1)]),_:2},1024),y(w)[t.id]?(p(),v(u,{key:0,size:`small`,type:`success`},{default:m(()=>[...i[6]||=[S(` 已查看 `,-1)]]),_:1})):b(``,!0),j(`span`,we,d(t.activityId),1)]),j(`div`,Te,d(je(t))+` · `+d(t.appInfo?.versionName||`unknown`)+` · 导入 `+d(y(q.default)(y(g)[t.id]||t.id).format(`YYYY-MM-DD HH:mm:ss`)),1)],40,Ce),x(se,{class:`ml-auto shrink-0`,snapshot:t,onDelete:e.updateSnapshots},null,8,[`snapshot`,`onDelete`])],2))),128)),r.snapshots.length>ge(t.appId,r.activityId)?(p(),O(`div`,Ee,[x(_,{size:`small`,secondary:``,onClick:e=>K(t.appId,r.activityId)},{default:m(()=>[...i[7]||=[S(` 显示更多 `,-1)]]),_:1},8,[`onClick`])])):b(``,!0)])]),_:2},1032,[`name`]))),128))]),_:2},1032,[`expandedNames`])]),_:2},1032,[`name`]))),128))]),_:1},8,[`expandedNames`])]),_:1})),x(P,{show:!!y(I),x:y(te),y:y(ne),placement:y(re),flip:!0,showArrow:!1,contentStyle:{maxWidth:`${y(L)}px`,maxHeight:`${y(R)}px`,overflow:`hidden`,pointerEvents:`none`}},{default:m(()=>[y(I)?(p(),O(`div`,{key:0,"overflow-hidden":``,style:f({maxWidth:`${y(L)}px`,maxHeight:`${y(R)}px`})},[y(F).urls[y(I).id]?(p(),O(`img`,{key:0,src:y(F).urls[y(I).id],class:`block h-auto w-auto max-w-full rounded-6px`,style:f({maxHeight:`${y(R)}px`}),alt:`快照预览`},null,12,De)):(p(),O(`div`,Oe,d(y(F).errors[y(I).id]||(y(F).loading[y(I).id]?`预览加载中...`:`暂无预览图`)),1))],4)):b(``,!0)]),_:1},8,[`show`,`x`,`y`,`placement`,`contentStyle`])]),_:1},8,[`show`])}}}),[[`__scopeId`,`data-v-084bdb8e`]]),Z={flex:``,"flex-col":``,"overflow-hidden":``,"p-10px":``,"gap-10px":``,"page-size":``},Q={flex:``},$={flex:``,"gap-24px":``,"items-center":``,"pr-8px":``,class:`[--svg-h:24px]`},je={flex:``,href:`https://github.com/orgs/gkd-kit/discussions`,target:`_blank`,rel:`noopener noreferrer`},Me={flex:``,href:`https://github.com/gkd-kit/inspect`,target:`_blank`,rel:`noopener noreferrer`},Ne=M({__name:`SnapshotListView`,setup(e){let n=k([]),s=k(!0),c=async()=>{s.value=!0,n.value=(await U.getAllItems()).reverse(),Y.value=[],ce(),s.value=!1};g(c);let u=_({query:``,actualQuery:``,updateQuery:()=>{let e=u.query.trim();e!=u.actualQuery&&(u.actualQuery=e,Y.value=[],ce())}}),d=T(()=>{let e=u.actualQuery;return e?n.value.filter(t=>(H(t).name||``).includes(e)||(t.appId||``).includes(e)||(t.activityId||``).includes(e)):n.value}),f=z(async e=>{await G(e)&&await c()});i(document.body,`drop`,async e=>{e.preventDefault(),await f.invoke(ge(e))}),i(document.body,`dragover`,e=>{e.preventDefault()});let{activityIdCol:h,appIdCol:ee,appNameCol:b,ctimeCol:D,mtimeCol:A,deviceCol:M,appVersionCodeCol:ie,appVersionNameCol:ae,resetColWidth:oe}=de(),R=e=>{let t=[...new Set(e)];if(!(t.length<=1))return t.map(e=>({value:e,label:e}))};function ce(){let e=d.value;M.filterOptions=R(e.map(pe)),b.filterOptions=R(e.map(e=>H(e).name)),h.filterOptions=R(e.map(e=>e.activityId))}let le=E([{type:`selection`},D,A,M,b,ee,ie,ae,h,{key:`actions`,title:`操作`,fixed:`right`,width:`160px`,render(e){return x(se,{snapshot:e,onDelete:c},null)}}]),W=_({page:1,pageSize:50,showSizePicker:!0,pageSizes:[50,100],onChange:e=>{W.page=e,oe()},onUpdatePageSize:e=>{W.pageSize=e,W.page=1,oe()}}),ue=e=>{[D,A].forEach(t=>{e.columnKey==t.key?t.sortOrder=e.order:t.sortOrder=void 0})};A.sortOrder=`descend`;let K=k(!1),q=k(``),J=z(async()=>{let e=q.value.trim().split(`
|
|
2
|
+
`).map(e=>e.trim()).filter(e=>V(e));e.length!=0&&await he(e)&&(await c(),q.value=``)});i(document.body,`paste`,e=>{if(K.value)return;let t=e.target;if(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||(t.className||``).includes(`input`))return;let n=e.clipboardData;if(!n)return;let r=(n.getData(`text`)||``).trim();(r.startsWith(`https://`)||r.startsWith(`http://`))&&(K.value=!0,setTimeout(()=>{q.value=r}))});let Y=w([]),{batchDelete:_e,batchDownloadImage:ve,batchDownloadZip:ye,batchShareImageUrl:be,batchShareZipUrl:xe}=me(Y,{onAfterDelete:c}),Se=k(!1),Ce=e=>{Se.value=e},X=k(`table`),we=e=>{X.value=e},Te=k();return(e,n)=>{let i=r,h=re,g=F,ee=te,_=L,b=o,w=N(`RouterLink`),T=a,E=ne,D=P;return p(),O(C,null,[j(`div`,Z,[j(`div`,Q,[x(_,null,{default:m(()=>[x(h,null,{default:m(()=>[x(i,{type:y(X)==`table`?`primary`:`default`,title:`表格视图`,"aria-label":`表格视图`,"aria-pressed":y(X)==`table`,onClick:n[0]||=e=>we(`table`)},{icon:m(()=>[x(I,{name:`view-table`})]),_:1},8,[`type`,`aria-pressed`]),x(i,{type:y(X)==`group`?`primary`:`default`,title:`分组视图`,"aria-label":`分组视图`,"aria-pressed":y(X)==`group`,onClick:n[1]||=e=>we(`group`)},{icon:m(()=>[x(I,{name:`view-group`})]),_:1},8,[`type`,`aria-pressed`])]),_:1}),x(ee,null,{default:m(()=>[x(g,{value:y(u).query,"onUpdate:value":n[2]||=e=>y(u).query=e,placeholder:`请输入应用名称/应用ID/界面ID`,clearable:``,class:`min-w-320px`,onKeyup:t(y(u).updateQuery,[`enter`]),onChange:y(u).updateQuery},null,8,[`value`,`onKeyup`,`onChange`]),x(i,{onClick:y(u).updateQuery},{icon:m(()=>[x(I,{name:`search`})]),_:1},8,[`onClick`])]),_:1}),x(fe,{checkedCount:y(Y).length,batchDelete:y(_e),batchDownloadImage:y(ve),batchDownloadZip:y(ye),batchShareImageUrl:y(be),batchShareZipUrl:y(xe)},null,8,[`checkedCount`,`batchDelete`,`batchDownloadImage`,`batchDownloadZip`,`batchShareImageUrl`,`batchShareZipUrl`])]),_:1}),n[25]||=j(`div`,{"flex-1":``},null,-1),j(`div`,$,[x(b,null,{trigger:m(()=>[x(i,{text:``,onClick:n[3]||=e=>Ce(!0)},{default:m(()=>[x(I,{name:`settings`})]),_:1})]),default:m(()=>[n[14]||=S(` 设置 `,-1)]),_:1}),x(b,null,{trigger:m(()=>[x(w,{flex:``,to:`/selector/library`},{default:m(()=>[x(i,{text:``,"aria-label":`选择器库`},{default:m(()=>[x(I,{name:`selector-library`})]),_:1})]),_:1})]),default:m(()=>[n[15]||=S(` 选择器库 `,-1)]),_:1}),x(b,null,{trigger:m(()=>[x(w,{flex:``,to:`/selector`},{default:m(()=>[x(i,{text:``},{default:m(()=>[x(I,{name:`terminal`})]),_:1})]),_:1})]),default:m(()=>[n[16]||=S(` 测试选择器 `,-1)]),_:1}),x(T,null,{trigger:m(()=>[x(i,{text:``},{default:m(()=>[x(I,{name:`import`})]),_:1})]),default:m(()=>[x(_,{vertical:``},{default:m(()=>[x(b,{placement:`left`},{trigger:m(()=>[x(i,{loading:y(f).loading,onClick:n[4]||=e=>y(f).invoke()},{default:m(()=>[...n[17]||=[S(` 导入本地文件 `,-1)]]),_:1},8,[`loading`])]),default:m(()=>[n[18]||=j(`div`,{class:`whitespace-nowrap`},`支持拖拽文件到页面任意位置`,-1)]),_:1}),x(b,{placement:`left`},{trigger:m(()=>[x(i,{loading:y(J).loading,onClick:n[5]||=e=>K.value=!0},{default:m(()=>[...n[19]||=[S(` 导入网络文件 `,-1)]]),_:1},8,[`loading`])]),default:m(()=>[n[20]||=j(`div`,{class:`whitespace-nowrap`},` 支持任意位置粘贴(Ctrl+V)文本触发导入 `,-1)]),_:1}),x(w,{flex:``,to:`/log`},{default:m(()=>[x(i,{class:`w-full`},{default:m(()=>[...n[21]||=[S(`查看日志包`,-1)]]),_:1})]),_:1})]),_:1})]),_:1}),x(b,null,{trigger:m(()=>[x(w,{flex:``,to:`/device`},{default:m(()=>[x(i,{text:``},{default:m(()=>[x(I,{name:`device`})]),_:1})]),_:1})]),default:m(()=>[n[22]||=S(` 连接设备 `,-1)]),_:1}),x(b,null,{trigger:m(()=>[j(`a`,je,[x(i,{text:``},{default:m(()=>[x(I,{name:`discussion`})]),_:1})])]),default:m(()=>[n[23]||=S(` 讨论交流 `,-1)]),_:1}),x(b,null,{trigger:m(()=>[j(`a`,Me,[x(i,{text:``},{default:m(()=>[x(I,{name:`github`})]),_:1})])]),default:m(()=>[n[24]||=S(` Github `,-1)]),_:1})])]),y(X)==`table`?(p(),v(E,{key:0,checkedRowKeys:y(Y),"onUpdate:checkedRowKeys":n[6]||=e=>l(Y)?Y.value=e:null,striped:``,virtualScroll:``,data:y(d),columns:y(le),scrollX:1800,rowKey:e=>e.id,size:`small`,class:`flex-1`,"flex-height":``,loading:y(s),"onUpdate:sorter":ue},null,8,[`checkedRowKeys`,`data`,`columns`,`rowKey`,`loading`])):(p(),v(Ae,{key:1,snapshots:y(d),checkedRowKeys:y(Y),loading:y(s),updateSnapshots:c,onUpdateCheckedRowKeys:n[7]||=e=>Y.value=e},null,8,[`snapshots`,`checkedRowKeys`,`loading`]))]),x(D,{show:y(K),preset:`dialog`,title:`导入网络文件`,maskClosable:!1,showIcon:!1,positiveText:`确认`,negativeText:`取消`,style:{width:`800px`},loading:y(J).loading,onPositiveClick:y(J).invoke,onNegativeClick:n[9]||=e=>K.value=!1,onClose:n[10]||=e=>K.value=!1,onEsc:n[11]||=e=>K.value=!1,onAfterEnter:n[12]||=e=>y(Te)?.focus(),onAfterLeave:n[13]||=e=>q.value=``},{default:m(()=>[x(g,{ref_key:`inputImportRef`,ref:Te,value:y(q),type:`textarea`,placeholder:`1.支持ZIP文件链接
|
|
3
|
+
2.支持快照链接
|
|
4
|
+
每行一个
|
|
5
|
+
空白行自动忽略
|
|
6
|
+
非法链接行自动忽略`,autosize:{minRows:8,maxRows:16},inputProps:{style:`white-space: nowrap;`},"onUpdate:value":n[8]||=e=>{y(J).loading||(q.value=e)}},null,8,[`value`])]),_:1},8,[`show`,`loading`,`onPositiveClick`]),x(B,{show:y(Se),"onUpdate:show":Ce},null,8,[`show`])],64)}}}),Pe=M({__name:`HomePage`,setup(e){return(e,t)=>(p(),v(Ne))}});export{Pe as default};
|
|
7
|
+
//# sourceMappingURL=HomePage-D-EKtxNC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HomePage-D-EKtxNC.js","names":[],"sources":["../../src/features/snapshot-list/snapshot_group.ts","../../src/features/snapshot-list/useSnapshotPreviewCache.ts","../../src/features/snapshot-list/SnapshotGroupView.vue","../../src/features/snapshot-list/SnapshotGroupView.vue","../../src/features/snapshot-list/SnapshotListView.vue","../../src/features/snapshot-list/SnapshotListView.vue","../../src/pages/home/HomePage.vue","../../src/pages/home/HomePage.vue"],"sourcesContent":["import type { Snapshot } from '@/entities/snapshot/types';\nimport { getAppInfo } from '../../entities/snapshot/node.ts';\n\nexport interface SnapshotActivityGroup {\n activityId: string;\n snapshots: Snapshot[];\n}\n\nexport interface SnapshotAppGroup {\n appId: string;\n appName: string;\n activities: SnapshotActivityGroup[];\n snapshotCount: number;\n}\n\nexport const buildSnapshotGroups = (\n snapshots: Snapshot[],\n snapshotImportTime: Readonly<Record<string, number>>,\n): SnapshotAppGroup[] => {\n const appGroups = new Map<string, Map<string, Snapshot[]>>();\n for (const snapshot of snapshots) {\n const appId = snapshot.appId || snapshot.appInfo?.id || '(unknown)';\n const activityId = snapshot.activityId || '(unknown)';\n const activities = appGroups.get(appId) ?? new Map<string, Snapshot[]>();\n const activitySnapshots = activities.get(activityId) ?? [];\n activitySnapshots.push(snapshot);\n activities.set(activityId, activitySnapshots);\n appGroups.set(appId, activities);\n }\n\n return [...appGroups.entries()]\n .map(([appId, activities]): SnapshotAppGroup => {\n const activityGroups = [...activities.entries()]\n .map(([activityId, activitySnapshots]) => ({\n activityId,\n snapshots: [...activitySnapshots].sort(\n (left, right) =>\n (snapshotImportTime[String(right.id)] ?? right.id) -\n (snapshotImportTime[String(left.id)] ?? left.id),\n ),\n }))\n .sort((left, right) => {\n const countDelta = right.snapshots.length - left.snapshots.length;\n return countDelta || left.activityId.localeCompare(right.activityId);\n });\n const allSnapshots = activityGroups.flatMap(\n (activity) => activity.snapshots,\n );\n return {\n appId,\n appName:\n allSnapshots\n .map((snapshot) => getAppInfo(snapshot).name)\n .find(Boolean) || appId,\n activities: activityGroups,\n snapshotCount: allSnapshots.length,\n };\n })\n .sort((left, right) => {\n const countDelta = right.snapshotCount - left.snapshotCount;\n return countDelta || left.appId.localeCompare(right.appId);\n });\n};\n","import { onBeforeUnmount, shallowReactive, shallowReadonly } from 'vue';\n\ninterface SnapshotPreviewCacheOptions {\n getScreenshot: (snapshotId: number) => Promise<ArrayBuffer | null>;\n maxEntries?: number;\n}\n\nexport const useSnapshotPreviewCache = (\n options: SnapshotPreviewCacheOptions,\n) => {\n const maxEntries = Math.max(1, options.maxEntries ?? 12);\n const urlData = shallowReactive<Record<number, string>>({});\n const loadingData = shallowReactive<Record<number, boolean>>({});\n const errorData = shallowReactive<Record<number, string>>({});\n const revisions = new Map<number, number>();\n const order: number[] = [];\n let disposed = false;\n\n const removeOrderEntry = (snapshotId: number) => {\n const index = order.indexOf(snapshotId);\n if (index >= 0) order.splice(index, 1);\n };\n\n const clearPreview = (snapshotId: number) => {\n revisions.set(snapshotId, (revisions.get(snapshotId) ?? 0) + 1);\n const url = urlData[snapshotId];\n if (url) URL.revokeObjectURL(url);\n delete urlData[snapshotId];\n delete loadingData[snapshotId];\n delete errorData[snapshotId];\n removeOrderEntry(snapshotId);\n };\n\n const touch = (snapshotId: number) => {\n removeOrderEntry(snapshotId);\n order.push(snapshotId);\n while (order.length > maxEntries) {\n const oldestId = order[0];\n if (oldestId === undefined) break;\n clearPreview(oldestId);\n }\n };\n\n const loadPreview = async (snapshotId: number) => {\n if (disposed) return;\n if (urlData[snapshotId]) {\n touch(snapshotId);\n return;\n }\n if (loadingData[snapshotId]) return;\n\n const revision = (revisions.get(snapshotId) ?? 0) + 1;\n revisions.set(snapshotId, revision);\n loadingData[snapshotId] = true;\n delete errorData[snapshotId];\n try {\n const screenshot = await options.getScreenshot(snapshotId);\n if (disposed || revisions.get(snapshotId) !== revision) return;\n if (!screenshot) {\n errorData[snapshotId] = '暂无预览图';\n return;\n }\n const url = URL.createObjectURL(new Blob([screenshot]));\n if (disposed || revisions.get(snapshotId) !== revision) {\n URL.revokeObjectURL(url);\n return;\n }\n urlData[snapshotId] = url;\n touch(snapshotId);\n } catch {\n if (!disposed && revisions.get(snapshotId) === revision) {\n errorData[snapshotId] = '预览加载失败';\n }\n } finally {\n if (!disposed && revisions.get(snapshotId) === revision) {\n delete loadingData[snapshotId];\n }\n }\n };\n\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n [...order].forEach(clearPreview);\n revisions.clear();\n };\n onBeforeUnmount(dispose);\n\n return {\n urls: shallowReadonly(urlData),\n loading: shallowReadonly(loadingData),\n errors: shallowReadonly(errorData),\n loadPreview,\n clearPreview,\n dispose,\n };\n};\n","<script setup lang=\"ts\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport { getDevice } from '@/entities/snapshot/node';\nimport { screenshotStorage } from '@/entities/snapshot/storage';\nimport { useSnapshotMetadataStore } from '@/entities/snapshot/metadataStore';\nimport dayjs from 'dayjs';\nimport { buildSnapshotGroups } from './snapshot_group';\nimport { useSnapshotPreviewCache } from './useSnapshotPreviewCache';\n\nconst props = defineProps<{\n snapshots: Snapshot[];\n checkedRowKeys: number[];\n loading: boolean;\n updateSnapshots: () => Promise<void>;\n}>();\n\nconst emit = defineEmits<{\n updateCheckedRowKeys: [value: number[]];\n}>();\n\nconst { snapshotImportTime, snapshotViewedTime } = useSnapshotMetadataStore();\nconst groups = computed(() =>\n buildSnapshotGroups(props.snapshots, snapshotImportTime),\n);\nconst checkedSet = computed(() => new Set(props.checkedRowKeys));\nconst expandedApps = shallowRef<Array<string | number>>([]);\nconst expandedActivities = shallowRef<Array<string | number>>([]);\nconst visibleCounts = shallowReactive<Record<string, number>>({});\n\nconst previewCache = useSnapshotPreviewCache({\n getScreenshot: (snapshotId) => screenshotStorage.getItem(snapshotId),\n maxEntries: 12,\n});\nconst previewSnapshot = shallowRef<Snapshot>();\nconst previewX = shallowRef(0);\nconst previewY = shallowRef(0);\nconst previewPlacement = shallowRef<'left-start' | 'right-start'>(\n 'right-start',\n);\nconst previewMaxWidth = shallowRef(1);\nconst previewMaxHeight = shallowRef(1);\nconst previewShowDelay = 300;\nlet previewPointerX = 0;\nlet previewShowTaskId = 0;\nlet pendingPreviewSnapshot: Snapshot | undefined;\nlet pendingPreviewY = 0;\n\nconst clearPreviewShowTask = () => {\n if (previewShowTaskId) window.clearTimeout(previewShowTaskId);\n previewShowTaskId = 0;\n pendingPreviewSnapshot = undefined;\n};\nonScopeDispose(clearPreviewShowTask);\n\nconst updatePreviewHorizontalPosition = (pointerX: number) => {\n const viewportPadding = 12;\n const pointerGap = 10;\n const popoverChromeWidth = 32;\n const availableRight =\n window.innerWidth - pointerX - pointerGap - viewportPadding;\n const availableLeft = pointerX - pointerGap - viewportPadding;\n const placeRight = availableRight >= availableLeft;\n const availableWidth = placeRight ? availableRight : availableLeft;\n previewPlacement.value = placeRight ? 'right-start' : 'left-start';\n previewX.value = Math.min(\n window.innerWidth - viewportPadding,\n Math.max(\n viewportPadding,\n pointerX + (placeRight ? pointerGap : -pointerGap),\n ),\n );\n previewMaxWidth.value = Math.max(\n 1,\n Math.min(window.innerWidth * 0.8, availableWidth - popoverChromeWidth),\n );\n};\nconst updatePreviewViewportBounds = () => {\n updatePreviewHorizontalPosition(previewPointerX);\n previewMaxHeight.value = Math.max(1, Math.min(360, window.innerHeight - 48));\n};\nconst showSnapshotPreview = (snapshot: Snapshot, event: MouseEvent) => {\n const trigger = event.currentTarget;\n clearPreviewShowTask();\n pendingPreviewSnapshot = snapshot;\n previewPointerX = event.clientX;\n pendingPreviewY =\n trigger instanceof HTMLElement\n ? trigger.getBoundingClientRect().top\n : event.clientY;\n updatePreviewViewportBounds();\n previewShowTaskId = window.setTimeout(() => {\n if (pendingPreviewSnapshot?.id != snapshot.id) return;\n previewShowTaskId = 0;\n pendingPreviewSnapshot = undefined;\n previewY.value = pendingPreviewY;\n previewSnapshot.value = snapshot;\n void previewCache.loadPreview(snapshot.id);\n }, previewShowDelay);\n};\nconst moveSnapshotPreview = (event: MouseEvent) => {\n previewPointerX = event.clientX;\n updatePreviewHorizontalPosition(previewPointerX);\n};\nconst hideSnapshotPreview = (snapshotId: number) => {\n if (pendingPreviewSnapshot?.id == snapshotId) clearPreviewShowTask();\n if (previewSnapshot.value?.id == snapshotId) {\n previewSnapshot.value = undefined;\n }\n};\nconst handlePreviewViewportResize = () => {\n if (previewSnapshot.value) updatePreviewViewportBounds();\n};\nuseEventListener(window, 'resize', handlePreviewViewportResize);\n\nconst getActivityKey = (appId: string, activityId: string) =>\n `${appId}::${activityId}`;\nconst getVisibleCount = (appId: string, activityId: string) =>\n visibleCounts[getActivityKey(appId, activityId)] ?? 50;\nconst showMore = (appId: string, activityId: string) => {\n const key = getActivityKey(appId, activityId);\n visibleCounts[key] = getVisibleCount(appId, activityId) + 50;\n};\n\nconst setChecked = (ids: number[], checked: boolean) => {\n const next = new Set(props.checkedRowKeys);\n ids.forEach((id) => (checked ? next.add(id) : next.delete(id)));\n emit('updateCheckedRowKeys', [...next]);\n};\nconst getCheckedState = (ids: number[]) => {\n const checkedCount = ids.filter((id) => checkedSet.value.has(id)).length;\n return {\n checked: ids.length > 0 && checkedCount === ids.length,\n indeterminate: checkedCount > 0 && checkedCount < ids.length,\n };\n};\nconst getActivityIds = (snapshots: Snapshot[]) =>\n snapshots.map((snapshot) => snapshot.id);\nconst getAppIds = (activities: Array<{ snapshots: Snapshot[] }>) =>\n activities.flatMap((activity) => getActivityIds(activity.snapshots));\nconst getDeviceText = (snapshot: Snapshot) => {\n const device = getDevice(snapshot);\n return `${device.manufacturer} Android ${device.release || ''}`.trim();\n};\n</script>\n\n<template>\n <NSpin\n :show=\"loading\"\n class=\"min-h-0 flex-1 overflow-hidden\"\n contentClass=\"h-full min-h-0 overflow-hidden\"\n >\n <div v-if=\"!loading && !groups.length\" py-40px text-center opacity-70>\n 未找到匹配快照\n </div>\n <NScrollbar v-else class=\"h-full min-h-0\">\n <NCollapse v-model:expandedNames=\"expandedApps\" :accordion=\"false\">\n <NCollapseItem\n v-for=\"group in groups\"\n :key=\"group.appId\"\n :name=\"group.appId\"\n >\n <template #header>\n <div flex items-center gap-8px>\n <NCheckbox\n :checked=\"getCheckedState(getAppIds(group.activities)).checked\"\n :indeterminate=\"\n getCheckedState(getAppIds(group.activities)).indeterminate\n \"\n @click.stop\n @update:checked=\"\n setChecked(getAppIds(group.activities), $event)\n \"\n />\n <NTag type=\"info\" size=\"small\">应用</NTag>\n <span class=\"gkd_code font-600\">\n {{ `${group.appName} (${group.appId})` }}\n </span>\n <NTag size=\"small\">{{ group.snapshotCount }} 个快照</NTag>\n </div>\n </template>\n\n <NCollapse\n v-model:expandedNames=\"expandedActivities\"\n :accordion=\"false\"\n >\n <NCollapseItem\n v-for=\"activity in group.activities\"\n :key=\"getActivityKey(group.appId, activity.activityId)\"\n :name=\"getActivityKey(group.appId, activity.activityId)\"\n >\n <template #header>\n <div flex items-center gap-8px>\n <NCheckbox\n :checked=\"\n getCheckedState(getActivityIds(activity.snapshots))\n .checked\n \"\n :indeterminate=\"\n getCheckedState(getActivityIds(activity.snapshots))\n .indeterminate\n \"\n @click.stop\n @update:checked=\"\n setChecked(getActivityIds(activity.snapshots), $event)\n \"\n />\n <NTag type=\"success\" size=\"small\">Activity</NTag>\n <span class=\"gkd_code\">{{ activity.activityId }}</span>\n <NTag size=\"small\">\n {{ activity.snapshots.length }} 个快照\n </NTag>\n </div>\n </template>\n\n <div flex flex-col gap-6px>\n <div\n v-for=\"snapshot in activity.snapshots.slice(\n 0,\n getVisibleCount(group.appId, activity.activityId),\n )\"\n :key=\"snapshot.id\"\n class=\"snapshot-group-row flex items-start gap-10px rounded-8px border border-solid px-10px py-7px\"\n :class=\"{\n 'snapshot-group-row-viewed':\n snapshotViewedTime[snapshot.id],\n }\"\n >\n <NCheckbox\n :checked=\"checkedSet.has(snapshot.id)\"\n @update:checked=\"setChecked([snapshot.id], $event)\"\n />\n <div\n min-w-0\n flex-1\n cursor-default\n @mouseenter=\"showSnapshotPreview(snapshot, $event)\"\n @mousemove=\"moveSnapshotPreview\"\n @mouseleave=\"hideSnapshotPreview(snapshot.id)\"\n >\n <div flex flex-wrap items-center gap-6px>\n <NTag size=\"small\" type=\"warning\">\n {{ dayjs(snapshot.id).format('MM-DD HH:mm:ss') }}\n </NTag>\n <NTag\n v-if=\"snapshotViewedTime[snapshot.id]\"\n size=\"small\"\n type=\"success\"\n >\n 已查看\n </NTag>\n <span class=\"font-600\">{{ snapshot.activityId }}</span>\n </div>\n <div mt-3px text-12px opacity-65>\n {{ getDeviceText(snapshot) }} ·\n {{ snapshot.appInfo?.versionName || 'unknown' }} · 导入\n {{\n dayjs(\n snapshotImportTime[snapshot.id] || snapshot.id,\n ).format('YYYY-MM-DD HH:mm:ss')\n }}\n </div>\n </div>\n <SnapshotActionCard\n class=\"ml-auto shrink-0\"\n :snapshot=\"snapshot\"\n :onDelete=\"updateSnapshots\"\n />\n </div>\n <div\n v-if=\"\n activity.snapshots.length >\n getVisibleCount(group.appId, activity.activityId)\n \"\n py-6px\n text-center\n >\n <NButton\n size=\"small\"\n secondary\n @click=\"showMore(group.appId, activity.activityId)\"\n >\n 显示更多\n </NButton>\n </div>\n </div>\n </NCollapseItem>\n </NCollapse>\n </NCollapseItem>\n </NCollapse>\n </NScrollbar>\n <NPopover\n :show=\"!!previewSnapshot\"\n :x=\"previewX\"\n :y=\"previewY\"\n :placement=\"previewPlacement\"\n :flip=\"true\"\n :showArrow=\"false\"\n :contentStyle=\"{\n maxWidth: `${previewMaxWidth}px`,\n maxHeight: `${previewMaxHeight}px`,\n overflow: `hidden`,\n pointerEvents: `none`,\n }\"\n >\n <div\n v-if=\"previewSnapshot\"\n overflow-hidden\n :style=\"{\n maxWidth: `${previewMaxWidth}px`,\n maxHeight: `${previewMaxHeight}px`,\n }\"\n >\n <img\n v-if=\"previewCache.urls[previewSnapshot.id]\"\n :src=\"previewCache.urls[previewSnapshot.id]\"\n class=\"block h-auto w-auto max-w-full rounded-6px\"\n :style=\"{ maxHeight: `${previewMaxHeight}px` }\"\n alt=\"快照预览\"\n />\n <div v-else py-20px text-center opacity-70>\n {{\n previewCache.errors[previewSnapshot.id] ||\n (previewCache.loading[previewSnapshot.id]\n ? '预览加载中...'\n : '暂无预览图')\n }}\n </div>\n </div>\n </NPopover>\n </NSpin>\n</template>\n\n<style scoped>\n.snapshot-group-row {\n border-color: var(--app-border);\n background: var(--app-panel);\n}\n\n.snapshot-group-row-viewed {\n border-color: var(--app-viewed-border);\n background: var(--app-viewed-bg);\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport { getDevice } from '@/entities/snapshot/node';\nimport { screenshotStorage } from '@/entities/snapshot/storage';\nimport { useSnapshotMetadataStore } from '@/entities/snapshot/metadataStore';\nimport dayjs from 'dayjs';\nimport { buildSnapshotGroups } from './snapshot_group';\nimport { useSnapshotPreviewCache } from './useSnapshotPreviewCache';\n\nconst props = defineProps<{\n snapshots: Snapshot[];\n checkedRowKeys: number[];\n loading: boolean;\n updateSnapshots: () => Promise<void>;\n}>();\n\nconst emit = defineEmits<{\n updateCheckedRowKeys: [value: number[]];\n}>();\n\nconst { snapshotImportTime, snapshotViewedTime } = useSnapshotMetadataStore();\nconst groups = computed(() =>\n buildSnapshotGroups(props.snapshots, snapshotImportTime),\n);\nconst checkedSet = computed(() => new Set(props.checkedRowKeys));\nconst expandedApps = shallowRef<Array<string | number>>([]);\nconst expandedActivities = shallowRef<Array<string | number>>([]);\nconst visibleCounts = shallowReactive<Record<string, number>>({});\n\nconst previewCache = useSnapshotPreviewCache({\n getScreenshot: (snapshotId) => screenshotStorage.getItem(snapshotId),\n maxEntries: 12,\n});\nconst previewSnapshot = shallowRef<Snapshot>();\nconst previewX = shallowRef(0);\nconst previewY = shallowRef(0);\nconst previewPlacement = shallowRef<'left-start' | 'right-start'>(\n 'right-start',\n);\nconst previewMaxWidth = shallowRef(1);\nconst previewMaxHeight = shallowRef(1);\nconst previewShowDelay = 300;\nlet previewPointerX = 0;\nlet previewShowTaskId = 0;\nlet pendingPreviewSnapshot: Snapshot | undefined;\nlet pendingPreviewY = 0;\n\nconst clearPreviewShowTask = () => {\n if (previewShowTaskId) window.clearTimeout(previewShowTaskId);\n previewShowTaskId = 0;\n pendingPreviewSnapshot = undefined;\n};\nonScopeDispose(clearPreviewShowTask);\n\nconst updatePreviewHorizontalPosition = (pointerX: number) => {\n const viewportPadding = 12;\n const pointerGap = 10;\n const popoverChromeWidth = 32;\n const availableRight =\n window.innerWidth - pointerX - pointerGap - viewportPadding;\n const availableLeft = pointerX - pointerGap - viewportPadding;\n const placeRight = availableRight >= availableLeft;\n const availableWidth = placeRight ? availableRight : availableLeft;\n previewPlacement.value = placeRight ? 'right-start' : 'left-start';\n previewX.value = Math.min(\n window.innerWidth - viewportPadding,\n Math.max(\n viewportPadding,\n pointerX + (placeRight ? pointerGap : -pointerGap),\n ),\n );\n previewMaxWidth.value = Math.max(\n 1,\n Math.min(window.innerWidth * 0.8, availableWidth - popoverChromeWidth),\n );\n};\nconst updatePreviewViewportBounds = () => {\n updatePreviewHorizontalPosition(previewPointerX);\n previewMaxHeight.value = Math.max(1, Math.min(360, window.innerHeight - 48));\n};\nconst showSnapshotPreview = (snapshot: Snapshot, event: MouseEvent) => {\n const trigger = event.currentTarget;\n clearPreviewShowTask();\n pendingPreviewSnapshot = snapshot;\n previewPointerX = event.clientX;\n pendingPreviewY =\n trigger instanceof HTMLElement\n ? trigger.getBoundingClientRect().top\n : event.clientY;\n updatePreviewViewportBounds();\n previewShowTaskId = window.setTimeout(() => {\n if (pendingPreviewSnapshot?.id != snapshot.id) return;\n previewShowTaskId = 0;\n pendingPreviewSnapshot = undefined;\n previewY.value = pendingPreviewY;\n previewSnapshot.value = snapshot;\n void previewCache.loadPreview(snapshot.id);\n }, previewShowDelay);\n};\nconst moveSnapshotPreview = (event: MouseEvent) => {\n previewPointerX = event.clientX;\n updatePreviewHorizontalPosition(previewPointerX);\n};\nconst hideSnapshotPreview = (snapshotId: number) => {\n if (pendingPreviewSnapshot?.id == snapshotId) clearPreviewShowTask();\n if (previewSnapshot.value?.id == snapshotId) {\n previewSnapshot.value = undefined;\n }\n};\nconst handlePreviewViewportResize = () => {\n if (previewSnapshot.value) updatePreviewViewportBounds();\n};\nuseEventListener(window, 'resize', handlePreviewViewportResize);\n\nconst getActivityKey = (appId: string, activityId: string) =>\n `${appId}::${activityId}`;\nconst getVisibleCount = (appId: string, activityId: string) =>\n visibleCounts[getActivityKey(appId, activityId)] ?? 50;\nconst showMore = (appId: string, activityId: string) => {\n const key = getActivityKey(appId, activityId);\n visibleCounts[key] = getVisibleCount(appId, activityId) + 50;\n};\n\nconst setChecked = (ids: number[], checked: boolean) => {\n const next = new Set(props.checkedRowKeys);\n ids.forEach((id) => (checked ? next.add(id) : next.delete(id)));\n emit('updateCheckedRowKeys', [...next]);\n};\nconst getCheckedState = (ids: number[]) => {\n const checkedCount = ids.filter((id) => checkedSet.value.has(id)).length;\n return {\n checked: ids.length > 0 && checkedCount === ids.length,\n indeterminate: checkedCount > 0 && checkedCount < ids.length,\n };\n};\nconst getActivityIds = (snapshots: Snapshot[]) =>\n snapshots.map((snapshot) => snapshot.id);\nconst getAppIds = (activities: Array<{ snapshots: Snapshot[] }>) =>\n activities.flatMap((activity) => getActivityIds(activity.snapshots));\nconst getDeviceText = (snapshot: Snapshot) => {\n const device = getDevice(snapshot);\n return `${device.manufacturer} Android ${device.release || ''}`.trim();\n};\n</script>\n\n<template>\n <NSpin\n :show=\"loading\"\n class=\"min-h-0 flex-1 overflow-hidden\"\n contentClass=\"h-full min-h-0 overflow-hidden\"\n >\n <div v-if=\"!loading && !groups.length\" py-40px text-center opacity-70>\n 未找到匹配快照\n </div>\n <NScrollbar v-else class=\"h-full min-h-0\">\n <NCollapse v-model:expandedNames=\"expandedApps\" :accordion=\"false\">\n <NCollapseItem\n v-for=\"group in groups\"\n :key=\"group.appId\"\n :name=\"group.appId\"\n >\n <template #header>\n <div flex items-center gap-8px>\n <NCheckbox\n :checked=\"getCheckedState(getAppIds(group.activities)).checked\"\n :indeterminate=\"\n getCheckedState(getAppIds(group.activities)).indeterminate\n \"\n @click.stop\n @update:checked=\"\n setChecked(getAppIds(group.activities), $event)\n \"\n />\n <NTag type=\"info\" size=\"small\">应用</NTag>\n <span class=\"gkd_code font-600\">\n {{ `${group.appName} (${group.appId})` }}\n </span>\n <NTag size=\"small\">{{ group.snapshotCount }} 个快照</NTag>\n </div>\n </template>\n\n <NCollapse\n v-model:expandedNames=\"expandedActivities\"\n :accordion=\"false\"\n >\n <NCollapseItem\n v-for=\"activity in group.activities\"\n :key=\"getActivityKey(group.appId, activity.activityId)\"\n :name=\"getActivityKey(group.appId, activity.activityId)\"\n >\n <template #header>\n <div flex items-center gap-8px>\n <NCheckbox\n :checked=\"\n getCheckedState(getActivityIds(activity.snapshots))\n .checked\n \"\n :indeterminate=\"\n getCheckedState(getActivityIds(activity.snapshots))\n .indeterminate\n \"\n @click.stop\n @update:checked=\"\n setChecked(getActivityIds(activity.snapshots), $event)\n \"\n />\n <NTag type=\"success\" size=\"small\">Activity</NTag>\n <span class=\"gkd_code\">{{ activity.activityId }}</span>\n <NTag size=\"small\">\n {{ activity.snapshots.length }} 个快照\n </NTag>\n </div>\n </template>\n\n <div flex flex-col gap-6px>\n <div\n v-for=\"snapshot in activity.snapshots.slice(\n 0,\n getVisibleCount(group.appId, activity.activityId),\n )\"\n :key=\"snapshot.id\"\n class=\"snapshot-group-row flex items-start gap-10px rounded-8px border border-solid px-10px py-7px\"\n :class=\"{\n 'snapshot-group-row-viewed':\n snapshotViewedTime[snapshot.id],\n }\"\n >\n <NCheckbox\n :checked=\"checkedSet.has(snapshot.id)\"\n @update:checked=\"setChecked([snapshot.id], $event)\"\n />\n <div\n min-w-0\n flex-1\n cursor-default\n @mouseenter=\"showSnapshotPreview(snapshot, $event)\"\n @mousemove=\"moveSnapshotPreview\"\n @mouseleave=\"hideSnapshotPreview(snapshot.id)\"\n >\n <div flex flex-wrap items-center gap-6px>\n <NTag size=\"small\" type=\"warning\">\n {{ dayjs(snapshot.id).format('MM-DD HH:mm:ss') }}\n </NTag>\n <NTag\n v-if=\"snapshotViewedTime[snapshot.id]\"\n size=\"small\"\n type=\"success\"\n >\n 已查看\n </NTag>\n <span class=\"font-600\">{{ snapshot.activityId }}</span>\n </div>\n <div mt-3px text-12px opacity-65>\n {{ getDeviceText(snapshot) }} ·\n {{ snapshot.appInfo?.versionName || 'unknown' }} · 导入\n {{\n dayjs(\n snapshotImportTime[snapshot.id] || snapshot.id,\n ).format('YYYY-MM-DD HH:mm:ss')\n }}\n </div>\n </div>\n <SnapshotActionCard\n class=\"ml-auto shrink-0\"\n :snapshot=\"snapshot\"\n :onDelete=\"updateSnapshots\"\n />\n </div>\n <div\n v-if=\"\n activity.snapshots.length >\n getVisibleCount(group.appId, activity.activityId)\n \"\n py-6px\n text-center\n >\n <NButton\n size=\"small\"\n secondary\n @click=\"showMore(group.appId, activity.activityId)\"\n >\n 显示更多\n </NButton>\n </div>\n </div>\n </NCollapseItem>\n </NCollapse>\n </NCollapseItem>\n </NCollapse>\n </NScrollbar>\n <NPopover\n :show=\"!!previewSnapshot\"\n :x=\"previewX\"\n :y=\"previewY\"\n :placement=\"previewPlacement\"\n :flip=\"true\"\n :showArrow=\"false\"\n :contentStyle=\"{\n maxWidth: `${previewMaxWidth}px`,\n maxHeight: `${previewMaxHeight}px`,\n overflow: `hidden`,\n pointerEvents: `none`,\n }\"\n >\n <div\n v-if=\"previewSnapshot\"\n overflow-hidden\n :style=\"{\n maxWidth: `${previewMaxWidth}px`,\n maxHeight: `${previewMaxHeight}px`,\n }\"\n >\n <img\n v-if=\"previewCache.urls[previewSnapshot.id]\"\n :src=\"previewCache.urls[previewSnapshot.id]\"\n class=\"block h-auto w-auto max-w-full rounded-6px\"\n :style=\"{ maxHeight: `${previewMaxHeight}px` }\"\n alt=\"快照预览\"\n />\n <div v-else py-20px text-center opacity-70>\n {{\n previewCache.errors[previewSnapshot.id] ||\n (previewCache.loading[previewSnapshot.id]\n ? '预览加载中...'\n : '暂无预览图')\n }}\n </div>\n </div>\n </NPopover>\n </NSpin>\n</template>\n\n<style scoped>\n.snapshot-group-row {\n border-color: var(--app-border);\n background: var(--app-panel);\n}\n\n.snapshot-group-row-viewed {\n border-color: var(--app-viewed-border);\n background: var(--app-viewed-bg);\n}\n</style>\n","<script lang=\"tsx\" setup>\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport SettingsModal from '@/features/settings/SettingsModal.vue';\nimport { toValidURL } from '@/shared/lib/check';\nimport {\n importFromLocal,\n importFromNetwork,\n} from '@/features/snapshot-management/import';\nimport { getAppInfo } from '@/entities/snapshot/node';\nimport { getDragEventFiles } from '@/shared/lib/files';\nimport { shallowSnapshotStorage } from '@/entities/snapshot/storage';\nimport {\n renderDevice,\n useSnapshotColumns,\n} from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { SortState } from 'naive-ui/es/data-table/src/interface';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport SnapshotGroupView from './SnapshotGroupView.vue';\n\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst loading = shallowRef(true);\nconst updateSnapshots = async () => {\n loading.value = true;\n snapshots.value = (await shallowSnapshotStorage.getAllItems()).reverse();\n checkedRowKeys.value = [];\n updateColumnFilterOptions();\n loading.value = false;\n};\nonMounted(updateSnapshots);\nconst filterOption = shallowReactive({\n query: ``,\n actualQuery: ``,\n updateQuery: () => {\n const query = filterOption.query.trim();\n if (query == filterOption.actualQuery) return;\n filterOption.actualQuery = query;\n checkedRowKeys.value = [];\n updateColumnFilterOptions();\n },\n});\nconst filterSnapshots = computed(() => {\n const actualQuery = filterOption.actualQuery;\n if (!actualQuery) return snapshots.value;\n return snapshots.value.filter((s) => {\n return (\n (getAppInfo(s).name || ``).includes(actualQuery) ||\n (s.appId || ``).includes(actualQuery) ||\n (s.activityId || ``).includes(actualQuery)\n );\n });\n});\n\nconst importLocal = useTask(async (_files?: File[]) => {\n if (await importFromLocal(_files)) {\n await updateSnapshots();\n }\n});\n\nuseEventListener(document.body, 'drop', async (e) => {\n e.preventDefault();\n await importLocal.invoke(getDragEventFiles(e));\n});\nuseEventListener(document.body, 'dragover', (e) => {\n e.preventDefault();\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n mtimeCol,\n deviceCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\n\nconst toFilterOptions = (values: Iterable<string>) => {\n const uniqueValues = [...new Set(values)];\n if (uniqueValues.length <= 1) return undefined;\n return uniqueValues.map((value) => ({ value, label: value }));\n};\nfunction updateColumnFilterOptions() {\n const currentSnapshots = filterSnapshots.value;\n deviceCol.filterOptions = toFilterOptions(currentSnapshots.map(renderDevice));\n appNameCol.filterOptions = toFilterOptions(\n currentSnapshots.map((snapshot) => getAppInfo(snapshot).name),\n );\n activityIdCol.filterOptions = toFilterOptions(\n currentSnapshots.map((snapshot) => snapshot.activityId),\n );\n}\n\nconst columns: DataTableColumns<Snapshot> = reactive([\n {\n type: 'selection',\n },\n ctimeCol,\n mtimeCol,\n deviceCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return <SnapshotActionCard snapshot={row} onDelete={updateSnapshots} />;\n },\n },\n]);\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst handleSorterChange = (sorter: SortState) => {\n [ctimeCol, mtimeCol].forEach((c) => {\n if (sorter.columnKey != c.key) {\n c.sortOrder = undefined;\n } else {\n c.sortOrder = sorter.order;\n }\n });\n};\nmtimeCol.sortOrder = `descend`;\nconst showImportModal = shallowRef(false);\nconst textImportValue = shallowRef(``);\nconst importNetwork = useTask(async () => {\n const urls = textImportValue.value\n .trim()\n .split(`\\n`)\n .map((u) => u.trim())\n .filter((u) => toValidURL(u));\n if (urls.length == 0) return;\n const r = await importFromNetwork(urls);\n if (!r) return;\n await updateSnapshots();\n textImportValue.value = ``;\n});\n\nuseEventListener(document.body, 'paste', (e) => {\n if (showImportModal.value) return;\n const target = e.target as HTMLElement;\n if (\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n (target.className || '').includes('input')\n ) {\n return;\n }\n const dataTransfer = e.clipboardData;\n if (!dataTransfer) return;\n const text = (dataTransfer.getData('text') || '').trim();\n if (text.startsWith('https://') || text.startsWith('http://')) {\n showImportModal.value = true;\n setTimeout(() => {\n textImportValue.value = text;\n });\n }\n});\n\nconst checkedRowKeys = ref<number[]>([]);\nconst {\n batchDelete,\n batchDownloadImage,\n batchDownloadZip,\n batchShareImageUrl,\n batchShareZipUrl,\n} = useBatchActions(checkedRowKeys, {\n onAfterDelete: updateSnapshots,\n});\n\nconst settingsDlgShow = shallowRef(false);\nconst setSettingsVisible = (visible: boolean) => {\n settingsDlgShow.value = visible;\n};\nconst viewMode = shallowRef<'table' | 'group'>('table');\nconst setViewMode = (mode: 'table' | 'group') => {\n viewMode.value = mode;\n};\n\nconst inputImportRef = shallowRef();\n</script>\n<template>\n <div flex flex-col overflow-hidden p-10px gap-10px page-size>\n <div flex>\n <NSpace>\n <NButtonGroup>\n <NButton\n :type=\"viewMode == 'table' ? 'primary' : 'default'\"\n title=\"表格视图\"\n aria-label=\"表格视图\"\n :aria-pressed=\"viewMode == 'table'\"\n @click=\"setViewMode('table')\"\n >\n <template #icon><GkSvg name=\"view-table\" /></template>\n </NButton>\n <NButton\n :type=\"viewMode == 'group' ? 'primary' : 'default'\"\n title=\"分组视图\"\n aria-label=\"分组视图\"\n :aria-pressed=\"viewMode == 'group'\"\n @click=\"setViewMode('group')\"\n >\n <template #icon><GkSvg name=\"view-group\" /></template>\n </NButton>\n </NButtonGroup>\n <NInputGroup>\n <NInput\n v-model:value=\"filterOption.query\"\n placeholder=\"请输入应用名称/应用ID/界面ID\"\n clearable\n class=\"min-w-320px\"\n @keyup.enter=\"filterOption.updateQuery\"\n @change=\"filterOption.updateQuery\"\n />\n <NButton @click=\"filterOption.updateQuery\">\n <template #icon>\n <GkSvg name=\"search\" />\n </template>\n </NButton>\n </NInputGroup>\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n :batchDownloadImage=\"batchDownloadImage\"\n :batchDownloadZip=\"batchDownloadZip\"\n :batchShareImageUrl=\"batchShareImageUrl\"\n :batchShareZipUrl=\"batchShareZipUrl\"\n />\n </NSpace>\n <div flex-1 />\n <div flex gap-24px items-center pr-8px class=\"[--svg-h:24px]\">\n <NTooltip>\n <template #trigger>\n <NButton text @click=\"setSettingsVisible(true)\">\n <GkSvg name=\"settings\" />\n </NButton>\n </template>\n 设置\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/selector/library\">\n <NButton text aria-label=\"选择器库\">\n <GkSvg name=\"selector-library\" />\n </NButton>\n </RouterLink>\n </template>\n 选择器库\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/selector\">\n <NButton text>\n <GkSvg name=\"terminal\" />\n </NButton>\n </RouterLink>\n </template>\n 测试选择器\n </NTooltip>\n <NPopover>\n <template #trigger>\n <NButton text>\n <GkSvg name=\"import\" />\n </NButton>\n </template>\n <NSpace vertical>\n <NTooltip placement=\"left\">\n <template #trigger>\n <NButton\n :loading=\"importLocal.loading\"\n @click=\"importLocal.invoke()\"\n >\n 导入本地文件\n </NButton>\n </template>\n <div class=\"whitespace-nowrap\">支持拖拽文件到页面任意位置</div>\n </NTooltip>\n <NTooltip placement=\"left\">\n <template #trigger>\n <NButton\n :loading=\"importNetwork.loading\"\n @click=\"showImportModal = true\"\n >\n 导入网络文件\n </NButton>\n </template>\n <div class=\"whitespace-nowrap\">\n 支持任意位置粘贴(Ctrl+V)文本触发导入\n </div>\n </NTooltip>\n <RouterLink flex to=\"/log\">\n <NButton class=\"w-full\">查看日志包</NButton>\n </RouterLink>\n </NSpace>\n </NPopover>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/device\">\n <NButton text>\n <GkSvg name=\"device\" />\n </NButton>\n </RouterLink>\n </template>\n 连接设备\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <a\n flex\n href=\"https://github.com/orgs/gkd-kit/discussions\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <NButton text>\n <GkSvg name=\"discussion\" />\n </NButton>\n </a>\n </template>\n 讨论交流\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <a\n flex\n href=\"https://github.com/gkd-kit/inspect\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <NButton text>\n <GkSvg name=\"github\" />\n </NButton>\n </a>\n </template>\n Github\n </NTooltip>\n </div>\n </div>\n <NDataTable\n v-if=\"viewMode == 'table'\"\n v-model:checkedRowKeys=\"checkedRowKeys\"\n striped\n virtualScroll\n :data=\"filterSnapshots\"\n :columns=\"columns\"\n :scrollX=\"1800\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n flex-height\n :loading=\"loading\"\n @update:sorter=\"handleSorterChange\"\n />\n <SnapshotGroupView\n v-else\n :snapshots=\"filterSnapshots\"\n :checkedRowKeys=\"checkedRowKeys\"\n :loading=\"loading\"\n :updateSnapshots=\"updateSnapshots\"\n @updateCheckedRowKeys=\"checkedRowKeys = $event\"\n />\n </div>\n <NModal\n :show=\"showImportModal\"\n preset=\"dialog\"\n title=\"导入网络文件\"\n :maskClosable=\"false\"\n :showIcon=\"false\"\n positiveText=\"确认\"\n negativeText=\"取消\"\n style=\"width: 800px\"\n :loading=\"importNetwork.loading\"\n @positiveClick=\"importNetwork.invoke\"\n @negativeClick=\"showImportModal = false\"\n @close=\"showImportModal = false\"\n @esc=\"showImportModal = false\"\n @afterEnter=\"inputImportRef?.focus()\"\n @afterLeave=\"textImportValue = ``\"\n >\n <NInput\n ref=\"inputImportRef\"\n :value=\"textImportValue\"\n type=\"textarea\"\n :placeholder=\"`1.支持ZIP文件链接\\n2.支持快照链接\\n每行一个\\n空白行自动忽略\\n非法链接行自动忽略`\"\n :autosize=\"{\n minRows: 8,\n maxRows: 16,\n }\"\n :inputProps=\"{\n style: `white-space: nowrap;`,\n }\"\n @update:value=\"\n if (!importNetwork.loading) {\n textImportValue = $event;\n }\n \"\n />\n </NModal>\n\n <SettingsModal :show=\"settingsDlgShow\" @update:show=\"setSettingsVisible\" />\n</template>\n","<script lang=\"tsx\" setup>\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport SnapshotActionCard from '@/features/snapshot-management/ui/SnapshotActionCard.vue';\nimport SnapshotBatchActionsBar from '@/features/snapshot-management/ui/SnapshotBatchActionsBar.vue';\nimport SettingsModal from '@/features/settings/SettingsModal.vue';\nimport { toValidURL } from '@/shared/lib/check';\nimport {\n importFromLocal,\n importFromNetwork,\n} from '@/features/snapshot-management/import';\nimport { getAppInfo } from '@/entities/snapshot/node';\nimport { getDragEventFiles } from '@/shared/lib/files';\nimport { shallowSnapshotStorage } from '@/entities/snapshot/storage';\nimport {\n renderDevice,\n useSnapshotColumns,\n} from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useTask } from '@/shared/lib/task';\nimport type { DataTableColumns, PaginationProps } from 'naive-ui';\nimport type { SortState } from 'naive-ui/es/data-table/src/interface';\nimport { useBatchActions } from '@/features/snapshot-management/useBatchActions';\nimport SnapshotGroupView from './SnapshotGroupView.vue';\n\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst loading = shallowRef(true);\nconst updateSnapshots = async () => {\n loading.value = true;\n snapshots.value = (await shallowSnapshotStorage.getAllItems()).reverse();\n checkedRowKeys.value = [];\n updateColumnFilterOptions();\n loading.value = false;\n};\nonMounted(updateSnapshots);\nconst filterOption = shallowReactive({\n query: ``,\n actualQuery: ``,\n updateQuery: () => {\n const query = filterOption.query.trim();\n if (query == filterOption.actualQuery) return;\n filterOption.actualQuery = query;\n checkedRowKeys.value = [];\n updateColumnFilterOptions();\n },\n});\nconst filterSnapshots = computed(() => {\n const actualQuery = filterOption.actualQuery;\n if (!actualQuery) return snapshots.value;\n return snapshots.value.filter((s) => {\n return (\n (getAppInfo(s).name || ``).includes(actualQuery) ||\n (s.appId || ``).includes(actualQuery) ||\n (s.activityId || ``).includes(actualQuery)\n );\n });\n});\n\nconst importLocal = useTask(async (_files?: File[]) => {\n if (await importFromLocal(_files)) {\n await updateSnapshots();\n }\n});\n\nuseEventListener(document.body, 'drop', async (e) => {\n e.preventDefault();\n await importLocal.invoke(getDragEventFiles(e));\n});\nuseEventListener(document.body, 'dragover', (e) => {\n e.preventDefault();\n});\n\nconst {\n activityIdCol,\n appIdCol,\n appNameCol,\n ctimeCol,\n mtimeCol,\n deviceCol,\n appVersionCodeCol,\n appVersionNameCol,\n resetColWidth,\n} = useSnapshotColumns();\n\nconst toFilterOptions = (values: Iterable<string>) => {\n const uniqueValues = [...new Set(values)];\n if (uniqueValues.length <= 1) return undefined;\n return uniqueValues.map((value) => ({ value, label: value }));\n};\nfunction updateColumnFilterOptions() {\n const currentSnapshots = filterSnapshots.value;\n deviceCol.filterOptions = toFilterOptions(currentSnapshots.map(renderDevice));\n appNameCol.filterOptions = toFilterOptions(\n currentSnapshots.map((snapshot) => getAppInfo(snapshot).name),\n );\n activityIdCol.filterOptions = toFilterOptions(\n currentSnapshots.map((snapshot) => snapshot.activityId),\n );\n}\n\nconst columns: DataTableColumns<Snapshot> = reactive([\n {\n type: 'selection',\n },\n ctimeCol,\n mtimeCol,\n deviceCol,\n appNameCol,\n appIdCol,\n appVersionCodeCol,\n appVersionNameCol,\n activityIdCol,\n {\n key: `actions`,\n title: `操作`,\n fixed: 'right',\n width: `160px`,\n render(row) {\n return <SnapshotActionCard snapshot={row} onDelete={updateSnapshots} />;\n },\n },\n]);\n\nconst pagination = shallowReactive<PaginationProps>({\n page: 1,\n pageSize: 50,\n showSizePicker: true,\n pageSizes: [50, 100],\n onChange: (page: number) => {\n pagination.page = page;\n resetColWidth();\n },\n onUpdatePageSize: (pageSize: number) => {\n pagination.pageSize = pageSize;\n pagination.page = 1;\n resetColWidth();\n },\n});\n\nconst handleSorterChange = (sorter: SortState) => {\n [ctimeCol, mtimeCol].forEach((c) => {\n if (sorter.columnKey != c.key) {\n c.sortOrder = undefined;\n } else {\n c.sortOrder = sorter.order;\n }\n });\n};\nmtimeCol.sortOrder = `descend`;\nconst showImportModal = shallowRef(false);\nconst textImportValue = shallowRef(``);\nconst importNetwork = useTask(async () => {\n const urls = textImportValue.value\n .trim()\n .split(`\\n`)\n .map((u) => u.trim())\n .filter((u) => toValidURL(u));\n if (urls.length == 0) return;\n const r = await importFromNetwork(urls);\n if (!r) return;\n await updateSnapshots();\n textImportValue.value = ``;\n});\n\nuseEventListener(document.body, 'paste', (e) => {\n if (showImportModal.value) return;\n const target = e.target as HTMLElement;\n if (\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n (target.className || '').includes('input')\n ) {\n return;\n }\n const dataTransfer = e.clipboardData;\n if (!dataTransfer) return;\n const text = (dataTransfer.getData('text') || '').trim();\n if (text.startsWith('https://') || text.startsWith('http://')) {\n showImportModal.value = true;\n setTimeout(() => {\n textImportValue.value = text;\n });\n }\n});\n\nconst checkedRowKeys = ref<number[]>([]);\nconst {\n batchDelete,\n batchDownloadImage,\n batchDownloadZip,\n batchShareImageUrl,\n batchShareZipUrl,\n} = useBatchActions(checkedRowKeys, {\n onAfterDelete: updateSnapshots,\n});\n\nconst settingsDlgShow = shallowRef(false);\nconst setSettingsVisible = (visible: boolean) => {\n settingsDlgShow.value = visible;\n};\nconst viewMode = shallowRef<'table' | 'group'>('table');\nconst setViewMode = (mode: 'table' | 'group') => {\n viewMode.value = mode;\n};\n\nconst inputImportRef = shallowRef();\n</script>\n<template>\n <div flex flex-col overflow-hidden p-10px gap-10px page-size>\n <div flex>\n <NSpace>\n <NButtonGroup>\n <NButton\n :type=\"viewMode == 'table' ? 'primary' : 'default'\"\n title=\"表格视图\"\n aria-label=\"表格视图\"\n :aria-pressed=\"viewMode == 'table'\"\n @click=\"setViewMode('table')\"\n >\n <template #icon><GkSvg name=\"view-table\" /></template>\n </NButton>\n <NButton\n :type=\"viewMode == 'group' ? 'primary' : 'default'\"\n title=\"分组视图\"\n aria-label=\"分组视图\"\n :aria-pressed=\"viewMode == 'group'\"\n @click=\"setViewMode('group')\"\n >\n <template #icon><GkSvg name=\"view-group\" /></template>\n </NButton>\n </NButtonGroup>\n <NInputGroup>\n <NInput\n v-model:value=\"filterOption.query\"\n placeholder=\"请输入应用名称/应用ID/界面ID\"\n clearable\n class=\"min-w-320px\"\n @keyup.enter=\"filterOption.updateQuery\"\n @change=\"filterOption.updateQuery\"\n />\n <NButton @click=\"filterOption.updateQuery\">\n <template #icon>\n <GkSvg name=\"search\" />\n </template>\n </NButton>\n </NInputGroup>\n <SnapshotBatchActionsBar\n :checkedCount=\"checkedRowKeys.length\"\n :batchDelete=\"batchDelete\"\n :batchDownloadImage=\"batchDownloadImage\"\n :batchDownloadZip=\"batchDownloadZip\"\n :batchShareImageUrl=\"batchShareImageUrl\"\n :batchShareZipUrl=\"batchShareZipUrl\"\n />\n </NSpace>\n <div flex-1 />\n <div flex gap-24px items-center pr-8px class=\"[--svg-h:24px]\">\n <NTooltip>\n <template #trigger>\n <NButton text @click=\"setSettingsVisible(true)\">\n <GkSvg name=\"settings\" />\n </NButton>\n </template>\n 设置\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/selector/library\">\n <NButton text aria-label=\"选择器库\">\n <GkSvg name=\"selector-library\" />\n </NButton>\n </RouterLink>\n </template>\n 选择器库\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/selector\">\n <NButton text>\n <GkSvg name=\"terminal\" />\n </NButton>\n </RouterLink>\n </template>\n 测试选择器\n </NTooltip>\n <NPopover>\n <template #trigger>\n <NButton text>\n <GkSvg name=\"import\" />\n </NButton>\n </template>\n <NSpace vertical>\n <NTooltip placement=\"left\">\n <template #trigger>\n <NButton\n :loading=\"importLocal.loading\"\n @click=\"importLocal.invoke()\"\n >\n 导入本地文件\n </NButton>\n </template>\n <div class=\"whitespace-nowrap\">支持拖拽文件到页面任意位置</div>\n </NTooltip>\n <NTooltip placement=\"left\">\n <template #trigger>\n <NButton\n :loading=\"importNetwork.loading\"\n @click=\"showImportModal = true\"\n >\n 导入网络文件\n </NButton>\n </template>\n <div class=\"whitespace-nowrap\">\n 支持任意位置粘贴(Ctrl+V)文本触发导入\n </div>\n </NTooltip>\n <RouterLink flex to=\"/log\">\n <NButton class=\"w-full\">查看日志包</NButton>\n </RouterLink>\n </NSpace>\n </NPopover>\n <NTooltip>\n <template #trigger>\n <RouterLink flex to=\"/device\">\n <NButton text>\n <GkSvg name=\"device\" />\n </NButton>\n </RouterLink>\n </template>\n 连接设备\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <a\n flex\n href=\"https://github.com/orgs/gkd-kit/discussions\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <NButton text>\n <GkSvg name=\"discussion\" />\n </NButton>\n </a>\n </template>\n 讨论交流\n </NTooltip>\n <NTooltip>\n <template #trigger>\n <a\n flex\n href=\"https://github.com/gkd-kit/inspect\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <NButton text>\n <GkSvg name=\"github\" />\n </NButton>\n </a>\n </template>\n Github\n </NTooltip>\n </div>\n </div>\n <NDataTable\n v-if=\"viewMode == 'table'\"\n v-model:checkedRowKeys=\"checkedRowKeys\"\n striped\n virtualScroll\n :data=\"filterSnapshots\"\n :columns=\"columns\"\n :scrollX=\"1800\"\n :rowKey=\"(r:Snapshot)=>r.id\"\n size=\"small\"\n class=\"flex-1\"\n flex-height\n :loading=\"loading\"\n @update:sorter=\"handleSorterChange\"\n />\n <SnapshotGroupView\n v-else\n :snapshots=\"filterSnapshots\"\n :checkedRowKeys=\"checkedRowKeys\"\n :loading=\"loading\"\n :updateSnapshots=\"updateSnapshots\"\n @updateCheckedRowKeys=\"checkedRowKeys = $event\"\n />\n </div>\n <NModal\n :show=\"showImportModal\"\n preset=\"dialog\"\n title=\"导入网络文件\"\n :maskClosable=\"false\"\n :showIcon=\"false\"\n positiveText=\"确认\"\n negativeText=\"取消\"\n style=\"width: 800px\"\n :loading=\"importNetwork.loading\"\n @positiveClick=\"importNetwork.invoke\"\n @negativeClick=\"showImportModal = false\"\n @close=\"showImportModal = false\"\n @esc=\"showImportModal = false\"\n @afterEnter=\"inputImportRef?.focus()\"\n @afterLeave=\"textImportValue = ``\"\n >\n <NInput\n ref=\"inputImportRef\"\n :value=\"textImportValue\"\n type=\"textarea\"\n :placeholder=\"`1.支持ZIP文件链接\\n2.支持快照链接\\n每行一个\\n空白行自动忽略\\n非法链接行自动忽略`\"\n :autosize=\"{\n minRows: 8,\n maxRows: 16,\n }\"\n :inputProps=\"{\n style: `white-space: nowrap;`,\n }\"\n @update:value=\"\n if (!importNetwork.loading) {\n textImportValue = $event;\n }\n \"\n />\n </NModal>\n\n <SettingsModal :show=\"settingsDlgShow\" @update:show=\"setSettingsVisible\" />\n</template>\n","<script setup lang=\"ts\">\nimport SnapshotListView from '@/features/snapshot-list/SnapshotListView.vue';\n</script>\n\n<template>\n <SnapshotListView />\n</template>\n","<script setup lang=\"ts\">\nimport SnapshotListView from '@/features/snapshot-list/SnapshotListView.vue';\n</script>\n\n<template>\n <SnapshotListView />\n</template>\n"],"mappings":"qrCAeA,GAAA,EAAA,IAAA,ynBA+CA,ECvDa,EACX,GACG,CACH,IAAM,EAAa,KAAK,IAAI,EAAG,EAAQ,YAAc,EAAE,EACjD,EAAU,EAAwC,CAAC,CAAC,EACpD,EAAc,EAAyC,CAAC,CAAC,EACzD,EAAY,EAAwC,CAAC,CAAC,EACtD,EAAY,IAAI,IAChB,EAAkB,CAAC,EACrB,EAAW,GAET,EAAoB,GAAuB,CAC/C,IAAM,EAAQ,EAAM,QAAQ,CAAU,EAClC,GAAS,GAAG,EAAM,OAAO,EAAO,CAAC,CACvC,EAEM,EAAgB,GAAuB,CAC3C,EAAU,IAAI,GAAa,EAAU,IAAI,CAAU,GAAK,GAAK,CAAC,EAC9D,IAAM,EAAM,EAAQ,GAChB,GAAK,IAAI,gBAAgB,CAAG,EAChC,OAAO,EAAQ,GACf,OAAO,EAAY,GACnB,OAAO,EAAU,GACjB,EAAiB,CAAU,CAC7B,EAEM,EAAS,GAAuB,CAGpC,IAFA,EAAiB,CAAU,EAC3B,EAAM,KAAK,CAAU,EACd,EAAM,OAAS,GAAY,CAChC,IAAM,EAAW,EAAM,GACvB,GAAI,IAAa,IAAA,GAAW,MAC5B,EAAa,CAAQ,CACvB,CACF,EAEM,EAAc,KAAO,IAAuB,CAChD,GAAI,EAAU,OACd,GAAI,EAAQ,GAAa,CACvB,EAAM,CAAU,EAChB,MACF,CACA,GAAI,EAAY,GAAa,OAE7B,IAAM,GAAY,EAAU,IAAI,CAAU,GAAK,GAAK,EACpD,EAAU,IAAI,EAAY,CAAQ,EAClC,EAAY,GAAc,GAC1B,OAAO,EAAU,GACjB,GAAI,CACF,IAAM,EAAa,MAAM,EAAQ,cAAc,CAAU,EACzD,GAAI,GAAY,EAAU,IAAI,CAAU,IAAM,EAAU,OACxD,GAAI,CAAC,EAAY,CACf,EAAU,GAAc,QACxB,MACF,CACA,IAAM,EAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,CAAU,CAAC,CAAC,EACtD,GAAI,GAAY,EAAU,IAAI,CAAU,IAAM,EAAU,CACtD,IAAI,gBAAgB,CAAG,EACvB,MACF,CACA,EAAQ,GAAc,EACtB,EAAM,CAAU,CAClB,MAAQ,CACF,CAAC,GAAY,EAAU,IAAI,CAAU,IAAM,IAC7C,EAAU,GAAc,SAE5B,QAAU,CACJ,CAAC,GAAY,EAAU,IAAI,CAAU,IAAM,GAC7C,OAAO,EAAY,EAEvB,CACF,EAEM,MAAgB,CAChB,IACJ,EAAW,GACX,CAAC,GAAG,CAAK,EAAE,QAAQ,CAAY,EAC/B,EAAU,MAAM,EAClB,EAGA,OAFA,EAAgB,CAAO,EAEhB,CACL,KAAM,EAAgB,CAAO,EAC7B,QAAS,EAAgB,CAAW,EACpC,OAAQ,EAAgB,CAAS,EACjC,cACA,eACA,SACF,CACF,+fCrDM,GAAmB,sLAhCzB,IAAM,EAAQ,EAOR,EAAO,EAIP,CAAE,qBAAoB,sBAAuB,GAAyB,EACtE,EAAS,MACb,EAAoB,EAAM,UAAW,CAAkB,CACzD,EACM,EAAa,MAAe,IAAI,IAAI,EAAM,cAAc,CAAC,EACzD,EAAe,EAAmC,CAAC,CAAC,EACpD,EAAqB,EAAmC,CAAC,CAAC,EAC1D,EAAgB,EAAwC,CAAC,CAAC,EAE1D,EAAe,EAAwB,CAC3C,cAAgB,GAAe,EAAkB,QAAQ,CAAU,EACnE,WAAY,EACd,CAAC,EACK,EAAkB,EAAqB,EACvC,GAAW,EAAW,CAAC,EACvB,GAAW,EAAW,CAAC,EACvB,GAAmB,EACvB,aACF,EACM,EAAkB,EAAW,CAAC,EAC9B,EAAmB,EAAW,CAAC,EAEjC,EAAkB,EAClB,EAAoB,EACpB,EACA,EAAkB,EAEhB,MAA6B,CAC7B,GAAmB,OAAO,aAAa,CAAiB,EAC5D,EAAoB,EACpB,EAAyB,IAAA,EAC3B,EACA,EAAe,CAAoB,EAEnC,IAAM,GAAmC,GAAqB,CAC5D,IAGM,EACJ,OAAO,WAAa,EAAW,GAAa,GACxC,EAAgB,EAAW,GAAa,GACxC,EAAa,GAAkB,EAC/B,EAAiB,EAAa,EAAiB,EACrD,GAAiB,MAAQ,EAAa,cAAgB,aACtD,GAAS,MAAQ,KAAK,IACpB,OAAO,WAAa,GACpB,KAAK,IACH,GACA,GAAY,EAAa,GAAa,IACxC,CACF,EACA,EAAgB,MAAQ,KAAK,IAC3B,EACA,KAAK,IAAI,OAAO,WAAa,GAAK,EAAiB,EAAkB,CACvE,CACF,EACM,OAAoC,CACxC,GAAgC,CAAe,EAC/C,EAAiB,MAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,OAAO,YAAc,EAAE,CAAC,CAC7E,EACM,IAAuB,EAAoB,IAAsB,CACrE,IAAM,EAAU,EAAM,cACtB,EAAqB,EACrB,EAAyB,EACzB,EAAkB,EAAM,QACxB,EACE,aAAmB,YACf,EAAQ,sBAAsB,EAAE,IAChC,EAAM,QACZ,GAA4B,EAC5B,EAAoB,OAAO,eAAiB,CACtC,GAAwB,IAAM,EAAS,KAC3C,EAAoB,EACpB,EAAyB,IAAA,GACzB,GAAS,MAAQ,EACjB,EAAgB,MAAQ,EACxB,EAAkB,YAAY,EAAS,EAAE,EAC3C,EAAG,EAAgB,CACrB,EACM,GAAuB,GAAsB,CACjD,EAAkB,EAAM,QACxB,GAAgC,CAAe,CACjD,EACM,GAAuB,GAAuB,CAC9C,GAAwB,IAAM,GAAY,EAAqB,EAC/D,EAAgB,OAAO,IAAM,IAC/B,EAAgB,MAAQ,IAAA,GAE5B,EAIA,EAAiB,OAAQ,aAHiB,CACpC,EAAgB,OAAO,GAA4B,CACzD,CAC8D,EAE9D,IAAM,GAAkB,EAAe,IACrC,GAAG,EAAM,IAAI,IACT,IAAmB,EAAe,IACtC,EAAc,EAAe,EAAO,CAAU,IAAM,GAChD,GAAY,EAAe,IAAuB,CACtD,IAAM,EAAM,EAAe,EAAO,CAAU,EAC5C,EAAc,GAAO,GAAgB,EAAO,CAAU,EAAI,EAC5D,EAEM,IAAc,EAAe,IAAqB,CACtD,IAAM,EAAO,IAAI,IAAI,EAAM,cAAc,EACzC,EAAI,QAAS,GAAQ,EAAU,EAAK,IAAI,CAAE,EAAI,EAAK,OAAO,CAAE,CAAE,EAC9D,EAAK,uBAAwB,CAAC,GAAG,CAAI,CAAC,CACxC,EACM,EAAmB,GAAkB,CACzC,IAAM,EAAe,EAAI,OAAQ,GAAO,EAAW,MAAM,IAAI,CAAE,CAAC,EAAE,OAClE,MAAO,CACL,QAAS,EAAI,OAAS,GAAK,IAAiB,EAAI,OAChD,cAAe,EAAe,GAAK,EAAe,EAAI,MACxD,CACF,EACM,EAAkB,GACtB,EAAU,IAAK,GAAa,EAAS,EAAE,EACnC,EAAa,GACjB,EAAW,QAAS,GAAa,EAAe,EAAS,SAAS,CAAC,EAC/D,GAAiB,GAAuB,CAC5C,IAAM,EAAS,GAAU,CAAQ,EACjC,MAAO,GAAG,EAAO,aAAa,WAAW,EAAO,SAAW,KAAK,KAAK,CACvE,mEAIE,EAuLQ,EAAA,CAtLL,KAAM,EAAA,QACP,MAAM,iCACN,aAAa,iDAIP,CAAA,CAFM,EAAA,SAAO,CAAK,EAAA,CAAA,EAAO,QAAA,EAAA,EAA/B,EAEM,MAFN,GAAsE,WAEtE,IAAA,EAAA,EACA,EAuIa,EAAA,OAvIM,MAAM,iCAsIX,CArIZ,EAqIY,EAAA,CArIO,cAAe,EAAA,CAAA,4CAAY,MAAA,EAAA,KAAG,UAAW,mBAEjC,EAAA,EAAA,EAAA,EADzB,EAmIgB,EAAA,KAAA,EAlIE,EAAA,CAAA,EAAT,QADT,EAmIgB,EAAA,CAjIb,IAAK,EAAM,MACX,KAAM,EAAM,QAEF,OAAM,MAiBT,CAhBN,EAgBM,MAhBN,GAgBM,CAfJ,EASE,EAAA,CARC,QAAS,EAAgB,EAAU,EAAM,UAAU,CAAA,EAAG,QACtD,cAAkC,EAAgB,EAAU,EAAM,UAAU,CAAA,EAAG,cAG/E,QAAK,AAAA,EAAA,KAAA,MAAN,CAAA,EAAW,CAAA,MAAA,CAAA,EACV,mBAAc,GAAqB,GAAW,EAAU,EAAM,UAAU,EAAG,CAAM,0DAIpF,EAAwC,EAAA,CAAlC,KAAK,OAAO,KAAK,wBAAU,CAAA,GAAA,AAAA,EAAA,KAAA,CAAA,EAAF,KAAE,EAAA,CAAA,CAAA,CAAA,QACjC,EAEO,OAFP,GAEO,EAAA,GADC,EAAM,QAAO,IAAK,EAAM,MAAK,EAAA,EAAA,CAAA,EAErC,EAAuD,EAAA,CAAjD,KAAK,OAAO,EAAA,eAA0B,CAAA,EAAA,EAAtB,EAAM,aAAa,EAAG,OAAI,CAAA,CAAA,CAAA,+BA6GxC,CAzGZ,EAyGY,EAAA,CAxGF,cAAe,EAAA,CAAA,4CAAkB,MAAA,EAAA,KACxC,UAAW,mBAG0B,EAAA,EAAA,EAAA,EADtC,EAoGgB,EAAA,KAAA,EAnGK,EAAM,WAAlB,QADT,EAoGgB,EAAA,CAlGb,IAAK,EAAe,EAAM,MAAO,EAAS,UAAU,EACpD,KAAM,EAAe,EAAM,MAAO,EAAS,UAAU,IAE3C,OAAM,MAqBT,CApBN,EAoBM,MApBN,GAoBM,CAnBJ,EAaE,EAAA,CAZC,QAAgC,EAAgB,EAAe,EAAS,SAAS,CAAA,EAA4B,QAI7G,cAAsC,EAAgB,EAAe,EAAS,SAAS,CAAA,EAA4B,cAInH,QAAK,AAAA,EAAA,KAAA,MAAN,CAAA,EAAW,CAAA,MAAA,CAAA,EACV,mBAAc,GAAyB,GAAW,EAAe,EAAS,SAAS,EAAG,CAAM,0DAI/F,EAAiD,EAAA,CAA3C,KAAK,UAAU,KAAK,wBAAgB,CAAA,GAAA,AAAA,EAAA,KAAA,CAAA,EAAR,WAAQ,EAAA,CAAA,CAAA,CAAA,QAC1C,EAAuD,OAAvD,GAAuD,EAA7B,EAAS,UAAU,EAAA,CAAA,EAC7C,EAEO,EAAA,CAFD,KAAK,OAAO,EAAA,eACe,CAAA,EAAA,EAA5B,EAAS,UAAU,MAAM,EAAG,QACjC,CAAA,CAAA,CAAA,+BA0EE,CAtEN,EAsEM,MAtEN,GAsEM,EAAA,EAAA,EAAA,EArEJ,EAoDM,EAAA,KAAA,EAnDe,EAAS,UAAU,MAAA,EAAkD,GAAgB,EAAM,MAAO,EAAS,UAAU,CAAA,EAAjI,QADT,EAoDM,MAAA,CA/CH,IAAK,EAAS,GACf,MAAK,GAAA,CAAC,8FAA6F,CAAA,4BAClB,EAAA,CAAA,EAAmB,EAAS,GAAA,CAAA,CAAA,IAK7G,EAGE,EAAA,CAFC,QAAS,EAAA,CAAA,EAAW,IAAI,EAAS,EAAE,EACnC,mBAAc,GAAE,GAAU,CAAE,EAAS,EAAE,EAAG,CAAM,0CAEnD,EA8BM,MAAA,CA7BJ,UAAA,GACA,SAAA,GACA,iBAAA,GACC,aAAU,GAAE,GAAoB,EAAU,CAAM,EAChD,YAAW,GACX,aAAU,GAAE,GAAoB,EAAS,EAAE,IAE5C,EAYM,MAZN,EAYM,CAXJ,EAEO,EAAA,CAFD,KAAK,QAAQ,KAAK,0BAC2B,CAAA,EAAA,EAA9C,EAAA,EAAA,OAAA,EAAM,EAAS,EAAE,EAAE,OAAM,gBAAA,CAAA,EAAA,CAAA,CAAA,CAAA,aAGtB,EAAA,CAAA,EAAmB,EAAS,KAAA,EAAA,EADpC,EAMO,EAAA,OAJL,KAAK,QACL,KAAK,0BAGP,CAAA,GAAA,AAAA,EAAA,KAAA,CAAA,EAFC,QAED,EAAA,CAAA,CAAA,CAAA,kBACA,EAAuD,OAAvD,GAAuD,EAA7B,EAAS,UAAU,EAAA,CAAA,IAE/C,EAQM,MARN,GAQM,EAPD,GAAc,CAAQ,CAAA,EAAI,MAC7B,EAAG,EAAS,SAAS,aAAW,SAAA,EAAgB,SAChD,EACE,EAAA,EAAA,OAAA,EAAiC,EAAA,CAAA,EAAmB,EAAS,KAAO,EAAS,EAAA,EAA8B,OAAM,qBAAA,CAAA,EAAA,CAAA,CAAA,EAAA,GAAA,EAAA,EAMvH,EAIE,GAAA,CAHA,MAAM,mBACK,WACV,SAAU,EAAA,6DAIc,EAAS,UAAU,OAA6B,GAAgB,EAAM,MAAO,EAAS,UAAU,GAAA,EAAA,EAD7H,EAeM,MAfN,GAeM,CAPJ,EAMU,EAAA,CALR,KAAK,QACL,UAAA,GACC,QAAK,GAAE,EAAS,EAAM,MAAO,EAAS,UAAU,kBAGnD,CAAA,GAAA,AAAA,EAAA,KAAA,CAAA,EAFC,SAED,EAAA,CAAA,CAAA,CAAA,oKAQd,EAsCW,EAAA,CArCR,KAAI,CAAA,CAAI,EAAA,CAAA,EACR,EAAG,EAAA,EAAA,EACH,EAAG,EAAA,EAAA,EACH,UAAW,EAAA,EAAA,EACX,KAAM,GACN,UAAW,GACX,aAAY,aAAyB,EAAA,CAAA,EAAe,iBAA4B,EAAA,CAAA,EAAgB,4DA8B3F,CAtBE,EAAA,CAAA,GAAA,EAAA,EADR,EAuBM,MAAA,OArBJ,kBAAA,GACC,MAAK,EAAA,aAA2B,EAAA,CAAA,EAAe,iBAA8B,EAAA,CAAA,EAAgB,QAMtF,EAAA,CAAA,EAAa,KAAK,EAAA,CAAA,EAAgB,KAAA,EAAA,EAD1C,EAME,MAAA,OAJC,IAAK,EAAA,CAAA,EAAa,KAAK,EAAA,CAAA,EAAgB,IACxC,MAAM,6CACL,MAAK,EAAA,CAAA,UAAA,GAAkB,EAAA,CAAA,EAAgB,GAAA,CAAA,EACxC,IAAI,0BAEN,EAOM,MAPN,GAOM,EALF,EAAA,CAAA,EAAa,OAAO,EAAA,CAAA,EAAgB,MAAoB,EAAA,CAAA,EAAa,QAAQ,EAAA,CAAA,EAAgB,IAAA,WAAA,QAAA,EAAA,CAAA,EAAA,EAAA,CAAA,GAAA,EAAA,GAAA,EAAA,CAAA,CAAA,uhBE1SzG,IAAM,EAAY,EAAuB,CAAA,CAAE,EACrC,EAAU,EAAW,EAAI,EACzB,EAAkB,SAAY,CAClC,EAAQ,MAAQ,GAChB,EAAU,OAAS,MAAM,EAAuB,YAAY,GAAG,QAAQ,EACvE,EAAe,MAAQ,CAAA,EACvB,GAA0B,EAC1B,EAAQ,MAAQ,EAClB,EACA,EAAU,CAAe,EACzB,IAAM,EAAe,EAAgB,CACnC,MAAO,GACP,YAAa,GACb,gBAAmB,CACjB,IAAM,EAAQ,EAAa,MAAM,KAAK,EAClC,GAAS,EAAa,cAC1B,EAAa,YAAc,EAC3B,EAAe,MAAQ,CAAA,EACvB,GAA0B,EAC5B,CACF,CAAC,EACK,EAAkB,MAAe,CACrC,IAAM,EAAc,EAAa,YAEjC,OADK,EACE,EAAU,MAAM,OAAQ,IAE1B,EAAW,CAAC,EAAE,MAAQ,IAAI,SAAS,CAAW,IAC9C,EAAE,OAAS,IAAI,SAAS,CAAW,IACnC,EAAE,YAAc,IAAI,SAAS,CAAW,CAE5C,EAPwB,EAAU,KAQrC,CAAC,EAEK,EAAc,EAAQ,KAAO,IAAoB,CACjD,MAAM,EAAgB,CAAM,GAC9B,MAAM,EAAgB,CAE1B,CAAC,EAED,EAAiB,SAAS,KAAM,OAAQ,KAAO,IAAM,CACnD,EAAE,eAAe,EACjB,MAAM,EAAY,OAAO,GAAkB,CAAC,CAAC,CAC/C,CAAC,EACD,EAAiB,SAAS,KAAM,WAAa,GAAM,CACjD,EAAE,eAAe,CACnB,CAAC,EAED,GAAM,CACJ,gBACA,YACA,aACA,WACA,WACA,YACA,qBACA,qBACA,kBACE,GAAmB,EAEjB,EAAmB,GAA6B,CACpD,IAAM,EAAe,CAAC,GAAG,IAAI,IAAI,CAAM,CAAC,EACpC,OAAa,QAAU,GAC3B,OAAO,EAAa,IAAK,IAAW,CAAE,QAAO,MAAO,CAAM,EAAE,CAC9D,EACA,SAAS,IAA4B,CACnC,IAAM,EAAmB,EAAgB,MACzC,EAAU,cAAgB,EAAgB,EAAiB,IAAI,EAAY,CAAC,EAC5E,EAAW,cAAgB,EACzB,EAAiB,IAAK,GAAa,EAAW,CAAQ,EAAE,IAAI,CAC9D,EACA,EAAc,cAAgB,EAC5B,EAAiB,IAAK,GAAa,EAAS,UAAU,CACxD,CACF,CAEA,IAAM,GAAsC,EAAS,CACnD,CACE,KAAM,WACR,EACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,EACA,CACE,IAAK,UACL,MAAO,KACP,MAAO,QACP,MAAO,QACP,OAAO,EAAK,CACV,OAAA,EAAA,GAAA,CAAA,SAAqC,EAAG,SAAY,CAAe,EAAA,IAAA,CACrE,CACF,CAAC,CACF,EAEK,EAAa,EAAiC,CAClD,KAAM,EACN,SAAU,GACV,eAAgB,GAChB,UAAW,CAAC,GAAI,GAAG,EACnB,SAAW,GAAiB,CAC1B,EAAW,KAAO,EAClB,GAAc,CAChB,EACA,iBAAmB,GAAqB,CACtC,EAAW,SAAW,EACtB,EAAW,KAAO,EAClB,GAAc,CAChB,CACF,CAAC,EAEK,GAAsB,GAAsB,CAChD,CAAC,EAAU,CAAQ,EAAE,QAAS,GAAM,CAC9B,EAAO,WAAa,EAAE,IAGxB,EAAE,UAAY,EAAO,MAFrB,EAAE,UAAY,IAAA,EAIlB,CAAC,CACH,EACA,EAAS,UAAY,UACrB,IAAM,EAAkB,EAAW,EAAK,EAClC,EAAkB,EAAW,EAAE,EAC/B,EAAgB,EAAQ,SAAY,CACxC,IAAM,EAAO,EAAgB,MAC1B,KAAK,EACL,MAAM;CAAI,EACV,IAAK,GAAM,EAAE,KAAK,CAAC,EACnB,OAAQ,GAAM,EAAW,CAAC,CAAC,EAC1B,EAAK,QAAU,GAEd,MADW,GAAkB,CAAI,IAEtC,MAAM,EAAgB,EACtB,EAAgB,MAAQ,GAC1B,CAAC,EAED,EAAiB,SAAS,KAAM,QAAU,GAAM,CAC9C,GAAI,EAAgB,MAAO,OAC3B,IAAM,EAAS,EAAE,OACjB,GACE,aAAkB,kBAClB,aAAkB,sBACjB,EAAO,WAAa,IAAI,SAAS,OAAO,EAEzC,OAEF,IAAM,EAAe,EAAE,cACvB,GAAI,CAAC,EAAc,OACnB,IAAM,GAAQ,EAAa,QAAQ,MAAM,GAAK,IAAI,KAAK,GACnD,EAAK,WAAW,UAAU,GAAK,EAAK,WAAW,SAAS,KAC1D,EAAgB,MAAQ,GACxB,eAAiB,CACf,EAAgB,MAAQ,CAC1B,CAAC,EAEL,CAAC,EAED,IAAM,EAAiB,EAAc,CAAA,CAAE,EACjC,CACJ,eACA,sBACA,oBACA,sBACA,qBACE,GAAgB,EAAgB,CAClC,cAAe,CACjB,CAAC,EAEK,GAAkB,EAAW,EAAK,EAClC,GAAsB,GAAqB,CAC/C,GAAgB,MAAQ,CAC1B,EACM,EAAW,EAA8B,OAAO,EAChD,GAAe,GAA4B,CAC/C,EAAS,MAAQ,CACnB,EAEM,GAAiB,EAAW,mGAGhC,EAkLM,MAlLN,EAkLM,CAjLJ,EAyJM,MAzJN,EAyJM,CAxJJ,EA4CS,EAAA,KAAA,eAxBQ,CAnBf,EAmBe,EAAA,KAAA,eAVH,CARV,EAQU,EAAA,CAPP,KAAM,EAAA,CAAA,GAAQ,QAAA,UAAA,UACf,MAAM,OACN,aAAW,OACV,eAAc,EAAA,CAAA,GAAQ,QACtB,QAAK,AAAA,EAAA,KAAA,GAAE,GAAW,OAAA,IAER,KAAI,MAA4B,CAA3B,EAA2B,EAAA,CAApB,KAAK,YAAY,CAAA,CAAA,CAAA,kCAE1C,EAQU,EAAA,CAPP,KAAM,EAAA,CAAA,GAAQ,QAAA,UAAA,UACf,MAAM,OACN,aAAW,OACV,eAAc,EAAA,CAAA,GAAQ,QACtB,QAAK,AAAA,EAAA,KAAA,GAAE,GAAW,OAAA,IAER,KAAI,MAA4B,CAA3B,EAA2B,EAAA,CAApB,KAAK,YAAY,CAAA,CAAA,CAAA,0CAG5C,EAcc,GAAA,KAAA,eANV,CAPF,EAOE,EAAA,CANQ,MAAO,EAAA,CAAA,EAAa,iCAAb,EAAA,CAAA,EAAa,MAAK,EACjC,YAAY,oBACZ,UAAA,GACA,MAAM,cACL,QAAK,EAAQ,EAAA,CAAA,EAAa,YAAW,CAAA,OAAA,CAAA,EACrC,SAAQ,EAAA,CAAA,EAAa,oDAExB,EAIU,EAAA,CAJA,QAAO,EAAA,CAAA,EAAa,WAAA,EAAA,CACjB,KAAI,MACU,CAAvB,EAAuB,EAAA,CAAhB,KAAK,QAAQ,CAAA,CAAA,CAAA,8BAI1B,EAOE,GAAA,CANC,aAAc,EAAA,CAAA,EAAe,OAC7B,YAAa,EAAA,EAAA,EACb,mBAAoB,EAAA,EAAA,EACpB,iBAAkB,EAAA,EAAA,EAClB,mBAAoB,EAAA,EAAA,EACpB,iBAAkB,EAAA,EAAA,0IAGvB,EAAc,MAAA,CAAT,SAAA,EAAM,EAAA,KAAA,EAAA,EACX,EAyGM,MAzGN,EAyGM,CAxGJ,EAOW,EAAA,KAAA,CANE,QAAO,MAGN,CAFV,EAEU,EAAA,CAFD,KAAA,GAAM,QAAK,AAAA,EAAA,KAAA,GAAE,GAAkB,EAAA,kBACb,CAAzB,EAAyB,EAAA,CAAlB,KAAK,UAAU,CAAA,CAAA,CAAA,wBAI5B,CAAA,AAAA,EAAA,MAAA,EAFa,OAEb,EAAA,CAAA,CAAA,QACA,EASW,EAAA,KAAA,CARE,QAAO,MAKH,CAJb,EAIa,EAAA,CAJD,KAAA,GAAK,GAAG,oCAGR,CAFV,EAEU,EAAA,CAFD,KAAA,GAAK,aAAW,uBACU,CAAjC,EAAiC,EAAA,CAA1B,KAAK,kBAAkB,CAAA,CAAA,CAAA,gCAKtC,CAAA,AAAA,EAAA,MAAA,EAFa,SAEb,EAAA,CAAA,CAAA,QACA,EASW,EAAA,KAAA,CARE,QAAO,MAKH,CAJb,EAIa,EAAA,CAJD,KAAA,GAAK,GAAG,4BAGR,CAFV,EAEU,EAAA,CAFD,KAAA,EAAI,EAAA,eACc,CAAzB,EAAyB,EAAA,CAAlB,KAAK,UAAU,CAAA,CAAA,CAAA,gCAK9B,CAAA,AAAA,EAAA,MAAA,EAFa,UAEb,EAAA,CAAA,CAAA,QACA,EAmCW,EAAA,KAAA,CAlCE,QAAO,MAGN,CAFV,EAEU,EAAA,CAFD,KAAA,EAAI,EAAA,eACY,CAAvB,EAAuB,EAAA,CAAhB,KAAK,QAAQ,CAAA,CAAA,CAAA,wBA+Bf,CA5BT,EA4BS,EAAA,CA5BD,SAAA,EAAQ,EAAA,eAWH,CAVX,EAUW,EAAA,CAVD,UAAU,MAAM,EAAA,CACb,QAAO,MAMN,CALV,EAKU,EAAA,CAJP,QAAS,EAAA,CAAA,EAAY,QACrB,QAAK,AAAA,EAAA,KAAA,GAAE,EAAA,CAAA,EAAY,OAAM,kBAG5B,CAAA,GAAA,AAAA,EAAA,MAAA,CAAA,EAFC,WAED,EAAA,CAAA,CAAA,CAAA,sCAEgD,CAAA,AAAA,EAAA,MAAlD,EAAkD,MAAA,CAA7C,MAAM,mBAAmB,EAAC,gBAAa,EAAA,CAAA,CAAA,QAE9C,EAYW,EAAA,CAZD,UAAU,MAAM,EAAA,CACb,QAAO,MAMN,CALV,EAKU,EAAA,CAJP,QAAS,EAAA,CAAA,EAAc,QACvB,QAAK,AAAA,EAAA,KAAA,GAAE,EAAA,MAAe,mBAGzB,CAAA,GAAA,AAAA,EAAA,MAAA,CAAA,EAFC,WAED,EAAA,CAAA,CAAA,CAAA,sCAII,CAAA,AAAA,EAAA,MAFN,EAEM,MAAA,CAFD,MAAM,mBAAmB,EAAC,2BAE/B,EAAA,CAAA,CAAA,QAEF,EAEa,EAAA,CAFD,KAAA,GAAK,GAAG,uBACqB,CAAvC,EAAuC,EAAA,CAA9B,MAAM,QAAQ,EAAA,eAAM,CAAA,GAAA,AAAA,EAAA,MAAA,CAAA,EAAL,QAAK,EAAA,CAAA,CAAA,CAAA,gCAInC,EASW,EAAA,KAAA,CARE,QAAO,MAKH,CAJb,EAIa,EAAA,CAJD,KAAA,GAAK,GAAG,0BAGR,CAFV,EAEU,EAAA,CAFD,KAAA,EAAI,EAAA,eACY,CAAvB,EAAuB,EAAA,CAAhB,KAAK,QAAQ,CAAA,CAAA,CAAA,gCAK5B,CAAA,AAAA,EAAA,MAAA,EAFa,SAEb,EAAA,CAAA,CAAA,QACA,EAcW,EAAA,KAAA,CAbE,QAAO,MAUZ,CATJ,EASI,IATJ,GASI,CAHF,EAEU,EAAA,CAFD,KAAA,EAAI,EAAA,eACgB,CAA3B,EAA2B,EAAA,CAApB,KAAK,YAAY,CAAA,CAAA,CAAA,0BAKhC,CAAA,AAAA,EAAA,MAAA,EAFa,SAEb,EAAA,CAAA,CAAA,QACA,EAcW,EAAA,KAAA,CAbE,QAAO,MAUZ,CATJ,EASI,IATJ,GASI,CAHF,EAEU,EAAA,CAFD,KAAA,EAAI,EAAA,eACY,CAAvB,EAAuB,EAAA,CAAhB,KAAK,QAAQ,CAAA,CAAA,CAAA,0BAK5B,CAAA,AAAA,EAAA,MAAA,EAFa,WAEb,EAAA,CAAA,CAAA,YAII,EAAA,CAAA,GAAQ,SAAA,EAAA,EADhB,EAcE,EAAA,OAZQ,eAAgB,EAAA,CAAA,2CAAA,EAAc,MAAA,EAAA,KACtC,QAAA,GACA,cAAA,GACC,KAAM,EAAA,CAAA,EACN,QAAS,EAAA,EAAA,EACT,QAAS,KACT,OAAS,GAAa,EAAE,GACzB,KAAK,QACL,MAAM,SACN,cAAA,GACC,QAAS,EAAA,CAAA,EACT,kBAAe,yEAElB,EAOE,GAAA,OALC,UAAW,EAAA,CAAA,EACX,eAAgB,EAAA,CAAA,EAChB,QAAS,EAAA,CAAA,EACQ,kBACjB,uBAAoB,AAAA,EAAA,KAAA,GAAE,EAAA,MAAiB,uDAG5C,EAmCS,EAAA,CAlCN,KAAM,EAAA,CAAA,EACP,OAAO,SACP,MAAM,SACL,aAAc,GACd,SAAU,GACX,aAAa,KACb,aAAa,KACb,MAAA,CAAA,MAAA,OAAA,EACC,QAAS,EAAA,CAAA,EAAc,QACvB,gBAAe,EAAA,CAAA,EAAc,OAC7B,gBAAa,AAAA,EAAA,KAAA,GAAE,EAAA,MAAe,GAC9B,QAAK,AAAA,EAAA,MAAA,GAAE,EAAA,MAAe,GACtB,MAAG,AAAA,EAAA,MAAA,GAAE,EAAA,MAAe,GACpB,aAAU,AAAA,EAAA,MAAA,GAAE,EAAA,EAAA,GAAgB,MAAK,EACjC,aAAU,AAAA,EAAA,MAAA,GAAE,EAAA,MAAe,mBAmB1B,CAjBF,EAiBE,EAAA,SAhBI,iBAAJ,IAAI,GACH,MAAO,EAAA,CAAA,EACR,KAAK,WACJ,YAAa;;;;WACb,SAAU,uBAIV,WAAY,CAAA,MAAA,sBAAA,EAGZ,iBAAY,AAAA,EAAA,KAAA,GAAA,CAAgB,EAAA,CAAA,EAAc,UAAqB,EAAA,MAAkB,sEAQtF,EAA2E,EAAA,CAA3D,KAAM,EAAA,EAAA,EAAkB,gBAAa,iFEnarD,EAAoB,EAAA"}
|