Added song loading state
This commit is contained in:
1 parent
869ca7d728
commit
e908ae27ad
3 files changed
+38
-10
No files matched your search
@@ -47,6 +47,8 @@ interface ModalFormProps<T> {
|
|||||||
fields: Field<T>[]
|
fields: Field<T>[]
|
||||||
title?: string
|
title?: string
|
||||||
onSave: (value: any) => Promise<boolean>
|
onSave: (value: any) => Promise<boolean>
|
||||||
|
loading?: boolean
|
||||||
|
loadingText?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<ModalFormProps<any>>()
|
const props = defineProps<ModalFormProps<any>>()
|
||||||
@@ -150,7 +152,10 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Button type="submit" full center-text>Save</Button>
|
<Button type="submit" full center-text :disabled="props.loading">
|
||||||
|
<span v-if="props.loading" class="spinner" aria-hidden="true" />
|
||||||
|
{{ props.loading ? props.loadingText || "Saving…" : "Save" }}
|
||||||
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
@@ -169,4 +174,19 @@ watch(
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.8rem;
|
gap: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
border: 2px solid currentColor;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.7s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -106,6 +106,7 @@ const getOptionId = (option: string | Option) =>
|
|||||||
|
|
||||||
const showModal = ref<boolean>(false)
|
const showModal = ref<boolean>(false)
|
||||||
const item = ref<Track | undefined>(undefined)
|
const item = ref<Track | undefined>(undefined)
|
||||||
|
const addingSong = ref(false)
|
||||||
|
|
||||||
const save = async (value: any) => {
|
const save = async (value: any) => {
|
||||||
if (value._id) {
|
if (value._id) {
|
||||||
@@ -127,7 +128,6 @@ const save = async (value: any) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
|
||||||
if (!value.name || !value.file || !value.artists?.length) {
|
if (!value.name || !value.file || !value.artists?.length) {
|
||||||
toast.show(
|
toast.show(
|
||||||
"Name, audio file, and at least one artist are required",
|
"Name, audio file, and at least one artist are required",
|
||||||
@@ -136,8 +136,7 @@ const save = async (value: any) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.createSingle) {
|
if (value.createSingle && (!value.cover || !value.genre)) {
|
||||||
if (!value.cover || !value.genre) {
|
|
||||||
toast.show(
|
toast.show(
|
||||||
"A cover image and genre are required for a single",
|
"A cover image and genre are required for a single",
|
||||||
"error",
|
"error",
|
||||||
@@ -145,6 +144,15 @@ const save = async (value: any) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!value.createSingle && !value.album) {
|
||||||
|
toast.show("Choose an album or create a single album", "error")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
addingSong.value = true
|
||||||
|
await nextTick()
|
||||||
|
try {
|
||||||
|
if (value.createSingle) {
|
||||||
const albumResponse = await cfetch<{ _id: string }>("/admin/album", {
|
const albumResponse = await cfetch<{ _id: string }>("/admin/album", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: {
|
data: {
|
||||||
@@ -155,12 +163,6 @@ const save = async (value: any) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
value.album = albumResponse._id
|
value.album = albumResponse._id
|
||||||
} else if (!value.album) {
|
|
||||||
toast.show(
|
|
||||||
"Choose an album or create a single album",
|
|
||||||
"error",
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await cfetch("/admin/track", {
|
await cfetch("/admin/track", {
|
||||||
@@ -178,6 +180,8 @@ const save = async (value: any) => {
|
|||||||
toast.show("Failed to add song", "error")
|
toast.show("Failed to add song", "error")
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
} finally {
|
||||||
|
addingSong.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +245,8 @@ useHead({ title: "Admin - Songs" })
|
|||||||
:fields="modalFields"
|
:fields="modalFields"
|
||||||
:item="item"
|
:item="item"
|
||||||
:title="item ? 'Edit song' : 'Add new song'"
|
:title="item ? 'Edit song' : 'Add new song'"
|
||||||
|
:loading="addingSong"
|
||||||
|
:loading-text="'Adding song…'"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface ButtonProps {
|
|||||||
full?: boolean
|
full?: boolean
|
||||||
iconName?: string
|
iconName?: string
|
||||||
centerText?: boolean
|
centerText?: boolean
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||||
@@ -22,6 +23,7 @@ const props = withDefaults(defineProps<ButtonProps>(), {
|
|||||||
props.type == 'link' && props.to != undefined ? NuxtLink : 'button'
|
props.type == 'link' && props.to != undefined ? NuxtLink : 'button'
|
||||||
"
|
"
|
||||||
:to="props.to"
|
:to="props.to"
|
||||||
|
:disabled="props.disabled"
|
||||||
:class="[
|
:class="[
|
||||||
`button type-${props.variant}`,
|
`button type-${props.variant}`,
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user