mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 12:10:18 +03:00
feat(dashboard): Add display mode configuration (#1184)
This commit is contained in:
parent
6b84594885
commit
cd695ab96d
32 changed files with 916 additions and 160 deletions
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import dayjs from '@/plugins/dayjs'
|
||||
import dayjs from '@/plugins/dayjs.ts'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import dayjs from '@/plugins/dayjs'
|
||||
import dayjs from '@/plugins/dayjs.ts'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import dayjs from '@/plugins/dayjs'
|
||||
import dayjs from '@/plugins/dayjs.ts'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
</script>
|
||||
|
|
77
src/components/Dashboard/Views/Grid/GridTorrent.vue
Normal file
77
src/components/Dashboard/Views/Grid/GridTorrent.vue
Normal file
|
@ -0,0 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
import { DashboardPropertyType } from '@/constants/vuetorrent'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
import ItemAmount from '@/components/Dashboard/DashboardItems/ItemAmount.vue'
|
||||
import ItemChip from '@/components/Dashboard/DashboardItems/ItemChip.vue'
|
||||
import ItemData from '@/components/Dashboard/DashboardItems/ItemData.vue'
|
||||
import ItemDateTime from '@/components/Dashboard/DashboardItems/ItemDateTime.vue'
|
||||
import ItemDuration from '@/components/Dashboard/DashboardItems/ItemDuration.vue'
|
||||
import ItemPercent from '@/components/Dashboard/DashboardItems/ItemPercent.vue'
|
||||
import ItemRelativeTime from '@/components/Dashboard/DashboardItems/ItemRelativeTime.vue'
|
||||
import ItemSpeed from '@/components/Dashboard/DashboardItems/ItemSpeed.vue'
|
||||
import ItemText from '@/components/Dashboard/DashboardItems/ItemText.vue'
|
||||
|
||||
const props = defineProps<{ torrent: Torrent }>()
|
||||
|
||||
defineEmits<{
|
||||
onTorrentClick: [e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: Torrent]
|
||||
}>()
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
|
||||
const torrentProperties = computed(() => {
|
||||
let ppts = props.torrent.progress === 1 ? vuetorrentStore.doneGridProperties : vuetorrentStore.busyGridProperties
|
||||
|
||||
return ppts.filter(ppt => ppt.active).sort((a, b) => a.order - b.order)
|
||||
})
|
||||
|
||||
const getComponent = (type: DashboardPropertyType) => {
|
||||
switch (type) {
|
||||
case DashboardPropertyType.AMOUNT:
|
||||
return ItemAmount
|
||||
case DashboardPropertyType.CHIP:
|
||||
return ItemChip
|
||||
case DashboardPropertyType.DATA:
|
||||
return ItemData
|
||||
case DashboardPropertyType.DATETIME:
|
||||
return ItemDateTime
|
||||
case DashboardPropertyType.DURATION:
|
||||
return ItemDuration
|
||||
case DashboardPropertyType.PERCENT:
|
||||
return ItemPercent
|
||||
case DashboardPropertyType.RELATIVE:
|
||||
return ItemRelativeTime
|
||||
case DashboardPropertyType.SPEED:
|
||||
return ItemSpeed
|
||||
case DashboardPropertyType.TEXT:
|
||||
default:
|
||||
return ItemText
|
||||
}
|
||||
}
|
||||
|
||||
const isTorrentSelected = computed(() => dashboardStore.isTorrentInSelection(props.torrent.hash))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card :class="`sideborder ${torrent.state} pointer`" height="100%"
|
||||
:color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined"
|
||||
@click="$emit('onTorrentClick', $event, torrent)">
|
||||
<v-card-title class="text-wrap text-subtitle-1 pt-1 pb-0">{{ torrent.name }}</v-card-title>
|
||||
<v-card-text>
|
||||
<div class="d-flex gap flex-wrap">
|
||||
<component :is="getComponent(ppt.type)" :torrent="torrent" v-bind="ppt.props"
|
||||
v-for="ppt in torrentProperties" />
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gap {
|
||||
gap: 8px 16px;
|
||||
}
|
||||
</style>
|
49
src/components/Dashboard/Views/Grid/GridView.vue
Normal file
49
src/components/Dashboard/Views/Grid/GridView.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import GridTorrent from '@/components/Dashboard/Views/Grid/GridTorrent.vue'
|
||||
import { useDashboardStore } from '@/stores/dashboard.ts'
|
||||
import { Torrent as TorrentType } from '@/types/vuetorrent'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
defineProps<{
|
||||
paginatedTorrents: TorrentType[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
onCheckboxClick: [torrent: TorrentType],
|
||||
onTorrentClick: [e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: TorrentType],
|
||||
onTorrentDblClick: [torrent: TorrentType],
|
||||
onTorrentRightClick: [e: MouseEvent, torrent: TorrentType],
|
||||
startPress: [e: Touch, torrent: TorrentType],
|
||||
endPress: []
|
||||
}>()
|
||||
|
||||
const display = useDisplay()
|
||||
const dashboardStore = useDashboardStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row id="torrentList">
|
||||
<v-col v-for="torrent in paginatedTorrents" cols="12" lg="3" md="4" sm="6" xl="2"
|
||||
:class="display.mobile ? 'pb-2' : 'pb-4'" class="pt-0"
|
||||
@contextmenu="$emit('onTorrentRightClick', $event, torrent)"
|
||||
@touchcancel="$emit('endPress')" @touchend="$emit('endPress')" @touchmove="$emit('endPress')"
|
||||
@touchstart="$emit('startPress', $event.touches.item(0)!, torrent)"
|
||||
@dblclick="$emit('onTorrentDblClick', torrent)">
|
||||
<div class="d-flex align-center" style="height: 100%; width: 100%">
|
||||
<v-expand-x-transition>
|
||||
<v-btn v-show="dashboardStore.isSelectionMultiple"
|
||||
:color="`torrent-${torrent.state}`"
|
||||
:icon="dashboardStore.isTorrentInSelection(torrent.hash) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
|
||||
class="mr-2" variant="text" @click="$emit('onCheckboxClick', torrent)" />
|
||||
</v-expand-x-transition>
|
||||
<GridTorrent :torrent="torrent" @onTorrentClick="(e, t) => $emit('onTorrentClick', e, t)" />
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#torrentList {
|
||||
background-color: unset;
|
||||
}
|
||||
</style>
|
|
@ -1,21 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import ItemAmount from '@/components/Dashboard/DashboardItems/ItemAmount.vue'
|
||||
import ItemChip from '@/components/Dashboard/DashboardItems/ItemChip.vue'
|
||||
import ItemData from '@/components/Dashboard/DashboardItems/ItemData.vue'
|
||||
import ItemDateTime from '@/components/Dashboard/DashboardItems/ItemDateTime.vue'
|
||||
import ItemDuration from '@/components/Dashboard/DashboardItems/ItemDuration.vue'
|
||||
import ItemPercent from '@/components/Dashboard/DashboardItems/ItemPercent.vue'
|
||||
import ItemRelativeTime from '@/components/Dashboard/DashboardItems/ItemRelativeTime.vue'
|
||||
import ItemSpeed from '@/components/Dashboard/DashboardItems/ItemSpeed.vue'
|
||||
import ItemText from '@/components/Dashboard/DashboardItems/ItemText.vue'
|
||||
import { DashboardPropertyType } from '@/constants/vuetorrent'
|
||||
import { doesCommand } from '@/helpers'
|
||||
import { useDashboardStore, useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
import ItemAmount from './DashboardItems/ItemAmount.vue'
|
||||
import ItemChip from './DashboardItems/ItemChip.vue'
|
||||
import ItemData from './DashboardItems/ItemData.vue'
|
||||
import ItemDateTime from './DashboardItems/ItemDateTime.vue'
|
||||
import ItemDuration from './DashboardItems/ItemDuration.vue'
|
||||
import ItemPercent from './DashboardItems/ItemPercent.vue'
|
||||
import ItemRelativeTime from './DashboardItems/ItemRelativeTime.vue'
|
||||
import ItemSpeed from './DashboardItems/ItemSpeed.vue'
|
||||
import ItemText from './DashboardItems/ItemText.vue'
|
||||
|
||||
const props = defineProps<{ torrent: Torrent }>()
|
||||
|
||||
defineEmits<{
|
||||
onTorrentClick: [e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: Torrent]
|
||||
}>()
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
|
||||
|
@ -25,15 +28,6 @@ const torrentProperties = computed(() => {
|
|||
return ppts.filter(ppt => ppt.active).sort((a, b) => a.order - b.order)
|
||||
})
|
||||
|
||||
function onClick(e: MouseEvent) {
|
||||
if (e.shiftKey) {
|
||||
dashboardStore.spanTorrentSelection(props.torrent.hash)
|
||||
} else if (doesCommand(e) || dashboardStore.isSelectionMultiple) {
|
||||
dashboardStore.isSelectionMultiple = true
|
||||
dashboardStore.toggleSelect(props.torrent.hash)
|
||||
}
|
||||
}
|
||||
|
||||
const getComponent = (type: DashboardPropertyType) => {
|
||||
switch (type) {
|
||||
case DashboardPropertyType.AMOUNT:
|
||||
|
@ -61,7 +55,9 @@ const isTorrentSelected = computed(() => dashboardStore.isTorrentInSelection(pro
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<v-card :class="`sideborder ${torrent.state} pointer`" :color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined" width="100%" @click="onClick">
|
||||
<v-card :class="`sideborder ${torrent.state} pointer`" width="100%"
|
||||
:color="isTorrentSelected ? `torrent-${torrent.state}-darken-3` : undefined"
|
||||
@click="$emit('onTorrentClick', $event, torrent)">
|
||||
<v-card-title class="text-wrap text-subtitle-1 pt-1 pb-0">{{ torrent.name }}</v-card-title>
|
||||
<v-card-text class="pa-2 pt-0">
|
||||
<div class="d-flex gap flex-wrap">
|
49
src/components/Dashboard/Views/List/ListView.vue
Normal file
49
src/components/Dashboard/Views/List/ListView.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts" setup>
|
||||
import ListTorrent from '@/components/Dashboard/Views/List/ListTorrent.vue'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import { Torrent as TorrentType } from '@/types/vuetorrent'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
defineProps<{
|
||||
paginatedTorrents: TorrentType[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
onCheckboxClick: [torrent: TorrentType],
|
||||
onTorrentClick: [e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: TorrentType],
|
||||
onTorrentDblClick: [torrent: TorrentType],
|
||||
onTorrentRightClick: [e: MouseEvent, torrent: TorrentType],
|
||||
startPress: [e: Touch, torrent: TorrentType],
|
||||
endPress: []
|
||||
}>()
|
||||
|
||||
const display = useDisplay()
|
||||
const dashboardStore = useDashboardStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list id="torrentList" class="pa-0">
|
||||
<v-list-item v-for="torrent in paginatedTorrents" :id="`torrent-${torrent.hash}`"
|
||||
:class="display.mobile ? 'mb-2' : 'mb-4'" class="pa-0"
|
||||
@contextmenu="$emit('onTorrentRightClick', $event, torrent)"
|
||||
@touchcancel="$emit('endPress')" @touchend="$emit('endPress')" @touchmove="$emit('endPress')"
|
||||
@touchstart="$emit('startPress', $event.touches.item(0)!, torrent)"
|
||||
@dblclick="$emit('onTorrentDblClick', torrent)">
|
||||
<div class="d-flex align-center">
|
||||
<v-expand-x-transition>
|
||||
<v-btn v-show="dashboardStore.isSelectionMultiple"
|
||||
:color="`torrent-${torrent.state}`"
|
||||
:icon="dashboardStore.isTorrentInSelection(torrent.hash) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
|
||||
class="mr-2" variant="text" @click="$emit('onCheckboxClick', torrent)" />
|
||||
</v-expand-x-transition>
|
||||
<ListTorrent :torrent="torrent" @onTorrentClick="(e, t) => $emit('onTorrentClick', e, t)" />
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#torrentList {
|
||||
background-color: unset;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string; total: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>{{ torrent[value] }} / {{ torrent[total] }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{ torrent: Torrent; title: string; value: string; color: string }>()
|
||||
|
||||
const values = computed(() => {
|
||||
const val = props.torrent[props.value]
|
||||
const type = typeof val
|
||||
|
||||
if (type === 'string') return val.length > 0 ? [val] : []
|
||||
else if (type === 'object' /* array */) return val
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>
|
||||
<div class="d-flex flex-row gap">
|
||||
<v-chip v-if="!values || values.length < 1" :color="color.replace('$1', torrent[value])" variant="flat">
|
||||
{{ $t(`torrent.properties.empty_${value}`) }}
|
||||
</v-chip>
|
||||
<v-chip v-else v-for="val in values" :color="color.replace('$1', torrent.state)" variant="flat">
|
||||
{{ val }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gap {
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { formatData } from '@/helpers'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>{{ formatData(torrent[value], vuetorrentStore.useBinarySize) }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import dayjs from '@/plugins/dayjs.ts'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
const vueTorrentStore = useVueTorrentStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td v-if="torrent[value] > 0">
|
||||
{{ dayjs(torrent[value] * 1000).format(vueTorrentStore.dateFormat ?? 'DD/MM/YYYY, HH:mm:ss') }}
|
||||
</td>
|
||||
<td v-else>{{ $t('dashboard.not_complete') }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import dayjs from '@/plugins/dayjs.ts'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
const durationFormat = 'D[d] H[h] m[m] s[s]'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td v-if="torrent[value] > 0">
|
||||
{{ dayjs.duration(torrent[value], 'seconds').format(durationFormat) }}
|
||||
</td>
|
||||
<td v-else>{{ $t('dashboard.not_complete') }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { TorrentState } from '@/constants/qbit'
|
||||
import { formatPercent } from '@/helpers'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
const isTorrentActive = computed(() => {
|
||||
return [
|
||||
TorrentState.UPLOADING,
|
||||
TorrentState.CHECKING_UP,
|
||||
TorrentState.FORCED_UP,
|
||||
TorrentState.ALLOCATING,
|
||||
TorrentState.DOWNLOADING,
|
||||
TorrentState.META_DL,
|
||||
TorrentState.CHECKING_DL,
|
||||
TorrentState.FORCED_DL,
|
||||
TorrentState.CHECKING_RESUME_DATA,
|
||||
TorrentState.MOVING
|
||||
].includes(props.torrent.state)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>
|
||||
<v-progress-linear :model-value="torrent[value]" :max="1" :striped="isTorrentActive" :height="20" :color="`torrent-${torrent.state}`" rounded="sm" style="width: 10em">
|
||||
{{ formatPercent(torrent[value]) }}
|
||||
</v-progress-linear>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import dayjs from '@/plugins/dayjs'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>{{ dayjs(torrent[value] * 1000).fromNow() }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { formatSpeed } from '@/helpers'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>{{ formatSpeed(torrent[value], vuetorrentStore.useBitSpeed) }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
|
||||
defineProps<{ torrent: Torrent; title: string; value: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<td>{{ torrent[value] }}</td>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
52
src/components/Dashboard/Views/Table/TableTorrent.vue
Normal file
52
src/components/Dashboard/Views/Table/TableTorrent.vue
Normal file
|
@ -0,0 +1,52 @@
|
|||
<script setup lang="ts">
|
||||
import { DashboardPropertyType } from '@/constants/vuetorrent'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { Torrent as TorrentType } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
import ItemAmount from './DashboardItems/ItemAmount.vue'
|
||||
import ItemChip from './DashboardItems/ItemChip.vue'
|
||||
import ItemData from './DashboardItems/ItemData.vue'
|
||||
import ItemDateTime from './DashboardItems/ItemDateTime.vue'
|
||||
import ItemDuration from './DashboardItems/ItemDuration.vue'
|
||||
import ItemPercent from './DashboardItems/ItemPercent.vue'
|
||||
import ItemRelativeTime from './DashboardItems/ItemRelativeTime.vue'
|
||||
import ItemSpeed from './DashboardItems/ItemSpeed.vue'
|
||||
import ItemText from './DashboardItems/ItemText.vue'
|
||||
|
||||
defineProps<{ torrent: TorrentType }>()
|
||||
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
|
||||
const torrentProperties = computed(() => vuetorrentStore.tableProperties.filter(ppt => ppt.active).sort((a, b) => a.order - b.order))
|
||||
|
||||
const getComponent = (type: DashboardPropertyType) => {
|
||||
switch (type) {
|
||||
case DashboardPropertyType.AMOUNT:
|
||||
return ItemAmount
|
||||
case DashboardPropertyType.CHIP:
|
||||
return ItemChip
|
||||
case DashboardPropertyType.DATA:
|
||||
return ItemData
|
||||
case DashboardPropertyType.DATETIME:
|
||||
return ItemDateTime
|
||||
case DashboardPropertyType.DURATION:
|
||||
return ItemDuration
|
||||
case DashboardPropertyType.PERCENT:
|
||||
return ItemPercent
|
||||
case DashboardPropertyType.RELATIVE:
|
||||
return ItemRelativeTime
|
||||
case DashboardPropertyType.SPEED:
|
||||
return ItemSpeed
|
||||
case DashboardPropertyType.TEXT:
|
||||
default:
|
||||
return ItemText
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component v-for="ppt in torrentProperties" :is="getComponent(ppt.type)"
|
||||
:torrent="torrent" v-bind="ppt.props" />
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
93
src/components/Dashboard/Views/Table/TableView.vue
Normal file
93
src/components/Dashboard/Views/Table/TableView.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<script lang="ts" setup>
|
||||
import TableTorrent from '@/components/Dashboard/Views/Table/TableTorrent.vue'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { useDashboardStore } from '@/stores/dashboard.ts'
|
||||
import { Torrent as TorrentType } from '@/types/vuetorrent'
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
paginatedTorrents: TorrentType[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
onCheckboxClick: [torrent: TorrentType],
|
||||
onTorrentClick: [e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: TorrentType],
|
||||
onTorrentDblClick: [torrent: TorrentType],
|
||||
onTorrentRightClick: [e: MouseEvent, torrent: TorrentType],
|
||||
startPress: [e: Touch, torrent: TorrentType],
|
||||
endPress: []
|
||||
}>()
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const vuetorrentStore = useVueTorrentStore()
|
||||
|
||||
const torrentProperties = computed(() => vuetorrentStore.tableProperties.filter(ppt => ppt.active).sort((a, b) => a.order - b.order))
|
||||
|
||||
function isTorrentSelected(torrent: TorrentType) {
|
||||
return dashboardStore.isTorrentInSelection(torrent.hash)
|
||||
}
|
||||
|
||||
const getTorrentRowColorClass = (torrent: TorrentType) => [
|
||||
'pointer',
|
||||
isTorrentSelected(torrent) ? `bg-torrent-${ torrent.state }-darken-3 selected` : ''
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-table id="torrentList" class="pa-0" density="compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-1" />
|
||||
<th v-if="dashboardStore.isSelectionMultiple" />
|
||||
<th class="text-left">{{ $t('torrent.properties.name') }}</th>
|
||||
|
||||
<th v-for="ppt in torrentProperties" class="text-left">
|
||||
{{ $t(`torrent.properties.${ ppt.props.title }`) }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="torrent in paginatedTorrents" :class="getTorrentRowColorClass(torrent)" v-ripple
|
||||
@contextmenu="$emit('onTorrentRightClick', $event, torrent)"
|
||||
@touchcancel="$emit('endPress')" @touchend="$emit('endPress')" @touchmove="$emit('endPress')"
|
||||
@touchstart="$emit('startPress', $event.touches.item(0)!, torrent)"
|
||||
@click="$emit('onTorrentClick', $event, torrent)"
|
||||
@dblclick="$emit('onTorrentDblClick', torrent)">
|
||||
<td :class="`pa-0 bg-torrent-${ torrent.state }`" />
|
||||
<td v-if="dashboardStore.isSelectionMultiple">
|
||||
<v-checkbox-btn :model-value="isTorrentSelected(torrent)"
|
||||
:color="`torrent-${torrent.state}`" variant="text"
|
||||
@click.stop="$emit('onCheckboxClick', torrent)" />
|
||||
</td>
|
||||
<td>{{ torrent.name }}</td>
|
||||
<TableTorrent :torrent="torrent" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use 'vuetify/settings';
|
||||
|
||||
#torrentList {
|
||||
background-color: unset;
|
||||
|
||||
tbody tr:nth-child(odd) {
|
||||
background-color: settings.$card-background;
|
||||
}
|
||||
|
||||
tbody tr.selected {
|
||||
position: relative;
|
||||
|
||||
&:nth-child(odd)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -11,9 +11,12 @@ const authStore = useAuthStore()
|
|||
const dndZoneRef = ref<HTMLDivElement>()
|
||||
|
||||
function onDragEnter() {
|
||||
const routeName = route.name as string
|
||||
const tabParam = route.params.tab as string
|
||||
const subtabParam = route.params.subtab as string
|
||||
if (
|
||||
(route.name as string) === 'login' ||
|
||||
((route.name as string) === 'settings' && route.params.tab === 'vuetorrent' && route.params.subtab === 'torrentCard') ||
|
||||
(routeName) === 'login' ||
|
||||
((routeName) === 'settings' && tabParam === 'vuetorrent' && subtabParam.startsWith('torrentCard')) ||
|
||||
!authStore.isAuthenticated
|
||||
)
|
||||
return
|
||||
|
@ -25,12 +28,11 @@ function onDrop(files: File[] | null, event: DragEvent) {
|
|||
event.stopPropagation()
|
||||
if (!event.dataTransfer) return
|
||||
|
||||
// Handle .torrent files
|
||||
const torrentFiles = (files || []).filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
|
||||
const links = event.dataTransfer
|
||||
.getData('text/plain')
|
||||
.split('\n')
|
||||
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))
|
||||
const torrentFiles = (files || [])
|
||||
.filter(file => file.type === 'application/x-bittorrent' || file.name.endsWith('.torrent'))
|
||||
|
||||
const links = event.dataTransfer.getData('text/plain').split('\n')
|
||||
.filter(link => link.startsWith('magnet:') || link.startsWith('http'))
|
||||
|
||||
torrentFiles.forEach(addTorrentStore.pushTorrentToQueue)
|
||||
links.forEach(addTorrentStore.pushTorrentToQueue)
|
||||
|
|
|
@ -18,7 +18,16 @@ const titleOptionsList = [
|
|||
{ title: t('constants.titleOptions.custom'), value: TitleOptions.CUSTOM }
|
||||
]
|
||||
|
||||
const paginationSizes = ref([{ title: t('settings.vuetorrent.general.paginationSize.infinite_scroll'), value: -1 }, 5, 15, 30, 50])
|
||||
const paginationSizes = ref([
|
||||
{ title: t('settings.vuetorrent.general.paginationSize.infinite_scroll'), value: -1 },
|
||||
5,
|
||||
15,
|
||||
30,
|
||||
50,
|
||||
100,
|
||||
250,
|
||||
500
|
||||
])
|
||||
|
||||
const theme = computed({
|
||||
get() {
|
||||
|
|
60
src/components/Settings/VueTorrent/TorrentCard/Grid.vue
Normal file
60
src/components/Settings/VueTorrent/TorrentCard/Grid.vue
Normal file
|
@ -0,0 +1,60 @@
|
|||
<script setup lang="ts">
|
||||
import DashboardItem from '@/components/Settings/VueTorrent/DashboardItem.vue'
|
||||
import { TorrentProperty } from '@/constants/vuetorrent'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { computed } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
const vueTorrentStore = useVueTorrentStore()
|
||||
|
||||
const busyProperties = computed({
|
||||
get: () => vueTorrentStore.busyGridProperties,
|
||||
set: newValue => vueTorrentStore.updateBusyGridProperties(newValue)
|
||||
})
|
||||
const doneProperties = computed({
|
||||
get: () => vueTorrentStore.doneGridProperties,
|
||||
set: newValue => vueTorrentStore.updateDoneGridProperties(newValue)
|
||||
})
|
||||
|
||||
function toggleActive(isBusy: boolean, property: TorrentProperty) {
|
||||
if (isBusy) {
|
||||
vueTorrentStore.toggleBusyGridProperty(property.name)
|
||||
} else {
|
||||
vueTorrentStore.toggleDoneGridProperty(property.name)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.grid.busyTip') }}</v-list-subheader>
|
||||
|
||||
<v-table>
|
||||
<draggable v-model="busyProperties" item-key="name" tag="tbody" handle=".dnd-handle">
|
||||
<template v-slot:item="{ element }">
|
||||
<DashboardItem :property="element" @update="toggleActive(true, element)" />
|
||||
</template>
|
||||
</draggable>
|
||||
</v-table>
|
||||
</v-list>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.grid.doneTip') }}</v-list-subheader>
|
||||
|
||||
<v-table>
|
||||
<draggable v-model="doneProperties" item-key="name" tag="tbody" handle=".dnd-handle">
|
||||
<template v-slot:item="{ element }">
|
||||
<DashboardItem :property="element" @update="toggleActive(false, element)" />
|
||||
</template>
|
||||
</draggable>
|
||||
</v-table>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -29,7 +29,7 @@ function toggleActive(isBusy: boolean, property: TorrentProperty) {
|
|||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.busyTorrentTip') }}</v-list-subheader>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.list.busyTip') }}</v-list-subheader>
|
||||
|
||||
<v-table>
|
||||
<draggable v-model="busyTorrentProperties" item-key="name" tag="tbody" handle=".dnd-handle">
|
||||
|
@ -43,7 +43,7 @@ function toggleActive(isBusy: boolean, property: TorrentProperty) {
|
|||
|
||||
<v-col cols="12" md="6">
|
||||
<v-list>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.doneTorrentTip') }}</v-list-subheader>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.list.doneTip') }}</v-list-subheader>
|
||||
|
||||
<v-table>
|
||||
<draggable v-model="doneTorrentProperties" item-key="name" tag="tbody" handle=".dnd-handle">
|
38
src/components/Settings/VueTorrent/TorrentCard/Table.vue
Normal file
38
src/components/Settings/VueTorrent/TorrentCard/Table.vue
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script setup lang="ts">
|
||||
import DashboardItem from '@/components/Settings/VueTorrent/DashboardItem.vue'
|
||||
import { TorrentProperty } from '@/constants/vuetorrent'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { computed } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
|
||||
const vueTorrentStore = useVueTorrentStore()
|
||||
|
||||
const busyTorrentProperties = computed({
|
||||
get: () => vueTorrentStore.tableProperties,
|
||||
set: newValue => vueTorrentStore.updateTableProperties(newValue)
|
||||
})
|
||||
|
||||
function toggleActive(property: TorrentProperty) {
|
||||
vueTorrentStore.toggleTableProperty(property.name)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-list>
|
||||
<v-list-subheader>{{ $t('settings.vuetorrent.torrentCard.table.tip') }}</v-list-subheader>
|
||||
|
||||
<v-table>
|
||||
<draggable v-model="busyTorrentProperties" item-key="name" tag="tbody" handle=".dnd-handle">
|
||||
<template v-slot:item="{ element }">
|
||||
<DashboardItem :property="element" @update="toggleActive(element)" />
|
||||
</template>
|
||||
</draggable>
|
||||
</v-table>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
5
src/constants/vuetorrent/DashboardDisplayMode.ts
Normal file
5
src/constants/vuetorrent/DashboardDisplayMode.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export enum DashboardDisplayMode {
|
||||
LIST = 'list',
|
||||
GRID = 'grid',
|
||||
TABLE = 'table'
|
||||
}
|
|
@ -1,9 +1,23 @@
|
|||
import type { TorrentProperty, PropertyData, PropertyMetadata } from './DashboardDefaults'
|
||||
import type { PropertyData, PropertyMetadata, TorrentProperty } from './DashboardDefaults'
|
||||
import { propsData, propsMetadata } from './DashboardDefaults'
|
||||
import { DashboardDisplayMode } from './DashboardDisplayMode'
|
||||
import { DashboardProperty } from './DashboardProperty'
|
||||
import { DashboardPropertyType } from './DashboardPropertyType'
|
||||
import { typesMap, getFileIcon } from './FileIcon'
|
||||
import { getFileIcon, typesMap } from './FileIcon'
|
||||
import { HistoryKey } from './HistoryKey'
|
||||
import { TitleOptions } from './TitleOptions'
|
||||
|
||||
export { TorrentProperty, PropertyData, PropertyMetadata, propsData, propsMetadata, DashboardProperty, DashboardPropertyType, typesMap, getFileIcon, HistoryKey, TitleOptions }
|
||||
export {
|
||||
TorrentProperty,
|
||||
PropertyData,
|
||||
PropertyMetadata,
|
||||
propsData,
|
||||
propsMetadata,
|
||||
DashboardDisplayMode,
|
||||
DashboardProperty,
|
||||
DashboardPropertyType,
|
||||
getFileIcon,
|
||||
typesMap,
|
||||
HistoryKey,
|
||||
TitleOptions
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@ export const isMac = window.navigator.userAgent.toLowerCase().includes('mac')
|
|||
/**
|
||||
* Check Ctrl/Cmd key
|
||||
*/
|
||||
export function doesCommand(e: KeyboardEvent | MouseEvent | TouchEvent): boolean {
|
||||
export function doesCommand(e: { metaKey: boolean, ctrlKey: boolean }): boolean {
|
||||
return isMac ? e.metaKey : e.ctrlKey
|
||||
}
|
||||
|
|
|
@ -1,23 +1,30 @@
|
|||
<script lang="ts" setup>
|
||||
import Torrent from '@/components/Dashboard/Torrent.vue'
|
||||
import RightClickMenu from '@/components/Dashboard/TRC/RightClickMenu.vue'
|
||||
import GridView from '@/components/Dashboard/Views/Grid/GridView.vue'
|
||||
import ListView from '@/components/Dashboard/Views/List/ListView.vue'
|
||||
import TableView from '@/components/Dashboard/Views/Table/TableView.vue'
|
||||
import ConfirmDeleteDialog from '@/components/Dialogs/ConfirmDeleteDialog.vue'
|
||||
import { useArrayPagination } from '@/composables'
|
||||
import { DashboardDisplayMode } from '@/constants/vuetorrent'
|
||||
import { doesCommand } from '@/helpers'
|
||||
import { useDashboardStore, useDialogStore, useMaindataStore, useTorrentStore, useVueTorrentStore } from '@/stores'
|
||||
import { Torrent as TorrentType } from '@/types/vuetorrent'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { computed, mergeProps, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const display = useDisplay()
|
||||
const { currentPage: dashboardPage, isSelectionMultiple, selectedTorrents, torrentCountString } = storeToRefs(useDashboardStore())
|
||||
const dashboardStore = useDashboardStore()
|
||||
const {
|
||||
currentPage: dashboardPage,
|
||||
isSelectionMultiple,
|
||||
selectedTorrents,
|
||||
displayMode,
|
||||
torrentCountString
|
||||
} = storeToRefs(dashboardStore)
|
||||
const dialogStore = useDialogStore()
|
||||
const maindataStore = useMaindataStore()
|
||||
const torrentStore = useTorrentStore()
|
||||
|
@ -94,7 +101,15 @@ const torrentTitleFilter = computed({
|
|||
}, 300)
|
||||
})
|
||||
|
||||
const { paginatedResults: paginatedTorrents, currentPage, pageCount } = useArrayPagination(filteredTorrents, vuetorrentStore.paginationSize, dashboardPage)
|
||||
const isListView = computed(() => displayMode.value === DashboardDisplayMode.LIST)
|
||||
const isGridView = computed(() => displayMode.value === DashboardDisplayMode.GRID)
|
||||
const isTableView = computed(() => displayMode.value === DashboardDisplayMode.TABLE)
|
||||
|
||||
const {
|
||||
paginatedResults: paginatedTorrents,
|
||||
currentPage,
|
||||
pageCount
|
||||
} = useArrayPagination(filteredTorrents, vuetorrentStore.paginationSize, dashboardPage)
|
||||
const hasSearchFilter = computed(() => !!torrentStore.textFilter && torrentStore.textFilter.length > 0)
|
||||
|
||||
const isAllTorrentsSelected = computed(() => filteredTorrents.value.length <= selectedTorrents.value.length)
|
||||
|
@ -109,16 +124,6 @@ function toggleSearchFilter(forceState?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleSelectTorrent(hash: string) {
|
||||
dashboardStore.toggleSelect(hash)
|
||||
}
|
||||
|
||||
function goToInfo(hash: string) {
|
||||
if (!isSelectionMultiple.value) {
|
||||
router.push({ name: 'torrentDetail', params: { hash } })
|
||||
}
|
||||
}
|
||||
|
||||
function resetInput() {
|
||||
torrentStore.textFilter = ''
|
||||
}
|
||||
|
@ -142,9 +147,26 @@ function toggleSelectAll() {
|
|||
}
|
||||
}
|
||||
|
||||
async function onRightClick(e: PointerEvent, torrent: TorrentType) {
|
||||
e.preventDefault()
|
||||
function goToInfo(torrent: TorrentType) {
|
||||
if (!isSelectionMultiple.value) {
|
||||
router.push({ name: 'torrentDetail', params: { hash: torrent.hash } })
|
||||
}
|
||||
}
|
||||
|
||||
function onCheckboxClick(torrent: TorrentType) {
|
||||
dashboardStore.toggleSelect(torrent.hash)
|
||||
}
|
||||
|
||||
function onTorrentClick(e: { shiftKey: boolean, metaKey: boolean, ctrlKey: boolean }, torrent: TorrentType) {
|
||||
if (e.shiftKey) {
|
||||
dashboardStore.spanTorrentSelection(torrent.hash)
|
||||
} else if (doesCommand(e) || dashboardStore.isSelectionMultiple) {
|
||||
dashboardStore.isSelectionMultiple = true
|
||||
dashboardStore.toggleSelect(torrent.hash)
|
||||
}
|
||||
}
|
||||
|
||||
async function onTorrentRightClick(e: MouseEvent | Touch, torrent: TorrentType) {
|
||||
if (trcProperties.isVisible) {
|
||||
trcProperties.isVisible = false
|
||||
await nextTick()
|
||||
|
@ -161,14 +183,19 @@ async function onRightClick(e: PointerEvent, torrent: TorrentType) {
|
|||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => trcProperties.isVisible,
|
||||
newValue => {
|
||||
if (!newValue && !isSelectionMultiple.value) {
|
||||
dashboardStore.unselectAllTorrents()
|
||||
}
|
||||
}
|
||||
)
|
||||
// mobile long press
|
||||
const timer = ref<NodeJS.Timeout>()
|
||||
|
||||
function startPress(e: Touch, torrent: TorrentType) {
|
||||
timer.value = setTimeout(() => {
|
||||
onTorrentRightClick(e, torrent)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function endPress() {
|
||||
clearTimeout(timer.value)
|
||||
}
|
||||
// END mobile long press
|
||||
|
||||
function handleKeyboardShortcuts(e: KeyboardEvent) {
|
||||
if (dialogStore.hasActiveDialog) {
|
||||
|
@ -228,6 +255,15 @@ function handleKeyboardShortcuts(e: KeyboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => trcProperties.isVisible,
|
||||
newValue => {
|
||||
if (!newValue && !isSelectionMultiple.value) {
|
||||
dashboardStore.unselectAllTorrents()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await maindataStore.fetchCategories()
|
||||
await maindataStore.fetchTags()
|
||||
|
@ -243,19 +279,6 @@ onMounted(() => {
|
|||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', handleKeyboardShortcuts)
|
||||
})
|
||||
|
||||
// mobile long press
|
||||
const timer = ref<NodeJS.Timeout>()
|
||||
|
||||
function startPress(e: PointerEvent, torrent: TorrentType) {
|
||||
timer.value = setTimeout(() => {
|
||||
onRightClick(e, torrent)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function endPress() {
|
||||
clearTimeout(timer.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -288,6 +311,27 @@ function endPress() {
|
|||
<v-btn :icon="isSelectionMultiple ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'" v-bind="props" variant="plain" @click="toggleSelectMode" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props: menu }">
|
||||
<v-tooltip :text="$t('dashboard.displayMode.title')" location="top">
|
||||
<template v-slot:activator="{ props: tooltip }">
|
||||
<v-btn icon v-bind="mergeProps(menu, tooltip)" variant="plain">
|
||||
<v-icon v-if="displayMode === DashboardDisplayMode.LIST" icon="mdi-view-list" />
|
||||
<v-icon v-if="displayMode === DashboardDisplayMode.GRID" icon="mdi-view-grid" />
|
||||
<v-icon v-if="displayMode === DashboardDisplayMode.TABLE" icon="mdi-table" />
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item :title="$t('dashboard.displayMode.list')" prepend-icon="mdi-view-list"
|
||||
@click="displayMode = DashboardDisplayMode.LIST" />
|
||||
<v-list-item :title="$t('dashboard.displayMode.grid')" prepend-icon="mdi-view-grid"
|
||||
@click="displayMode = DashboardDisplayMode.GRID" />
|
||||
<v-list-item :title="$t('dashboard.displayMode.table')" prepend-icon="mdi-table"
|
||||
@click="displayMode = DashboardDisplayMode.TABLE" />
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-tooltip :text="t('dashboard.toggleSortOrder')" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
|
@ -331,53 +375,40 @@ function endPress() {
|
|||
</v-card>
|
||||
</v-expand-transition>
|
||||
</v-row>
|
||||
|
||||
<div v-if="filteredTorrents.length === 0" class="mt-5 text-xs-center">
|
||||
<p class="text-grey">{{ t('common.emptyList') }}</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-list id="torrentList" class="pa-0" color="transparent">
|
||||
<v-list-item v-if="vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
|
||||
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
v-for="torrent in paginatedTorrents"
|
||||
:id="`torrent-${torrent.hash}`"
|
||||
:class="display.mobile ? 'mb-2' : 'mb-4'"
|
||||
class="pa-0"
|
||||
@contextmenu="onRightClick($event, torrent)"
|
||||
@touchcancel="endPress"
|
||||
@touchend="endPress"
|
||||
@touchmove="endPress"
|
||||
@touchstart="startPress($event, torrent)"
|
||||
@dblclick.prevent="goToInfo(torrent.hash)">
|
||||
<div class="d-flex align-center">
|
||||
<v-expand-x-transition>
|
||||
<v-card v-show="isSelectionMultiple" class="mr-3" color="transparent">
|
||||
<v-btn
|
||||
:icon="dashboardStore.isTorrentInSelection(torrent.hash) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline'"
|
||||
color="transparent"
|
||||
variant="flat"
|
||||
@click="toggleSelectTorrent(torrent.hash)" />
|
||||
</v-card>
|
||||
</v-expand-x-transition>
|
||||
<Torrent :torrent="torrent" />
|
||||
</div>
|
||||
</v-list-item>
|
||||
<div v-if="vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
|
||||
<v-pagination v-model="currentPage" :length="pageCount"
|
||||
next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
|
||||
</div>
|
||||
|
||||
<v-list-item v-if="!vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
|
||||
<v-pagination v-model="currentPage" :length="pageCount" next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<ListView v-if="isListView" :paginated-torrents="paginatedTorrents"
|
||||
@onTorrentClick="onTorrentClick" @onTorrentDblClick="goToInfo"
|
||||
@onCheckboxClick="onCheckboxClick" @onTorrentRightClick="onTorrentRightClick"
|
||||
@startPress="startPress" @endPress="endPress" />
|
||||
<GridView v-else-if="isGridView" class="mb-2" :paginated-torrents="paginatedTorrents"
|
||||
@onTorrentClick="onTorrentClick" @onTorrentDblClick="goToInfo"
|
||||
@onCheckboxClick="onCheckboxClick" @onTorrentRightClick="onTorrentRightClick"
|
||||
@startPress="startPress" @endPress="endPress" />
|
||||
<TableView v-else-if="isTableView" :paginated-torrents="paginatedTorrents"
|
||||
@onTorrentClick="onTorrentClick" @onTorrentDblClick="goToInfo"
|
||||
@onCheckboxClick="onCheckboxClick" @onTorrentRightClick="onTorrentRightClick"
|
||||
@startPress="startPress" @endPress="endPress" />
|
||||
|
||||
<div v-if="!vuetorrentStore.isPaginationOnTop && !vuetorrentStore.isInfiniteScrollActive">
|
||||
<v-pagination v-model="currentPage" :length="pageCount"
|
||||
next-icon="mdi-menu-right" prev-icon="mdi-menu-left" @input="scrollToTop" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :style="`position: absolute; left: ${trcProperties.offset[0]}px; top: ${trcProperties.offset[1]}px;`">
|
||||
<RightClickMenu v-model="trcProperties.isVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#torrentList {
|
||||
background-color: unset;
|
||||
}
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -10,7 +10,9 @@ import RRules from '@/components/Settings/RSS/Rules.vue'
|
|||
import Speed from '@/components/Settings/Speed.vue'
|
||||
import TagsAndCategories from '@/components/Settings/TagsAndCategories.vue'
|
||||
import VGeneral from '@/components/Settings/VueTorrent/General.vue'
|
||||
import VTorrentCard from '@/components/Settings/VueTorrent/TorrentCard.vue'
|
||||
import VTorrentCardList from '@/components/Settings/VueTorrent/TorrentCard/List.vue'
|
||||
import VTorrentCardGrid from '@/components/Settings/VueTorrent/TorrentCard/Grid.vue'
|
||||
import VTorrentCardTable from '@/components/Settings/VueTorrent/TorrentCard/Table.vue'
|
||||
import WebUI from '@/components/Settings/WebUI.vue'
|
||||
import { useDialogStore, usePreferenceStore } from '@/stores'
|
||||
import { onBeforeUnmount, onMounted, ref, watchEffect } from 'vue'
|
||||
|
@ -38,7 +40,9 @@ const tabs = [
|
|||
|
||||
const tabsV = [
|
||||
{ text: t('settings.tabs.vuetorrent.general'), value: 'general' },
|
||||
{ text: t('settings.tabs.vuetorrent.torrent_card'), value: 'torrentCard' }
|
||||
{ text: t('settings.tabs.vuetorrent.torrent_card.list'), value: 'torrentCardList' },
|
||||
{ text: t('settings.tabs.vuetorrent.torrent_card.grid'), value: 'torrentCardGrid' },
|
||||
{ text: t('settings.tabs.vuetorrent.torrent_card.table'), value: 'torrentCardTable' }
|
||||
]
|
||||
|
||||
const tabsR = [
|
||||
|
@ -102,6 +106,7 @@ onMounted(() => {
|
|||
document.addEventListener('keydown', handleKeyboardShortcut)
|
||||
updateTabHandle()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', handleKeyboardShortcut)
|
||||
})
|
||||
|
@ -140,8 +145,14 @@ onBeforeUnmount(() => {
|
|||
<v-window-item value="general">
|
||||
<VGeneral />
|
||||
</v-window-item>
|
||||
<v-window-item value="torrentCard">
|
||||
<VTorrentCard />
|
||||
<v-window-item value="torrentCardList">
|
||||
<VTorrentCardList />
|
||||
</v-window-item>
|
||||
<v-window-item value="torrentCardGrid">
|
||||
<VTorrentCardGrid />
|
||||
</v-window-item>
|
||||
<v-window-item value="torrentCardTable">
|
||||
<VTorrentCardTable />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-window-item>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { DashboardDisplayMode } from '@/constants/vuetorrent'
|
||||
import { formatData } from '@/helpers'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
@ -10,6 +11,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
|||
const isSelectionMultiple = ref(false)
|
||||
const selectedTorrents = ref<string[]>([])
|
||||
const latestSelectedTorrent = ref<string>()
|
||||
const displayMode = ref(DashboardDisplayMode.LIST)
|
||||
|
||||
const { t } = useI18n()
|
||||
const torrentStore = useTorrentStore()
|
||||
|
@ -100,22 +102,35 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
|||
if (pageCount < currentPage.value) {
|
||||
currentPage.value = Math.max(1, pageCount)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
isSelectionMultiple,
|
||||
selectedTorrents,
|
||||
latestSelectedTorrent,
|
||||
torrentCountString,
|
||||
isTorrentInSelection,
|
||||
selectTorrent,
|
||||
selectTorrents,
|
||||
unselectTorrent,
|
||||
spanTorrentSelection,
|
||||
selectAllTorrents,
|
||||
unselectAllTorrents,
|
||||
toggleSelect
|
||||
return {
|
||||
currentPage,
|
||||
isSelectionMultiple,
|
||||
selectedTorrents,
|
||||
latestSelectedTorrent,
|
||||
displayMode,
|
||||
torrentCountString,
|
||||
isTorrentInSelection,
|
||||
selectTorrent,
|
||||
selectTorrents,
|
||||
unselectTorrent,
|
||||
spanTorrentSelection,
|
||||
selectAllTorrents,
|
||||
unselectAllTorrents,
|
||||
toggleSelect
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
storage: localStorage,
|
||||
key: 'vuetorrent_dashboard',
|
||||
paths: ['displayMode']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
|
@ -129,17 +129,19 @@ export const useMaindataStore = defineStore('maindata', () => {
|
|||
}
|
||||
|
||||
// update torrents
|
||||
torrents.value = data.map(t => torrentBuilder.buildFromQbit(t))
|
||||
const tempTorrents = data.map(t => torrentBuilder.buildFromQbit(t))
|
||||
|
||||
if (import.meta.env.DEV && import.meta.env.VITE_USE_FAKE_TORRENTS === 'true') {
|
||||
const count = Number(import.meta.env.VITE_FAKE_TORRENT_COUNT)
|
||||
const fakeTorrents: Partial<Torrent> = (await import('../../__mocks__/torrents.json')).default
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
torrents.value.push(torrentBuilder.buildFromFaker({ ...fakeTorrents.at(i), hash: uuidFromRaw(BigInt(i)) }, i))
|
||||
for (let i = 0; i < count; i++) {
|
||||
tempTorrents.push(torrentBuilder.buildFromFaker({ ...fakeTorrents.at(i), hash: uuidFromRaw(BigInt(i)) }, i))
|
||||
}
|
||||
}
|
||||
|
||||
torrents.value = tempTorrents
|
||||
|
||||
// filter out deleted torrents from selection
|
||||
const hash_index = torrents.value.map(torrent => torrent.hash)
|
||||
dashboardStore.selectedTorrents = dashboardStore.selectedTorrents.filter(hash => hash_index.includes(hash))
|
||||
|
|
|
@ -40,6 +40,11 @@ export const useVueTorrentStore = defineStore(
|
|||
const _busyProperties = ref<PropertyData>(JSON.parse(JSON.stringify(propsData)))
|
||||
const _doneProperties = ref<PropertyData>(JSON.parse(JSON.stringify(propsData)))
|
||||
|
||||
const _busyGridProperties = ref<PropertyData>(JSON.parse(JSON.stringify(propsData)))
|
||||
const _doneGridProperties = ref<PropertyData>(JSON.parse(JSON.stringify(propsData)))
|
||||
|
||||
const _tableProperties = ref<PropertyData>(JSON.parse(JSON.stringify(propsData)))
|
||||
|
||||
const getCurrentThemeName = computed(() => (darkMode.value ? Theme.DARK : Theme.LIGHT))
|
||||
const isInfiniteScrollActive = computed(() => paginationSize.value === -1)
|
||||
|
||||
|
@ -68,6 +73,44 @@ export const useVueTorrentStore = defineStore(
|
|||
return formattedPpt
|
||||
})
|
||||
|
||||
const busyGridProperties = computed<TorrentProperty[]>(() => {
|
||||
const formattedPpt: TorrentProperty[] = new Array(Object.keys(propsData).length)
|
||||
|
||||
for (const [k, v] of Object.entries(_busyGridProperties.value)) {
|
||||
formattedPpt[v.order - 1] = {
|
||||
name: k as DashboardProperty,
|
||||
...v,
|
||||
...propsMetadata[k]
|
||||
}
|
||||
}
|
||||
return formattedPpt
|
||||
})
|
||||
const doneGridProperties = computed<TorrentProperty[]>(() => {
|
||||
const formattedPpt: TorrentProperty[] = new Array(Object.keys(propsData).length)
|
||||
|
||||
for (const [k, v] of Object.entries(_doneGridProperties.value)) {
|
||||
formattedPpt[v.order - 1] = {
|
||||
name: k as DashboardProperty,
|
||||
...v,
|
||||
...propsMetadata[k]
|
||||
}
|
||||
}
|
||||
return formattedPpt
|
||||
})
|
||||
|
||||
const tableProperties = computed<TorrentProperty[]>(() => {
|
||||
const formattedPpt: TorrentProperty[] = new Array(Object.keys(propsData).length)
|
||||
|
||||
for (const [k, v] of Object.entries(_tableProperties.value)) {
|
||||
formattedPpt[v.order - 1] = {
|
||||
name: k as DashboardProperty,
|
||||
...v,
|
||||
...propsMetadata[k]
|
||||
}
|
||||
}
|
||||
return formattedPpt
|
||||
})
|
||||
|
||||
const i18n = useI18n()
|
||||
const router = useRouter()
|
||||
const theme = useTheme()
|
||||
|
@ -122,6 +165,27 @@ export const useVueTorrentStore = defineStore(
|
|||
})
|
||||
}
|
||||
|
||||
function updateBusyGridProperties(values: TorrentProperty[]) {
|
||||
values.forEach((ppt, index) => {
|
||||
_busyGridProperties.value[ppt.name].active = ppt.active
|
||||
_busyGridProperties.value[ppt.name].order = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
function updateDoneGridProperties(values: TorrentProperty[]) {
|
||||
values.forEach((ppt, index) => {
|
||||
_doneGridProperties.value[ppt.name].active = ppt.active
|
||||
_doneGridProperties.value[ppt.name].order = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
function updateTableProperties(values: TorrentProperty[]) {
|
||||
values.forEach((ppt, index) => {
|
||||
_tableProperties.value[ppt.name].active = ppt.active
|
||||
_tableProperties.value[ppt.name].order = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
function toggleBusyProperty(name: DashboardProperty) {
|
||||
_busyProperties.value[name].active = !_busyProperties.value[name].active
|
||||
}
|
||||
|
@ -130,6 +194,18 @@ export const useVueTorrentStore = defineStore(
|
|||
_doneProperties.value[name].active = !_doneProperties.value[name].active
|
||||
}
|
||||
|
||||
function toggleBusyGridProperty(name: DashboardProperty) {
|
||||
_busyGridProperties.value[name].active = !_busyGridProperties.value[name].active
|
||||
}
|
||||
|
||||
function toggleDoneGridProperty(name: DashboardProperty) {
|
||||
_doneGridProperties.value[name].active = !_doneGridProperties.value[name].active
|
||||
}
|
||||
|
||||
function toggleTableProperty(name: DashboardProperty) {
|
||||
_tableProperties.value[name].active = !_tableProperties.value[name].active
|
||||
}
|
||||
|
||||
return {
|
||||
canvasRenderThreshold,
|
||||
canvasRefreshThreshold,
|
||||
|
@ -161,6 +237,12 @@ export const useVueTorrentStore = defineStore(
|
|||
busyTorrentProperties,
|
||||
_doneProperties,
|
||||
doneTorrentProperties,
|
||||
_busyGridProperties,
|
||||
busyGridProperties,
|
||||
_doneGridProperties,
|
||||
doneGridProperties,
|
||||
_tableProperties,
|
||||
tableProperties,
|
||||
getCurrentThemeName,
|
||||
isInfiniteScrollActive,
|
||||
setLanguage,
|
||||
|
@ -170,8 +252,14 @@ export const useVueTorrentStore = defineStore(
|
|||
redirectToLogin,
|
||||
updateBusyProperties,
|
||||
updateDoneProperties,
|
||||
updateBusyGridProperties,
|
||||
updateDoneGridProperties,
|
||||
updateTableProperties,
|
||||
toggleBusyProperty,
|
||||
toggleDoneProperty
|
||||
toggleDoneProperty,
|
||||
toggleBusyGridProperty,
|
||||
toggleDoneGridProperty,
|
||||
toggleTableProperty
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -35,68 +35,68 @@ ul.no-bullet {
|
|||
$sideborder-margin: 6px;
|
||||
|
||||
.sideborder {
|
||||
border-left: #{$sideborder-margin} solid grey !important;
|
||||
border-left: #{$sideborder-margin} solid grey;
|
||||
|
||||
&.error {
|
||||
border-left-color: #{$torrent-error} !important;
|
||||
border-left-color: #{$torrent-error};
|
||||
}
|
||||
&.missingFiles {
|
||||
border-left-color: #{$torrent-missingFiles} !important;
|
||||
border-left-color: #{$torrent-missingFiles};
|
||||
}
|
||||
&.uploading {
|
||||
border-left-color: #{$torrent-uploading} !important;
|
||||
border-left-color: #{$torrent-uploading};
|
||||
}
|
||||
&.forcedUP {
|
||||
border-left-color: #{$torrent-forcedUP} !important;
|
||||
border-left-color: #{$torrent-forcedUP};
|
||||
}
|
||||
&.pausedUP {
|
||||
border-left-color: #{$torrent-pausedUP} !important;
|
||||
border-left-color: #{$torrent-pausedUP};
|
||||
}
|
||||
&.queuedUP {
|
||||
border-left-color: #{$torrent-queuedUP} !important;
|
||||
border-left-color: #{$torrent-queuedUP};
|
||||
}
|
||||
&.stalledUP {
|
||||
border-left-color: #{$torrent-stalledUP} !important;
|
||||
border-left-color: #{$torrent-stalledUP};
|
||||
}
|
||||
&.checkingUP {
|
||||
border-left-color: #{$torrent-checkingUP} !important;
|
||||
border-left-color: #{$torrent-checkingUP};
|
||||
}
|
||||
&.allocating {
|
||||
border-left-color: #{$torrent-allocating} !important;
|
||||
border-left-color: #{$torrent-allocating};
|
||||
}
|
||||
&.downloading {
|
||||
border-left-color: #{$torrent-downloading} !important;
|
||||
border-left-color: #{$torrent-downloading};
|
||||
}
|
||||
&.forcedDL {
|
||||
border-left-color: #{$torrent-forcedDL} !important;
|
||||
border-left-color: #{$torrent-forcedDL};
|
||||
}
|
||||
&.metaDL {
|
||||
border-left-color: #{$torrent-metaDL} !important;
|
||||
border-left-color: #{$torrent-metaDL};
|
||||
}
|
||||
&.pausedDL {
|
||||
border-left-color: #{$torrent-pausedDL} !important;
|
||||
border-left-color: #{$torrent-pausedDL};
|
||||
}
|
||||
&.queuedDL {
|
||||
border-left-color: #{$torrent-queuedDL} !important;
|
||||
border-left-color: #{$torrent-queuedDL};
|
||||
}
|
||||
&.stalledDL {
|
||||
border-left-color: #{$torrent-stalledDL} !important;
|
||||
border-left-color: #{$torrent-stalledDL};
|
||||
}
|
||||
&.checkingDL {
|
||||
border-left-color: #{$torrent-checkingDL} !important;
|
||||
border-left-color: #{$torrent-checkingDL};
|
||||
}
|
||||
&.checkingResumeData {
|
||||
border-left-color: #{$torrent-checkingResumeData} !important;
|
||||
border-left-color: #{$torrent-checkingResumeData};
|
||||
}
|
||||
&.moving {
|
||||
border-left-color: #{$torrent-moving} !important;
|
||||
border-left-color: #{$torrent-moving};
|
||||
}
|
||||
&.unknown {
|
||||
&.v-theme--darkTheme {
|
||||
border-left-color: #{$torrent-unknown-dark} !important;
|
||||
border-left-color: #{$torrent-unknown-dark};
|
||||
}
|
||||
&.v-theme-lightTheme {
|
||||
border-left-color: #{$torrent-unknown-light} !important;
|
||||
border-left-color: #{$torrent-unknown-light};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue