mirror of
https://github.com/lights0123/n-link.git
synced 2026-08-08 18:53:30 +00:00
feat(core): port DeviceSelect and DeviceQueue to Vue 3 + element-plus
@PropSync -> defineModel, element-ui popover -> element-plus (v-model:visible), slot="reference" -> #reference, and webpack tilde image paths -> ESM imports.
This commit is contained in:
parent
3ee9925f91
commit
fec8fa2695
|
|
@ -20,25 +20,28 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-2 flex-shrink-0">
|
<div class="ml-2 flex-shrink-0">
|
||||||
<button class="focus:outline-none" :disabled="i===0" :class="i===0 && 'cursor-not-allowed'"
|
<button class="focus:outline-none" :disabled="i===0" :class="i===0 && 'cursor-not-allowed'"
|
||||||
@click="device.queue.splice(i, 1)">
|
@click="device.queue?.splice(i, 1)">
|
||||||
<img src="~feather-icons/dist/icons/x-circle.svg" :class="i===0 && 'opacity-25'"/>
|
<img :src="xCircle" :class="i===0 && 'opacity-25'"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<svg slot="reference" viewBox="-1 -1 2 2" class="circle focus:outline-none">
|
<template #reference>
|
||||||
<circle r="1" class="bg"/>
|
<svg viewBox="-1 -1 2 2" class="circle focus:outline-none">
|
||||||
<path class="fg" :d="pathData"/>
|
<circle r="1" class="bg"/>
|
||||||
</svg>
|
<path class="fg" :d="pathData"/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {Component, Prop, Vue} from 'vue-property-decorator';
|
import {computed} from 'vue';
|
||||||
import ElPopover from 'element-ui/packages/popover/src/main.vue';
|
import {ElPopover} from 'element-plus';
|
||||||
import 'element-ui/lib/theme-chalk/popover.css';
|
import 'element-plus/es/components/popover/style/css';
|
||||||
import type {Device} from "./devices";
|
import type {Device} from './devices';
|
||||||
|
import xCircle from 'feather-icons/dist/icons/x-circle.svg';
|
||||||
|
|
||||||
function getCoordinatesForPercent(percent: number) {
|
function getCoordinatesForPercent(percent: number) {
|
||||||
const x = Math.cos(2 * Math.PI * percent);
|
const x = Math.cos(2 * Math.PI * percent);
|
||||||
|
|
@ -51,45 +54,45 @@ function trimPath(path: string) {
|
||||||
return path.split(/[\\/]/).pop() as string;
|
return path.split(/[\\/]/).pop() as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({components: {ElPopover}})
|
const props = defineProps<{
|
||||||
export default class FileView extends Vue {
|
device: Device;
|
||||||
@Prop({type: Object, required: true}) private device!: Device;
|
}>();
|
||||||
|
|
||||||
get pathData() {
|
const pathData = computed(() => {
|
||||||
const {progress, queue} = this.device;
|
const {progress, queue} = props.device;
|
||||||
const length = (queue?.length || 0);
|
const length = (queue?.length || 0);
|
||||||
let percent = progress ? (1 - progress.remaining / progress.total) : 1 / (length);
|
let percent = progress ? (1 - progress.remaining / progress.total) : 1 / (length);
|
||||||
if (!Number.isFinite(percent)) percent = 0;
|
if (!Number.isFinite(percent)) percent = 0;
|
||||||
|
|
||||||
const [startX, startY] = getCoordinatesForPercent(0);
|
const [startX, startY] = getCoordinatesForPercent(0);
|
||||||
const [endX, endY] = getCoordinatesForPercent(percent);
|
const [endX, endY] = getCoordinatesForPercent(percent);
|
||||||
|
|
||||||
const largeArcFlag = percent > .5 ? 1 : 0;
|
const largeArcFlag = percent > .5 ? 1 : 0;
|
||||||
return [
|
return [
|
||||||
`M ${startX} ${startY}`,
|
`M ${startX} ${startY}`,
|
||||||
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`,
|
`A 1 1 0 ${largeArcFlag} 1 ${endX} ${endY}`,
|
||||||
`L 0 0`,
|
`L 0 0`,
|
||||||
].join(' ');
|
].join(' ');
|
||||||
}
|
});
|
||||||
|
|
||||||
get queue() {
|
const queue = computed(() => {
|
||||||
if (!this.device.queue) return [];
|
if (!props.device.queue) return [];
|
||||||
return this.device.queue.map(item => {
|
return props.device.queue.map(item => {
|
||||||
let desc = '';
|
let desc = '';
|
||||||
if (item.action === 'download') desc = `Download ${trimPath(item.path[0])}`;
|
if (item.action === 'download') desc = `Download ${trimPath(item.path[0])}`;
|
||||||
else if (item.action === 'upload') desc = `Upload ${'src' in item ? trimPath(item.src) : item.file.name}`;
|
else if (item.action === 'upload') desc = `Upload ${'src' in item ? trimPath(item.src) : item.file.name}`;
|
||||||
else if (item.action === 'uploadOs') desc = 'Upload OS';
|
else if (item.action === 'uploadOs') desc = 'Upload OS';
|
||||||
else if (item.action === 'deleteFile') desc = `Delete file ${item.path}`;
|
else if (item.action === 'deleteFile') desc = `Delete file ${item.path}`;
|
||||||
else if (item.action === 'deleteDir') desc = `Delete directory ${item.path}`;
|
else if (item.action === 'deleteDir') desc = `Delete directory ${item.path}`;
|
||||||
else if (item.action === 'createDir') desc = `Create directory ${item.path}`;
|
else if (item.action === 'createDir') desc = `Create directory ${item.path}`;
|
||||||
else if (item.action === 'copy') desc = `Copy file ${item.src} to ${item.dest}`;
|
else if (item.action === 'copy') desc = `Copy file ${item.src} to ${item.dest}`;
|
||||||
else if (item.action === 'move') desc = `Move file ${item.src} to ${item.dest}`;
|
else if (item.action === 'move') desc = `Move file ${item.src} to ${item.dest}`;
|
||||||
return {
|
return {
|
||||||
desc,
|
id: item.id,
|
||||||
};
|
desc,
|
||||||
});
|
};
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,33 @@
|
||||||
<div class="header border-b px-2 py-2 flex w-full">
|
<div class="header border-b px-2 py-2 flex w-full">
|
||||||
<button @click="$devices.enumerate()" class="flex-shrink-0 mr-2 focus:outline-none"
|
<button @click="$devices.enumerate()" class="flex-shrink-0 mr-2 focus:outline-none"
|
||||||
:class="$devices.enumerating && 'cursor-not-allowed opacity-25'" :disabled="$devices.enumerating">
|
:class="$devices.enumerating && 'cursor-not-allowed opacity-25'" :disabled="$devices.enumerating">
|
||||||
<img src="~feather-icons/dist/icons/refresh-cw.svg" class="w-5"/>
|
<img :src="refreshCw" class="w-5"/>
|
||||||
<div v-if="scanHint && Object.keys($devices.devices).length === 0" class="p-4 refresh-popup">
|
<div v-if="scanHint && Object.keys($devices.devices).length === 0" class="p-4 refresh-popup">
|
||||||
Click to connect a device
|
Click to connect a device
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<el-popover width="239" :visible-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model="active"
|
<el-popover width="239" :show-arrow="false" popper-class="focus:outline-none dev-select-pop" v-model:visible="active"
|
||||||
class="w-full overflow-hidden">
|
class="w-full overflow-hidden">
|
||||||
<div slot="reference" class="relative w-full focus:outline-none">
|
<template #reference>
|
||||||
<div
|
<div class="relative w-full focus:outline-none">
|
||||||
class="block w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-3/2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline h-8 truncate">
|
<div
|
||||||
<span v-if="selectedCalculator">
|
class="block w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-3/2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline h-8 truncate">
|
||||||
<small class="tabular-nums">{{ selectedCalculator }}</small>
|
<span v-if="selectedCalculator">
|
||||||
<span v-if="calc && calc.info"> {{ calc.info.name }}</span>
|
<small class="tabular-nums">{{ selectedCalculator }}</small>
|
||||||
<span v-else> {{ calc.name }}</span>
|
<span v-if="calc && calc.info"> {{ calc.info.name }}</span>
|
||||||
</span>
|
<span v-else> {{ calc!.name }}</span>
|
||||||
<span v-else class="text-gray-700 text-sm">
|
</span>
|
||||||
Select a device...
|
<span v-else class="text-gray-700 text-sm">
|
||||||
</span>
|
Select a device...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
||||||
|
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||||
|
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
</template>
|
||||||
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
|
||||||
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(device, id) in $devices.devices" :key="id"
|
<li v-for="(device, id) in $devices.devices" :key="id"
|
||||||
class="p-2 hover:bg-blue-500 hover:text-white w-full cursor-pointer" @click="select(id)">
|
class="p-2 hover:bg-blue-500 hover:text-white w-full cursor-pointer" @click="select(id)">
|
||||||
|
|
@ -42,30 +44,36 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import {Component, Prop, PropSync, Vue} from 'vue-property-decorator';
|
import {computed, inject, ref} from 'vue';
|
||||||
import ElPopover from 'element-ui/packages/popover/src/main.vue';
|
import {ElPopover} from 'element-plus';
|
||||||
import 'element-ui/lib/theme-chalk/popover.css';
|
import 'element-plus/es/components/popover/style/css';
|
||||||
|
import {DEVICES_KEY, type GenericDevices} from './devices';
|
||||||
|
import refreshCw from 'feather-icons/dist/icons/refresh-cw.svg';
|
||||||
|
|
||||||
@Component({components: {ElPopover}})
|
withDefaults(defineProps<{
|
||||||
export default class DeviceSelect extends Vue {
|
scanHint?: boolean;
|
||||||
@PropSync('selected', {type: [String]}) selectedCalculator!: string | null;
|
}>(), {
|
||||||
@Prop({type: Boolean, default: false}) scanHint!: boolean;
|
scanHint: false,
|
||||||
active = false;
|
});
|
||||||
|
|
||||||
select(dev: string) {
|
const selectedCalculator = defineModel<string | null>('selected');
|
||||||
this.selectedCalculator = dev;
|
|
||||||
this.active = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get calc() {
|
const devices = inject(DEVICES_KEY) as GenericDevices;
|
||||||
return this.selectedCalculator && this.$devices.devices[this.selectedCalculator];
|
|
||||||
}
|
|
||||||
|
|
||||||
get anyCalcs() {
|
const active = ref(false);
|
||||||
return !!Object.keys(this.$devices.devices).length;
|
|
||||||
}
|
function select(dev: string) {
|
||||||
|
selectedCalculator.value = dev;
|
||||||
|
active.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calc = computed(() => {
|
||||||
|
const id = selectedCalculator.value;
|
||||||
|
return id ? devices.devices[id] : undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const anyCalcs = computed(() => !!Object.keys(devices.devices).length);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue