Resolve some TODOs

This commit is contained in:
wvffle 2022-08-30 20:23:17 +00:00 committed by Georg Krause
parent 74d1a0a03e
commit e7da8b5f43
98 changed files with 318 additions and 261 deletions

View File

@ -56,7 +56,6 @@ onMounted(async () => {
})
// Time ago
// TODO (wvffle): Migrate to useTimeAgo
useIntervalFn(() => {
// used to redraw ago dates every minute
store.commit('ui/computeLastDate')

View File

@ -1,18 +1,18 @@
<script setup lang="ts">
import type { QueueItemSource } from '~/types'
interface Events {
(e: 'play', index: number): void
(e: 'remove', index: number): void
}
interface Props {
source: QueueItemSource
index: number
}
interface Emits {
(e: 'play', index: number): void
(e: 'remove', index: number): void
}
defineEmits<Events>()
defineProps<Props>()
defineEmits<Emits>()
</script>
<template>

View File

@ -9,6 +9,10 @@ import { useGettext } from 'vue3-gettext'
type Type = 'rss' | 'artists' | 'both'
interface Events {
(e: 'subscribed', rss: object): void
}
interface Props {
initialId?: string
initialType?: Type
@ -17,6 +21,7 @@ interface Props {
standalone?: boolean
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
initialId: '',
initialType: 'artists',
@ -118,7 +123,6 @@ const createFetch = async () => {
isLoading.value = false
}
const emit = defineEmits(['subscribed'])
const store = useStore()
const rssSubscribe = async () => {

View File

@ -9,13 +9,17 @@ import axios from 'axios'
import SemanticModal from '~/components/semantic/Modal.vue'
interface Events {
(e: 'update:show', show: boolean): void
}
interface Props {
show: boolean
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const emit = defineEmits(['update:show'])
const show = useVModel(props, 'show', emit)
const instanceUrl = ref('')

View File

@ -4,12 +4,17 @@ import { useVModel } from '@vueuse/core'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Events {
(e: 'update:show', show: boolean): void
}
interface Props {
show: boolean
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const emit = defineEmits(['update:show'])
const showRef = useVModel(props, 'show', emit)
const { $pgettext } = useGettext()

View File

@ -7,16 +7,20 @@ import { computed, ref } from 'vue'
import { useGettext } from 'vue3-gettext'
import { arrayMove } from '~/utils'
interface Events {
(e: 'update:modelValue', value: Form): void
}
interface Props {
modelValue: Form
signupApprovalEnabled?: boolean
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
signupApprovalEnabled: false
})
const emit = defineEmits(['update:modelValue'])
const value = useVModel(props, 'modelValue', emit, { deep: true })
const maxFields = ref(10)
@ -140,7 +144,6 @@ const move = (idx: number, increment: number) => {
</tr>
</thead>
<tbody>
<!-- TODO (wvffle): Add random _id as :key -->
<tr
v-for="(field, idx) in value.fields"
:key="idx"

View File

@ -1,16 +1,17 @@
<script setup lang="ts">
import type { Channel } from '~/types'
import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue'
import { momentFormat } from '~/utils/filters'
import { useGettext } from 'vue3-gettext'
import { useStore } from '~/store'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
import moment from 'moment'
import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue'
interface Props {
// TODO (wvffle) : Find type
object: Channel
}
@ -43,7 +44,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
<div class="card app-card">
<div
v-lazy:background-image="imageUrl"
:class="['ui', 'head-image', {'circular': object.artist.content_category != 'podcast'}, {'padded': object.artist.content_category === 'podcast'}, 'image', {'default-cover': !object.artist.cover}]"
:class="['ui', 'head-image', {'circular': object.artist?.content_category != 'podcast'}, {'padded': object.artist?.content_category === 'podcast'}, 'image', {'default-cover': !object.artist?.cover}]"
@click="$router.push({name: 'channels.detail', params: {id: urlId}})"
>
<play-button
@ -59,12 +60,12 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
class="discrete link"
:to="{name: 'channels.detail', params: {id: urlId}}"
>
{{ object.artist.name }}
{{ object.artist?.name }}
</router-link>
</strong>
<div class="description">
<translate
v-if="object.artist.content_category === 'podcast'"
v-if="object.artist?.content_category === 'podcast'"
class="meta ellipsis"
translate-context="Content/Channel/Paragraph"
translate-plural="%{ count } episodes"
@ -76,8 +77,8 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
<translate
v-else
translate-context="*/*/*"
:translate-params="{count: object.artist.tracks_count}"
:translate-n="object.artist.tracks_count"
:translate-params="{count: object.artist?.tracks_count}"
:translate-n="object.artist?.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
@ -87,7 +88,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
:truncate-size="20"
:limit="2"
:show-more="false"
:tags="object.artist.tags ?? []"
:tags="object.artist?.tags ?? []"
/>
</div>
</div>
@ -96,7 +97,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
v-translate="{ updatedAgo }"
:translate-params="{ updatedAgo }"
class="meta ellipsis"
:datetime="object.artist.modification_date"
:datetime="object.artist?.modification_date"
:title="updatedTitle"
>
%{ updatedAgo }

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Cover, Track, BackendError } from '~/types'
import type { Cover, Track, BackendResponse, BackendError } from '~/types'
import { clone } from 'lodash-es'
import { ref, watch } from 'vue'
@ -8,6 +8,10 @@ import axios from 'axios'
import PodcastTable from '~/components/audio/podcast/Table.vue'
import TrackTable from '~/components/audio/track/Table.vue'
interface Events {
(e: 'fetched', data: BackendResponse<Track[]>): void
}
interface Props {
filters: object
limit?: number
@ -15,7 +19,7 @@ interface Props {
isPodcast: boolean
}
const emit = defineEmits(['fetched'])
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
limit: 10,
defaultCover: null

View File

@ -8,7 +8,6 @@ import usePlayer from '~/composables/audio/usePlayer'
import { computed } from 'vue'
interface Props {
// TODO (wvffle): Is it correct type?
entry: Track
defaultCover: Cover
}
@ -42,7 +41,7 @@ const duration = computed(() => props.entry.uploads.find(upload => upload.durati
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
>
<img
v-else-if="entry.artist.content_category === 'podcast' && defaultCover != undefined"
v-else-if="entry.artist?.content_category === 'podcast' && defaultCover != undefined"
v-lazy="$store.getters['instance/absoluteUrl'](defaultCover.urls.medium_square_crop)"
class="channel-image image"
@click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Channel, BackendError } from '~/types'
import type { ContentCategory, Channel, BackendError, Tag } from '~/types'
import { slugify } from 'transliteration'
import { reactive, computed, ref, watchEffect, watch } from 'vue'
@ -9,12 +9,21 @@ import axios from 'axios'
import AttachmentInput from '~/components/common/AttachmentInput.vue'
import TagsSelector from '~/components/library/TagsSelector.vue'
interface Events {
(e: 'category', contentCategory: ContentCategory): void
(e: 'submittable', value: boolean): void
(e: 'loading', value: boolean): void
(e: 'errored', errors: string[]): void
(e: 'created', channel: Channel): void
(e: 'updated', channel: Channel): void
}
interface Props {
object?: Channel | null
step: number
}
const emit = defineEmits(['category', 'submittable', 'loading', 'errored', 'created', 'updated'])
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
object: null,
step: 1
@ -25,7 +34,7 @@ const { $pgettext } = useGettext()
const newValues = reactive({
name: props.object?.artist?.name ?? '',
username: props.object?.actor.preferred_username ?? '',
tags: props.object?.artist?.tags ?? [],
tags: props.object?.artist?.tags?.map(name => ({ name } as Tag)) ?? [] as Tag[],
description: props.object?.artist?.description?.text ?? '',
cover: props.object?.artist?.cover?.uuid ?? null,
content_category: props.object?.artist?.content_category ?? 'podcast',
@ -76,11 +85,11 @@ const labels = computed(() => ({
usernamePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname')
}))
const submittable = computed(() =>
const submittable = computed(() => !!(
newValues.content_category === 'podcast'
? newValues.name && newValues.username && newValues.metadata.itunes_category && newValues.metadata.language
: newValues.name && newValues.username
)
))
watch(() => newValues.name, (name) => {
if (creating.value) {
@ -130,7 +139,8 @@ const submit = async () => {
: axios.patch(`channels/${props.object?.uuid}`, payload)
const response = await request()
emit(creating.value ? 'created' : 'updated', response.data)
if (creating.value) emit('created', response.data)
else emit('updated', response.data)
} catch (error) {
errors.value = (error as BackendError).backendErrors
emit('errored', errors.value)

View File

@ -1,18 +1,23 @@
<script setup lang="ts">
import type { BackendError, Channel } from '~/types'
import type { BackendError, BackendResponse, Channel } from '~/types'
import { clone } from 'lodash-es'
import { ref, reactive } from 'vue'
import { clone } from 'lodash-es'
import axios from 'axios'
import ChannelCard from '~/components/audio/ChannelCard.vue'
interface Events {
(e: 'fetched', channels: BackendResponse<Channel>): void
}
interface Props {
filters: object
limit?: number
}
const emit = defineEmits(['fetched'])
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
limit: 5
})

View File

@ -4,10 +4,16 @@ import type { Library } from '~/types'
import { computed } from 'vue'
import { useStore } from '~/store'
interface Events {
(e: 'unfollowed'): void
(e: 'followed'): void
}
interface Props {
library: Library
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const store = useStore()
@ -15,7 +21,6 @@ const follow = computed(() => store.getters['libraries/follow'](props.library.uu
const isPending = computed(() => follow.value && follow.value.approved === null)
const isApproved = computed(() => follow.value && (follow.value?.approved === true || (isPending.value && props.library.privacy_level === 'everyone')))
const emit = defineEmits(['followed', 'unfollowed'])
const toggle = () => {
if (isPending.value || isApproved.value) {
emit('unfollowed')

View File

@ -152,7 +152,6 @@ const openMenu = () => {
<div class="menu">
<button
class="item basic"
data-ref="enqueue"
:disabled="!playable"
:title="labels.addToQueue"
@click.stop.prevent="enqueue"
@ -161,7 +160,6 @@ const openMenu = () => {
</button>
<button
class="item basic"
data-ref="enqueueNext"
:disabled="!playable"
:title="labels.playNext"
@click.stop.prevent="enqueueNext()"
@ -170,7 +168,6 @@ const openMenu = () => {
</button>
<button
class="item basic"
data-ref="playNow"
:disabled="!playable"
:title="labels.playNow"
@click.stop.prevent="enqueueNext(true)"
@ -213,7 +210,6 @@ const openMenu = () => {
<div class="divider" />
<button
v-if="filterableArtist"
data-ref="filterArtist"
class="item basic"
:disabled="!filterableArtist"
:title="labels.hideArtist"
@ -226,7 +222,6 @@ const openMenu = () => {
v-for="obj in getReportableObjects({track, album, artist, playlist, account, channel})"
:key="obj.target.type + obj.target.id"
class="item basic"
:data-ref="`report${obj.target.type}${obj.target.id}`"
@click.stop.prevent="report(obj)"
>
<i class="share icon" /> {{ obj.label }}

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
// TODO (wvffle): Move most of this stufff to usePlayer
import { useStore } from '~/store'
import VolumeControl from './VolumeControl.vue'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'

View File

@ -12,7 +12,7 @@ import { useStore } from '~/store'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
interface Emits {
interface Events {
(e: 'search'): void
}
@ -41,7 +41,7 @@ interface Result {
routerUrl: RouteLocationNamedRaw
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const search = ref()
const { focused } = useFocus(search)

View File

@ -11,6 +11,10 @@ import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport'
import { useVModel } from '@vueuse/core'
interface Events {
(e: 'update:show', value: boolean): void
}
interface Props extends PlayOptionsProps {
track: Track
index: number
@ -30,6 +34,7 @@ interface Props extends PlayOptionsProps {
account?: Actor | null
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
isArtist: false,
isAlbum: false
@ -37,7 +42,6 @@ const props = withDefaults(defineProps<Props>(), {
const modal = ref()
const emit = defineEmits(['update:show'])
const show = useVModel(props, 'show', emit)
const { report, getReportableObjects } = useReport()
@ -280,9 +284,7 @@ const labels = computed(() => ({
<div
v-for="obj in getReportableObjects({ track, album: track.album, artist: track.artist })"
:key="obj.target.type + obj.target.id"
:ref="`report${obj.target.type}${obj.target.id}`"
class="row"
:data-ref="`report${obj.target.type}${obj.target.id}`"
@click.stop.prevent="report(obj)"
>
<div class="column">

View File

@ -11,6 +11,10 @@ import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport'
import { useVModel } from '@vueuse/core'
interface Events {
(e: 'update:show', value: boolean): void
}
interface Props extends PlayOptionsProps {
track: Track
index: number
@ -30,6 +34,7 @@ interface Props extends PlayOptionsProps {
account?: Actor | null
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
isArtist: false,
isAlbum: false
@ -37,7 +42,6 @@ const props = withDefaults(defineProps<Props>(), {
const modal = ref()
const emit = defineEmits(['update:show'])
const show = useVModel(props, 'show', emit)
const { report, getReportableObjects } = useReport()
@ -225,9 +229,7 @@ const labels = computed(() => ({
<div
v-for="obj in getReportableObjects({ track, album: track.album, artist: track.artist })"
:key="obj.target.type + obj.target.id"
:ref="`report${obj.target.type}${obj.target.id}`"
class="row"
:data-ref="`report${obj.target.type}${obj.target.id}`"
@click.stop.prevent="report(obj)"
>
<div class="column">

View File

@ -14,6 +14,11 @@ import TrackRow from '~/components/audio/track/Row.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Events {
(e: 'fetched'): void
(e: 'page-changed', page: number): void
}
interface Props {
tracks?: Track[]
@ -28,7 +33,6 @@ interface Props {
isAlbum?: boolean
isPodcast?: boolean
// TODO (wvffle): Find correct type
filters?: object
nextUrl?: string | null
@ -41,6 +45,7 @@ interface Props {
unique?: boolean
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
tracks: () => [],
@ -95,8 +100,6 @@ const labels = computed(() => ({
artist: $pgettext('*/*/*/Noun', 'Artist')
}))
const emit = defineEmits(['fetched', 'page-changed'])
const isLoading = ref(false)
const fetchData = async () => {
isLoading.value = true
@ -255,7 +258,7 @@ const updatePage = (page: number) => {
:total="totalTracks"
:current=" tracks.length > 0 ? page : currentPage"
:paginate-by="paginateBy"
@page-changed="updatePage"
@update:current="updatePage"
/>
</div>
</div>
@ -295,7 +298,7 @@ const updatePage = (page: number) => {
:total="totalTracks"
:current="tracks.length > 0 ? page : currentPage"
:compact="true"
@page-changed="updatePage"
@update:current="updatePage"
/>
</div>
</div>

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import type { Track, Listening } from '~/types'
// TODO (wvffle): Fix websocket update (#1534)
import { ref, reactive, watch } from 'vue'
import { useStore } from '~/store'
import { clone } from 'lodash-es'
@ -14,7 +13,7 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'count', count: number): void
}
@ -28,7 +27,7 @@ interface Props {
websocketHandlers?: string[]
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
isActivity: true,
showCount: false,

View File

@ -9,7 +9,7 @@ import { uniq } from 'lodash-es'
import useScopes from '~/composables/auth/useScopes'
interface Emits {
interface Events {
(e: 'updated', application: Application): void
(e: 'created', application: Application): void
}
@ -19,7 +19,7 @@ interface Props {
defaults?: Partial<Application>
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
app: null,
defaults: () => ({})

View File

@ -315,9 +315,9 @@ fetchOwnedApps()
:key="f.id"
class="field"
>
<label :for="f.id">{{ sharedLabels.fields[f.id].label }}</label>
<p v-if="sharedLabels.fields[f.id].help">
{{ sharedLabels.fields[f.id].help }}
<label :for="f.id">{{ sharedLabels.fields[f.id as FieldId].label }}</label>
<p v-if="sharedLabels.fields[f.id as FieldId].help">
{{ sharedLabels.fields[f.id as FieldId].help }}
</p>
<select
v-if="f.type === 'dropdown'"
@ -330,7 +330,7 @@ fetchOwnedApps()
:key="key"
:value="c"
>
{{ sharedLabels.fields[f.id].choices[c] }}
{{ sharedLabels.fields[f.id as FieldId].choices[c] }}
</option>
</select>
<content-form

View File

@ -4,7 +4,7 @@ import type { BackendError, Channel } from '~/types'
import { computed, watch, ref } from 'vue'
import axios from 'axios'
interface Emits {
interface Events {
(e: 'submittable', value: boolean): void
(e: 'loading', value: boolean): void
(e: 'created'): void
@ -14,7 +14,7 @@ interface Props {
channel: Channel
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const title = ref('')

View File

@ -5,7 +5,7 @@ import axios from 'axios'
import { useVModel } from '@vueuse/core'
import { reactive, ref, watch } from 'vue'
interface Emits {
interface Events {
(e: 'update:modelValue', value: string): void
}
@ -14,7 +14,7 @@ interface Props {
channel: Channel | null
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
modelValue: null,
channel: null

View File

@ -5,7 +5,7 @@ import { computed, reactive, ref } from 'vue'
import axios from 'axios'
import { useVModel } from '@vueuse/core'
interface Emits {
interface Events {
(e: 'update:modelValue', value: string): void
}
@ -13,7 +13,7 @@ interface Props {
modelValue: string | null
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
modelValue: null
})

View File

@ -7,7 +7,7 @@ import { computed } from 'vue'
import LoginModal from '~/components/common/LoginModal.vue'
interface Emits {
interface Events {
(e: 'unsubscribed'): void
(e: 'subscribed'): void
}
@ -16,7 +16,7 @@ interface Props {
channel: Channel
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { $pgettext } = useGettext()

View File

@ -18,7 +18,7 @@ import AlbumSelect from '~/components/channels/AlbumSelect.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'status', status: UploadStatus): void
(e: 'step', step: 1 | 2 | 3): void
}
@ -47,7 +47,7 @@ interface UploadedFile extends VueUploadItem {
metadata: Record<string, string>
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
channel: null
})
@ -55,7 +55,6 @@ const props = withDefaults(defineProps<Props>(), {
const { $pgettext } = useGettext()
const store = useStore()
// TODO (wvffle): Find types in UploadMetadataForm.vue
const errors = ref([] as string[])
const values = reactive({

View File

@ -6,8 +6,7 @@ import { ref, computed, watch } from 'vue'
import TagsSelector from '~/components/library/TagsSelector.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue'
interface Emits {
// TODO (wvffle): Find correct type
interface Events {
(e: 'values', values: Record<string, string>): void
}
@ -16,13 +15,12 @@ interface Props {
values?: Record<string, string> | null
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
values: null
})
// TODO (wvffle): This is something like a Track, but `cover` is a plain uuid
const newValues = ref({ ...(props.values ?? props.upload.import_metadata) } as any)
const newValues = ref({ ...(props.values ?? props.upload.import_metadata) } as Record<string, string>)
// computed: {
// isLoading () {

View File

@ -16,14 +16,14 @@ interface Action {
filterChackable?: (item: never) => boolean
}
interface Emits {
interface Events {
(e: 'action-launched', data: any): void
(e: 'refresh'): void
}
interface Props {
objectsData: { results: [], count: number }
actions: [Action]
actions: Action[]
actionUrl: string
idField?: string
refreshable?: boolean
@ -32,7 +32,7 @@ interface Props {
customObjects?: Record<string, unknown>[]
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
idField: 'id',
refreshable: false,

View File

@ -3,10 +3,9 @@ import type { BackendError } from '~/types'
import { ref } from 'vue'
// TODO (wvffle): Remove this component
import axios from 'axios'
interface Emits {
interface Events {
(e: 'action-done', data: any): void
(e: 'action-error', error: BackendError): void
}
@ -16,7 +15,7 @@ interface Props {
url: string
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const isLoading = ref(false)

View File

@ -7,6 +7,11 @@ import { reactive, ref, watch } from 'vue'
import { useStore } from '~/store'
import useFormData from '~/composables/useFormData'
interface Events {
(e: 'update:modelValue', value: string | null): void
(e: 'delete'): void
}
interface Props {
modelValue: string | null
imageClass?: string
@ -15,6 +20,7 @@ interface Props {
initialValue?: string | undefined
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
imageClass: '',
required: false,
@ -22,7 +28,6 @@ const props = withDefaults(defineProps<Props>(), {
initialValue: undefined
})
const emit = defineEmits(['update:modelValue', 'delete'])
const value = useVModel(props, 'modelValue', emit)
const attachment = ref()

View File

@ -1,12 +1,16 @@
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
interface Events {
(e: 'update:modelValue', value: boolean): void
}
interface Props {
modelValue: boolean
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const value = useVModel(props, 'modelValue', emit)
</script>

View File

@ -4,7 +4,7 @@ import { useVModel, watchDebounced, useTextareaAutosize, syncRef } from '@vueuse
import { ref, computed, watchEffect, onMounted, nextTick } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Emits {
interface Events {
(e: 'update:modelValue', value: string): void
}
@ -17,7 +17,7 @@ interface Props {
charLimit?: number
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
placeholder: undefined,
autofocus: false,

View File

@ -2,16 +2,17 @@
import SemanticModal from '~/components/semantic/Modal.vue'
import { ref } from 'vue'
interface Events {
(e: 'confirm'): void
}
interface Props {
action?: () => void
disabled?: boolean
// TODO (wvffle): Find correct type
confirmColor?: 'danger'
confirmColor?: 'danger' | 'success'
}
// TODO (wvffle): MOVE ALL defineEmits ABOVE defineProps
const emit = defineEmits()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
action: () => {},
disabled: false,
@ -30,7 +31,7 @@ const confirm = () => {
<template>
<button
:class="[{disabled: disabled}]"
:disabled="disabled || null"
:disabled="disabled"
@click.prevent.stop="showModal = true"
>
<slot />

View File

@ -3,16 +3,21 @@ import { useVModel } from '@vueuse/core'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Events {
(e: 'update:modelValue', value: string): void
(e: 'search', query: string): void
}
interface Props {
modelValue: string
placeholder?: string
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
placeholder: ''
})
const emit = defineEmits(['update:modelValue', 'search'])
const value = useVModel(props, 'modelValue', emit)
const { $pgettext } = useGettext()

View File

@ -7,7 +7,7 @@ import { whenever } from '@vueuse/core'
import axios from 'axios'
import clip from 'text-clipper'
interface Emits {
interface Events {
(e: 'updated', data: unknown): void
}
@ -21,7 +21,7 @@ interface Props {
truncateLength?: number
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
content: null,
fieldName: 'description',

View File

@ -5,11 +5,11 @@ import useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme'
import { computed } from 'vue'
interface Emits {
interface Events {
(e: 'show:shortcuts-modal'): void
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const { $pgettext } = useGettext()
const themes = useThemeList()

View File

@ -6,11 +6,17 @@ import { useVModel } from '@vueuse/core'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Events {
(e: 'update:show', value: boolean): void
(e: 'showLanguageModalEvent'): void
(e: 'showThemeModalEvent'): void
}
interface Props {
show: boolean
}
const emit = defineEmits(['update:show', 'showThemeModalEvent', 'showLanguageModalEvent'])
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const show = useVModel(props, 'show', emit)
@ -53,8 +59,8 @@ const labels = computed(() => ({
class="header"
>
<img
v-if="$store.state.auth.profile.avatar && $store.state.auth.profile.avatar.urls.medium_square_crop"
v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile.avatar.urls.medium_square_crop)"
v-if="$store.state.auth.profile?.avatar && $store.state.auth.profile?.avatar.urls.medium_square_crop"
v-lazy="$store.getters['instance/absoluteUrl']($store.state.auth.profile?.avatar.urls.medium_square_crop)"
alt=""
class="ui centered small circular image"
>
@ -80,7 +86,7 @@ const labels = computed(() => ({
<div
class="column"
role="button"
@click="[$emit('update:show', false), $emit('showLanguageModalEvent')]"
@click="[$emit('update:show', false), emit('showLanguageModalEvent')]"
>
<i class="language icon user-modal list-icon" />
<span class="user-modal list-item">{{ labels.language }}:</span>
@ -94,12 +100,12 @@ const labels = computed(() => ({
<div
class="column"
role="button"
@click="[$emit('update:show', false), $emit('showThemeModalEvent')]"
@click="[$emit('update:show', false), emit('showThemeModalEvent')]"
>
<i class="palette icon user-modal list-icon" />
<span class="user-modal list-item">{{ labels.theme }}:</span>
<div class="right floated">
<span class="user-modal list-item"> {{ themes.find(x => x.key === theme).name }}</span>
<span class="user-modal list-item"> {{ themes.find(x => x.key === theme)?.name }}</span>
<i class="action-hint chevron right icon user-modal" />
</div>
</div>

View File

@ -33,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const store = useStore()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [

View File

@ -9,7 +9,7 @@ import LibraryCard from '~/views/content/remote/Card.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'loaded', libraries: Library[]): void
}
@ -17,7 +17,7 @@ interface Props {
url: string
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const nextPage = ref()

View File

@ -4,6 +4,10 @@ import { useGettext } from 'vue3-gettext'
import { useClipboard, useVModel } from '@vueuse/core'
import { useStore } from '~/store'
interface Events {
(e: 'update:modelValue', value: string): void
}
interface Props {
modelValue: string
defaultShow?: boolean
@ -11,12 +15,12 @@ interface Props {
fieldId: string
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
defaultShow: false,
copyButton: false
})
const emit = defineEmits(['update:modelValue'])
const value = useVModel(props, 'modelValue', emit)
const showPassword = ref(props.defaultShow)

View File

@ -16,10 +16,15 @@ import AlbumDropdown from './AlbumDropdown.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Events {
(e: 'deleted'): void
}
interface Props {
id: string
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const object = ref<Album | null>(null)
@ -81,7 +86,6 @@ const fetchData = async () => {
watch(() => props.id, fetchData, { immediate: true })
watch(page, fetchData)
const emit = defineEmits(['deleted'])
const router = useRouter()
const remove = async () => {
isLoading.value = true

View File

@ -6,7 +6,7 @@ import ChannelEntries from '~/components/audio/ChannelEntries.vue'
import TrackTable from '~/components/audio/track/Table.vue'
import PlayButton from '~/components/audio/PlayButton.vue'
interface Emits {
interface Events {
(e: 'page-changed', page: number): void
(e: 'libraries-loaded', libraries: Library[]): void
}
@ -23,7 +23,7 @@ interface Props {
totalTracks: number
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
defineProps<Props>()
const getDiscKey = (disc: Track[]) => disc.map(track => track.id).join('|')

View File

@ -9,6 +9,10 @@ import { useGettext } from 'vue3-gettext'
import { getDomain } from '~/utils'
interface Events {
(e: 'remove'): void
}
interface Props {
isLoading: boolean
artist: Artist | null
@ -19,6 +23,7 @@ interface Props {
isSerie: boolean
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { report, getReportableObjects } = useReport()
@ -35,7 +40,6 @@ const isEmbedable = computed(() => (props.isChannel && props.artist?.channel?.ac
const musicbrainzUrl = computed(() => props.object?.mbid ? `https://musicbrainz.org/release/${props.object.mbid}` : null)
const discogsUrl = computed(() => `https://discogs.com/search/?type=release&title=${encodeURI(props.object?.title)}&artist=${encodeURI(props.object?.artist.name)}`)
const emit = defineEmits(['remove'])
const remove = () => emit('remove')
</script>

View File

@ -37,7 +37,6 @@ const props = withDefaults(defineProps<Props>(), {
scope: 'all'
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -37,7 +37,6 @@ const props = withDefaults(defineProps<Props>(), {
scope: 'all'
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -13,7 +13,7 @@ import axios from 'axios'
import useEditConfigs from '~/composables/moderation/useEditConfigs'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'approved', isApproved: boolean): void
(e: 'deleted'): void
}
@ -23,7 +23,7 @@ interface Props {
currentState?: ReviewState
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
currentState: () => ({})
})
@ -41,7 +41,6 @@ const canDelete = computed(() => {
if (props.obj.is_applied || props.obj.is_approved) return false
if (!store.state.auth.authenticated) return false
// TODO (wvffle): Is it better to compare ids? Is full_username unique?
return props.obj.created_by.full_username === store.state.auth.fullUsername
|| store.state.auth.availablePermissions.library
})

View File

@ -38,7 +38,6 @@ const canEdit = computed(() => {
if (!store.state.auth.authenticated) return false
const isOwner = props.object.attributed_to
// TODO (wvffle): Is it better to compare ids? Is full_username unique?
&& store.state.auth.fullUsername === props.object.attributed_to.full_username
return isOwner || store.state.auth.availablePermissions.library

View File

@ -20,7 +20,7 @@ import useWebSocketHandler from '~/composables/useWebSocketHandler'
import updateQueryString from '~/composables/updateQueryString'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'uploads-finished', delta: number):void
}
@ -29,7 +29,7 @@ interface Props {
defaultImportReference?: string
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
defaultImportReference: ''
})

View File

@ -3,14 +3,19 @@ import type { FileSystem, FSEntry } from '~/types'
import { useVModel } from '@vueuse/core'
interface Events {
(e: 'update:modelValue', value: string[]): void
(e: 'import'): void
}
interface Props {
data: FileSystem
loading: boolean
modelValue: string[]
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'import'])
const value = useVModel(props, 'modelValue', emit)
const handleClick = (entry: FSEntry) => {

View File

@ -10,12 +10,16 @@ interface ErrorEntry {
value: string
}
interface Events {
(e: 'update:show', value: boolean): void
}
interface Props {
upload: Upload
show: boolean
}
const emit = defineEmits(['update:show'])
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const show = useVModel(props, 'show', emit)

View File

@ -40,7 +40,6 @@ const props = withDefaults(defineProps<Props>(), {
scope: 'all'
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -34,7 +34,6 @@ const props = withDefaults(defineProps<Props>(), {
scope: 'all'
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -7,7 +7,7 @@ import { useStore } from '~/store'
import $ from 'jquery'
interface Emits {
interface Events {
(e: 'update:modelValue', tags: Tag[]): void
}
@ -15,7 +15,7 @@ interface Props {
modelValue: Tag[]
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const store = useStore()

View File

@ -21,11 +21,17 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
import useLogger from '~/composables/useLogger'
interface Events {
(e: 'deleted'): void
}
interface Props {
id: string
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { report, getReportableObjects } = useReport()
const track = ref<Track | null>(null)
@ -107,7 +113,6 @@ const fetchData = async () => {
watch(() => props.id, fetchData, { immediate: true })
const emit = defineEmits(['deleted'])
const remove = async () => {
isLoading.value = true
try {

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
// TODO (wvffle): SORT IMPORTS LIKE SO EVERYWHERE
import type { Track } from '~/types'
import type { BuilderFilter, FilterConfig } from './Builder.vue'
import type { Track } from '~/types'
import axios from 'axios'
import $ from 'jquery'
@ -16,6 +16,14 @@ import TrackTable from '~/components/audio/track/Table.vue'
import useErrorHandler from '~/composables/useErrorHandler'
type Filter = { candidates: { count: number, sample: Track[] } }
type ResponseType = { filters: Array<Filter> }
interface Events {
(e: 'update-config', index: number, name: string, value: number[]): void
(e: 'delete', index: number): void
}
interface Props {
index: number
@ -23,10 +31,7 @@ interface Props {
config: FilterConfig
}
type Filter = { candidates: { count: number, sample: Track[] } }
type ResponseType = { filters: Array<Filter> }
const emit = defineEmits(['update-config', 'delete'])
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const store = useStore()
@ -127,7 +132,6 @@ watch(exclude, fetchCandidates)
<div
v-for="f in filter.fields"
:key="f.name"
:ref="f.name"
class="ui field"
>
<div :class="['ui', 'search', 'selection', 'dropdown', {'autocomplete': f.autocomplete}, {'multiple': f.type === 'list'}]">

View File

@ -17,7 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -32,7 +31,6 @@ const props = withDefaults(defineProps<Props>(), {
filters: () => ({})
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)
@ -47,8 +45,6 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
]
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
// TODO (wvffle): Find correct type
const actions: unknown[] = []
const isLoading = ref(false)
const fetchData = async () => {
@ -176,7 +172,7 @@ const labels = computed(() => ({
<action-table
v-if="result"
:objects-data="result"
:actions="actions"
:actions="[]"
action-url="manage/library/artists/action/"
:filters="actionFilters"
@action-launched="fetchData"

View File

@ -17,8 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): Remove from EVERY SINGLE component that does not use it at all
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -35,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -17,7 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -34,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -20,7 +20,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -38,7 +37,6 @@ const props = withDefaults(defineProps<Props>(), {
const configs = useEditConfigs()
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type StateTarget = { id: number, type: keyof typeof targets }

View File

@ -17,7 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -34,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -19,7 +19,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -36,7 +35,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -17,7 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -34,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -20,7 +20,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -37,7 +36,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -17,7 +17,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -34,7 +33,6 @@ const props = withDefaults(defineProps<Props>(), {
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -16,7 +16,6 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering'
interface Props extends OrderingProps {
// TODO (wvffle): find object type
filters?: object
allowListEnabled?: boolean
@ -29,7 +28,6 @@ const props = withDefaults(defineProps<Props>(), {
allowListEnabled: false
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -7,7 +7,7 @@ import { useGettext } from 'vue3-gettext'
import axios from 'axios'
interface Emits {
interface Events {
(e: 'save', data: InstancePolicy): void
(e: 'delete'): void
(e: 'cancel'): void
@ -19,7 +19,7 @@ interface Props {
object?: InstancePolicy | null
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
object: null
})

View File

@ -5,7 +5,7 @@ import axios from 'axios'
import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Emits {
interface Events {
(e: 'created', note: Note): void
}
@ -13,7 +13,7 @@ interface Props {
target: Note
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { $pgettext } = useGettext()

View File

@ -8,13 +8,16 @@ import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
interface Events {
(e: 'deleted', uuid: string): void
}
interface Props {
notes: Note[]
}
const emit = defineEmits<Events>()
defineProps<Props>()
const emit = defineEmits(['deleted'])
const isLoading = ref(false)
const remove = async (note: Note) => {
isLoading.value = true

View File

@ -16,7 +16,7 @@ import useReportConfigs from '~/composables/moderation/useReportConfigs'
import useErrorHandler from '~/composables/useErrorHandler'
import useMarkdown from '~/composables/useMarkdown'
interface Emits {
interface Events {
(e: 'updated', updating: { type: string }): void
(e: 'handled', isHandled: boolean): void
}
@ -25,7 +25,7 @@ interface Props {
initObj: Report
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const configs = useReportConfigs()

View File

@ -11,7 +11,7 @@ import NoteForm from '~/components/manage/moderation/NoteForm.vue'
import useErrorHandler from '~/composables/useErrorHandler'
interface Emits {
interface Events {
(e: 'handled', status: UserRequestStatus): void
}
@ -19,7 +19,7 @@ interface Props {
initObj: UserRequest
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const store = useStore()

View File

@ -17,7 +17,6 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering'
interface Props extends OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -28,7 +27,6 @@ const props = withDefaults(defineProps<Props>(), {
filters: () => ({})
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)

View File

@ -16,7 +16,6 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useOrdering from '~/composables/useOrdering'
interface Props extends OrderingProps {
// TODO (wvffle): find object type
filters?: object
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -27,7 +26,6 @@ const props = withDefaults(defineProps<Props>(), {
filters: () => ({})
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
const query = ref('')
type ResponseType = { count: number, results: any[] }
@ -58,8 +56,6 @@ const permissions = computed(() => [
const { $pgettext } = useGettext()
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
// TODO (wvffle): Find correct type
const actions: unknown[] = []
const isLoading = ref(false)
const fetchData = async () => {
@ -158,7 +154,7 @@ const labels = computed(() => ({
<action-table
v-if="result"
:objects-data="result"
:actions="actions"
:actions="[]"
:action-url="'manage/library/uploads/action/'"
:filters="actionFilters"
@action-launched="fetchData"

View File

@ -4,6 +4,10 @@ import { useGettext } from 'vue3-gettext'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import { useVModel } from '@vueuse/core'
interface Events {
(e: 'update:modelValue', value: string): void
}
interface Props {
modelValue: string
all?: boolean
@ -13,6 +17,7 @@ interface Props {
restrictTo?: string[] // TODO (wvffle): Make sure its string list
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
all: false,
label: false,
@ -21,7 +26,6 @@ const props = withDefaults(defineProps<Props>(), {
restrictTo: () => []
})
const emit = defineEmits(['update:modelValue'])
const value = useVModel(props, 'modelValue', emit)
const { $pgettext } = useGettext()

View File

@ -94,16 +94,12 @@ const handleAction = (handler?: () => void) => {
}
const approveLibraryFollow = async (follow: LibraryFollow) => {
follow.isLoading = true
await axios.post(`federation/follows/library/${follow.uuid}/accept/`)
follow.isLoading = false
follow.approved = true
}
const rejectLibraryFollow = async (follow: LibraryFollow) => {
follow.isLoading = true
await axios.post(`federation/follows/library/${follow.uuid}/reject/`)
follow.isLoading = false
follow.approved = false
}
</script>

View File

@ -1,23 +1,31 @@
<script setup lang="ts">
import type { Playlist, Track, PlaylistTrack, BackendError, APIErrorResponse } from '~/types'
import { useStore } from '~/store'
import { useGettext } from 'vue3-gettext'
import { computed, ref } from 'vue'
import axios from 'axios'
import PlaylistForm from '~/components/playlists/Form.vue'
import draggable from 'vuedraggable'
import { useVModels } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useStore } from '~/store'
import draggable from 'vuedraggable'
import axios from 'axios'
import PlaylistForm from '~/components/playlists/Form.vue'
import useQueue from '~/composables/audio/useQueue'
interface Events {
(e: 'update:playlistTracks', value: PlaylistTrack[]): void
(e: 'update:playlist', value: Playlist): void
}
interface Props {
playlist: Playlist | null
playlistTracks: PlaylistTrack[]
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const emit = defineEmits(['update:playlist', 'update:playlistTracks'])
const { playlistTracks, playlist } = useVModels(props, emit)
const errors = ref([] as string[])
@ -72,8 +80,6 @@ const responseHandlers = {
return this.errored(error)
}
// TODO (wvffle): Test if it works
// if (errors.length === 1 && errors[0].code === 'tracks_already_exist_in_playlist') {
if (backendErrors.length === 1 && backendErrors[0] === 'Tracks already exist in playlist') {
duplicateTrackAddInfo.value = rawPayload ?? null
showDuplicateTrackAddConfirmation.value = true

View File

@ -1,14 +1,20 @@
<script setup lang="ts">
import type { Playlist, PrivacyLevel, BackendError } from '~/types'
import $ from 'jquery'
import axios from 'axios'
import { useVModels, useCurrentElement } from '@vueuse/core'
import { ref, computed, onMounted, nextTick } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useStore } from '~/store'
import { ref, computed, onMounted, nextTick } from 'vue'
import useLogger from '~/composables/useLogger'
import axios from 'axios'
import $ from 'jquery'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import useLogger from '~/composables/useLogger'
interface Events {
(e: 'update:playlist', value: Playlist): void
}
interface Props {
title?: boolean
@ -16,13 +22,13 @@ interface Props {
playlist?: Playlist | null
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
title: true,
create: false,
playlist: null
})
const emit = defineEmits(['update:playlist'])
const { playlist } = useVModels(props, emit)
const logger = useLogger()

View File

@ -5,6 +5,14 @@ import { computed, onBeforeUnmount, ref, watchEffect } from 'vue'
import { useVModel } from '@vueuse/core'
import { useStore } from '~/store'
interface Events {
(e: 'update:show', show: boolean): void
(e: 'approved'): void
(e: 'deny'): void
(e: 'show'): void
(e: 'hide'): void
}
interface Props {
show: boolean
fullscreen?: boolean
@ -12,14 +20,13 @@ interface Props {
additionalClasses?: string[]
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
fullscreen: true,
scrolling: false,
additionalClasses: () => []
})
const emit = defineEmits(['approved', 'deny', 'update:show', 'show', 'hide'])
const modal = ref()
const { activate, deactivate, pause, unpause } = useFocusTrap(modal, {
allowOutsideClick: true

View File

@ -4,6 +4,10 @@ import { range, clamp } from 'lodash-es'
import { computed } from 'vue'
import { useGettext } from 'vue3-gettext'
interface Events {
(e: 'update:current', page: number): void
}
interface Props {
current?: number
paginateBy?: number
@ -11,13 +15,13 @@ interface Props {
compact?: boolean
}
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
current: 1,
paginateBy: 25,
compact: false
})
const emit = defineEmits(['update:current', 'pageChanged'])
const current = useVModel(props, 'current', emit)
const RANGE = 2
@ -47,8 +51,6 @@ const setPage = (page: number) => {
}
current.value = page
// TODO (wvffle): Compat before change to v-model
emit('pageChanged', page)
}
const { $pgettext } = useGettext()

View File

@ -7,7 +7,7 @@ import { ref, watchEffect, reactive } from 'vue'
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
interface Emits {
interface Events {
(e: 'reorder', from: number, to: number): void
(e: 'visible'): void
(e: 'hidden'): void
@ -18,7 +18,7 @@ interface Props {
size: number
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const ghostContainer = ref()

View File

@ -23,8 +23,6 @@ export interface PlayOptionsProps {
}
export default (props: PlayOptionsProps) => {
// TODO (wvffle): Test if we can defineProps in composable
const store = useStore()
const { resume, pause, playing } = usePlayer()
const { currentTrack } = useQueue()
@ -95,7 +93,7 @@ export default (props: PlayOptionsProps) => {
const tracks: Track[] = []
// TODO (wvffle): There is no channel?
// TODO (wvffle): Why is there no channel?
if (props.tracks?.length) {
tracks.push(...props.tracks)
} else if (props.track) {
@ -126,7 +124,6 @@ export default (props: PlayOptionsProps) => {
tracks.push(...await getTracksPage({ library: props.library.uuid, ordering: '-creation_date' }))
}
// TODO (wvffle): It was behind 250ms timeout, why?
isLoading.value = false
return tracks.filter(track => track.uploads?.length).map(markRaw)

View File

@ -16,7 +16,6 @@ export default (defaultQuery: MaybeRef<string>, updateUrl: MaybeRef<boolean>) =>
const tokens = ref([] as Token[])
watch(query, (value) => {
// TODO (wvffle): Move normalizeQuery and parseTokens into the composable file
tokens.value = parseTokens(normalizeQuery(value))
}, { immediate: true })

View File

@ -9,8 +9,6 @@ import useTheme from '~/composables/useTheme'
// NOTE: Set the theme as fast as possible
useTheme()
// TODO (wvffle): Make sure V_FOR_REF works
// Search pattern: v-for([^>]|\n)+?[^h]ref
const logger = useLogger()
logger.info('Loading environment:', import.meta.env.MODE)
logger.debug('Environment variables:', import.meta.env)
@ -49,11 +47,4 @@ Promise.all(modules).finally(() => {
})
// TODO (wvffle): Rename filters from useSharedLabels to filters from backend
// TODO (wvffle): Check for mixin merging: https://v3-migration.vuejs.org/breaking-changes/data-option.html#mixin-merge-behavior-change=
// TODO (wvffle): Use emits options: https://v3-migration.vuejs.org/breaking-changes/emits-option.html
// TODO (wvffle): Find all array watchers and make them deep
// TODO (wvffle): Migrate to <script setup lang="ts"> and remove allowJs from tsconfig.json
// TODO (wvffle): Replace `from '(../)+` with `from '~/`
// TODO (wvffle): Fix props not being available in template in IntelliJ Idea
// TODO (wvffle): Use navigation guards
// TODO (wvffle): Use computedEager whenever there is a cheap operation that can be executed eagerly
// TODO (wvffle): Migrate EmbedFrame.vue to <script setup lang="ts"> and remove allowJs from tsconfig.json

View File

@ -1,13 +1,12 @@
import type { RouteRecordRaw } from 'vue-router'
import { requireLoggedOut, requireLoggedIn } from '../guards'
import { requireLoggedOut, requireLoggedIn } from '~/router/guards'
export default [
{
path: '/login',
name: 'login',
component: () => import('~/views/auth/Login.vue'),
// TODO (wvffle): Use named routes EVERYWHERE
props: route => ({ next: route.query.next || '/library' }),
beforeEnter: requireLoggedOut({ name: 'library.index' })
},

View File

@ -7,7 +7,7 @@ import manage from './manage'
import store from '~/store'
import auth from './auth'
import user from './user'
import { requireLoggedIn } from '../guards'
import { requireLoggedIn } from '~/router/guards'
export default [
{

View File

@ -246,7 +246,7 @@ export default [
// browse a single library via it's uuid
path: ':id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})',
props: true,
component: () => import('~/views/library/DetailBase.vue'),
component: () => import('~/views/library/LibraryBase.vue'),
children: [
{
path: '',

View File

@ -9,13 +9,13 @@ export interface State {
frontSettings: FrontendSettings
instanceUrl?: string
knownInstances: string[]
nodeinfo: unknown | null // TODO (wvffle): Get nodeinfo type from swagger automatically
nodeinfo: unknown | null
settings: Settings
}
interface FrontendSettings {
defaultServerUrl: string
additionalStylesheets: string[] // TODO (wvffle): Ensure it's not nullable
additionalStylesheets: string[]
}
interface InstanceSettings {

View File

@ -19,13 +19,11 @@ export interface CurrentRadio {
clientOnly: boolean
session: null
type: 'account'
// TODO (wvffle): Find correct type
customRadioId: unknown
customRadioId: number
config: RadioConfig
objectId: ObjectId | null
}
// TODO (wvffle): Find correct type
export type RadioConfig = { type: 'tag', names: string[] } | { type: 'artist', ids: string[] }
export interface PopulateQueuePayload {

View File

@ -24,7 +24,6 @@ export interface QueueItemSource {
duration: string
coverUrl: string
// TODO (wvffle): Maybe use <translate> component to avoid passing the labels
labels: {
remove: string
selectTrack: string
@ -53,6 +52,7 @@ export interface Artist {
description: Content
cover?: Cover
channel?: Channel
// TODO (wvffle): Check if it's Tag[] or string[]
tags: string[]
content_category: ContentCategory
@ -164,9 +164,6 @@ export interface LibraryFollow {
name: string
type?: 'music.Library' | 'federation.LibraryFollow'
target: Library
// TODO (wvffle): Check if it's not added only on frontend side
isLoading?: boolean
}
export interface Cover {
@ -225,6 +222,11 @@ export interface BackendError extends AxiosError {
rawPayload?: APIErrorResponse
}
export interface BackendResponse<T> {
count: number
results: T[]
}
export interface RateLimitStatus {
limit: string
scope: string
@ -251,14 +253,14 @@ export interface FileSystem {
export interface FSLogs {
status: 'pending' | 'started'
reference: unknown // TODO (wvffle): Find correct type
reference: unknown
logs: string[]
}
// Content stuff
export interface Content {
content_type: 'text/plain' | 'text/markdown'
text: string // TODO (wvffle): Ensure it's not nullable from backend side
text: string
}
// Form stuff
@ -311,8 +313,7 @@ export interface User {
id: string
avatar?: Cover
email: string
// TODO (wvffle): Is it really a string? Or maybe it's { text: string, content_type: string }
summary: string
summary: { text: string, content_type: string }
username: string
full_username: string
instance_support_message_display_date: string
@ -361,7 +362,7 @@ export interface SettingsDataEntry {
export interface Note {
uuid: string
type: 'request' | 'report'
author?: Actor // TODO (wvffle): Check if is valid
author?: Actor
summary?: string
creation_date?: string
}

View File

@ -51,7 +51,6 @@ const paginateBy = ref(25)
const { $pgettext } = useGettext()
// TODO (wvffle): Check if can rename to Category
interface SearchType {
id: QueryType
label: string
@ -132,7 +131,6 @@ const updateQueryString = () => router.replace({
}
})
// TODO (wvffle): Debounce all `fetchData` functions
const isLoading = ref(false)
const search = async () => {
if (!query.value) {
@ -358,10 +356,9 @@ const radioConfig = computed(() => {
<pagination
v-if="currentResults && currentResults.count > paginateBy"
:current="page"
v-model:current="page"
:paginate-by="paginateBy"
:total="currentResults.count"
@page-changed="page = $event"
/>
</div>
</section>

View File

@ -19,7 +19,6 @@ import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find more types
mode?: 'card'
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged

View File

@ -18,10 +18,15 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
interface Events {
(e: 'deleted'): void
}
interface Props {
id: string
}
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { report, getReportableObjects } = useReport()
const store = useStore()
@ -100,7 +105,6 @@ watchEffect(() => {
}
})
const emit = defineEmits(['deleted'])
const remove = async () => {
isLoading.value = true
try {

View File

@ -20,11 +20,13 @@ import useErrorHandler from '~/composables/useErrorHandler'
import useSmartSearch from '~/composables/useSmartSearch'
import useOrdering from '~/composables/useOrdering'
interface Events {
(e: 'fetch-start'): void
}
interface Props extends SmartSearchProps, OrderingProps {
// TODO (wvffle): find object type
filters?: object
needsRefresh?: boolean
// TODO (wvffle): find object type
customObjects?: any[]
// TODO(wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
@ -33,8 +35,7 @@ interface Props extends SmartSearchProps, OrderingProps {
updateUrl?: boolean
}
const search = ref()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
defaultQuery: '',
updateUrl: false,
@ -43,6 +44,8 @@ const props = withDefaults(defineProps<Props>(), {
customObjects: () => []
})
const search = ref()
// TODO (wvffle): Make sure everything is it's own type
const page = ref(1)
type ResponseType = { count: number, results: any[] }
@ -83,8 +86,6 @@ const actions = computed(() => [
}
])
const emit = defineEmits(['fetch-start'])
const isLoading = ref(false)
const fetchData = async () => {
emit('fetch-start')

View File

@ -11,7 +11,7 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Emits {
interface Events {
(e: 'updated', data: Library): void
(e: 'created', data: Library): void
(e: 'deleted'): void
@ -21,7 +21,7 @@ interface Props {
library?: Library
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const props = defineProps<Props>()
const { $pgettext } = useGettext()

View File

@ -13,6 +13,10 @@ import RadioButton from '~/components/radios/Button.vue'
import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
interface Emits {
(e: 'followed'): void
}
interface Props {
initialLibrary: Library
displayFollow?: boolean
@ -20,6 +24,7 @@ interface Props {
displayCopyFid?: boolean
}
const emit = defineEmits<Emits>()
const props = withDefaults(defineProps<Props>(), {
displayFollow: true,
displayScan: true,
@ -68,7 +73,6 @@ const launchScan = async () => {
}
}
const emit = defineEmits(['followed', 'deleted'])
const follow = async () => {
isLoadingFollow.value = true
try {
@ -76,7 +80,7 @@ const follow = async () => {
library.value.follow = response.data
emit('followed')
} catch (error) {
// TODO (wvffle): ==> CORRECTLY HANDLED ERROR HERE <==
console.error(error)
store.commit('ui/addMessage', {
// TODO (wvffle): Translate
content: 'Cannot follow remote library: ' + error,

View File

@ -6,11 +6,11 @@ import { computed, ref } from 'vue'
import axios from 'axios'
interface Emits {
interface Events {
(e: 'scanned', data: object): void
}
const emit = defineEmits<Emits>()
const emit = defineEmits<Events>()
const { $pgettext } = useGettext()

View File

@ -35,18 +35,13 @@ const fetchData = async () => {
fetchData()
// TODO (wvffle): Find correct type
const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
follow.isLoading = true
try {
await axios.post(`federation/follows/library/${follow.uuid}/${approved ? 'accept' : 'reject'}/`)
follow.approved = approved
} catch (error) {
useErrorHandler(error as Error)
}
follow.isLoading = false
}
</script>
@ -85,7 +80,7 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
</div>
</div>
<table
v-else-if="follows?.count > 0"
v-else-if="(follows ?? { count: 0 }).count > 0"
class="ui table"
>
<thead>

View File

@ -1,5 +1,4 @@
<script setup lang="ts">
// TODO (wvffle): Rename to LibraryBase
import type { Library } from '~/types'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'

View File

@ -32,7 +32,6 @@ const props = withDefaults(defineProps<Props>(), {
scope: 'all'
})
// TODO (wvffle): Make sure everything is it's own type
const page = ref(+props.defaultPage)
type ResponseType = { count: number, results: any[] }
const result = ref<null | ResponseType>(null)