diff --git a/components.d.ts b/components.d.ts index d9f551d..467ebfe 100644 --- a/components.d.ts +++ b/components.d.ts @@ -13,6 +13,8 @@ declare module 'vue' { ElCol: typeof import('element-plus/es')['ElCol'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] + ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] + ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] diff --git a/package.json b/package.json index b8aa035..2b6e6d0 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "electron-in-page-search": "^1.3.2", + "nanoid": "^5.0.7", "vue": "^3.4.26" }, "devDependencies": { diff --git a/src/App.vue b/src/App.vue index 5c388f5..9ee7327 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,29 +1,31 @@ diff --git a/src/components/Device/components/BatchActions/FileManage/index.vue b/src/components/Device/components/BatchActions/FileManage/index.vue index a0a3a45..2f4aee1 100644 --- a/src/components/Device/components/BatchActions/FileManage/index.vue +++ b/src/components/Device/components/BatchActions/FileManage/index.vue @@ -2,7 +2,6 @@
-
diff --git a/src/components/Device/components/BatchActions/Screenshot/index.vue b/src/components/Device/components/BatchActions/Screenshot/index.vue index 1c5af49..b0713f4 100644 --- a/src/components/Device/components/BatchActions/Screenshot/index.vue +++ b/src/components/Device/components/BatchActions/Screenshot/index.vue @@ -1,51 +1,31 @@ - diff --git a/src/components/Device/components/BatchActions/Shell/index.vue b/src/components/Device/components/BatchActions/Shell/index.vue index de1cbef..f130d31 100644 --- a/src/components/Device/components/BatchActions/Shell/index.vue +++ b/src/components/Device/components/BatchActions/Shell/index.vue @@ -5,9 +5,8 @@ diff --git a/src/components/Device/components/BatchActions/Tasks/components/TaskDialog/index.vue b/src/components/Device/components/BatchActions/Tasks/components/TaskDialog/index.vue new file mode 100644 index 0000000..29dadb3 --- /dev/null +++ b/src/components/Device/components/BatchActions/Tasks/components/TaskDialog/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/src/components/Device/components/BatchActions/Tasks/index.vue b/src/components/Device/components/BatchActions/Tasks/index.vue index 8d18015..2976ff9 100644 --- a/src/components/Device/components/BatchActions/Tasks/index.vue +++ b/src/components/Device/components/BatchActions/Tasks/index.vue @@ -1,10 +1,14 @@ diff --git a/src/components/Device/components/ControlBar/FileManage/index.vue b/src/components/Device/components/ControlBar/FileManage/index.vue index 0e54ecf..57bb459 100644 --- a/src/components/Device/components/ControlBar/FileManage/index.vue +++ b/src/components/Device/components/ControlBar/FileManage/index.vue @@ -1,7 +1,7 @@ diff --git a/src/components/Device/components/ControlBar/Tasks/components/TaskDialog/index.vue b/src/components/Device/components/ControlBar/Tasks/components/TaskDialog/index.vue deleted file mode 100644 index 28da8c0..0000000 --- a/src/components/Device/components/ControlBar/Tasks/components/TaskDialog/index.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/src/components/Device/components/ControlBar/Tasks/index.vue b/src/components/Device/components/ControlBar/Tasks/index.vue index 395c198..820e301 100644 --- a/src/components/Device/components/ControlBar/Tasks/index.vue +++ b/src/components/Device/components/ControlBar/Tasks/index.vue @@ -7,12 +7,12 @@ diff --git a/src/components/Device/components/TerminalAction/components/TerminalDialog/index.vue b/src/components/Device/components/TerminalAction/components/TerminalDialog/index.vue index d7028cd..abb85c0 100644 --- a/src/components/Device/components/TerminalAction/components/TerminalDialog/index.vue +++ b/src/components/Device/components/TerminalAction/components/TerminalDialog/index.vue @@ -48,9 +48,10 @@ import { useAdb } from './composables/adb-async.js' import { useScrcpy } from './composables/scrcpy.js' import { useGnirehtet } from './composables/gnirehtet.js' import { sleep } from '$/utils/index.js' -import { useThemeStore } from '$/store/index.js' +import { useTaskStore, useThemeStore } from '$/store/index.js' const themeStore = useThemeStore() +const taskStore = useTaskStore() const loading = ref(false) const visible = ref(false) @@ -166,6 +167,10 @@ function onClosed() { history.value = [createQuery()] } +taskStore.on('terminal', (task) => { + invoke(task.command) +}) + defineExpose({ open, close, diff --git a/src/components/Device/index.vue b/src/components/Device/index.vue index b2656aa..66d6573 100644 --- a/src/components/Device/index.vue +++ b/src/components/Device/index.vue @@ -150,11 +150,6 @@ export default { WirelessAction, BatchActions, }, - provide() { - return { - invokeTerminal: (...args) => this.$refs.terminalActionRef.invoke(...args), - } - }, data() { return { loading: false, diff --git a/src/components/Preference/components/PreferenceForm/components/InputPath/index.vue b/src/components/Preference/components/PreferenceForm/components/InputPath/index.vue index 3771acd..6288ea8 100644 --- a/src/components/Preference/components/PreferenceForm/components/InputPath/index.vue +++ b/src/components/Preference/components/PreferenceForm/components/InputPath/index.vue @@ -70,7 +70,7 @@ export default { }, ) - const value = files[0] + const value = files.join(',') this.pathValue = value } diff --git a/src/composables/useFileActions/index.js b/src/composables/useFileActions/index.js new file mode 100644 index 0000000..174ab60 --- /dev/null +++ b/src/composables/useFileActions/index.js @@ -0,0 +1,149 @@ +import { ElMessage } from 'element-plus' +import { allSettledWrapper } from '$/utils' +import { useDeviceStore } from '$/store' + +export function useFileActions() { + const deviceStore = useDeviceStore() + + const loading = ref(false) + + function send(...args) { + const [devices] = args + + if (Array.isArray(devices)) { + return multipleSend(...args) + } + + return singleSend(...args) + } + + async function selectFiles() { + try { + const files = await window.electron.ipcRenderer.invoke( + 'show-open-dialog', + { + properties: ['openFile', 'multiSelections'], + filters: [ + { + name: window.t('device.control.file.push.placeholder'), + extensions: ['*'], + }, + ], + }, + ) + + return files + } + catch (error) { + const message = error.message?.match(/Error: (.*)/)?.[1] || error.message + + throw new Error(message) + } + } + + async function singleSend(device, { files, silent = false } = {}) { + if (!files) { + try { + files = await selectFiles() + } + catch (error) { + ElMessage.warning(error.message) + + return false + } + } + + loading.value = true + + let closeLoading + + if (!silent) { + closeLoading = ElMessage.loading( + `${deviceStore.getLabel(device)}: ${window.t( + 'device.control.file.push.loading', + )}`, + ).close + } + + let failCount = 0 + + await allSettledWrapper(files, (item) => { + return window.adbkit.push(device.id, item).catch(() => { + ++failCount + }) + }) + + loading.value = false + + if (silent) { + return false + } + + const totalCount = files.length + const successCount = totalCount - failCount + + if (successCount) { + closeLoading() + + if (totalCount > 1) { + ElMessage.success( + window.t('device.control.file.push.success', { + deviceName: deviceStore.getLabel(device), + totalCount, + successCount, + failCount, + }), + ) + } + else { + ElMessage.success( + window.t('device.control.file.push.success.single', { + deviceName: deviceStore.getLabel(device), + }), + ) + } + + return false + } + + closeLoading() + ElMessage.warning(window.t('device.control.file.push.error')) + } + + async function multipleSend(devices, { files } = {}) { + if (!files) { + try { + files = await selectFiles() + } + catch (error) { + ElMessage.warning(error.message) + + return false + } + } + + loading.value = true + + const closeMessage = ElMessage.loading( + window.t('device.control.file.push.loading'), + ).close + + await allSettledWrapper(devices, (item) => { + return singleSend(item, { files, silent: true }) + }) + + closeMessage() + + ElMessage.success(window.t('common.success.batch')) + + loading.value = false + } + + return { + loading, + send, + selectFiles, + singleSend, + multipleSend, + } +} diff --git a/src/composables/useInstallAction/index.js b/src/composables/useInstallAction/index.js new file mode 100644 index 0000000..ba5bdbc --- /dev/null +++ b/src/composables/useInstallAction/index.js @@ -0,0 +1,150 @@ +import { ElMessage } from 'element-plus' + +import { allSettledWrapper } from '$/utils' + +import { useDeviceStore } from '$/store' + +export function useInstallAction() { + const deviceStore = useDeviceStore() + + const loading = ref(false) + + function invoke(...args) { + const [devices] = args + + if (Array.isArray(devices)) { + return multipleInvoke(...args) + } + + return singleInvoke(...args) + } + + async function selectFiles() { + try { + const files = await window.electron.ipcRenderer.invoke( + 'show-open-dialog', + { + properties: ['openFile', 'multiSelections'], + filters: [ + { + name: window.t('device.control.install.placeholder'), + extensions: ['apk'], + }, + ], + }, + ) + + return files + } + catch (error) { + const message = error.message?.match(/Error: (.*)/)?.[1] || error.message + throw new Error(message) + } + } + + async function singleInvoke(device, { files, silent = false } = {}) { + if (!files) { + try { + files = await selectFiles() + } + catch (error) { + ElMessage.warning(error.message) + return false + } + } + + let closeLoading = null + if (!silent) { + closeLoading = ElMessage.loading( + window.t('device.control.install.progress', { + deviceName: deviceStore.getLabel(device), + }), + ).close + } + + let failCount = 0 + + await allSettledWrapper(files, (item) => { + return window.adbkit.install(device.id, item).catch((e) => { + console.warn(e) + ++failCount + }) + }) + + if (silent) { + return false + } + + closeLoading() + + const totalCount = files.length + const successCount = totalCount - failCount + + if (successCount) { + if (totalCount > 1) { + ElMessage.success( + window.t('device.control.install.success', { + deviceName: deviceStore.getLabel(device), + totalCount, + successCount, + failCount, + }), + ) + } + else { + ElMessage.success( + window.t('device.control.install.success.single', { + deviceName: deviceStore.getLabel(device), + }), + ) + } + return false + } + + ElMessage.warning(window.t('device.control.install.error')) + } + + async function multipleInvoke(devices, { files } = {}) { + if (!files) { + try { + files = await selectFiles() + } + catch (error) { + ElMessage.warning(error.message) + return false + } + } + + loading.value = true + + const closeMessage = ElMessage.loading( + window.t('device.control.install.progress', { + deviceName: window.t('common.device'), + }), + ).close + + await allSettledWrapper(devices, (item) => { + return singleInvoke(item, { + files, + silent: true, + }) + }) + + closeMessage() + + ElMessage.success(window.t('common.success.batch')) + + loading.value = false + } + + return { + invoke, + loading, + deviceStore, + selectFiles, + multipleInvoke, + singleInvoke, + } +} + +export default useInstallAction diff --git a/src/composables/useScreenshotAction/index.js b/src/composables/useScreenshotAction/index.js new file mode 100644 index 0000000..8f5cbcf --- /dev/null +++ b/src/composables/useScreenshotAction/index.js @@ -0,0 +1,90 @@ +import { ElMessage } from 'element-plus' + +import { allSettledWrapper } from '$/utils/index.js' + +import { useDeviceStore, usePreferenceStore } from '$/store' + +export function useScreenshotAction() { + const deviceStore = useDeviceStore() + const preferenceStore = usePreferenceStore() + + const loading = ref(false) + + function invoke(...args) { + const [devices] = args + + if (Array.isArray(devices)) { + return multipleInvoke(...args) + } + + return singleInvoke(...args) + } + + async function singleInvoke(device, { silent = false } = {}) { + let closeLoading + + if (!silent) { + closeLoading = ElMessage.loading( + window.t('device.control.capture.progress', { + deviceName: deviceStore.getLabel(device), + }), + ).close + } + + const fileName = deviceStore.getLabel( + device, + ({ time }) => `screenshot-${time}.jpg`, + ) + + const deviceConfig = preferenceStore.getData(device.id) + const savePath = window.nodePath.resolve(deviceConfig.savePath, fileName) + + try { + await window.adbkit.screencap(device.id, { savePath }) + } + catch (error) { + if (error.message) { + ElMessage.warning(error.message) + } + return false + } + + if (silent) { + return false + } + + closeLoading() + + ElMessage.success( + `${window.t('device.control.capture.success.message.title')}: ${savePath}`, + ) + } + async function multipleInvoke(devices) { + loading.value = true + + const closeMessage = ElMessage.loading( + window.t('device.control.capture.progress', { + deviceName: window.t('common.device'), + }), + ).close + + await allSettledWrapper(devices, (item) => { + return singleInvoke(item, { silent: true }) + }) + + closeMessage() + + ElMessage.success(window.t('common.success.batch')) + + loading.value = false + } + + return { + invoke, + loading, + singleInvoke, + multipleInvoke, + } +} + +export default useScreenshotAction diff --git a/src/composables/useShellAction/index.js b/src/composables/useShellAction/index.js new file mode 100644 index 0000000..d7645f7 --- /dev/null +++ b/src/composables/useShellAction/index.js @@ -0,0 +1,124 @@ +import { ElMessage, ElMessageBox } from 'element-plus' + +import { allSettledWrapper } from '$/utils/index.js' +import { selectAndSendFileToDevice } from '$/utils/device/index.js' +import { useTaskStore } from '$/store' + +export function useShellAction() { + const taskStore = useTaskStore() + + const loading = ref(false) + + async function invoke(...args) { + const [devices] = args + + if (Array.isArray(devices)) { + return multipleInvoke(...args) + } + + return singleInvoke(...args) + } + + async function singleInvoke(device, { files } = {}) { + if (!files) { + try { + files = await selectAndSendFileToDevice(device.id, { + extensions: ['sh'], + selectText: window.t('device.control.shell.select'), + loadingText: window.t('device.control.shell.push.loading'), + successText: window.t('device.control.shell.push.success'), + }) + } + catch (error) { + loading.value = false + ElMessage.warning(error.message) + return false + } + } + + loading.value = true + + const filePath = files[0] + + const command = `adb -s ${device.id} shell sh ${filePath}` + + taskStore.emit('terminal', { command }) + + loading.value = false + } + + async function multipleInvoke(devices, { files } = {}) { + if (!files) { + try { + files = await window.electron.ipcRenderer.invoke('show-open-dialog', { + properties: ['openFile'], + filters: [ + { + name: window.t('device.control.shell.select'), + extensions: ['sh'], + }, + ], + }) + } + catch (error) { + if (error.message) { + const message = error.message?.match(/Error: (.*)/)?.[1] + ElMessage.warning(message || error.message) + } + return false + } + } + + loading.value = true + + const closeLoading = ElMessage.loading( + window.t('device.control.shell.push.loading'), + ).close + + const failFiles = [] + + await allSettledWrapper(devices, async (device) => { + const successFiles = await selectAndSendFileToDevice(device.id, { + files, + silent: true, + }).catch((e) => { + console.warn(e.message) + failFiles.push(e.message) + }) + + const filePath = successFiles?.[0] + + if (filePath) { + window.adbkit.deviceShell(device.id, `sh ${filePath}`) + } + }) + + if (failFiles.length) { + ElMessageBox.alert( + `
${failFiles.map(text => `${text}
`).join('')}
`, + window.t('common.tips'), + { + type: 'warning', + dangerouslyUseHTMLString: true, + }, + ) + loading.value = false + return false + } + + closeLoading() + + await ElMessage.success(window.t('device.control.shell.success')) + + loading.value = false + } + + return { + invoke, + loading, + singleInvoke, + multipleInvoke, + } +} + +export default useShellAction diff --git a/src/dicts/index.js b/src/dicts/index.js new file mode 100644 index 0000000..b540e0b --- /dev/null +++ b/src/dicts/index.js @@ -0,0 +1 @@ +export * from './tasks/index' diff --git a/src/dicts/tasks/index.js b/src/dicts/tasks/index.js new file mode 100644 index 0000000..5c00ef1 --- /dev/null +++ b/src/dicts/tasks/index.js @@ -0,0 +1,41 @@ +export const timerType = [ + { + label: '单次执行', + value: 'timeout', + }, + { + label: '周期重复', + value: 'interval', + }, +] + +export const timeUnit = [ + { + label: '月', + value: 'month', + }, + { + label: '周', + value: 'week', + }, + { + label: '天', + value: 'day', + }, + { + label: '小时', + value: 'hour', + }, + { + label: '分钟', + value: 'minute', + }, + { + label: '秒', + value: 'second', + }, + { + label: '毫秒', + value: 'millisecond', + }, +] diff --git a/src/plugins/element-plus/components/EleFormRow/index.vue b/src/plugins/element-plus/components/EleFormRow/index.vue index 572f59d..319496f 100644 --- a/src/plugins/element-plus/components/EleFormRow/index.vue +++ b/src/plugins/element-plus/components/EleFormRow/index.vue @@ -1,5 +1,5 @@