mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-08 17:16:52 +08:00
feat: 前端支持以拖入的方式导入插件
This commit is contained in:
@@ -1,13 +1,15 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, ref, type PropType } from 'vue'
|
import { computed, defineComponent, onMounted, onUnmounted, ref, useTemplateRef, type PropType } from 'vue'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { NButton, NCollapseTransition, NInputNumber, NSwitch, NTag, useMessage, NEmpty, NIcon } from 'naive-ui'
|
import { NButton, NCollapseTransition, NInputNumber, NSwitch, NTag, useMessage, NEmpty, NIcon } from 'naive-ui'
|
||||||
import { commands, events, PluginInfo } from '../../../bindings.ts'
|
import { commands, events, PluginInfo } from '../../../bindings.ts'
|
||||||
import { PhPlusCircle } from '@phosphor-icons/vue'
|
import { PhPlusCircle } from '@phosphor-icons/vue'
|
||||||
import { path } from '@tauri-apps/api'
|
import { path } from '@tauri-apps/api'
|
||||||
import { appDataDir } from '@tauri-apps/api/path'
|
import { appDataDir } from '@tauri-apps/api/path'
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
const allowedPluginExtensions = new Set(['.dll', '.so', '.dylib'])
|
||||||
|
|
||||||
const pluginInfos = ref<Map<string, PluginInfo>>(new Map())
|
const pluginInfos = ref<Map<string, PluginInfo>>(new Map())
|
||||||
|
|
||||||
@@ -15,6 +17,8 @@ const sortedPluginInfos = computed<PluginInfo[]>(() =>
|
|||||||
Array.from(pluginInfos.value.values()).sort((a, b) => b.priority - a.priority),
|
Array.from(pluginInfos.value.values()).sort((a, b) => b.priority - a.priority),
|
||||||
)
|
)
|
||||||
const expandedPluginIds = ref<Set<string>>(new Set())
|
const expandedPluginIds = ref<Set<string>>(new Set())
|
||||||
|
const dropZoneRef = useTemplateRef('dropZoneRef')
|
||||||
|
const isDragOverDropZone = ref<boolean>(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const infos = await commands.getPluginInfos()
|
const infos = await commands.getPluginInfos()
|
||||||
@@ -29,6 +33,7 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let unListenPluginEvent: (() => void) | undefined
|
let unListenPluginEvent: (() => void) | undefined
|
||||||
|
let unListenDragDropEvent: (() => void) | undefined
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
events.pluginEvent
|
events.pluginEvent
|
||||||
.listen(({ payload: { event, data } }) => {
|
.listen(({ payload: { event, data } }) => {
|
||||||
@@ -53,11 +58,97 @@ onMounted(() => {
|
|||||||
.then((unListenFn) => {
|
.then((unListenFn) => {
|
||||||
unListenPluginEvent = unListenFn
|
unListenPluginEvent = unListenFn
|
||||||
})
|
})
|
||||||
|
getCurrentWindow()
|
||||||
|
.onDragDropEvent(({ payload }) => {
|
||||||
|
if (payload.type === 'leave') {
|
||||||
|
isDragOverDropZone.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === 'enter' || payload.type === 'over') {
|
||||||
|
isDragOverDropZone.value = isPointInDropZone(payload.position.x, payload.position.y)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const droppingOnDropZone = isPointInDropZone(payload.position.x, payload.position.y)
|
||||||
|
isDragOverDropZone.value = false
|
||||||
|
if (!droppingOnDropZone) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addPluginsByPaths(payload.paths)
|
||||||
})
|
})
|
||||||
|
.then((unListenFn) => {
|
||||||
|
unListenDragDropEvent = unListenFn
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
unListenPluginEvent?.()
|
unListenPluginEvent?.()
|
||||||
|
unListenDragDropEvent?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function getFileName(pluginPath: string): string {
|
||||||
|
const normalizedPath = pluginPath.replace(/\\/g, '/')
|
||||||
|
const parts = normalizedPath.split('/')
|
||||||
|
const filename = parts[parts.length - 1]
|
||||||
|
return filename === '' ? pluginPath : filename
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileExtension(pluginPath: string): string {
|
||||||
|
const filename = getFileName(pluginPath)
|
||||||
|
const dotIndex = filename.lastIndexOf('.')
|
||||||
|
if (dotIndex <= 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return filename.slice(dotIndex).toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPluginDynamicLibrary(pluginPath: string): boolean {
|
||||||
|
return allowedPluginExtensions.has(getFileExtension(pluginPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPointInDropZone(rawX: number, rawY: number): boolean {
|
||||||
|
const dropZone = dropZoneRef.value
|
||||||
|
if (dropZone === null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = dropZone.getBoundingClientRect()
|
||||||
|
const dpr = window.devicePixelRatio || 1
|
||||||
|
|
||||||
|
const candidates: Array<[number, number]> = [[rawX, rawY]]
|
||||||
|
if (dpr !== 1) {
|
||||||
|
candidates.push([rawX / dpr, rawY / dpr])
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates.some(([x, y]) => x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addPluginByPath(pluginPath: string) {
|
||||||
|
const result = await commands.addPlugin(pluginPath)
|
||||||
|
const filename = getFileName(pluginPath)
|
||||||
|
|
||||||
|
if (result.status === 'error') {
|
||||||
|
console.error(result.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message.success(`添加插件成功: ${filename}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addPluginsByPaths(pluginPaths: string[]) {
|
||||||
|
for (const pluginPath of pluginPaths) {
|
||||||
|
const filename = getFileName(pluginPath)
|
||||||
|
if (!isPluginDynamicLibrary(pluginPath)) {
|
||||||
|
message.warning(`跳过非插件文件: ${filename}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await addPluginByPath(pluginPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function addPlugin() {
|
async function addPlugin() {
|
||||||
const selectedPath = await open({
|
const selectedPath = await open({
|
||||||
directory: false,
|
directory: false,
|
||||||
@@ -68,13 +159,7 @@ async function addPlugin() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await commands.addPlugin(selectedPath)
|
await addPluginByPath(selectedPath)
|
||||||
if (result.status === 'error') {
|
|
||||||
console.error(result.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message.success('添加插件成功')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showPluginConfigInFileManager() {
|
async function showPluginConfigInFileManager() {
|
||||||
@@ -282,8 +367,12 @@ const PluginCard = defineComponent({
|
|||||||
<PluginCard v-for="pluginInfo in sortedPluginInfos" :key="pluginInfo.path" :plugin-info="pluginInfo" />
|
<PluginCard v-for="pluginInfo in sortedPluginInfos" :key="pluginInfo.path" :plugin-info="pluginInfo" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex mt-2">
|
<div class="mt-2 flex items-center gap-2">
|
||||||
<n-button class="mr-auto" size="small" type="primary" @click="addPlugin">
|
<div
|
||||||
|
ref="dropZoneRef"
|
||||||
|
class="flex items-center gap-2 rounded-md border border-dashed px-2 py-1 transition-colors"
|
||||||
|
:class="isDragOverDropZone ? 'border-sky-4 bg-sky-1/70' : 'border-gray-3 bg-transparent'">
|
||||||
|
<n-button size="small" type="primary" @click="addPlugin">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon size="20">
|
<n-icon size="20">
|
||||||
<PhPlusCircle />
|
<PhPlusCircle />
|
||||||
@@ -291,8 +380,10 @@ const PluginCard = defineComponent({
|
|||||||
</template>
|
</template>
|
||||||
添加插件
|
添加插件
|
||||||
</n-button>
|
</n-button>
|
||||||
|
<span class="text-xs text-gray-6">也可以拖入此处导入</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<n-button size="small" @click="showPluginConfigInFileManager">打开配置目录</n-button>
|
<n-button class="ml-auto" size="small" @click="showPluginConfigInFileManager">打开配置目录</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user