perf: 🚀 Extended floating control bar function

This commit is contained in:
viarotel 2024-09-13 19:38:29 +08:00
parent 6991d1fee2
commit 790e70349a
15 changed files with 313 additions and 107 deletions

View File

@ -25,7 +25,7 @@
type="primary" type="primary"
text text
class="!px-2 !h-full" class="!px-2 !h-full"
icon="Switch" icon="ArrowDown"
@click="switchDevice" @click="switchDevice"
> >
<span class="mr-2">{{ deviceInfo.$remark || deviceInfo.$name }}</span> <span class="mr-2">{{ deviceInfo.$remark || deviceInfo.$name }}</span>
@ -107,7 +107,7 @@ async function switchDevice(e) {
const data = await deviceStore.getList() const data = await deviceStore.getList()
window.electron.ipcRenderer.send('show-device-list', data) window.electron.ipcRenderer.invoke('open-control-device-menu', data)
} }
</script> </script>

View File

@ -0,0 +1,24 @@
import { BrowserWindow, ipcMain, Menu } from 'electron'
import { openControlWindow } from '$control/electron/helpers/index.js'
export default function (controlWindow) {
ipcMain.handle('open-control-device-menu', (event, deviceList) => {
const template = deviceList.map((item) => {
let label = item.$remark || item.$name
if (item.$wifi) {
label += ` (WIFI)`
}
return {
label,
click: () => {
openControlWindow(controlWindow, item)
},
}
})
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
})
}

View File

@ -0,0 +1,21 @@
import { BrowserWindow, ipcMain, Menu } from 'electron'
export default function (controlWindow) {
ipcMain.handle('open-device-gnirehtet-menu', openDeviceGnirehtetMenu)
function openDeviceGnirehtetMenu(event, args = {}) {
const { options = [] } = args
const template = options.map((item) => {
return {
label: item.label,
click() {
controlWindow.webContents.send(item.value)
},
}
})
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
}
}

View File

@ -0,0 +1,4 @@
export { default as devices } from './devices/index.js'
export { default as gnirehtet } from './gnirehtet/index.js'
export { default as rotation } from './rotation/index.js'
export { default as volume } from './volume/index.js'

View File

@ -0,0 +1,24 @@
import { BrowserWindow, ipcMain, Menu } from 'electron'
export default function (controlWindow) {
ipcMain.handle('open-device-rotation-menu', openDeviceRotationMenu)
function openDeviceRotationMenu(event, args = {}) {
const { options = [] } = args
const template = options.map((item) => {
return {
label: item.label,
click: () => {
controlWindow.webContents.send(
'execute-device-rotation-shell',
item.value,
)
},
}
})
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
}
}

View File

@ -0,0 +1,21 @@
import { BrowserWindow, ipcMain, Menu } from 'electron'
export default function (controlWindow) {
ipcMain.handle('open-device-volume-menu', openDeviceVolumeMenu)
function openDeviceVolumeMenu(event, args = {}) {
const { options = [] } = args
const template = options.map((item) => {
return {
label: item.label,
click() {
controlWindow.webContents.send('execute-device-volume-shell', item.value)
},
}
})
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
}
}

View File

