@gkd-kit/inspect 0.0.1792378239741 → 0.0.1792378669665

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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/dist/404.html +4 -4
  3. package/dist/assets/{DevicePage-CtKwtxqK.js → DevicePage-Wl0kkEdi.js} +2 -2
  4. package/dist/assets/{DevicePage-CtKwtxqK.js.map → DevicePage-Wl0kkEdi.js.map} +1 -1
  5. package/dist/assets/{LogPage-Cl-1jmfv.js → LogPage-SoGsxHKl.js} +2 -2
  6. package/dist/assets/{LogPage-Cl-1jmfv.js.map → LogPage-SoGsxHKl.js.map} +1 -1
  7. package/dist/assets/{SelectorLibraryDialog-CRkofSgG.js → SelectorLibraryDialog-Dkm5zMnZ.js} +2 -2
  8. package/dist/assets/{SelectorLibraryDialog-CRkofSgG.js.map → SelectorLibraryDialog-Dkm5zMnZ.js.map} +1 -1
  9. package/dist/assets/{SelectorLibraryPage-ImH93dN8.js → SelectorLibraryPage-D9HhUEXj.js} +2 -2
  10. package/dist/assets/{SelectorLibraryPage-ImH93dN8.js.map → SelectorLibraryPage-D9HhUEXj.js.map} +1 -1
  11. package/dist/assets/{SelectorPage-SRtoxAAY.js → SelectorPage-D8i086tG.js} +2 -2
  12. package/dist/assets/{SelectorPage-SRtoxAAY.js.map → SelectorPage-D8i086tG.js.map} +1 -1
  13. package/dist/assets/{SelectorSyntaxInput-Cp8Q7PZD.js → SelectorSyntaxInput-BX8ucdOi.js} +2 -2
  14. package/dist/assets/{SelectorSyntaxInput-Cp8Q7PZD.js.map → SelectorSyntaxInput-BX8ucdOi.js.map} +1 -1
  15. package/dist/assets/{SnapshotPage-DRXXaAZR.js → SnapshotPage-LP8lOmRQ.js} +3 -3
  16. package/dist/assets/{SnapshotPage-DRXXaAZR.js.map → SnapshotPage-LP8lOmRQ.js.map} +1 -1
  17. package/dist/assets/{bootstrap-DA3-hdnZ.js → bootstrap-CdjfkEzB.js} +6 -6
  18. package/dist/assets/{bootstrap-DA3-hdnZ.js.map → bootstrap-CdjfkEzB.js.map} +1 -1
  19. package/dist/assets/chunk-C_YEEQPG.js +1 -1
  20. package/dist/assets/{diagnostics-D9a2Va8f.js → diagnostics-_N4Ejvg9.js} +2 -2
  21. package/dist/assets/{diagnostics-D9a2Va8f.js.map → diagnostics-_N4Ejvg9.js.map} +1 -1
  22. package/dist/assets/index-CRpFYTin.js +3 -0
  23. package/dist/assets/{index-jBrm_r0P.js.map → index-CRpFYTin.js.map} +1 -1
  24. package/dist/assets/{parser-qW4LX6ZD.js → parser-7gIDPQ2Y.js} +2 -2
  25. package/dist/assets/{parser-qW4LX6ZD.js.map → parser-7gIDPQ2Y.js.map} +1 -1
  26. package/dist/assets/{retrace-wasm-CQxBnVuf.js → retrace-wasm-DbXnAc0C.js} +2 -2
  27. package/dist/assets/{retrace-wasm-CQxBnVuf.js.map → retrace-wasm-DbXnAc0C.js.map} +1 -1
  28. package/dist/assets/{sql-wasm-browser-ZghWQdbt.js → sql-wasm-browser-DMblT0GH.js} +2 -2
  29. package/dist/assets/{sql-wasm-browser-ZghWQdbt.js.map → sql-wasm-browser-DMblT0GH.js.map} +1 -1
  30. package/dist/assets/{store-0J0dVS6c.js → store-BhGheFcW.js} +2 -2
  31. package/dist/assets/{store-0J0dVS6c.js.map → store-BhGheFcW.js.map} +1 -1
  32. package/dist/assets/{worker-entry-retrace.worker-C0VtGPAc.js → worker-entry-retrace.worker-DkFpgSJq.js} +2 -2
  33. package/dist/assets/{worker-entry-retrace.worker-C0VtGPAc.js.map → worker-entry-retrace.worker-DkFpgSJq.js.map} +1 -1
  34. package/dist/assets/{worker-entry-sqlite.worker-CNSc3mBW.js → worker-entry-sqlite.worker-DLkyJOuV.js} +2 -2
  35. package/dist/assets/{worker-entry-sqlite.worker-CNSc3mBW.js.map → worker-entry-sqlite.worker-DLkyJOuV.js.map} +1 -1
  36. package/dist/index.html +4 -4
  37. package/package.json +1 -1
  38. package/dist/assets/index-jBrm_r0P.js +0 -3
package/README.md CHANGED
@@ -50,7 +50,7 @@ pnpm preview # 本地预览构建产物
50
50
  pnpm build:mirror # 镜像构建,同时更新前端 package.json 的版本号
