chore(front): replace semantic-modal with Modal component (+ some modernization)

This commit is contained in:
upsiflu 2025-02-06 17:51:23 +01:00
parent eb4258d66e
commit a463cc305a
16 changed files with 184 additions and 299 deletions

View File

@ -1,11 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import axios from 'axios' import axios from 'axios'
import { uniq } from 'lodash-es' import { uniq } from 'lodash-es'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { ref, computed, watch, nextTick } from 'vue' import { ref, computed, watch, nextTick } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
// import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface Props { interface Props {
show: boolean show: boolean
@ -63,13 +65,13 @@ const checkAndSwitch = async (url: string) => {
</script> </script>
<template> <template>
<semantic-modal <Modal :title="t('views.ChooseInstance.header.chooseInstance')"
v-model:show="show" v-model="show"
@update:show="isError = false" @update="isError = false"
> >
<h3 class="header"> <h3 class="header">
<!-- TODO: translate --> <!-- TODO: translate -->
Choose your instance
<!-- <translate translate-context="Popup/Instance/Title"> <!-- <translate translate-context="Popup/Instance/Title">
</translate> --> </translate> -->
</h3> </h3>
@ -177,5 +179,5 @@ const checkAndSwitch = async (url: string) => {
</translate> --> </translate> -->
</button> </button>
</div> </div>
</semantic-modal> </Modal>
</template> </template>

View File

@ -1,12 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types' import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions' import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
// import type { Track } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import usePlayOptions from '~/composables/audio/usePlayOptions' import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
@ -93,13 +92,14 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<semantic-modal <Modal
:title="track.title"
ref="modal" ref="modal"
v-model:show="show" v-model="show"
:scrolling="true" :scrolling="true"
:additional-classes="['scrolling-track-options']" class="scrolling-track-options"
> >
<div class="header"> <template #topright>
<div class="ui large centered rounded image"> <div class="ui large centered rounded image">
<img <img
v-if="track.album && track.album.cover && track.album.cover.urls.original" v-if="track.album && track.album.cover && track.album.cover.urls.original"
@ -122,7 +122,7 @@ const labels = computed(() => ({
class="ui centered image" class="ui centered image"
> >
<img <img
v-else-if="!!track.artist_credit?.length && track.artist_credit[0].artist.cover" v-else-if="!!track.artist_credit?.length && track.artist_credit[0].artist.attachment_cover"
v-lazy=" v-lazy="
getArtistCoverUrl(track.artist_credit) getArtistCoverUrl(track.artist_credit)
" "
@ -136,13 +136,10 @@ const labels = computed(() => ({
src="../../../assets/audio/default-cover.png" src="../../../assets/audio/default-cover.png"
> >
</div> </div>
<h3 class="track-modal-title">
{{ track.title }}
</h3>
<h4 class="track-modal-subtitle"> <h4 class="track-modal-subtitle">
{{ generateTrackCreditString(track) }} {{ generateTrackCreditString(track) }}
</h4> </h4>
</div> </template>
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<div class="content"> <div class="content">
<div class="ui one column unstackable grid"> <div class="ui one column unstackable grid">
@ -178,7 +175,7 @@ const labels = computed(() => ({
:aria-label="labels.addToQueue" :aria-label="labels.addToQueue"
@click.stop.prevent=" @click.stop.prevent="
enqueue(); enqueue();
modal.closeModal(); show=false
" "
> >
<i class="plus icon track-modal list-icon" /> <i class="plus icon track-modal list-icon" />
@ -192,7 +189,7 @@ const labels = computed(() => ({
:aria-label="labels.playNext" :aria-label="labels.playNext"
@click.stop.prevent=" @click.stop.prevent="
enqueueNext(true); enqueueNext(true);
modal.closeModal(); show=false
" "
> >
<i class="step forward icon track-modal list-icon" /> <i class="step forward icon track-modal list-icon" />
@ -209,7 +206,7 @@ const labels = computed(() => ({
type: 'similar', type: 'similar',
objectId: track.id, objectId: track.id,
}); });
modal.closeModal(); show=false
" "
> >
<i class="rss icon track-modal list-icon" /> <i class="rss icon track-modal list-icon" />
@ -301,5 +298,5 @@ const labels = computed(() => ({
</div> </div>
</div> </div>
</div> </div>
</semantic-modal> </Modal>
</template> </template>

View File

@ -1,17 +1,18 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types' import type { Track, SimpleArtist as Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions' import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
// import type { Track } from '~/types' import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { generateTrackCreditString, getArtistCoverUrl } from '~/utils/utils' import { generateTrackCreditString, getArtistCoverUrl } from '~/utils/utils'
import Modal from '~/components/ui/Modal.vue'
interface Events { interface Events {
(e: 'update:show', value: boolean): void (e: 'update:show', value: boolean): void
} }
@ -92,11 +93,11 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<semantic-modal <Modal :title="track.title"
ref="modal" ref="modal"
v-model:show="show" v-model="show"
:scrolling="true" :scrolling="true"
:additional-classes="['scrolling-track-options']" class="scrolling-track-options"
> >
<div class="header"> <div class="header">
<div class="ui large centered rounded image"> <div class="ui large centered rounded image">
@ -125,9 +126,6 @@ const labels = computed(() => ({
src="../../../assets/audio/default-cover.png" src="../../../assets/audio/default-cover.png"
> >
</div> </div>
<h3 class="track-modal-title">
{{ track.title }}
</h3>
<h4 class="track-modal-subtitle"> <h4 class="track-modal-subtitle">
{{ generateTrackCreditString(track) }} {{ generateTrackCreditString(track) }}
</h4> </h4>
@ -155,7 +153,7 @@ const labels = computed(() => ({
class="column" class="column"
role="button" role="button"
:aria-label="labels.addToQueue" :aria-label="labels.addToQueue"
@click.stop.prevent="enqueue(); modal.closeModal()" @click.stop.prevent="enqueue(); show = false"
> >
<i class="plus icon track-modal list-icon" /> <i class="plus icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.addToQueue }}</span> <span class="track-modal list-item">{{ labels.addToQueue }}</span>
@ -166,7 +164,7 @@ const labels = computed(() => ({
class="column" class="column"
role="button" role="button"
:aria-label="labels.playNext" :aria-label="labels.playNext"
@click.stop.prevent="enqueueNext(true);modal.closeModal()" @click.stop.prevent="enqueueNext(true);show = false"
> >
<i class="step forward icon track-modal list-icon" /> <i class="step forward icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.playNext }}</span> <span class="track-modal list-item">{{ labels.playNext }}</span>
@ -177,7 +175,7 @@ const labels = computed(() => ({
class="column" class="column"
role="button" role="button"
:aria-label="labels.startRadio" :aria-label="labels.startRadio"
@click.stop.prevent="() => { store.dispatch('radios/start', { type: 'similar', objectId: track.id }); modal.closeModal() }" @click.stop.prevent="() => { store.dispatch('radios/start', { type: 'similar', objectId: track.id }); show = false }"
> >
<i class="rss icon track-modal list-icon" /> <i class="rss icon track-modal list-icon" />
<span class="track-modal list-item">{{ labels.startRadio }}</span> <span class="track-modal list-item">{{ labels.startRadio }}</span>
@ -253,5 +251,5 @@ const labels = computed(() => ({
</div> </div>
</div> </div>
</div> </div>
</semantic-modal> </Modal>
</template> </template>

View File

@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Channel } from '~/types' import type { Channel } from '~/types'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import ChannelAlbumForm from '~/components/channels/AlbumForm.vue' import ChannelAlbumForm from '~/components/channels/AlbumForm.vue'
import Button from '~/components/ui/Button.vue'
import { watch, ref } from 'vue' import { watch, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Button from '~/components/ui/Button.vue'
import Spacer from '~/components/ui/Spacer.vue'
interface Events { interface Events {
(e: 'created'): void (e: 'created'): void
} }
@ -36,18 +38,11 @@ defineExpose({
</script> </script>
<template> <template>
<semantic-modal <Modal :title="t(channel.artist.content_category === 'podcast' ? 'components.channels.AlbumModal.header.newSeries' : 'components.channels.AlbumModal.header.newAlbum')"
v-model:show="show" v-model="show"
class="small" class="small"
:cancel="t('components.channels.AlbumModal.button.cancel')"
> >
<h4 class="header">
<span v-if="channel.content_category === 'podcast'">
{{ t('components.channels.AlbumModal.header.newSeries') }}
</span>
<span v-else>
{{ t('components.channels.AlbumModal.header.newAlbum') }}
</span>
</h4>
<div class="scrolling content"> <div class="scrolling content">
<channel-album-form <channel-album-form
ref="albumForm" ref="albumForm"
@ -57,10 +52,8 @@ defineExpose({
@created="emit('created')" @created="emit('created')"
/> />
</div> </div>
<div class="actions"> <template #actions>
<Button secondary> <Spacer h grow />
{{ t('components.channels.AlbumModal.button.cancel') }}
</Button>
<Button <Button
:is-loading="isLoading" :is-loading="isLoading"
:disabled="!submittable" :disabled="!submittable"
@ -68,6 +61,6 @@ defineExpose({
> >
{{ t('components.channels.AlbumModal.button.create') }} {{ t('components.channels.AlbumModal.button.create') }}
</Button> </Button>
</div> </template>
</semantic-modal> </Modal>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import ChannelUploadForm from '~/components/channels/UploadForm.vue' import ChannelUploadForm from '~/components/channels/UploadForm.vue'
import Popover from '~/components/ui/Popover.vue' import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue' import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
@ -53,24 +53,11 @@ const open = ref(false)
</script> </script>
<template> <template>
<semantic-modal <Modal
v-model:show="store.state.channels.showUploadModal" :title="t(`components.channels.UploadModal.header.${['', 'publish', 'uploadFiles', 'uploadDetails', 'processing'][step]}`)"
v-model="store.state.channels.showUploadModal"
class="small" class="small"
> >
<h4 class="header">
<span v-if="step === 1">
{{ t('components.channels.UploadModal.header.publish') }}
</span>
<span v-else-if="step === 2">
{{ t('components.channels.UploadModal.header.uploadFiles') }}
</span>
<span v-else-if="step === 3">
{{ t('components.channels.UploadModal.header.uploadDetails') }}
</span>
<span v-else-if="step === 4">
{{ t('components.channels.UploadModal.header.processing') }}
</span>
</h4>
<div class="scrolling content"> <div class="scrolling content">
<channel-upload-form <channel-upload-form
ref="uploadForm" ref="uploadForm"
@ -156,5 +143,5 @@ const open = ref(false)
{{ t('components.channels.UploadModal.button.close') }} {{ t('components.channels.UploadModal.button.close') }}
</Button> </Button>
</div> </div>
</semantic-modal> </Modal>
</template> </template>

View File

@ -2,11 +2,14 @@
import type { RouteLocationRaw } from 'vue-router' import type { RouteLocationRaw } from 'vue-router'
import type { Cover } from '~/types' import type { Cover } from '~/types'
import SemanticModal from '~/components/semantic/Modal.vue'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import Modal from '~/components/ui/Modal.vue'
import Link from '~/components/ui/Link.vue'
import Spacer from '~/components/ui/Spacer.vue'
interface Props { interface Props {
nextRoute: RouteLocationRaw nextRoute: RouteLocationRaw
message: string message: string
@ -29,10 +32,7 @@ const labels = computed(() => ({
</script> </script>
<template> <template>
<semantic-modal v-model:show="show"> <Modal v-model="show" :title="labels.header">
<h4 class="header">
{{ labels.header }}
</h4>
<div <div
v-if="cover" v-if="cover"
class="image content" class="image content"
@ -60,22 +60,21 @@ const labels = computed(() => ({
{{ message }} {{ message }}
</p> </p>
</div> </div>
<div class="actions"> <template #actions>
<router-link <Spacer grow />
<Link
:to="{path: '/login', query: { next: nextRoute as string }}" :to="{path: '/login', query: { next: nextRoute as string }}"
class="ui labeled icon button" icon="bi-key-fill"
> >
<i class="key icon" />
{{ labels.login }} {{ labels.login }}
</router-link> </Link>
<router-link <Link
v-if="store.state.instance.settings.users.registration_enabled.value" v-if="store.state.instance.settings.users.registration_enabled.value"
:to="{path: '/signup'}" :to="{path: '/signup'}"
class="ui labeled icon button" icon="bi-person-fill"
> >
<i class="user icon" />
{{ labels.signup }} {{ labels.signup }}
</router-link> </Link>
</div> </template>
</semantic-modal> </Modal>
</template> </template>

View File

@ -2,7 +2,7 @@
import type { BackendError } from '~/types' import type { BackendError } from '~/types'
import axios from 'axios' import axios from 'axios'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import { useTimeoutFn } from '@vueuse/core' import { useTimeoutFn } from '@vueuse/core'
import { ref } from 'vue' import { ref } from 'vue'
@ -80,13 +80,11 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
<div> <div>
<slot /> <slot />
</div> </div>
<semantic-modal <Modal :title="t('components.federation.FetchButton.header.refresh')"
v-model:show="showModal" v-model="showModal"
class="small" class="small"
:cancel="t('components.federation.FetchButton.button.close')"
> >
<h3 class="header">
{{ t('components.federation.FetchButton.header.refresh') }}
</h3>
<div class="scrolling content"> <div class="scrolling content">
<template v-if="data && data.status != 'pending'"> <template v-if="data && data.status != 'pending'">
<div <div
@ -213,9 +211,6 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<Button color="secondary">
{{ t('components.federation.FetchButton.button.close') }}
</Button>
<Button <Button
v-if="data && data.status === 'finished'" v-if="data && data.status === 'finished'"
@click.prevent="showModal = false; emit('refresh')" @click.prevent="showModal = false; emit('refresh')"
@ -223,6 +218,6 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
{{ t('components.federation.FetchButton.button.reload') }} {{ t('components.federation.FetchButton.button.reload') }}
</Button> </Button>
</div> </div>
</semantic-modal> </Modal>
</div> </div>
</template> </template>

View File

@ -10,7 +10,8 @@ import { getDomain } from '~/utils'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
import EmbedWizard from '~/components/audio/EmbedWizard.vue' import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Popover from '~/components/ui/Popover.vue' import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue' import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
@ -45,7 +46,10 @@ const labels = computed(() => ({
more: t('components.library.AlbumDropdown.button.more') more: t('components.library.AlbumDropdown.button.more')
})) }))
const isEmbedable = computed(() => (props.isChannel && props.artistCredit[0].artist?.channel?.actor) || props.publicLibraries.length) // TODO: What is the condition for an album to be embeddable?
// (a) props.publicLibraries.length
// (b) I am the channel's artist: props.isChannel && props.artistCredit[0].artist?.channel?.actor)
const isEmbedable = computed(() => (props.publicLibraries.length))
const musicbrainzUrl = computed(() => props.object?.mbid ? `https://musicbrainz.org/release/${props.object.mbid}` : null) 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_credit[0].artist.name)}`) const discogsUrl = computed(() => `https://discogs.com/search/?type=release&title=${encodeURI(props.object?.title)}&artist=${encodeURI(props.object?.artist_credit[0].artist.name)}`)
@ -56,32 +60,20 @@ const open = ref(false)
<template> <template>
<span> <span>
<semantic-modal <Modal :title="t('components.library.AlbumDropdown.modal.embed.header')"
v-if="isEmbedable" v-if="isEmbedable"
v-model:show="showEmbedModal" v-model="showEmbedModal"
:cancel="t('components.library.AlbumDropdown.button.cancel')"
> >
<h4 class="header">
{{ t('components.library.AlbumDropdown.modal.embed.header') }}
</h4>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<embed-wizard <embed-wizard
:id="object.id" :id="object.id"
type="album" type="album"
/> />
</div> </div>
</div> </div>
<div class="actions"> </Modal>
<Button
color="secondary"
variant="outline"
@click="showEmbedModal = false"
>
{{ t('components.library.AlbumDropdown.button.cancel') }}
</Button>
</div>
</semantic-modal>
<Popover v-model:open="open"> <Popover v-model:open="open">
<template #default="{ toggleOpen }"> <template #default="{ toggleOpen }">
<OptionsButton <OptionsButton

View File

@ -3,7 +3,7 @@ import type { Upload } from '~/types'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
interface ErrorEntry { interface ErrorEntry {
@ -76,10 +76,10 @@ const getErrorData = (upload: Upload) => {
</script> </script>
<template> <template>
<semantic-modal v-model:show="show"> <Modal :title="t('components.library.ImportStatusModal.header.importDetail')"
<h4 class="header"> v-model="show"
{{ t('components.library.ImportStatusModal.header.importDetail') }} :cancel="t('components.library.ImportStatusModal.button.close')"
</h4> >
<div <div
v-if="Object.keys(upload).length > 0" v-if="Object.keys(upload).length > 0"
class="content" class="content"
@ -183,10 +183,5 @@ const getErrorData = (upload: Upload) => {
</template> </template>
</div> </div>
</div> </div>
<div class="actions"> </Modal>
<Button color="secondary">
{{ t('components.library.ImportStatusModal.button.close') }}
</Button>
</div>
</semantic-modal>
</template> </template>

View File

@ -12,7 +12,7 @@ import { ref, onMounted, watch, computed } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import { clone } from 'lodash-es' import { clone } from 'lodash-es'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import TrackTable from '~/components/audio/track/Table.vue' import TrackTable from '~/components/audio/track/Table.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
@ -49,6 +49,10 @@ const exclude = computed({
}) })
const el = useCurrentElement() const el = useCurrentElement()
// This component appears on "create new radio" and offers filters. "New filter" => Dropdown search field
// TODO: Re-implement with <Input>, <select>
onMounted(() => { onMounted(() => {
for (const field of data.value.filter.fields) { for (const field of data.value.filter.fields) {
const settings: SemanticUI.DropdownSettings = { const settings: SemanticUI.DropdownSettings = {
@ -182,13 +186,10 @@ fetchCandidates()
> >
{{ t('components.library.radios.Filter.matchingTracks', checkResult.candidates.count) }} {{ t('components.library.radios.Filter.matchingTracks', checkResult.candidates.count) }}
</a> </a>
<semantic-modal <Modal :title="t('components.library.radios.Filter.matchingTracksModalHeader')"
v-if="checkResult" v-if="checkResult"
v-model:show="showCandidadesModal" v-model:show="showCandidadesModal"
> >
<h4 class="header">
{{ t('components.library.radios.Filter.matchingTracksModalHeader') }}
</h4>
<div class="content"> <div class="content">
<div class="description"> <div class="description">
<track-table <track-table
@ -202,7 +203,7 @@ fetchCandidates()
{{ t('components.library.radios.Filter.cancelButton') }} {{ t('components.library.radios.Filter.cancelButton') }}
</Button> </Button>
</div> </div>
</semantic-modal> </Modal>
</td> </td>
<td> <td>
<Button <Button

View File

@ -3,12 +3,12 @@ import type { BackendError } from '~/types'
import axios from 'axios' import axios from 'axios'
import { ref, computed } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue' import InstancePolicyForm from '~/components/manage/moderation/InstancePolicyForm.vue'
import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue' import InstancePolicyCard from '~/components/manage/moderation/InstancePolicyCard.vue'
import SemanticModal from '~/components/semantic/Modal.vue' import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
interface Props { interface Props {
@ -29,6 +29,11 @@ const result = ref()
const obj = computed(() => result.value?.results[0] ?? null) const obj = computed(() => result.value?.results[0] ?? null)
const isLoading = ref(false) const isLoading = ref(false)
watch (show, (newValue) => {
if (newValue) fetchData()
})
const fetchData = async () => { const fetchData = async () => {
const [username, domain] = props.target.split('@') const [username, domain] = props.target.split('@')
@ -67,13 +72,10 @@ const fetchData = async () => {
<slot> <slot>
{{ t('components.manage.moderation.InstancePolicyModal.button.show') }} {{ t('components.manage.moderation.InstancePolicyModal.button.show') }}
</slot> </slot>
<semantic-modal <Modal :title="t('components.manage.moderation.InstancePolicyModal.modal.manage.header', {obj: target})"
v-model:show="show" v-model="show"
@show="fetchData" :cancel="t('components.manage.moderation.InstancePolicyModal.button.close')"
> >
<h4 class="header">
{{ t('components.manage.moderation.InstancePolicyModal.modal.manage.header', {obj: target}) }}
</h4>
<div class="content"> <div class="content">
<div class="description"> <div class="description">
<div <div
@ -104,11 +106,6 @@ const fetchData = async () => {
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<div class="ui hidden divider" /> <div class="ui hidden divider" />
</div> </div>
<div class="actions"> </Modal>
<Button color="secondary">
{{ t('components.manage.moderation.InstancePolicyModal.button.close') }}
</Button>
</div>
</semantic-modal>
</Button> </Button>
</template> </template>

View File

@ -7,9 +7,11 @@ import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import SemanticModal from '~/components/semantic/Modal.vue'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
import Modal from '~/components/ui/Modal.vue'
import Alert from '~/components/ui/Alert.vue'
const logger = useLogger() const logger = useLogger()
const { t } = useI18n() const { t } = useI18n()
@ -58,23 +60,15 @@ const hide = async () => {
</script> </script>
<template> <template>
<semantic-modal v-model:show="show"> <Modal v-model="show"
<h4 :title="type==='artist' ? t('components.moderation.FilterModal.header.modal', {name: target?.name}) : errors.length > 0 ? t('components.moderation.FilterModal.header.failure') : ''"
v-if="type === 'artist'" :cancel="t('components.moderation.FilterModal.button.cancel')"
class="header"
> >
{{ t('components.moderation.FilterModal.header.modal', {name: target?.name}) }}
</h4>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<div <Alert red
v-if="errors.length > 0" v-if="errors.length > 0"
role="alert"
class="ui negative message"
> >
<h4 class="header">
{{ t('components.moderation.FilterModal.header.failure') }}
</h4>
<ul class="list"> <ul class="list">
<li <li
v-for="(error, key) in errors" v-for="(error, key) in errors"
@ -83,7 +77,7 @@ const hide = async () => {
{{ error }} {{ error }}
</li> </li>
</ul> </ul>
</div> </Alert>
<template v-if="type === 'artist'"> <template v-if="type === 'artist'">
<p> <p>
{{ t('components.moderation.FilterModal.warning.createFilter.listIntro') }} {{ t('components.moderation.FilterModal.warning.createFilter.listIntro') }}
@ -109,9 +103,6 @@ const hide = async () => {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button">
{{ t('components.moderation.FilterModal.button.cancel') }}
</button>
<button <button
:class="['ui', 'success', {loading: isLoading}, 'button']" :class="['ui', 'success', {loading: isLoading}, 'button']"
@click="hide" @click="hide"
@ -119,5 +110,5 @@ const hide = async () => {
{{ t('components.moderation.FilterModal.button.hide') }} {{ t('components.moderation.FilterModal.button.hide') }}
</button> </button>
</div> </div>
</semantic-modal> </Modal>
</template> </template>

View File

@ -3,11 +3,13 @@ import type { BackendError } from '~/types'
import axios from 'axios' import axios from 'axios'
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue' import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import { computed, ref, watchEffect } from 'vue' import { computed, ref, watchEffect } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue'
interface ReportType { interface ReportType {
anonymous: boolean anonymous: boolean
type: string type: string
@ -120,12 +122,14 @@ watchEffect(async () => {
</script> </script>
<template> <template>
<semantic-modal v-model:show="show"> <Modal :title="target ? t('components.moderation.ReportModal.header.modal') : errors.length > 0 ? t('components.moderation.ReportModal.header.submissionFailure') : ''"
v-model="show"
:cancel="t('components.moderation.ReportModal.button.cancel')"
>
<h2 <h2
v-if="target" v-if="target"
class="ui header" class="ui header"
> >
{{ t('components.moderation.ReportModal.header.modal') }}
<div class="ui sub header"> <div class="ui sub header">
{{ target.typeLabel }} {{ target.typeLabel }}
<span class="middle hyphen symbol" /> <span class="middle hyphen symbol" />
@ -134,14 +138,9 @@ watchEffect(async () => {
</h2> </h2>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<div <Alert red
v-if="errors.length > 0" v-if="errors.length > 0"
role="alert"
class="ui negative message"
> >
<h4 class="header">
{{ t('components.moderation.ReportModal.header.submissionFailure') }}
</h4>
<ul class="list"> <ul class="list">
<li <li
v-for="(error, key) in errors" v-for="(error, key) in errors"
@ -150,7 +149,7 @@ watchEffect(async () => {
{{ error }} {{ error }}
</li> </li>
</ul> </ul>
</div> </Alert>
</div> </div>
<p> <p>
{{ t('components.moderation.ReportModal.description.modal') }} {{ t('components.moderation.ReportModal.description.modal') }}
@ -238,18 +237,15 @@ watchEffect(async () => {
</h4> </h4>
</div> </div>
</div> </div>
<div class="actions"> <template #actions>
<button class="ui basic cancel button"> <Button
{{ t('components.moderation.ReportModal.button.cancel') }}
</button>
<button
v-if="canSubmit" v-if="canSubmit"
:class="['ui', 'success', {loading: isLoading}, 'button']" :class="['ui', 'success', {loading: isLoading}, 'button']"
type="submit" type="submit"
form="report-form" form="report-form"
> >
{{ t('components.moderation.ReportModal.button.submit') }} {{ t('components.moderation.ReportModal.button.submit') }}
</button> </Button>
</div> </template>
</semantic-modal> </Modal>
</template> </template>

View File

@ -1,33 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed } from 'vue'
import { useStore } from '~/store'
import Modal from '~/components/ui/Modal.vue'
interface Props {
fullscreen?: boolean
scrolling?: boolean
additionalClasses?: string[]
}
const props = withDefaults(defineProps<Props>(), {
fullscreen: true,
scrolling: false,
additionalClasses: () => []
})
const store = useStore()
const classes = computed(() => [
...props.additionalClasses,
{
scrolling: props.scrolling,
'overlay fullscreen': props.fullscreen && ['phone', 'tablet'].includes(store.getters['ui/windowSize'])
}
])
const isOpen = ref(false)
</script>
<template>
(Semantic Modal)
</template>

View File

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Actor } from '~/types' import type { Actor } from '~/types'
import SemanticModal from '~/components/semantic/Modal.vue'
import LibraryWidget from '~/components/federation/LibraryWidget.vue' import LibraryWidget from '~/components/federation/LibraryWidget.vue'
import ChannelsWidget from '~/components/audio/ChannelsWidget.vue' import ChannelsWidget from '~/components/audio/ChannelsWidget.vue'
import ChannelForm from '~/components/audio/ChannelForm.vue' import ChannelForm from '~/components/audio/ChannelForm.vue'
@ -10,6 +9,9 @@ import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import Modal from '~/components/ui/Modal.vue'
import Button from '~/components/ui/Button.vue'
interface Events { interface Events {
(e: 'updated', value: Actor): void (e: 'updated', value: Actor): void
} }
@ -83,24 +85,16 @@ const createForm = ref()
</library-widget> </library-widget>
</div> </div>
<semantic-modal v-model:show="showCreateModal"> <Modal v-model="showCreateModal"
<h4 class="header"> :title="t(`views.auth.ProfileOverview.modal.createChannel.${
<span step === 1 ?
v-if="step === 1" 'header' :
category === 'podcast' ?
'podcast.header'
:
'artist.header'
}`)"
> >
{{ t('views.auth.ProfileOverview.modal.createChannel.header') }}
</span>
<span
v-else-if="category === 'podcast'"
>
{{ t('views.auth.ProfileOverview.modal.createChannel.podcast.header') }}
</span>
<span
v-else
>
{{ t('views.auth.ProfileOverview.modal.createChannel.artist.header') }}
</span>
</h4>
<div <div
ref="modalContent" ref="modalContent"
class="scrolling content" class="scrolling content"
@ -117,37 +111,35 @@ const createForm = ref()
/> />
<div class="ui hidden divider" /> <div class="ui hidden divider" />
</div> </div>
<div class="actions"> <template #actions>
<button <Button destructive
v-if="step === 1" v-if="step === 1"
class="ui basic deny button" autofocus
> >
{{ t('views.auth.ProfileOverview.button.cancel') }} {{ t('views.auth.ProfileOverview.button.cancel') }}
</button> </Button>
<button <Button secondary
v-if="step > 1" v-if="step > 1"
class="ui basic button"
@click.stop.prevent="step -= 1" @click.stop.prevent="step -= 1"
> >
{{ t('views.auth.ProfileOverview.button.previous') }} {{ t('views.auth.ProfileOverview.button.previous') }}
</button> </Button>
<button <Button primary
v-if="step === 1" v-if="step === 1"
class="ui primary button"
@click.stop.prevent="step += 1" @click.stop.prevent="step += 1"
> >
{{ t('views.auth.ProfileOverview.button.next') }} {{ t('views.auth.ProfileOverview.button.next') }}
</button> </Button>
<button <Button primary
v-if="step === 2" v-if="step === 2"
:class="['ui', 'primary button', { loading }]"
type="submit" type="submit"
:disabled="!submittable && !loading" :disabled="!submittable && !loading"
:isLoading="loading"
@click.prevent.stop="createForm.submit" @click.prevent.stop="createForm.submit"
> >
{{ t('views.auth.ProfileOverview.button.createChannel') }} {{ t('views.auth.ProfileOverview.button.createChannel') }}
</button> </Button>
</div> </template>
</semantic-modal> </Modal>
</section> </section>
</template> </template>

View File

@ -8,14 +8,17 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
import SubscribeButton from '~/components/channels/SubscribeButton.vue' import SubscribeButton from '~/components/channels/SubscribeButton.vue'
import ChannelForm from '~/components/audio/ChannelForm.vue' import ChannelForm from '~/components/audio/ChannelForm.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue' import EmbedWizard from '~/components/audio/EmbedWizard.vue'
import HumanDuration from '~/components/common/HumanDuration.vue' import HumanDuration from '~/components/common/HumanDuration.vue'
import PlayButton from '~/components/audio/PlayButton.vue' import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
import SemanticModal from '~/components/semantic/Modal.vue'
import RadioButton from '~/components/radios/Button.vue' import RadioButton from '~/components/radios/Button.vue'
import Loader from '~/components/ui/Loader.vue' import Loader from '~/components/ui/Loader.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Tabs from '~/components/ui/Tabs.vue' import Tabs from '~/components/ui/Tabs.vue'
@ -24,11 +27,8 @@ import OptionsButton from '~/components/ui/button/Options.vue'
import Popover from '~/components/ui/Popover.vue' import Popover from '~/components/ui/Popover.vue'
import PopoverItem from '~/components/ui/popover/PopoverItem.vue' import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Modal from '~/components/ui/Modal.vue'
import Spacer from '~/components/ui/Spacer.vue' import Spacer from '~/components/ui/Spacer.vue'
import Modal from '~/components/ui/Modal.vue'
import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
interface Events { interface Events {
(e: 'deleted'): void (e: 'deleted'): void
@ -347,13 +347,12 @@ const updateSubscriptionCount = (delta: number) => {
@unsubscribed="updateSubscriptionCount(-1)" @unsubscribed="updateSubscriptionCount(-1)"
/> />
<semantic-modal <Modal
:title="t('views.channels.DetailBase.modal.embed.header')"
v-if="totalTracks > 0" v-if="totalTracks > 0"
v-model:show="showEmbedModal" v-model="showEmbedModal"
:cancel="t('views.channels.DetailBase.button.cancel')"
> >
<h4 class="header">
{{ t('views.channels.DetailBase.modal.embed.header') }}
</h4>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<embed-wizard <embed-wizard
@ -362,28 +361,19 @@ const updateSubscriptionCount = (delta: number) => {
/> />
</div> </div>
</div> </div>
<div class="actions"> <template #actions>
<button class="ui basic deny button"> <button class="ui basic deny button">
{{ t('views.channels.DetailBase.button.cancel') }} {{ t('views.channels.DetailBase.button.cancel') }}
</button> </button>
</div> </template>
</semantic-modal> </Modal>
<semantic-modal <Modal
:title="t(`views.channels.DetailBase.header.${
object.artist?.content_category === 'podcast' ? 'podcastChannel' : 'artistChannel'
}`)"
v-if="isOwner" v-if="isOwner"
v-model:show="showEditModal" v-model="showEditModal"
> >
<h4 class="header">
<span
v-if="object.artist?.content_category === 'podcast'"
>
{{ t('views.channels.DetailBase.header.podcastChannel') }}
</span>
<span
v-else
>
{{ t('views.channels.DetailBase.header.artistChannel') }}
</span>
</h4>
<div class="scrolling content"> <div class="scrolling content">
<channel-form <channel-form
ref="editForm" ref="editForm"
@ -394,31 +384,29 @@ const updateSubscriptionCount = (delta: number) => {
/> />
<div class="ui hidden divider" /> <div class="ui hidden divider" />
</div> </div>
<div class="actions"> <template #actions>
<button class="ui left floated basic deny button"> <Button
{{ t('views.channels.DetailBase.button.cancel') }} primary
</button> autofocus
<button :isLoading="edit.loading"
:class="['ui', 'primary', 'confirm', {loading: edit.loading}, 'button']"
:disabled="!edit.submittable" :disabled="!edit.submittable"
@click.stop="editForm?.submit" @click.stop="editForm?.submit"
> >
{{ t('views.channels.DetailBase.button.updateChannel') }} {{ t('views.channels.DetailBase.button.updateChannel') }}
</button> </Button>
</div> </template>
</semantic-modal> </Modal>
<Button <Button
secondary secondary
icon="bi-rss" icon="bi-rss"
@click.stop.prevent="showSubscribeModal = true" @click.stop.prevent="showSubscribeModal = true"
/> />
<semantic-modal <Modal
:title="t('views.channels.DetailBase.modal.subscribe.header')"
v-model:show="showSubscribeModal" v-model:show="showSubscribeModal"
class="tiny" class="tiny"
:cancel="t('views.channels.DetailBase.button.cancel')"
> >
<h4 class="header">
{{ t('views.channels.DetailBase.modal.subscribe.header') }}
</h4>
<div class="scrollable content"> <div class="scrollable content">
<div class="description"> <div class="description">
<template v-if="store.state.auth.authenticated"> <template v-if="store.state.auth.authenticated">
@ -457,12 +445,7 @@ const updateSubscriptionCount = (delta: number) => {
</template> </template>
</div> </div>
</div> </div>
<div class="actions"> </Modal>
<button class="ui basic deny button">
{{ t('views.channels.DetailBase.button.cancel') }}
</button>
</div>
</semantic-modal>
</Layout> </Layout>
</Layout> </Layout>
</Layout> </Layout>