mirror of
https://github.com/viarotel-org/escrcpy.git
synced 2025-02-17 10:58:38 +01:00
feat: ✨ Support graphic file manager
This commit is contained in:
parent
94ee0070ef
commit
815572303a
@ -214,10 +214,9 @@ Windows 及 Linux 端内部集成了 Gnirehtet, 用于提供 PC 到安卓设
|
||||
19. 灵活启动镜像 ✅
|
||||
20. 批量处理 ✅
|
||||
21. 计划任务 ✅
|
||||
22. 对设备进行分组 🚧
|
||||
23. 文件传输助手 🚧
|
||||
24. 通过界面管理设备文件 🚧
|
||||
25. 游戏键位映射 🚧
|
||||
22. 图形化文件管理器 ✅
|
||||
23. 对设备进行分组 🚧
|
||||
24. 游戏键位映射 🚧
|
||||
|
||||
## 常见问题
|
||||
|
||||
|
@ -212,10 +212,9 @@ Refer to [scrcpy/doc/shortcuts](https://github.com/Genymobile/scrcpy/blob/master
|
||||
19. Flexible mirroring launch ✅
|
||||
20. Batch processing ✅
|
||||
21. Scheduled tasks ✅
|
||||
22. Device grouping 🚧
|
||||
23. File transfer assistant 🚧
|
||||
24. Manage device files via interface 🚧
|
||||
25. Game key mapping 🚧
|
||||
22. Graphical file manager ✅
|
||||
23. Device grouping 🚧
|
||||
24. Game key mapping 🚧
|
||||
|
||||
## FAQ
|
||||
|
||||
|
@ -187,28 +187,6 @@ const clearOverlayDisplayDevices = async (deviceId) => {
|
||||
)
|
||||
}
|
||||
|
||||
const push = async (
|
||||
id,
|
||||
filePath,
|
||||
{ progress, savePath = `/sdcard/Download/${path.basename(filePath)}` } = {},
|
||||
) => {
|
||||
const res = await client.getDevice(id).push(filePath, savePath)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
res.on('progress', (stats) => {
|
||||
progress?.(stats)
|
||||
})
|
||||
|
||||
res.on('end', () => {
|
||||
resolve(savePath)
|
||||
})
|
||||
|
||||
res.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const watch = async (callback) => {
|
||||
const tracker = await client.trackDevices()
|
||||
tracker.on('add', async (ret) => {
|
||||
@ -233,11 +211,12 @@ const watch = async (callback) => {
|
||||
return close
|
||||
}
|
||||
|
||||
async function getFiles(id, path) {
|
||||
const value = await client.getDevice(id).readdir(path)
|
||||
async function readdir(id, filePath) {
|
||||
const value = await client.getDevice(id).readdir(filePath)
|
||||
|
||||
return value.map(item => ({
|
||||
...item,
|
||||
id: [filePath, item.name].join('/'),
|
||||
type: item.isFile() ? 'file' : 'directory',
|
||||
name: item.name,
|
||||
size: formatFileSize(item.size),
|
||||
@ -245,6 +224,50 @@ async function getFiles(id, path) {
|
||||
}))
|
||||
}
|
||||
|
||||
async function push(id, filePath, args = {}) {
|
||||
const { progress, savePath = `/sdcard/Download/${path.basename(filePath)}` }
|
||||
= args
|
||||
|
||||
const transfer = await client.getDevice(id).push(filePath, savePath)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
transfer.on('progress', (stats) => {
|
||||
progress?.(stats)
|
||||
})
|
||||
|
||||
transfer.on('end', () => {
|
||||
resolve(savePath)
|
||||
})
|
||||
|
||||
transfer.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function pull(id, filePath, args = {}) {
|
||||
const { progress, savePath = path.resolve('../', path.basename(filePath)) }
|
||||
= args
|
||||
|
||||
const transfer = await client.getDevice(id).pull(filePath)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
transfer.on('progress', (stats) => {
|
||||
progress?.(stats)
|
||||
})
|
||||
|
||||
transfer.on('end', () => {
|
||||
resolve(savePath)
|
||||
})
|
||||
|
||||
transfer.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
|
||||
transfer.pipe(fs.createWriteStream(savePath))
|
||||
})
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const binPath = appStore.get('common.adbPath') || adbPath
|
||||
|
||||
@ -269,7 +292,8 @@ export default () => {
|
||||
display,
|
||||
clearOverlayDisplayDevices,
|
||||
push,
|
||||
pull,
|
||||
watch,
|
||||
getFiles,
|
||||
readdir,
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
placement="bottom-start"
|
||||
:width="300"
|
||||
trigger="click"
|
||||
@hide="onHide"
|
||||
>
|
||||
<template #reference>
|
||||
<slot name="reference"></slot>
|
||||
</template>
|
||||
<div class="flex items-center space-x-2">
|
||||
<el-input
|
||||
v-model="dirname"
|
||||
:placeholder="$t('common.input.placeholder')"
|
||||
clearable
|
||||
class="flex-1 w-0"
|
||||
></el-input>
|
||||
|
||||
<el-button type="primary" class="flex-none" @click="handleConfirm">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
const defaultText = 'NewFolder'
|
||||
|
||||
const dirname = ref(defaultText)
|
||||
|
||||
const popoverRef = ref()
|
||||
|
||||
function onHide() {
|
||||
dirname.value = defaultText
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
emit('success', dirname.value)
|
||||
popoverRef.value.hide()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
v-model="dialog.visible"
|
||||
:title="$t('device.control.file.name')"
|
||||
width="97%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="el-dialog--beautify"
|
||||
@closed="onClosed"
|
||||
>
|
||||
@ -40,88 +41,122 @@
|
||||
</el-breadcrumb>
|
||||
|
||||
<div class="ml-auto">
|
||||
<el-button text icon="Refresh" circle></el-button>
|
||||
<el-button text icon="Refresh" circle @click="getTableData"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 -ml-px">
|
||||
<el-button type="default" icon="FolderAdd">
|
||||
新建文件夹
|
||||
<el-button-group class="mb-4 -ml-px">
|
||||
<AddPopover @success="handleAdd">
|
||||
<template #reference>
|
||||
<el-button type="default" icon="FolderAdd">
|
||||
{{ $t('device.control.file.manager.add') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</AddPopover>
|
||||
|
||||
<el-button
|
||||
type="default"
|
||||
icon="DocumentAdd"
|
||||
v-bind="{ loading: fileActions.loading }"
|
||||
@click="handleUpload(device)"
|
||||
>
|
||||
{{ $t('device.control.file.manager.upload') }}
|
||||
</el-button>
|
||||
<el-button type="default" icon="DocumentAdd">
|
||||
上传文件
|
||||
<el-button
|
||||
type="default"
|
||||
icon="Download"
|
||||
:disabled="!selectionRows.length"
|
||||
@click="handleDownload()"
|
||||
>
|
||||
{{ $t('device.control.file.manager.download') }}
|
||||
</el-button>
|
||||
<el-button type="default" icon="Download">
|
||||
下载文件
|
||||
</el-button>
|
||||
</div>
|
||||
</el-button-group>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
stripe
|
||||
size="small"
|
||||
row-key="id"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
reserve-selection
|
||||
width="50"
|
||||
align="left"
|
||||
:selectable="(row) => ['file'].includes(row.type)"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column prop="name" label="名称" sortable>
|
||||
<el-table-column prop="name" :label="$t('common.name')" sortable>
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center">
|
||||
<el-button
|
||||
<el-link
|
||||
v-if="row.type === 'directory'"
|
||||
text
|
||||
type="default"
|
||||
icon="Folder"
|
||||
class="!p-0 !bg-transparent"
|
||||
class="!space-x-2"
|
||||
@click="handleDirectory(row)"
|
||||
>
|
||||
{{ row.name }}
|
||||
</el-button>
|
||||
<el-button
|
||||
</el-link>
|
||||
<el-link
|
||||
v-else
|
||||
text
|
||||
type="default"
|
||||
icon="Document"
|
||||
class="!p-0 !bg-transparent"
|
||||
@click="handleFile(row)"
|
||||
class="!space-x-2"
|
||||
@click="handleDownload(row)"
|
||||
>
|
||||
{{ row.name }}
|
||||
</el-button>
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="size"
|
||||
label="大小"
|
||||
:label="$t('common.size')"
|
||||
sortable
|
||||
align="center"
|
||||
></el-table-column>
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
prop="updateTime"
|
||||
label="修改时间"
|
||||
:label="$t('time.update')"
|
||||
sortable
|
||||
align="center"
|
||||
></el-table-column>
|
||||
>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" align="center">
|
||||
<el-table-column :label="$t('device.control.name')" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="">
|
||||
<EleTooltipButton
|
||||
v-if="['file'].includes(row.type)"
|
||||
effect="light"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
:content="$t('common.download')"
|
||||
text
|
||||
type="primary"
|
||||
icon="Download"
|
||||
circle
|
||||
@click="handleFile(row)"
|
||||
>
|
||||
</EleTooltipButton>
|
||||
</div>
|
||||
<EleTooltipButton
|
||||
v-if="['file'].includes(row.type)"
|
||||
effect="light"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
:content="$t('common.download')"
|
||||
text
|
||||
type="primary"
|
||||
icon="Download"
|
||||
circle
|
||||
@click="handleDownload(row)"
|
||||
>
|
||||
</EleTooltipButton>
|
||||
|
||||
<EleTooltipButton
|
||||
effect="light"
|
||||
placement="top"
|
||||
:offset="2"
|
||||
:content="$t('common.delete')"
|
||||
text
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
circle
|
||||
@click="handleRemove(row)"
|
||||
>
|
||||
</EleTooltipButton>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -131,7 +166,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const visible = ref(false)
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import AddPopover from './AddPopover/index.vue'
|
||||
|
||||
import { usePreferenceStore } from '$/store'
|
||||
|
||||
import { useDialog, useFileActions } from '$/composables/index.js'
|
||||
|
||||
const preferenceStore = usePreferenceStore()
|
||||
|
||||
const fileActions = reactive(useFileActions())
|
||||
|
||||
const dialog = reactive(useDialog())
|
||||
|
||||
const device = ref()
|
||||
|
||||
@ -145,7 +191,10 @@ const breadcrumbModel = computed(() => {
|
||||
const pathList = currentPath.value.split('/')
|
||||
|
||||
const value = pathList.map(item => ({
|
||||
label: item === 'sdcard' ? '内部存储空间' : void 0,
|
||||
label:
|
||||
item === 'sdcard'
|
||||
? window.t('device.control.file.manager.storage')
|
||||
: void 0,
|
||||
value: item,
|
||||
}))
|
||||
|
||||
@ -153,17 +202,20 @@ const breadcrumbModel = computed(() => {
|
||||
})
|
||||
|
||||
function open(args) {
|
||||
visible.value = true
|
||||
device.value = args
|
||||
dialog.open(args)
|
||||
getTableData()
|
||||
}
|
||||
|
||||
function onClosed() {}
|
||||
function onClosed() {
|
||||
currentPath.value = 'sdcard'
|
||||
dialog.reset()
|
||||
}
|
||||
|
||||
async function getTableData() {
|
||||
loading.value = true
|
||||
|
||||
const data = await window.adbkit.getFiles(device.value.id, currentPath.value)
|
||||
const data = await window.adbkit.readdir(device.value.id, currentPath.value)
|
||||
|
||||
loading.value = false
|
||||
|
||||
@ -176,8 +228,6 @@ function onSelectionChange(selection) {
|
||||
selectionRows.value = selection
|
||||
}
|
||||
|
||||
function handleFile(row) {}
|
||||
|
||||
function handleDirectory(row) {
|
||||
currentPath.value += `/${row.name}`
|
||||
getTableData()
|
||||
@ -206,6 +256,84 @@ function handlePrev() {
|
||||
getTableData()
|
||||
}
|
||||
|
||||
async function handleAdd(dirname) {
|
||||
await window.adbkit.deviceShell(
|
||||
device.value.id,
|
||||
`mkdir ${currentPath.value}/${dirname}`,
|
||||
)
|
||||
|
||||
getTableData()
|
||||
}
|
||||
|
||||
async function handleRemove(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
window.t('device.control.file.manager.delete.tips'),
|
||||
window.t('common.tips'),
|
||||
{
|
||||
type: 'warning',
|
||||
},
|
||||
)
|
||||
}
|
||||
catch (error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
await window.adbkit.deviceShell(
|
||||
device.value.id,
|
||||
`rm -r ${currentPath.value}/${row.name}`,
|
||||
)
|
||||
|
||||
getTableData()
|
||||
}
|
||||
|
||||
async function handleUpload() {
|
||||
await fileActions.send(device.value)
|
||||
getTableData()
|
||||
}
|
||||
|
||||
async function handleDownload(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
window.t('device.control.file.manager.download.tips'),
|
||||
window.t('common.tips'),
|
||||
{
|
||||
type: 'info',
|
||||
},
|
||||
)
|
||||
}
|
||||
catch (error) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
const pathList = row
|
||||
? [row.id]
|
||||
: selectionRows.value
|
||||
.filter(item => item.type === 'file')
|
||||
.map(item => item.id)
|
||||
|
||||
const deviceConfig = preferenceStore.getData(device.value.id)
|
||||
|
||||
const closeLoading = ElMessage.loading(window.t('common.downloading')).close
|
||||
|
||||
for (let index = 0; index < pathList.length; index++) {
|
||||
const item = pathList[index]
|
||||
|
||||
const savePath = window.nodePath.resolve(
|
||||
deviceConfig.savePath,
|
||||
window.nodePath.basename(item),
|
||||
)
|
||||
|
||||
await window.adbkit
|
||||
.pull(device.value.id, item, { savePath })
|
||||
.catch(e => console.warn(e?.message))
|
||||
}
|
||||
|
||||
closeLoading()
|
||||
|
||||
ElMessage.success(window.t('common.success'))
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
|
5
src/composables/index.js
Normal file
5
src/composables/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
export { useDialog } from './useDialog/index.js'
|
||||
export { useFileActions } from './useFileActions/index.js'
|
||||
export { useInstallAction } from './useInstallAction/index.js'
|
||||
export { useScreenshotAction } from './useScreenshotAction/index.js'
|
||||
export { useShellAction } from './useShellAction/index.js'
|
45
src/composables/useDialog/index.js
Normal file
45
src/composables/useDialog/index.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { sleep } from '$/utils'
|
||||
|
||||
export function useDialog() {
|
||||
const visible = ref(false)
|
||||
const lazyVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const params = ref({})
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
async (value) => {
|
||||
if (!value) {
|
||||
await sleep()
|
||||
}
|
||||
lazyVisible.value = value
|
||||
},
|
||||
)
|
||||
|
||||
function open(args) {
|
||||
visible.value = true
|
||||
params.value = args?.params ?? {}
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function reset() {
|
||||
visible.value = false
|
||||
loading.value = false
|
||||
params.value = {}
|
||||
}
|
||||
|
||||
return {
|
||||
visible,
|
||||
lazyVisible,
|
||||
loading,
|
||||
params,
|
||||
open,
|
||||
close,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
|
||||
export default useDialog
|
@ -19,6 +19,11 @@
|
||||
"common.remove": "Remove",
|
||||
"common.select.please": "Please Select",
|
||||
"common.required": "This field cannot be empty",
|
||||
"common.download": "Download",
|
||||
"common.downloading": "Downloading",
|
||||
"common.delete": "Delete",
|
||||
"common.name": "Name",
|
||||
"common.size": "Size",
|
||||
|
||||
"common.language.name": "Language",
|
||||
"common.language.placeholder": "Select language",
|
||||
@ -26,6 +31,7 @@
|
||||
"common.language.zh-TW": "繁體中文",
|
||||
"common.language.en-US": "English",
|
||||
|
||||
"time.update": "Update Time",
|
||||
"time.unit.month": "month",
|
||||
"time.unit.week": "week",
|
||||
"time.unit.day": "day",
|
||||
@ -134,9 +140,15 @@
|
||||
"device.control.file.push.placeholder": "Please select the file to push",
|
||||
"device.control.file.push.loading": "Push file...",
|
||||
"device.control.file.push.success.name": "Push files successfully",
|
||||
"device.control.file.push.success": "Successfully pushed {totalCount} files to the /sdcard/Download/ directory of {deviceName}, {successCount} succeeded, and {failCount} failed",
|
||||
"device.control.file.push.success": "Successfully pushed {totalCount} files to {deviceName}, {successCount} succeeded, and {failCount} failed",
|
||||
"device.control.file.push.success.single": "Files successfully pushed to the /sdcard/Download/ directory of {deviceName}",
|
||||
"device.control.file.push.error": "Failed to push the file, please check the file and try again",
|
||||
"device.control.file.manager.storage": "Internal Storage",
|
||||
"device.control.file.manager.add": "New Folder",
|
||||
"device.control.file.manager.upload": "Upload File",
|
||||
"device.control.file.manager.download": "Download File",
|
||||
"device.control.file.manager.download.tips": "Are you sure you want to download the selected content?",
|
||||
"device.control.file.manager.delete.tips": "Are you sure you want to delete the selected content?",
|
||||
"device.control.shell.name": "Execute Script",
|
||||
"device.control.shell.tips": "Perform custom script through the ADB command",
|
||||
"device.control.shell.select": "Please select the script you want to execute",
|
||||
|
@ -5,7 +5,6 @@
|
||||
"common.default": "默认",
|
||||
"common.tips": "提示",
|
||||
"common.open": "打开",
|
||||
"common.download": "下载",
|
||||
"common.input.placeholder": "请填写",
|
||||
"common.success": "操作成功",
|
||||
"common.success.batch": "批量操作成功",
|
||||
@ -20,6 +19,11 @@
|
||||
"common.remove": "移除",
|
||||
"common.select.please": "请选择",
|
||||
"common.required": "该选项不能为空",
|
||||
"common.download": "下载",
|
||||
"common.downloading": "正在下载中",
|
||||
"common.delete": "删除",
|
||||
"common.name": "名称",
|
||||
"common.size": "大小",
|
||||
|
||||
"common.language.name": "语言",
|
||||
"common.language.placeholder": "选择你需要的语言",
|
||||
@ -27,6 +31,7 @@
|
||||
"common.language.zh-TW": "繁體中文",
|
||||
"common.language.en-US": "English",
|
||||
|
||||
"time.update": "更新时间",
|
||||
"time.unit.month": "月",
|
||||
"time.unit.week": "周",
|
||||
"time.unit.day": "天",
|
||||
@ -135,9 +140,15 @@
|
||||
"device.control.file.push.placeholder": "请选择要推送的文件",
|
||||
"device.control.file.push.loading": "推送文件中...",
|
||||
"device.control.file.push.success.name": "推送文件成功",
|
||||
"device.control.file.push.success": "已成功将 {totalCount} 个文件推送到 {deviceName} 的 /sdcard/Download/ 目录,{successCount} 成功,{failCount} 失败。",
|
||||
"device.control.file.push.success": "已成功将 {totalCount} 个文件推送到 {deviceName},{successCount} 成功,{failCount} 失败。",
|
||||
"device.control.file.push.success.single": "文件已成功推送到 {deviceName} 的 /sdcard/Download/ 目录",
|
||||
"device.control.file.push.error": "推送文件失败,请检查文件后重试",
|
||||
"device.control.file.manager.storage": "内部存储空间",
|
||||
"device.control.file.manager.add": "新建文件夹",
|
||||
"device.control.file.manager.upload": "上传文件",
|
||||
"device.control.file.manager.download": "下载文件",
|
||||
"device.control.file.manager.download.tips": "确定要下载所选内容吗",
|
||||
"device.control.file.manager.delete.tips": "确定要删除所选内容吗",
|
||||
"device.control.shell.name": "执行脚本",
|
||||
"device.control.shell.tips": "通过 ADB 命令执行自定义脚本",
|
||||
"device.control.shell.select": "请选择要执行的脚本",
|
||||
|
@ -19,6 +19,11 @@
|
||||
"common.remove": "移除",
|
||||
"common.select.please": "請選擇",
|
||||
"common.required": "該選項不能為空",
|
||||
"common.download": "下載",
|
||||
"common.downloading": "正在下載中",
|
||||
"common.delete": "刪除",
|
||||
"common.name": "名稱",
|
||||
"common.size": "大小",
|
||||
|
||||
"common.language.name": "語言",
|
||||
"common.language.placeholder": "選擇你要的語言",
|
||||
@ -26,6 +31,7 @@
|
||||
"common.language.zh-TW": "繁體中文",
|
||||
"common.language.en-US": "English",
|
||||
|
||||
"time.update": "更新時間",
|
||||
"time.unit.month": "月",
|
||||
"time.unit.week": "週",
|
||||
"time.unit.day": "天",
|
||||
@ -134,9 +140,15 @@
|
||||
"device.control.file.push.placeholder": "請選擇要推送的檔案",
|
||||
"device.control.file.push.loading": "推送檔案中...",
|
||||
"device.control.file.push.success.name": "推送檔案成功",
|
||||
"device.control.file.push.success": "已成功將 {totalCount} 個檔案推送到 {deviceName} 的 /sdcard/Download/ 目錄,{successCount} 成功,{failCount} 失敗。",
|
||||
"device.control.file.push.success": "已成功將 {totalCount} 個檔案推送到 {deviceName},{successCount} 成功,{failCount} 失敗。",
|
||||
"device.control.file.push.success.single": "檔案已成功推送到 {deviceName} 的 /sdcard/Download/ 目錄",
|
||||
"device.control.file.push.error": "推送檔案失敗,請檢查檔案後重試",
|
||||
"device.control.file.manager.storage": "內部儲存空間",
|
||||
"device.control.file.manager.add": "新增資料夾",
|
||||
"device.control.file.manager.upload": "上傳檔案",
|
||||
"device.control.file.manager.download": "下載檔案",
|
||||
"device.control.file.manager.download.tips": "確定要下載所選內容嗎?",
|
||||
"device.control.file.manager.delete.tips": "確定要刪除所選內容嗎?",
|
||||
"device.control.shell.name": "執行腳本",
|
||||
"device.control.shell.tips": "透過 ADB 命令執行自訂腳本",
|
||||
"device.control.shell.select": "請選擇要執行的腳本",
|
||||
|
Loading…
x
Reference in New Issue
Block a user