Skip to content

Commit

Permalink
perf(TorrentDetail): Use colors for ratio and chips (#1662)
Browse files Browse the repository at this point in the history
  • Loading branch information
Larsluph committed Apr 25, 2024
1 parent bd71710 commit c902d62
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 50 deletions.
79 changes: 79 additions & 0 deletions src/components/Core/ColoredChip.spec.ts
@@ -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 changes: 31 additions & 0 deletions src/components/Core/ColoredChip.vue
@@ -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>
18 changes: 6 additions & 12 deletions src/components/Core/DataCard.spec.ts
@@ -1,28 +1,22 @@
import { describe, beforeEach, it, expect, vi } from 'vitest'
import { VueWrapper, mount } from '@vue/test-utils'
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'
import i18n from '@/plugins/i18n'
import vuetify from '@/plugins/vuetify'

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]
}
})
})
Expand Down
11 changes: 5 additions & 6 deletions src/components/TorrentDetail/Info/PanelLongText.vue
Expand Up @@ -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 }>()
Expand All @@ -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>

Expand All @@ -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>
Expand Down
27 changes: 14 additions & 13 deletions src/components/TorrentDetail/Overview.vue
@@ -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'
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
6 changes: 2 additions & 4 deletions src/constants/vuetorrent/DashboardDefaults.ts
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion src/helpers/colors.spec.ts
@@ -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')
})
7 changes: 7 additions & 0 deletions src/helpers/colors.ts
Expand Up @@ -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'
}
3 changes: 2 additions & 1 deletion src/helpers/index.ts
@@ -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'
Expand All @@ -8,6 +8,7 @@ import { titleCase, capitalize, extractHostname, getDomainBody, splitByUrl, stri

export {
getColorFromName,
getRatioColor,
formatDataValue,
formatDataUnit,
formatData,
Expand Down
14 changes: 1 addition & 13 deletions tests/setup.ts
@@ -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')
1 change: 1 addition & 0 deletions vite.config.ts
Expand Up @@ -57,6 +57,7 @@ export default defineConfig(({ mode }) => {
},
test: {
environment: 'jsdom',
globals: true,
setupFiles: [resolve(__dirname, 'tests/setup.ts')],
coverage: {
reportsDirectory: './tests/unit/coverage'
Expand Down

0 comments on commit c902d62

Please sign in to comment.