@gkd-kit/inspect 0.0.1790527631913 → 0.0.1791657047599
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-CLR5I-4l.js +47 -0
- package/dist/assets/DevicePage-CLR5I-4l.js.map +1 -0
- package/dist/assets/LogPage-A49MTa0K.js +448 -0
- package/dist/assets/LogPage-A49MTa0K.js.map +1 -0
- package/dist/assets/SnapshotPage-DTVxifNL.js +1 -1
- package/dist/assets/bootstrap-3t2JD1ar.css +2 -0
- package/dist/assets/{bootstrap-B4t3ePiM.js → bootstrap-CkAcd5K-.js} +7 -7
- package/dist/assets/{bootstrap-B4t3ePiM.js.map → bootstrap-CkAcd5K-.js.map} +1 -1
- package/dist/assets/chunk-CvjC-kdB.js +1 -1
- package/dist/assets/index-NF4nvaLb.js +3 -0
- package/dist/assets/{index-BTkiXNBG.js.map → index-NF4nvaLb.js.map} +1 -1
- package/dist/assets/{sql-wasm-browser--LxQcKnR.js → sql-wasm-browser-DbfIDWyU.js} +2 -2
- package/dist/assets/{sql-wasm-browser--LxQcKnR.js.map → sql-wasm-browser-DbfIDWyU.js.map} +1 -1
- package/dist/assets/{worker-entry-retrace.worker-BS3SOdaF.js → worker-entry-retrace.worker-UClIC_OD.js} +2 -2
- package/dist/assets/{worker-entry-retrace.worker-BS3SOdaF.js.map → worker-entry-retrace.worker-UClIC_OD.js.map} +1 -1
- package/dist/assets/{worker-entry-sqlite.worker-BMkH7qJT.js → worker-entry-sqlite.worker-CDaCHzQt.js} +2 -2
- package/dist/assets/{worker-entry-sqlite.worker-BMkH7qJT.js.map → worker-entry-sqlite.worker-CDaCHzQt.js.map} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/dist/assets/DevicePage-saBa_BwH.js +0 -47
- package/dist/assets/DevicePage-saBa_BwH.js.map +0 -1
- package/dist/assets/LogPage-Bvep01pK.js +0 -448
- package/dist/assets/LogPage-Bvep01pK.js.map +0 -1
- package/dist/assets/bootstrap-CB2lmq-e.css +0 -2
- package/dist/assets/index-BTkiXNBG.js +0 -3
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.1791657047599/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.1791657047599/files/dist/assets/index-NF4nvaLb.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1791657047599/files/dist/assets/preload-helper-xBbMyY7u.js">
|
|
17
|
+
<link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1791657047599/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 ee,t as w}from"./GkSvg-FXttD_NY.js";import{i as T,n as E}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 V=class extends Error{code;serverStackTrace;path;constructor(e){super(e.message),this.name=`RpcException`,this.code=e.code,this.serverStackTrace=e.stackTrace??void 0,this.path=e.path??void 0}};async function H(e){if(!e.ok)throw Error(`HTTP ${e.status}`);let t=e.headers.get(`GKD-RPC-Status`);if(t===`ok`)return e;if(t===`error`){let t;try{t=await e.json()}catch(e){throw Error(`RPC 错误响应不是有效 JSON`,{cause:e})}if(typeof t!=`object`||!t||!(`code`in t)||typeof t.code!=`string`||!(`message`in t)||typeof t.message!=`string`)throw Error(`RPC 错误响应缺少 code 或 message`);let n=`stackTrace`in t?t.stackTrace:void 0;if(n!=null&&typeof n!=`string`)throw Error(`RPC 错误响应的 stackTrace 不是字符串`);let r=`path`in t?t.path:void 0;throw r!=null&&typeof r!=`string`?Error(`RPC 错误响应的 path 不是字符串`):new V({code:t.code,message:t.message,stackTrace:n,path:r})}if(t!==null)throw Error(`未知 RPC 状态: ${t}`);if(e.headers.get(`Content-Type`)?.includes(`application/json`)){let t=await e.clone().json();if(typeof t==`object`&&t&&`__error`in t&&t.__error===!0)throw!(`message`in t)||typeof t.message!=`string`?Error(`旧版 RPC 错误响应缺少 message`):new V({code:`LEGACY_ERROR`,message:t.message})}return e}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`);return H(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})).catch(t=>{throw D.error(t instanceof V?t.path?`${t.path}: ${t.message}`:t.message:`接口错误:${e}:${t instanceof Error?t.message:String(t)}`),t})},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}},U=e(L(),1),W={class:`mt-8px flex justify-end`},G={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},w=b(!1),T=()=>{w.value=!0},E=e=>{w.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(()=>U.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=ee,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`,W,[m(D,{secondary:``,onClick:T},{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`,G,[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(w),onUse:A,"onUpdate:show":E},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":``},K=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:q,appIdCol:de,appNameCol:fe,ctimeCol:J,appVersionCodeCol:pe,appVersionNameCol:me,resetColWidth:Y}=ne(),he=e=>{e.columnKey==J.key&&(J.sortOrder=e.order)},ge=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`},J,fe,de,pe,me,q,{key:`actions`,title:`操作`,fixed:`right`,width:`160px`,render(e){return m(j,{snapshot:e,showExport:!1,showShare:!1,deleteConfirmText:`是否确认删除? 此操作不可恢复!\n快照ID:${e.id}`,onPreview:()=>ge.invoke(e),previewLoading:ge.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,Y()},onUpdatePageSize:e=>{Z.pageSize=e,Z.page=1,Y()}}),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,_=ee,v=T,b=E;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(w,{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(w,{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(w,{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(w,{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(w,{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":he},null,8,[`checkedRowKeys`,`data`,`pagination`,`rowKey`])])],64)}}}),q=S({__name:`DevicePage`,setup(e){return(e,t)=>(s(),d(K))}});export{q as default};
|
|
47
|
+
//# sourceMappingURL=DevicePage-CLR5I-4l.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DevicePage-CLR5I-4l.js","names":[],"sources":["../../src/features/device-control/rpc.ts","../../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 { RpcError } from './types.ts';\n\nexport class RpcException extends Error {\n readonly code: string;\n readonly serverStackTrace?: string;\n readonly path?: string;\n\n constructor(error: RpcError) {\n super(error.message);\n this.name = 'RpcException';\n this.code = error.code;\n this.serverStackTrace = error.stackTrace ?? undefined;\n this.path = error.path ?? undefined;\n }\n}\n\n/** Leaves successful bodies untouched; callers choose JSON or binary decoding. */\nexport async function readRpcResponse(response: Response): Promise<Response> {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n const status = response.headers.get('GKD-RPC-Status');\n if (status === 'ok') return response;\n if (status === 'error') {\n let error: unknown;\n try {\n error = await response.json();\n } catch (cause) {\n throw new Error('RPC 错误响应不是有效 JSON', { cause });\n }\n if (\n typeof error !== 'object' ||\n error === null ||\n !('code' in error) ||\n typeof error.code !== 'string' ||\n !('message' in error) ||\n typeof error.message !== 'string'\n ) {\n throw new Error('RPC 错误响应缺少 code 或 message');\n }\n const stackTrace = 'stackTrace' in error ? error.stackTrace : undefined;\n if (stackTrace != null && typeof stackTrace !== 'string') {\n throw new Error('RPC 错误响应的 stackTrace 不是字符串');\n }\n const path = 'path' in error ? error.path : undefined;\n if (path != null && typeof path !== 'string') {\n throw new Error('RPC 错误响应的 path 不是字符串');\n }\n throw new RpcException({\n code: error.code,\n message: error.message,\n stackTrace,\n path,\n });\n }\n if (status !== null) {\n throw new Error(`未知 RPC 状态: ${status}`);\n }\n\n // Older GKD servers signal errors only in the JSON body.\n if (response.headers.get('Content-Type')?.includes('application/json')) {\n const error: unknown = await response.clone().json();\n if (\n typeof error === 'object' &&\n error !== null &&\n '__error' in error &&\n error.__error === true\n ) {\n if (!('message' in error) || typeof error.message !== 'string') {\n throw new Error('旧版 RPC 错误响应缺少 message');\n }\n throw new RpcException({ code: 'LEGACY_ERROR', message: error.message });\n }\n }\n return response;\n}\n","import type { Snapshot } from '@/entities/snapshot/types';\nimport type { RpcSuccess, ServerInfo } from '@/features/device-control/types';\nimport { readRpcResponse, RpcException } from './rpc';\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 return readRpcResponse(response).catch((error: unknown) => {\n message.error(\n error instanceof RpcException\n ? error.path\n ? `${error.path}: ${error.message}`\n : error.message\n : `接口错误:${name}:${error instanceof Error ? error.message : String(error)}`,\n );\n throw error;\n });\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<RpcSuccess>(`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<RpcSuccess>(`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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"}
|