parent
12b09b085a
commit
b7d66232f6
|
@ -82,7 +82,6 @@
|
||||||
"vite": "2.8.6",
|
"vite": "2.8.6",
|
||||||
"vite-plugin-pwa": "0.12.0",
|
"vite-plugin-pwa": "0.12.0",
|
||||||
"vue-jest": "3.0.7",
|
"vue-jest": "3.0.7",
|
||||||
"vue-template-compiler": "2.6.14",
|
|
||||||
"workbox-core": "6.5.3",
|
"workbox-core": "6.5.3",
|
||||||
"workbox-precaching": "6.5.3",
|
"workbox-precaching": "6.5.3",
|
||||||
"workbox-routing": "6.5.3",
|
"workbox-routing": "6.5.3",
|
||||||
|
|
|
@ -252,7 +252,7 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
import showdown from 'showdown'
|
import showdown from 'showdown'
|
||||||
import { humanSize } from '~/init/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
import SignupForm from '~/components/auth/SignupForm.vue'
|
import SignupForm from '~/components/auth/SignupForm.vue'
|
||||||
import LogoText from '~/components/LogoText.vue'
|
import LogoText from '~/components/LogoText.vue'
|
||||||
|
|
|
@ -278,7 +278,7 @@ We render some markdown to html here, the content is set by the admin so we shou
|
||||||
class="right aligned"
|
class="right aligned"
|
||||||
>
|
>
|
||||||
<span class="features-status ui text">
|
<span class="features-status ui text">
|
||||||
{{ defaultUploadQuota * 1000 * 1000 | humanSize }}
|
{{ humanSize(defaultUploadQuota * 1000 * 1000) }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<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 { mapState } from 'vuex'
|
||||||
import { get } from 'lodash-es'
|
import { get } from 'lodash-es'
|
||||||
import showdown from 'showdown'
|
import showdown from 'showdown'
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
// TODO (wvffle): Remove v-html
|
||||||
markdown: new showdown.Converter(),
|
markdown: new showdown.Converter(),
|
||||||
showAllowedDomains: false
|
showAllowedDomains: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,7 +332,7 @@ import AlbumWidget from '~/components/audio/album/Widget.vue'
|
||||||
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
|
||||||
import LoginForm from '~/components/auth/LoginForm.vue'
|
import LoginForm from '~/components/auth/LoginForm.vue'
|
||||||
import SignupForm from '~/components/auth/SignupForm.vue'
|
import SignupForm from '~/components/auth/SignupForm.vue'
|
||||||
import { humanSize } from '~/init/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -78,7 +78,7 @@
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
|
||||||
import { momentFormat } from '~/init/filters'
|
import { momentFormat } from '~/utils/filters'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="extra content">
|
<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
|
||||||
translate-context="*/*/*"
|
translate-context="*/*/*"
|
||||||
:translate-params="{count: album.tracks_count}"
|
:translate-params="{count: album.tracks_count}"
|
||||||
|
@ -59,6 +59,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
|
import { momentFormat} from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -67,6 +68,9 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
album: { type: Object, required: true }
|
album: { type: Object, required: true }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { momentFormat }
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
imageUrl () {
|
imageUrl () {
|
||||||
if (this.album.cover && this.album.cover.urls.original) {
|
if (this.album.cover && this.album.cover.urls.original) {
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
:to="{name: 'library.artists.detail', params: {id: artist.id}}"
|
:to="{name: 'library.artists.detail', params: {id: artist.id}}"
|
||||||
>
|
>
|
||||||
{{ artist.name|truncate(30) }}
|
{{ truncate(artist.name, 30) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@
|
||||||
<script>
|
<script>
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import { truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -74,6 +75,9 @@ export default {
|
||||||
TagsList
|
TagsList
|
||||||
},
|
},
|
||||||
props: { artist: { type: Object, required: true } },
|
props: { artist: { type: Object, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
initialAlbums: 30,
|
initialAlbums: 30,
|
||||||
|
|
|
@ -158,12 +158,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { clone } from 'lodash-es'
|
import { clone, uniqBy } from 'lodash-es'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import TrackRow from '~/components/audio/track/Row.vue'
|
import TrackRow from '~/components/audio/track/Row.vue'
|
||||||
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
|
import TrackMobileRow from '~/components/audio/track/MobileRow.vue'
|
||||||
import Pagination from '~/components/Pagination.vue'
|
import Pagination from '~/components/Pagination.vue'
|
||||||
import { unique } from '~/init/filters'
|
import { unique } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -205,7 +205,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
allTracks () {
|
allTracks () {
|
||||||
const tracks = (this.tracks || []).concat(this.additionalTracks)
|
const tracks = (this.tracks || []).concat(this.additionalTracks)
|
||||||
return unique(tracks, 'id')
|
return uniqBy(tracks, 'id')
|
||||||
},
|
},
|
||||||
|
|
||||||
labels () {
|
labels () {
|
||||||
|
|
|
@ -137,7 +137,7 @@
|
||||||
</template>
|
</template>
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="file.response.uuid">
|
<template v-if="file.response.uuid">
|
||||||
{{ file.size | humanSize }}
|
{{ humanSize(file.size) }}
|
||||||
<template v-if="file.response.duration">
|
<template v-if="file.response.duration">
|
||||||
· <human-duration :duration="file.response.duration" />
|
· <human-duration :duration="file.response.duration" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -164,7 +164,7 @@
|
||||||
>
|
>
|
||||||
Pending
|
Pending
|
||||||
</translate>
|
</translate>
|
||||||
· {{ file.size | humanSize }}
|
· {{ humanSize(file.size) }}
|
||||||
· {{ parseInt(file.progress) }}%
|
· {{ parseInt(file.progress) }}%
|
||||||
</template>
|
</template>
|
||||||
· <a @click.stop.prevent="remove(file)">
|
· <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 AlbumSelect from '~/components/channels/AlbumSelect.vue'
|
||||||
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
|
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
|
||||||
import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
|
import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
function setIfEmpty (obj, k, v) {
|
function setIfEmpty (obj, k, v) {
|
||||||
if (obj[k] !== undefined) {
|
if (obj[k] !== undefined) {
|
||||||
|
@ -261,6 +262,9 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
channel: { type: Object, default: null, required: false }
|
channel: { type: Object, default: null, required: false }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
availableChannels: {
|
availableChannels: {
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
<translate translate-context="Content/Library/Paragraph">
|
<translate translate-context="Content/Library/Paragraph">
|
||||||
Remaining storage space:
|
Remaining storage space:
|
||||||
</translate>
|
</translate>
|
||||||
{{ (statusData.quotaStatus.remaining * 1000 * 1000) - statusData.uploadedSize | humanSize }}
|
{{ (statusData.quotaStatus.remaining * 1000 * 1000) - humanSize(statusData.uploadedSize) }}
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden clearing divider mobile-only" />
|
<div class="ui hidden clearing divider mobile-only" />
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from '~/components/semantic/Modal.vue'
|
import Modal from '~/components/semantic/Modal.vue'
|
||||||
import ChannelUploadForm from '~/components/channels/UploadForm.vue'
|
import ChannelUploadForm from '~/components/channels/UploadForm.vue'
|
||||||
import { humanSize } from '~/init/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
import {onBeforeRouteLeave, onBeforeRouteUpdate} from 'vue-router'
|
import {onBeforeRouteLeave, onBeforeRouteUpdate} from 'vue-router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -159,6 +159,8 @@ export default {
|
||||||
|
|
||||||
onBeforeRouteUpdate(guard)
|
onBeforeRouteUpdate(guard)
|
||||||
onBeforeRouteLeave(guard)
|
onBeforeRouteLeave(guard)
|
||||||
|
|
||||||
|
return { humanSize }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<router-link
|
<router-link
|
||||||
:to="url"
|
:to="url"
|
||||||
:title="actor.full_username"
|
:title="actor.full_username"
|
||||||
>
|
>
|
||||||
<template v-if="avatar">
|
<actor-avatar
|
||||||
<actor-avatar :actor="actor" /><span> </span>
|
v-if="avatar"
|
||||||
</template><slot>{{ repr | truncate(truncateLength) }}</slot>
|
:actor="actor"
|
||||||
|
/>
|
||||||
|
<span> </span>
|
||||||
|
<slot>{{ repr }}</slot>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</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>
|
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<translate
|
<translate
|
||||||
v-if="durationData.hours > 0"
|
v-if="duration.hours > 0"
|
||||||
translate-context="Content/*/Paragraph"
|
translate-context="Content/*/Paragraph"
|
||||||
:translate-params="{minutes: durationData.minutes, hours: durationData.hours}"
|
:translate-params="duration"
|
||||||
>%{ hours } h %{ minutes } min</translate>
|
>%{ hours } h %{ minutes } min</translate>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
translate-context="Content/*/Paragraph"
|
translate-context="Content/*/Paragraph"
|
||||||
:translate-params="{minutes: durationData.minutes}"
|
:translate-params="duration"
|
||||||
>%{ minutes } min</translate>
|
>%{ minutes } min</translate>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
import { secondsToObject } from '~/init/filters'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: { seconds: { type: Number, default: null } },
|
|
||||||
computed: {
|
|
||||||
durationData () {
|
|
||||||
return secondsToObject(this.seconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<time
|
<time
|
||||||
:datetime="date"
|
:datetime="date"
|
||||||
:title="date | moment"
|
:title="momentFormat(date)"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
v-if="icon"
|
v-if="props.icon"
|
||||||
class="outline clock icon"
|
class="outline clock icon"
|
||||||
/>
|
/>
|
||||||
{{ realDate | ago($store.state.ui.momentLocale) }}
|
{{ realDate }}
|
||||||
</time>
|
</time>
|
||||||
</template>
|
</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>
|
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<time :datetime="`${duration}s`">
|
<time :datetime="`${duration}s`">
|
||||||
{{ duration | duration }}
|
{{ parsedDuration }}
|
||||||
</time>
|
</time>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
duration: { type: Number, required: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
v-if="object.release_date || (totalTracks > 0)"
|
v-if="object.release_date || (totalTracks > 0)"
|
||||||
class="ui small hidden divider"
|
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">
|
<template v-if="totalTracks > 0">
|
||||||
<translate
|
<translate
|
||||||
v-if="isSerie"
|
v-if="isSerie"
|
||||||
|
@ -254,6 +254,7 @@ import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
import ArtistLabel from '~/components/audio/ArtistLabel.vue'
|
import ArtistLabel from '~/components/audio/ArtistLabel.vue'
|
||||||
import AlbumDropdown from './AlbumDropdown.vue'
|
import AlbumDropdown from './AlbumDropdown.vue'
|
||||||
|
import { momentFormat} from '~/utils/filters'
|
||||||
|
|
||||||
function groupByDisc (initial) {
|
function groupByDisc (initial) {
|
||||||
function inner (acc, track) {
|
function inner (acc, track) {
|
||||||
|
@ -276,6 +277,9 @@ export default {
|
||||||
AlbumDropdown
|
AlbumDropdown
|
||||||
},
|
},
|
||||||
props: { id: { type: [String, Number], required: true } },
|
props: { id: { type: [String, Number], required: true } },
|
||||||
|
setup () {
|
||||||
|
return { momentFormat }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -61,13 +61,13 @@
|
||||||
</translate>
|
</translate>
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{{ remainingSpace * 1000 * 1000 | humanSize }}
|
{{ humanSize(remainingSpace * 1000 * 1000) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui divider" />
|
<div class="ui divider" />
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<translate translate-context="Content/Library/Title/Verb">
|
<translate translate-context="Content/Library/Title/Verb">
|
||||||
Upload music from your local storage
|
Upload music from '~/your local storage
|
||||||
</translate>
|
</translate>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="ui message">
|
<div class="ui message">
|
||||||
|
@ -174,9 +174,9 @@
|
||||||
:key="file.id"
|
:key="file.id"
|
||||||
>
|
>
|
||||||
<td :title="file.name">
|
<td :title="file.name">
|
||||||
{{ file.name | truncate(60) }}
|
{{ truncate(file.name, 60) }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ file.size | humanSize }}</td>
|
<td>{{ humanSize(file.size) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
v-if="file.error"
|
v-if="file.error"
|
||||||
|
@ -318,6 +318,7 @@ import FsBrowser from './FsBrowser.vue'
|
||||||
import FsLogs from './FsLogs.vue'
|
import FsLogs from './FsLogs.vue'
|
||||||
import LibraryFilesTable from '~/views/content/libraries/FilesTable.vue'
|
import LibraryFilesTable from '~/views/content/libraries/FilesTable.vue'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -330,6 +331,9 @@ export default {
|
||||||
library: { type: Object, required: true },
|
library: { type: Object, required: true },
|
||||||
defaultImportReference: { type: String, required: false, default: '' }
|
defaultImportReference: { type: String, required: false, default: '' }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
const importReference = this.defaultImportReference || moment().format()
|
const importReference = this.defaultImportReference || moment().format()
|
||||||
// Since $router.replace is pushing the same route, it raises NavigationDuplicated
|
// Since $router.replace is pushing the same route, it raises NavigationDuplicated
|
||||||
|
|
|
@ -228,7 +228,7 @@ import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
||||||
import Modal from '~/components/semantic/Modal.vue'
|
import Modal from '~/components/semantic/Modal.vue'
|
||||||
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
|
||||||
import ReportMixin from '~/components/mixins/Report.vue'
|
import ReportMixin from '~/components/mixins/Report.vue'
|
||||||
import { momentFormat } from '~/init/filters'
|
import { momentFormat } from '~/utils/filters'
|
||||||
import updateQueryString from '~/composables/updateQueryString'
|
import updateQueryString from '~/composables/updateQueryString'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
<template v-if="upload.duration">
|
<template v-if="upload.duration">
|
||||||
{{ upload.duration | duration }}
|
{{ time.parse(upload.duration) }}
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
<template v-if="upload.size">
|
<template v-if="upload.size">
|
||||||
{{ upload.size | humanSize }}
|
{{ humanSize(upload.size) }}
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
<template v-if="upload.bitrate">
|
<template v-if="upload.bitrate">
|
||||||
{{ upload.bitrate | humanSize }}/s
|
{{ humanSize(upload.bitrate) }}/s
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="right aligned">
|
<td class="right aligned">
|
||||||
<template v-if="track.album && track.album.release_date">
|
<template v-if="track.album && track.album.release_date">
|
||||||
{{ track.album.release_date | moment('Y') }}
|
{{ momentFormat(track.album.release_date, 'Y') }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<translate translate-context="*/*/*">
|
<translate translate-context="*/*/*">
|
||||||
|
@ -205,7 +205,7 @@
|
||||||
<span
|
<span
|
||||||
v-if="track.copyright"
|
v-if="track.copyright"
|
||||||
:title="track.copyright"
|
:title="track.copyright"
|
||||||
>{{ track.copyright|truncate(50) }}</span>
|
>{{ truncate(track.copyright, 50) }}</span>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<translate translate-context="*/*/*">
|
<translate translate-context="*/*/*">
|
||||||
N/A
|
N/A
|
||||||
|
@ -246,7 +246,7 @@
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
{{ track.fid|truncate(65) }}
|
{{ truncate(track.fid, 65) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -298,6 +298,8 @@ import axios from 'axios'
|
||||||
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
import LibraryWidget from '~/components/federation/LibraryWidget.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
import PlaylistWidget from '~/components/playlists/Widget.vue'
|
||||||
|
import { humanSize, momentFormat, truncate } from '~/utils/filters'
|
||||||
|
import time from '~/utils/time'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -309,6 +311,9 @@ export default {
|
||||||
track: { type: Object, required: true },
|
track: { type: Object, required: true },
|
||||||
libraries: { type: Array, default: null }
|
libraries: { type: Array, default: null }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { humanSize, momentFormat, time, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
id: this.track.id,
|
id: this.track.id,
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="{name: 'manage.library.tags.detail', params: {id: scope.obj.name }}">
|
<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>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -156,6 +156,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||||
|
import { truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -167,6 +168,9 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
filters: { type: Object, required: false, default: () => { return {} } }
|
filters: { type: Object, required: false, default: () => { return {} } }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
detailedUpload: {},
|
detailedUpload: {},
|
||||||
|
|
|
@ -196,7 +196,7 @@
|
||||||
:title="scope.obj.library.name"
|
:title="scope.obj.library.name"
|
||||||
@click.prevent="addSearchToken('library_id', scope.obj.library.id)"
|
@click.prevent="addSearchToken('library_id', scope.obj.library.id)"
|
||||||
>
|
>
|
||||||
{{ scope.obj.library.name | truncate(20) }}
|
{{ truncate(scope.obj.library.name, 20) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -258,7 +258,7 @@
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<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
|
<translate
|
||||||
v-else
|
v-else
|
||||||
translate-context="*/*/*"
|
translate-context="*/*/*"
|
||||||
|
@ -317,6 +317,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -328,6 +329,9 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
filters: { type: Object, required: false, default: function () { return {} } }
|
filters: { type: Object, required: false, default: function () { return {} } }
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
detailedUpload: {},
|
detailedUpload: {},
|
||||||
|
|
|
@ -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>
|
<template>
|
||||||
<div class="component-tags-list">
|
<div class="component-tags-list">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="tag in toDisplay"
|
v-for="tag in tags"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
:to="{name: detailRoute, params: {id: tag}}"
|
:to="{name: props.detailRoute, params: { id: tag } }"
|
||||||
:class="['ui', 'circular', 'hashtag', 'label', labelClasses]"
|
:class="['ui', 'circular', 'hashtag', 'label', props.labelClasses]"
|
||||||
>
|
>
|
||||||
#{{ tag|truncate(truncateSize) }}
|
#{{ truncate(tag, props.truncateSize) }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div
|
<div
|
||||||
v-if="showMore && toDisplay.length < tags.length"
|
v-if="props.showMore && tags.length < props.tags.length"
|
||||||
role="button"
|
role="button"
|
||||||
class="ui circular inverted accent label"
|
class="ui circular inverted accent label"
|
||||||
@click.prevent="honorLimit = false"
|
@click.prevent="honorLimit = false"
|
||||||
>
|
>
|
||||||
<translate
|
<translate
|
||||||
translate-context="Content/*/Button/Label/Verb"
|
translate-context="Content/*/Button/Label/Verb"
|
||||||
:translate-params="{count: tags.length - toDisplay.length}"
|
:translate-params="{ count: props.tags.length - tags.length }"
|
||||||
:translate-n="tags.length - toDisplay.length"
|
:translate-n="props.tags.length - tags.length"
|
||||||
translate-plural="Show %{ count } more tags"
|
translate-plural="Show %{ count } more tags"
|
||||||
>
|
>
|
||||||
Show 1 more tag
|
Show 1 more tag
|
||||||
|
@ -25,28 +61,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -70,3 +70,11 @@ export interface ListenWSEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WebSocketEvent = PendingReviewEditsWSEvent | PendingReviewReportsWSEvent | PendingReviewRequestsWSEvent | 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
|
||||||
|
}
|
||||||
|
|
|
@ -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]}`
|
||||||
|
}
|
|
@ -26,7 +26,7 @@
|
||||||
src="../../assets/audio/default-cover.png"
|
src="../../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.artist.name | truncate(100) }}
|
{{ truncate(object.artist.name) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.artist.is_local">
|
<template v-if="object.artist.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -354,7 +354,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -364,7 +364,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -418,6 +418,7 @@ import axios from 'axios'
|
||||||
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -425,6 +426,9 @@ export default {
|
||||||
TagsList
|
TagsList
|
||||||
},
|
},
|
||||||
props: { id: { type: String, required: true } },
|
props: { id: { type: String, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
src="../../../assets/audio/default-cover.png"
|
src="../../../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.title | truncate(100) }}
|
{{ truncate(object.title) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.is_local">
|
<template v-if="object.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -337,7 +337,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -347,7 +347,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -401,6 +401,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import { humanSize, truncate} from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -408,6 +409,9 @@ export default {
|
||||||
TagsList
|
TagsList
|
||||||
},
|
},
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
src="../../../assets/audio/default-cover.png"
|
src="../../../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.name | truncate(100) }}
|
{{ truncate(object.name) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.is_local">
|
<template v-if="object.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -336,7 +336,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -346,7 +346,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -413,6 +413,7 @@ import axios from 'axios'
|
||||||
|
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -420,6 +421,9 @@ export default {
|
||||||
TagsList
|
TagsList
|
||||||
},
|
},
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<i class="circular inverted book icon" />
|
<i class="circular inverted book icon" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.name | truncate(100) }}
|
{{ truncate(object.name) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.is_local">
|
<template v-if="object.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -285,7 +285,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -295,7 +295,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -360,6 +360,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
import { humanSize, truncate} from '~/utils/filters'
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
|
|
||||||
|
@ -368,6 +369,9 @@ export default {
|
||||||
TranslationsMixin
|
TranslationsMixin
|
||||||
],
|
],
|
||||||
props: { id: { type: String, required: true } },
|
props: { id: { type: String, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<i class="circular inverted hashtag icon" />
|
<i class="circular inverted hashtag icon" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.name | truncate(100) }}
|
{{ truncate(object.name) }}
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="header-buttons">
|
<div class="header-buttons">
|
||||||
|
@ -210,9 +210,13 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { truncate} from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
src="../../../assets/audio/default-cover.png"
|
src="../../../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ object.title | truncate(100) }}
|
{{ truncate(object.title) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.is_local">
|
<template v-if="object.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -402,7 +402,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -412,7 +412,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
@ -454,6 +454,7 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import FetchButton from '~/components/federation/FetchButton.vue'
|
import FetchButton from '~/components/federation/FetchButton.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -461,6 +462,9 @@ export default {
|
||||||
TagsList
|
TagsList
|
||||||
},
|
},
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<i class="circular inverted file icon" />
|
<i class="circular inverted file icon" />
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ displayName(object) | truncate(100) }}
|
{{ truncate(displayName(object)) }}
|
||||||
<div class="sub header">
|
<div class="sub header">
|
||||||
<template v-if="object.is_local">
|
<template v-if="object.is_local">
|
||||||
<span class="ui tiny accent label">
|
<span class="ui tiny accent label">
|
||||||
|
@ -289,7 +289,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="object.audio_file">
|
<template v-if="object.audio_file">
|
||||||
{{ object.size | humanSize }}
|
{{ humanSize(object.size) }}
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ object.size | humanSize }}
|
{{ humanSize(object.size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -317,7 +317,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="object.bitrate">
|
<template v-if="object.bitrate">
|
||||||
{{ object.bitrate | humanSize }}/s
|
{{ humanSize(object.bitrate) }}/s
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -335,7 +335,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="object.duration">
|
<template v-if="object.duration">
|
||||||
{{ object.duration | duration }}
|
{{ time.parse(object.duration) }}
|
||||||
</template>
|
</template>
|
||||||
<translate
|
<translate
|
||||||
v-else
|
v-else
|
||||||
|
@ -380,6 +380,7 @@ import axios from 'axios'
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||||
import time from '~/utils/time'
|
import time from '~/utils/time'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -389,9 +390,11 @@ export default {
|
||||||
TranslationsMixin
|
TranslationsMixin
|
||||||
],
|
],
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize, time, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
time,
|
|
||||||
detailedUpload: {},
|
detailedUpload: {},
|
||||||
showUploadDetailModal: false,
|
showUploadDetailModal: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -447,7 +447,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="object.user">
|
<tr v-if="object.user">
|
||||||
|
@ -486,7 +486,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -572,6 +572,7 @@ import $ from 'jquery'
|
||||||
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
||||||
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
import { humanSize} from '~/utils/filters'
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
|
|
||||||
|
@ -581,6 +582,9 @@ export default {
|
||||||
InstancePolicyCard
|
InstancePolicyCard
|
||||||
},
|
},
|
||||||
props: { id: { type: Number, required: true } },
|
props: { id: { type: Number, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -361,7 +361,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_downloaded_size | humanSize }}
|
{{ humanSize(stats.media_downloaded_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -371,7 +371,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ stats.media_total_size | humanSize }}
|
{{ humanSize(stats.media_total_size) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -462,6 +462,7 @@ import { get } from 'lodash-es'
|
||||||
|
|
||||||
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
|
||||||
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
|
||||||
|
import { humanSize} from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -469,6 +470,9 @@ export default {
|
||||||
InstancePolicyCard
|
InstancePolicyCard
|
||||||
},
|
},
|
||||||
props: { id: { type: String, required: true }, allowListEnabled: { type: Boolean, required: true } },
|
props: { id: { type: String, required: true }, allowListEnabled: { type: Boolean, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
get,
|
get,
|
||||||
|
|
|
@ -82,7 +82,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { humanSize } from '~/init/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
:data-tooltip="size_label"
|
:data-tooltip="size_label"
|
||||||
>
|
>
|
||||||
<i class="database icon" />
|
<i class="database icon" />
|
||||||
{{ library.size | humanSize }}
|
{{ humanSize(library.size) }}
|
||||||
</span>
|
</span>
|
||||||
<i class="music icon" />
|
<i class="music icon" />
|
||||||
<translate
|
<translate
|
||||||
|
@ -79,10 +79,14 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
|
import { humanSize} from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [TranslationsMixin],
|
mixins: [TranslationsMixin],
|
||||||
props: { library: { type: Object, required: true } },
|
props: { library: { type: Object, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
size_label () {
|
size_label () {
|
||||||
return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library')
|
return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library')
|
||||||
|
|
|
@ -181,7 +181,7 @@
|
||||||
<template v-if="scope.obj.track">
|
<template v-if="scope.obj.track">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="{name: 'library.tracks.detail', params: {id: scope.obj.track.id }}">
|
<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>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -189,7 +189,7 @@
|
||||||
href=""
|
href=""
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
@click.prevent="addSearchToken('artist', scope.obj.track.artist.name)"
|
@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>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
|
@ -197,12 +197,12 @@
|
||||||
href=""
|
href=""
|
||||||
class="discrete link"
|
class="discrete link"
|
||||||
@click.prevent="addSearchToken('album', scope.obj.track.album.title)"
|
@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>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<td :title="scope.obj.source">
|
<td :title="scope.obj.source">
|
||||||
{{ scope.obj.source | truncate(25) }}
|
{{ truncate(scope.obj.source, 25) }}
|
||||||
</td>
|
</td>
|
||||||
<td />
|
<td />
|
||||||
<td />
|
<td />
|
||||||
|
@ -227,7 +227,7 @@
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="scope.obj.duration">
|
<td v-if="scope.obj.duration">
|
||||||
{{ scope.obj.duration | duration }}
|
{{ time.parse(scope.obj.duration) }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
<translate translate-context="*/*/*">
|
<translate translate-context="*/*/*">
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
</td>
|
</td>
|
||||||
<td v-if="scope.obj.size">
|
<td v-if="scope.obj.size">
|
||||||
{{ scope.obj.size | humanSize }}
|
{{ humanSize(scope.obj.size) }}
|
||||||
</td>
|
</td>
|
||||||
<td v-else>
|
<td v-else>
|
||||||
<translate translate-context="*/*/*">
|
<translate translate-context="*/*/*">
|
||||||
|
@ -277,6 +277,7 @@ import OrderingMixin from '~/components/mixins/Ordering.vue'
|
||||||
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
import TranslationsMixin from '~/components/mixins/Translations.vue'
|
||||||
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
import SmartSearchMixin from '~/components/mixins/SmartSearch.vue'
|
||||||
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
|
||||||
|
import { humanSize, truncate } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -296,9 +297,11 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setup () {
|
||||||
|
return { humanSize, time, truncate }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
time,
|
|
||||||
detailedUpload: {},
|
detailedUpload: {},
|
||||||
showUploadDetailModal: false,
|
showUploadDetailModal: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
|
@ -194,7 +194,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { humanSize } from '~/init/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
import { compileTokens } from '~/search'
|
import { compileTokens } from '~/search'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
</translate>
|
</translate>
|
||||||
<span v-if="object.size">
|
<span v-if="object.size">
|
||||||
· <i class="database icon" />
|
· <i class="database icon" />
|
||||||
{{ object.size | humanSize }}
|
{{ humanSize(object.size) }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
@ -230,6 +230,7 @@ import axios from 'axios'
|
||||||
import LibraryFollowButton from '~/components/audio/LibraryFollowButton.vue'
|
import LibraryFollowButton from '~/components/audio/LibraryFollowButton.vue'
|
||||||
import ReportMixin from '~/components/mixins/Report.vue'
|
import ReportMixin from '~/components/mixins/Report.vue'
|
||||||
import RadioButton from '~/components/radios/Button.vue'
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
|
import { humanSize } from '~/utils/filters'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -242,6 +243,9 @@ export default {
|
||||||
next()
|
next()
|
||||||
},
|
},
|
||||||
props: { id: { type: String, required: true } },
|
props: { id: { type: String, required: true } },
|
||||||
|
setup () {
|
||||||
|
return { humanSize }
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {expect} from 'chai'
|
||||||
|
|
||||||
import Username from '~/components/common/Username.vue'
|
import Username from '~/components/common/Username.vue'
|
||||||
|
|
||||||
import { render } from '../../utils'
|
import { render } from '~/utils'
|
||||||
|
|
||||||
describe('Username', () => {
|
describe('Username', () => {
|
||||||
it('displays username', () => {
|
it('displays username', () => {
|
||||||
|
|
|
@ -1,59 +1,22 @@
|
||||||
import {expect} from 'chai'
|
import { expect } from 'chai'
|
||||||
import moment from 'moment'
|
import { truncate } from '~/filters'
|
||||||
import {truncate, ago, capitalize, year, unique} from '~/init/filters'
|
|
||||||
|
|
||||||
describe('filters', () => {
|
describe('filters', () => {
|
||||||
describe('truncate', () => {
|
describe('truncate', () => {
|
||||||
it('leave strings as it if correct size', () => {
|
it('leave strings as it if correct size', () => {
|
||||||
const input = 'Hello world'
|
const input = 'Hello world'
|
||||||
let output = truncate(input, 100)
|
const output = truncate(input, 100)
|
||||||
expect(output).to.equal(input)
|
expect(output).to.equal(input)
|
||||||
})
|
})
|
||||||
it('returns shorter string with character', () => {
|
it('returns shorter string with character', () => {
|
||||||
const input = 'Hello world'
|
const input = 'Hello world'
|
||||||
let output = truncate(input, 5)
|
const output = truncate(input, 5)
|
||||||
expect(output).to.equal('Hello…')
|
expect(output).to.equal('Hello…')
|
||||||
})
|
})
|
||||||
it('custom ellipsis', () => {
|
it('custom ellipsis', () => {
|
||||||
const input = 'Hello world'
|
const input = 'Hello world'
|
||||||
let output = truncate(input, 5, ' pouet')
|
const output = truncate(input, 5, ' pouet')
|
||||||
expect(output).to.equal('Hello 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}])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {expect} from 'chai'
|
||||||
import moxios from 'moxios'
|
import moxios from 'moxios'
|
||||||
import store from '~/store/auth'
|
import store from '~/store/auth'
|
||||||
|
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/auth', () => {
|
describe('store/auth', () => {
|
||||||
var sandbox
|
var sandbox
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {expect} from 'chai'
|
||||||
|
|
||||||
import store from '~/store/favorites'
|
import store from '~/store/favorites'
|
||||||
|
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/favorites', () => {
|
describe('store/favorites', () => {
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ var sinon = require('sinon')
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import moxios from 'moxios'
|
import moxios from 'moxios'
|
||||||
import store from '~/store/instance'
|
import store from '~/store/instance'
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/instance', () => {
|
describe('store/instance', () => {
|
||||||
var sandbox
|
var sandbox
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {expect} from 'chai'
|
||||||
|
|
||||||
import store from '~/store/player'
|
import store from '~/store/player'
|
||||||
|
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/player', () => {
|
describe('store/player', () => {
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ var sinon = require('sinon')
|
||||||
import moxios from 'moxios'
|
import moxios from 'moxios'
|
||||||
import store from '~/store/playlists'
|
import store from '~/store/playlists'
|
||||||
|
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/playlists', () => {
|
describe('store/playlists', () => {
|
||||||
var sandbox
|
var sandbox
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {expect} from 'chai'
|
||||||
import * as _ from 'lodash-es'
|
import * as _ from 'lodash-es'
|
||||||
|
|
||||||
import store from '~/store/queue'
|
import store from '~/store/queue'
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/queue', () => {
|
describe('store/queue', () => {
|
||||||
var sandbox
|
var sandbox
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {expect} from 'chai'
|
||||||
|
|
||||||
import moxios from 'moxios'
|
import moxios from 'moxios'
|
||||||
import store from '~/store/radios'
|
import store from '~/store/radios'
|
||||||
import { testAction } from '../../utils'
|
import { testAction } from '~/utils'
|
||||||
|
|
||||||
describe('store/radios', () => {
|
describe('store/radios', () => {
|
||||||
var sandbox
|
var sandbox
|
||||||
|
|
|
@ -2909,11 +2909,14 @@ data-urls@^2.0.0:
|
||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
de-indent@^1.0.2:
|
de-indent@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||||
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 25ad51a8 (Remove vue 2 filters)
|
||||||
deasync@^0.1.15:
|
deasync@^0.1.15:
|
||||||
version "0.1.28"
|
version "0.1.28"
|
||||||
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.28.tgz#9b447b79b3f822432f0ab6a8614c0062808b5ad2"
|
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.28.tgz#9b447b79b3f822432f0ab6a8614c0062808b5ad2"
|
||||||
|
@ -4068,11 +4071,6 @@ has@^1.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
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:
|
howler@2.2.3:
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da"
|
resolved "https://registry.yarnpkg.com/howler/-/howler-2.2.3.tgz#a2eff9b08b586798e7a2ee17a602a90df28715da"
|
||||||
|
@ -6764,14 +6762,6 @@ vue-router@4.0.14:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/devtools-api" "^6.0.0"
|
"@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:
|
vue-template-es2015-compiler@^1.6.0:
|
||||||
version "1.9.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||||
|
|
Loading…
Reference in New Issue