4
0
mirror of https://github.com/AzuraCast/AzuraCast.git synced 2024-06-14 05:06:37 +00:00
AzuraCast/frontend/vue/components/Admin/Stations/StationForm.vue
2022-12-30 08:38:34 -06:00

511 lines
14 KiB
Vue

<template>
<b-overlay
variant="card"
:show="loading"
>
<b-alert
variant="danger"
:show="error != null"
>
{{ error }}
</b-alert>
<b-form
class="form vue-form"
@submit.prevent="submit"
>
<b-tabs
:card="!isModal"
pills
:content-class="tabContentClass"
>
<b-tab
:title-link-class="getTabClass(v$.$validationGroups.profileTab)"
active
>
<template #title>
{{ $gettext('Profile') }}
</template>
<admin-stations-profile-form
:form="v$"
:timezones="timezones"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.frontendTab)">
<template #title>
{{ $gettext('Broadcasting') }}
</template>
<admin-stations-frontend-form
:form="v$"
:is-shoutcast-installed="isShoutcastInstalled"
:countries="countries"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.backendTab)">
<template #title>
{{ $gettext('AutoDJ') }}
</template>
<admin-stations-backend-form
:form="v$"
:station="station"
:is-stereo-tool-installed="isStereoToolInstalled"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.hlsTab)">
<template #title>
{{ $gettext('HLS') }}
</template>
<admin-stations-hls-form
:form="v$"
:station="station"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.requestsTab)">
<template #title>
{{ $gettext('Song Requests') }}
</template>
<admin-stations-requests-form
:form="v$"
:station="station"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab :title-link-class="getTabClass(v$.$validationGroups.streamersTab)">
<template #title>
{{ $gettext('Streamers/DJs') }}
</template>
<admin-stations-streamers-form
:form="v$"
:station="station"
:show-advanced="showAdvanced"
/>
</b-tab>
<b-tab
v-if="showAdminTab"
:title-link-class="getTabClass(v$.$validationGroups.adminTab)"
>
<template #title>
{{ $gettext('Administration') }}
</template>
<admin-stations-admin-form
:form="v$"
:is-edit-mode="isEditMode"
:storage-location-api-url="storageLocationApiUrl"
:show-advanced="showAdvanced"
/>
</b-tab>
</b-tabs>
<slot name="submitButton">
<b-card-body body-class="card-padding-sm">
<b-button
size="lg"
type="submit"
:variant="(!isValid) ? 'danger' : 'primary'"
>
<slot name="submitButtonText">
{{ $gettext('Save Changes') }}
</slot>
</b-button>
</b-card-body>
</slot>
</b-form>
</b-overlay>
</template>
<script>
import {defineComponent} from "vue";
export default defineComponent({
inheritAttrs: false
});
</script>
<script setup>
import AdminStationsProfileForm from "./Form/ProfileForm.vue";
import AdminStationsFrontendForm from "./Form/FrontendForm.vue";
import AdminStationsBackendForm from "./Form/BackendForm.vue";
import AdminStationsAdminForm from "./Form/AdminForm.vue";
import AdminStationsHlsForm from "./Form/HlsForm.vue";
import AdminStationsRequestsForm from "./Form/RequestsForm.vue";
import AdminStationsStreamersForm from "./Form/StreamersForm.vue";
import {decimal, numeric, required, url} from '@vuelidate/validators';
import {AUDIO_PROCESSING_NONE, BACKEND_LIQUIDSOAP, FRONTEND_ICECAST} from "~/components/Entity/RadioAdapters";
import {computed, ref, watch} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import mergeExisting from "~/functions/mergeExisting";
import {useVuelidateOnForm} from "~/functions/useVuelidateOnForm";
import {isArray, merge, mergeWith} from "lodash";
import stationFormProps from "~/components/Admin/Stations/stationFormProps";
const props = defineProps({
...stationFormProps,
createUrl: {
type: String,
required: true
},
editUrl: {
type: String,
required: true
},
isEditMode: {
type: Boolean,
required: true
},
isModal: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['error', 'submitted', 'loadingUpdate', 'validUpdate']);
const buildForm = () => {
let validations = {
name: {required},
description: {},
genre: {},
url: {url},
timezone: {},
enable_public_page: {},
enable_on_demand: {},
enable_hls: {},
default_album_art_url: {},
enable_on_demand_download: {},
frontend_type: {required},
frontend_config: {
sc_license_id: {},
sc_user_id: {},
source_pw: {},
admin_pw: {},
},
backend_type: {required},
backend_config: {
hls_enable_on_public_player: {},
hls_is_default: {},
crossfade_type: {},
crossfade: {decimal},
audio_processing_method: {},
stereo_tool_license_key: {},
record_streams: {},
record_streams_format: {},
record_streams_bitrate: {},
dj_buffer: {numeric},
},
enable_requests: {},
request_delay: {numeric},
request_threshold: {numeric},
enable_streamers: {},
disconnect_deactivate_streamer: {},
$validationGroups: {
profileTab: [
'name', 'description', 'genre', 'url', 'timezone', 'enable_public_page',
'enable_on_demand', 'enable_on_demand_download', 'default_album_art_url'
],
frontendTab: [
'frontend_type', 'frontend_config'
],
backendTab: [
'backend_type', 'backend_config',
],
hlsTab: [
'enable_hls',
],
requestsTab: [
'enable_requests',
'request_delay',
'request_threshold'
],
streamersTab: [
'enable_streamers',
'disconnect_deactivate_streamer'
]
}
};
let blankForm = {
name: '',
description: '',
genre: '',
url: '',
timezone: 'UTC',
enable_public_page: true,
enable_on_demand: false,
enable_hls: false,
default_album_art_url: '',
enable_on_demand_download: true,
frontend_type: FRONTEND_ICECAST,
frontend_config: {
sc_license_id: '',
sc_user_id: '',
source_pw: '',
admin_pw: '',
},
backend_type: BACKEND_LIQUIDSOAP,
backend_config: {
hls_enable_on_public_player: false,
hls_is_default: false,
crossfade_type: 'normal',
crossfade: 2,
audio_processing_method: AUDIO_PROCESSING_NONE,
stereo_tool_license_key: '',
record_streams: false,
record_streams_format: 'mp3',
record_streams_bitrate: 128,
dj_buffer: 5,
},
enable_requests: false,
request_delay: 5,
request_threshold: 15,
enable_streamers: false,
disconnect_deactivate_streamer: 0,
};
function mergeCustom(objValue, srcValue) {
if (isArray(objValue)) {
return objValue.concat(srcValue);
}
}
if (props.showAdvanced) {
const advancedValidations = {
short_name: {},
api_history_items: {},
frontend_config: {
port: {numeric},
max_listeners: {},
custom_config: {},
banned_ips: {},
banned_countries: {},
allowed_ips: {},
banned_user_agents: {}
},
backend_config: {
hls_segment_length: {numeric},
hls_segments_in_playlist: {numeric},
hls_segments_overhead: {numeric},
dj_port: {numeric},
telnet_port: {numeric},
dj_mount_point: {},
enable_replaygain_metadata: {},
autodj_queue_length: {},
use_manual_autodj: {},
charset: {},
performance_mode: {},
duplicate_prevention_time_range: {},
},
$validationGroups: {
profileTab: [
'short_name', 'api_history_items'
],
}
};
mergeWith(validations, advancedValidations, mergeCustom);
const advancedForm = {
short_name: '',
api_history_items: 5,
frontend_config: {
port: '',
max_listeners: '',
custom_config: '',
banned_ips: '',
banned_countries: [],
allowed_ips: '',
banned_user_agents: '',
},
backend_config: {
hls_segment_length: 4,
hls_segments_in_playlist: 5,
hls_segments_overhead: 2,
dj_port: '',
telnet_port: '',
dj_mount_point: '/',
enable_replaygain_metadata: false,
autodj_queue_length: 3,
use_manual_autodj: false,
charset: 'UTF-8',
performance_mode: 'disabled',
duplicate_prevention_time_range: 120,
},
};
merge(blankForm, advancedForm);
}
if (props.showAdminTab) {
const adminValidations = {
media_storage_location: {},
recordings_storage_location: {},
podcasts_storage_location: {},
is_enabled: {},
$validationGroups: {
adminTab: [
'media_storage_location', 'recordings_storage_location',
'podcasts_storage_location', 'is_enabled'
]
}
};
mergeWith(validations, adminValidations, mergeCustom);
const adminForm = {
media_storage_location: '',
recordings_storage_location: '',
podcasts_storage_location: '',
is_enabled: true,
};
merge(blankForm, adminForm);
if (props.showAdvanced) {
const advancedAdminValidations = {
radio_base_dir: {},
$validationGroups: {
adminTab: [
'radio_base_dir'
]
}
}
mergeWith(validations, advancedAdminValidations, mergeCustom);
const adminAdvancedForm = {
radio_base_dir: '',
};
merge(blankForm, adminAdvancedForm);
}
}
return {blankForm, validations};
};
const {blankForm, validations} = buildForm();
const {form, resetForm, v$} = useVuelidateOnForm(validations, blankForm);
const isValid = computed(() => {
return !v$.value?.$invalid ?? true;
});
watch(isValid, (newValue) => {
emit('validUpdate', newValue);
});
const loading = ref(true);
watch(loading, (newValue) => {
emit('loadingUpdate', newValue);
});
const error = ref(null);
const blankStation = {
stereo_tool_configuration_file_path: null,
links: {
stereo_tool_configuration: null
}
};
const station = ref({...blankStation});
const tabContentClass = computed(() => {
return (props.isModal)
? 'mt-3'
: '';
});
const getTabClass = (validationGroup) => {
if (!loading.value && validationGroup.$invalid) {
return 'text-danger';
}
return null;
}
const clear = () => {
resetForm();
loading.value = false;
error.value = null;
station.value = {...blankStation};
};
const populateForm = (data) => {
form.value = mergeExisting(form.value, data);
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doLoad = () => {
loading.value = true;
wrapWithLoading(
axios.get(props.editUrl)
).then((resp) => {
populateForm(resp.data);
}).catch((err) => {
emit('error', err);
}).finally(() => {
loading.value = false;
});
};
const reset = () => {
clear();
if (props.isEditMode) {
doLoad();
}
};
const submit = () => {
v$.value.$touch();
if (v$.value.$errors.length > 0) {
return;
}
error.value = null;
wrapWithLoading(
axios({
method: (props.isEditMode)
? 'PUT'
: 'POST',
url: (props.isEditMode)
? props.editUrl
: props.createUrl,
data: form.value
})
).then(() => {
notifySuccess();
emit('submitted');
}).catch((err) => {
error.value = err.response.data.message;
});
};
defineExpose({
reset,
submit
});
</script>