fix(front): [WIP] use generated types to make the CI (`lint:tsc`) happy
This commit is contained in:
parent
2b3831d4d3
commit
a5098c0952
|
@ -133,5 +133,6 @@
|
|||
"workbox-precaching": "6.5.4",
|
||||
"workbox-routing": "6.5.4",
|
||||
"workbox-strategies": "6.5.4"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import { useStore } from '~/store'
|
|||
import { get } from 'lodash-es'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { components } from '~/generated/types.ts'
|
||||
|
||||
import useMarkdown from '~/composables/useMarkdown'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
|
@ -40,24 +38,14 @@ const federationEnabled = computed(() => {
|
|||
|
||||
const onDesktop = computed(() => window.innerWidth > 800)
|
||||
|
||||
const stats = computed(() => {
|
||||
const info = nodeinfo.value ?? {} as components['schemas']['NodeInfo20']
|
||||
|
||||
const data = {
|
||||
users: get(info, 'usage.users.activeMonth', null),
|
||||
hours: get(info, 'metadata.content.local.hoursOfContent', null),
|
||||
artists: get(info, 'metadata.content.local.artists.total', null),
|
||||
albums: get(info, 'metadata.content.local.albums.total', null),
|
||||
tracks: get(info, 'metadata.content.local.tracks.total', null),
|
||||
listenings: get(info, 'metadata.usage.listenings.total', null)
|
||||
}
|
||||
|
||||
if (data.users === null || data.artists === null) {
|
||||
return data
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
const stats = computed(() => ({
|
||||
users: nodeinfo.value?.usage.users.activeMonth,
|
||||
hours: nodeinfo.value?.metadata.content.local.hoursOfContent,
|
||||
artists: nodeinfo.value?.metadata.content.local.artists,
|
||||
albums: nodeinfo.value?.metadata.content.local.releases, // TODO: Check where to get 'metadata.content.local.albums.total'
|
||||
tracks: nodeinfo.value?.metadata.content.local.recordings, // TODO: 'metadata.content.local.tracks.total'
|
||||
listenings: nodeinfo.value?.metadata.usage?.listenings.total
|
||||
}))
|
||||
|
||||
const headerStyle = computed(() => {
|
||||
if (!banner.value) {
|
||||
|
@ -341,7 +329,7 @@ const headerStyle = computed(() => {
|
|||
class="statistics-statistic"
|
||||
>
|
||||
<span class="statistics-figure ui text">
|
||||
<span class="ui big text"><strong>{{ stats.hours.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<span class="ui big text"><strong>{{ stats.hours?.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
{{ t('components.AboutPod.stat.hoursOfMusic', stats.hours) }}
|
||||
</span>
|
||||
|
@ -351,7 +339,7 @@ const headerStyle = computed(() => {
|
|||
class="statistics-statistic"
|
||||
>
|
||||
<span class="statistics-figure ui text">
|
||||
<span class="ui big text"><strong>{{ stats.artists.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<span class="ui big text"><strong>{{ stats.artists?.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
{{ t('components.AboutPod.stat.artistsCount', stats.artists) }}
|
||||
</span>
|
||||
|
@ -361,7 +349,7 @@ const headerStyle = computed(() => {
|
|||
class="statistics-statistic"
|
||||
>
|
||||
<span class="statistics-figure ui text">
|
||||
<span class="ui big text"><strong>{{ stats.albums.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<span class="ui big text"><strong>{{ stats.albums?.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
{{ t('components.AboutPod.stat.albumsCount', stats.albums) }}
|
||||
</span>
|
||||
|
@ -371,7 +359,7 @@ const headerStyle = computed(() => {
|
|||
class="statistics-statistic"
|
||||
>
|
||||
<span class="statistics-figure ui text">
|
||||
<span class="ui big text"><strong>{{ stats.tracks.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<span class="ui big text"><strong>{{ stats.tracks?.toLocaleString(store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
{{ t('components.AboutPod.stat.tracksCount', stats.tracks) }}
|
||||
</span>
|
||||
|
|
|
@ -107,6 +107,8 @@ const save = async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<!-- TODO: type the different values in `settings` (use generics) -->
|
||||
<!-- eslint-disable vue/valid-v-model -->
|
||||
<Section
|
||||
align-left
|
||||
:h2="group.label"
|
||||
|
@ -135,33 +137,30 @@ const save = async () => {
|
|||
v-bind="setting.fieldParams"
|
||||
v-model="values[setting.identifier]"
|
||||
/>
|
||||
<!-- eslint-disable vue/valid-v-model -->
|
||||
<signup-form-builder
|
||||
v-else-if="setting.fieldType === 'formBuilder'"
|
||||
v-model="values[setting.identifier] as Form"
|
||||
:signup-approval-enabled="!!values.moderation__signup_approval_enabled"
|
||||
/>
|
||||
<!-- eslint-enable vue/valid-v-model -->
|
||||
<Input
|
||||
v-else-if="setting.field.widget.class === 'PasswordInput'"
|
||||
v-model="values[setting.identifier]"
|
||||
v-model="values[setting.identifier] as string"
|
||||
password
|
||||
type="password"
|
||||
class="ui input"
|
||||
/>
|
||||
<Input
|
||||
v-else-if="setting.field.widget.class === 'TextInput'"
|
||||
v-model="values[setting.identifier]"
|
||||
v-model="values[setting.identifier] as string"
|
||||
type="text"
|
||||
class="ui input"
|
||||
/>
|
||||
<Input
|
||||
v-else-if="setting.field.class === 'IntegerField'"
|
||||
v-model.number="values[setting.identifier]"
|
||||
v-model.number="values[setting.identifier] as number"
|
||||
type="number"
|
||||
class="ui input"
|
||||
/>
|
||||
<!-- eslint-disable vue/valid-v-model -->
|
||||
<textarea
|
||||
v-else-if="setting.field.widget.class === 'Textarea'"
|
||||
v-model="values[setting.identifier] as string"
|
||||
|
@ -172,13 +171,11 @@ const save = async () => {
|
|||
<div
|
||||
v-else-if="setting.field.widget.class === 'CheckboxInput'"
|
||||
>
|
||||
<!-- eslint-disable vue/valid-v-model -->
|
||||
<Toggle
|
||||
v-model="values[setting.identifier] as boolean"
|
||||
big
|
||||
:label="setting.verbose_name"
|
||||
/>
|
||||
<!-- eslint-enable vue/valid-v-model -->
|
||||
<Spacer :size="8" />
|
||||
<p v-if="setting.help_text">
|
||||
{{ setting.help_text }}
|
||||
|
@ -215,11 +212,15 @@ const save = async () => {
|
|||
</option>
|
||||
</select>
|
||||
<div v-else-if="setting.field.widget.class === 'ImageWidget'">
|
||||
<!-- TODO: Implement image input -->
|
||||
|
||||
<!-- @vue-ignore -->
|
||||
<Input
|
||||
:id="setting.identifier"
|
||||
:ref="setFileRef(setting.identifier)"
|
||||
type="file"
|
||||
/>
|
||||
|
||||
<div v-if="values[setting.identifier]">
|
||||
<h3 class="ui header">
|
||||
{{ t('components.admin.SettingsGroup.header.image') }}
|
||||
|
@ -271,6 +272,7 @@ const save = async () => {
|
|||
</Section>
|
||||
<hr :class="$style.separator">
|
||||
<Spacer size-64 />
|
||||
<!-- eslint-enable vue/valid-v-model -->
|
||||
</template>
|
||||
|
||||
<style module>
|
||||
|
|
|
@ -13,8 +13,6 @@ import Spacer from '~/components/ui/Spacer.vue'
|
|||
|
||||
import { type Album } from '~/types'
|
||||
|
||||
const play = defineEmit<[album: Album]>()
|
||||
|
||||
interface Props {
|
||||
album: Album;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { components, operations } from '~/generated/types.ts'
|
||||
import type { components } from '~/generated/types.ts'
|
||||
|
||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||
import Card from '~/components/ui/Card.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
|
||||
import type { Artist, Cover, Track, Album } from '~/types'
|
||||
import type { Artist, Album } from '~/types'
|
||||
|
||||
const albums = ref([] as Album[])
|
||||
const tracks = ref([] as Track[])
|
||||
|
||||
interface Props {
|
||||
artist: Artist | components['schemas']['ArtistWithAlbums'];
|
||||
|
|
|
@ -79,7 +79,7 @@ watch(
|
|||
<template>
|
||||
<Section
|
||||
align-left
|
||||
columns-per-item="1"
|
||||
:columns-per-item="1"
|
||||
:h2="title"
|
||||
>
|
||||
<Loader
|
||||
|
|
|
@ -14,6 +14,9 @@ interface Props {
|
|||
const props = defineProps<Props>()
|
||||
|
||||
// TODO: Fix getRoute
|
||||
|
||||
// TODO: check if still needed:
|
||||
/*
|
||||
const getRoute = (ac: ArtistCredit) => {
|
||||
return {
|
||||
name: ac.artist.channel ? 'channels.detail' : 'library.artists.detail',
|
||||
|
@ -22,6 +25,7 @@ const getRoute = (ac: ArtistCredit) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -13,7 +13,7 @@ interface Props {
|
|||
const props = defineProps<Props>()
|
||||
|
||||
const route = computed(() => props.artist.channel
|
||||
? { name: 'channels.detail', params: { id: props.artist.channel.uuid } }
|
||||
? { name: 'channels.detail', params: { id: props.artist.channel } }
|
||||
: { name: 'library.artists.detail', params: { id: props.artist.id } }
|
||||
)
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,6 @@ import { momentFormat } from '~/utils/filters'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
|
@ -20,7 +19,6 @@ interface Props {
|
|||
|
||||
const props = defineProps<Props>()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const imageUrl = computed(() => props.object.artist?.cover
|
||||
? store.getters['instance/absoluteUrl'](props.object.artist.cover.urls.medium_square_crop)
|
||||
|
|
|
@ -79,6 +79,8 @@ interface MetadataChoices {
|
|||
const metadataChoices = ref({ itunes_category: null } as MetadataChoices)
|
||||
const itunesSubcategories = computed(() => {
|
||||
for (const element of metadataChoices.value.itunes_category ?? []) {
|
||||
// TODO: Backend: Define schema for `metadata` field
|
||||
// @ts-expect-error No types defined by backend schema for `metadata` field
|
||||
if (element.value === newValues.metadata.itunes_category) {
|
||||
return element.children ?? []
|
||||
}
|
||||
|
@ -94,6 +96,7 @@ const labels = computed(() => ({
|
|||
|
||||
const submittable = computed(() => !!(
|
||||
newValues.content_category === 'podcast'
|
||||
// @ts-expect-error No types defined by backend schema for `metadata` field
|
||||
? newValues.name && newValues.username && newValues.metadata.itunes_category && newValues.metadata.language
|
||||
: newValues.name && newValues.username
|
||||
))
|
||||
|
@ -104,13 +107,16 @@ watch(() => newValues.name, (name) => {
|
|||
}
|
||||
})
|
||||
|
||||
// @ts-expect-error No types defined by backend schema for `metadata` field
|
||||
watch(() => newValues.metadata.itunes_category, () => {
|
||||
// @ts-expect-error No types defined by backend schema for `metadata` field
|
||||
newValues.metadata.itunes_subcategory = null
|
||||
})
|
||||
|
||||
const isLoading = ref(false)
|
||||
const errors = ref([] as string[])
|
||||
|
||||
// @ts-expect-error Re-check emits
|
||||
watchEffect(() => emit('category', newValues.content_category))
|
||||
watchEffect(() => emit('loading', isLoading.value))
|
||||
watchEffect(() => emit('submittable', submittable.value))
|
||||
|
@ -265,6 +271,8 @@ defineExpose({
|
|||
<label for="channel-language">
|
||||
{{ t('components.audio.ChannelForm.label.language') }}
|
||||
</label>
|
||||
|
||||
<!-- @vue-ignore -->
|
||||
<select
|
||||
id="channel-language"
|
||||
v-model="newValues.metadata.language"
|
||||
|
@ -295,6 +303,8 @@ defineExpose({
|
|||
<label for="channel-itunes-category">
|
||||
{{ t('components.audio.ChannelForm.label.category') }}
|
||||
</label>
|
||||
|
||||
<!-- @vue-ignore -->
|
||||
<select
|
||||
id="itunes-category"
|
||||
v-model="newValues.metadata.itunes_category"
|
||||
|
@ -315,6 +325,8 @@ defineExpose({
|
|||
<label for="channel-itunes-category">
|
||||
{{ t('components.audio.ChannelForm.label.subcategory') }}
|
||||
</label>
|
||||
|
||||
<!-- @vue-ignore -->
|
||||
<select
|
||||
id="itunes-category"
|
||||
v-model="newValues.metadata.itunes_subcategory"
|
||||
|
@ -342,6 +354,7 @@ defineExpose({
|
|||
</span>
|
||||
</Alert>
|
||||
<div class="ui field">
|
||||
<!-- @vue-ignore -->
|
||||
<Input
|
||||
id="channel-itunes-email"
|
||||
v-model="newValues.metadata.owner_email"
|
||||
|
@ -351,6 +364,7 @@ defineExpose({
|
|||
/>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<!-- @vue-ignore -->
|
||||
<Input
|
||||
id="channel-itunes-name"
|
||||
v-model="newValues.metadata.owner_name"
|
||||
|
|
|
@ -18,7 +18,10 @@ interface Props {
|
|||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const width = ref(null)
|
||||
|
||||
// TODO: This used to be `null`. Is `0` correct?
|
||||
const width = ref(0)
|
||||
|
||||
const height = ref(150)
|
||||
const minHeight = ref(100)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { components } from '~/generated/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
|
@ -29,12 +30,12 @@ interface Props extends PlayOptionsProps {
|
|||
isPlayable?: boolean
|
||||
tracks?: Track[]
|
||||
track?: Track | null
|
||||
artist?: Artist | null
|
||||
artist?: Artist | components["schemas"]["SimpleChannelArtist"] | components['schemas']['ArtistWithAlbums'] | null
|
||||
album?: Album | null
|
||||
playlist?: Playlist | null
|
||||
library?: Library | null
|
||||
channel?: Channel | null
|
||||
account?: Actor | null
|
||||
account?: Actor | components['schemas']['APIActor'] | null
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
@ -201,7 +202,7 @@ const isOpen = ref(false)
|
|||
</span>
|
||||
</PopoverItem>
|
||||
|
||||
<hr v-if="filterableArtist || Object.keys(getReportableObjects({track, album, artist, playlist, account, channel})).length > 0">
|
||||
<hr v-if="filterableArtist || Object.keys(getReportableObjects({ track, album, artist, playlist, account, channel })).length > 0">
|
||||
|
||||
<PopoverItem
|
||||
v-if="filterableArtist"
|
||||
|
@ -214,7 +215,7 @@ const isOpen = ref(false)
|
|||
</PopoverItem>
|
||||
|
||||
<PopoverItem
|
||||
v-for="obj in getReportableObjects({track, album, artist, playlist, account, channel})"
|
||||
v-for="obj in getReportableObjects({ track, album, artist, playlist, account, channel })"
|
||||
:key="obj.target.type + obj.target.id"
|
||||
icon="bi-exclamation-triangle-fill"
|
||||
@click.stop.prevent="report(obj)"
|
||||
|
|
|
@ -119,17 +119,18 @@ const loopingTitle = computed(() => {
|
|||
: t('components.audio.Player.label.loopingWholeQueue')
|
||||
})
|
||||
|
||||
const hideArtist = () => {
|
||||
if (currentTrack.value.artistId !== -1 && currentTrack.value.artistCredit) {
|
||||
return store.dispatch('moderation/hide', {
|
||||
type: 'artist',
|
||||
target: {
|
||||
id: currentTrack.value.artistCredit[0].artist.id,
|
||||
name: currentTrack.value.artistCredit[0].artist.name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// TODO: check if still useful for filtering
|
||||
// const hideArtist = () => {
|
||||
// if (currentTrack.value.artistId !== -1 && currentTrack.value.artistCredit) {
|
||||
// return store.dispatch('moderation/hide', {
|
||||
// type: 'artist',
|
||||
// target: {
|
||||
// id: currentTrack.value.artistCredit[0].artist.id,
|
||||
// name: currentTrack.value.artistCredit[0].artist.name
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -7,7 +7,8 @@ import { useQueue } from '~/composables/audio/queue'
|
|||
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const { playPrevious, hasNext, playNext, currentTrack } = useQueue()
|
||||
// TODO: Check if we want to use `currentTrack` from useQueue() in order to disable some icon. Or not.
|
||||
const { playPrevious, hasNext, playNext } = useQueue()
|
||||
const { isPlaying } = usePlayer()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
|
|
@ -1,49 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { Artist, Track, Album, Tag } from '~/types'
|
||||
import type { RouteRecordName, RouteLocationNamedRaw } from 'vue-router'
|
||||
|
||||
import jQuery from 'jquery'
|
||||
import { trim } from 'lodash-es'
|
||||
import { useFocus, useCurrentElement } from '@vueuse/core'
|
||||
import { useFocus } from '@vueuse/core'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
import { generateTrackCreditString } from '~/utils/utils'
|
||||
|
||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
|
||||
|
||||
interface Events {
|
||||
(e: 'search'): void
|
||||
}
|
||||
|
||||
type CategoryCode = 'federation' | 'podcasts' | 'artists' | 'albums' | 'tracks' | 'tags' | 'more'
|
||||
interface Category {
|
||||
code: CategoryCode,
|
||||
name: string,
|
||||
route: RouteRecordName
|
||||
getId: (obj: unknown) => number
|
||||
getTitle: (obj: unknown) => string
|
||||
getDescription: (obj: unknown) => string
|
||||
}
|
||||
|
||||
type SimpleCategory = Partial<Category> & Pick<Category, 'code' | 'name'>
|
||||
const isCategoryGuard = (object: Category | SimpleCategory): object is Category => typeof object.route === 'string'
|
||||
|
||||
interface Results {
|
||||
name: string,
|
||||
results: Result[]
|
||||
}
|
||||
|
||||
interface Result {
|
||||
title: string
|
||||
id?: number
|
||||
description?: string
|
||||
routerUrl: RouteLocationNamedRaw
|
||||
}
|
||||
|
||||
const emit = defineEmits<Events>()
|
||||
|
||||
const search = ref()
|
||||
const { focused } = useFocus(search)
|
||||
onKeyboardShortcut(['shift', 'f'], () => (focused.value = true), true)
|
||||
|
@ -60,8 +23,6 @@ const labels = computed(() => ({
|
|||
}))
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const el = useCurrentElement()
|
||||
const query = ref()
|
||||
|
||||
const enter = () => {
|
||||
|
@ -76,66 +37,6 @@ const blur = () => {
|
|||
search.value.blur()
|
||||
}
|
||||
|
||||
const categories = computed(() => [
|
||||
{
|
||||
code: 'federation',
|
||||
name: t('components.audio.SearchBar.label.category.federation')
|
||||
},
|
||||
{
|
||||
code: 'podcasts',
|
||||
name: t('components.audio.SearchBar.label.category.podcasts')
|
||||
},
|
||||
{
|
||||
code: 'artists',
|
||||
route: 'library.artists.detail',
|
||||
name: labels.value.artist,
|
||||
getId: (obj: Artist) => obj.id,
|
||||
getTitle: (obj: Artist) => obj.name,
|
||||
getDescription: () => ''
|
||||
},
|
||||
{
|
||||
code: 'albums',
|
||||
route: 'library.albums.detail',
|
||||
name: labels.value.album,
|
||||
getId: (obj: Album) => obj.id,
|
||||
getTitle: (obj: Album) => obj.title,
|
||||
getDescription: (obj: Album) => generateTrackCreditString(obj)
|
||||
},
|
||||
{
|
||||
code: 'tracks',
|
||||
route: 'library.tracks.detail',
|
||||
name: labels.value.track,
|
||||
getId: (obj: Track) => obj.id,
|
||||
getTitle: (obj: Track) => obj.title,
|
||||
getDescription: (track: Track) => {
|
||||
const album = track.album ?? null
|
||||
return generateTrackCreditString(album) ?? generateTrackCreditString(track) ?? ''
|
||||
}
|
||||
},
|
||||
{
|
||||
code: 'tags',
|
||||
route: 'library.tags.detail',
|
||||
name: labels.value.tag,
|
||||
getId: (obj: Tag) => obj.name,
|
||||
getTitle: (obj: Tag) => `#${obj.name}`,
|
||||
getDescription: (obj: Tag) => ''
|
||||
},
|
||||
{
|
||||
code: 'more',
|
||||
name: ''
|
||||
}
|
||||
] as (Category | SimpleCategory)[])
|
||||
|
||||
const objectId = computed(() => {
|
||||
const trimmedQuery = trim(trim(query.value), '@')
|
||||
|
||||
if (trimmedQuery.startsWith('http://') || trimmedQuery.startsWith('https://') || trimmedQuery.includes('@')) {
|
||||
return query.value
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: Find out what jQuery version supports `search`
|
||||
// jQuery(el.value).search({
|
||||
|
|
|
@ -18,7 +18,7 @@ const { t } = useI18n()
|
|||
const props = defineProps<Props>()
|
||||
|
||||
const cover = computed(() => !props.artist.cover?.urls.original
|
||||
? props.artist.albums.find(album => !!album.cover?.urls.original)?.cover
|
||||
? undefined // TODO: Also check Albums. Like in props.artist.albums.find(album => !!album.cover?.urls.original)?.cover
|
||||
: props.artist.cover
|
||||
)
|
||||
|
||||
|
@ -41,7 +41,7 @@ const imageUrl = computed(() => cover.value?.urls.original
|
|||
>
|
||||
<play-button
|
||||
:icon-only="true"
|
||||
:is-playable="artist.is_playable"
|
||||
:is-playable="true /* TODO: check if artist.is_playable exists instead */"
|
||||
:button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']"
|
||||
:artist="artist"
|
||||
/>
|
||||
|
@ -67,15 +67,15 @@ const imageUrl = computed(() => cover.value?.urls.original
|
|||
</div>
|
||||
<div class="extra content">
|
||||
<span v-if="artist.content_category === 'music'">
|
||||
{{ t('components.audio.artist.Card.meta.tracks', artist.tracks_count) }}
|
||||
{{ t('components.audio.artist.Card.meta.tracks', (0 /* TODO: check where artist.tracks_count exists */)) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('components.audio.artist.Card.meta.episodes', artist.tracks_count) }}
|
||||
{{ t('components.audio.artist.Card.meta.episodes', (0 /* TODO: check where artist.tracks_count exists */)) }}
|
||||
</span>
|
||||
<play-button
|
||||
class="right floated basic icon"
|
||||
:dropdown-only="true"
|
||||
:is-playable="artist.is_playable"
|
||||
:is-playable="true /* TODO: check if is_playable can be derived from the data */"
|
||||
:dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']"
|
||||
:artist="artist"
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { useStore } from '~/store'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, Album, Playlist, Library, Channel, Actor, Cover, ArtistCredit } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
import { getArtistCoverUrl } from '~/utils/utils'
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
|
@ -13,7 +13,7 @@ import usePlayOptions from '~/composables/audio/usePlayOptions'
|
|||
|
||||
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||
import TrackModal from '~/components/audio/track/Modal.vue'
|
||||
import { generateTrackCreditString, getArtistCoverUrl } from '~/utils/utils'
|
||||
import { generateTrackCreditString } from '~/utils/utils'
|
||||
|
||||
interface Props extends PlayOptionsProps {
|
||||
track: Track
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||
import useReport from '~/composables/moderation/useReport'
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
import type { Track } from '~/types'
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { clone, uniqBy, sortedUniq } from 'lodash-es'
|
||||
import { clone, uniqBy } from 'lodash-es'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -91,7 +90,6 @@ const allTracks = computed(() => {
|
|||
const paginateResults = computed(() => props.paginateResults && allTracks.value.length < props.paginateBy)
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: t('components.audio.track.Table.table.header.title'),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { BackendError } from '~/types'
|
||||
import { onBeforeRouteLeave, type RouteLocationRaw, useRouter } from 'vue-router'
|
||||
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
|
|
|
@ -13,7 +13,6 @@ import Alert from '~/components/ui/Alert.vue'
|
|||
import Input from '~/components/ui/Input.vue'
|
||||
import Textarea from '~/components/ui/Textarea.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
|
||||
import useLogger from '~/composables/useLogger'
|
||||
|
|
|
@ -33,7 +33,7 @@ const fetchAlbums = async () => {
|
|||
|
||||
watch(() => model.value.channel, fetchAlbums, { immediate: true })
|
||||
watch(albums, (value) => {
|
||||
if (value.length === 1) { selectedAlbumId.value = albums.value[0].id }
|
||||
if (value.length === 1) { model.value.albumId = albums.value[0].id }
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
import type { BackendError, Channel, Upload, Track, Album } from '~/types'
|
||||
import type { VueUploadItem } from 'vue-upload-component'
|
||||
|
||||
import { computed, ref, reactive, watchEffect, watch, onMounted } from 'vue'
|
||||
import { computed, ref, reactive, watchEffect, watch } from 'vue'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { humanSize } from '~/utils/filters'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
import { useModal } from '~/ui/composables/useModal.ts'
|
||||
|
||||
import axios from 'axios'
|
||||
import { type paths, type operations, type components } from '~/generated/types.ts'
|
||||
|
@ -16,14 +15,12 @@ import UploadMetadataForm from '~/components/channels/UploadMetadataForm.vue'
|
|||
import FileUploadWidget from '~/components/library/FileUploadWidget.vue'
|
||||
import LicenseSelect from '~/components/channels/LicenseSelect.vue'
|
||||
import AlbumSelect from '~/components/channels/AlbumSelect.vue'
|
||||
import AlbumModal from '~/components/channels/AlbumModal.vue'
|
||||
|
||||
import useErrorHandler from '~/composables/useErrorHandler'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Loader from '~/components/ui/Loader.vue'
|
||||
import Spacer from '~/components/ui/Spacer.vue'
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
})
|
||||
|
||||
const newValues = reactive<Values>({
|
||||
position: 0,
|
||||
description: '',
|
||||
title: '',
|
||||
tags: [],
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import type { Actor } from '~/types'
|
||||
import type { components } from '~/generated/types'
|
||||
|
||||
import { toRefs } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { truncate } from '~/utils/filters'
|
||||
|
||||
import Link from '~/components/ui/Link.vue'
|
||||
import Pill from '~/components/ui/Pill.vue'
|
||||
|
||||
interface Props {
|
||||
actor: Actor
|
||||
actor: Actor | components['schemas']['APIActor']
|
||||
avatar?: boolean
|
||||
admin?: boolean
|
||||
displayName?: boolean
|
||||
|
@ -32,7 +32,7 @@ const repr = computed(() => {
|
|||
? actor.value.preferred_username
|
||||
: actor.value.full_username
|
||||
|
||||
return truncate(name, truncateLength.value)
|
||||
return truncate(name || '', truncateLength.value)
|
||||
})
|
||||
|
||||
const url = computed(() => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { components } from '~/generated/types'
|
||||
import type { ContentFilter } from '~/store/moderation'
|
||||
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
|
@ -15,12 +16,12 @@ export interface PlayOptionsProps {
|
|||
isPlayable?: boolean
|
||||
tracks?: Track[]
|
||||
track?: Track | null
|
||||
artist?: Artist | null
|
||||
artist?: Artist | components["schemas"]["SimpleChannelArtist"] | components['schemas']['ArtistWithAlbums'] | null
|
||||
album?: Album | null
|
||||
playlist?: Playlist | null
|
||||
library?: Library | null
|
||||
channel?: Channel | null
|
||||
account?: Actor | null
|
||||
account?: Actor | components['schemas']['APIActor'] | null
|
||||
}
|
||||
|
||||
export default (props: PlayOptionsProps) => {
|
||||
|
@ -36,8 +37,12 @@ export default (props: PlayOptionsProps) => {
|
|||
if (props.track) {
|
||||
return props.track.uploads?.length > 0
|
||||
} else if (props.artist) {
|
||||
// TODO: Find out how to get tracks, album from Artist
|
||||
|
||||
/*
|
||||
return props.artist.tracks_count > 0
|
||||
|| props.artist?.albums?.some((album) => album.is_playable === true)
|
||||
*/
|
||||
} else if (props.tracks) {
|
||||
return props.tracks?.some((track) => (track.uploads?.length ?? 0) > 0)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor, ArtistCredit } from '~/types'
|
||||
import type { components } from '~/generated/types'
|
||||
|
||||
import { i18n } from '~/init/locale'
|
||||
|
||||
|
@ -8,11 +9,11 @@ const { t } = i18n.global
|
|||
|
||||
interface Objects {
|
||||
track?: Track | null
|
||||
album?: Album | null
|
||||
artist?: Artist | null
|
||||
album?: Album | components['schemas']['TrackAlbum'] | null
|
||||
artist?: Artist | components['schemas']['ArtistWithAlbums'] | components["schemas"]["SimpleChannelArtist"] | null
|
||||
artistCredit?: ArtistCredit[] | null
|
||||
playlist?: Playlist | null
|
||||
account?: Actor | null
|
||||
account?: Actor | components['schemas']['APIActor'] | null
|
||||
library?: Library | null
|
||||
channel?: Channel | null
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue