4
0
mirror of https://github.com/AzuraCast/AzuraCast.git synced 2024-06-14 05:06:37 +00:00

Restructure main Vue app invocation to support using BootstrapVue components in Composition API.

This commit is contained in:
Buster Neece 2022-12-21 04:47:12 -06:00
parent 250dafcbb1
commit f20a78ccad
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
5 changed files with 235 additions and 305 deletions

View File

@ -1,39 +1,91 @@
import installPinia from './vendor/pinia'; import installPinia from './vendor/pinia';
import gettext from './vendor/gettext'; import gettext from './vendor/gettext';
import {createApp} from "vue"; import {createApp, h} from "vue";
import installBootstrapVue from "./vendor/bootstrapVue"; import installBootstrapVue from "./vendor/bootstrapVue";
import installSweetAlert from "./vendor/sweetalert"; import installSweetAlert from "./vendor/sweetalert";
import installAxios from "~/vendor/axios"; import installAxios from "~/vendor/axios";
import useAzuraCast from "~/vendor/azuracast";
import axios from "axios";
import {useEventBus} from "@vueuse/core";
export default function (component, options) { export default function (component) {
return function (el, props) { const vueApp = createApp({
const vueApp = createApp( render() {
component, return h(component, this.$appProps)
{ },
...options, mounted() {
...props // Workaround to use BootstrapVue toast notifications in Vue 3 composition API.
} const notifyBus = useEventBus('notify');
);
/* Gettext */ notifyBus.on((event, payload) => {
if (typeof App.locale !== 'undefined') { if (event === 'show') {
vueApp.config.language = App.locale; this.$bvToast.toast(payload.message, payload.options);
} else if (event === 'hide') {
this.$bvToast.hide(payload.id);
}
});
// Configure some Axios settings that depend on the BootstrapVue $bvToast superglobal.
const handleAxiosError = (error) => {
const {$gettext} = gettext;
let notifyMessage = $gettext('An error occurred and your request could not be completed.');
if (error.response) {
// Request made and server responded
notifyMessage = error.response.data.message;
console.error(notifyMessage);
} else if (error.request) {
// The request was made but no response was received
console.error(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
}
if (typeof this.$notifyError === 'function') {
this.$notifyError(notifyMessage);
}
};
axios.interceptors.request.use((config) => {
return config;
}, (error) => {
handleAxiosError(error);
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
handleAxiosError(error);
return Promise.reject(error);
});
} }
});
vueApp.use(gettext); /* Gettext */
const {locale} = useAzuraCast();
/* Axios */ if (typeof locale !== 'undefined') {
installAxios(vueApp); vueApp.config.language = locale;
}
/* Pinia */ vueApp.use(gettext);
installPinia(vueApp);
/* Bootstrap Vue */ /* Axios */
installBootstrapVue(vueApp); installAxios(vueApp);
/* SweetAlert */ /* Pinia */
installSweetAlert(vueApp); installPinia(vueApp);
/* Bootstrap Vue */
installBootstrapVue(vueApp);
/* SweetAlert */
installSweetAlert(vueApp);
return function (el, props) {
vueApp.config.globalProperties.$appProps = props;
vueApp.mount(el); vueApp.mount(el);
}; };
} }

View File

@ -1,10 +1,10 @@
<template> <template>
<modal-form ref="modal" size="lg" :title="langTitle" :loading="loading" :disable-save-button="v$.form.$invalid" <modal-form ref="modal" size="lg" :title="$gettext('Configure Backups')" :loading="loading"
@submit="submit" @hidden="clearContents"> :disable-save-button="v$.$invalid" @submit="submit" @hidden="clearContents">
<b-form-fieldset> <b-form-fieldset>
<div class="form-row mb-3"> <div class="form-row mb-3">
<b-wrapped-form-checkbox class="col-md-12" id="form_edit_backup_enabled" <b-wrapped-form-checkbox class="col-md-12" id="form_edit_backup_enabled"
:field="v$.form.backup_enabled"> :field="v$.backup_enabled">
<template #label> <template #label>
{{ $gettext('Run Automatic Nightly Backups') }} {{ $gettext('Run Automatic Nightly Backups') }}
</template> </template>
@ -16,8 +16,8 @@
</b-wrapped-form-checkbox> </b-wrapped-form-checkbox>
</div> </div>
<div class="form-row" v-if="v$.form.backup_enabled.$model"> <div class="form-row" v-if="v$.backup_enabled.$model">
<b-wrapped-form-group class="col-md-6" id="form_backup_time_code" :field="v$.form.backup_time_code"> <b-wrapped-form-group class="col-md-6" id="form_backup_time_code" :field="v$.backup_time_code">
<template #label> <template #label>
{{ $gettext('Scheduled Backup Time') }} {{ $gettext('Scheduled Backup Time') }}
</template> </template>
@ -30,7 +30,7 @@
</b-wrapped-form-group> </b-wrapped-form-group>
<b-wrapped-form-checkbox class="col-md-6" id="form_edit_exclude_media" <b-wrapped-form-checkbox class="col-md-6" id="form_edit_exclude_media"
:field="v$.form.backup_exclude_media"> :field="v$.backup_exclude_media">
<template #label> <template #label>
{{ $gettext('Exclude Media from Backup') }} {{ $gettext('Exclude Media from Backup') }}
</template> </template>
@ -41,7 +41,7 @@
</template> </template>
</b-wrapped-form-checkbox> </b-wrapped-form-checkbox>
<b-wrapped-form-group class="col-md-6" id="form_backup_keep_copies" :field="v$.form.backup_keep_copies" <b-wrapped-form-group class="col-md-6" id="form_backup_keep_copies" :field="v$.backup_keep_copies"
input-type="number" :input-attrs="{min: '0', max: '365'}"> input-type="number" :input-attrs="{min: '0', max: '365'}">
<template #label> <template #label>
{{ $gettext('Number of Backup Copies to Keep') }} {{ $gettext('Number of Backup Copies to Keep') }}
@ -54,7 +54,7 @@
</b-wrapped-form-group> </b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_backup_storage_location" <b-wrapped-form-group class="col-md-6" id="edit_form_backup_storage_location"
:field="v$.form.backup_storage_location"> :field="v$.backup_storage_location">
<template #label> <template #label>
{{ $gettext('Storage Location') }} {{ $gettext('Storage Location') }}
</template> </template>
@ -64,7 +64,7 @@
</template> </template>
</b-wrapped-form-group> </b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_backup_format" :field="v$.form.backup_format"> <b-wrapped-form-group class="col-md-6" id="edit_form_backup_format" :field="v$.backup_format">
<template #label> <template #label>
{{ $gettext('Backup Format') }} {{ $gettext('Backup Format') }}
</template> </template>
@ -78,9 +78,7 @@
</modal-form> </modal-form>
</template> </template>
<script> <script setup>
import useVuelidate from "@vuelidate/core";
import CodemirrorTextarea from "~/components/Common/CodemirrorTextarea";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup"; import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import ModalForm from "~/components/Common/ModalForm"; import ModalForm from "~/components/Common/ModalForm";
import BFormFieldset from "~/components/Form/BFormFieldset"; import BFormFieldset from "~/components/Form/BFormFieldset";
@ -88,113 +86,113 @@ import mergeExisting from "~/functions/mergeExisting";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox"; import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import TimeCode from "~/components/Common/TimeCode"; import TimeCode from "~/components/Common/TimeCode";
import objectToFormOptions from "~/functions/objectToFormOptions"; import objectToFormOptions from "~/functions/objectToFormOptions";
import {computed, ref} from "vue";
import {useAxios} from "~/vendor/axios";
import {useNotify} from "~/vendor/bootstrapVue";
import useVuelidate from "@vuelidate/core";
export default { const props = defineProps({
name: 'AdminBackupsConfigureModal', settingsUrl: String,
emits: ['relist'], storageLocations: Object
setup() { });
return {v$: useVuelidate()}
}, const emit = defineEmits(['relist']);
props: {
settingsUrl: String, const loading = ref(true);
storageLocations: Object const error = ref(null);
},
components: { const form = ref({});
ModalForm,
BFormFieldset, const modal = ref(); // Template Ref
BWrappedFormGroup,
BWrappedFormCheckbox, const validations = {
CodemirrorTextarea, 'backup_enabled': {},
TimeCode 'backup_time_code': {},
}, 'backup_exclude_media': {},
data() { 'backup_keep_copies': {},
return { 'backup_storage_location': {},
loading: true, 'backup_format': {},
error: null, };
form: {},
}; const v$ = useVuelidate(validations, form);
},
validations: { const storageLocationOptions = computed(() => {
form: { return objectToFormOptions(props.storageLocations);
'backup_enabled': {}, });
'backup_time_code': {},
'backup_exclude_media': {}, const formatOptions = computed(() => {
'backup_keep_copies': {}, return [
'backup_storage_location': {}, {
'backup_format': {}, value: 'zip',
text: 'Zip',
},
{
value: 'tgz',
text: 'TarGz'
},
{
value: 'tzst',
text: 'ZStd'
} }
}, ];
computed: { });
langTitle() {
return this.$gettext('Configure Backups');
},
storageLocationOptions() {
return objectToFormOptions(this.storageLocations);
},
formatOptions() {
return [
{
value: 'zip',
text: 'Zip',
},
{
value: 'tgz',
text: 'TarGz'
},
{
value: 'tzst',
text: 'ZStd'
}
];
},
},
methods: {
open() {
this.clearContents();
this.loading = true;
this.$refs.modal.show(); const clearContents = () => {
v$.value.$reset();
this.axios.get(this.settingsUrl).then((resp) => { form.value = {
this.form = mergeExisting(this.form, resp.data); backup_enabled: false,
this.loading = false; backup_time_code: null,
}).catch(() => { backup_exclude_media: null,
this.close(); backup_keep_copies: null,
}); backup_storage_location: null,
}, backup_format: null,
clearContents() { };
this.v$.$reset(); };
this.form = { const {axios} = useAxios();
backup_enabled: false,
backup_time_code: null,
backup_exclude_media: null,
backup_keep_copies: null,
backup_storage_location: null,
backup_format: null,
};
},
close() {
this.$emit('relist');
this.$refs.modal.hide();
},
submit() {
this.v$.$touch();
if (this.v$.$errors.length > 0) {
return;
}
this.$wrapWithLoading( const close = () => {
this.axios({ emit('relist');
method: 'PUT', modal.value.hide();
url: this.settingsUrl, };
data: this.form
}) const open = () => {
).then(() => { clearContents();
this.$notifySuccess(); loading.value = true;
this.close();
}); modal.value.show();
}
axios.get(props.settingsUrl).then((resp) => {
form.value = mergeExisting(form.value, resp.data);
loading.value = false;
}).catch(() => {
close();
});
};
const {wrapWithLoading, notifySuccess} = useNotify();
const submit = () => {
v$.value.$touch();
if (v$.value.$errors.length > 0) {
return;
} }
wrapWithLoading(
axios({
method: 'PUT',
url: props.settingsUrl,
data: form.value
})
).then(() => {
notifySuccess();
close();
});
} }
defineExpose({
open
});
</script> </script>

View File

@ -1,8 +1,8 @@
<template> <template>
<modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="v$.form.$invalid" <modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="v$.$invalid"
@submit="doSubmit" @hidden="clearContents"> @submit="doSubmit" @hidden="clearContents">
<admin-custom-fields-form :form="v$.form" :auto-assign-types="autoAssignTypes"> <admin-custom-fields-form :form="v$" :auto-assign-types="autoAssignTypes">
</admin-custom-fields-form> </admin-custom-fields-form>
</modal-form> </modal-form>
@ -13,14 +13,39 @@ import useVuelidate from "@vuelidate/core";
import {required} from '@vuelidate/validators'; import {required} from '@vuelidate/validators';
import BaseEditModal from '~/components/Common/BaseEditModal'; import BaseEditModal from '~/components/Common/BaseEditModal';
import AdminCustomFieldsForm from "~/components/Admin/CustomFields/Form"; import AdminCustomFieldsForm from "~/components/Admin/CustomFields/Form";
import {ref} from "vue";
export default { export default {
name: 'AdminCustomFieldsEditModal', name: 'AdminCustomFieldsEditModal',
mixins: [BaseEditModal],
components: {AdminCustomFieldsForm}, components: {AdminCustomFieldsForm},
setup() { setup() {
return {v$: useVuelidate()} const blankForm = {
'name': '',
'short_name': '',
'auto_assign': ''
};
const form = ref(blankForm);
const resetForm = () => {
form.value = blankForm;
}
const validations = {
'name': {required},
'short_name': {},
'auto_assign': {}
};
const v$ = useVuelidate(validations, form);
return {
form,
resetForm,
v$
};
}, },
mixins: [BaseEditModal],
props: { props: {
autoAssignTypes: Object autoAssignTypes: Object
}, },
@ -31,23 +56,5 @@ export default {
: this.$gettext('Add Custom Field'); : this.$gettext('Add Custom Field');
} }
}, },
validations() {
return {
form: {
'name': {required},
'short_name': {},
'auto_assign': {}
}
};
},
methods: {
resetForm() {
this.form = {
'name': '',
'short_name': '',
'auto_assign': ''
};
}
}
}; };
</script> </script>

