Skip to content

Commit

Permalink
perf(TorrentDetail): Overview Piece Renderer (#1564)
Browse files Browse the repository at this point in the history
  • Loading branch information
fredkilbourn authored Mar 12, 2024
1 parent 82c55d0 commit 5ed280c
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 104 deletions.
71 changes: 71 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"@ctrl/tinycolor": "^4.0.3",
"@faker-js/faker": "^8.4.1",
"@flatten-js/interval-tree": "^1.1.2",
"@fontsource/roboto": "^5.0.12",
"@fontsource/roboto-mono": "^5.0.17",
"@mdi/font": "^7.4.47",
Expand All @@ -28,6 +29,7 @@
"lodash.debounce": "^4.0.8",
"pinia": "^2.1.6",
"pinia-plugin-persist": "^1.0.0",
"pixi.js": "^8.0.1",
"uuid": "^9.0.1",
"vite-plugin-vuetify": "^2.0.2",
"vue": "^3.4.21",
Expand Down
9 changes: 0 additions & 9 deletions src/components/Settings/VueTorrent/General.vue
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,6 @@ onBeforeMount(() => {
</v-col>
</v-row>

<v-row>
<v-col cols="12" md="6">
<v-text-field v-model.number="vueTorrentStore.canvasRenderThreshold" hide-details type="number" :label="t('settings.vuetorrent.general.canvasRenderThreshold')" />
</v-col>
<v-col cols="12" md="6">
<v-text-field v-model.number="vueTorrentStore.canvasRefreshThreshold" hide-details type="number" :label="t('settings.vuetorrent.general.canvasRefreshThreshold')" />
</v-col>
</v-row>

<v-row>
<v-col cols="12" md="6">
<v-select v-model="vueTorrentStore.language" flat hide-details :items="LOCALES" :label="t('settings.vuetorrent.general.language')" />
Expand Down
92 changes: 7 additions & 85 deletions src/components/TorrentDetail/Overview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,24 @@
import ConfirmDeleteDialog from '@/components/Dialogs/ConfirmDeleteDialog.vue'
import MoveTorrentDialog from '@/components/Dialogs/MoveTorrentDialog.vue'
import MoveTorrentFileDialog from '@/components/Dialogs/MoveTorrentFileDialog.vue'
import { FilePriority, PieceState, TorrentState } from '@/constants/qbit'
import { FilePriority, TorrentState } from '@/constants/qbit'
import { formatData, formatDataUnit, formatDataValue, formatPercent, formatSpeed, getDomainBody, splitByUrl, stringContainsUrl } from '@/helpers'
import { useContentStore, useDialogStore, useMaindataStore, useTorrentDetailStore, useVueTorrentStore } from '@/stores'
import { useContentStore, useDialogStore, useTorrentDetailStore, useVueTorrentStore } from '@/stores'
import { Torrent } from '@/types/vuetorrent'
import { storeToRefs } from 'pinia'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { toast } from 'vue3-toastify'
import { useTheme } from 'vuetify'
import PieceCanvas from './PieceCanvas.vue'
const props = defineProps<{ torrent: Torrent; isActive: boolean }>()
const { t } = useI18n()
const theme = useTheme()
const { cachedFiles } = storeToRefs(useContentStore())
const dialogStore = useDialogStore()
const maindataStore = useMaindataStore()
const { properties } = storeToRefs(useTorrentDetailStore())
const vuetorrentStore = useVueTorrentStore()
const canvas = ref<HTMLCanvasElement>()
const selectedFiles = computed(() => cachedFiles.value.filter(f => f.priority !== FilePriority.DO_NOT_DOWNLOAD))
const torrentFileCount = computed(() => cachedFiles.value.length)
const torrentFileName = computed(() => (selectedFiles.value.length === 1 ? selectedFiles.value[0].name : ''))
Expand All @@ -38,62 +34,6 @@ const uploadSpeedAvg = computed(() => properties.value?.up_speed_avg ?? 0)
const torrentStateColor = computed(() => `torrent-${props.torrent.state}`)
const pieceSize = computed(() => `${parseInt(formatDataValue(torrentPieceSize.value, true))} ${formatDataUnit(torrentPieceSize.value, true)}`)
const isFetchingMetadata = computed(() => props.torrent.state === TorrentState.META_DL)
const shouldRenderPieceState = computed(() => !isFetchingMetadata.value && torrentPieceCount.value > 0 && torrentPieceCount.value < vuetorrentStore.canvasRenderThreshold)
const shouldRefreshPieceState = computed(() => shouldRenderPieceState.value && torrentPieceCount.value < vuetorrentStore.canvasRefreshThreshold)
/**
* Source:
* https://github.com/qbittorrent/qBittorrent/blob/6229b817300344759139d2fedbd59651065a561d/src/webui/www/private/scripts/prop-general.js#L230
*/
async function renderTorrentPieceStates() {
if (!canvas.value) return
const pieces = await maindataStore.fetchPieceState(props.torrent.hash)
canvas.value.width = pieces.length || -1
const ctx = canvas.value.getContext('2d') as CanvasRenderingContext2D
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)
// Group contiguous colors together and draw as a single rectangle
let color = ''
let rectWidth = 1
for (let i = 0; i < pieces.length; ++i) {
const state = pieces[i]
let newColor = ''
if (state === PieceState.DOWNLOADING) newColor = theme.current.value.colors['torrent-downloading']
else if (state === PieceState.DOWNLOADED) newColor = theme.current.value.colors['torrent-pausedUP']
else if (state === PieceState.MISSING) {
const selected_piece_ranges = cachedFiles.value.filter(file => file.priority !== FilePriority.DO_NOT_DOWNLOAD).map(file => file.piece_range)
for (const [min_piece_range, max_piece_range] of selected_piece_ranges) {
if (i > min_piece_range && i < max_piece_range) {
newColor = theme.current.value.colors['torrent-pausedDL']
break
}
}
}
if (newColor === color) {
++rectWidth
continue
}
if (color !== '') {
ctx.fillStyle = color
ctx.fillRect(i - rectWidth, 0, rectWidth, canvas.value.height)
}
rectWidth = 1
color = newColor
}
// Fill a rect at the end of the canvas if one is needed
if (color !== '') {
ctx.fillStyle = color
ctx.fillRect(pieces.length - rectWidth, 0, rectWidth, canvas.value.height)
}
}
async function copyHash() {
try {
Expand All @@ -118,12 +58,6 @@ function openMoveTorrentFileDialog() {
})
}
watch(cachedFiles, () => {
if (props.isActive && shouldRefreshPieceState.value) {
renderTorrentPieceStates()
}
})
function handleKeyboardShortcuts(e: KeyboardEvent) {
if (dialogStore.hasActiveDialog || !props.isActive) return false
Expand Down Expand Up @@ -156,7 +90,7 @@ onMounted(() => {
document.addEventListener('keydown', handleKeyboardShortcuts)
})
onUnmounted(() => {
onUnmounted(async () => {
document.removeEventListener('keydown', handleKeyboardShortcuts)
})
</script>
Expand Down Expand Up @@ -193,15 +127,8 @@ onUnmounted(() => {
<div v-if="isFetchingMetadata">
<span>{{ $t('torrentDetail.overview.waitingForMetadata') }}</span>
</div>
<div v-else-if="shouldRenderPieceState">
<canvas ref="canvas" width="0" height="10" />
</div>

<div v-if="!isFetchingMetadata && !shouldRenderPieceState">
<span>{{ $t('torrentDetail.overview.canvasRenderDisabled') }}</span>
</div>
<div v-else-if="!isFetchingMetadata && !shouldRefreshPieceState">
<span>{{ $t('torrentDetail.overview.canvasRefreshDisabled') }}</span>
<div v-else>
<PieceCanvas :torrent="torrent" :isActive="isActive" />
</div>

<div v-if="torrentPieceCount > 0">
Expand Down Expand Up @@ -327,11 +254,6 @@ onUnmounted(() => {
</template>

<style scoped>
canvas {
height: 100%;
width: 100%;
}
.chipgap {
gap: 4px;
}
Expand Down
Loading

0 comments on commit 5ed280c

Please sign in to comment.