chore(front): modernize DangerousButton (destructive action confirmation dialog)

This commit is contained in:
upsiflu 2025-02-06 18:55:14 +01:00
parent cae4d7c0f4
commit 60463d405e
21 changed files with 169 additions and 380 deletions

View File

@ -13,9 +13,11 @@ import { useStore } from '~/store'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
import DangerousButton from '~/components/common/DangerousButton.vue'
import SubsonicTokenForm from '~/components/auth/SubsonicTokenForm.vue' import SubsonicTokenForm from '~/components/auth/SubsonicTokenForm.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue' import AttachmentInput from '~/components/common/AttachmentInput.vue'
import PasswordInput from '~/components/forms/PasswordInput.vue' import PasswordInput from '~/components/forms/PasswordInput.vue'
import Input from '~/components/ui/Input.vue' import Input from '~/components/ui/Input.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Header from '~/components/ui/Header.vue' import Header from '~/components/ui/Header.vue'
@ -412,13 +414,9 @@ fetchOwnedApps()
<dangerous-button <dangerous-button
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']" :class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
:action="submitPassword" :action="submitPassword"
:title="t('components.auth.Settings.modal.changePassword.header')"
> >
{{ t('components.auth.Settings.button.password') }} {{ t('components.auth.Settings.button.password') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.changePassword.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div> <div>
<p> <p>
@ -558,22 +556,14 @@ fetchOwnedApps()
<dangerous-button <dangerous-button
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']" :class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
@confirm="revokeApp(app.client_id)" @confirm="revokeApp(app.client_id)"
:title="t('components.auth.Settings.modal.revokeApp.header', {app: app.name})"
> >
{{ t('components.auth.Settings.button.revoke') }} {{ t('components.auth.Settings.button.revoke') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.revokeApp.header', {app: app.name}) }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.auth.Settings.modal.revokeApp.content.warning') }} {{ t('components.auth.Settings.modal.revokeApp.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.revokeAccess') }} {{ t('components.auth.Settings.button.revokeAccess') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</td> </td>
@ -648,29 +638,20 @@ fetchOwnedApps()
> >
{{ t('components.auth.Settings.button.edit') }} {{ t('components.auth.Settings.button.edit') }}
</Link> </Link>
<Button <DangerousButton
color="destructive"
:is-loading="isDeleting.has(app.client_id)" :is-loading="isDeleting.has(app.client_id)"
class="tiny" class="tiny"
@confirm="deleteApp(app.client_id)" @confirm="deleteApp(app.client_id)"
:title="t('components.auth.Settings.modal.deleteApp.header', {app: app.name})"
> >
{{ t('components.auth.Settings.button.remove') }} {{ t('components.auth.Settings.button.remove') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.deleteApp.header', {app: app.name}) }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.auth.Settings.modal.deleteApp.content.warning') }} {{ t('components.auth.Settings.modal.deleteApp.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.removeApp') }} {{ t('components.auth.Settings.button.removeApp') }}
</div>
</template> </template>
</Button> </DangerousButton>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -804,26 +785,18 @@ fetchOwnedApps()
/> />
</div> </div>
<dangerous-button <dangerous-button
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']" :is-loading="isDeletingAccount"
:disabled="!deleteAccountPassword || undefined"
:class="{danger: deleteAccountPassword}"
:action="deleteAccount" :action="deleteAccount"
:title="t('components.auth.Settings.modal.deleteAccount.header')"
> >
{{ t('components.auth.Settings.button.deleteAccount') }} {{ t('components.auth.Settings.button.deleteAccount') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.deleteAccount.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('components.auth.Settings.modal.deleteAccount.content.warning') }} {{ t('components.auth.Settings.modal.deleteAccount.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.deleteAccountConfirm') }} {{ t('components.auth.Settings.button.deleteAccountConfirm') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</Layout> </Layout>

View File

@ -6,6 +6,8 @@ import { computed, ref } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import PasswordInput from '~/components/forms/PasswordInput.vue' import PasswordInput from '~/components/forms/PasswordInput.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
@ -147,29 +149,20 @@ fetchToken()
:default-show="showToken" :default-show="showToken"
/> />
</div> </div>
<Button <DangerousButton
v-if="token" v-if="token"
destructive
:is-loading="isLoading" :is-loading="isLoading"
:action="requestNewToken" :action="requestNewToken"
:title="t('components.auth.SubsonicTokenForm.modal.newPassword.header')"
> >
{{ t('components.auth.SubsonicTokenForm.button.newPassword') }} {{ t('components.auth.SubsonicTokenForm.button.newPassword') }}
<template #modal-header>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.newPassword.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.newPassword.content.warning') }} {{ t('components.auth.SubsonicTokenForm.modal.newPassword.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }} {{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }}
</div>
</template> </template>
</Button> </DangerousButton>
<Button <Button
primary primary
v-else v-else
@ -178,30 +171,20 @@ fetchToken()
> >
{{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }} {{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }}
</Button> </Button>
<Button <DangerousButton
primary
v-if="token" v-if="token"
:is-loading="isLoading" :is-loading="isLoading"
color="destructive"
:action="disable" :action="disable"
:title="t('components.auth.SubsonicTokenForm.modal.disableSubsonic.header')"
> >
{{ t('components.auth.SubsonicTokenForm.button.disable') }} {{ t('components.auth.SubsonicTokenForm.button.disable') }}
<template #modal-header>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.content.warning') }} {{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.auth.SubsonicTokenForm.button.confirmDisable') }} {{ t('components.auth.SubsonicTokenForm.button.confirmDisable') }}
</div>
</template> </template>
</Button> </DangerousButton>
</template> </template>
</form> </form>
</template> </template>

View File

@ -4,6 +4,8 @@ import type { BackendError } from '~/types'
import { ref, computed, reactive, watch } from 'vue' import { ref, computed, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import DangerousButton from '~/components/common/DangerousButton.vue'
import axios from 'axios' import axios from 'axios'
interface Action { interface Action {
@ -203,33 +205,26 @@ const launchAction = async () => {
<div class="field"> <div class="field">
<dangerous-button <dangerous-button
v-if="selectAll || currentAction?.isDangerous" v-if="selectAll || currentAction?.isDangerous"
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']" :disabled="checked.length === 0 || undefined"
:isLoading="isLoading"
:confirm-color="currentAction?.confirmColor ?? 'success'" :confirm-color="currentAction?.confirmColor ?? 'success'"
:aria-label="labels.performAction" :aria-label="labels.performAction"
@confirm="launchAction" @confirm="launchAction"
:title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)"
> >
{{ t('components.common.ActionTable.button.go') }} {{ t('components.common.ActionTable.button.go') }}
<template #modal-header>
<p>
<span key="1">
{{ t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount) }}
</span>
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
<template v-if="currentAction?.confirmationMessage"> <template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }} {{ currentAction?.confirmationMessage }}
</template> </template>
<span v-else> <span v-else>
{{ t('components.common.ActionTable.modal.performAction.content.warning') }} {{ t('components.common.ActionTable.modal.performAction.content.warning') }}
</span> </span>
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div :aria-label="labels.performAction"> <span :aria-label="labels.performAction">
{{ t('components.common.ActionTable.button.launch') }} {{ t('components.common.ActionTable.button.launch') }}
</div> </span>
</template> </template>
</dangerous-button> </dangerous-button>
<button <button

View File

@ -2,27 +2,23 @@
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Modal from '~/components/ui/Modal.vue'
interface Events { interface Events {
(e: 'confirm'): void (e: 'confirm'): void
} }
interface Props { // Note that properties such as [disabled] and 'destructive' | 'primary' are inherited.
action?: () => void const props = defineProps<{
disabled?: boolean title?: string
confirmColor?: 'destructive' | 'primary' action?: () => void,
} confirmColor?:'success' | 'danger'
}>()
const { t } = useI18n() const { t } = useI18n()
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
action: () => undefined, // -> Button.onClick prop
disabled: false, // -> Pure html/css. Just add attribute `disabled` and the button is inert.
confirmColor: 'destructive'
})
const showModal = ref(false) const showModal = ref(false)
@ -36,22 +32,16 @@ const confirm = () => {
<template> <template>
<Button <Button
destructive destructive
:class="{ 'is-disabled': disabled }"
:disabled="disabled"
@click.prevent.stop="showModal = true" @click.prevent.stop="showModal = true"
> >
<slot /> <slot />
<SemanticModal <Modal
v-model:show="showModal" destructive
:title="t('components.common.DangerousButton.header.confirm')" v-model="showModal"
class="small" :title="title || t('components.common.DangerousButton.header.confirm')"
:cancel="t('components.common.DangerousButton.button.cancel')"
> >
<h4 class="header">
<slot name="modal-header">
{{ t('components.common.DangerousButton.header.confirm') }}
</slot>
</h4>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<slot name="modal-content" /> <slot name="modal-content" />
@ -59,14 +49,7 @@ const confirm = () => {
</div> </div>
<template #actions> <template #actions>
<Button <Button
secondary v-bind="{[{success: 'primary', danger: 'destructive'}[confirmColor || 'success']]: true}"
outline
@click="showModal = false"
>
{{ t('components.common.DangerousButton.button.cancel') }}
</Button>
<Button
:color="confirmColor"
@click="confirm" @click="confirm"
> >
<slot name="modal-confirm"> <slot name="modal-confirm">
@ -74,6 +57,6 @@ const confirm = () => {
</slot> </slot>
</Button> </Button>
</template> </template>
</SemanticModal> </Modal>
</Button> </Button>
</template> </template>

View File

@ -334,26 +334,16 @@ const alertProps = computed(() => {
<!--TODO: Make Dangerous Button hand through isLoading prop --> <!--TODO: Make Dangerous Button hand through isLoading prop -->
<dangerous-button <dangerous-button
v-if="canDelete" v-if="canDelete"
:class="['ui', {loading: isLoading}, 'basic danger button']" :isLoading="isLoading"
:action="remove" :action="remove"
:title="t('components.library.EditCard.modal.delete.header')"
> >
{{ t('components.library.EditCard.button.delete') }} {{ t('components.library.EditCard.button.delete') }}
<template #modal-header>
<p>
{{ t('components.library.EditCard.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('components.library.EditCard.modal.content.warning') }} {{ t('components.library.EditCard.modal.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('components.library.EditCard.button.delete') }} {{ t('components.library.EditCard.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</template> </template>

View File

@ -7,6 +7,8 @@ import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Events { interface Events {
(e: 'save', data: InstancePolicy): void (e: 'save', data: InstancePolicy): void
(e: 'delete'): void (e: 'delete'): void
@ -238,24 +240,16 @@ const remove = async () => {
</button> </button>
<dangerous-button <dangerous-button
v-if="object" v-if="object"
class="ui right floated basic danger button" style="float: right;"
@confirm="remove" @confirm="remove"
:title="t('components.manage.moderation.InstancePolicyForm.modal.delete.header')"
> >
{{ t('components.manage.moderation.InstancePolicyForm.button.delete') }} {{ t('components.manage.moderation.InstancePolicyForm.button.delete') }}
<template #modal-header>
<p>
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }} {{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }} {{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</form> </form>

View File

@ -5,11 +5,12 @@ import { useMarkdownRaw } from '~/composables/useMarkdown'
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Events { interface Events {
(e: 'deleted', uuid: string): void (e: 'deleted', uuid: string): void
} }
@ -64,27 +65,19 @@ const remove = async (note: Note) => {
</div> </div>
<div class="meta"> <div class="meta">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic borderless mini button']" :is-loading="isLoading"
low-height
@confirm="remove(note)" @confirm="remove(note)"
icon="bi-trash"
:title="t('components.manage.moderation.NotesThread.modal.delete.header')"
> >
<i class="trash icon" />
{{ t('components.manage.moderation.NotesThread.button.delete') }} {{ t('components.manage.moderation.NotesThread.button.delete') }}
<template #modal-header>
<p>
{{ t('components.manage.moderation.NotesThread.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }} {{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('components.manage.moderation.NotesThread.button.delete') }} {{ t('components.manage.moderation.NotesThread.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -11,6 +11,7 @@ import InstancePolicyModal from '~/components/manage/moderation/InstancePolicyMo
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue' import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
import NotesThread from '~/components/manage/moderation/NotesThread.vue' import NotesThread from '~/components/manage/moderation/NotesThread.vue'
import NoteForm from '~/components/manage/moderation/NoteForm.vue' import NoteForm from '~/components/manage/moderation/NoteForm.vue'
import DangerousButton from '~/components/common/DangerousButton.vue'
import useReportConfigs from '~/composables/moderation/useReportConfigs' import useReportConfigs from '~/composables/moderation/useReportConfigs'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
@ -446,21 +447,17 @@ const handleRemovedNote = (uuid: string) => {
> >
<dangerous-button <dangerous-button
v-if="action.dangerous && action.show(obj)" v-if="action.dangerous && action.show(obj)"
:class="['ui', {loading: isLoading}, 'button']" :is-loading="isLoading"
:action="action.handler" :action="action.handler"
:title="action.modalHeader"
:icon="`${action.iconColor} ${action.icon}`"
> >
<i :class="[action.iconColor, action.icon, 'icon']" />&nbsp;
{{ action.label }} {{ action.label }}
<template #modal-header>
<p>{{ action.modalHeader }}</p>
</template>
<template #modal-content> <template #modal-content>
<div> {{ action.modalContent }}
<p>{{ action.modalContent }}</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>{{ action.modalConfirmLabel }}</p> {{ action.modalConfirmLabel }}
</template> </template>
</dangerous-button> </dangerous-button>
</template> </template>

View File

@ -13,7 +13,9 @@ import { useStore } from '~/store'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import PlaylistForm from '~/components/playlists/Form.vue' import PlaylistForm from '~/components/playlists/Form.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
@ -237,25 +239,17 @@ const insertMany = async (insertedTracks: number[], allowDuplicates: boolean) =>
<dangerous-button <dangerous-button
:disabled="tracks.length === 0" :disabled="tracks.length === 0"
class="ui labeled right floated danger icon button" icon="bi-eraser-fill"
style="float: right;"
:action="clearPlaylist" :action="clearPlaylist"
:title="t('components.playlists.Editor.modal.clearPlaylist.header', { playlist: playlist?.name })"
> >
<i class="eraser icon" />
{{ t('components.playlists.Editor.button.clear') }} {{ t('components.playlists.Editor.button.clear') }}
<template #modal-header>
<p>
{{ t('components.playlists.Editor.modal.clearPlaylist.header', {playlist: playlist?.name}) }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('components.playlists.Editor.modal.clearPlaylist.content.warning') }} {{ t('components.playlists.Editor.modal.clearPlaylist.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('components.playlists.Editor.button.clear') }} {{ t('components.playlists.Editor.button.clear') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
<div class="ui hidden divider" /> <div class="ui hidden divider" />

View File

@ -12,6 +12,8 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props { interface Props {
id: number id: number
} }
@ -172,26 +174,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.ChannelDetail.modal.delete.header')"
> >
{{ t('views.admin.ChannelDetail.button.delete') }} {{ t('views.admin.ChannelDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.ChannelDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.ChannelDetail.modal.delete.content.warning') }} {{ t('views.admin.ChannelDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.ChannelDetail.button.delete') }} {{ t('views.admin.ChannelDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -7,6 +7,7 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import FetchButton from '~/components/federation/FetchButton.vue' import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
@ -193,26 +194,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.AlbumDetail.modal.delete.header')"
> >
{{ t('views.admin.library.AlbumDetail.button.delete') }} {{ t('views.admin.library.AlbumDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.AlbumDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.AlbumDetail.modal.delete.content.warning') }} {{ t('views.admin.library.AlbumDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.AlbumDetail.button.delete') }} {{ t('views.admin.library.AlbumDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -7,6 +7,7 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import FetchButton from '~/components/federation/FetchButton.vue' import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
@ -181,37 +182,27 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</button> </button>
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<router-link <Link solid secondary
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="ui labeled icon button" class="ui labeled icon button"
icon="bi-pencil-fill"
> >
<i class="edit icon" />
{{ t('views.admin.library.ArtistDetail.button.edit') }} {{ t('views.admin.library.ArtistDetail.button.edit') }}
</router-link> </Link>
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.ArtistDetail.modal.delete.header')"
> >
{{ t('views.admin.library.ArtistDetail.button.delete') }} {{ t('views.admin.library.ArtistDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.ArtistDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.ArtistDetail.modal.delete.content.warning') }} {{ t('views.admin.library.ArtistDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.ArtistDetail.button.delete') }} {{ t('views.admin.library.ArtistDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -13,6 +13,8 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useLogger from '~/composables/useLogger' import useLogger from '~/composables/useLogger'
import DangerousButton from '~/components/common/DangerousButton.vue'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[] const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Props { interface Props {
@ -168,26 +170,16 @@ const updateObj = async (attr: string) => {
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.LibraryDetail.modal.delete.header')"
> >
{{ t('views.admin.library.LibraryDetail.button.delete') }} {{ t('views.admin.library.LibraryDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.LibraryDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.LibraryDetail.modal.delete.content.warning') }} {{ t('views.admin.library.LibraryDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.LibraryDetail.button.delete') }} {{ t('views.admin.library.LibraryDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -9,6 +9,8 @@ import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props { interface Props {
id: number id: number
} }
@ -105,26 +107,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.TagDetail.modal.delete.header')"
> >
{{ t('views.admin.library.TagDetail.button.delete') }} {{ t('views.admin.library.TagDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.TagDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.TagDetail.modal.delete.content.warning') }} {{ t('views.admin.library.TagDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.TagDetail.button.delete') }} {{ t('views.admin.library.TagDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -12,6 +12,8 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props { interface Props {
id: number id: number
} }
@ -193,26 +195,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.TrackDetail.modal.delete.header')"
> >
{{ t('views.admin.library.TrackDetail.button.delete') }} {{ t('views.admin.library.TrackDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.TrackDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.TrackDetail.modal.delete.content.warning') }} {{ t('views.admin.library.TrackDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.TrackDetail.button.delete') }} {{ t('views.admin.library.TrackDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -15,6 +15,8 @@ import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props { interface Props {
id: number id: number
} }
@ -155,26 +157,16 @@ const showUploadDetailModal = ref(false)
</div> </div>
<div class="ui buttons"> <div class="ui buttons">
<dangerous-button <dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']" :is-loading="isLoading"
:action="remove" :action="remove"
:title="t('views.admin.library.UploadDetail.modal.delete.header')"
> >
{{ t('views.admin.library.UploadDetail.button.delete') }} {{ t('views.admin.library.UploadDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.UploadDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.admin.library.UploadDetail.modal.delete.content.warning') }} {{ t('views.admin.library.UploadDetail.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.admin.library.UploadDetail.button.delete') }} {{ t('views.admin.library.UploadDetail.button.delete') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -11,6 +11,7 @@ import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
import DangerousButton from '~/components/common/DangerousButton.vue'
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'
@ -305,22 +306,13 @@ const updateSubscriptionCount = (delta: number) => {
</PopoverItem> </PopoverItem>
<dangerous-button <dangerous-button
v-if="object" v-if="object"
:class="['ui', {loading: isLoading}, 'item']" :title="t('views.channels.DetailBase.button.delete')"
:is-loading="isLoading"
icon="bi-trash"
@confirm="remove()" @confirm="remove()"
> >
<i class="bi bi-trash" />
{{ t('views.channels.DetailBase.button.delete') }}
<template #modal-header>
<p>
{{ t('views.channels.DetailBase.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.channels.DetailBase.modal.delete.content.warning') }} {{ t('views.channels.DetailBase.modal.delete.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p> <p>

View File

@ -9,6 +9,8 @@ import axios from 'axios'
import useSharedLabels from '~/composables/locale/useSharedLabels' import useSharedLabels from '~/composables/locale/useSharedLabels'
import DangerousButton from '~/components/common/DangerousButton.vue'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[] const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Events { interface Events {
@ -169,25 +171,16 @@ const remove = async () => {
</button> </button>
<dangerous-button <dangerous-button
v-if="library" v-if="library"
type="button" style="float: right;"
class="ui right floated basic danger button"
@confirm="remove" @confirm="remove"
:title="t('views.content.libraries.Form.modal.delete.header')"
> >
{{ t('views.content.libraries.Form.button.delete') }} {{ t('views.content.libraries.Form.button.delete') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Form.modal.delete.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('views.content.libraries.Form.modal.delete.content.warning') }} {{ t('views.content.libraries.Form.modal.delete.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('views.content.libraries.Form.button.confirm') }} {{ t('views.content.libraries.Form.button.confirm') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</form> </form>

View File

@ -10,6 +10,8 @@ import useErrorHandler from '~/composables/useErrorHandler'
import axios from 'axios' import axios from 'axios'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import DangerousButton from '~/components/common/DangerousButton.vue'
const { t } = useI18n() const { t } = useI18n()
const quotaStatus = ref() const quotaStatus = ref()
@ -111,24 +113,16 @@ const purgeErroredFiles = () => purge('errored')
</router-link> </router-link>
<dangerous-button <dangerous-button
class="ui basic tiny button" low-height
:action="purgePendingFiles" :action="purgePendingFiles"
:title="t('views.content.libraries.Quota.modal.purgePending.header')"
> >
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgePending.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgePending.content.description') }} {{ t('views.content.libraries.Quota.modal.purgePending.content.description') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>
@ -153,24 +147,16 @@ const purgeErroredFiles = () => purge('errored')
{{ t('views.content.libraries.Quota.link.viewFiles') }} {{ t('views.content.libraries.Quota.link.viewFiles') }}
</router-link> </router-link>
<dangerous-button <dangerous-button
class="ui basic tiny button" low-height
:action="purgeSkippedFiles" :action="purgeSkippedFiles"
:title="t('views.content.libraries.Quota.modal.purgeSkipped.header')"
> >
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgeSkipped.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgeSkipped.content.description') }} {{ t('views.content.libraries.Quota.modal.purgeSkipped.content.description') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>
@ -195,24 +181,16 @@ const purgeErroredFiles = () => purge('errored')
{{ t('views.content.libraries.Quota.link.viewFiles') }} {{ t('views.content.libraries.Quota.link.viewFiles') }}
</router-link> </router-link>
<dangerous-button <dangerous-button
class="ui basic tiny button" low-height
:action="purgeErroredFiles" :action="purgeErroredFiles"
:title="t('views.content.libraries.Quota.modal.purgeErrored.header')"
> >
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgeErrored.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgeErrored.content.description') }} {{ t('views.content.libraries.Quota.modal.purgeErrored.content.description') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }} {{ t('views.content.libraries.Quota.button.purge') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</div> </div>

View File

@ -8,6 +8,7 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import RadioButton from '~/components/radios/Button.vue' import RadioButton from '~/components/radios/Button.vue'
import Card from '~/components/ui/Card.vue' import Card from '~/components/ui/Card.vue'
import OptionsButton from '~/components/ui/button/Options.vue' import OptionsButton from '~/components/ui/button/Options.vue'
@ -321,26 +322,15 @@ const isOpen = ref(false)
</template> </template>
<template v-else-if="library.follow.approved"> <template v-else-if="library.follow.approved">
<dangerous-button <dangerous-button
:class="['ui', 'button']"
:action="unfollow" :action="unfollow"
:title="t('views.content.remote.Card.modal.unfollow.header')"
> >
{{ t('views.content.remote.Card.button.unfollow') }} {{ t('views.content.remote.Card.button.unfollow') }}
<template #modal-header>
<p>
{{ t('views.content.remote.Card.modal.unfollow.header') }}
</p>
</template>
<template #modal-content> <template #modal-content>
<div>
<p>
{{ t('views.content.remote.Card.modal.unfollow.content.warning') }} {{ t('views.content.remote.Card.modal.unfollow.content.warning') }}
</p>
</div>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div>
{{ t('views.content.remote.Card.button.unfollow') }} {{ t('views.content.remote.Card.button.unfollow') }}
</div>
</template> </template>
</dangerous-button> </dangerous-button>
</template> </template>

View File

@ -8,12 +8,13 @@ import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import TrackTable from '~/components/audio/track/Table.vue' import TrackTable from '~/components/audio/track/Table.vue'
import RadioButton from '~/components/radios/Button.vue' import RadioButton from '~/components/radios/Button.vue'
import Layout from '~/components/ui/Layout.vue' import Layout from '~/components/ui/Layout.vue'
import Header from '~/components/ui/Header.vue' import Header from '~/components/ui/Header.vue'
import Section from '~/components/ui/Section.vue' import Section from '~/components/ui/Section.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Pagination from '~/components/ui/Pagination.vue' import Pagination from '~/components/ui/Pagination.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
@ -98,24 +99,16 @@ const deleteRadio = async () => {
{{ t('views.radios.Detail.button.edit') }} {{ t('views.radios.Detail.button.edit') }}
</Button> </Button>
<dangerous-button <dangerous-button
class="ui labeled danger icon button"
:action="deleteRadio" :action="deleteRadio"
:title="t('views.radios.Detail.modal.delete.header', {radio: radio.name})"
icon="bi-trash"
> >
<i class="bi bi-trash" /> {{ t('views.radios.Detail.button.delete') }} {{ t('views.radios.Detail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.radios.Detail.modal.delete.header', {radio: radio.name}) }}
</p>
</template>
<template #modal-content> <template #modal-content>
<p>
{{ t('views.radios.Detail.modal.delete.content.warning') }} {{ t('views.radios.Detail.modal.delete.content.warning') }}
</p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p>
{{ t('views.radios.Detail.button.confirm') }} {{ t('views.radios.Detail.button.confirm') }}
</p>
</template> </template>
</dangerous-button> </dangerous-button>
</template> </template>
@ -127,9 +120,8 @@ const deleteRadio = async () => {
<track-table :tracks="tracks" /> <track-table :tracks="tracks" />
<Pagination <Pagination
v-if="totalTracks > 25" v-if="totalTracks > 25"
v-model:current="page" v-model:page="page"
:paginate-by="25" :pages="Math.ceil(totalTracks / 25)"
:total="totalTracks"
/> />
</Section> </Section>
<Alert <Alert