Resolve most type conflicts

This commit is contained in:
wvffle 2022-08-31 16:39:24 +00:00 committed by Georg Krause
parent e7da8b5f43
commit 436a76928f
50 changed files with 168 additions and 151 deletions

View File

@ -41,7 +41,7 @@ defineProps<Props>()
> >
<strong>{{ source.track.title }}</strong><br> <strong>{{ source.track.title }}</strong><br>
<span> <span>
{{ source.track.artist.name }} {{ source.track.artist?.name }}
</span> </span>
</button> </button>
</div> </div>

View File

@ -219,10 +219,10 @@ onMounted(() => {
<div class="item"> <div class="item">
<div class="ui user-dropdown dropdown"> <div class="ui user-dropdown dropdown">
<img <img
v-if="$store.state.auth.authenticated && $store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop" v-if="$store.state.auth.authenticated && $store.state.auth.profile?.avatar && $store.state.auth.profile?.avatar.urls.medium_square_crop"
class="ui avatar image" class="ui avatar image"
alt="" alt=""
:src="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)" :src="$store.getters['instance/absoluteUrl']($store.state.auth.profile?.avatar.urls.medium_square_crop)"
> >
<actor-avatar <actor-avatar
v-else-if="$store.state.auth.authenticated" v-else-if="$store.state.auth.authenticated"

View File

@ -6,7 +6,7 @@ import { useClipboard } from '@vueuse/core'
interface Props { interface Props {
type: string type: string
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -234,7 +234,7 @@ const updatePage = (page: number) => {
<track-row <track-row
v-for="(track, index) in allTracks" v-for="(track, index) in allTracks"
:key="track.id + track.position" :key="track.id + (track.position ?? 0)"
:data-track-id="track.id" :data-track-id="track.id"
:data-track-position="track.position" :data-track-position="track.position"
:track="track" :track="track"

View File

@ -42,16 +42,15 @@ const submit = async () => {
isLoading.value = true isLoading.value = true
try { try {
const event = props.app !== null const isUpdating = props.app !== null
? 'updated' const request = isUpdating
: 'created'
const request = props.app !== null
? () => axios.patch(`oauth/apps/${props.app?.client_id}/`, fields) ? () => axios.patch(`oauth/apps/${props.app?.client_id}/`, fields)
: () => axios.post('oauth/apps/', fields) : () => axios.post('oauth/apps/', fields)
const response = await request() const response = await request()
emit(event, response.data as Application)
if (isUpdating) emit('updated', response.data as Application)
else emit('created', response.data as Application)
} catch (error) { } catch (error) {
errors.value = (error as BackendError).backendErrors errors.value = (error as BackendError).backendErrors
} }

View File

@ -126,7 +126,7 @@ const submitAndScan = async () => {
</translate> </translate>
</div> </div>
</div> </div>
<template v-if="plugin.conf?.length > 0"> <template v-if="(plugin.conf?.length ?? 0) > 0">
<template <template
v-for="field in plugin.conf" v-for="field in plugin.conf"
:key="field.name" :key="field.name"

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BackendError, Application } from '~/types' import type { BackendError, Application, PrivacyLevel } from '~/types'
import type { $ElementType } from 'utility-types'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery' import $ from 'jquery'
@ -18,9 +19,9 @@ import PasswordInput from '~/components/forms/PasswordInput.vue'
const SETTINGS_ORDER: FieldId[] = ['summary', 'privacy_level'] const SETTINGS_ORDER: FieldId[] = ['summary', 'privacy_level']
type FieldId = 'summary' | 'privacy_level' type Field = { id: 'summary', type: 'content', value: { text: string, content_type: 'text/markdown' } }
type Field = { id: string, type: 'content', value: { text: string, content_type: 'text/markdown' } } | { id: 'privacy_level', type: 'dropdown', choices: PrivacyLevel[], value: string }
| { id: string, type: 'dropdown', choices: string[], value: string } type FieldId = $ElementType<Field, 'id'>
interface Settings { interface Settings {
success: boolean success: boolean
@ -157,7 +158,9 @@ const avatar = ref({ uuid: null, ...(store.state.auth.profile?.avatar ?? {}) })
const initialAvatar = avatar.value.uuid ?? undefined const initialAvatar = avatar.value.uuid ?? undefined
const avatarErrors = ref([] as string[]) const avatarErrors = ref([] as string[])
const isLoadingAvatar = ref(false) const isLoadingAvatar = ref(false)
const submitAvatar = async (uuid: string) => { const submitAvatar = async (uuid: string | null) => {
if (!uuid) return
isLoadingAvatar.value = true isLoadingAvatar.value = true
try { try {
@ -315,9 +318,9 @@ fetchOwnedApps()
:key="f.id" :key="f.id"
class="field" class="field"
> >
<label :for="f.id">{{ sharedLabels.fields[f.id as FieldId].label }}</label> <label :for="f.id">{{ sharedLabels.fields[f.id].label }}</label>
<p v-if="sharedLabels.fields[f.id as FieldId].help"> <p v-if="sharedLabels.fields[f.id].help">
{{ sharedLabels.fields[f.id as FieldId].help }} {{ sharedLabels.fields[f.id].help }}
</p> </p>
<select <select
v-if="f.type === 'dropdown'" v-if="f.type === 'dropdown'"
@ -330,7 +333,7 @@ fetchOwnedApps()
:key="key" :key="key"
:value="c" :value="c"
> >
{{ sharedLabels.fields[f.id as FieldId].choices[c] }} {{ sharedLabels.fields[f.id].choices?.[c] }}
</option> </option>
</select> </select>
<content-form <content-form
@ -833,7 +836,7 @@ fetchOwnedApps()
</p> </p>
<p> <p>
<translate <translate
:translate-params="{email: $store.state.auth.profile.email}" :translate-params="{email: $store.state.auth.profile?.email}"
translate-context="Content/Settings/Paragraph'" translate-context="Content/Settings/Paragraph'"
> >
Your current e-mail address is %{ email }. Your current e-mail address is %{ email }.

View File

@ -48,7 +48,7 @@ const payload = reactive({
password1: '', password1: '',
email: '', email: '',
invitation: props.defaultInvitation, invitation: props.defaultInvitation,
request_fields: {} request_fields: {} as Record<string, string | number | string[]>
}) })
const submitted = ref(false) const submitted = ref(false)

View File

@ -19,6 +19,7 @@ watch(show, () => {
submittable.value = false submittable.value = false
}) })
const albumForm = ref()
</script> </script>
<template> <template>
@ -28,7 +29,7 @@ watch(show, () => {
> >
<h4 class="header"> <h4 class="header">
<translate <translate
v-if="channel.content_category === 'podcasts'" v-if="channel.content_category === 'podcast'"
translate-context="Popup/Channels/Title/Verb" translate-context="Popup/Channels/Title/Verb"
> >
New series New series
@ -58,7 +59,7 @@ watch(show, () => {
<button <button
:class="['ui', 'primary', {loading: isLoading}, 'button']" :class="['ui', 'primary', {loading: isLoading}, 'button']"
:disabled="!submittable" :disabled="!submittable"
@click.stop.prevent="$refs.albumForm.submit()" @click.stop.prevent="albumForm.submit()"
> >
<translate translate-context="*/*/Button.Label"> <translate translate-context="*/*/Button.Label">
Create Create

View File

@ -2,8 +2,8 @@
import type { Channel } from '~/types' import type { Channel } from '~/types'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { computed, ref } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import { computed } from 'vue'
import LoginModal from '~/components/common/LoginModal.vue' import LoginModal from '~/components/common/LoginModal.vue'
@ -34,8 +34,12 @@ const message = computed(() => ({
const toggle = async () => { const toggle = async () => {
await store.dispatch('channels/toggle', props.channel.uuid) await store.dispatch('channels/toggle', props.channel.uuid)
emit(isSubscribed.value ? 'unsubscribed' : 'subscribed')
if (isSubscribed.value) emit('unsubscribed')
else emit('subscribed')
} }
const loginModal = ref()
</script> </script>
<template> <template>
@ -50,7 +54,7 @@ const toggle = async () => {
<button <button
v-else v-else
:class="['ui', 'pink', 'icon', 'labeled', 'button']" :class="['ui', 'pink', 'icon', 'labeled', 'button']"
@click="$refs.loginModal.show = true" @click="loginModal.show = true"
> >
<i class="heart icon" /> <i class="heart icon" />
{{ title }} {{ title }}
@ -59,8 +63,8 @@ const toggle = async () => {
class="small" class="small"
:next-route="$route.fullPath" :next-route="$route.fullPath"
:message="message.authMessage" :message="message.authMessage"
:cover="channel.artist.cover!" :cover="channel.artist?.cover!"
@created="$refs.loginModal.show = false;" @created="loginModal.show = false"
/> />
</button> </button>
</template> </template>

View File

@ -579,9 +579,8 @@ const labels = computed(() => ({
</div> </div>
<upload-metadata-form <upload-metadata-form
v-if="selectedUpload" v-if="selectedUpload"
v-model:values="uploadImportData[selectedUploadId]"
:upload="selectedUpload" :upload="selectedUpload"
:values="uploadImportData[selectedUploadId]"
@values="uploadImportData.selectedUploadId = $event"
/> />
<div <div
v-if="step === 2" v-if="step === 2"

View File

@ -1,34 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Upload } from '~/types' import type { Upload, Tag } from '~/types'
import { ref, computed, watch } from 'vue' import { reactive, computed, watch } from 'vue'
import { useVModel } from '@vueuse/core'
import TagsSelector from '~/components/library/TagsSelector.vue' import TagsSelector from '~/components/library/TagsSelector.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue' import AttachmentInput from '~/components/common/AttachmentInput.vue'
interface Events { interface Events {
(e: 'values', values: Record<string, string>): void (e: 'update:values', values: Values): void
} }
interface Props { interface Props {
upload: Upload upload: Upload
values?: Record<string, string> | null values: Values
} }
type Values = (Record<string, string> & { tags?: Tag[] }) | null
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
values: null values: null
}) })
const newValues = ref({ ...(props.values ?? props.upload.import_metadata) } as Record<string, string>) const newValues = reactive<Exclude<Values, null>>({
...(props.values ?? props.upload.import_metadata),
tags: (props.values ?? props.upload.import_metadata)?.tags ?? [] as Tag[]
})
// computed: {
// isLoading () {
// return !!this.metadata
// }
// },
const isLoading = computed(() => !props.upload) const isLoading = computed(() => !props.upload)
watch(newValues, (values) => emit('values', values), { immediate: true }) watch(newValues, (values) => emit('update:values', values), { immediate: true })
</script> </script>
<template> <template>
@ -44,7 +45,7 @@ watch(newValues, (values) => emit('values', values), { immediate: true })
</div> </div>
<attachment-input <attachment-input
v-model="newValues.cover" v-model="newValues.cover"
@delete="newValues.cover = null" @delete="newValues.cover = ''"
> >
<translate translate-context="Content/Channel/*"> <translate translate-context="Content/Channel/*">
Track Picture Track Picture

View File

@ -13,7 +13,7 @@ interface Action {
allowAll?: boolean allowAll?: boolean
confirmColor?: string confirmColor?: string
confirmationMessage?: string confirmationMessage?: string
filterChackable?: (item: never) => boolean filterChackable?: (item: any) => boolean
} }
interface Events { interface Events {
@ -22,7 +22,7 @@ interface Events {
} }
interface Props { interface Props {
objectsData: { results: [], count: number } objectsData: { results: any[], count: number }
actions: Action[] actions: Action[]
actionUrl: string actionUrl: string
idField?: string idField?: string

View File

@ -24,6 +24,7 @@ onMounted(() => {
...props.message ...props.message
} }
// @ts-expect-error fomantic ui
$('body').toast(params) $('body').toast(params)
$('.ui.toast.visible').last().attr('role', 'alert') $('.ui.toast.visible').last().attr('role', 'alert')
}) })

View File

@ -21,7 +21,7 @@ interface Events {
} }
interface Props { interface Props {
id: string id: number
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Album, Library } from '~/types' import type { EditObjectType } from '~/composables/moderation/useEditConfigs'
import type { Album, Library, Actor } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
import EditForm from '~/components/library/EditForm.vue' import EditForm from '~/components/library/EditForm.vue'
interface Props { interface Props {
objectType: string objectType: EditObjectType
object: Album object: Album & { attributed_to: Actor }
libraries: Library[] libraries: Library[]
} }
@ -46,7 +47,6 @@ const canEdit = store.state.auth.availablePermissions.library
v-else v-else
:object-type="objectType" :object-type="objectType"
:object="object" :object="object"
:can-edit="canEdit"
/> />
</div> </div>
</section> </section>

View File

@ -41,7 +41,7 @@ const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const query = ref(props.defaultQuery) const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.slice()) const tags = reactive(props.defaultTags.map(name => ({ name })))
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['creation_date', 'creation_date'], ['creation_date', 'creation_date'],
@ -59,7 +59,7 @@ const updateQueryString = () => router.replace({
query: { query: {
query: query.value, query: query.value,
page: page.value, page: page.value,
tag: tags, tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value, paginateBy: paginateBy.value,
ordering: orderingString.value ordering: orderingString.value
} }

View File

@ -1,22 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Track, Album, Artist, Library } from '~/types' import type { Track, Album, Artist, Library } from '~/types'
import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import axios from 'axios' import { useRouter } from 'vue-router'
import PlayButton from '~/components/audio/PlayButton.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import RadioButton from '~/components/radios/Button.vue'
import TagsList from '~/components/tags/List.vue'
import useReport from '~/composables/moderation/useReport'
import useLogger from '~/composables/useLogger'
import { getDomain } from '~/utils' import { getDomain } from '~/utils'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useRouter } from 'vue-router'
import { computed, ref, watch } from 'vue' import axios from 'axios'
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import PlayButton from '~/components/audio/PlayButton.vue'
import RadioButton from '~/components/radios/Button.vue'
import TagsList from '~/components/tags/List.vue'
import useReport from '~/composables/moderation/useReport'
import useLogger from '~/composables/useLogger'
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { EditObjectType } from '~/composables/moderation/useEditConfigs'
import type { Artist, Library } from '~/types' import type { Artist, Library } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -6,7 +7,7 @@ import { useStore } from '~/store'
import EditForm from '~/components/library/EditForm.vue' import EditForm from '~/components/library/EditForm.vue'
interface Props { interface Props {
objectType: string objectType: EditObjectType
object: Artist object: Artist
libraries: Library[] libraries: Library[]
} }
@ -46,7 +47,6 @@ const canEdit = store.state.auth.availablePermissions.library
v-else v-else
:object-type="objectType" :object-type="objectType"
:object="object" :object="object"
:can-edit="canEdit"
/> />
</div> </div>
</section> </section>

View File

@ -41,7 +41,7 @@ const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const query = ref(props.defaultQuery) const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.slice()) const tags = reactive(props.defaultTags.map(name => ({ name })))
const excludeCompilation = ref(true) const excludeCompilation = ref(true)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
@ -59,7 +59,7 @@ const updateQueryString = () => router.replace({
query: { query: {
query: query.value, query: query.value,
page: page.value, page: page.value,
tag: tags, tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value, paginateBy: paginateBy.value,
ordering: orderingString.value, ordering: orderingString.value,
content_category: 'music', content_category: 'music',

View File

@ -19,10 +19,12 @@ import EditCard from '~/components/library/EditCard.vue'
interface Props { interface Props {
objectType: EditObjectType objectType: EditObjectType
object: EditObject object: EditObject
licenses: License[] licenses?: License[]
} }
const props = defineProps<Props>() const props = withDefaults(defineProps<Props>(), {
licenses: () => []
})
const { $pgettext } = useGettext() const { $pgettext } = useGettext()
const configs = useEditConfigs() const configs = useEditConfigs()

View File

@ -53,7 +53,7 @@ const labels = computed(() => ({
'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }', 'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }',
{ extensions: supportedExtensions.value.join(', ') } { extensions: supportedExtensions.value.join(', ') }
) )
} } as Record<string, string>
})) }))
const uploads = reactive({ const uploads = reactive({
@ -113,7 +113,7 @@ const fetchStatus = async () => {
uploads[status as keyof typeof uploads] = response.data.count uploads[status as keyof typeof uploads] = response.data.count
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
} }
} }
} }
@ -473,7 +473,7 @@ useEventListener(window, 'beforeunload', (event) => {
<span <span
v-if="typeof file.error === 'string' && file.error" v-if="typeof file.error === 'string' && file.error"
class="ui tooltip" class="ui tooltip"
:data-tooltip="labels.tooltips[file.error as keyof typeof labels.tooltips]" :data-tooltip="labels.tooltips[file.error]"
> >
<span class="ui danger icon label"> <span class="ui danger icon label">
<i class="question circle outline icon" /> {{ file.error }} <i class="question circle outline icon" /> {{ file.error }}

View File

@ -44,7 +44,7 @@ const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] } type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const query = ref(props.defaultQuery) const query = ref(props.defaultQuery)
const tags = reactive(props.defaultTags.slice()) const tags = reactive(props.defaultTags.map(name => ({ name })))
const showSubscribeModal = ref(false) const showSubscribeModal = ref(false)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
@ -62,7 +62,7 @@ const updateQueryString = () => router.replace({
query: { query: {
query: query.value, query: query.value,
page: page.value, page: page.value,
tag: tags, tag: tags.map(({ name }) => name),
paginateBy: paginateBy.value, paginateBy: paginateBy.value,
ordering: orderingString.value, ordering: orderingString.value,
content_category: 'podcast', content_category: 'podcast',

View File

@ -26,7 +26,7 @@ interface Events {
} }
interface Props { interface Props {
id: string id: number
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()

View File

@ -20,7 +20,7 @@ type Filter = { candidates: { count: number, sample: Track[] } }
type ResponseType = { filters: Array<Filter> } type ResponseType = { filters: Array<Filter> }
interface Events { interface Events {
(e: 'update-config', index: number, name: string, value: number[]): void (e: 'update-config', index: number, name: string, value: number[] | boolean): void
(e: 'delete', index: number): void (e: 'delete', index: number): void
} }

View File

@ -3,6 +3,7 @@ import type { EditObjectType } from '~/composables/moderation/useEditConfigs'
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { RouteWithPreferences, OrderingField } from '~/store/ui'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { SmartSearchProps } from '~/composables/useSmartSearch'
import type { OrderingProps } from '~/composables/useOrdering' import type { OrderingProps } from '~/composables/useOrdering'
import type { ReviewState, Review } from '~/types'
import { ref, reactive, watch, computed } from 'vue' import { ref, reactive, watch, computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
@ -39,9 +40,8 @@ const search = ref()
const page = ref(1) const page = ref(1)
type StateTarget = { id: number, type: keyof typeof targets } type StateTarget = Review['target']
type ResponseResult = { uuid: string, is_approved: boolean, target?: StateTarget } type ResponseType = { count: number, results: Review[] }
type ResponseType = { count: number, results: ResponseResult[] }
const result = ref<null | ResponseType>(null) const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl)
@ -53,20 +53,23 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
] ]
interface TargetType { interface TargetType {
payload: ResponseResult payload: Review
currentState: Record<EditObjectType, { value: unknown }> currentState: Record<EditObjectType, { value: unknown }>
} }
type Targets = Exclude<StateTarget, undefined>['type']
const targets = reactive({ const targets = reactive({
track: {} as Record<string, TargetType> track: {}
}) }) as Record<Targets, Record<string, TargetType>>
const fetchTargets = async () => { const fetchTargets = async () => {
// we request target data via the API so we can display previous state // we request target data via the API so we can display previous state
// additionnal data next to the edit card // additionnal data next to the edit card
type Config = { url: string, ids: number[] } type Config = { url: string, ids: number[] }
const typesAndIds: Record<keyof typeof targets, Config> = { const typesAndIds: Record<Targets, Config> = {
track: { url: 'tracks/', ids: [] } artist: { url: 'artists/', ids: [] },
track: { url: 'tracks/', ids: [] },
album: { url: 'albums/', ids: [] }
} }
for (const res of result.value?.results ?? []) { for (const res of result.value?.results ?? []) {
@ -85,7 +88,7 @@ const fetchTargets = async () => {
id: uniq(config.ids), id: uniq(config.ids),
hidden: 'null' hidden: 'null'
} }
}).catch(() => { }).catch((error) => {
useErrorHandler(error as Error) useErrorHandler(error as Error)
}) })
@ -147,8 +150,8 @@ const handle = (type: 'delete' | 'approved', id: string, value: boolean) => {
} }
} }
const getCurrentState = (target?: StateTarget): object => { const getCurrentState = (target?: StateTarget): ReviewState => {
if (!target) return {} if (!target || !(target.type in targets)) return {}
return targets[target.type]?.[target.id]?.currentState ?? {} return targets[target.type]?.[target.id]?.currentState ?? {}
} }
</script> </script>

View File

@ -97,8 +97,7 @@ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by name') searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by name')
})) }))
// TODO (wvffle): Check if we can remove the prop const detailedUpload = ref()
const detailedUpload = ref({})
const showUploadDetailModal = ref(false) const showUploadDetailModal = ref(false)
</script> </script>
@ -157,6 +156,7 @@ const showUploadDetailModal = ref(false)
</div> </div>
</div> </div>
<import-status-modal <import-status-modal
v-if="detailedUpload"
v-model:show="showUploadDetailModal" v-model:show="showUploadDetailModal"
:upload="detailedUpload" :upload="detailedUpload"
/> />

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ImportStatus, PrivacyLevel, Upload, BackendResponse } from '~/types'
import type { RouteWithPreferences, OrderingField } from '~/store/ui' import type { RouteWithPreferences, OrderingField } from '~/store/ui'
import type { SmartSearchProps } from '~/composables/useSmartSearch' import type { SmartSearchProps } from '~/composables/useSmartSearch'
import type { ImportStatus, PrivacyLevel, Upload } from '~/types'
import type { OrderingProps } from '~/composables/useOrdering' import type { OrderingProps } from '~/composables/useOrdering'
import { humanSize, truncate } from '~/utils/filters' import { humanSize, truncate } from '~/utils/filters'
@ -37,8 +37,7 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref() const search = ref()
const page = ref(1) const page = ref(1)
type ResponseType = { count: number, results: any[] } const result = ref<BackendResponse<Upload>>()
const result = ref<null | ResponseType>(null)
const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl) const { onSearch, query, addSearchToken, getTokenValue } = useSmartSearch(props.defaultQuery, props.updateUrl)
const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName) const { onOrderingUpdate, orderingString, paginateBy, ordering, orderingDirection } = useOrdering(props.orderingConfigName)
@ -84,7 +83,7 @@ const fetchData = async () => {
result.value = response.data result.value = response.data
} catch (error) { } catch (error) {
useErrorHandler(error as Error) useErrorHandler(error as Error)
result.value = null result.value = undefined
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@ -104,7 +103,7 @@ const displayName = (upload: Upload): string => {
return upload.filename ?? upload.source ?? upload.uuid return upload.filename ?? upload.source ?? upload.uuid
} }
const detailedUpload = ref({}) const detailedUpload = ref<Upload>()
const showUploadDetailModal = ref(false) const showUploadDetailModal = ref(false)
const getImportStatusChoice = (importStatus: ImportStatus) => { const getImportStatusChoice = (importStatus: ImportStatus) => {
@ -229,8 +228,9 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
</div> </div>
</div> </div>
</div> </div>
<!-- TODO (wvffle): Check if :upload shouldn't be v-modl:upload --> <!-- TODO (wvffle): Check if :upload shouldn't be v-model:upload -->
<import-status-modal <import-status-modal
v-if="detailedUpload"
v-model:show="showUploadDetailModal" v-model:show="showUploadDetailModal"
:upload="detailedUpload" :upload="detailedUpload"
/> />
@ -296,9 +296,7 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
</translate> </translate>
</th> </th>
</template> </template>
<template <template #row-cells="scope">
#row-cells="scope"
>
<td> <td>
<router-link :to="{name: 'manage.library.uploads.detail', params: {id: scope.obj.uuid }}"> <router-link :to="{name: 'manage.library.uploads.detail', params: {id: scope.obj.uuid }}">
{{ truncate(displayName(scope.obj), 30, undefined, true) }} {{ truncate(displayName(scope.obj), 30, undefined, true) }}
@ -369,7 +367,7 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
</a> </a>
<button <button
class="ui tiny basic icon button" class="ui tiny basic icon button"
:title="sharedLabels.fields.import_status.detailTitle" :title="sharedLabels.fields.import_status.label"
@click="detailedUpload = scope.obj; showUploadDetailModal = true" @click="detailedUpload = scope.obj; showUploadDetailModal = true"
> >
<i class="question circle outline icon" /> <i class="question circle outline icon" />

View File

@ -326,7 +326,7 @@ const handleRemovedNote = (uuid: string) => {
<router-link <router-link
v-if="target && configs[target.type].urls.getDetail" v-if="target && configs[target.type].urls.getDetail"
class="ui basic button" class="ui basic button"
:to="configs[target.type].urls.getDetail(obj.target_state)" :to="configs[target.type].urls.getDetail?.(obj.target_state) ?? '/'"
> >
<i class="eye icon" /> <i class="eye icon" />
<translate translate-context="Content/Moderation/Link"> <translate translate-context="Content/Moderation/Link">
@ -336,7 +336,7 @@ const handleRemovedNote = (uuid: string) => {
<router-link <router-link
v-if="target && configs[target.type].urls.getAdminDetail" v-if="target && configs[target.type].urls.getAdminDetail"
class="ui basic button" class="ui basic button"
:to="configs[target.type].urls.getAdminDetail(obj.target_state)" :to="configs[target.type].urls.getAdminDetail?.(obj.target_state) ?? '/'"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link"> <translate translate-context="Content/Moderation/Link">
@ -391,10 +391,10 @@ const handleRemovedNote = (uuid: string) => {
</td> </td>
<td> <td>
<instance-policy-modal <instance-policy-modal
v-if="!obj.target_owner.is_local" v-if="!obj.target_owner?.is_local"
class="right floated mini basic" class="right floated mini basic"
type="actor" type="actor"
:target="obj.target_owner.full_username" :target="obj.target_owner?.full_username ?? ''"
/> />
</td> </td>
</tr> </tr>

View File

@ -9,7 +9,7 @@ interface Props {
customRadioId?: number | null customRadioId?: number | null
type?: string type?: string
clientOnly?: boolean clientOnly?: boolean
objectId?: ObjectId | string | null objectId?: ObjectId | number | string | null
radioConfig?: RadioConfig | null radioConfig?: RadioConfig | null
} }
@ -31,7 +31,10 @@ const running = computed(() => {
&& store.state.radios.current?.customRadioId === props.customRadioId && store.state.radios.current?.customRadioId === props.customRadioId
&& ( && (
typeof props.objectId === 'string' typeof props.objectId === 'string'
|| store.state.radios.current?.objectId?.fullUsername === props.objectId?.fullUsername || (
typeof props.objectId !== 'number'
&& store.state.radios.current?.objectId?.fullUsername === props.objectId?.fullUsername
)
) )
}) })

View File

@ -6,12 +6,12 @@ import { reactive, watchEffect, ref } from 'vue'
const MAX_PRELOADED = 3 const MAX_PRELOADED = 3
export interface CachedSound { export interface CachedSound {
id: string id: number
date: Date date: Date
howl: Howl howl: Howl
} }
const soundCache = reactive(new Map<string, CachedSound>()) const soundCache = reactive(new Map<number, CachedSound>())
const cleaningCache = ref(false) const cleaningCache = ref(false)
watchEffect(() => { watchEffect(() => {

View File

@ -22,7 +22,7 @@ export default () => ({
} as Record<PrivacyLevel, string> } as Record<PrivacyLevel, string>
}, },
import_status: { import_status: {
detailTitle: $pgettext('Content/Library/Link.Title', 'Click to display more information about the import process for this upload'), label: $pgettext('Content/Library/Link.Title', 'Click to display more information about the import process for this upload'),
choices: { choices: {
skipped: { skipped: {
label: $pgettext('Content/Library/*', 'Skipped'), label: $pgettext('Content/Library/*', 'Skipped'),
@ -57,7 +57,8 @@ export default () => ({
} }
}, },
summary: { summary: {
label: $pgettext('Content/Account/*', 'Bio') label: $pgettext('Content/Account/*', 'Bio'),
help: undefined
}, },
content_category: { content_category: {
label: $pgettext('Content/*/Dropdown.Label/Noun', 'Content category'), label: $pgettext('Content/*/Dropdown.Label/Noun', 'Content category'),

View File

@ -23,7 +23,7 @@ interface ReportableObject {
_obj: Objects[keyof Objects] _obj: Objects[keyof Objects]
full_username?: string full_username?: string
id?: string id?: number
uuid?: string uuid?: string
} }
} }

View File

@ -1,8 +0,0 @@
import jQuery from 'jquery'
// NOTE: Workaround for fomantic-ui-css
if (import.meta.env.DEV) {
window.$ = window.jQuery = jQuery
}
export default jQuery

View File

@ -1,3 +1,5 @@
/// <reference lib="webworker" />
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching' import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies' import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration' import { ExpirationPlugin } from 'workbox-expiration'

View File

@ -31,7 +31,7 @@ export interface ContentFilter {
creation_date: Date creation_date: Date
target: { target: {
type: 'artist' type: 'artist'
id: string id: number
} }
} }

View File

@ -44,7 +44,7 @@ export interface ThemeEntry {
export type ContentCategory = 'podcast' | 'music' export type ContentCategory = 'podcast' | 'music'
export interface Artist { export interface Artist {
id: string id: number
fid: string fid: string
mbid?: string mbid?: string
@ -65,7 +65,7 @@ export interface Artist {
} }
export interface Album { export interface Album {
id: string id: number
fid: string fid: string
mbid?: string mbid?: string
@ -84,7 +84,7 @@ export interface Album {
} }
export interface Track { export interface Track {
id: string id: number
fid: string fid: string
mbid?: string mbid?: string
@ -111,7 +111,7 @@ export interface Track {
} }
export interface Channel { export interface Channel {
id: string id: number
uuid: string uuid: string
artist?: Artist artist?: Artist
actor: Actor actor: Actor
@ -134,7 +134,7 @@ export interface Channel {
export type PrivacyLevel = 'everyone' | 'instance' | 'me' export type PrivacyLevel = 'everyone' | 'instance' | 'me'
export interface Library { export interface Library {
id: string id: number
uuid: string uuid: string
fid?: string fid?: string
name: string name: string
@ -182,7 +182,7 @@ export interface License {
} }
export interface Playlist { export interface Playlist {
id: string id: number
name: string name: string
modification_date: string modification_date: string
user: User user: User
@ -206,7 +206,7 @@ export interface Radio {
} }
export interface Listening { export interface Listening {
id: string id: number
track: Track track: Track
user: User user: User
actor: Actor actor: Actor
@ -277,6 +277,7 @@ export interface Form {
// Upload stuff // Upload stuff
export interface Upload { export interface Upload {
id: number
uuid: string uuid: string
filename?: string filename?: string
source?: string source?: string
@ -293,12 +294,12 @@ export interface Upload {
error_code: string error_code: string
} }
import_metadata?: Record<string, string> import_metadata?: Record<string, string> & { tags?: Tag[] }
} }
// Profile stuff // Profile stuff
export interface Actor { export interface Actor {
id: string id: number
fid?: string fid?: string
name?: string name?: string
icon?: Cover icon?: Cover
@ -310,7 +311,7 @@ export interface Actor {
} }
export interface User { export interface User {
id: string id: number
avatar?: Cover avatar?: Cover
email: string email: string
summary: { text: string, content_type: string } summary: { text: string, content_type: string }
@ -403,7 +404,7 @@ export interface Plugin {
export type EntityObjectType = 'artist' | 'album' | 'track' | 'library' | 'playlist' | 'account' | 'channel' export type EntityObjectType = 'artist' | 'album' | 'track' | 'library' | 'playlist' | 'account' | 'channel'
export interface ReportTarget { export interface ReportTarget {
id: string id: number
type: EntityObjectType type: EntityObjectType
} }

View File

@ -1,9 +1,10 @@
import type { ListenWSEvent } from '~/types' import type { CurrentRadio, PopulateQueuePayload } from '~/store/radios'
import type { ListenWS } from '~/composables/useWebSocketHandler'
import type { RootState } from '~/store' import type { RootState } from '~/store'
import type { Store } from 'vuex' import type { Store } from 'vuex'
import type { CurrentRadio, PopulateQueuePayload } from '~/store/radios'
import axios from 'axios' import axios from 'axios'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
const logger = useLogger() const logger = useLogger()
@ -14,7 +15,7 @@ export const CLIENT_RADIOS = {
account: { account: {
offset: 1, offset: 1,
populateQueue ({ current, dispatch, playNow }: PopulateQueuePayload) { populateQueue ({ current, dispatch, playNow }: PopulateQueuePayload) {
const params = { scope: `actor:${current.objectId.fullUsername}`, ordering: '-creation_date', page_size: 1, page: this.offset } const params = { scope: `actor:${current.objectId?.fullUsername}`, ordering: '-creation_date', page_size: 1, page: this.offset }
axios.get('history/listenings', { params }).then(async (response) => { axios.get('history/listenings', { params }).then(async (response) => {
const latest = response.data.results[0] const latest = response.data.results[0]
if (!latest) { if (!latest) {
@ -35,9 +36,9 @@ export const CLIENT_RADIOS = {
stop () { stop () {
this.offset = 1 this.offset = 1
}, },
handleListen (current: CurrentRadio, event: ListenWSEvent, store: Store<RootState>) { handleListen (current: CurrentRadio, event: ListenWS, store: Store<RootState>) {
// TODO: handle actors from other pods // TODO: handle actors from other pods
if (event.actor.local_id === current.objectId.username) { if (event.actor.local_id === current.objectId?.username) {
axios.get(`tracks/${event.object.local_id}`).then(async (response) => { axios.get(`tracks/${event.object.local_id}`).then(async (response) => {
if (response.data.uploads.length > 0) { if (response.data.uploads.length > 0) {
await store.dispatch('queue/append', { track: response.data }) await store.dispatch('queue/append', { track: response.data })

View File

@ -12,7 +12,7 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -15,7 +15,7 @@ import useLogger from '~/composables/useLogger'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[] const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -14,7 +14,7 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -258,7 +258,7 @@ const showUploadDetailModal = ref(false)
{{ importStatus }} {{ importStatus }}
<button <button
class="ui tiny basic icon button" class="ui tiny basic icon button"
:title="sharedLabels.fields.import_status.detailTitle" :title="sharedLabels.fields.import_status.label"
@click="showUploadDetailModal = true" @click="showUploadDetailModal = true"
> >
<i class="question circle outline icon" /> <i class="question circle outline icon" />

View File

@ -38,7 +38,7 @@ const allPermissions = computed(() => [
const isLoadingPolicy = ref(false) const isLoadingPolicy = ref(false)
const policy = ref() const policy = ref()
const fetchPolicy = async (id: string) => { const fetchPolicy = async (id: number) => {
isLoadingPolicy.value = true isLoadingPolicy.value = true
try { try {

View File

@ -28,7 +28,7 @@ const labels = computed(() => ({
const isLoadingPolicy = ref(false) const isLoadingPolicy = ref(false)
const policy = ref() const policy = ref()
const fetchPolicy = async (id: string) => { const fetchPolicy = async (id: number) => {
isLoadingPolicy.value = true isLoadingPolicy.value = true
try { try {

View File

@ -18,6 +18,9 @@ const showCreateModal = ref(false)
const loading = ref(false) const loading = ref(false)
const submittable = ref(false) const submittable = ref(false)
const category = ref('podcast') const category = ref('podcast')
const modalContent = ref()
const createForm = ref()
</script> </script>
<template> <template>
@ -108,7 +111,7 @@ const category = ref('podcast')
@loading="loading = $event" @loading="loading = $event"
@submittable="submittable = $event" @submittable="submittable = $event"
@category="category = $event" @category="category = $event"
@errored="$refs.modalContent.scrollTop = 0" @errored="modalContent.scrollTop = 0"
@created="$router.push({name: 'channels.detail', params: {id: $event.actor.preferred_username}})" @created="$router.push({name: 'channels.detail', params: {id: $event.actor.preferred_username}})"
/> />
<div class="ui hidden divider" /> <div class="ui hidden divider" />
@ -145,7 +148,7 @@ const category = ref('podcast')
:class="['ui', 'primary button', { loading }]" :class="['ui', 'primary button', { loading }]"
type="submit" type="submit"
:disabled="!submittable && !loading" :disabled="!submittable && !loading"
@click.prevent.stop="$refs.createForm.submit" @click.prevent.stop="createForm.submit"
> >
<translate translate-context="*/Channels/Button.Label"> <translate translate-context="*/Channels/Button.Label">
Create channel Create channel

View File

@ -23,7 +23,7 @@ interface Events {
} }
interface Props { interface Props {
id: string id: number
} }
const emit = defineEmits<Events>() const emit = defineEmits<Events>()

View File

@ -124,7 +124,7 @@ const labels = computed(() => ({
showStatus: $pgettext('Content/Library/Button.Label/Verb', 'Show information about the upload status for this track') showStatus: $pgettext('Content/Library/Button.Label/Verb', 'Show information about the upload status for this track')
})) }))
const detailedUpload = ref({}) const detailedUpload = ref()
const showUploadDetailModal = ref(false) const showUploadDetailModal = ref(false)
const getImportStatusChoice = (importStatus: ImportStatus) => { const getImportStatusChoice = (importStatus: ImportStatus) => {
@ -235,6 +235,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
</div> </div>
</div> </div>
<import-status-modal <import-status-modal
v-if="detailedUpload"
v-model:show="showUploadDetailModal" v-model:show="showUploadDetailModal"
:upload="detailedUpload" :upload="detailedUpload"
/> />
@ -352,7 +353,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
>{{ getImportStatusChoice(scope.obj.import_status).label }}</a> >{{ getImportStatusChoice(scope.obj.import_status).label }}</a>
<button <button
class="ui tiny basic icon button" class="ui tiny basic icon button"
:title="sharedLabels.fields.import_status.detailTitle" :title="sharedLabels.fields.import_status.label"
:aria-label="labels.showStatus" :aria-label="labels.showStatus"
@click="detailedUpload = scope.obj; showUploadDetailModal = true" @click="detailedUpload = scope.obj; showUploadDetailModal = true"
> >

View File

@ -83,7 +83,6 @@ const libraryCreated = (library: Library) => {
</a> </a>
<library-form <library-form
v-if="!hiddenForm" v-if="!hiddenForm"
:library="null"
@created="libraryCreated" @created="libraryCreated"
/> />
<div class="ui hidden divider" /> <div class="ui hidden divider" />

View File

@ -16,7 +16,7 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()

View File

@ -17,7 +17,7 @@ import PlayButton from '~/components/audio/PlayButton.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
interface Props { interface Props {
id: string id: number
defaultEdit?: boolean defaultEdit?: boolean
} }

View File

@ -14,7 +14,7 @@ import Pagination from '~/components/vui/Pagination.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
interface Props { interface Props {
id: string id: number
} }
const props = defineProps<Props>() const props = defineProps<Props>()