mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2024-10-22 10:46:28 +03:00
perf(TorrentDetail): Use colors for ratio and chips (#1662)
This commit is contained in:
parent
bd71710412
commit
c902d622b9
11 changed files with 161 additions and 51 deletions
79
src/components/Core/ColoredChip.spec.ts
Normal file
79
src/components/Core/ColoredChip.spec.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import i18n from '@/plugins/i18n'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import ColoredChip from './ColoredChip.vue'
|
||||
|
||||
const defaultColor = 'default'
|
||||
const disabledValue = 'disabled value'
|
||||
const value = 'foo'
|
||||
const fooRgb = 'rgb(123, 47, 222)'
|
||||
|
||||
describe('ColoredChip.vue', () => {
|
||||
it('should render chip with random color and value', () => {
|
||||
const chip = mount(ColoredChip, {
|
||||
props: { defaultColor, value },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
initialState: {
|
||||
vuetorrent: {
|
||||
enableHashColors: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
i18n,
|
||||
vuetify
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(chip.attributes().style).toContain(`background-color: ${fooRgb}`)
|
||||
expect(chip.text()).toBe(value)
|
||||
})
|
||||
|
||||
it('should render chip with default color and value', () => {
|
||||
const chip = mount(ColoredChip, {
|
||||
props: { defaultColor, value },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
initialState: {
|
||||
vuetorrent: {
|
||||
enableHashColors: false
|
||||
}
|
||||
}
|
||||
}),
|
||||
i18n,
|
||||
vuetify
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(chip.classes()).toContain(`bg-${defaultColor}`)
|
||||
expect(chip.text()).toBe(value)
|
||||
})
|
||||
|
||||
it('should render chip with default color and default value', () => {
|
||||
const chip = mount(ColoredChip, {
|
||||
props: { defaultColor, disabled: true, disabledValue, value },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
initialState: {
|
||||
vuetorrent: {
|
||||
enableHashColors: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
i18n,
|
||||
vuetify
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(chip.classes()).toContain(`bg-${defaultColor}`)
|
||||
expect(chip.text()).toBe(disabledValue)
|
||||
})
|
||||
})
|
31
src/components/Core/ColoredChip.vue
Normal file
31
src/components/Core/ColoredChip.vue
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { computed } from 'vue'
|
||||
import { getColorFromName } from '@/helpers'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
defaultColor: string
|
||||
disabled?: boolean
|
||||
disabledValue?: string
|
||||
value: string
|
||||
}>(),
|
||||
{
|
||||
disabled: false
|
||||
}
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
const { enableHashColors } = storeToRefs(useVueTorrentStore())
|
||||
|
||||
const chipColor = computed(() => (props.disabled || !enableHashColors.value ? props.defaultColor : getColorFromName(props.value)))
|
||||
const chipValue = computed(() => (props.disabled ? props.disabledValue || t('common.none') : props.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-chip :color="chipColor" variant="flat">
|
||||
{{ chipValue }}
|
||||
</v-chip>
|
||||
</template>
|
|
@ -1,28 +1,22 @@
|
|||
import { describe, beforeEach, it, expect, vi } from 'vitest'
|
||||
import { VueWrapper, mount } from '@vue/test-utils'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
|
||||
import DataCard from './DataCard.vue'
|
||||
import i18n from '@/plugins/i18n'
|
||||
import vuetify from '@/plugins/vuetify'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount, VueWrapper } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import DataCard from './DataCard.vue'
|
||||
|
||||
const title = 'Downloaded'
|
||||
const value = 10000
|
||||
const color = 'download'
|
||||
|
||||
let wrapper: VueWrapper
|
||||
describe('StorageCard.vue', () => {
|
||||
describe('DataCard.vue', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(DataCard, {
|
||||
propsData: { title, value, color },
|
||||
global: {
|
||||
plugins: [
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn()
|
||||
}),
|
||||
i18n,
|
||||
vuetify
|
||||
]
|
||||
plugins: [createTestingPinia(), i18n, vuetify]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { useTorrentDetailStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import ColoredChip from '@/components/Core/ColoredChip.vue'
|
||||
|
||||
const props = defineProps<{ torrent: Torrent }>()
|
||||
|
||||
|
@ -17,8 +18,8 @@ const values = [
|
|||
{ title: 'name', getter: () => props.torrent.name },
|
||||
{ title: 'save_path', getter: () => props.torrent.savePath },
|
||||
{ title: 'tracker', getter: () => props.torrent.tracker },
|
||||
{ title: 'comment', getter: () => properties.value?.comment ?? '' },
|
||||
{ title: 'created_by', getter: () => properties.value?.created_by ?? '' }
|
||||
{ title: 'comment', getter: () => properties.value?.comment },
|
||||
{ title: 'created_by', getter: () => properties.value?.created_by }
|
||||
]
|
||||
</script>
|
||||
|
||||
|
@ -27,14 +28,12 @@ const values = [
|
|||
<v-expansion-panel-text>
|
||||
<v-list>
|
||||
<v-list-item v-for="ppt in values" :title="$t(`torrent.properties.${ppt.title}`)">
|
||||
<v-list-item-subtitle>{{ ppt.getter() }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>{{ ppt.getter() || $t('common.none') }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item :title="$t('torrent.properties.tags')">
|
||||
<div v-if="torrent.tags?.length" class="d-flex gap">
|
||||
<v-chip v-for="tag in torrent.tags" variant="flat" color="tag">
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
<ColoredChip v-for="tag in torrent.tags" defaultColor="tag" :value="tag" />
|
||||
</div>
|
||||
<v-list-item-subtitle v-else>{{ $t('torrent.properties.empty_tags') }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import ColoredChip from '@/components/Core/ColoredChip.vue'
|
||||
import ConfirmDeleteDialog from '@/components/Dialogs/ConfirmDeleteDialog.vue'
|
||||
import MoveTorrentDialog from '@/components/Dialogs/MoveTorrentDialog.vue'
|
||||
import MoveTorrentFileDialog from '@/components/Dialogs/MoveTorrentFileDialog.vue'
|
||||
import { FilePriority, TorrentState } from '@/constants/qbit'
|
||||
import { formatData, formatDataUnit, formatDataValue, formatPercent, formatSpeed, getDomainBody, splitByUrl, stringContainsUrl } from '@/helpers'
|
||||
import { formatData, formatDataUnit, formatDataValue, formatPercent, formatSpeed, getRatioColor, splitByUrl, stringContainsUrl } from '@/helpers'
|
||||
import { useContentStore, useDialogStore, useTorrentDetailStore, useVueTorrentStore } from '@/stores'
|
||||
import { Torrent } from '@/types/vuetorrent'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
@ -34,6 +35,10 @@ 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(() => [TorrentState.META_DL, TorrentState.FORCED_META_DL].includes(props.torrent.state))
|
||||
const ratioColor = computed(() => {
|
||||
if (!vuetorrentStore.enableRatioColors) return ''
|
||||
return getRatioColor(props.torrent.ratio)
|
||||
})
|
||||
|
||||
async function copyHash() {
|
||||
try {
|
||||
|
@ -188,25 +193,21 @@ onUnmounted(async () => {
|
|||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div>{{ $t('torrent.properties.category') }}:</div>
|
||||
<v-chip variant="flat" color="category">
|
||||
{{ torrent.category.length ? torrent.category : $t('navbar.side.filters.uncategorized') }}
|
||||
</v-chip>
|
||||
<ColoredChip default-color="category" :disabled="!torrent.category.length" :disabled-value="$t('navbar.side.filters.uncategorized')" :value="torrent.category" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<div>{{ $t('torrent.properties.tracker') }}:</div>
|
||||
<v-chip variant="flat" color="tracker">
|
||||
{{ torrent.tracker ? getDomainBody(torrent.tracker) : $t('navbar.side.filters.untracked') }}
|
||||
</v-chip>
|
||||
<ColoredChip default-color="tracker" :disabled-value="$t('navbar.side.filters.untracked')" :value="torrent.tracker_domain" />
|
||||
</v-col>
|
||||
<v-col cols="6" class="d-flex flex-wrap chipgap">
|
||||
<v-col cols="6">
|
||||
<div>{{ $t('torrent.properties.tags') }}:</div>
|
||||
<v-chip v-if="torrent.tags" v-for="tag in torrent.tags" :key="tag" variant="flat" color="tag">
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
<v-chip v-if="!torrent.tags || torrent.tags.length === 0" variant="flat" color="tag">
|
||||
<div v-if="torrent.tags.length" class="d-flex flex-wrap chipgap">
|
||||
<ColoredChip v-for="tag in torrent.tags" default-color="tag" :value="tag" />
|
||||
</div>
|
||||
<v-chip v-else variant="flat" color="tag">
|
||||
{{ $t('navbar.side.filters.untagged') }}
|
||||
</v-chip>
|
||||
</v-col>
|
||||
|
@ -222,7 +223,7 @@ onUnmounted(async () => {
|
|||
</v-col>
|
||||
<v-col cols="6">
|
||||
<div>{{ $t('torrentDetail.overview.ratio') }}:</div>
|
||||
<div>{{ torrent.ratio }}</div>
|
||||
<div :class="ratioColor">{{ torrent.ratio }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Torrent } from '@/types/vuetorrent'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { DashboardProperty } from './DashboardProperty'
|
||||
import { DashboardPropertyType } from './DashboardPropertyType'
|
||||
import { getRatioColor } from '@/helpers'
|
||||
import { useVueTorrentStore } from '@/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { DurationUnitType } from 'dayjs/plugin/duration'
|
||||
|
@ -311,10 +312,7 @@ export const propsMetadata: PropertyMetadata = {
|
|||
const { enableRatioColors } = storeToRefs(useVueTorrentStore())
|
||||
|
||||
if (!enableRatioColors.value) return ''
|
||||
if (v < 0.5) return 'text-ratio-bad'
|
||||
if (v < 1) return 'text-ratio-almost'
|
||||
if (v < 5) return 'text-ratio-good'
|
||||
return 'text-ratio-best'
|
||||
return getRatioColor(v)
|
||||
}
|
||||
},
|
||||
type: DashboardPropertyType.TEXT
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
import { expect, test } from 'vitest'
|
||||
import { getColorFromName } from './colors'
|
||||
import { getColorFromName, getRatioColor } from './colors'
|
||||
|
||||
test('helpers/colors/getColorFromName', () => {
|
||||
expect(getColorFromName('foo')).toBe('#7b2fde')
|
||||
expect(getColorFromName('bar')).toBe('#97e374')
|
||||
expect(getColorFromName('baz')).toBe('#447ecf')
|
||||
})
|
||||
|
||||
test('helpers/colors/getRatioColor', () => {
|
||||
expect(getRatioColor(0)).toBe('text-ratio-bad')
|
||||
expect(getRatioColor(0.4)).toBe('text-ratio-bad')
|
||||
expect(getRatioColor(0.5)).toBe('text-ratio-almost')
|
||||
expect(getRatioColor(0.9)).toBe('text-ratio-almost')
|
||||
expect(getRatioColor(1)).toBe('text-ratio-good')
|
||||
expect(getRatioColor(4.9)).toBe('text-ratio-good')
|
||||
expect(getRatioColor(5)).toBe('text-ratio-best')
|
||||
expect(getRatioColor(10)).toBe('text-ratio-best')
|
||||
})
|
||||
|
|
|
@ -15,3 +15,10 @@ export function getColorFromName(name: string) {
|
|||
|
||||
return color.toHexString()
|
||||
}
|
||||
|
||||
export function getRatioColor(ratio: number) {
|
||||
if (ratio < 0.5) return 'text-ratio-bad'
|
||||
if (ratio < 1) return 'text-ratio-almost'
|
||||
if (ratio < 5) return 'text-ratio-good'
|
||||
return 'text-ratio-best'
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getColorFromName } from './colors'
|
||||
import { getColorFromName, getRatioColor } from './colors'
|
||||
import { formatDataValue, formatDataUnit, formatData } from './data'
|
||||
import { formatEta, formatTimeMs, formatTimeSec } from './datetime'
|
||||
import { toPrecision, formatPercent } from './number'
|
||||
|
@ -8,6 +8,7 @@ import { titleCase, capitalize, extractHostname, getDomainBody, splitByUrl, stri
|
|||
|
||||
export {
|
||||
getColorFromName,
|
||||
getRatioColor,
|
||||
formatDataValue,
|
||||
formatDataUnit,
|
||||
formatData,
|
||||
|
|
|
@ -1,15 +1,3 @@
|
|||
import { vi } from 'vitest'
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(query => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(), // deprecated
|
||||
removeListener: vi.fn(), // deprecated
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn()
|
||||
}))
|
||||
})
|
||||
vi.mock('vue-router')
|
||||
|
|
|
@ -57,6 +57,7 @@ export default defineConfig(({ mode }) => {
|
|||
},
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
setupFiles: [resolve(__dirname, 'tests/setup.ts')],
|
||||
coverage: {
|
||||
reportsDirectory: './tests/unit/coverage'
|
||||
|
|
Loading…
Reference in a new issue