refactor: 🔨 对偏好设置存储规则逻辑代码进行重构以便于添加更多功能

This commit is contained in:
viarotel 2023-10-26 17:50:45 +08:00
parent 054c55b26d
commit 517eecd2e1
24 changed files with 981 additions and 749 deletions

View File

@ -4,6 +4,7 @@ module.exports = {
'no-unused-vars': 'off',
'eqeqeq': 'off',
'prefer-promise-reject-errors': 'off',
'no-new-func': 'off',
'antfu/top-level-function': 'off',

View File

@ -18,8 +18,8 @@ window.addEventListener('beforeunload', () => {
}
})
appStore.onDidChange('scrcpy.global.adbPath', async (value, oldValue) => {
console.log('onDidChange.scrcpy.global.adbPath', value)
appStore.onDidChange('scrcpy.common.adbPath', async (value, oldValue) => {
console.log('onDidChange.scrcpy.common.adbPath', value)
if (value === oldValue) {
return false
@ -146,7 +146,7 @@ const watch = async (callback) => {
}
export default () => {
const binPath = appStore.get('scrcpy.global.adbPath') || adbPath
const binPath = appStore.get('scrcpy.common.adbPath') || adbPath
client = Adb.createClient({
bin: binPath,

View File

@ -3,8 +3,8 @@ import appStore from '@electron/helpers/store.js'
import { adbPath, scrcpyPath } from '@electron/configs/index.js'
const shell = async (command, { stdout, stderr } = {}) => {
const spawnPath = appStore.get('scrcpy.global.scrcpyPath') || scrcpyPath
const ADB = appStore.get('scrcpy.global.adbPath') || adbPath
const spawnPath = appStore.get('scrcpy.common.scrcpyPath') || scrcpyPath
const ADB = appStore.get('scrcpy.common.adbPath') || adbPath
const args = command.split(' ')
const scrcpyProcess = spawn(`"${spawnPath}"`, args, {

View File

@ -29,7 +29,7 @@ export default {
return {
tabsModel: [
{
label: this.$t('devices.name'),
label: this.$t('device.list'),
prop: 'Device',
},
{
@ -47,7 +47,7 @@ export default {
}
},
created() {
this.$store.scrcpy.init()
this.$store.preference.init()
this.showTips()
},
methods: {

View File

@ -17,7 +17,7 @@
>
{{
loading && percent
? `${$t("about.updating")}...${percent.toFixed(1)}%`
? `${$t("about.update.progress")}...${percent.toFixed(1)}%`
: $t("about.update")
}}
</el-button>

View File

@ -36,46 +36,46 @@ export default {
return {
controlModel: [
{
label: this.$t('devices.operates.switch'),
label: this.$t('device.operates.switch'),
elIcon: 'Switch',
command: 'input keyevent KEYCODE_APP_SWITCH',
},
{
label: this.$t('devices.operates.home'),
label: this.$t('device.operates.home'),
elIcon: 'HomeFilled',
command: 'input keyevent KEYCODE_HOME',
},
{
label: this.$t('devices.operates.return'),
label: this.$t('device.operates.return'),
elIcon: 'Back',
command: 'input keyevent KEYCODE_BACK',
},
{
label: this.$t('devices.operates.notification'),
label: this.$t('device.operates.notification'),
elIcon: 'Notification',
command: 'cmd statusbar expand-notifications',
tips: '打开下拉菜单选项',
},
{
label: this.$t('devices.operates.power'),
label: this.$t('device.operates.power'),
elIcon: 'SwitchButton',
command: 'input keyevent KEYCODE_POWER',
tips: '可以用来开启或关闭屏幕',
},
{
label: this.$t('devices.operates.reboot'),
label: this.$t('device.operates.reboot'),
elIcon: 'RefreshLeft',
command: 'reboot',
tips: '可以用来开启或关闭屏幕',
},
{
label: this.$t('devices.operates.capture'),
label: this.$t('device.operates.capture'),
elIcon: 'Crop',
handle: this.handleScreenCap,
tips: '',
},
{
label: this.$t('devices.operates.install'),
label: this.$t('device.operates.install'),
svgIcon: 'install',
handle: this.handleInstall,
tips: '',
@ -85,8 +85,8 @@ export default {
},
computed: {},
methods: {
scrcpyConfig(...args) {
return this.$store.scrcpy.getConfig(...args)
preferenceData(...args) {
return this.$store.preference.getData(...args)
},
async handleInstall(device) {
let files = null
@ -166,7 +166,7 @@ export default {
'YYYY-MM-DD-HH-mm-ss',
)}.png`
const deviceConfig = this.scrcpyConfig(device.id)
const deviceConfig = this.preferenceData(device.id)
const savePath = this.$path.resolve(deviceConfig.savePath, fileName)
try {

View File

@ -12,7 +12,7 @@
<EditPen />
</el-icon>
<span class="pl-1">{{
device.$remark || $t("devices.device.remark")
device.$remark || $t("device.remark")
}}</span>
</div>
</el-tag>

View File

@ -8,7 +8,7 @@
clearable
>
<template #prepend>
{{ $t("devices.wireless.name") }}
{{ $t("device.wireless.name") }}
</template>
</el-input>
<div class="text-gray-500 text-sm">
@ -30,7 +30,7 @@
:loading="connectLoading"
@click="handleConnect"
>
{{ $t("devices.wireless.connect.name") }}
{{ $t("device.wireless.connect.name") }}
</el-button>
<el-button
type="primary"
@ -38,13 +38,13 @@
:loading="loading"
@click="handleRefresh"
>
{{ $t("devices.refresh.name") }}
{{ $t("device.refresh.name") }}
</el-button>
<el-button type="warning" icon="RefreshRight" @click="handleRestart">
{{ $t("devices.restart.name") }}
{{ $t("device.restart.name") }}
</el-button>
<el-button icon="View" @click="handleLog">
{{ $t("devices.log.name") }}
{{ $t("device.log.name") }}
</el-button>
</div>
<div class="pt-4 flex-1 h-0 overflow-hidden">
@ -59,17 +59,17 @@
row-key="id"
>
<template #empty>
<el-empty :description="$t('devices.empty')" />
<el-empty :description="$t('device.list.empty')" />
</template>
<el-table-column
prop="id"
:label="$t('devices.device.id')"
:label="$t('device.id')"
show-overflow-tooltip
align="left"
width="200"
/>
<el-table-column
:label="$t('devices.device.name')"
:label="$t('device.name')"
show-overflow-tooltip
align="left"
>
@ -77,7 +77,7 @@
<div class="flex items-center">
<el-tooltip
v-if="row.$unauthorized"
:content="$t('devices.device.permission.error')"
:content="$t('device.permission.error')"
placement="top-start"
>
<el-icon class="mr-1 text-red-600 text-lg">
@ -98,7 +98,7 @@
</template>
</el-table-column>
<el-table-column
:label="$t('devices.operates.name')"
:label="$t('device.operates.name')"
width="450"
align="left"
>
@ -113,8 +113,8 @@
>
{{
row.$loading
? $t("devices.mirror.progress")
: $t("devices.mirror.start")
? $t("device.mirror.progress")
: $t("device.mirror.start")
}}
</el-button>
@ -128,8 +128,8 @@
>
{{
row.$recordLoading
? $t("devices.record.progress")
: $t("devices.record.start")
? $t("device.record.progress")
: $t("device.record.start")
}}
</el-button>
@ -145,7 +145,7 @@
<template #icon>
<svg-icon name="wifi"></svg-icon>
</template>
{{ $t("devices.wireless.mode") }}
{{ $t("device.wireless.mode") }}
</el-button>
<el-button
@ -159,15 +159,15 @@
>
{{
row.$stopLoading
? $t("devices.wireless.disconnect.progress")
: $t("devices.wireless.disconnect.start")
? $t("device.wireless.disconnect.progress")
: $t("device.wireless.disconnect.start")
}}
</el-button>
</template>
</el-table-column>
<el-table-column type="expand">
<template #header>
<el-icon class="" :title="$t('devices.operates.more')">
<el-icon class="" :title="$t('device.operates.more')">
<Operation class="" />
</el-icon>
</template>
@ -199,7 +199,7 @@ export default {
const adbCache = storage.get('adbCache') || {}
return {
loading: false,
loadingText: this.$t('devices.loading'),
loadingText: this.$t('device.list.loading'),
connectLoading: false,
deviceList: [],
formData: {
@ -233,11 +233,11 @@ export default {
}
},
methods: {
scrcpyConfig(...args) {
return this.$store.scrcpy.getConfig(...args)
preferenceData(...args) {
return this.$store.preference.getData(...args)
},
scrcpyArgs(...args) {
return this.$store.scrcpy.getStringConfig(...args)
return this.$store.preference.getScrcpyData(...args)
},
handleRefresh() {
this.getDeviceData({ resetResolve: true })
@ -246,24 +246,24 @@ export default {
try {
await this.$confirm(
`
<div>${this.$t('devices.reset.reasons[0]')}</div>
<div class="text-red-500">${this.$t('devices.reset.reasons[1]')}</div>
<div>${this.$t('device.reset.reasons[0]')}</div>
<div class="text-red-500">${this.$t('device.reset.reasons[1]')}</div>
`,
this.$t('devices.reset.title'),
this.$t('device.reset.title'),
{
dangerouslyUseHTMLString: true,
confirmButtonText: this.$t('devices.reset.confirm'),
cancelButtonText: this.$t('devices.reset.cancel'),
confirmButtonText: this.$t('device.reset.confirm'),
cancelButtonText: this.$t('device.reset.cancel'),
closeOnClickModal: false,
type: 'warning',
},
)
this.$store.scrcpy.resetDeps(depType)
this.$store.preference.resetDeps(depType)
this.$root.reRender('Preference')
this.$message.success(this.$t('devices.reset.success'))
this.$message.success(this.$t('device.reset.success'))
}
catch (error) {
if (error.message) {
@ -276,7 +276,7 @@ export default {
this.$refs.elTable.toggleRowExpansion(...params)
},
getRecordPath(row) {
const rowConfig = this.scrcpyConfig(row.id)
const rowConfig = this.preferenceData(row.id)
const basePath = rowConfig.savePath
const recordFormat = rowConfig['--record-format']
@ -301,7 +301,7 @@ export default {
const command = `--serial=${row.id} --window-title=${
row.$remark ? `${row.$remark}-` : ''
}${row.$name}-${row.id}-🎥${this.$t(
'devices.record.progress',
'device.record.progress',
)}... --record=${savePath} ${this.scrcpyArgs(row.id)}`
console.log('handleRecord.command', command)
@ -309,8 +309,8 @@ export default {
await this.$scrcpy.shell(command, { stdout: this.onStdout })
await this.$confirm(
this.$t('devices.record.success.message'),
this.$t('devices.record.success.title'),
this.$t('device.record.success.message'),
this.$t('device.record.success.title'),
{
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
@ -379,7 +379,7 @@ export default {
async handleConnect() {
if (!this.formData.host) {
this.$message.warning(
this.$t('devices.wireless.connect.error.no-address'),
this.$t('device.wireless.connect.error.no-address'),
)
return false
}
@ -387,7 +387,7 @@ export default {
this.connectLoading = true
try {
await this.$adb.connect(this.formData.host, this.formData.port || 5555)
this.$message.success(this.$t('devices.wireless.connect.success'))
this.$message.success(this.$t('device.wireless.connect.success'))
storage.set('adbCache', this.formData)
}
catch (error) {
@ -400,23 +400,21 @@ export default {
await this.$confirm(
`
<div class="pb-4 text-sm text-red-500">${this.$t(
'devices.wireless.connect.error.detail',
'device.wireless.connect.error.detail',
)}${message}</div>
<div>${this.$t('devices.wireless.connect.error.reasons[0]')}</div>
<div>1. ${this.$t('devices.wireless.connect.error.reasons[1]')} </div>
<div>2. ${this.$t('devices.wireless.connect.error.reasons[2]')} </div>
<div>3. ${this.$t('devices.wireless.connect.error.reasons[3]')} </div>
<div>4. ${this.$t('devices.wireless.connect.error.reasons[4]')} </div>
<div>5. ${this.$t('devices.wireless.connect.error.reasons[5]')} </div>
<div>${this.$t('device.wireless.connect.error.reasons[0]')}</div>
<div>1. ${this.$t('device.wireless.connect.error.reasons[1]')} </div>
<div>2. ${this.$t('device.wireless.connect.error.reasons[2]')} </div>
<div>3. ${this.$t('device.wireless.connect.error.reasons[3]')} </div>
<div>4. ${this.$t('device.wireless.connect.error.reasons[4]')} </div>
<div>5. ${this.$t('device.wireless.connect.error.reasons[5]')} </div>
`,
this.$t('devices.wireless.connect.error.title'),
this.$t('device.wireless.connect.error.title'),
{
dangerouslyUseHTMLString: true,
closeOnClickModal: false,
confirmButtonText: this.$t(
'devices.wireless.connect.error.confirm',
),
cancelButtonText: this.$t('devices.wireless.connect.error.cancel'),
confirmButtonText: this.$t('device.wireless.connect.error.confirm'),
cancelButtonText: this.$t('device.wireless.connect.error.cancel'),
type: 'warning',
},
)
@ -432,7 +430,7 @@ export default {
try {
await this.$adb.disconnect(host, port)
await sleep()
this.$message.success(this.$t('devices.wireless.disconnect.success'))
this.$message.success(this.$t('device.wireless.disconnect.success'))
}
catch (error) {
if (error.message)

View File

@ -3,7 +3,7 @@
<div class="pb-4 pr-2 flex items-center justify-between">
<div class="">
<el-select
v-model="scopeValue"
v-model="deviceScope"
value-key=""
:placeholder="$t('preferences.scope.placeholder')"
filterable
@ -57,8 +57,8 @@
</div>
<div class="grid gap-6 pr-2">
<el-card
v-for="(item, index) of scrcpyModel"
:key="index"
v-for="(item, parentId) of preferenceModel"
:key="parentId"
shadow="hover"
class=""
>
@ -68,7 +68,7 @@
{{ item.label }}
</div>
<div class="flex-none pl-4">
<el-button type="primary" text @click="handleReset(item.type)">
<el-button type="primary" text @click="handleReset(parentId)">
{{ $t("preferences.reset") }}
</el-button>
</div>
@ -77,13 +77,13 @@
<div class="">
<el-form
ref="elForm"
:model="scrcpyForm"
:model="preferenceData"
label-width="170px"
class="pr-8 pt-4"
>
<el-row :gutter="20">
<el-col
v-for="(item_1, index_1) of getSubModel(item.type)"
v-for="(item_1, index_1) of getSubModel(item)"
:key="index_1"
:span="12"
:offset="0"
@ -111,28 +111,29 @@
}}</span>
</div>
</template>
<el-input
v-if="item_1.type === 'input'"
v-if="item_1.type === 'Input'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
v-model="preferenceData[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.number'"
v-else-if="item_1.type === 'Input.number'"
v-bind="item_1.props || {}"
v-model.number="scrcpyForm[item_1.field]"
v-model.number="preferenceData[item_1.field]"
class="!w-full"
:title="item_1.placeholder"
:placeholder="item_1.placeholder"
clearable
></el-input>
<el-input
v-if="item_1.type === 'input.path'"
v-else-if="item_1.type === 'Input.path'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
v-model="preferenceData[item_1.field]"
class="!w-full"
clearable
:placeholder="item_1.placeholder"
@ -151,18 +152,18 @@
</template>
</el-input>
<el-switch
v-if="item_1.type === 'switch'"
v-else-if="item_1.type === 'Switch'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
v-model="preferenceData[item_1.field]"
class="!w-full"
clearable
:title="item_1.placeholder"
></el-switch>
<el-select
v-if="item_1.type === 'select'"
v-else-if="item_1.type === 'Select'"
v-bind="item_1.props || {}"
v-model="scrcpyForm[item_1.field]"
v-model="preferenceData[item_1.field]"
:placeholder="item_1.placeholder"
class="!w-full"
clearable
@ -188,43 +189,20 @@
<script>
import { debounce } from 'lodash-es'
import { useScrcpyStore } from '@/store/index.js'
import { usePreferenceStore } from '@/store/index.js'
import LoadingIcon from '@/components/Device/ControlBar/LoadingIcon/index.vue'
export default {
data() {
const scrcpyStore = useScrcpyStore()
const preferenceStore = usePreferenceStore()
// console.raw('preferenceStore.data', preferenceStore.data)
// console.raw('preferenceStore.model', preferenceStore.model)
return {
scrcpyModel: [
{
label: this.$t('preferences.custom.name'),
type: 'custom',
},
{
label: this.$t('preferences.video.name'),
type: 'video',
},
{
label: this.$t('preferences.device.name'),
type: 'device',
},
{
label: this.$t('preferences.window.name'),
type: 'window',
},
{
label: this.$t('preferences.record.name'),
type: 'record',
},
{
label: this.$t('preferences.audio.name'),
type: 'audio',
},
],
scrcpyForm: { ...scrcpyStore.config },
scopeValue: scrcpyStore.scope,
preferenceModel: preferenceStore.model,
preferenceData: preferenceStore.data,
deviceScope: preferenceStore.deviceScope,
}
},
computed: {
@ -247,18 +225,23 @@ export default {
},
},
watch: {
scrcpyForm: {
preferenceData: {
handler() {
this.handleSave()
},
deep: true,
},
scopeValue: {
handler(value) {
deviceScope: {
async handler(value) {
if (value === 'global') {
return
this.$store.preference.resetModel()
return false
}
this.getDisplay(value)
const display = await this.$adb.display(value)
this.$store.preference.setModelParams('video', { display })
this.preferenceModel = this.$store.preference.model
},
immediate: true,
},
@ -271,20 +254,13 @@ export default {
},
methods: {
handleResetAll() {
this.$store.scrcpy.reset(this.scopeValue)
this.scrcpyForm = this.$store.scrcpy.config
this.$store.preference.reset(this.deviceScope)
this.preferenceData = this.$store.preference.data
},
onScopeChange(value) {
const replaceIPValue = this.$store.device.replaceIP(value)
this.$store.scrcpy.setScope(replaceIPValue)
this.scrcpyForm = this.$store.scrcpy.config
},
async getDisplay(value) {
const display = await this.$adb.display(value)
console.log('display', display)
this.$store.scrcpy.setModel('video', { display })
const replaceValue = this.$replaceIP(value)
this.$store.preference.setScope(replaceValue)
this.preferenceData = this.$store.preference.data
},
async handleImport() {
try {
@ -301,7 +277,7 @@ export default {
this.$message.success(this.$t('preferences.config.import.success'))
this.scrcpyForm = this.$store.scrcpy.init()
this.preferenceData = this.$store.preference.init()
}
catch (error) {
if (error.message) {
@ -344,7 +320,7 @@ export default {
},
async handleSelect({ field }, { properties, filters } = {}) {
try {
const defaultPath = this.scrcpyForm[field]
const defaultPath = this.preferenceData[field]
const files = await this.$electron.ipcRenderer.invoke(
'show-open-dialog',
{
@ -360,7 +336,7 @@ export default {
const value = files[0]
this.scrcpyForm[field] = value
this.preferenceData[field] = value
}
catch (error) {
if (error.message) {
@ -370,20 +346,22 @@ export default {
}
},
handleSave() {
this.$store.scrcpy.setConfig(this.scrcpyForm)
this.$store.preference.setData(this.preferenceData)
this.$message.success(this.$t('preferences.config.save.placeholder'))
},
getSubModel(type) {
console.log('getSubModel')
const value = this.$store.scrcpy.getModel(type)
return value
getSubModel(item) {
const data = item?.children() || []
console.raw(`getSubModel.${item.field}.data`, data)
return data
},
handleReset(type) {
this.scrcpyForm = {
...this.scrcpyForm,
...this.$store.scrcpy.getDefaultConfig(type),
this.preferenceData = {
...this.preferenceData,
...this.$store.preference.getDefaultData(type),
}
this.$store.scrcpy.setConfig(this.scrcpyForm)
this.$store.preference.setData(this.preferenceData)
},
},
}

View File

@ -1,294 +1 @@
{
"common": {
"cancel": "Cancel",
"confirm": "Confirm"
},
"devices": {
"name": "Devices",
"loading": "Devices loading...",
"empty": "No device detected",
"wireless": {
"name": "Wireless",
"mode": "Wireless mode",
"connect": {
"name": "Connect",
"error": {
"title": "连接设备失败",
"detail": "错误详情",
"reasons": [
"可能有以下原因",
"IP地址或端口号错误",
"设备未与当前电脑配对成功",
"电脑网络和提供的设备网络IP不在同一个局域网中",
"adb 依赖路径错误",
"其他未知错误"
],
"confirm": "无线配对",
"cancel": "@:common.cancel",
"no-address": "无线调试地址不能为空"
},
"success": "连接设备成功"
},
"disconnect": {
"start": "断开连接",
"progress": "正在断开",
"success": "断开连接成功"
}
},
"reset": {
"title": "操作失败",
"reasons": [
"通常情况下,这可能是因为更新 Escrcpy 后,缓存的依赖配置不兼容所导致的,是否重置依赖配置?",
"注意:重置后,之前保存的依赖配置将会被清除,因此建议在执行重置操作之前备份您的配置。"
],
"confirm": "重置依赖配置",
"cancel": "@:common.cancel",
"success": "操作成功,请重新尝试。"
},
"refresh": {
"name": "Refresh"
},
"restart": {
"name": "Restart"
},
"log": {
"name": "Logs"
},
"device": {
"id": "ID",
"name": "Name",
"remark": "Remark",
"permission": {
"error": "设备可能未授权成功请重新插拔设备并点击允许USB调试"
}
},
"mirror": {
"start": "Mirror",
"progress": "Mirroring"
},
"record": {
"start": "Record",
"progress": "Recording",
"success": {
"title": "录制成功",
"message": "是否前往录制位置进行查看?"
}
},
"operates": {
"name": "Operations",
"install": "Install APP",
"capture": "Capture",
"reboot": "Reboot",
"power": "Power",
"notification": "Notification",
"return": "Back",
"home": "Home",
"switch": "Switch"
}
},
"preferences": {
"name": "Preferences",
"reset": "Resets",
"scope": {
"global": "Global",
"placeholder": "偏好设置的作用域范围",
"no-data": "暂无数据",
"details": [
"对全局或者单个设备设置不同的偏好配置",
"全局:将对所有设备生效。",
"单个设备:继承于全局配置,并对单个设备进行独立设置,仅对此设备生效。"
]
},
"config": {
"import": {
"name": "导入配置",
"placeholder": "请选择要导入的配置文件",
"success": "导入成功"
},
"export": {
"name": "导出配置",
"message": "导出配置",
"placeholder": "请选择要导出的位置",
"success": "导出成功"
},
"edit": {
"name": "编辑配置"
},
"reset": {
"name": "重置配置"
},
"save": {
"name": "保存配置",
"placeholder": "保存配置成功,将在下一次控制设备时生效"
}
},
"custom": {
"name": "Custom",
"file": {
"name": "File Path",
"placeholder": "Put on the user desktop by default",
"tips": "Screenshots and recorded audio and video exist here"
},
"adb": {
"name": "ADB Path",
"placeholder": "Please set the ADB path",
"tips": "The address of the ADB used to connect the device. Note that this option is not affected by the configuration of a single device"
},
"scrcpy": {
"name": "Scrcpy Path",
"placeholder": "Please set the SCRCPY path",
"tips": "The address of the SCRCPY used to connect the device. Note that this option is not affected by the configuration of a single device"
}
},
"video": {
"name": "Video",
"resolution": {
"name": "Resolution",
"placeholder": "Default device resolution",
"tips": ""
},
"bit": {
"name": "Bitrate",
"placeholder": "Default 4M",
"tips": ""
},
"refresh-rate": {
"name": "Refresh Rate",
"placeholder": "Default 60",
"tips": ""
},
"decoder": {
"name": "Decoder",
"placeholder": "Default h264",
"tips": ""
},
"encoder": {
"name": "Encoder",
"placeholder": "Default device encoder",
"tips": ""
},
"screen-rotation": {
"name": "Rotation",
"placeholder": "Default device rotation",
"tips": ""
},
"screen-cropping": {
"name": "Cropping",
"placeholder": "Default no cropping",
"tips": ""
},
"multi-display": {
"name": "Multi-Display",
"placeholder": "Default 0 (main)",
"tips": ""
},
"video-buffering": {
"name": "Video Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"audio-buffering": {
"name": "Audio Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"receiver-buffering": {
"name": "Receiver Buffering",
"placeholder": "Default 0ms",
"tips": ""
},
"disable": {
"name": "Disable Video",
"placeholder": "Disable video if enabled",
"tips": ""
}
},
"device": {
"name": "Device",
"show-touch": {
"name": "Show Touches",
"placeholder": "Enable to show tap feedback in developer options",
"tips": "Only on physical devices"
},
"stay-awake": {
"name": "Stay Awake",
"placeholder": "Enable to prevent sleep",
"tips": "Wired connection only"
},
"control-in-close-screen": {
"name": "Turn Off Screen",
"placeholder": "Automatically turn off screen when controlling device",
"tips": ""
},
"control-end-video": {
"name": "Turn Off at End",
"placeholder": "Automatically turn off screen when control stops",
"tips": ""
},
"control-in-stop-charging": {
"name": "Stop Charging",
"placeholder": "Stop charging when controlling device",
"tips": "May not work on some models"
}
},
"window": {
"name": "Window",
"borderless": {
"name": "Borderless",
"placeholder": "Removes window border",
"tips": ""
},
"full-screen": {
"name": "Fullscreen",
"placeholder": "Fullscreen mode",
"tips": ""
},
"always-top": {
"name": "Always on Top",
"placeholder": "Window always on top",
"tips": ""
},
"disable-screen-saver": {
"name": "Disable Screen Saver",
"placeholder": "Disables screen saver",
"tips": ""
}
},
"record": {
"name": "Recording",
"format": {
"name": "Format",
"placeholder": "Default .mp4 format",
"tips": ""
}
},
"audio": {
"name": "Audio",
"disable": {
"name": "Disable Audio",
"placeholder": "Disables audio",
"tips": ""
}
}
},
"about": {
"name": "About",
"description": "📱 Display and control your Android device with graphical Scrcpy, powered by Electron",
"update": "Check for updates",
"update-not-available": "Already up to date",
"update-error": {
"title": "Update check failed",
"message": "You may need a VPN. Go to releases page to download manually?"
},
"update-downloaded": {
"title": "New version downloaded",
"message": "Restart now to update?",
"confirm": "Update"
},
"update-available": {
"title": "New version available",
"confirm": "Update"
},
"updating": "Updating..."
}
}
{}

View File

@ -1,296 +1,169 @@
{
"common": {
"cancel": "取消",
"confirm": "确认"
},
"devices": {
"name": "设备列表",
"loading": "努力加载中...",
"empty": "没有检测到设备",
"wireless": {
"name": "无线连接",
"mode": "无线模式",
"connect": {
"name": "连接设备",
"error": {
"title": "连接设备失败",
"detail": "错误详情",
"reasons": [
"可能有以下原因",
"IP地址或端口号错误",
"设备未与当前电脑配对成功",
"电脑网络和提供的设备网络IP不在同一个局域网中",
"adb 依赖路径错误",
"其他未知错误"
],
"confirm": "无线配对",
"cancel": "@:common.cancel",
"no-address": "无线调试地址不能为空"
},
"success": "连接设备成功"
},
"disconnect": {
"start": "断开连接",
"progress": "正在断开",
"success": "断开连接成功"
}
},
"reset": {
"title": "操作失败",
"reasons": [
"通常情况下,这可能是因为更新 Escrcpy 后,缓存的依赖配置不兼容所导致的,是否重置依赖配置?",
"注意:重置后,之前保存的依赖配置将会被清除,因此建议在执行重置操作之前备份您的配置。"
],
"confirm": "重置依赖配置",
"cancel": "@:common.cancel",
"success": "操作成功,请重新尝试。"
},
"refresh": {
"name": "刷新设备"
},
"restart": {
"name": "重启服务"
},
"log": {
"name": "运行日志"
},
"device": {
"id": "设备 ID",
"name": "设备名称",
"remark": "备注",
"permission": {
"error": "设备可能未授权成功请重新插拔设备并点击允许USB调试"
}
},
"mirror": {
"start": "开始镜像",
"progress": "正在镜像"
},
"record": {
"start": "开始录制",
"progress": "正在录制",
"success": {
"title": "录制成功",
"message": "是否前往录制位置进行查看?"
}
},
"operates": {
"name": "操作",
"more": "设备交互",
"install": "安装应用",
"capture": "截取屏幕",
"reboot": "重启设备",
"power": "电源键",
"notification": "通知栏",
"return": "切换键",
"home": "切换键",
"switch": "切换键"
}
},
"preferences": {
"name": "偏好设置",
"reset": "恢复默认值",
"scope": {
"global": "全局",
"placeholder": "偏好设置的作用域范围",
"no-data": "暂无数据",
"details": [
"对全局或者单个设备设置不同的偏好配置",
"全局:将对所有设备生效。",
"单个设备:继承于全局配置,并对单个设备进行独立设置,仅对此设备生效。"
]
},
"config": {
"import": {
"name": "导入",
"placeholder": "请选择要导入的配置文件",
"success": "导入成功"
},
"export": {
"name": "导出",
"message": "导出配置",
"placeholder": "请选择要导出的位置",
"success": "导出成功"
},
"edit": {
"name": "编辑"
},
"reset": {
"name": "重置"
},
"save": {
"name": "保存配置",
"placeholder": "保存配置成功,将在下一次控制设备时生效"
}
},
"custom": {
"name": "自定义",
"file": {
"name": "文件存储路径",
"placeholder": "默认情况下放在用户桌面上",
"tips": "截图和录制的音视频存放在这里"
},
"adb": {
"name": "adb 路径",
"placeholder": "请设置 adb 路径",
"tips": "用于连接设备的 adb 地址。注意:此选项不受单个设备配置的影响"
},
"scrcpy": {
"name": "scrcpy 路径",
"placeholder": "请设置 scrcpy 路径",
"tips": "用于连接设备的 scrcpy 地址。注意:此选项不受单个设备配置的影响"
}
},
"video": {
"name": "视频控制",
"resolution": {
"name": "分辨率",
"placeholder": "默认值为设备分辨率,如 1920",
"tips": ""
},
"bit": {
"name": "比特率",
"placeholder": "默认值为 4M等同于 4000000",
"tips": ""
},
"refresh-rate": {
"name": "刷新率",
"placeholder": "默认值为 60",
"tips": ""
},
"decoder": {
"name": "视频解码器",
"placeholder": "默认值为 h264",
"tips": ""
},
"encoder": {
"name": "视频编码器",
"placeholder": "默认值为设备默认编码器",
"tips": ""
},
"screen-rotation": {
"name": "屏幕旋转",
"placeholder": "默认值为设备屏幕旋转角度",
"tips": ""
},
"screen-cropping": {
"name": "屏幕裁剪",
"placeholder": "默认不裁剪,格式为 1224:1440:0:0",
"tips": ""
},
"multi-display": {
"name": "多显示器",
"placeholder": "默认值为 0主屏幕",
"tips": ""
},
"video-buffering": {
"name": "视频缓冲",
"placeholder": "默认值为 0ms",
"tips": ""
},
"audio-buffering": {
"name": "音频缓冲",
"placeholder": "默认值为 0ms",
"tips": ""
},
"receiver-buffering": {
"name": "接收器缓冲(v412)",
"placeholder": "默认值为 0ms",
"tips": ""
},
"disable": {
"name": "禁用视频",
"placeholder": "开启后将禁用视频",
"tips": ""
}
},
"device": {
"name": "设备控制",
"show-touch": {
"name": "展示触摸点",
"placeholder": "开启后将打开开发者选项中的显示点按触摸反馈",
"tips": "仅在物理设备上展示"
},
"stay-awake": {
"name": "保持清醒",
"placeholder": "开启以防止设备进入睡眠状态",
"tips": "仅有线方式连接时有效"
},
"control-in-close-screen": {
"name": "控制时关闭屏幕",
"placeholder": "开启后控制设备时将自动关闭设备屏幕",
"tips": ""
},
"control-end-video": {
"name": "控制结束关闭屏幕",
"placeholder": "开启后停止控制设备将自动关闭设备屏幕",
"tips": ""
},
"control-in-stop-charging": {
"name": "控制时停止充电",
"placeholder": "开启后控制设备时将停止充电",
"tips": "某些机型上似乎不起作用"
}
},
"window": {
"name": "窗口控制",
"borderless": {
"name": "无边框模式",
"placeholder": "开启后控制窗口将变为无边框模式",
"tips": ""
},
"full-screen": {
"name": "全屏模式",
"placeholder": "开启后控制窗口将全屏显示模式",
"tips": ""
},
"always-top": {
"name": "始终位于顶部",
"placeholder": "开启后控制窗口将始终位于顶部",
"tips": ""
},
"disable-screen-saver": {
"name": "禁用屏幕保护程序",
"placeholder": "开启后将禁用计算机屏幕保护程序",
"tips": ""
}
},
"record": {
"name": "音视频录制",
"format": {
"name": "录制视频格式",
"placeholder": "默认为 *.mp4 格式",
"tips": ""
}
},
"audio": {
"name": "音频控制",
"disable": {
"name": "禁用音频",
"placeholder": "开启后将禁用音频功能",
"tips": ""
}
}
},
"about": {
"name": "关于",
"description": "📱 使用图形化的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动",
"update": "检查并更新",
"update-not-available": "已经是最新版本",
"update-error": {
"title": "检查更新失败",
"message": "你可能需要科学上网,是否前往发布页面手动下载更新?"
},
"update-downloaded": {
"title": "下载新版本成功",
"message": "是否立即重启更新?",
"confirm": "更新"
},
"update-available": {
"title": "发现新版本",
"confirm": "更新"
},
"updating": "正在更新中"
}
"common.cancel": "取消",
"common.confirm": "确认",
"device.list": "设备列表",
"device.list.loading": "努力加载中...",
"device.list.empty": "没有检测到设备",
"device.id": "设备 ID",
"device.name": "设备名称",
"device.remark": "备注",
"device.permission.error": "设备可能未授权成功请重新插拔设备并点击允许USB调试",
"device.wireless.name": "无线连接",
"device.wireless.mode": "无线模式",
"device.wireless.connect.name": "连接设备",
"device.wireless.connect.error.title": "连接设备失败",
"device.wireless.connect.error.detail": "错误详情",
"device.wireless.connect.error.reasons[0]": "可能有以下原因",
"device.wireless.connect.error.reasons[1]": "IP地址或端口号错误",
"device.wireless.connect.error.reasons[2]": "设备未与当前电脑配对成功",
"device.wireless.connect.error.reasons[3]": "电脑网络和提供的设备网络IP不在同一个局域网中",
"device.wireless.connect.error.reasons[4]": "adb 依赖路径错误",
"device.wireless.connect.error.reasons[5]": "其他未知错误",
"device.wireless.connect.error.confirm": "无线配对",
"device.wireless.connect.error.cancel": "@:common.cancel",
"device.wireless.connect.error.no-address": "无线调试地址不能为空",
"device.wireless.connect.success": "连接设备成功",
"device.wireless.disconnect.start": "断开连接",
"device.wireless.disconnect.progress": "正在断开",
"device.wireless.disconnect.success": "断开连接成功",
"device.reset.title": "操作失败",
"device.reset.reasons[0]": "通常情况下,这可能是因为更新 Escrcpy 后,缓存的依赖配置不兼容所导致的,是否重置依赖配置?",
"device.reset.reasons[1]": "注意:重置后,之前保存的依赖配置将会被清除,因此建议在执行重置操作之前备份您的配置。",
"device.reset.confirm": "重置依赖配置",
"device.reset.cancel": "@:common.cancel",
"device.reset.success": "操作成功,请重新尝试。",
"device.refresh.name": "刷新设备",
"device.restart.name": "重启服务",
"device.log.name": "运行日志",
"device.mirror.start": "开始镜像",
"device.mirror.progress": "正在镜像",
"device.record.start": "开始录制",
"device.record.progress": "正在录制",
"device.record.success.title": "录制成功",
"device.record.success.message": "是否前往录制位置进行查看?",
"device.operates.name": "操作",
"device.operates.more": "设备交互",
"device.operates.install": "安装应用",
"device.operates.capture": "截取屏幕",
"device.operates.reboot": "重启设备",
"device.operates.power": "电源键",
"device.operates.notification": "通知栏",
"device.operates.return": "切换键",
"device.operates.home": "切换键",
"device.operates.switch": "切换键",
"preferences.name": "偏好设置",
"preferences.reset": "恢复默认值",
"preferences.scope.global": "全局",
"preferences.scope.placeholder": "偏好设置的作用域范围",
"preferences.scope.no-data": "暂无数据",
"preferences.scope.details[0]": "对全局或者单个设备设置不同的偏好配置",
"preferences.scope.details[1]": "全局:将对所有设备生效。",
"preferences.scope.details[2]": "单个设备:继承于全局配置,并对单个设备进行独立设置,仅对此设备生效。",
"preferences.config.import.name": "导入",
"preferences.config.import.placeholder": "请选择要导入的配置文件",
"preferences.config.import.success": "导入成功",
"preferences.config.export.name": "导出",
"preferences.config.export.message": "导出配置",
"preferences.config.export.placeholder": "请选择要导出的位置",
"preferences.config.export.success": "导出成功",
"preferences.config.edit.name": "编辑",
"preferences.config.reset.name": "重置",
"preferences.config.save.name": "保存配置",
"preferences.config.save.placeholder": "保存配置成功,将在下一次控制设备时生效",
"preferences.common.name": "通用",
"preferences.common.file.name": "文件存储路径",
"preferences.common.file.placeholder": "默认情况下放在用户桌面上",
"preferences.common.file.tips": "截图和录制的音视频存放在这里",
"preferences.common.adb.name": "adb 路径",
"preferences.common.adb.placeholder": "请设置 adb 路径",
"preferences.common.adb.tips": "用于连接设备的 adb 地址。注意:此选项不受单个设备配置的影响",
"preferences.common.scrcpy.name": "scrcpy 路径",
"preferences.common.scrcpy.placeholder": "请设置 scrcpy 路径",
"preferences.common.scrcpy.tips": "用于连接设备的 scrcpy 地址。注意:此选项不受单个设备配置的影响",
"preferences.video.name": "视频控制",
"preferences.video.resolution.name": "分辨率",
"preferences.video.resolution.placeholder": "默认值为设备分辨率,如 1920",
"preferences.video.resolution.tips": "",
"preferences.video.bit.name": "比特率",
"preferences.video.bit.placeholder": "默认值为 4M,等同于 4000000",
"preferences.video.bit.tips": "",
"preferences.video.refresh-rate.name": "刷新率",
"preferences.video.refresh-rate.placeholder": "默认值为 60",
"preferences.video.refresh-rate.tips": "",
"preferences.video.decoder.name": "视频解码器",
"preferences.video.decoder.placeholder": "默认值为 h264",
"preferences.video.decoder.tips": "",
"preferences.video.encoder.name": "视频编码器",
"preferences.video.encoder.placeholder": "默认值为设备默认编码器",
"preferences.video.encoder.tips": "",
"preferences.video.screen-rotation.name": "屏幕旋转",
"preferences.video.screen-rotation.placeholder": "默认值为设备屏幕旋转角度",
"preferences.video.screen-rotation.tips": "",
"preferences.video.screen-cropping.name": "屏幕裁剪",
"preferences.video.screen-cropping.placeholder": "默认不裁剪,格式为 1224:1440:0:0",
"preferences.video.screen-cropping.tips": "",
"preferences.video.multi-display.name": "多显示器",
"preferences.video.multi-display.placeholder": "默认值为 0(主屏幕)",
"preferences.video.multi-display.tips": "",
"preferences.video.video-buffering.name": "视频缓冲",
"preferences.video.video-buffering.placeholder": "默认值为 0ms",
"preferences.video.video-buffering.tips": "",
"preferences.video.audio-buffering.name": "音频缓冲",
"preferences.video.audio-buffering.placeholder": "默认值为 0ms",
"preferences.video.audio-buffering.tips": "",
"preferences.video.receiver-buffering.name": "接收器缓冲(v412)",
"preferences.video.receiver-buffering.placeholder": "默认值为 0ms",
"preferences.video.receiver-buffering.tips": "",
"preferences.video.disable.name": "禁用视频",
"preferences.video.disable.placeholder": "开启后将禁用视频",
"preferences.video.disable.tips": "",
"preferences.device.name": "设备控制",
"preferences.device.show-touch.name": "展示触摸点",
"preferences.device.show-touch.placeholder": "开启后将打开开发者选项中的显示点按触摸反馈",
"preferences.device.show-touch.tips": "仅在物理设备上展示",
"preferences.device.stay-awake.name": "保持清醒",
"preferences.device.stay-awake.placeholder": "开启以防止设备进入睡眠状态",
"preferences.device.stay-awake.tips": "仅有线方式连接时有效",
"preferences.device.control-in-close-screen.name": "控制时关闭屏幕",
"preferences.device.control-in-close-screen.placeholder": "开启后控制设备时将自动关闭设备屏幕",
"preferences.device.control-in-close-screen.tips": "",
"preferences.device.control-end-video.name": "控制结束关闭屏幕",
"preferences.device.control-end-video.placeholder": "开启后停止控制设备将自动关闭设备屏幕",
"preferences.device.control-end-video.tips": "",
"preferences.device.control-in-stop-charging.name": "控制时停止充电",
"preferences.device.control-in-stop-charging.placeholder": "开启后控制设备时将停止充电",
"preferences.device.control-in-stop-charging.tips": "某些机型上似乎不起作用",
"preferences.window.name": "窗口控制",
"preferences.window.borderless.name": "无边框模式",
"preferences.window.borderless.placeholder": "开启后控制窗口将变为无边框模式",
"preferences.window.borderless.tips": "",
"preferences.window.full-screen.name": "全屏模式",
"preferences.window.full-screen.placeholder": "开启后控制窗口将全屏显示模式",
"preferences.window.full-screen.tips": "",
"preferences.window.always-top.name": "始终位于顶部",
"preferences.window.always-top.placeholder": "开启后控制窗口将始终位于顶部",
"preferences.window.always-top.tips": "",
"preferences.window.disable-screen-saver.name": "禁用屏幕保护程序",
"preferences.window.disable-screen-saver.placeholder": "开启后将禁用计算机屏幕保护程序",
"preferences.window.disable-screen-saver.tips": "",
"preferences.record.name": "音视频录制",
"preferences.record.format.name": "录制视频格式",
"preferences.record.format.placeholder": "默认为 *.mp4 格式",
"preferences.record.format.tips": "",
"preferences.audio.name": "音频控制",
"preferences.audio.disable.name": "禁用音频",
"preferences.audio.disable.placeholder": "开启后将禁用音频功能",
"preferences.audio.disable.tips": "",
"about.name": "关于",
"about.description": "📱 使用图形化的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动",
"about.update": "检查并更新",
"about.update-not-available": "已经是最新版本",
"about.update-error.title": "检查更新失败",
"about.update-error.message": "你可能需要科学上网,是否前往发布页面手动下载更新?",
"about.update-downloaded.title": "下载新版本成功",
"about.update-downloaded.message": "是否立即重启更新?",
"about.update-downloaded.confirm": "更新",
"about.update-available.title": "发现新版本",
"about.update-available.confirm": "更新",
"about.update.progress": "正在更新中"
}

View File

@ -1,17 +1,20 @@
import { createPinia } from 'pinia'
import { useScrcpyStore } from './scrcpy/index.js'
import { useDeviceStore } from './device/index.js'
import { usePreferenceStore } from './preference/index.js'
export { useScrcpyStore, useDeviceStore }
export { useScrcpyStore, useDeviceStore, usePreferenceStore }
export default {
install(app) {
const store = createPinia()
app.use(store)
app.config.globalProperties.$store = {
scrcpy: useScrcpyStore(),
device: useDeviceStore(),
preference: usePreferenceStore(),
}
},
}

View File

@ -0,0 +1,129 @@
import { cloneDeep, keyBy, mergeWith, uniq } from 'lodash-es'
import model from '../model/index.js'
export function getModelFields(data = model) {
return uniq(Object.values(data).map(item => item.field))
}
const modelFields = getModelFields()
export function getModelMap(data = model) {
const value = Object.entries(data).reduce((obj, [key, item]) => {
const children
= item?.children()?.map(item_1 => ({
...item_1,
parentField: item.field,
parentId: key,
})) || []
const subData = keyBy(children, 'field')
obj = {
...obj,
...subData,
}
return obj
}, {})
// console.raw('getModelMap.value', value)
return value
}
export function getDefaultData(parentId) {
const modelMap = getModelMap()
const value = Object.entries(modelMap).reduce((obj, [key, data]) => {
if (!parentId || data.parentId === parentId) {
obj[key] = data.value
}
return obj
}, {})
// console.raw('getDefaultData.value', value)
return value
}
export const getStoreData = (scope) => {
const value = {}
modelFields.forEach((key) => {
const storeValue = window.appStore.get(key) || {}
if (key === 'scrcpy') {
Object.assign(value, storeValue[scope || 'global'])
return
}
Object.assign(value, storeValue)
})
// console.raw('getStoreData.value', value)
return value
}
export function setStoreData(data, scope) {
const modelMap = getModelMap()
const fieldModel = modelFields.reduce((obj, key) => {
obj[key] = {}
return obj
}, {})
Object.entries(data).forEach(([key, value]) => {
const { parentField } = modelMap[key]
fieldModel[parentField][key] = value
})
const fieldList = Object.entries(fieldModel).reduce((arr, [field, value]) => {
arr.push({
field: field === 'scrcpy' ? `scrcpy.${scope}` : field,
value,
})
return arr
}, [])
// console.raw('setStoreData.fieldList', fieldList)
fieldList.forEach((item) => {
window.appStore.set(item.field, item.value)
})
}
export function mergeConfig(object, sources, { debug = false } = {}) {
const cloneObject = cloneDeep(object)
const cloneSources = cloneDeep(sources)
const customizer = (objValue, srcValue, key) => {
let value = srcValue || objValue
if (typeof srcValue === 'boolean') {
value = srcValue
}
if (debug) {
console.raw(key, value)
}
return value
}
const value = mergeWith(cloneObject, cloneSources, customizer)
return value
}
export const getOtherFields = (excludeKey = '') => {
const modelMap = getModelMap()
const value = Object.entries(modelMap).reduce((arr, [key, data]) => {
if (data.parentField !== excludeKey) {
arr.push(data.field)
}
return arr
}, [])
return value
}

View File

@ -0,0 +1,179 @@
import { defineStore } from 'pinia'
import { cloneDeep } from 'lodash-es'
import model from './model/index.js'
import {
getDefaultData,
getModelFields,
getOtherFields,
getStoreData,
mergeConfig,
setStoreData,
} from './helpers/index.js'
import { replaceIP } from '@/utils/index.js'
const { adbPath, scrcpyPath } = window.electron?.configs || {}
export const usePreferenceStore = defineStore({
id: 'app-preference',
state() {
return {
model: cloneDeep(model),
data: { ...getDefaultData() },
deviceScope: window.appStore.get('scrcpy.deviceScope') || 'global',
scrcpyExcludeKeys: ['--record-format', ...getOtherFields('scrcpy')],
}
},
getters: {
modelList() {
return Object.values(this.model)
},
},
actions: {
getDefaultData,
init(scope = this.deviceScope) {
const data = mergeConfig(getDefaultData(), getStoreData(scope))
this.data = data
return this.data
},
setScope(value) {
this.deviceScope = replaceIP(value)
window.appStore.set('scrcpy.deviceScope', this.deviceScope)
this.init()
},
setData(data, scope = this.deviceScope) {
const cloneData = cloneDeep(data)
// console.log('adbPath', adbPath)
// console.log('scrcpyPath', scrcpyPath)
if (data.adbPath === adbPath) {
delete cloneData.adbPath
}
if (data.scrcpyPath === scrcpyPath) {
delete cloneData.scrcpyPath
}
setStoreData(cloneData, scope)
this.init(scope)
},
reset(scope) {
if (!scope) {
window.appStore.reset()
}
else {
const fields = getModelFields()
fields.forEach((key) => {
if (key === 'scrcpy') {
this.deviceScope = scope
window.appStore.set(`scrcpy.${replaceIP(scope)}`, {})
return false
}
window.appStore.set(key, {})
})
}
this.init()
},
resetDeps(type) {
switch (type) {
case 'adb':
window.appStore.set('common.adbPath', '')
break
case 'scrcpy':
window.appStore.set('common.scrcpyPath', '')
break
default:
window.appStore.set('common.adbPath', '')
window.appStore.set('common.scrcpyPath', '')
break
}
this.init()
},
getData(scope = this.scope) {
const value = this.init(scope)
return value
},
getScrcpyData(scope = this.scope) {
const data = this.getData(scope)
if (!data) {
return ''
}
const value = Object.entries(data)
.reduce((arr, [key, value]) => {
if (!value) {
return arr
}
if (this.scrcpyExcludeKeys.includes(key)) {
return arr
}
if (typeof value === 'boolean') {
arr.push(key)
}
else {
arr.push(`${key}=${value}`)
}
return arr
}, [])
.join(' ')
console.log('getScrcpyData.value', value)
return value
},
getModel(key, params) {
const handler = this.model[key]
const value = handler(params)
// console.log('setModel.value', value)
return value
},
setModelParams(key, ...args) {
const handler = this.model[key]?.children
if (!handler) {
return false
}
const value = handler(...args)
console.raw('setModelParams.value', value)
this.model[key].children = () => value
return this.model
},
resetModel(key) {
const keys = []
if (key) {
keys.push(key)
}
else {
keys.push(...Object.keys(model))
}
keys.forEach((value) => {
if (!this.model?.[value]?.children) {
return false
}
this.model[value].children = (...args) =>
model[value].children(...args)
})
return true
},
},
})

View File

@ -0,0 +1,22 @@
import { t } from '@/locales/index.js'
export default {
label: t('preferences.audio.name'),
field: 'scrcpy',
children: () => {
// "[server] INFO: List of audio encoders:"
// "--audio-codec=opus --audio-encoder='c2.android.opus.encoder'"
// "--audio-codec=aac --audio-encoder='c2.android.aac.encoder'"
// "--audio-codec=aac --audio-encoder='OMX.google.aac.encoder'"
return [
{
label: t('preferences.audio.disable.name'),
field: '--no-audio',
type: 'Switch',
value: false,
placeholder: t('preferences.audio.disable.placeholder'),
},
]
},
}

View File

@ -0,0 +1,47 @@
import { t } from '@/locales/index.js'
export default {
label: t('preferences.common.name'),
field: 'common',
children: () => {
const { adbPath, scrcpyPath, desktopPath }
= window?.electron?.configs || {}
return [
{
label: t('preferences.common.file.name'),
field: 'savePath',
type: 'Input.path',
value: desktopPath,
placeholder: t('preferences.common.file.placeholder'),
tips: t('preferences.common.file.tips'),
properties: ['openDirectory'],
},
{
label: t('preferences.common.adb.name'),
field: 'adbPath',
value: adbPath,
type: 'Input.path',
placeholder: t('preferences.common.adb.placeholder'),
tips: t('preferences.common.adb.tips'),
properties: ['openFile'],
filters: [
{ name: t('preferences.common.adb.name'), extensions: ['*'] },
],
},
{
label: t('preferences.common.scrcpy.name'),
field: 'scrcpyPath',
value: scrcpyPath,
type: 'Input.path',
placeholder: t('preferences.common.scrcpy.placeholder'),
tips: t('preferences.common.scrcpy.tips'),
properties: ['openFile'],
filters: [
{ name: t('preferences.common.scrcpy.name'), extensions: ['*'] },
],
},
]
},
}

View File

@ -0,0 +1,52 @@
import { t } from '@/locales/index.js'
export default {
label: t('preferences.device.name'),
field: 'scrcpy',
children: () => {
return [
{
label: t('preferences.device.show-touch.name'),
field: '--show-touches',
type: 'Switch',
value: false,
placeholder: t('preferences.device.show-touch.placeholder'),
tips: t('preferences.device.show-touch.tips'),
},
{
label: t('preferences.device.stay-awake.name'),
field: '--stay-awake',
type: 'Switch',
value: false,
placeholder: t('preferences.device.stay-awake.placeholder'),
tips: t('preferences.device.stay-awake.tips'),
},
{
label: t('preferences.device.control-in-close-screen.name'),
field: '--turn-screen-off',
type: 'Switch',
value: false,
placeholder: t(
'preferences.device.control-in-close-screen.placeholder',
),
},
{
label: t('preferences.device.control-end-video.name'),
field: '--power-off-on-close',
type: 'Switch',
value: false,
placeholder: t('preferences.device.control-end-video.placeholder'),
},
{
label: t('preferences.device.control-in-stop-charging.name'),
field: '--no-power-on',
type: 'Switch',
value: false,
placeholder: t(
'preferences.device.control-in-stop-charging.placeholder',
),
tips: t('preferences.device.control-in-stop-charging.tips'),
},
]
},
}

View File

@ -0,0 +1,8 @@
import common from './common/index.js'
import video from './video/index.js'
import device from './device/index.js'
import window from './window/index.js'
import audio from './audio/index.js'
import record from './record/index.js'
export default { common, video, device, window, audio, record }

View File

@ -0,0 +1,27 @@
import { t } from '@/locales/index.js'
export default {
label: t('preferences.record.name'),
field: 'scrcpy',
children: () => {
return [
{
label: t('preferences.record.format.name'),
field: '--record-format',
type: 'Select',
value: 'mp4',
placeholder: t('preferences.record.format.placeholder'),
options: [
{
label: 'mp4',
value: 'mp4',
},
{
label: 'mkv',
value: 'mkv',
},
],
},
]
},
}

View File

@ -0,0 +1,159 @@
import { t } from '@/locales/index.js'
const getDisplayOptions = (display = []) =>
display?.map(value => ({ label: value, value })) || []
export default {
label: t('preferences.video.name'),
field: 'scrcpy',
children: ({ display } = {}) => {
const displayOptions = display?.length
? getDisplayOptions(display)
: [
{ label: '0', value: '0' },
{ label: '1', value: '1' },
{ label: '2', value: '2' },
]
return [
{
label: t('preferences.video.resolution.name'),
field: '--max-size',
type: 'Input.number',
value: '',
placeholder: t('preferences.video.resolution.placeholder'),
},
{
label: t('preferences.video.bit.name'),
field: '--video-bit-rate',
type: 'Input',
value: '',
placeholder: t('preferences.video.bit.placeholder'),
},
{
label: t('preferences.video.refresh-rate.name'),
field: '--max-fps',
type: 'Input.number',
value: '',
placeholder: t('preferences.video.refresh-rate.placeholder'),
},
{
label: t('preferences.video.decoder.name'),
field: '--video-codec',
type: 'Select',
value: '',
placeholder: t('preferences.video.decoder.placeholder'),
options: [
{
label: 'h264',
value: 'h264',
},
{
label: 'h265',
value: 'h265',
},
{
label: 'av1',
value: 'av1',
},
],
},
{
label: t('preferences.video.encoder.name'),
field: '--video-encoder',
type: 'Select',
value: '',
placeholder: t('preferences.video.encoder.placeholder'),
// "[server] INFO: List of video encoders:"
// "--video-codec=h264 --video-encoder='OMX.qcom.video.encoder.avc'"
// "--video-codec=h264 --video-encoder='c2.android.avc.encoder'"
// "--video-codec=h264 --video-encoder='OMX.google.h264.encoder'"
// "--video-codec=h265 --video-encoder='OMX.qcom.video.encoder.hevc'"
// "--video-codec=h265 --video-encoder='c2.android.hevc.encoder'"
options: [
{
label: 'Android HEVC(H.265) ',
value: 'OMX.qcom.video.encoder.avc',
},
{
label: 'Qualcomm HEVC(H.265) ',
value: 'c2.android.avc.encoder',
},
{
label: 'Google H.264(AVC)',
value: 'OMX.google.h264.encoder',
},
{
label: 'Android AVC(H.264) ',
value: 'OMX.qcom.video.encoder.hevc',
},
{
label: 'Qualcomm AVC(H.264)',
value: 'c2.android.hevc.encoder',
},
],
},
{
label: t('preferences.video.screen-rotation.name'),
field: '--rotation',
type: 'Select',
value: '',
placeholder: t('preferences.video.screen-rotation.placeholder'),
options: [
{ label: '0°', value: '0' },
{ label: '-90°', value: '1' },
{ label: '180°', value: '2' },
{ label: '90°', value: '3' },
],
},
{
label: t('preferences.video.screen-cropping.name'),
field: '--crop',
type: 'Input',
value: '',
placeholder: t('preferences.video.screen-cropping.placeholder'),
},
{
label: t('preferences.video.multi-display.name'),
field: '--display',
type: 'Select',
value: '',
placeholder: t('preferences.video.multi-display.placeholder'),
options: displayOptions,
props: {
filterable: true,
allowCreate: true,
},
},
{
label: t('preferences.video.video-buffering.name'),
field: '--display-buffer',
type: 'Input.number',
value: '',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: t('preferences.video.audio-buffering.name'),
field: '--audio-buffer',
type: 'Input.number',
value: '',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: t('preferences.video.receiver-buffering.name'),
field: '--v4l2-buffer',
type: 'Input.number',
value: '',
placeholder: t('preferences.video.video-buffering.placeholder'),
},
{
label: t('preferences.video.disable.name'),
field: '--no-video',
type: 'Switch',
value: false,
placeholder: t('preferences.video.disable.placeholder'),
},
]
},
}

View File

@ -0,0 +1,38 @@
import { t } from '@/locales/index.js'
export default {
label: t('preferences.window.name'),
field: 'scrcpy',
children: () => {
return [
{
label: t('preferences.window.borderless.name'),
field: '--window-borderless',
type: 'Switch',
value: false,
placeholder: t('preferences.window.borderless.placeholder'),
},
{
label: t('preferences.window.full-screen.name'),
field: '--fullscreen',
type: 'Switch',
value: false,
placeholder: t('preferences.window.full-screen.placeholder'),
},
{
label: t('preferences.window.always-top.name'),
field: '--always-on-top',
type: 'Switch',
value: false,
placeholder: t('preferences.window.always-top.placeholder'),
},
{
label: t('preferences.window.disable-screen-saver.name'),
field: '--disable-screensaver',
type: 'Switch',
value: false,
placeholder: t('preferences.window.disable-screen-saver.placeholder'),
},
]
},
}

View File

@ -94,14 +94,14 @@ export const useScrcpyStore = defineStore({
resetDeps(type) {
switch (type) {
case 'adb':
$appStore.set('scrcpy.global.adbPath', '')
$appStore.set('scrcpy.common.adbPath', '')
break
case 'scrcpy':
$appStore.set('scrcpy.global.scrcpyPath', '')
$appStore.set('scrcpy.common.scrcpyPath', '')
break
default:
$appStore.set('scrcpy.global.adbPath', '')
$appStore.set('scrcpy.global.scrcpyPath', '')
$appStore.set('scrcpy.common.adbPath', '')
$appStore.set('scrcpy.common.scrcpyPath', '')
break
}
this.init()

View File

@ -5,34 +5,34 @@ export default () => {
return [
{
label: t('preferences.custom.file.name'),
label: t('preferences.common.file.name'),
type: 'input.path',
field: 'savePath',
value: desktopPath,
placeholder: t('preferences.custom.file.placeholder'),
tips: t('preferences.custom.file.tips'),
placeholder: t('preferences.common.file.placeholder'),
tips: t('preferences.common.file.tips'),
properties: ['openDirectory'],
},
{
label: t('preferences.custom.adb.name'),
label: t('preferences.common.adb.name'),
field: 'adbPath',
type: 'input.path',
value: adbPath,
placeholder: t('preferences.custom.adb.placeholder'),
tips: t('preferences.custom.adb.tips'),
placeholder: t('preferences.common.adb.placeholder'),
tips: t('preferences.common.adb.tips'),
properties: ['openFile'],
filters: [{ name: t('preferences.custom.adb.name'), extensions: ['*'] }],
filters: [{ name: t('preferences.common.adb.name'), extensions: ['*'] }],
},
{
label: t('preferences.custom.scrcpy.name'),
label: t('preferences.common.scrcpy.name'),
field: 'scrcpyPath',
type: 'input.path',
value: scrcpyPath,
placeholder: t('preferences.custom.scrcpy.placeholder'),
tips: t('preferences.custom.scrcpy.tips'),
placeholder: t('preferences.common.scrcpy.placeholder'),
tips: t('preferences.common.scrcpy.tips'),
properties: ['openFile'],
filters: [
{ name: t('preferences.custom.scrcpy.name'), extensions: ['*'] },
{ name: t('preferences.common.scrcpy.name'), extensions: ['*'] },
],
},
]

View File

@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash-es'
import { cloneDeep, keyBy } from 'lodash-es'
/**
* @desc 使用async await 进项进行延时操作
@ -37,3 +37,14 @@ export function createProxy(targetObject, methodNames) {
return proxyObj
}, {})
}
export function keyByValue(data, key = 'key', valueKey = 'value') {
const model = keyBy(data, key) || {}
const value = Object.entries(model).reduce((obj, [modelKey, modelValue]) => {
obj[modelKey] = modelValue?.[valueKey]
return obj
}, {})
return value
}