fix(ui):[WIP] replace popover menus, buttons, messages
This commit is contained in:
parent
481fee8f5f
commit
4d78c2143c
|
@ -1,6 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||||
import ChannelUploadForm from '~/components/channels/UploadForm.vue'
|
import ChannelUploadForm from '~/components/channels/UploadForm.vue'
|
||||||
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
import { humanSize } from '~/utils/filters'
|
import { humanSize } from '~/utils/filters'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
@ -45,6 +48,8 @@ const statusInfo = computed(() => {
|
||||||
|
|
||||||
const step = ref(1)
|
const step = ref(1)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
const dropdownOpen = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -86,70 +91,70 @@ const isLoading = ref(false)
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui hidden clearing divider mobile-only" />
|
<div class="ui hidden clearing divider mobile-only" />
|
||||||
<button
|
<Button
|
||||||
v-if="step === 1"
|
v-if="step === 1"
|
||||||
class="ui basic cancel button"
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
@click="update(false)"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.cancel') }}
|
{{ $t('components.channels.UploadModal.button.cancel') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
v-else-if="step < 3"
|
v-else-if="step < 3"
|
||||||
class="ui basic button"
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
@click.stop.prevent="uploadForm.step -= 1"
|
@click.stop.prevent="uploadForm.step -= 1"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.previous') }}
|
{{ $t('components.channels.UploadModal.button.previous') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
v-else-if="step === 3"
|
v-else-if="step === 3"
|
||||||
class="ui basic button"
|
color="secondary"
|
||||||
@click.stop.prevent="uploadForm.step -= 1"
|
@click.stop.prevent="uploadForm.step -= 1"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.update') }}
|
{{ $t('components.channels.UploadModal.button.update') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
v-if="step === 1"
|
v-if="step === 1"
|
||||||
class="ui primary button"
|
color="secondary"
|
||||||
@click.stop.prevent="uploadForm.step += 1"
|
@click.stop.prevent="uploadForm.step += 1"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.next') }}
|
{{ $t('components.channels.UploadModal.button.next') }}
|
||||||
</button>
|
</Button>
|
||||||
<div
|
<div class="ui primary buttons">
|
||||||
v-if="step === 2"
|
<Button
|
||||||
class="ui primary buttons"
|
:is-loading="isLoading"
|
||||||
>
|
|
||||||
<button
|
|
||||||
:class="['ui', 'primary button', {loading: isLoading}]"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="!statusData?.canSubmit || undefined"
|
:disabled="!statusData?.canSubmit"
|
||||||
@click.prevent.stop="uploadForm.publish"
|
@click.prevent.stop="uploadForm.publish"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.publish') }}
|
{{ $t('components.channels.UploadModal.button.publish') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
|
||||||
ref="dropdown"
|
<Popover v-model:open="dropdownOpen">
|
||||||
v-dropdown
|
<template #default="{ toggleOpen }">
|
||||||
class="ui floating dropdown icon button"
|
<Button
|
||||||
:disabled="!statusData?.canSubmit || undefined"
|
color="primary"
|
||||||
>
|
icon="bi-chevron-down"
|
||||||
<i class="dropdown icon" />
|
:disabled="!statusData?.canSubmit"
|
||||||
<div class="menu">
|
@click="toggleOpen"
|
||||||
<div
|
/>
|
||||||
role="button"
|
</template>
|
||||||
class="basic item"
|
<template #items>
|
||||||
@click="update(false)"
|
<PopoverItem @click="update(false)">
|
||||||
>
|
|
||||||
{{ $t('components.channels.UploadModal.button.finishLater') }}
|
{{ $t('components.channels.UploadModal.button.finishLater') }}
|
||||||
|
</PopoverItem>
|
||||||
|
</template>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</button>
|
<Button
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
v-if="step === 4"
|
v-if="step === 4"
|
||||||
class="ui basic cancel button"
|
color="secondary"
|
||||||
@click="update(false)"
|
@click="update(false)"
|
||||||
>
|
>
|
||||||
{{ $t('components.channels.UploadModal.button.close') }}
|
{{ $t('components.channels.UploadModal.button.close') }}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</semantic-modal>
|
</semantic-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
|
@ -9,7 +10,7 @@ interface Events {
|
||||||
interface Props {
|
interface Props {
|
||||||
action?: () => void
|
action?: () => void
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
confirmColor?: 'danger' | 'success'
|
confirmColor?: 'destructive' | 'primary'
|
||||||
}
|
}
|
||||||
|
|
||||||
const emit = defineEmits<Events>()
|
const emit = defineEmits<Events>()
|
||||||
|
@ -30,7 +31,8 @@ const confirm = () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
:class="[{disabled: disabled}]"
|
class="funkwhale dangerous-button"
|
||||||
|
:class="{ 'is-disabled': disabled }"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click.prevent.stop="showModal = true"
|
@click.prevent.stop="showModal = true"
|
||||||
>
|
>
|
||||||
|
@ -51,17 +53,21 @@ const confirm = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="ui basic cancel button">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
@click="showModal = false"
|
||||||
|
>
|
||||||
{{ $t('components.common.DangerousButton.button.cancel') }}
|
{{ $t('components.common.DangerousButton.button.cancel') }}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', 'confirm', confirmColor, 'button']"
|
:color="confirmColor"
|
||||||
@click="confirm"
|
@click="confirm"
|
||||||
>
|
>
|
||||||
<slot name="modal-confirm">
|
<slot name="modal-confirm">
|
||||||
{{ $t('components.common.DangerousButton.button.confirm') }}
|
{{ $t('components.common.DangerousButton.button.confirm') }}
|
||||||
</slot>
|
</slot>
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</semantic-modal>
|
</semantic-modal>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,35 +1,49 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import $ from 'jquery'
|
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
import Alert from '~/components/ui/Alert.vue'
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
content: string
|
content: string
|
||||||
key: string
|
key: string
|
||||||
|
color?: 'blue' | 'red' | 'purple' | 'green' | 'yellow'
|
||||||
|
error?: boolean | string
|
||||||
|
date?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{ message: Message }>()
|
const props = defineProps<{ message: Message }>()
|
||||||
|
const isVisible = ref(true)
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
onMounted(() => {
|
|
||||||
const params = {
|
const messageColor = computed(() => {
|
||||||
context: '#app',
|
if (props.message.color) {
|
||||||
message: props.message.content,
|
return props.message.color
|
||||||
showProgress: 'top',
|
|
||||||
position: 'bottom right',
|
|
||||||
progressUp: true,
|
|
||||||
onRemove () {
|
|
||||||
store.commit('ui/removeMessage', props.message.key)
|
|
||||||
},
|
|
||||||
...props.message
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error fomantic ui
|
if (props.message.error || props.message.content?.toLowerCase().includes('error')) {
|
||||||
$('body').toast(params)
|
return 'red'
|
||||||
$('.ui.toast.visible').last().attr('role', 'alert')
|
}
|
||||||
|
|
||||||
|
return 'blue'
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
isVisible.value = false
|
||||||
|
store.commit('ui/removeMessage', props.message.key)
|
||||||
|
}, 5000)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div />
|
<Transition name="fade">
|
||||||
|
<Alert
|
||||||
|
v-if="isVisible"
|
||||||
|
role="alert"
|
||||||
|
:color="messageColor"
|
||||||
|
class="is-notification"
|
||||||
|
>
|
||||||
|
{{ message.content }}
|
||||||
|
</Alert>
|
||||||
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -10,6 +10,10 @@ 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 SemanticModal from '~/components/semantic/Modal.vue'
|
||||||
|
import Button from '~/components/ui/Button.vue'
|
||||||
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
|
import DangerousButton from '~/components/common/DangerousButton.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'remove'): void
|
(e: 'remove'): void
|
||||||
|
@ -43,6 +47,8 @@ const musicbrainzUrl = computed(() => props.object?.mbid ? `https://musicbrainz.
|
||||||
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)}`)
|
||||||
|
|
||||||
const remove = () => emit('remove')
|
const remove = () => emit('remove')
|
||||||
|
|
||||||
|
const open = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -65,120 +71,116 @@ const remove = () => emit('remove')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="ui basic deny button">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="outline"
|
||||||
|
@click="showEmbedModal = false"
|
||||||
|
>
|
||||||
{{ $t('components.library.AlbumDropdown.button.cancel') }}
|
{{ $t('components.library.AlbumDropdown.button.cancel') }}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</semantic-modal>
|
</semantic-modal>
|
||||||
<button
|
<Popover v-model:open="open">
|
||||||
v-dropdown="{direction: 'downward'}"
|
<template #default="{ toggleOpen }">
|
||||||
class="ui floating dropdown circular icon basic button"
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
round
|
||||||
|
icon="bi-three-dots-vertical"
|
||||||
:title="labels.more"
|
:title="labels.more"
|
||||||
>
|
@click="toggleOpen"
|
||||||
<i class="ellipsis vertical icon" />
|
/>
|
||||||
<div class="menu">
|
</template>
|
||||||
<a
|
|
||||||
|
<template #items>
|
||||||
|
<PopoverItem
|
||||||
v-if="domain != $store.getters['instance/domain']"
|
v-if="domain != $store.getters['instance/domain']"
|
||||||
:href="object.fid"
|
:href="object.fid"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="bi bi-box-arrow-up-right" />
|
||||||
{{ $t('components.library.AlbumDropdown.link.domain') }}
|
{{ $t('components.library.AlbumDropdown.link.domain') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
|
|
||||||
<div
|
<PopoverItem
|
||||||
v-if="isEmbedable"
|
v-if="isEmbedable"
|
||||||
role="button"
|
|
||||||
class="basic item"
|
|
||||||
@click="showEmbedModal = !showEmbedModal"
|
@click="showEmbedModal = !showEmbedModal"
|
||||||
>
|
>
|
||||||
<i class="code icon" />
|
<i class="bi bi-code" />
|
||||||
{{ $t('components.library.AlbumDropdown.button.embed') }}
|
{{ $t('components.library.AlbumDropdown.button.embed') }}
|
||||||
</div>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
v-if="isAlbum && musicbrainzUrl"
|
v-if="isAlbum && musicbrainzUrl"
|
||||||
:href="musicbrainzUrl"
|
:href="musicbrainzUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="bi bi-box-arrow-up-right" />
|
||||||
{{ $t('components.library.AlbumDropdown.link.musicbrainz') }}
|
{{ $t('components.library.AlbumDropdown.link.musicbrainz') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
v-if="!isChannel && isAlbum"
|
v-if="!isChannel && isAlbum"
|
||||||
:href="discogsUrl"
|
:href="discogsUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="bi bi-box-arrow-up-right" />
|
||||||
{{ $t('components.library.AlbumDropdown.link.discogs') }}
|
{{ $t('components.library.AlbumDropdown.link.discogs') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
<router-link
|
|
||||||
|
<PopoverItem
|
||||||
v-if="object.is_local"
|
v-if="object.is_local"
|
||||||
:to="{name: 'library.albums.edit', params: {id: object.id }}"
|
:to="{name: 'library.albums.edit', params: {id: object.id }}"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="edit icon" />
|
<i class="bi bi-pencil" />
|
||||||
{{ $t('components.library.AlbumDropdown.button.edit') }}
|
{{ $t('components.library.AlbumDropdown.button.edit') }}
|
||||||
</router-link>
|
</PopoverItem>
|
||||||
<dangerous-button
|
|
||||||
|
<PopoverItem
|
||||||
v-if="artistCredit[0] && $store.state.auth.authenticated && artistCredit[0].artist.channel && artistCredit[0].artist.attributed_to?.full_username === $store.state.auth.fullUsername"
|
v-if="artistCredit[0] && $store.state.auth.authenticated && artistCredit[0].artist.channel && artistCredit[0].artist.attributed_to?.full_username === $store.state.auth.fullUsername"
|
||||||
:class="['ui', {loading: isLoading}, 'item']"
|
>
|
||||||
|
<DangerousButton
|
||||||
|
:is-loading="isLoading"
|
||||||
@confirm="remove()"
|
@confirm="remove()"
|
||||||
>
|
>
|
||||||
<i class="ui trash icon" />
|
<i class="bi bi-trash" />
|
||||||
{{ $t('components.library.AlbumDropdown.button.delete') }}
|
{{ $t('components.library.AlbumDropdown.button.delete') }}
|
||||||
<template #modal-header>
|
</DangerousButton>
|
||||||
<p>
|
</PopoverItem>
|
||||||
{{ $t('components.library.AlbumDropdown.modal.delete.header') }}
|
|
||||||
</p>
|
<hr>
|
||||||
</template>
|
|
||||||
<template #modal-content>
|
<PopoverItem
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
{{ $t('components.library.AlbumDropdown.modal.delete.content.warning') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #modal-confirm>
|
|
||||||
<p>
|
|
||||||
{{ $t('components.library.AlbumDropdown.button.delete') }}
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
</dangerous-button>
|
|
||||||
<div class="divider" />
|
|
||||||
<div
|
|
||||||
v-for="obj in getReportableObjects({album: object, channel: artistCredit[0]?.artist.channel})"
|
v-for="obj in getReportableObjects({album: object, channel: artistCredit[0]?.artist.channel})"
|
||||||
:key="obj.target.type + obj.target.id"
|
:key="obj.target.type + obj.target.id"
|
||||||
role="button"
|
@click="report(obj)"
|
||||||
class="basic item"
|
|
||||||
@click.stop.prevent="report(obj)"
|
|
||||||
>
|
>
|
||||||
<i class="share icon" /> {{ obj.label }}
|
<i class="bi bi-flag" />
|
||||||
</div>
|
{{ obj.label }}
|
||||||
<div class="divider" />
|
</PopoverItem>
|
||||||
<router-link
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<PopoverItem
|
||||||
v-if="$store.state.auth.availablePermissions['library']"
|
v-if="$store.state.auth.availablePermissions['library']"
|
||||||
class="basic item"
|
|
||||||
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
|
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
|
||||||
>
|
>
|
||||||
<i class="wrench icon" />
|
<i class="bi bi-wrench" />
|
||||||
{{ $t('components.library.AlbumDropdown.link.moderation') }}
|
{{ $t('components.library.AlbumDropdown.link.moderation') }}
|
||||||
</router-link>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
|
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
|
||||||
class="basic item"
|
|
||||||
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
|
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<i class="wrench icon" />
|
<i class="bi bi-wrench" />
|
||||||
{{ $t('components.library.AlbumDropdown.link.django') }}
|
{{ $t('components.library.AlbumDropdown.link.django') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
</div>
|
</template>
|
||||||
</button>
|
</Popover>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -14,6 +14,8 @@ import SemanticModal from '~/components/semantic/Modal.vue'
|
||||||
import PlayButton from '~/components/audio/PlayButton.vue'
|
import PlayButton from '~/components/audio/PlayButton.vue'
|
||||||
import RadioButton from '~/components/radios/Button.vue'
|
import RadioButton from '~/components/radios/Button.vue'
|
||||||
import TagsList from '~/components/tags/List.vue'
|
import TagsList from '~/components/tags/List.vue'
|
||||||
|
import Popover from '~/components/ui/Popover.vue'
|
||||||
|
import PopoverItem from '~/components/ui/popover/PopoverItem.vue'
|
||||||
|
|
||||||
import useReport from '~/composables/moderation/useReport'
|
import useReport from '~/composables/moderation/useReport'
|
||||||
import useLogger from '~/composables/useLogger'
|
import useLogger from '~/composables/useLogger'
|
||||||
|
@ -36,8 +38,6 @@ const nextTracksUrl = ref(null)
|
||||||
const totalAlbums = ref(0)
|
const totalAlbums = ref(0)
|
||||||
const totalTracks = ref(0)
|
const totalTracks = ref(0)
|
||||||
|
|
||||||
const dropdown = ref()
|
|
||||||
|
|
||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -166,107 +166,103 @@ watch(() => props.id, fetchData, { immediate: true })
|
||||||
</div>
|
</div>
|
||||||
</semantic-modal>
|
</semantic-modal>
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
|
<Popover>
|
||||||
|
<template #default="{ toggleOpen }">
|
||||||
<button
|
<button
|
||||||
class="ui button"
|
class="ui button"
|
||||||
@click="dropdown.click()"
|
@click="toggleOpen"
|
||||||
>
|
>
|
||||||
{{ $t('components.library.ArtistBase.button.more') }}
|
{{ $t('components.library.ArtistBase.button.more') }}
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ref="dropdown"
|
|
||||||
v-dropdown
|
|
||||||
class="ui floating dropdown icon button"
|
|
||||||
>
|
|
||||||
<i class="dropdown icon" />
|
<i class="dropdown icon" />
|
||||||
<div class="menu">
|
</button>
|
||||||
<a
|
</template>
|
||||||
|
|
||||||
|
<template #items>
|
||||||
|
<PopoverItem
|
||||||
v-if="domain != $store.getters['instance/domain']"
|
v-if="domain != $store.getters['instance/domain']"
|
||||||
:href="object.fid"
|
:href="object.fid"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="basic item"
|
class="funkwhale item"
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="external icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.domain', {domain: domain}) }}
|
{{ $t('components.library.ArtistBase.link.domain', {domain: domain}) }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
|
|
||||||
<button
|
<PopoverItem
|
||||||
v-if="publicLibraries.length > 0"
|
v-if="publicLibraries.length > 0"
|
||||||
role="button"
|
@click="showEmbedModal = !showEmbedModal"
|
||||||
class="basic item"
|
|
||||||
@click.prevent="showEmbedModal = !showEmbedModal"
|
|
||||||
>
|
>
|
||||||
<i class="code icon" />
|
<i class="code icon" />
|
||||||
{{ $t('components.library.ArtistBase.button.embed') }}
|
{{ $t('components.library.ArtistBase.button.embed') }}
|
||||||
</button>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
:href="wikipediaUrl"
|
:href="wikipediaUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="wikipedia w icon" />
|
<i class="wikipedia w icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.wikipedia') }}
|
{{ $t('components.library.ArtistBase.link.wikipedia') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
v-if="musicbrainzUrl"
|
v-if="musicbrainzUrl"
|
||||||
:href="musicbrainzUrl"
|
:href="musicbrainzUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="external icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.musicbrainz') }}
|
{{ $t('components.library.ArtistBase.link.musicbrainz') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
:href="discogsUrl"
|
:href="discogsUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="external icon" />
|
<i class="external icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.discogs') }}
|
{{ $t('components.library.ArtistBase.link.discogs') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
<router-link
|
|
||||||
|
<PopoverItem
|
||||||
v-if="object.is_local"
|
v-if="object.is_local"
|
||||||
:to="{name: 'library.artists.edit', params: {id: object.id }}"
|
:to="{name: 'library.artists.edit', params: {id: object.id }}"
|
||||||
class="basic item"
|
|
||||||
>
|
>
|
||||||
<i class="edit icon" />
|
<i class="edit icon" />
|
||||||
{{ $t('components.library.ArtistBase.button.edit') }}
|
{{ $t('components.library.ArtistBase.button.edit') }}
|
||||||
</router-link>
|
</PopoverItem>
|
||||||
<div class="divider" />
|
|
||||||
<div
|
<hr>
|
||||||
|
|
||||||
|
<PopoverItem
|
||||||
v-for="obj in getReportableObjects({artist: object})"
|
v-for="obj in getReportableObjects({artist: object})"
|
||||||
:key="obj.target.type + obj.target.id"
|
:key="obj.target.type + obj.target.id"
|
||||||
role="button"
|
@click="report(obj)"
|
||||||
class="basic item"
|
|
||||||
@click.stop.prevent="report(obj)"
|
|
||||||
>
|
>
|
||||||
<i class="share icon" /> {{ obj.label }}
|
<i class="share icon" /> {{ obj.label }}
|
||||||
</div>
|
</PopoverItem>
|
||||||
|
|
||||||
<div class="divider" />
|
<hr>
|
||||||
<router-link
|
|
||||||
|
<PopoverItem
|
||||||
v-if="$store.state.auth.availablePermissions['library']"
|
v-if="$store.state.auth.availablePermissions['library']"
|
||||||
class="basic item"
|
|
||||||
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
|
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
|
||||||
>
|
>
|
||||||
<i class="wrench icon" />
|
<i class="wrench icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.moderation') }}
|
{{ $t('components.library.ArtistBase.link.moderation') }}
|
||||||
</router-link>
|
</PopoverItem>
|
||||||
<a
|
|
||||||
|
<PopoverItem
|
||||||
v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
|
v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
|
||||||
class="basic item"
|
|
||||||
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
|
:href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<i class="wrench icon" />
|
<i class="wrench icon" />
|
||||||
{{ $t('components.library.ArtistBase.link.django') }}
|
{{ $t('components.library.ArtistBase.link.django') }}
|
||||||
</a>
|
</PopoverItem>
|
||||||
</div>
|
</template>
|
||||||
</button>
|
</Popover>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -74,14 +74,12 @@ const toggleRadio = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<Button
|
||||||
:class="['ui', 'primary', {'inverted': running}, 'icon', 'labeled', 'button']"
|
:is-active="running"
|
||||||
|
color="primary"
|
||||||
|
icon="bi-broadcast"
|
||||||
@click="toggleRadio"
|
@click="toggleRadio"
|
||||||
>
|
>
|
||||||
<i
|
|
||||||
class="ui feed icon"
|
|
||||||
role="button"
|
|
||||||
/>
|
|
||||||
{{ buttonLabel }}
|
{{ buttonLabel }}
|
||||||
</button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import $ from 'jquery'
|
|
||||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||||
import { computed, onBeforeUnmount, ref, watchEffect } from 'vue'
|
import { computed, onBeforeUnmount, ref, watchEffect } from 'vue'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
|
||||||
interface Events {
|
interface Events {
|
||||||
(e: 'update:show', show: boolean): void
|
(e: 'update:show', show: boolean): void
|
||||||
|
@ -26,55 +26,10 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
scrolling: false,
|
scrolling: false,
|
||||||
additionalClasses: () => []
|
additionalClasses: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
const modal = ref()
|
|
||||||
const { activate, deactivate, pause, unpause } = useFocusTrap(modal, {
|
|
||||||
allowOutsideClick: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const show = useVModel(props, 'show', emit)
|
|
||||||
|
|
||||||
const control = ref<JQuery | undefined>()
|
|
||||||
const initModal = () => {
|
|
||||||
control.value = $(modal.value).modal({
|
|
||||||
duration: 100,
|
|
||||||
onApprove: () => emit('approved'),
|
|
||||||
onDeny: () => emit('deny'),
|
|
||||||
onHidden: () => (show.value = false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (show.value) {
|
|
||||||
initModal()
|
|
||||||
emit('show')
|
|
||||||
control.value?.modal('show')
|
|
||||||
activate()
|
|
||||||
unpause()
|
|
||||||
document.body.classList.add('scrolling')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (control.value) {
|
|
||||||
emit('hide')
|
|
||||||
control.value.modal('hide')
|
|
||||||
control.value.remove()
|
|
||||||
deactivate()
|
|
||||||
pause()
|
|
||||||
document.body.classList.remove('scrolling')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
control.value?.modal('hide')
|
|
||||||
})
|
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const classes = computed(() => [
|
const classes = computed(() => [
|
||||||
...props.additionalClasses,
|
...props.additionalClasses,
|
||||||
'ui', 'modal',
|
|
||||||
{
|
{
|
||||||
active: show.value,
|
|
||||||
scrolling: props.scrolling,
|
scrolling: props.scrolling,
|
||||||
'overlay fullscreen': props.fullscreen && ['phone', 'tablet'].includes(store.getters['ui/windowSize'])
|
'overlay fullscreen': props.fullscreen && ['phone', 'tablet'].includes(store.getters['ui/windowSize'])
|
||||||
}
|
}
|
||||||
|
@ -82,14 +37,15 @@ const classes = computed(() => [
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<Modal
|
||||||
ref="modal"
|
:model-value="show"
|
||||||
|
@update:model-value="(value) => emit('update:show', value)"
|
||||||
:class="classes"
|
:class="classes"
|
||||||
|
@approve="emit('approved')"
|
||||||
|
@deny="emit('deny')"
|
||||||
|
@show="emit('show')"
|
||||||
|
@hide="emit('hide')"
|
||||||
>
|
>
|
||||||
<i
|
<slot />
|
||||||
tabindex="0"
|
</Modal>
|
||||||
class="close inside icon"
|
|
||||||
/>
|
|
||||||
<slot v-if="show" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -3,10 +3,21 @@ import { useColorOrPastel, type ColorProps, type PastelProps } from '~/composabl
|
||||||
|
|
||||||
const props = defineProps<ColorProps | PastelProps>()
|
const props = defineProps<ColorProps | PastelProps>()
|
||||||
const color = useColorOrPastel(() => props.color, 'secondary')
|
const color = useColorOrPastel(() => props.color, 'secondary')
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: [event: MouseEvent]
|
||||||
|
}>()
|
||||||
|
const handleClick = (event: MouseEvent) => {
|
||||||
|
emit('click', event)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="funkwhale is-colored pill" :class="[color]">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="funkwhale is-colored pill"
|
||||||
|
:class="[color]"
|
||||||
|
@click.stop="handleClick"
|
||||||
|
>
|
||||||
<div v-if="!!$slots.image" class="pill-image">
|
<div v-if="!!$slots.image" class="pill-image">
|
||||||
<slot name="image" />
|
<slot name="image" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,4 +41,25 @@
|
||||||
> .actions {
|
> .actions {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add styles for when alert is used as a notification
|
||||||
|
&.is-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 400px;
|
||||||
|
|
||||||
|
&.fade-enter-active,
|
||||||
|
&.fade-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fade-enter-from,
|
||||||
|
&.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(1rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -81,7 +81,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include docs {
|
@if $docs {
|
||||||
color: var(--fw-text-color) !important;
|
color: var(--fw-text-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { inject, ref } from 'vue'
|
import { inject, ref } from 'vue'
|
||||||
import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-keys'
|
import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-keys'
|
||||||
|
|
||||||
const setId = defineEmit<[value: number]>('internal:id')
|
const emit = defineEmits<{'internal:id': [value: number]}>()
|
||||||
|
|
||||||
const { parentPopoverContext } = defineProps<{ parentPopoverContext?: PopoverContext }>()
|
const { parentPopoverContext } = defineProps<{ parentPopoverContext?: PopoverContext }>()
|
||||||
const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
|
const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
|
||||||
|
@ -11,7 +11,7 @@ const { items, hoveredItem } = parentPopoverContext ?? inject(POPOVER_CONTEXT_IN
|
||||||
})
|
})
|
||||||
|
|
||||||
const id = items.value++
|
const id = items.value++
|
||||||
setId(id)
|
emit('internal:id', id)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
Loading…
Reference in New Issue