@ -1,56 +1,41 @@
import { BrowserWindow, ipcMain, Menu } from 'electron' import { BrowserWindow, ipcMain } from 'electron'
import { initControlWindow, openControlWindow } from './helpers/index.js' import { initControlWindow, openControlWindow } from './helpers/index.js'
import { devices, gnirehtet, rotation, volume } from './events/index.js'
function onControlMounted(controlWindow) {
ipcMain.on('language-change', (event, data) => {
controlWindow.webContents.send('language-change', data)
})
ipcMain.on('theme-change', (event, data) => {
controlWindow.webContents.send('theme-change', data)
})
rotation(controlWindow)
devices(controlWindow)
volume(controlWindow)
gnirehtet(controlWindow)
}
export default (mainWindow) => { export default (mainWindow) => {
let controlWindow let controlWindow
ipcMain.on('open-control-window', (event, data) => { ipcMain.handle('open-control-window', (event, data) => {
controlWindow = BrowserWindow.getAllWindows().find( controlWindow = BrowserWindow.getAllWindows().find(
win => win.customId === 'control', win => win.customId === 'control',
) )
if (!controlWindow) { if (controlWindow) {
controlWindow = initControlWindow(mainWindow)
ipcMain.on('control-mounted', () => {
openControlWindow(controlWindow, data) openControlWindow(controlWindow, data)
})
return false return false
} }
controlWindow = initControlWindow(mainWindow)
ipcMain.on('control-mounted', () => {
onControlMounted(controlWindow)
openControlWindow(controlWindow, data) openControlWindow(controlWindow, data)
}) })
ipcMain.on('language-change', (event, data) => {
if (controlWindow) {
controlWindow.webContents.send('language-change', data)
}
})
ipcMain.on('theme-change', (event, data) => {
if (controlWindow) {
controlWindow.webContents.send('theme-change', data)
}
})
ipcMain.on('show-device-list', (event, deviceList) => {
const template = deviceList.map((item) => {
let label = item.$remark || item.$name
if (item.$wifi) {
label += ` (WIFI)`
}
return {
label,
click: () => {
openControlWindow(controlWindow, item)
},
}
})
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
}) })
} }

View File

