Remove vue 2 filters

Fixes #1759
This commit is contained in:
Kasper Seweryn 2022-04-30 22:46:37 +02:00 committed by Georg Krause
parent 12b09b085a
commit b7d66232f6
48 changed files with 368 additions and 382 deletions

View File

@ -82,7 +82,6 @@
"vite": "2.8.6",
"vite-plugin-pwa": "0.12.0",
"vue-jest": "3.0.7",
"vue-template-compiler": "2.6.14",
"workbox-core": "6.5.3",
"workbox-precaching": "6.5.3",
"workbox-routing": "6.5.3",

View File

@ -252,7 +252,7 @@
import { mapState } from 'vuex'
import { get } from 'lodash-es'
import showdown from 'showdown'
import { humanSize } from '~/init/filters'
import { humanSize } from '~/utils/filters'
import SignupForm from '~/components/auth/SignupForm.vue'
import LogoText from '~/components/LogoText.vue'

View File

@ -278,7 +278,7 @@ We render some markdown to html here, the content is set by the admin so we shou
class="right aligned"
>
<span class="features-status ui text">
{{ defaultUploadQuota * 1000 * 1000 | humanSize }}
{{ humanSize(defaultUploadQuota * 1000 * 1000) }}
</span>
</td>
<td
@ -436,10 +436,15 @@ We render some markdown to html here, the content is set by the admin so we shou
import { mapState } from 'vuex'
import { get } from 'lodash-es'
import showdown from 'showdown'
import { humanSize } from '~/utils/filters'
export default {
setup () {
return { humanSize }
},
data () {
return {
// TODO (wvffle): Remove v-html
markdown: new showdown.Converter(),
showAllowedDomains: false
}

View File

@ -332,7 +332,7 @@ import AlbumWidget from '~/components/audio/album/Widget.vue'
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
import LoginForm from '~/components/auth/LoginForm.vue'
import SignupForm from '~/components/auth/SignupForm.vue'
import { humanSize } from '~/init/filters'
import { humanSize } from '~/utils/filters'
export default {
components: {

View File

@ -78,7 +78,7 @@
import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue'
import { momentFormat } from '~/init/filters'
import { momentFormat } from '~/utils/filters'
import moment from 'moment'
export default {

View File

@ -37,7 +37,7 @@
</div>
</div>
<div class="extra content">
<span v-if="album.release_date">{{ album.release_date | moment('Y') }} · </span>
<span v-if="album.release_date">{{ momentFormat(album.release_date, 'Y') }} · </span>
<translate
translate-context="*/*/*"
:translate-params="{count: album.tracks_count}"
@ -59,6 +59,7 @@
<script>
import PlayButton from '~/components/audio/PlayButton.vue'
import { momentFormat} from '~/utils/filters'
export default {
components: {
@ -67,6 +68,9 @@ export default {
props: {
album: { type: Object, required: true }
},
setup () {
return { momentFormat }
},
computed: {
imageUrl () {
if (this.album.cover && this.album.cover.urls.original) {

View File

@ -22,7 +22,7 @@
class="discrete link"
:to="{name: 'library.artists.detail', params: {id: artist.id}}"
>
{{ artist.name|truncate(30) }}
{{ truncate(artist.name, 30) }}
</router-link>
</strong>
@ -67,6 +67,7 @@
<script>
import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue'
import { truncate } from '~/utils/filters'
export default {
components: {
@ -74,6 +75,9 @@ export default {
TagsList
},
props: { artist: { type: Object, required: true } },
setup () {
return { truncate }
},
data () {
return {
initialAlbums: 30,

View File

@ -158,12 +158,12 @@
</template>
<script>
import { clone } from 'lodash-es'
import { clone, uniqBy } from 'lodash-es'
import axios from 'axios'
import TrackRow from '~/components/audio/track/Row.vue'
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
import Pagination from '~/components/Pagination.vue'
import { unique } from '~/init/filters'
import { unique } from '~/utils/filters'
export default {
components: {
@ -205,7 +205,7 @@ export default {
computed: {
allTracks () {
const tracks = (this.tracks || []).concat(this.additionalTracks)
return unique(tracks, 'id')
return uniqBy(tracks, 'id')
},
labels () {

View File

@ -137,7 +137,7 @@
</template>
<div class="sub header">
<template v-if="file.response.uuid">
{{ file.size | humanSize }}
{{ humanSize(file.size) }}
<template v-if="file.response.duration">
· <human-duration :duration="file.response.duration" />
</template>
@ -164,7 +164,7 @@
>
Pending
</translate>
· {{ file.size | humanSize }}
· {{ humanSize(file.size) }}
· {{ parseInt(file.progress) }}%
</template>
· <a @click.stop.prevent="remove(file)">
@ -243,6 +243,7 @@ import LicenseSelect from '~/components/channels/LicenseSelect.vue'
import AlbumSelect from '~/components/channels/AlbumSelect.vue'
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
import { humanSize } from '~/utils/filters'
function setIfEmpty (obj, k, v) {
if (obj[k] !== undefined) {
@ -261,6 +262,9 @@ export default {
props: {
channel: { type: Object, default: null, required: false }
},
setup () {
return { humanSize }
},
data () {
return {
availableChannels: {

View File

@ -55,7 +55,7 @@
<translate translate-context="Content/Library/Paragraph">
Remaining storage space:
</translate>
{{ (statusData.quotaStatus.remaining * 1000 * 1000) - statusData.uploadedSize | humanSize }}
{{ (statusData.quotaStatus.remaining * 1000 * 1000) - humanSize(statusData.uploadedSize) }}
</template>
</div>
<div class="ui hidden clearing divider mobile-only" />
@ -144,7 +144,7 @@
<script>
import Modal from '~/components/semantic/Modal.vue'
import ChannelUploadForm from '~/components/channels/UploadForm.vue'
import { humanSize } from '~/init/filters'
import { humanSize } from '~/utils/filters'
import {onBeforeRouteLeave, onBeforeRouteUpdate} from 'vue-router'
export default {
@ -159,6 +159,8 @@ export default {
onBeforeRouteUpdate(guard)
onBeforeRouteLeave(guard)
return { humanSize }
},
data () {
return {

View File

@ -1,42 +1,64 @@
<script setup lang="ts">
import { toRefs } from '@vueuse/core'
import { computed } from 'vue'
import { truncate } from '~/utils/filters'
import { Actor } from '~/types'
interface Props {
actor: Actor
avatar?: boolean
admin?: boolean
displayName?: boolean
truncateLength?: number
}
const { displayName, actor, truncateLength, admin, avatar } = toRefs(withDefaults(
defineProps<Props>(),
{
avatar: true,
admin: false,
displayName: false,
truncateLength: 30
}
))
const repr = computed(() => {
const name = displayName.value || actor.value.is_local
? actor.value.preferred_username
: actor.value.full_username
return truncate(name, truncateLength.value)
})
const url = computed(() => {
if (admin.value) {
return { name: 'manage.moderation.accounts.detail', params: { id: actor.value.full_username } }
}
if (actor.value.is_local) {
return { name: 'profile.overview', params: { username: actor.value.preferred_username } }
}
return {
name: 'profile.full.overview',
params: {
username: actor.value.preferred_username,
domain: actor.value.domain
}
}
})
</script>
<template>
<router-link
:to="url"
:title="actor.full_username"
>
<template v-if="avatar">
<actor-avatar :actor="actor" /><span>&nbsp;</span>
</template><slot>{{ repr | truncate(truncateLength) }}</slot>
<actor-avatar
v-if="avatar"
:actor="actor"
/>
<span>&nbsp;</span>
<slot>{{ repr }}</slot>
</router-link>
</template>
<script>
export default {
props: {
actor: { type: Object, required: true },
avatar: { type: Boolean, default: true },
admin: { type: Boolean, default: false },
displayName: { type: Boolean, default: false },
truncateLength: { type: Number, default: 30 }
},
computed: {
url () {
if (this.admin) {
return { name: 'manage.moderation.accounts.detail', params: { id: this.actor.full_username } }
}
if (this.actor.is_local) {
return { name: 'profile.overview', params: { username: this.actor.preferred_username } }
} else {
return { name: 'profile.full.overview', params: { username: this.actor.preferred_username, domain: this.actor.domain } }
}
},
repr () {
if (this.displayName || this.actor.is_local) {
return this.actor.preferred_username
} else {
return this.actor.full_username
}
}
}
}
</script>

View File

@ -1,26 +1,29 @@
<script setup lang="ts">
import moment from 'moment'
import { computed } from 'vue'
interface Props {
seconds?: number
}
const props = defineProps<Props>()
const duration = computed(() => {
const { minutes, hours } = moment.duration(props.seconds, 'seconds')
return { minutes: minutes(), hours: hours() }
})
</script>
<template>
<span>
<translate
v-if="durationData.hours > 0"
v-if="duration.hours > 0"
translate-context="Content/*/Paragraph"
:translate-params="{minutes: durationData.minutes, hours: durationData.hours}"
:translate-params="duration"
>%{ hours } h %{ minutes } min</translate>
<translate
v-else
translate-context="Content/*/Paragraph"
:translate-params="{minutes: durationData.minutes}"
:translate-params="duration"
>%{ minutes } min</translate>
</span>
</template>
<script>
import { secondsToObject } from '~/init/filters'
export default {
props: { seconds: { type: Number, default: null } },
computed: {
durationData () {
return secondsToObject(this.seconds)
}
}
}
</script>

View File

@ -1,32 +1,32 @@
<script setup lang="ts">
import { momentFormat } from '~/utils/filters'
import { useTimeAgo } from '@vueuse/core'
import { computed } from 'vue'
interface Props {
date: string,
icon?: boolean
}
const props = withDefaults(
defineProps<Props>(),
{ icon: false }
)
const date = computed(() => new Date(props.date))
// TODO (wvffle): Translate useTimeAgo
const realDate = useTimeAgo(date)
</script>
<template>
<time
:datetime="date"
:title="date | moment"
:title="momentFormat(date)"
>
<i
v-if="icon"
v-if="props.icon"
class="outline clock icon"
/>
{{ realDate | ago($store.state.ui.momentLocale) }}
{{ realDate }}
</time>
</template>
<script>
import { mapState } from 'vuex'
export default {
props: {
date: { type: String, required: true },
icon: { type: Boolean, required: false, default: false }
},
computed: {
...mapState({
lastDate: state => state.ui.lastDate
}),
realDate () {
if (this.lastDate) {
// dummy code to trigger a recompute to update the ago render
}
return this.date
}
}
}
</script>

View File

@ -1,12 +1,18 @@
<script setup lang="ts">
import { toRefs } from '@vueuse/core'
import { computed } from 'vue'
import time from '~/utils/time'
interface Props {
duration: number
}
const { duration } = toRefs(defineProps<Props>())
const parsedDuration = computed(() => time.parse(duration.value))
</script>
<template>
<time :datetime="`${duration}s`">
{{ duration | duration }}
{{ parsedDuration }}
</time>
</template>
<script>
export default {
props: {
duration: { type: Number, required: true }
}
}
</script>

View File

@ -141,7 +141,7 @@
v-if="object.release_date || (totalTracks > 0)"
class="ui small hidden divider"
/>
<span v-if="object.release_date">{{ object.release_date | moment('Y') }} · </span>
<span v-if="object.release_date">{{ momentFormat(object.release_date, 'Y') }} · </span>
<template v-if="totalTracks > 0">
<translate
v-if="isSerie"
@ -254,6 +254,7 @@ import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue'
import ArtistLabel from '~/components/audio/ArtistLabel.vue'
import AlbumDropdown from './AlbumDropdown.vue'
import { momentFormat} from '~/utils/filters'
function groupByDisc (initial) {
function inner (acc, track) {
@ -276,6 +277,9 @@ export default {
AlbumDropdown
},
props: { id: { type: [String, Number], required: true } },
setup () {
return { momentFormat }
},
data () {
return {
isLoading: true,

View File

@ -61,13 +61,13 @@
</translate>
</div>
<div class="value">
{{ remainingSpace * 1000 * 1000 | humanSize }}
{{ humanSize(remainingSpace * 1000 * 1000) }}
</div>
</div>
<div class="ui divider" />
<h2 class="ui header">
<translate translate-context="Content/Library/Title/Verb">
Upload music from your local storage
Upload music from '~/your local storage
</translate>
</h2>
<div class="ui message">
@ -174,9 +174,9 @@
:key="file.id"
>
<td :title="file.name">
{{ file.name | truncate(60) }}
{{ truncate(file.name, 60) }}
</td>
<td>{{ file.size | humanSize }}</td>
<td>{{ humanSize(file.size) }}</td>
<td>
<span
v-if="file.error"
@ -318,6 +318,7 @@ import FsBrowser from './FsBrowser.vue'
import FsLogs from './FsLogs.vue'
import LibraryFilesTable from '~/views/content/libraries/FilesTable.vue'
import moment from 'moment'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -330,6 +331,9 @@ export default {
library: { type: Object, required: true },
defaultImportReference: { type: String, required: false, default: '' }
},
setup () {
return { humanSize, truncate }
},
data () {
const importReference = this.defaultImportReference || moment().format()
// Since $router.replace is pushing the same route, it raises NavigationDuplicated

View File

@ -228,7 +228,7 @@ import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import Modal from '~/components/semantic/Modal.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import ReportMixin from '~/components/mixins/Report.vue'
import { momentFormat } from '~/init/filters'
import { momentFormat } from '~/utils/filters'
import updateQueryString from '~/composables/updateQueryString'
import useLogger from '~/composables/useLogger'

View File

@ -48,7 +48,7 @@
</td>
<td class="right aligned">
<template v-if="upload.duration">
{{ upload.duration | duration }}
{{ time.parse(upload.duration) }}
</template>
<translate
v-else
@ -66,7 +66,7 @@
</td>
<td class="right aligned">
<template v-if="upload.size">
{{ upload.size | humanSize }}
{{ humanSize(upload.size) }}
</template>
<translate
v-else
@ -102,7 +102,7 @@
</td>
<td class="right aligned">
<template v-if="upload.bitrate">
{{ upload.bitrate | humanSize }}/s
{{ humanSize(upload.bitrate) }}/s
</template>
<translate
v-else
@ -186,7 +186,7 @@
</td>
<td class="right aligned">
<template v-if="track.album && track.album.release_date">
{{ track.album.release_date | moment('Y') }}
{{ momentFormat(track.album.release_date, 'Y') }}
</template>
<template v-else>
<translate translate-context="*/*/*">
@ -205,7 +205,7 @@
<span
v-if="track.copyright"
:title="track.copyright"
>{{ track.copyright|truncate(50) }}</span>
>{{ truncate(track.copyright, 50) }}</span>
<template v-else>
<translate translate-context="*/*/*">
N/A
@ -246,7 +246,7 @@
target="_blank"
rel="noopener noreferrer"
>
{{ track.fid|truncate(65) }}
{{ truncate(track.fid, 65) }}
</a>
</td>
</tr>
@ -298,6 +298,8 @@ import axios from 'axios'
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
import TagsList from '~/components/tags/List.vue'
import PlaylistWidget from '~/components/playlists/Widget.vue'
import { humanSize, momentFormat, truncate } from '~/utils/filters'
import time from '~/utils/time'
export default {
components: {
@ -309,6 +311,9 @@ export default {
track: { type: Object, required: true },
libraries: { type: Array, default: null }
},
setup () {
return { humanSize, momentFormat, time, truncate }
},
data () {
return {
id: this.track.id,

View File

@ -105,7 +105,7 @@
>
<td>
<router-link :to="{name: 'manage.library.tags.detail', params: {id: scope.obj.name }}">
{{ scope.obj.name|truncate(30, "…", true) }}
{{ truncate(scope.obj.name, 30, undefined, true) }}
</router-link>
</td>
<td>
@ -156,6 +156,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
import TranslationsMixin from '~/components/mixins/Translations.vue'
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import { truncate } from '~/utils/filters'
export default {
components: {
@ -167,6 +168,9 @@ export default {
props: {
filters: { type: Object, required: false, default: () => { return {} } }
},
setup () {
return { truncate }
},
data () {
return {
detailedUpload: {},

View File

@ -196,7 +196,7 @@
:title="scope.obj.library.name"
@click.prevent="addSearchToken('library_id', scope.obj.library.id)"
>
{{ scope.obj.library.name | truncate(20) }}
{{ truncate(scope.obj.library.name, 20) }}
</a>
</td>
<td>
@ -258,7 +258,7 @@
</button>
</td>
<td>
<span v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</span>
<span v-if="scope.obj.size">{{ humanSize(scope.obj.size) }}</span>
<translate
v-else
translate-context="*/*/*"
@ -317,6 +317,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
import TranslationsMixin from '~/components/mixins/Translations.vue'
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -328,6 +329,9 @@ export default {
props: {
filters: { type: Object, required: false, default: function () { return {} } }
},
setup () {
return { humanSize, truncate }
},
data () {
return {
detailedUpload: {},

View File

@ -1,23 +1,59 @@
<script setup lang="ts">
import { truncate } from '~/utils/filters'
import { computed, ref } from 'vue'
interface Props {
tags: string[]
showMore?: boolean
truncateSize?: number
limit?: number
labelClasses?: string
detailRoute?: string
}
const props = withDefaults(
defineProps<Props>(),
{
showMore: true,
truncateSize: 25,
limit: 5,
labelClasses: '',
detailRoute: 'library.tags.detail'
}
)
const honorLimit = ref(true)
const tags = computed(() => {
if (!honorLimit.value) {
return props.tags
}
return props.tags.slice(0, props.limit)
})
</script>
<template>
<div class="component-tags-list">
<router-link
v-for="tag in toDisplay"
v-for="tag in tags"
:key="tag"
:to="{name: detailRoute, params: {id: tag}}"
:class="['ui', 'circular', 'hashtag', 'label', labelClasses]"
:to="{name: props.detailRoute, params: { id: tag } }"
:class="['ui', 'circular', 'hashtag', 'label', props.labelClasses]"
>
#{{ tag|truncate(truncateSize) }}
#{{ truncate(tag, props.truncateSize) }}
</router-link>
<div
v-if="showMore && toDisplay.length < tags.length"
v-if="props.showMore && tags.length < props.tags.length"
role="button"
class="ui circular inverted accent label"
@click.prevent="honorLimit = false"
>
<translate
translate-context="Content/*/Button/Label/Verb"
:translate-params="{count: tags.length - toDisplay.length}"
:translate-n="tags.length - toDisplay.length"
:translate-params="{ count: props.tags.length - tags.length }"
:translate-n="props.tags.length - tags.length"
translate-plural="Show %{ count } more tags"
>
Show 1 more tag
@ -25,28 +61,3 @@
</div>
</div>
</template>
<script>
export default {
props: {
tags: { type: Array, required: true },
showMore: { type: Boolean, default: true },
truncateSize: { type: Number, default: 25 },
limit: { type: Number, default: 5 },
labelClasses: { type: String, default: '' },
detailRoute: { type: String, default: 'library.tags.detail' }
},
data () {
return {
honorLimit: true
}
},
computed: {
toDisplay () {
if (!this.honorLimit) {
return this.tags
}
return (this.tags || []).slice(0, this.limit)
}
}
}
</script>

View File

@ -1,140 +0,0 @@
import { InitModule } from '~/types'
import Vue from 'vue'
import time from '~/utils/time'
import moment from 'moment'
export function truncate (str: string, max = 100, ellipsis = '…', middle = false) {
if (max === 0) {
return ''
}
if (str.length <= max) {
return str
}
if (middle) {
const sepLen = 1
const charsToShow = max - sepLen
const frontChars = Math.ceil(charsToShow / 2)
const backChars = Math.floor(charsToShow / 2)
return str.substr(0, frontChars) +
ellipsis +
str.substr(str.length - backChars)
} else {
return str.slice(0, max) + ellipsis
}
}
export function ago (date: Date, locale: string) {
locale = locale || 'en'
const m = moment(date)
m.locale(locale)
return m.calendar(null, {
sameDay: 'LT',
nextDay: 'L',
nextWeek: 'L',
lastDay: 'L',
lastWeek: 'L',
sameElse: 'L'
})
}
export function fromNow (date: Date, locale: string) {
locale = 'en'
moment.locale('en', {
relativeTime: {
future: 'in %s',
past: '%s ago',
s: 'seconds',
ss: '%ss',
m: 'a minute',
mm: '%dm',
h: 'an hour',
hh: '%dh',
d: 'a day',
dd: '%dd',
M: 'a month',
MM: '%dM',
y: 'a year',
yy: '%dY'
}
})
const m = moment(date)
m.locale(locale)
return m.fromNow(true)
}
export function secondsToObject (seconds: number) {
const m = moment.duration(seconds, 'seconds')
return {
seconds: m.seconds(),
minutes: m.minutes(),
hours: m.hours()
}
}
export function padDuration (duration: string) {
let s = String(duration)
while (s.length < 2) { s = '0' + s }
return s
}
export function duration (seconds: string) {
return time.parse(+seconds)
}
export function momentFormat (date: Date, format: string) {
format = format || 'lll'
return moment(date).format(format)
}
export function year (date: Date) {
return moment(date).year()
}
export function capitalize (str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
export function humanSize (bytes: number) {
const si = true
const thresh = si ? 1000 : 1024
if (Math.abs(bytes) < thresh) {
return bytes + ' B'
}
const units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
let u = -1
do {
bytes /= thresh
++u
} while (Math.abs(bytes) >= thresh && u < units.length - 1)
return bytes.toFixed(1) + ' ' + units[u]
}
// Remove duplicates from a list
export function unique (list: Record<string, unknown>[], property: string) {
property = property || 'id'
const unique: Record<string, unknown>[] = []
list.map(x => unique.filter(a => a[property] === x[property]).length > 0 ? null : unique.push(x))
return unique
}
// TODO (wvffle): Migrate to Vue 3
// Replace filters with computed values
export const install: InitModule = () => {
Vue.filter('humanSize', humanSize)
Vue.filter('unique', unique)
Vue.filter('capitalize', capitalize)
Vue.filter('moment', momentFormat)
Vue.filter('year', year)
Vue.filter('duration', duration)
Vue.filter('padDuration', padDuration)
Vue.filter('secondsToObject', secondsToObject)
Vue.filter('fromNow', fromNow)
Vue.filter('ago', ago)
Vue.filter('truncate', truncate)
}

View File

@ -70,3 +70,11 @@ export interface ListenWSEvent {
}
export type WebSocketEvent = PendingReviewEditsWSEvent | PendingReviewReportsWSEvent | PendingReviewRequestsWSEvent | ListenWSEvent
// Yet uncategorized stuff
export interface Actor {
preferred_username: string
full_username: string
is_local: boolean
domain: string
}

View File

@ -0,0 +1,38 @@
import moment from 'moment'
export function truncate (str: string, max = 100, ellipsis = '…', middle = false) {
if (max === 0) return ''
if (str.length <= max) return str
if (!middle) return str.slice(0, max) + ellipsis
const charsToShow = max - ellipsis.length
return str.slice(0, Math.ceil(charsToShow / 2)) +
ellipsis +
str.slice(-Math.floor(charsToShow / 2))
}
export function momentFormat (date: Date, format = 'lll') {
return moment(date).format(format)
}
const HUMAN_UNITS = {
SI: ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
powerOf2: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
}
export function humanSize (bytes: number, isSI = true) {
const threshold = isSI ? 1000 : 1024
if (Math.abs(bytes) < threshold) {
return `${bytes} B`
}
const units = HUMAN_UNITS[isSI ? 'SI' : 'powerOf2']
let u = -1
do {
bytes /= threshold
++u
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
return `${bytes.toFixed(1)} ${units[u]}`
}

View File

@ -26,7 +26,7 @@
src="../../assets/audio/default-cover.png"
>
<div class="content">
{{ object.artist.name | truncate(100) }}
{{ truncate(object.artist.name) }}
<div class="sub header">
<template v-if="object.artist.is_local">
<span class="ui tiny accent label">
@ -354,7 +354,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -364,7 +364,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
<tr>
@ -418,6 +418,7 @@ import axios from 'axios'
import TagsList from '~/components/tags/List.vue'
import FetchButton from '~/components/federation/FetchButton.vue'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -425,6 +426,9 @@ export default {
TagsList
},
props: { id: { type: String, required: true } },
setup () {
return { humanSize, truncate }
},
data () {
return {
isLoading: true,

View File

@ -26,7 +26,7 @@
src="../../../assets/audio/default-cover.png"
>
<div class="content">
{{ object.title | truncate(100) }}
{{ truncate(object.title) }}
<div class="sub header">
<template v-if="object.is_local">
<span class="ui tiny accent label">
@ -337,7 +337,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -347,7 +347,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
@ -401,6 +401,7 @@
import axios from 'axios'
import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue'
import { humanSize, truncate} from '~/utils/filters'
export default {
components: {
@ -408,6 +409,9 @@ export default {
TagsList
},
props: { id: { type: Number, required: true } },
setup () {
return { humanSize, truncate }
},
data () {
return {
isLoading: true,

View File

@ -26,7 +26,7 @@
src="../../../assets/audio/default-cover.png"
>
<div class="content">
{{ object.name | truncate(100) }}
{{ truncate(object.name) }}
<div class="sub header">
<template v-if="object.is_local">
<span class="ui tiny accent label">
@ -336,7 +336,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -346,7 +346,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
@ -413,6 +413,7 @@ import axios from 'axios'
import TagsList from '~/components/tags/List.vue'
import FetchButton from '~/components/federation/FetchButton.vue'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -420,6 +421,9 @@ export default {
TagsList
},
props: { id: { type: Number, required: true } },
setup () {
return { humanSize, truncate }
},
data () {
return {
isLoading: true,

View File

@ -17,7 +17,7 @@
<h2 class="ui header">
<i class="circular inverted book icon" />
<div class="content">
{{ object.name | truncate(100) }}
{{ truncate(object.name) }}
<div class="sub header">
<template v-if="object.is_local">
<span class="ui tiny accent label">
@ -285,7 +285,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -295,7 +295,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
<tr>
@ -360,6 +360,7 @@
import axios from 'axios'
import TranslationsMixin from '~/components/mixins/Translations.vue'
import useLogger from '~/composables/useLogger'
import { humanSize, truncate} from '~/utils/filters'
const logger = useLogger()
@ -368,6 +369,9 @@ export default {
TranslationsMixin
],
props: { id: { type: String, required: true } },
setup () {
return { humanSize, truncate }
},
data () {
return {
isLoading: true,

View File

@ -17,7 +17,7 @@
<h2 class="ui header">
<i class="circular inverted hashtag icon" />
<div class="content">
{{ object.name | truncate(100) }}
{{ truncate(object.name) }}
</div>
</h2>
<div class="header-buttons">
@ -210,9 +210,13 @@
<script>
import axios from 'axios'
import { truncate} from '~/utils/filters'
export default {
props: { id: { type: Number, required: true } },
setup () {
return { truncate }
},
data () {
return {
isLoading: true,

View File

@ -26,7 +26,7 @@
src="../../../assets/audio/default-cover.png"
>
<div class="content">
{{ object.title | truncate(100) }}
{{ truncate(object.title) }}
<div class="sub header">
<template v-if="object.is_local">
<span class="ui tiny accent label">
@ -402,7 +402,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -412,7 +412,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
@ -454,6 +454,7 @@
import axios from 'axios'
import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -461,6 +462,9 @@ export default {
TagsList
},
props: { id: { type: Number, required: true } },
setup () {
return { humanSize, truncate }
},
data () {
return {
isLoading: true,

View File

@ -21,7 +21,7 @@
<h2 class="ui header">
<i class="circular inverted file icon" />
<div class="content">
{{ displayName(object) | truncate(100) }}
{{ truncate(displayName(object)) }}
<div class="sub header">
<template v-if="object.is_local">
<span class="ui tiny accent label">
@ -289,7 +289,7 @@
</td>
<td>
<template v-if="object.audio_file">
{{ object.size | humanSize }}
{{ humanSize(object.size) }}
</template>
<translate
v-else
@ -306,7 +306,7 @@
</translate>
</td>
<td>
{{ object.size | humanSize }}
{{ humanSize(object.size) }}
</td>
</tr>
<tr>
@ -317,7 +317,7 @@
</td>
<td>
<template v-if="object.bitrate">
{{ object.bitrate | humanSize }}/s
{{ humanSize(object.bitrate) }}/s
</template>
<translate
v-else
@ -335,7 +335,7 @@
</td>
<td>
<template v-if="object.duration">
{{ object.duration | duration }}
{{ time.parse(object.duration) }}
</template>
<translate
v-else
@ -380,6 +380,7 @@ import axios from 'axios'
import TranslationsMixin from '~/components/mixins/Translations.vue'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import time from '~/utils/time'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -389,9 +390,11 @@ export default {
TranslationsMixin
],
props: { id: { type: Number, required: true } },
setup () {
return { humanSize, time, truncate }
},
data () {
return {
time,
detailedUpload: {},
showUploadDetailModal: false,
isLoading: true,

View File

@ -447,7 +447,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr v-if="object.user">
@ -486,7 +486,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
<tr>
@ -572,6 +572,7 @@ import $ from 'jquery'
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
import useLogger from '~/composables/useLogger'
import { humanSize} from '~/utils/filters'
const logger = useLogger()
@ -581,6 +582,9 @@ export default {
InstancePolicyCard
},
props: { id: { type: Number, required: true } },
setup () {
return { humanSize }
},
data () {
return {
isLoading: true,

View File

@ -361,7 +361,7 @@
</translate>
</td>
<td>
{{ stats.media_downloaded_size | humanSize }}
{{ humanSize(stats.media_downloaded_size) }}
</td>
</tr>
<tr>
@ -371,7 +371,7 @@
</translate>
</td>
<td>
{{ stats.media_total_size | humanSize }}
{{ humanSize(stats.media_total_size) }}
</td>
</tr>
<tr>
@ -462,6 +462,7 @@ import { get } from 'lodash-es'
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
import { humanSize} from '~/utils/filters'
export default {
components: {
@ -469,6 +470,9 @@ export default {
InstancePolicyCard
},
props: { id: { type: String, required: true }, allowListEnabled: { type: Boolean, required: true } },
setup () {
return { humanSize }
},
data () {
return {
get,

View File

@ -82,7 +82,7 @@
</template>
<script>
import { humanSize } from '~/init/filters'
import { humanSize } from '~/utils/filters'
export default {
computed: {

View File

@ -42,7 +42,7 @@
:data-tooltip="size_label"
>
<i class="database icon" />
{{ library.size | humanSize }}
{{ humanSize(library.size) }}
</span>
<i class="music icon" />
<translate
@ -79,10 +79,14 @@
<script>
import TranslationsMixin from '~/components/mixins/Translations.vue'
import { humanSize} from '~/utils/filters'
export default {
mixins: [TranslationsMixin],
props: { library: { type: Object, required: true } },
setup () {
return { humanSize }
},
computed: {
size_label () {
return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library')

View File

@ -181,7 +181,7 @@
<template v-if="scope.obj.track">
<td>
<router-link :to="{name: 'library.tracks.detail', params: {id: scope.obj.track.id }}">
{{ scope.obj.track.title|truncate(25) }}
{{ truncate(scope.obj.track.title, 25) }}
</router-link>
</td>
<td>
@ -189,7 +189,7 @@
href=""
class="discrete link"
@click.prevent="addSearchToken('artist', scope.obj.track.artist.name)"
>{{ scope.obj.track.artist.name|truncate(20) }}</a>
>{{ truncate(scope.obj.track.artist.name, 20) }}</a>
</td>
<td>
<a
@ -197,12 +197,12 @@
href=""
class="discrete link"
@click.prevent="addSearchToken('album', scope.obj.track.album.title)"
>{{ scope.obj.track.album.title|truncate(20) }}</a>
>{{ truncate(scope.obj.track.album.title, 20) }}</a>
</td>
</template>
<template v-else>
<td :title="scope.obj.source">
{{ scope.obj.source | truncate(25) }}
{{ truncate(scope.obj.source, 25) }}
</td>
<td />
<td />
@ -227,7 +227,7 @@
</button>
</td>
<td v-if="scope.obj.duration">
{{ scope.obj.duration | duration }}
{{ time.parse(scope.obj.duration) }}
</td>
<td v-else>
<translate translate-context="*/*/*">
@ -235,7 +235,7 @@
</translate>
</td>
<td v-if="scope.obj.size">
{{ scope.obj.size | humanSize }}
{{ humanSize(scope.obj.size) }}
</td>
<td v-else>
<translate translate-context="*/*/*">
@ -277,6 +277,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
import TranslationsMixin from '~/components/mixins/Translations.vue'
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import { humanSize, truncate } from '~/utils/filters'
export default {
components: {
@ -296,9 +297,11 @@ export default {
}
}
},
setup () {
return { humanSize, time, truncate }
},
data () {
return {
time,
detailedUpload: {},
showUploadDetailModal: false,
isLoading: false,

View File

@ -194,7 +194,7 @@
</template>
<script>
import axios from 'axios'
import { humanSize } from '~/init/filters'
import { humanSize } from '~/utils/filters'
import { compileTokens } from '~/search'
export default {

View File

@ -111,7 +111,7 @@
</translate>
<span v-if="object.size">
· <i class="database icon" />
{{ object.size | humanSize }}
{{ humanSize(object.size) }}
</span>
</p>
@ -230,6 +230,7 @@ import axios from 'axios'
import LibraryFollowButton from '~/components/audio/LibraryFollowButton.vue'
import ReportMixin from '~/components/mixins/Report.vue'
import RadioButton from '~/components/radios/Button.vue'
import { humanSize } from '~/utils/filters'
export default {
components: {
@ -242,6 +243,9 @@ export default {
next()
},
props: { id: { type: String, required: true } },
setup () {
return { humanSize }
},
data () {
return {
isLoading: true,

View File

@ -2,7 +2,7 @@ import {expect} from 'chai'
import Username from '~/components/common/Username.vue'
import { render } from '../../utils'
import { render } from '~/utils'
describe('Username', () => {
it('displays username', () => {

View File

@ -1,59 +1,22 @@
import {expect} from 'chai'
import moment from 'moment'
import {truncate, ago, capitalize, year, unique} from '~/init/filters'
import { expect } from 'chai'
import { truncate } from '~/filters'
describe('filters', () => {
describe('truncate', () => {
it('leave strings as it if correct size', () => {
const input = 'Hello world'
let output = truncate(input, 100)
const output = truncate(input, 100)
expect(output).to.equal(input)
})
it('returns shorter string with character', () => {
const input = 'Hello world'
let output = truncate(input, 5)
const output = truncate(input, 5)
expect(output).to.equal('Hello…')
})
it('custom ellipsis', () => {
const input = 'Hello world'
let output = truncate(input, 5, ' pouet')
const output = truncate(input, 5, ' pouet')
expect(output).to.equal('Hello pouet')
})
})
describe('ago', () => {
it('works', () => {
const input = new Date()
let output = ago(input)
let expected = moment(input).calendar(input, {
sameDay: 'LT',
nextDay: 'L',
nextWeek: 'L',
lastDay: 'L',
lastWeek: 'L',
sameElse: 'L'
})
expect(output).to.equal(expected)
})
})
describe('year', () => {
it('works', () => {
const input = '2017-07-13'
let output = year(input)
expect(output).to.equal(2017)
})
})
describe('capitalize', () => {
it('works', () => {
const input = 'hello world'
let output = capitalize(input)
expect(output).to.deep.equal('Hello world')
})
})
describe('unique', () => {
it('works', () => {
const list = [{id: 1}, {id: 2}, {id: 3}, {id: 1}]
const dedupedList = unique(list, 'id')
expect(dedupedList).to.have.deep.members([{id: 1}, {id: 3}, {id: 2}])
})
})
})

View File

@ -4,7 +4,7 @@ import {expect} from 'chai'
import moxios from 'moxios'
import store from '~/store/auth'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/auth', () => {
var sandbox

View File

@ -2,7 +2,7 @@ import {expect} from 'chai'
import store from '~/store/favorites'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/favorites', () => {
describe('mutations', () => {

View File

@ -3,7 +3,7 @@ var sinon = require('sinon')
import axios from 'axios'
import moxios from 'moxios'
import store from '~/store/instance'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/instance', () => {
var sandbox

View File

@ -2,7 +2,7 @@ import {expect} from 'chai'
import store from '~/store/player'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/player', () => {
describe('mutations', () => {

View File

@ -3,7 +3,7 @@ var sinon = require('sinon')
import moxios from 'moxios'
import store from '~/store/playlists'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/playlists', () => {
var sandbox

View File

@ -3,7 +3,7 @@ import {expect} from 'chai'
import * as _ from 'lodash-es'
import store from '~/store/queue'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/queue', () => {
var sandbox

View File

@ -3,7 +3,7 @@ import {expect} from 'chai'
import moxios from 'moxios'
import store from '~/store/radios'
import { testAction } from '../../utils'
import { testAction } from '~/utils'
describe('store/radios', () => {
var sandbox

View File

@ -2909,11 +2909,14 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
<<<<<<< HEAD
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
=======
>>>>>>> 25ad51a8 (Remove vue 2 filters)
deasync@^0.1.15:
version "0.1.28"
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.28.tgz#9b447b79b3f822432f0ab6a8614c0062808b5ad2"
@ -4068,11 +4071,6 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
he@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
howler@2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da"
@ -6764,14 +6762,6 @@ vue-router@4.0.14:
dependencies:
"@vue/devtools-api" "^6.0.0"
vue-template-compiler@2.6.14:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
vue-template-es2015-compiler@^1.6.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"