51
51
  ```
52
52
 
53
- 镜像构建将大部分资源地址指向对应 npm 版本的 npmmirror 路径,Worker 入口仍使用同源地址。本地构建和预览使用 `pnpm build`。
53
+ 镜像构建将资源地址指向对应 npm 版本的 npmmirror 路径,Worker 使用内联 Data URL 入口导入镜像上的模块。本地构建和预览使用 `pnpm build`。
54
54
 
55
55
  [deploy-inspect.yml](../.github/workflows/deploy-inspect.yml) 在 `main` 分支的前端、共享依赖或该工作流文件变化时触发,也支持手动运行。流程执行镜像构建、发布 `@gkd-kit/inspect` 到 npm,再部署到 Cloudflare Pages 和 Netlify。
56
56
 
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.1792378239741/files/dist/assets/polyfills-CV_-ROyf.js"></script>
4
+ <script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378669665/files/dist/assets/polyfills-CV_-ROyf.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.1792378239741/files/dist/assets/index-jBrm_r0P.js"></script>
16
- <link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378239741/files/dist/assets/preload-helper-xBbMyY7u.js">
17
- <link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378239741/files/dist/assets/globalError-2ZW3trxX.js">
15
+ <script type="module" crossorigin src="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378669665/files/dist/assets/index-CRpFYTin.js"></script>
16
+ <link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378669665/files/dist/assets/preload-helper-xBbMyY7u.js">
17
+ <link rel="modulepreload" crossorigin href="https://registry.npmmirror.com/@gkd-kit/inspect/0.0.1792378669665/files/dist/assets/globalError-2ZW3trxX.js">
18
18
  </head>
19
19
  <body>
20
20
  </body>
@@ -1,4 +1,4 @@
1
- import{i as e}from"./chunk-62oNxeRG.js";import{A as t,F as n,M as r,Tt as i,at as a,d as o,f as s,g as c,h as l,i as u,l as d,lt as f,nt as p,p as m,q as h,rt as ee,u as g,v as _}from"./runtime-core.esm-bundler-BsYqHs64.js";import{an as te,h as v}from"./dist-D4AEbYOr.js";import{t as y}from"./Tooltip-BLwYDMkc.js";import{n as b,t as x}from"./Select-CXtFMPwc.js";import{a as S}from"./light-00IkvuK1.js";import{n as C,t as w}from"./GkSvg-B_QbuxNq.js";import{i as T,n as E}from"./directives-439mcEC_.js";import{r as D}from"./feedback-VDhsffGk.js";import{m as O,t as k}from"./SnapshotActionCard-BxI-R6x8.js";import{s as A}from"./vue-router-DgUnAeT-.js";import{t as j}from"./check-ChjnKVDI.js";import{a as M,i as ne,s as N}from"./async-Hd65-3Yl.js";import{i as P,r as re}from"./chunk-C_YEEQPG.js";import{i as ie,n as ae,t as F}from"./SelectorLibraryDialog-CRkofSgG.js";import{i as I,t as L}from"./storage-DVLaH9mD.js";import{i as oe,n as se,t as ce}from"./useBatchActions-CFvSCMKm.js";import{t as le}from"./PageBackButton-PLtmdVTj.js";var R=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 z(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 R({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 R({code:`LEGACY_ERROR`,message:t.message})}return e}var B=(e,t,n)=>{let r=async(n,r,i)=>{if(!e)throw Error(`origin must exist`);return z(await P(new URL(`/api/`+n,e),{...r,signal:t},i).catch(e=>{throw e instanceof DOMException&&e.name==`AbortError`||D.error(`网络错误:`+n),e})).catch(e=>{throw D.error(e instanceof R?e.path?`${e.path}: ${e.message}`:e.message:`接口错误:${n}:${e instanceof Error?e.message:String(e)}`),e})},i=async(e,t={},n)=>r(e,{method:`post`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)},n),a=async(e,t={})=>i(e,t).then(e=>e.json()),o=async(e,t={})=>i(e,t,e=>(e.responseType=`arraybuffer`,e)).then(e=>e.arrayBuffer());return{getServerInfo:async()=>a(`getServerInfo`),getSnapshot:async e=>n()?.gkdAppInfo?.versionName===`1.10.4`?r(`snapshot?id=`+e.id).then(e=>e.json()):a(`getSnapshot`,e),getScreenshot:async e=>o(`getScreenshot`,e),captureSnapshot:async()=>a(`captureSnapshot`),getSnapshots:async()=>a(`getSnapshots`),updateSubscription:async e=>a(`updateSubscription`,{...e,id:-1,name:`内存订阅`,version:0}),execSelector:async e=>(e=structuredClone(e),e.action||=void 0,a(`execSelector`,e)),deleteSnapshot:async e=>a(`deleteSnapshot`,e)}},ue=()=>{let e=a(),t=B(void 0,new AbortController().signal,()=>void 0);return{origin:d(()=>e.value?.origin),serverInfo:d(()=>e.value?.serverInfo),api:d(()=>e.value?.api??t),prepareConnection:async e=>{let t=new AbortController,n,r=B(e,t.signal,()=>n);try{let[i,a]=await Promise.all([r.getServerInfo(),r.getSnapshots()]);return n=i,{origin:e,serverInfo:i,snapshots:a,api:r,controller:t}}catch(e){throw t.abort(),e}},applyConnection:t=>{e.value?.controller.abort(),e.value=t},disconnect:()=>{e.value?.controller.abort(),e.value=void 0}}},V=e(ae(),1),H={class:`mt-[8px] flex justify-end`},U={class:`flex gap-[10px] items-center`},de=_({__name:`DeviceActionDialogs`,props:{api:{},selectorShow:{type:Boolean},subscriptionShow:{type:Boolean}},emits:[`update:selectorShow`,`update:subscriptionShow`],setup(e,{emit:t}){let r=e,i=t,o=a(``),s=ee({selector:``,action:`click`,quickFind:!1}),d=e=>{o.value=e},p=e=>{s.selector=e},_=e=>{s.quickFind=e},te=e=>{s.action=e},y=a(!1),w=()=>{y.value=!0},T=e=>{y.value=e},E=e=>{p(e)},k=[{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`}],A=N(async()=>{let e=ie(()=>V.default.parse(o.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(`修改成功`)}}),j=N(async()=>{let e=await r.api.execSelector({...s,fastQuery:s.quickFind});if(e.message){D.success(`操作成功:`+e.message);return}e.action?D.success((e.result?`操作成功:`:`操作失败`)+e.action):e.result&&D.success(`查询成功`)});return(t,r)=>(n(),m(u,null,[c(f(S),{show:e.subscriptionShow,preset:`dialog`,style:{width:`800px`},title:`修改内存订阅`,maskClosable:!1,positiveText:`确认`,positiveButtonProps:{loading:f(A).loading.value,onClick:f(A).run},"onUpdate:show":r[0]||=e=>i(`update:subscriptionShow`,e)},{default:h(()=>[c(f(C),{value:o.value,disabled:f(A).loading.value,type:`textarea`,class:`font-mono!`,autosize:{minRows:20,maxRows:25},placeholder:f(`请输入订阅文本(JSON5语法):
1
+ import{i as e}from"./chunk-62oNxeRG.js";import{A as t,F as n,M as r,Tt as i,at as a,d as o,f as s,g as c,h as l,i as u,l as d,lt as f,nt as p,p as m,q as h,rt as ee,u as g,v as _}from"./runtime-core.esm-bundler-BsYqHs64.js";import{an as te,h as v}from"./dist-D4AEbYOr.js";import{t as y}from"./Tooltip-BLwYDMkc.js";import{n as b,t as x}from"./Select-CXtFMPwc.js";import{a as S}from"./light-00IkvuK1.js";import{n as C,t as w}from"./GkSvg-B_QbuxNq.js";import{i as T,n as E}from"./directives-439mcEC_.js";import{r as D}from"./feedback-VDhsffGk.js";import{m as O,t as k}from"./SnapshotActionCard-BxI-R6x8.js";import{s as A}from"./vue-router-DgUnAeT-.js";import{t as j}from"./check-ChjnKVDI.js";import{a as M,i as ne,s as N}from"./async-Hd65-3Yl.js";import{i as P,r as re}from"./chunk-C_YEEQPG.js";import{i as ie,n as ae,t as F}from"./SelectorLibraryDialog-Dkm5zMnZ.js";import{i as I,t as L}from"./storage-DVLaH9mD.js";import{i as oe,n as se,t as ce}from"./useBatchActions-CFvSCMKm.js";import{t as le}from"./PageBackButton-PLtmdVTj.js";var R=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 z(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 R({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 R({code:`LEGACY_ERROR`,message:t.message})}return e}var B=(e,t,n)=>{let r=async(n,r,i)=>{if(!e)throw Error(`origin must exist`);return z(await P(new URL(`/api/`+n,e),{...r,signal:t},i).catch(e=>{throw e instanceof DOMException&&e.name==`AbortError`||D.error(`网络错误:`+n),e})).catch(e=>{throw D.error(e instanceof R?e.path?`${e.path}: ${e.message}`:e.message:`接口错误:${n}:${e instanceof Error?e.message:String(e)}`),e})},i=async(e,t={},n)=>r(e,{method:`post`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)},n),a=async(e,t={})=>i(e,t).then(e=>e.json()),o=async(e,t={})=>i(e,t,e=>(e.responseType=`arraybuffer`,e)).then(e=>e.arrayBuffer());return{getServerInfo:async()=>a(`getServerInfo`),getSnapshot:async e=>n()?.gkdAppInfo?.versionName===`1.10.4`?r(`snapshot?id=`+e.id).then(e=>e.json()):a(`getSnapshot`,e),getScreenshot:async e=>o(`getScreenshot`,e),captureSnapshot:async()=>a(`captureSnapshot`),getSnapshots:async()=>a(`getSnapshots`),updateSubscription:async e=>a(`updateSubscription`,{...e,id:-1,name:`内存订阅`,version:0}),execSelector:async e=>(e=structuredClone(e),e.action||=void 0,a(`execSelector`,e)),deleteSnapshot:async e=>a(`deleteSnapshot`,e)}},ue=()=>{let e=a(),t=B(void 0,new AbortController().signal,()=>void 0);return{origin:d(()=>e.value?.origin),serverInfo:d(()=>e.value?.serverInfo),api:d(()=>e.value?.api??t),prepareConnection:async e=>{let t=new AbortController,n,r=B(e,t.signal,()=>n);try{let[i,a]=await Promise.all([r.getServerInfo(),r.getSnapshots()]);return n=i,{origin:e,serverInfo:i,snapshots:a,api:r,controller:t}}catch(e){throw t.abort(),e}},applyConnection:t=>{e.value?.controller.abort(),e.value=t},disconnect:()=>{e.value?.controller.abort(),e.value=void 0}}},V=e(ae(),1),H={class:`mt-[8px] flex justify-end`},U={class:`flex gap-[10px] items-center`},de=_({__name:`DeviceActionDialogs`,props:{api:{},selectorShow:{type:Boolean},subscriptionShow:{type:Boolean}},emits:[`update:selectorShow`,`update:subscriptionShow`],setup(e,{emit:t}){let r=e,i=t,o=a(``),s=ee({selector:``,action:`click`,quickFind:!1}),d=e=>{o.value=e},p=e=>{s.selector=e},_=e=>{s.quickFind=e},te=e=>{s.action=e},y=a(!1),w=()=>{y.value=!0},T=e=>{y.value=e},E=e=>{p(e)},k=[{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`}],A=N(async()=>{let e=ie(()=>V.default.parse(o.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(`修改成功`)}}),j=N(async()=>{let e=await r.api.execSelector({...s,fastQuery:s.quickFind});if(e.message){D.success(`操作成功:`+e.message);return}e.action?D.success((e.result?`操作成功:`:`操作失败`)+e.action):e.result&&D.success(`查询成功`)});return(t,r)=>(n(),m(u,null,[c(f(S),{show:e.subscriptionShow,preset:`dialog`,style:{width:`800px`},title:`修改内存订阅`,maskClosable:!1,positiveText:`确认`,positiveButtonProps:{loading:f(A).loading.value,onClick:f(A).run},"onUpdate:show":r[0]||=e=>i(`update:subscriptionShow`,e)},{default:h(()=>[c(f(C),{value:o.value,disabled:f(A).loading.value,type:`textarea`,class:`font-mono!`,autosize:{minRows:20,maxRows:25},placeholder:f(`请输入订阅文本(JSON5语法):
2
2
  示例1-更新单个应用的规则:
3
3
  {
4
4
  id: 'appId',
@@ -44,4 +44,4 @@ import{i as e}from"./chunk-62oNxeRG.js";import{A as t,F as n,M as r,Tt as i,at a
44
44
  globalGroups: [],
45
45
  categories: [],
46
46
  }`),"onUpdate:value":d},null,8,[`value`,`disabled`,`placeholder`])]),_:1},8,[`show`,`positiveButtonProps`]),c(f(S),{show:e.selectorShow,preset:`dialog`,style:{width:`800px`},title:`执行选择器`,maskClosable:!1,positiveText:`确认`,positiveButtonProps:{loading:f(j).loading.value,onClick:f(j).run},"onUpdate:show":r[1]||=e=>i(`update:selectorShow`,e)},{default:h(()=>[c(f(C),{value:f(s).selector,disabled:f(j).loading.value,type:`textarea`,class:`font-mono!`,autosize:{minRows:4,maxRows:10},placeholder:`请输入合法的选择器`,"onUpdate:value":p},null,8,[`value`,`disabled`]),g(`div`,H,[c(f(v),{secondary:``,onClick:w},{default:h(()=>[...r[2]||=[l(`选择器库`,-1)]]),_:1})]),r[6]||=g(`div`,{class:`h-[15px]`},null,-1),c(f(O),null,{default:h(()=>[c(f(b),{checked:f(s).quickFind,"onUpdate:checked":_},{default:h(()=>[...r[3]||=[l(` 快速查找 `,-1)]]),_:1},8,[`checked`]),r[4]||=g(`a`,{href:`https://gkd.li/api/interfaces/RawCommonProps.html#quickfind`,target:`_blank`,rel:`noopener noreferrer`},` 查找说明 `,-1)]),_:1}),r[7]||=g(`div`,{class:`h-[10px]`},null,-1),g(`div`,U,[c(f(x),{value:f(s).action,options:k,class:`w-[150px]`,"onUpdate:value":te},null,8,[`value`]),r[5]||=g(`a`,{href:`https://gkd.li/api/interfaces/RawRuleProps#action`,target:`_blank`,rel:`noopener noreferrer`},` 操作说明 `,-1)])]),_:1},8,[`show`,`positiveButtonProps`]),c(F,{show:y.value,onUse:E,"onUpdate:show":T},null,8,[`show`])],64))}}),fe={class:`w-(--gkd-w) h-(--gkd-h) flex flex-col p-[10px] gap-[10px]`},pe={class:`flex items-center gap-[24px]`},me={key:1,class:`font-mono! pl-[16px] whitespace-nowrap flex items-center`},W=_({__name:`DeviceControlView`,setup(e){let _=A(),{api:b,origin:x,serverInfo:S,disconnect:O,prepareConnection:P,applyConnection:ae}=ue(),F=a(localStorage.getItem(`device_link`)||``),R=a([]),z=e=>{F.value=e},B=M(async({commit:e})=>{let t=b.value,n=await t.getSnapshots();t===b.value&&e(()=>{R.value=n.sort((e,t)=>t.id-e.id)})}),V=async()=>{await B.run()},H=M(async({commit:e})=>{if(!F.value)return;let t=ie(()=>new URL(F.value.trim()),()=>`非法设备地址`).origin,n=await P(t);e(()=>{B.cancel(),ae(n),F.value=t,R.value=n.snapshots.sort((e,t)=>t.id-e.id),Y.value=[],localStorage.setItem(`device_link`,t),document.title=U.value})||n.controller.abort()}),U=d(()=>{if(!S.value)return`未连接设备`;let e=S.value.device,t=S.value.gkdAppInfo;return`${e.manufacturer} Android${e.release} - GKD${t.versionName}`});r(()=>{j(F.value)&&H.run()}),t(()=>{H.cancel(),B.cancel(),O()});let W=N(async()=>{let e=b.value,t=await e.captureSnapshot(),n=await e.getScreenshot({id:t.id});await I.setItem(t.id,t),await L.setItem(t.id,n),D.success(`捕获并保存快照成功`),e===b.value&&await V()}),G=N(async()=>{let e=b.value,t=(await e.getSnapshots()).map(e=>e.id),n=new Set((await L.keys()).map(e=>parseInt(e))),r=t.filter(e=>!n.has(e));if(r.length==0){D.success(`没有新记录可导入`);return}let i=0,a=re(3);await Promise.all(r.map(t=>a(async()=>{let[n,r]=await Promise.all([e.getSnapshot({id:t}),e.getScreenshot({id:t})]);n.nodes&&(await Promise.all([I.setItem(t,n),L.setItem(t,r)]),i++)}))),D.success(`导入${i}条新记录`)}),{activityIdCol:he,appIdCol:ge,appNameCol:_e,ctimeCol:K,appVersionCodeCol:ve,appVersionNameCol:ye,resetColWidth:q}=oe(),be=e=>{e.columnKey==K.key&&(K.sortOrder=e.order)},J=ne(async(e,t)=>{let n=b.value;if(!await I.hasItem(t)){let e=await n.getSnapshot({id:t});await I.setItem(t,e)}if(!await L.hasItem(t)){let e=await n.getScreenshot({id:t});await L.setItem(t,e)}window.open(_.resolve({name:`snapshot`,params:{snapshotId:t}}).href)}),xe=[{type:`selection`},K,_e,ge,ve,ye,he,{key:`actions`,title:`操作`,fixed:`right`,width:`160px`,render(e){return c(k,{snapshot:e,showExport:!1,showShare:!1,deleteConfirmText:`是否确认删除? 此操作不可恢复!\n快照ID:${e.id}`,onPreview:()=>J.run(e.id),previewLoading:J.loading.value[e.id],onBeforeDelete:async()=>await b.value.deleteSnapshot({id:e.id}),onDelete:()=>{R.value=R.value.filter(t=>t.id!==e.id)}},null)}}],Y=p([]),{batchDelete:Se}=ce(Y,()=>{let e=b.value;return{isCurrent:()=>e===b.value,beforeDeleteItem:t=>e.deleteSnapshot({id:t}),prepareAfterDelete:async()=>{let t=await e.getSnapshots();return()=>{B.cancel(),R.value=t.sort((e,t)=>t.id-e.id)}}}}),X=ee({page:1,pageSize:50,showSizePicker:!0,pageSizes:[50,100],onChange:e=>{X.page=e,q()},onUpdatePageSize:e=>{X.pageSize=e,X.page=1,q()}}),Z=a(!1),Ce=a(!1),Q=e=>{Z.value=e},$=e=>{Ce.value=e},we=e=>{Y.value=e.filter(e=>typeof e==`number`)},Te=()=>{H.cancel(),B.cancel(),O(),R.value=[],Y.value=[],Q(!1),$(!1),document.title=`连接设备`};return(e,t)=>(n(),m(u,null,[(n(),o(de,{key:f(x)?.toString(),api:f(b),subscriptionShow:Z.value,selectorShow:Ce.value,"onUpdate:subscriptionShow":Q,"onUpdate:selectorShow":$},null,8,[`api`,`subscriptionShow`,`selectorShow`])),g(`div`,fe,[g(`div`,pe,[c(le,{class:`ml-[12px]`}),c(f(T),null,{default:h(()=>[c(f(C),{value:F.value,placeholder:`请输入设备地址`,class:`font-mono!`,style:{width:`240px`},onKeyup:te(f(H).run,[`enter`]),"onUpdate:value":z},{suffix:h(()=>[c(f(y),null,{trigger:h(()=>[c(f(v),{text:``,style:{"--n-icon-size":`20px`},loading:f(H).loading.value,onClick:f(H).run},{icon:h(()=>[c(w,{name:`refresh`})]),_:1},8,[`loading`,`onClick`])]),default:h(()=>[t[2]||=l(` 刷新连接 `,-1)]),_:1})]),_:1},8,[`value`,`onKeyup`]),f(H).loading.value?(n(),o(f(v),{key:0,secondary:``,type:`error`,onClick:Te},{default:h(()=>[...t[3]||=[l(` 停止 `,-1)]]),_:1})):s(``,!0),f(S)?(n(),m(`div`,me,i(U.value),1)):s(``,!0)]),_:1}),f(S)?(n(),m(u,{key:0},[c(se,{checkedCount:Y.value.length,batchDelete:f(Se)},null,8,[`checkedCount`,`batchDelete`]),c(f(y),null,{trigger:h(()=>[c(f(v),{text:``,style:{"--n-icon-size":`24px`},loading:f(W).loading.value,onClick:f(W).run},{icon:h(()=>[c(w,{name:`Snapshot`})]),_:1},8,[`loading`,`onClick`])]),default:h(()=>[t[4]||=l(` 捕获快照 `,-1)]),_:1}),c(f(y),null,{trigger:h(()=>[c(f(v),{text:``,style:{"--n-icon-size":`24px`},loading:f(G).loading.value,onClick:f(G).run},{icon:h(()=>[c(w,{name:`Down-all`})]),_:1},8,[`loading`,`onClick`])]),default:h(()=>[t[5]||=l(` 下载所有快照 `,-1)]),_:1}),c(f(y),null,{trigger:h(()=>[c(f(v),{text:``,style:{"--n-icon-size":`24px`},onClick:t[0]||=e=>Q(!0)},{icon:h(()=>[c(w,{name:`CacheSub`})]),_:1})]),default:h(()=>[t[6]||=l(` 修改内存订阅 `,-1)]),_:1}),c(f(y),null,{trigger:h(()=>[c(f(v),{text:``,style:{"--n-icon-size":`24px`},onClick:t[1]||=e=>$(!0)},{icon:h(()=>[c(w,{name:`Exe-Sel`})]),_:1})]),default:h(()=>[t[7]||=l(` 执行选择器 `,-1)]),_:1})],64)):s(``,!0)]),c(f(E),{checkedRowKeys:Y.value,striped:``,"flex-height":``,data:R.value,columns:xe,pagination:f(X),rowKey:e=>e.id,size:`small`,class:`flex-1`,scrollX:1200,"onUpdate:checkedRowKeys":we,"onUpdate:sorter":be},null,8,[`checkedRowKeys`,`data`,`pagination`,`rowKey`])])],64))}}),G=_({__name:`DevicePage`,setup(e){return(e,t)=>(n(),o(W))}});export{G as default};
47
- //# sourceMappingURL=DevicePage-CtKwtxqK.js.map
47
+ //# sourceMappingURL=DevicePage-Wl0kkEdi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DevicePage-CtKwtxqK.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 { computed, shallowRef } from 'vue';\nimport 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\nconst createDeviceApi = (\n origin: string | undefined,\n signal: AbortSignal,\n getServerInfo: () => ServerInfo | undefined,\n) => {\n const request = async (\n name: string,\n init?: RequestInit,\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n if (!origin) {\n throw new Error(`origin must exist`);\n }\n const u = new URL(`/api/` + name, origin);\n const response = await enhanceFetch(\n u,\n { ...init, 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 (getServerInfo()?.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 api;\n};\n\nexport type DeviceApi = ReturnType<typeof createDeviceApi>;\nexport const useDeviceApi = () => {\n const connection = shallowRef<{\n origin: string;\n serverInfo: ServerInfo;\n api: DeviceApi;\n controller: AbortController;\n }>();\n const disconnectedApi = createDeviceApi(\n undefined,\n new AbortController().signal,\n () => undefined,\n );\n const prepareConnection = async (origin: string) => {\n const controller = new AbortController();\n let serverInfo: ServerInfo | undefined;\n const api = createDeviceApi(origin, controller.signal, () => serverInfo);\n try {\n const [info, snapshots] = await Promise.all([\n api.getServerInfo(),\n api.getSnapshots(),\n ]);\n serverInfo = info;\n return { origin, serverInfo: info, snapshots, api, controller };\n } catch (error) {\n controller.abort();\n throw error;\n }\n };\n const applyConnection = (value: NonNullable<typeof connection.value>) => {\n connection.value?.controller.abort();\n connection.value = value;\n };\n const disconnect = () => {\n connection.value?.controller.abort();\n connection.value = undefined;\n };\n return {\n origin: computed(() => connection.value?.origin),\n serverInfo: computed(() => connection.value?.serverInfo),\n api: computed(() => connection.value?.api ?? disconnectedApi),\n prepareConnection,\n applyConnection,\n disconnect,\n };\n};\n","<script setup lang=\"ts\">\nimport { NButton, NCheckbox, NInput, NModal, NSelect, NSpace } from 'naive-ui';\nimport { shallowReactive, shallowRef } from 'vue';\n\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useSingleTask } from '@/shared/lib/latest_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 = useSingleTask(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 = useSingleTask(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.value,\n onClick: updateSubscription.run,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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.value,\n onClick: executeSelector.run,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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 class=\"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 class=\"h-[10px]\" />\n <div class=\"flex gap-[10px] 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 { NButton, NCheckbox, NInput, NModal, NSelect, NSpace } from 'naive-ui';\nimport { shallowReactive, shallowRef } from 'vue';\n\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useSingleTask } from '@/shared/lib/latest_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 = useSingleTask(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 = useSingleTask(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.value,\n onClick: updateSubscription.run,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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.value,\n onClick: executeSelector.run,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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 class=\"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 class=\"h-[10px]\" />\n <div class=\"flex gap-[10px] 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 { NButton, NDataTable, NInput, NInputGroup, NTooltip } from 'naive-ui';\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowReactive,\n shallowRef,\n} from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useLatestTask } from '@/shared/lib/latest_task';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useKeyedTask, useSingleTask } from '@/shared/lib/latest_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 {\n api,\n origin,\n serverInfo,\n disconnect,\n prepareConnection,\n applyConnection,\n} = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst snapshotsTask = useLatestTask(async ({ commit }) => {\n const currentApi = api.value;\n const result = await currentApi.getSnapshots();\n if (currentApi !== api.value) return;\n commit(() => {\n snapshots.value = result.sort((a, b) => b.id - a.id);\n });\n});\nconst loadSnapshots = async () => {\n await snapshotsTask.run();\n};\nconst connectTask = useLatestTask(async ({ commit }) => {\n if (!link.value) return;\n const address = errorWrap(\n () => new URL(link.value.trim()),\n () => '非法设备地址',\n ).origin;\n const result = await prepareConnection(address);\n if (\n !commit(() => {\n snapshotsTask.cancel();\n applyConnection(result);\n link.value = address;\n snapshots.value = result.snapshots.sort((a, b) => b.id - a.id);\n checkedRowKeys.value = [];\n localStorage.setItem('device_link', address);\n document.title = serverTitle.value;\n })\n )\n result.controller.abort();\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(() => {\n if (toValidURL(link.value)) void connectTask.run();\n});\nonBeforeUnmount(() => {\n connectTask.cancel();\n snapshotsTask.cancel();\n disconnect();\n});\n\nconst captureSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshot = await currentApi.captureSnapshot();\n const screenshot = await currentApi.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n if (currentApi === api.value) await loadSnapshots();\n});\nconst downloadAllSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshotIds = (await currentApi.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 currentApi.getSnapshot({ id: snapshotId }),\n currentApi.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 = useKeyedTask(async (_context, snapshotId: number) => {\n const currentApi = api.value;\n if (!(await snapshotStorage.hasItem(snapshotId))) {\n const obj = await currentApi.getSnapshot({ id: snapshotId });\n await snapshotStorage.setItem(snapshotId, obj);\n }\n if (!(await screenshotStorage.hasItem(snapshotId))) {\n const bf = await currentApi.getScreenshot({ id: snapshotId });\n await screenshotStorage.setItem(snapshotId, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: snapshotId },\n }).href,\n );\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.run(row.id)}\n previewLoading={previewSnapshot.loading.value[row.id]}\n onBeforeDelete={async () =>\n await api.value.deleteSnapshot({ id: row.id })\n }\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 const currentApi = api.value;\n return {\n isCurrent: () => currentApi === api.value,\n beforeDeleteItem: (id) => currentApi.deleteSnapshot({ id }),\n prepareAfterDelete: async () => {\n const result = await currentApi.getSnapshots();\n return () => {\n snapshotsTask.cancel();\n snapshots.value = result.sort((a, b) => b.id - a.id);\n };\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 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 connectTask.cancel();\n snapshotsTask.cancel();\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 class=\"w-(--gkd-w) h-(--gkd-h) flex flex-col p-[10px] gap-[10px]\">\n <div class=\"flex items-center gap-[24px]\">\n <PageBackButton class=\"ml-[12px]\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"font-mono!\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connectTask.run\"\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=\"connectTask.loading.value\"\n @click=\"connectTask.run\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connectTask.loading.value\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n class=\"font-mono! pl-[16px] whitespace-nowrap flex 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.value\"\n @click=\"captureSnapshot.run\"\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.value\"\n @click=\"downloadAllSnapshot.run\"\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 { NButton, NDataTable, NInput, NInputGroup, NTooltip } from 'naive-ui';\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowReactive,\n shallowRef,\n} from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useLatestTask } from '@/shared/lib/latest_task';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useKeyedTask, useSingleTask } from '@/shared/lib/latest_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 {\n api,\n origin,\n serverInfo,\n disconnect,\n prepareConnection,\n applyConnection,\n} = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst snapshotsTask = useLatestTask(async ({ commit }) => {\n const currentApi = api.value;\n const result = await currentApi.getSnapshots();\n if (currentApi !== api.value) return;\n commit(() => {\n snapshots.value = result.sort((a, b) => b.id - a.id);\n });\n});\nconst loadSnapshots = async () => {\n await snapshotsTask.run();\n};\nconst connectTask = useLatestTask(async ({ commit }) => {\n if (!link.value) return;\n const address = errorWrap(\n () => new URL(link.value.trim()),\n () => '非法设备地址',\n ).origin;\n const result = await prepareConnection(address);\n if (\n !commit(() => {\n snapshotsTask.cancel();\n applyConnection(result);\n link.value = address;\n snapshots.value = result.snapshots.sort((a, b) => b.id - a.id);\n checkedRowKeys.value = [];\n localStorage.setItem('device_link', address);\n document.title = serverTitle.value;\n })\n )\n result.controller.abort();\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(() => {\n if (toValidURL(link.value)) void connectTask.run();\n});\nonBeforeUnmount(() => {\n connectTask.cancel();\n snapshotsTask.cancel();\n disconnect();\n});\n\nconst captureSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshot = await currentApi.captureSnapshot();\n const screenshot = await currentApi.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n if (currentApi === api.value) await loadSnapshots();\n});\nconst downloadAllSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshotIds = (await currentApi.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 currentApi.getSnapshot({ id: snapshotId }),\n currentApi.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 = useKeyedTask(async (_context, snapshotId: number) => {\n const currentApi = api.value;\n if (!(await snapshotStorage.hasItem(snapshotId))) {\n const obj = await currentApi.getSnapshot({ id: snapshotId });\n await snapshotStorage.setItem(snapshotId, obj);\n }\n if (!(await screenshotStorage.hasItem(snapshotId))) {\n const bf = await currentApi.getScreenshot({ id: snapshotId });\n await screenshotStorage.setItem(snapshotId, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: snapshotId },\n }).href,\n );\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.run(row.id)}\n previewLoading={previewSnapshot.loading.value[row.id]}\n onBeforeDelete={async () =>\n await api.value.deleteSnapshot({ id: row.id })\n }\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 const currentApi = api.value;\n return {\n isCurrent: () => currentApi === api.value,\n beforeDeleteItem: (id) => currentApi.deleteSnapshot({ id }),\n prepareAfterDelete: async () => {\n const result = await currentApi.getSnapshots();\n return () => {\n snapshotsTask.cancel();\n snapshots.value = result.sort((a, b) => b.id - a.id);\n };\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 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 connectTask.cancel();\n snapshotsTask.cancel();\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 class=\"w-(--gkd-w) h-(--gkd-h) flex flex-col p-[10px] gap-[10px]\">\n <div class=\"flex items-center gap-[24px]\">\n <PageBackButton class=\"ml-[12px]\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"font-mono!\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connectTask.run\"\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=\"connectTask.loading.value\"\n @click=\"connectTask.run\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connectTask.loading.value\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n class=\"font-mono! pl-[16px] whitespace-nowrap flex 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.value\"\n @click=\"captureSnapshot.run\"\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.value\"\n @click=\"downloadAllSnapshot.run\"\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-Wl0kkEdi.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 { computed, shallowRef } from 'vue';\nimport 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\nconst createDeviceApi = (\n origin: string | undefined,\n signal: AbortSignal,\n getServerInfo: () => ServerInfo | undefined,\n) => {\n const request = async (\n name: string,\n init?: RequestInit,\n xhrDetails?: (arg: GmXhrOptions) => GmXhrOptions,\n ) => {\n if (!origin) {\n throw new Error(`origin must exist`);\n }\n const u = new URL(`/api/` + name, origin);\n const response = await enhanceFetch(\n u,\n { ...init, 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 (getServerInfo()?.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 api;\n};\n\nexport type DeviceApi = ReturnType<typeof createDeviceApi>;\nexport const useDeviceApi = () => {\n const connection = shallowRef<{\n origin: string;\n serverInfo: ServerInfo;\n api: DeviceApi;\n controller: AbortController;\n }>();\n const disconnectedApi = createDeviceApi(\n undefined,\n new AbortController().signal,\n () => undefined,\n );\n const prepareConnection = async (origin: string) => {\n const controller = new AbortController();\n let serverInfo: ServerInfo | undefined;\n const api = createDeviceApi(origin, controller.signal, () => serverInfo);\n try {\n const [info, snapshots] = await Promise.all([\n api.getServerInfo(),\n api.getSnapshots(),\n ]);\n serverInfo = info;\n return { origin, serverInfo: info, snapshots, api, controller };\n } catch (error) {\n controller.abort();\n throw error;\n }\n };\n const applyConnection = (value: NonNullable<typeof connection.value>) => {\n connection.value?.controller.abort();\n connection.value = value;\n };\n const disconnect = () => {\n connection.value?.controller.abort();\n connection.value = undefined;\n };\n return {\n origin: computed(() => connection.value?.origin),\n serverInfo: computed(() => connection.value?.serverInfo),\n api: computed(() => connection.value?.api ?? disconnectedApi),\n prepareConnection,\n applyConnection,\n disconnect,\n };\n};\n","<script setup lang=\"ts\">\nimport { NButton, NCheckbox, NInput, NModal, NSelect, NSpace } from 'naive-ui';\nimport { shallowReactive, shallowRef } from 'vue';\n\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useSingleTask } from '@/shared/lib/latest_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 = useSingleTask(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 = useSingleTask(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.value,\n onClick: updateSubscription.run,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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.value,\n onClick: executeSelector.run,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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 class=\"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 class=\"h-[10px]\" />\n <div class=\"flex gap-[10px] 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 { NButton, NCheckbox, NInput, NModal, NSelect, NSpace } from 'naive-ui';\nimport { shallowReactive, shallowRef } from 'vue';\n\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport { useSingleTask } from '@/shared/lib/latest_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 = useSingleTask(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 = useSingleTask(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.value,\n onClick: updateSubscription.run,\n }\"\n @update:show=\"emit('update:subscriptionShow', $event)\"\n >\n <NInput\n :value=\"subscriptionText\"\n :disabled=\"updateSubscription.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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.value,\n onClick: executeSelector.run,\n }\"\n @update:show=\"emit('update:selectorShow', $event)\"\n >\n <NInput\n :value=\"clickAction.selector\"\n :disabled=\"executeSelector.loading.value\"\n type=\"textarea\"\n class=\"font-mono!\"\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 class=\"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 class=\"h-[10px]\" />\n <div class=\"flex gap-[10px] 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 { NButton, NDataTable, NInput, NInputGroup, NTooltip } from 'naive-ui';\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowReactive,\n shallowRef,\n} from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useLatestTask } from '@/shared/lib/latest_task';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useKeyedTask, useSingleTask } from '@/shared/lib/latest_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 {\n api,\n origin,\n serverInfo,\n disconnect,\n prepareConnection,\n applyConnection,\n} = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst snapshotsTask = useLatestTask(async ({ commit }) => {\n const currentApi = api.value;\n const result = await currentApi.getSnapshots();\n if (currentApi !== api.value) return;\n commit(() => {\n snapshots.value = result.sort((a, b) => b.id - a.id);\n });\n});\nconst loadSnapshots = async () => {\n await snapshotsTask.run();\n};\nconst connectTask = useLatestTask(async ({ commit }) => {\n if (!link.value) return;\n const address = errorWrap(\n () => new URL(link.value.trim()),\n () => '非法设备地址',\n ).origin;\n const result = await prepareConnection(address);\n if (\n !commit(() => {\n snapshotsTask.cancel();\n applyConnection(result);\n link.value = address;\n snapshots.value = result.snapshots.sort((a, b) => b.id - a.id);\n checkedRowKeys.value = [];\n localStorage.setItem('device_link', address);\n document.title = serverTitle.value;\n })\n )\n result.controller.abort();\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(() => {\n if (toValidURL(link.value)) void connectTask.run();\n});\nonBeforeUnmount(() => {\n connectTask.cancel();\n snapshotsTask.cancel();\n disconnect();\n});\n\nconst captureSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshot = await currentApi.captureSnapshot();\n const screenshot = await currentApi.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n if (currentApi === api.value) await loadSnapshots();\n});\nconst downloadAllSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshotIds = (await currentApi.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 currentApi.getSnapshot({ id: snapshotId }),\n currentApi.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 = useKeyedTask(async (_context, snapshotId: number) => {\n const currentApi = api.value;\n if (!(await snapshotStorage.hasItem(snapshotId))) {\n const obj = await currentApi.getSnapshot({ id: snapshotId });\n await snapshotStorage.setItem(snapshotId, obj);\n }\n if (!(await screenshotStorage.hasItem(snapshotId))) {\n const bf = await currentApi.getScreenshot({ id: snapshotId });\n await screenshotStorage.setItem(snapshotId, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: snapshotId },\n }).href,\n );\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.run(row.id)}\n previewLoading={previewSnapshot.loading.value[row.id]}\n onBeforeDelete={async () =>\n await api.value.deleteSnapshot({ id: row.id })\n }\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 const currentApi = api.value;\n return {\n isCurrent: () => currentApi === api.value,\n beforeDeleteItem: (id) => currentApi.deleteSnapshot({ id }),\n prepareAfterDelete: async () => {\n const result = await currentApi.getSnapshots();\n return () => {\n snapshotsTask.cancel();\n snapshots.value = result.sort((a, b) => b.id - a.id);\n };\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 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 connectTask.cancel();\n snapshotsTask.cancel();\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 class=\"w-(--gkd-w) h-(--gkd-h) flex flex-col p-[10px] gap-[10px]\">\n <div class=\"flex items-center gap-[24px]\">\n <PageBackButton class=\"ml-[12px]\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"font-mono!\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connectTask.run\"\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=\"connectTask.loading.value\"\n @click=\"connectTask.run\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connectTask.loading.value\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n class=\"font-mono! pl-[16px] whitespace-nowrap flex 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.value\"\n @click=\"captureSnapshot.run\"\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.value\"\n @click=\"downloadAllSnapshot.run\"\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 { NButton, NDataTable, NInput, NInputGroup, NTooltip } from 'naive-ui';\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowReactive,\n shallowRef,\n} from 'vue';\nimport { useRouter } from 'vue-router';\n\nimport type { Snapshot } from '@/entities/snapshot/types';\n\nimport GkSvg from '@/shared/ui/GkSvg.vue';\nimport { useLatestTask } from '@/shared/lib/latest_task';\nimport { useDeviceApi } from './api';\nimport { toValidURL } from '@/shared/lib/check';\nimport { message } from '@/shared/services/feedback';\nimport { errorWrap } from '@/shared/services/error';\nimport {\n screenshotStorage,\n snapshotStorage,\n} from '@/entities/snapshot/storage';\nimport { useSnapshotColumns } from '@/entities/snapshot/ui/useSnapshotColumns';\nimport { useKeyedTask, useSingleTask } from '@/shared/lib/latest_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 {\n api,\n origin,\n serverInfo,\n disconnect,\n prepareConnection,\n applyConnection,\n} = useDeviceApi();\nconst link = shallowRef(localStorage.getItem(`device_link`) || ``);\nconst snapshots = shallowRef<Snapshot[]>([]);\nconst updateLink = (value: string) => {\n link.value = value;\n};\nconst snapshotsTask = useLatestTask(async ({ commit }) => {\n const currentApi = api.value;\n const result = await currentApi.getSnapshots();\n if (currentApi !== api.value) return;\n commit(() => {\n snapshots.value = result.sort((a, b) => b.id - a.id);\n });\n});\nconst loadSnapshots = async () => {\n await snapshotsTask.run();\n};\nconst connectTask = useLatestTask(async ({ commit }) => {\n if (!link.value) return;\n const address = errorWrap(\n () => new URL(link.value.trim()),\n () => '非法设备地址',\n ).origin;\n const result = await prepareConnection(address);\n if (\n !commit(() => {\n snapshotsTask.cancel();\n applyConnection(result);\n link.value = address;\n snapshots.value = result.snapshots.sort((a, b) => b.id - a.id);\n checkedRowKeys.value = [];\n localStorage.setItem('device_link', address);\n document.title = serverTitle.value;\n })\n )\n result.controller.abort();\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(() => {\n if (toValidURL(link.value)) void connectTask.run();\n});\nonBeforeUnmount(() => {\n connectTask.cancel();\n snapshotsTask.cancel();\n disconnect();\n});\n\nconst captureSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshot = await currentApi.captureSnapshot();\n const screenshot = await currentApi.getScreenshot({ id: snapshot.id });\n await snapshotStorage.setItem(snapshot.id, snapshot);\n await screenshotStorage.setItem(snapshot.id, screenshot);\n message.success(`捕获并保存快照成功`);\n if (currentApi === api.value) await loadSnapshots();\n});\nconst downloadAllSnapshot = useSingleTask(async () => {\n const currentApi = api.value;\n const snapshotIds = (await currentApi.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 currentApi.getSnapshot({ id: snapshotId }),\n currentApi.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 = useKeyedTask(async (_context, snapshotId: number) => {\n const currentApi = api.value;\n if (!(await snapshotStorage.hasItem(snapshotId))) {\n const obj = await currentApi.getSnapshot({ id: snapshotId });\n await snapshotStorage.setItem(snapshotId, obj);\n }\n if (!(await screenshotStorage.hasItem(snapshotId))) {\n const bf = await currentApi.getScreenshot({ id: snapshotId });\n await screenshotStorage.setItem(snapshotId, bf);\n }\n window.open(\n router.resolve({\n name: 'snapshot',\n params: { snapshotId: snapshotId },\n }).href,\n );\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.run(row.id)}\n previewLoading={previewSnapshot.loading.value[row.id]}\n onBeforeDelete={async () =>\n await api.value.deleteSnapshot({ id: row.id })\n }\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 const currentApi = api.value;\n return {\n isCurrent: () => currentApi === api.value,\n beforeDeleteItem: (id) => currentApi.deleteSnapshot({ id }),\n prepareAfterDelete: async () => {\n const result = await currentApi.getSnapshots();\n return () => {\n snapshotsTask.cancel();\n snapshots.value = result.sort((a, b) => b.id - a.id);\n };\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 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 connectTask.cancel();\n snapshotsTask.cancel();\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 class=\"w-(--gkd-w) h-(--gkd-h) flex flex-col p-[10px] gap-[10px]\">\n <div class=\"flex items-center gap-[24px]\">\n <PageBackButton class=\"ml-[12px]\" />\n <NInputGroup>\n <NInput\n :value=\"link\"\n placeholder=\"请输入设备地址\"\n class=\"font-mono!\"\n :style=\"{ width: `240px` }\"\n @keyup.enter=\"connectTask.run\"\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=\"connectTask.loading.value\"\n @click=\"connectTask.run\"\n >\n <template #icon><GkSvg name=\"refresh\" /></template>\n </NButton>\n </template>\n 刷新连接\n </NTooltip>\n </template>\n </NInput>\n <NButton\n v-if=\"connectTask.loading.value\"\n secondary\n type=\"error\"\n @click=\"stopConnection\"\n >\n 停止\n </NButton>\n\n <div\n v-if=\"serverInfo\"\n class=\"font-mono! pl-[16px] whitespace-nowrap flex 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.value\"\n @click=\"captureSnapshot.run\"\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.value\"\n @click=\"downloadAllSnapshot.run\"\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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"}