View File

@ -1,6 +1,5 @@
import axios from "axios"; import axios from "axios";
import VueAxios from "vue-axios"; import VueAxios from "vue-axios";
import gettext from "~/vendor/gettext";
import {inject} from "vue"; import {inject} from "vue";
import useAzuraCast from "~/vendor/azuracast"; import useAzuraCast from "~/vendor/azuracast";
@ -20,45 +19,6 @@ export default function installAxios(vueApp) {
} }
vueApp.use(VueAxios, axios); vueApp.use(VueAxios, axios);
vueApp.provide('axios', axios); vueApp.provide('axios', axios);
vueApp.mixin({
mounted() {
const handleAxiosError = (error) => {
const {$gettext} = gettext;
let notifyMessage = $gettext('An error occurred and your request could not be completed.');
if (error.response) {
// Request made and server responded
notifyMessage = error.response.data.message;
console.error(notifyMessage);
} else if (error.request) {
// The request was made but no response was received
console.error(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
}
if (typeof this.$notifyError === 'function') {
this.$notifyError(notifyMessage);
}
};
axios.interceptors.request.use((config) => {
return config;
}, (error) => {
handleAxiosError(error);
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
handleAxiosError(error);
return Promise.reject(error);
});
}
});
} }

View File

@ -1,15 +1,15 @@
import {BootstrapVue} from 'bootstrap-vue'; import {BootstrapVue} from 'bootstrap-vue';
import 'bootstrap-vue/dist/bootstrap-vue.css'; import 'bootstrap-vue/dist/bootstrap-vue.css';
import {inject} from "vue";
import gettext from "~/vendor/gettext"; import gettext from "~/vendor/gettext";
import {useEventBus} from "@vueuse/core";
/* Composition API BootstrapVue utilities */ /* Composition API BootstrapVue utilities */
export function useNotify() { export function useNotify() {
const $bvToast = inject('bvToast');
const {$gettext} = gettext; const {$gettext} = gettext;
const notifyBus = useEventBus('notify');
const notify = function (message = null, options = {}) { const notify = (message = null, options = {}) => {
if (!!document.hidden) { if (!!document.hidden) {
return; return;
} }
@ -21,7 +21,10 @@ export function useNotify() {
solid: true solid: true
}; };
$bvToast.toast(message, {...defaults, ...options}); notifyBus.emit('show', {
message: message,
options: {...defaults, ...options}
});
}; };
const notifyError = (message = null, options = {}) => { const notifyError = (message = null, options = {}) => {
@ -74,7 +77,9 @@ export function useNotify() {
}; };
const hideLoading = () => { const hideLoading = () => {
$bvToast.hide(LOADING_TOAST_ID); notifyBus.emit('hide', {
id: LOADING_TOAST_ID
});
}; };
let $isAxiosLoading = false; let $isAxiosLoading = false;
@ -109,6 +114,15 @@ export function useNotify() {
}; };
return { return {
install(app) {
app.config.globalProperties.$notify = notify;
app.config.globalProperties.$notifyError = notifyError;
app.config.globalProperties.$notifySuccess = notifySuccess;
app.config.globalProperties.$showLoading = showLoading;
app.config.globalProperties.$hideLoading = hideLoading;
app.config.globalProperties.$setLoading = setLoading;
app.config.globalProperties.$wrapWithLoading = wrapWithLoading;
},
notify, notify,
notifyError, notifyError,
notifySuccess, notifySuccess,
@ -121,106 +135,5 @@ export function useNotify() {
export default function installBootstrapVue(vueApp) { export default function installBootstrapVue(vueApp) {
vueApp.use(BootstrapVue); vueApp.use(BootstrapVue);
vueApp.use(useNotify());
vueApp.provide('bvToast', vueApp.config.globalProperties.$bvToast);
vueApp.provide('bvModal', vueApp.config.globalProperties.$bvModal);
vueApp.config.globalProperties.$notify = function (message = null, options = {}) {
if (!!document.hidden) {
return;
}
const defaults = {
variant: 'default',
toaster: 'b-toaster-top-right',
autoHideDelay: 3000,
solid: true
};
this.$bvToast.toast(message, {...defaults, ...options});
};
vueApp.config.globalProperties.$notifyError = function (message = null, options = {}) {
if (message === null) {
message = this.$gettext('An error occurred and your request could not be completed.');
}
const defaults = {
variant: 'danger',
title: this.$gettext('Error')
};
this.$notify(message, {...defaults, ...options});
return message;
};
vueApp.config.globalProperties.$notifySuccess = function (message = null, options = {}) {
if (message === null) {
message = this.$gettext('Changes saved.');
}
const defaults = {
variant: 'success',
title: this.$gettext('Success')
};
this.$notify(message, {...defaults, ...options});
return message;
};
const LOADING_TOAST_ID = 'toast-loading';
vueApp.config.globalProperties.$showLoading = function (message = null, options = {}) {
if (message === null) {
message = this.$gettext('Applying changes...');
}
const defaults = {
id: LOADING_TOAST_ID,
variant: 'warning',
title: this.$gettext('Please wait...'),
autoHideDelay: 10000,
isStatus: true
};
this.$notify(message, {...defaults, ...options});
return message;
};
vueApp.config.globalProperties.$hideLoading = function () {
this.$bvToast.hide(LOADING_TOAST_ID);
};
let $isAxiosLoading = false;
let $axiosLoadCount = 0;
vueApp.config.globalProperties.$setLoading = function (isLoading) {
let prevIsLoading = $isAxiosLoading;
if (isLoading) {
$axiosLoadCount++;
$isAxiosLoading = true;
} else if ($axiosLoadCount > 0) {
$axiosLoadCount--;
$isAxiosLoading = ($axiosLoadCount > 0);
}
// Handle state changes
if (!prevIsLoading && $isAxiosLoading) {
this.$showLoading();
} else if (prevIsLoading && !$isAxiosLoading) {
this.$hideLoading();
}
};
vueApp.config.globalProperties.$wrapWithLoading = function (promise) {
this.$setLoading(true);
promise.finally(() => {
this.$setLoading(false);
});
return promise;
};
}; };