@ -119,14 +119,14 @@ const record = async (
) )
} }
const mirrorGroup = async (serial, { open = 1, ...options } = {}) => { const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => {
const overlayDisplay const overlayDisplay
= appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`) = appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`)
|| appStore.get('scrcpy.global.--display-overlay') || appStore.get('scrcpy.global.--display-overlay')
|| '1080x1920/320,secure' || '1080x1920/320,secure'
const command = `settings put global overlay_display_devices "${[ const command = `settings put global overlay_display_devices "${[
...Array.from({ length: open }).keys(), ...Array.from({ length: openNum }).keys(),
] ]
.map(() => overlayDisplay) .map(() => overlayDisplay)
.join(';')}"` .join(';')}"`

View File

@ -10,5 +10,5 @@
} }
}, },
"exclude": ["node_modules", "dist", "dist-electron", "dist-release"], "exclude": ["node_modules", "dist", "dist-electron", "dist-release"],
"include": ["src", "electron"] "include": ["src", "electron", "control"]
} }

View File

@ -1,6 +1,11 @@
<template> <template>
<el-dropdown> <el-dropdown :disabled="floating">
<div class="" :title="device.$gnirehtetLoadingText" @click="handleStart"> <div
class=""
:title="device.$gnirehtetLoadingText"
@click="handleStart"
@mouseenter="onMouseenter"
>
<slot :loading="device.$gnirehtetLoading" /> <slot :loading="device.$gnirehtetLoading" />
</div> </div>
@ -16,6 +21,7 @@
<script> <script>
import { sleep } from '$/utils' import { sleep } from '$/utils'
import { adaptiveMessage } from '$/utils/modal/index.js'
export default { export default {
props: { props: {
@ -23,6 +29,10 @@ export default {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
floating: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return {} return {}
@ -36,10 +46,41 @@ export default {
} }
}, },
methods: { methods: {
onMouseenter() {
if (!this.floating) {
return false
}
if (!this.device.$gnirehtetLoading) {
return false
}
window.electron.ipcRenderer.once(
'stop-device-gnirehtet',
(event, data) => {
this.handleStop()
},
)
const options = [
{
label: window.t('device.control.gnirehtet.stop'),
value: 'stop-device-gnirehtet',
},
]
window.electron.ipcRenderer.invoke('open-device-gnirehtet-menu', {
options,
})
},
preferenceData(...args) { preferenceData(...args) {
return this.$store.preference.getData(...args) return this.$store.preference.getData(...args)
}, },
async handleStart() { async handleStart() {
if (this.device.$gnirehtetLoading) {
return false
}
this.device.$gnirehtetLoadingText = this.$t( this.device.$gnirehtetLoadingText = this.$t(
'device.control.gnirehtet.running', 'device.control.gnirehtet.running',
) )
@ -48,7 +89,10 @@ export default {
try { try {
await this.$gnirehtet.run(this.device.id) await this.$gnirehtet.run(this.device.id)
await sleep() await sleep()
this.$message.success(this.$t('device.control.gnirehtet.start.success')) adaptiveMessage(this.$t('device.control.gnirehtet.start.success'), {
system: this.floating,
type: 'success',
})
} }
catch (error) { catch (error) {
this.$message.warning(error.message || 'Start service failure') this.$message.warning(error.message || 'Start service failure')
@ -64,10 +108,16 @@ export default {
try { try {
await this.$gnirehtet.stop(this.device.id) await this.$gnirehtet.stop(this.device.id)
await sleep() await sleep()
this.$message.success(this.$t('common.success')) adaptiveMessage(this.$t('common.success'), {
system: this.floating,
type: 'success',
})
} }
catch (error) { catch (error) {
this.$message.warning(error.message || 'Stop service failure') adaptiveMessage(error.message || 'Stop service failure', {
system: this.floating,
type: 'warning',
})
} }
this.device.$gnirehtetLoading = false this.device.$gnirehtetLoading = false

View File

@ -1,8 +1,7 @@
<template> <template>
<el-dropdown :disabled="loading" @command="handleCommand"> <el-dropdown :disabled="loading || floating" @command="handleCommand">
<div class=""> <slot :loading :trigger="handleTrigger" />
<slot :loading="loading" />
</div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
@ -26,6 +25,10 @@ export default {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
floating: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -39,27 +42,51 @@ export default {
disable: disable:
'content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0', 'content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0',
}, },
options: [ }
},
computed: {
options() {
const value = [
{ {
label: 'device.control.rotation.vertically', label: this.$t('device.control.rotation.vertically'),
value: 'vertically', value: 'vertically',
}, },
{ {
label: 'device.control.rotation.horizontally', label: this.$t('device.control.rotation.horizontally'),
value: 'horizontally', value: 'horizontally',
}, },
{ {
label: 'device.control.rotation.auto', label: this.$t('device.control.rotation.auto'),
value: 'auto', value: 'auto',
}, },
{ {
label: 'device.control.rotation.disable', label: this.$t('device.control.rotation.disable'),
value: 'disable', value: 'disable',
}, },
], ]
} return value
},
}, },
methods: { methods: {
handleTrigger() {
if (!this.floating) {
return false
}
window.electron.ipcRenderer.once(
'execute-device-rotation-shell',
(event, data) => {
this.handleCommand(data)
},
)
const options = toRaw(this.options)
window.electron.ipcRenderer.invoke('open-device-rotation-menu', {
options,
})
},
async handleCommand(value) { async handleCommand(value) {
this.loading = true this.loading = true

View File

@ -1,20 +1,18 @@
<template> <template>
<el-dropdown @command="handleCommand"> <el-dropdown :disabled="loading" @command="handleCommand">
<div class=""> <slot :loading />
<slot :loading="loading" />
</div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<template v-if="!loading"> <template v-if="!loading">
<el-dropdown-item v-for="item of 4" :key="item" :command="item"> <el-dropdown-item
{{ $t("device.control.mirror-group.open", { num: item }) }} v-for="item of options"
:key="item"
:command="item.value"
:title="item.title"
>
{{ item.label }}
</el-dropdown-item> </el-dropdown-item>
</template> </template>
<el-dropdown-item v-else command="close">
<span class="" :title="$t('device.control.mirror-group.close.tips')">
{{ $t("device.control.mirror-group.close") }}
</span>
</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -35,6 +33,29 @@ export default {
loading: false, loading: false,
} }
}, },
computed: {
options() {
const value = []
if (this.loading) {
value.push({
label: window.t('device.control.mirror-group.close'),
value: 'close',
title: window.t('device.control.mirror-group.close.tips'),
})
}
else {
value.push(
...[1, 2, 3, 4].map(item => ({
label: this.$t('device.control.mirror-group.open', { num: item }),
value: item,
})),
)
}
return value
},
},
methods: { methods: {
scrcpyArgs(...args) { scrcpyArgs(...args) {
return this.$store.preference.getScrcpyArgs(...args) return this.$store.preference.getScrcpyArgs(...args)
@ -55,7 +76,7 @@ export default {
try { try {
const res = await this.$scrcpy.mirrorGroup(this.device.id, { const res = await this.$scrcpy.mirrorGroup(this.device.id, {
open: command, openNum: command,
title: ({ displayId }) => title: ({ displayId }) =>
`${this.$store.device.getLabel( `${this.$store.device.getLabel(
this.device, this.device,

View File

@ -1,8 +1,11 @@
<template> <template>
<el-dropdown :hide-on-click="false" @command="handleCommand"> <el-dropdown
<div class=""> :hide-on-click="false"
<slot :loading="loading" /> :disabled="loading || floating"
</div> @command="handleCommand"
>
<slot :loading :trigger="handleTrigger" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
@ -24,6 +27,10 @@ export default {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
floating: {
type: Boolean,
default: false,
},
}, },
data() { data() {
return { return {
@ -33,23 +40,46 @@ export default {
'volume-up': 'input keyevent KEYCODE_VOLUME_UP', 'volume-up': 'input keyevent KEYCODE_VOLUME_UP',
'volume-mute': 'input keyevent KEYCODE_VOLUME_MUTE', 'volume-mute': 'input keyevent KEYCODE_VOLUME_MUTE',
}, },
options: [ }
},
computed: {
options() {
const value = [
{ {
label: 'device.control.volume-up.name', label: this.$t('device.control.volume-up.name'),
value: 'volume-up', value: 'volume-up',
}, },
{ {
label: 'device.control.volume-down.name', label: this.$t('device.control.volume-down.name'),
value: 'volume-down', value: 'volume-down',
}, },
{ {
label: 'device.control.volume-mute.name', label: this.$t('device.control.volume-mute.name'),
value: 'volume-mute', value: 'volume-mute',
}, },
], ]
} return value
},
}, },
methods: { methods: {
handleTrigger() {
if (!this.floating) {
return false
}
window.electron.ipcRenderer.once(
'execute-device-volume-shell',
(event, data) => {
this.handleCommand(data)
},
)
const options = toRaw(this.options)
window.electron.ipcRenderer.invoke('open-device-volume-menu', {
options,
})
},
async handleCommand(value) { async handleCommand(value) {
this.loading = true this.loading = true

View File

@ -22,14 +22,9 @@
v-bind="{ v-bind="{
device, device,
floating, floating,
...(item.command
? {
onClick: () => handleShell(item),
}
: {}),
}" }"
> >
<template #default="{ loading = false } = {}"> <template #default="{ loading = false, trigger } = {}">
<el-button <el-button
type="primary" type="primary"
plain plain
@ -37,6 +32,7 @@
:disabled="device.$unauthorized" :disabled="device.$unauthorized"
:title="$t(item.tips || item.label)" :title="$t(item.tips || item.label)"
:loading="loading" :loading="loading"
@click="handleClick(item, trigger)"
> >
<template #icon> <template #icon>
<svg-icon <svg-icon
@ -71,7 +67,7 @@
import Application from './Application/index.vue' import Application from './Application/index.vue'
import FileManage from './FileManage/index.vue' import FileManage from './FileManage/index.vue'
import Gnirehtet from './Gnirehtet/index.vue' import Gnirehtet from './Gnirehtet/index.vue'
import MirrorGroup from './MirrorGroup/index.vue' import Synergy from './Synergy/index.vue'
import Rotation from './Rotation/index.vue' import Rotation from './Rotation/index.vue'
import Screenshot from './Screenshot/index.vue' import Screenshot from './Screenshot/index.vue'
import Shell from './Shell/index.vue' import Shell from './Shell/index.vue'
@ -83,7 +79,7 @@ export default {
Screenshot, Screenshot,
Application, Application,
Gnirehtet, Gnirehtet,
MirrorGroup, Synergy,
Rotation, Rotation,
Volume, Volume,
FileManage, FileManage,
@ -110,33 +106,28 @@ export default {
label: 'device.control.switch', label: 'device.control.switch',
elIcon: 'Switch', elIcon: 'Switch',
command: 'input keyevent 187', command: 'input keyevent 187',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.home', label: 'device.control.home',
svgIcon: 'home', svgIcon: 'home',
command: 'input keyevent 3', command: 'input keyevent 3',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.return', label: 'device.control.return',
elIcon: 'Back', elIcon: 'Back',
command: 'input keyevent 4', command: 'input keyevent 4',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.notification', label: 'device.control.notification',
elIcon: 'Notification', elIcon: 'Notification',
command: 'cmd statusbar expand-notifications', command: 'cmd statusbar expand-notifications',
tips: 'device.control.notification.tips', tips: 'device.control.notification.tips',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.power', label: 'device.control.power',
elIcon: 'SwitchButton', elIcon: 'SwitchButton',
command: 'input keyevent 26', command: 'input keyevent 26',
tips: 'device.control.power.tips', tips: 'device.control.power.tips',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.rotation.name', label: 'device.control.rotation.name',
@ -152,35 +143,35 @@ export default {
label: 'device.control.capture', label: 'device.control.capture',
elIcon: 'Crop', elIcon: 'Crop',
component: 'Screenshot', component: 'Screenshot',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.reboot', label: 'device.control.reboot',
elIcon: 'RefreshLeft', elIcon: 'RefreshLeft',
command: 'reboot', command: 'reboot',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.install', label: 'device.control.install',
svgIcon: 'install', svgIcon: 'install',
component: 'Application', component: 'Application',
visibleList: ['floating'],
}, },
{ {
label: 'device.control.file.name', label: 'device.control.file.name',
svgIcon: 'file-send', svgIcon: 'file-send',
component: 'FileManage', component: 'FileManage',
hiddenKeys: ['floating'],
}, },
{ {
label: 'device.control.shell.name', label: 'device.control.shell.name',
svgIcon: 'command', svgIcon: 'command',
component: 'Shell', component: 'Shell',
tips: 'device.control.shell.tips', tips: 'device.control.shell.tips',
hiddenKeys: ['floating'],
}, },
{ {
label: 'device.task.name', label: 'device.task.name',
elIcon: 'Clock', elIcon: 'Clock',
component: 'Tasks', component: 'Tasks',
hiddenKeys: ['floating'],
}, },
{ {
label: 'device.control.gnirehtet', label: 'device.control.gnirehtet',
@ -192,15 +183,16 @@ export default {
label: 'device.control.mirror-group.name', label: 'device.control.mirror-group.name',
svgIcon: 'multi-screen', svgIcon: 'multi-screen',
iconClass: '', iconClass: '',
component: 'MirrorGroup', component: 'Synergy',
tips: 'device.control.mirror-group.tips', tips: 'device.control.mirror-group.tips',
hiddenKeys: ['floating'],
}, },
] ]
return value.filter( const handler = item =>
item => !(item.hiddenKeys || []).some(key => this.$props[key])
!this.floating || (item.visibleList ?? []).includes('floating'),
) return value.filter(item => handler(item))
}, },
}, },
methods: { methods: {
@ -210,8 +202,15 @@ export default {
handleNext() { handleNext() {
this.$refs.scrollableRef.scrollForward() this.$refs.scrollableRef.scrollForward()
}, },
handleShell(row) { handleClick(row, trigger) {
if (row?.command) {
this.$adb.deviceShell(this.device.id, row.command) this.$adb.deviceShell(this.device.id, row.command)
return false
}
if (trigger) {
trigger(row)
}
}, },
}, },
} }

View File

@ -80,5 +80,5 @@ export function openFloatControl(deviceInfo) {
return false return false
} }
window.electron.ipcRenderer.send('open-control-window', deviceInfo) window.electron.ipcRenderer.invoke('open-control-window', deviceInfo)
} }