Move Station Form to Composition API.

This commit is contained in:
Buster Neece 2022-12-22 08:29:13 -06:00
parent 123d0f1f8b
commit 11adef3ba0
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
12 changed files with 914 additions and 910 deletions

View File

@ -1,5 +1,5 @@
<template>
<b-modal id="send_test_message" centered ref="modal" :title="langTitle">
<b-modal id="send_test_message" centered ref="modal" :title="$gettext('Send Test Message')">
<b-form @submit.prevent="doSendTest">
<b-wrapped-form-group id="email_address" :field="v$.emailAddress" autofocus>
<template #label>
@ -18,55 +18,61 @@
</b-modal>
</template>
<script>
<script setup>
import useVuelidate from "@vuelidate/core";
import {email, required} from '@vuelidate/validators';
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import gettext from "~/vendor/gettext";
import {useAxios} from "~/vendor/axios";
export default {
name: 'AdminSettingsTestMessageModal',
components: {BWrappedFormGroup},
setup() {
return {v$: useVuelidate()}
},
props: {
testMessageUrl: String
},
data() {
return {
emailAddress: null
};
},
validations: {
emailAddress: {required, email}
},
computed: {
langTitle() {
return this.$gettext('Send Test Message');
},
},
methods: {
close() {
this.emailAddress = null;
this.v$.$reset();
this.$refs.modal.hide();
},
async doSendTest() {
this.v$.$touch();
if (this.v$.$errors.length > 0) {
return;
}
const props = defineProps({
testMessageUrl: String
});
this.$wrapWithLoading(
this.axios.post(this.testMessageUrl, {
'email': this.emailAddress
})
).then(() => {
this.$notifySuccess(this.$gettext('Test message sent.'));
}).finally(() => {
this.close();
});
}
}
const blankForm = {
emailAddress: null
};
const form = ref({...blankForm});
const validations = {
emailAddress: {required, email}
};
const v$ = useVuelidate(validations, form);
const resetForm = () => {
form.value = {...blankForm};
};
const modal = ref(); // BModal
const close = () => {
v$.value.reset();
modal.value.hide();
}
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const {$gettext} = gettext;
const doSendTest = () => {
v$.value.$touch();
if (v$.value.$errors.length > 0) {
return;
}
wrapWithLoading(
axios.post(props.testMessageUrl, {
'email': form.value.emailAddress
})
).then(() => {
notifySuccess($gettext('Test message sent.'));
}).finally(() => {
close();
});
};
</script>

View File

@ -1,95 +1,102 @@
<template>
<modal-form ref="modal" :loading="loading" :title="langTitle" :error="error" :disable-save-button="v$.form.$invalid"
<modal-form ref="modal" :loading="loading" :title="$gettext('Clone Station')" :error="error"
:disable-save-button="v$.$invalid"
@submit="doSubmit" @hidden="clearContents">
<admin-stations-clone-modal-form :form="v$.form"></admin-stations-clone-modal-form>
<admin-stations-clone-modal-form :form="v$"></admin-stations-clone-modal-form>
</modal-form>
</template>
<script>
<script setup>
import useVuelidate from "@vuelidate/core";
import {required} from '@vuelidate/validators';
import ModalForm from "~/components/Common/ModalForm";
import AdminStationsCloneModalForm from "~/components/Admin/Stations/CloneModalForm";
import {ref} from "vue";
import gettext from "~/vendor/gettext";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
export default {
name: 'AdminStationsCloneModal',
components: {AdminStationsCloneModalForm, ModalForm},
setup() {
return {v$: useVuelidate()}
},
emits: ['relist'],
data() {
return {
loading: true,
cloneUrl: null,
error: null,
form: {},
}
},
validations() {
return {
form: {
name: {required},
description: {},
clone: {}
}
};
},
computed: {
langTitle() {
return this.$gettext('Clone Station');
},
},
methods: {
resetForm() {
this.form = {
name: '',
description: '',
clone: [],
};
},
create(stationName, cloneUrl) {
this.resetForm();
const emit = defineEmits(['relist']);
const newStationName = this.$gettext('%{station} - Copy');
this.form.name = this.$gettextInterpolate(newStationName, {station: stationName});
const loading = ref(true);
const cloneUrl = ref(null);
const error = ref(null);
this.loading = false;
this.error = null;
this.cloneUrl = cloneUrl;
const blankForm = {
name: '',
description: '',
clone: [],
};
this.$refs.modal.show();
},
clearContents() {
this.resetForm();
this.cloneUrl = null;
},
close() {
this.$refs.modal.hide();
},
doSubmit() {
this.v$.$touch();
if (this.v$.$errors.length > 0) {
return;
}
const form = ref({...blankForm});
this.error = null;
this.$wrapWithLoading(
this.axios({
method: 'POST',
url: this.cloneUrl,
data: this.form
})
).then(() => {
this.$notifySuccess();
this.$emit('relist');
this.close();
}).catch((error) => {
this.error = error.response.data.message;
});
},
const validations = {
name: {required},
description: {},
clone: {}
};
const v$ = useVuelidate(validations, form);
const resetForm = () => {
form.value = {...blankForm};
};
const modal = ref(); // BVModal
const {$gettext} = gettext;
const create = (stationName, stationCloneUrl) => {
resetForm();
form.value.name = $gettext(
'%{station} - Copy',
{station: stationName}
);
loading.value = false;
error.value = null;
cloneUrl.value = stationCloneUrl;
modal.value.show();
};
const clearContents = () => {
resetForm();
cloneUrl.value = null;
};
const close = () => {
modal.value.hide();
};
const {wrapWithLoading, notifySuccess} = useNotify();
const {axios} = useAxios();
const doSubmit = () => {
v$.value.$touch();
if (v$.value.$errors.length > 0) {
return;
}
}
error.value = null;
wrapWithLoading(
axios({
method: 'POST',
url: cloneUrl.value,
data: form.value
})
).then(() => {
notifySuccess();
emit('relist');
close();
}).catch((error) => {
error.value = error.response.data.message;
});
};
defineExpose({
create
});
</script>

View File

@ -29,56 +29,55 @@
</div>
</template>
<script>
<script setup>
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {computed} from "vue";
import gettext from "~/vendor/gettext";
export default {
name: 'AdminStationsCloneModalForm',
components: {BWrappedFormGroup},
props: {
form: Object
},
computed: {
cloneOptions() {
return [
{
text: this.$gettext('Share Media Storage Location'),
value: 'media_storage'
},
{
text: this.$gettext('Share Recordings Storage Location'),
value: 'recordings_storage'
},
{
text: this.$gettext('Share Podcasts Storage Location'),
value: 'podcasts_storage'
},
{
text: this.$gettext('Playlists'),
value: 'playlists',
},
{
text: this.$gettext('Mount Points'),
value: 'mounts'
},
{
text: this.$gettext('Remote Relays'),
value: 'remotes'
},
{
text: this.$gettext('Streamers/DJs'),
value: 'streamers'
},
{
text: this.$gettext('User Permissions'),
value: 'permissions'
},
{
text: this.$gettext('Web Hooks'),
value: 'webhooks'
}
];
const props = defineProps({
form: Object
});
const {$gettext} = gettext;
const cloneOptions = computed(() => {
return [
{
text: $gettext('Share Media Storage Location'),
value: 'media_storage'
},
{
text: $gettext('Share Recordings Storage Location'),
value: 'recordings_storage'
},
{
text: $gettext('Share Podcasts Storage Location'),
value: 'podcasts_storage'
},
{
text: $gettext('Playlists'),
value: 'playlists',
},
{
text: $gettext('Mount Points'),
value: 'mounts'
},
{
text: $gettext('Remote Relays'),
value: 'remotes'
},
{
text: $gettext('Streamers/DJs'),
value: 'streamers'
},
{
text: $gettext('User Permissions'),
value: 'permissions'
},
{
text: $gettext('Web Hooks'),
value: 'webhooks'
}
}
}
];
});
</script>

View File

@ -20,70 +20,86 @@
</b-modal>
</template>
<script>
import ModalForm from "~/components/Common/ModalForm";
<script setup>
import AdminStationsForm, {StationFormProps} from "~/components/Admin/Stations/StationForm";
import InvisibleSubmitButton from "~/components/Common/InvisibleSubmitButton";
import {computed, ref} from "vue";
import gettext from "~/vendor/gettext";
const props = defineProps({
...StationFormProps.props,
createUrl: String
});
const emit = defineEmits(['relist']);
const editUrl = ref(null);
const loading = ref(true);
const disableSaveButton = ref(true);
const isEditMode = computed(() => {
return editUrl.value !== null;
});
const {$gettext} = gettext;
const langTitle = computed(() => {
return isEditMode.value
? $gettext('Edit Station')
: $gettext('Add Station');
});
const modal = ref(); // BVModal
const onValidUpdate = (newValue) => {
disableSaveButton.value = !newValue;
};
const onLoadingUpdate = (newValue) => {
loading.value = newValue;
};
const create = () => {
editUrl.value = null;
modal.value.show();
};
const edit = (recordUrl) => {
editUrl.value = recordUrl;
modal.value.show();
};
const form = ref(); // Template Ref
const resetForm = () => {
form.value.reset();
};
const close = () => {
modal.value.hide();
};
const onSubmit = () => {
emit('relist');
close();
};
const doSubmit = () => {
form.value.submit();
};
const clearContents = () => {
editUrl.value = null;
};
defineExpose({
create,
edit
});
</script>
<script>
export default {
name: 'AdminStationsEditModal',
inheritAttrs: false,
components: {InvisibleSubmitButton, AdminStationsForm, ModalForm},
emits: ['relist'],
props: {
createUrl: String
},
mixins: [
StationFormProps
],
data() {
return {
editUrl: null,
loading: true,
disableSaveButton: true,
};
},
computed: {
langTitle() {
return this.isEditMode
? this.$gettext('Edit Station')
: this.$gettext('Add Station');
},
isEditMode() {
return this.editUrl !== null;
}
},
methods: {
onValidUpdate(newValue) {
this.disableSaveButton = !newValue;
},
onLoadingUpdate(newValue) {
this.loading = newValue;
},
create() {
this.editUrl = null;
this.$refs.modal.show();
},
edit(recordUrl) {
this.editUrl = recordUrl;
this.$refs.modal.show();
},
resetForm() {
this.$refs.form.reset();
},
onSubmit() {
this.$emit('relist');
this.close();
},
doSubmit() {
this.$refs.form.submit();
},
close() {
this.$refs.modal.hide();
},
clearContents() {
this.editUrl = null;
},
}
};
</script>

View File

@ -66,66 +66,62 @@
</b-form-group>
</template>
<script>
<script setup>
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import objectToFormOptions from "~/functions/objectToFormOptions";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import BFormFieldset from "~/components/Form/BFormFieldset";
import {onMounted, reactive, ref} from "vue";
import {useAxios} from "~/vendor/axios";
export default {
name: 'AdminStationsAdminForm',
components: {BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
isEditMode: Boolean,
storageLocationApiUrl: String,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
isEditMode: Boolean,
storageLocationApiUrl: String,
showAdvanced: {
type: Boolean,
default: true
},
data() {
return {
storageLocationsLoading: true,
storageLocationOptions: {
media_storage_location: [],
recordings_storage_location: [],
podcasts_storage_location: []
}
}
},
mounted() {
this.loadLocations();
},
methods: {
loadLocations() {
this.axios.get(this.storageLocationApiUrl).then((resp) => {
this.storageLocationOptions.media_storage_location = objectToFormOptions(
this.filterLocations(resp.data.media_storage_location)
);
this.storageLocationOptions.recordings_storage_location = objectToFormOptions(
this.filterLocations(resp.data.recordings_storage_location)
);
this.storageLocationOptions.podcasts_storage_location = objectToFormOptions(
this.filterLocations(resp.data.podcasts_storage_location)
);
}).finally(() => {
this.storageLocationsLoading = false;
});
},
filterLocations(group) {
if (!this.isEditMode) {
return group;
}
});
let newGroup = {};
for (const oldKey in group) {
if (oldKey !== "") {
newGroup[oldKey] = group[oldKey];
}
}
return newGroup;
const storageLocationsLoading = ref(true);
const storageLocationOptions = reactive({
media_storage_location: [],
recordings_storage_location: [],
podcasts_storage_location: []
});
const filterLocations = (group) => {
if (!props.isEditMode) {
return group;
}
let newGroup = {};
for (const oldKey in group) {
if (oldKey !== "") {
newGroup[oldKey] = group[oldKey];
}
}
return newGroup;
}
const {axios} = useAxios();
const loadLocations = () => {
axios.get(props.storageLocationApiUrl).then((resp) => {
storageLocationOptions.media_storage_location = objectToFormOptions(
filterLocations(resp.data.media_storage_location)
);
storageLocationOptions.recordings_storage_location = objectToFormOptions(
filterLocations(resp.data.recordings_storage_location)
);
storageLocationOptions.podcasts_storage_location = objectToFormOptions(
filterLocations(resp.data.podcasts_storage_location)
);
}).finally(() => {
storageLocationsLoading.value = false;
});
};
onMounted(loadLocations);
</script>

View File

@ -222,7 +222,7 @@
</b-form-fieldset>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {
@ -234,106 +234,111 @@ import {
} from "~/components/Entity/RadioAdapters";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import BFormMarkup from "~/components/Form/BFormMarkup";
import {computed} from "vue";
import gettext from "~/vendor/gettext";
export default {
name: 'AdminStationsBackendForm',
components: {BFormMarkup, BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
station: Object,
isStereoToolInstalled: {
type: Boolean,
default: true
},
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
station: Object,
isStereoToolInstalled: {
type: Boolean,
default: true
},
computed: {
backendTypeOptions() {
return [
{
text: this.$gettext('Use Liquidsoap on this server.'),
value: BACKEND_LIQUIDSOAP
},
{
text: this.$gettext('Do not use an AutoDJ service.'),
value: BACKEND_NONE
}
];
},
isBackendEnabled() {
return this.form.backend_type.$model !== BACKEND_NONE;
},
isStereoToolEnabled() {
return this.form.backend_config.audio_processing_method.$model === AUDIO_PROCESSING_STEREO_TOOL;
},
crossfadeOptions() {
return [
{
text: this.$gettext('Smart Mode'),
value: 'smart',
},
{
text: this.$gettext('Normal Mode'),
value: 'normal',
},
{
text: this.$gettext('Disable Crossfading'),
value: 'none',
}
];
},
audioProcessingOptions() {
const audioProcessingOptions = [
{
text: this.$gettext('Liquidsoap'),
value: AUDIO_PROCESSING_LIQUIDSOAP,
},
{
text: this.$gettext('Disable Processing'),
value: AUDIO_PROCESSING_NONE,
}
];
showAdvanced: {
type: Boolean,
default: true
},
});
if (this.isStereoToolInstalled) {
audioProcessingOptions.splice(1, 0,
{
text: this.$gettext('Stereo Tool'),
value: AUDIO_PROCESSING_STEREO_TOOL,
}
)
}
const isBackendEnabled = computed(() => {
return props.form.backend_type.$model !== BACKEND_NONE;
});
return audioProcessingOptions;
const isStereoToolEnabled = computed(() => {
return props.form.backend_config.audio_processing_method.$model === AUDIO_PROCESSING_STEREO_TOOL;
});
const {$gettext} = gettext;
const backendTypeOptions = computed(() => {
return [
{
text: $gettext('Use Liquidsoap on this server.'),
value: BACKEND_LIQUIDSOAP
},
charsetOptions() {
return [
{text: 'UTF-8', value: 'UTF-8'},
{text: 'ISO-8859-1', value: 'ISO-8859-1'}
];
},
performanceModeOptions() {
return [
{
text: this.$gettext('Use Less Memory (Uses More CPU)'),
value: 'less_memory'
},
{
text: this.$gettext('Balanced'),
value: 'balanced'
},
{
text: this.$gettext('Use Less CPU (Uses More Memory)'),
value: 'less_cpu'
},
{
text: this.$gettext('Disable Optimizations'),
value: 'disabled'
}
];
{
text: $gettext('Do not use an AutoDJ service.'),
value: BACKEND_NONE
}
];
});
const crossfadeOptions = computed(() => {
return [
{
text: $gettext('Smart Mode'),
value: 'smart',
},
{
text: $gettext('Normal Mode'),
value: 'normal',
},
{
text: $gettext('Disable Crossfading'),
value: 'none',
}
];
});
const audioProcessingOptions = computed(() => {
const audioProcessingOptions = [
{
text: $gettext('Liquidsoap'),
value: AUDIO_PROCESSING_LIQUIDSOAP,
},
{
text: $gettext('Disable Processing'),
value: AUDIO_PROCESSING_NONE,
}
];
if (props.isStereoToolInstalled) {
audioProcessingOptions.splice(1, 0,
{
text: $gettext('Stereo Tool'),
value: AUDIO_PROCESSING_STEREO_TOOL,
}
)
}
}
return audioProcessingOptions;
});
const charsetOptions = computed(() => {
return [
{text: 'UTF-8', value: 'UTF-8'},
{text: 'ISO-8859-1', value: 'ISO-8859-1'}
];
});
const performanceModeOptions = computed(() => {
return [
{
text: $gettext('Use Less Memory (Uses More CPU)'),
value: 'less_memory'
},
{
text: $gettext('Balanced'),
value: 'balanced'
},
{
text: $gettext('Use Less CPU (Uses More Memory)'),
value: 'less_cpu'
},
{
text: $gettext('Disable Optimizations'),
value: 'disabled'
}
];
});
</script>

View File

@ -171,64 +171,65 @@
</b-form-fieldset>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {FRONTEND_ICECAST, FRONTEND_REMOTE, FRONTEND_SHOUTCAST} from "~/components/Entity/RadioAdapters";
import objectToFormOptions from "~/functions/objectToFormOptions";
import {computed} from "vue";
import gettext from "~/vendor/gettext";
export default {
name: 'AdminStationsFrontendForm',
components: {BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
isShoutcastInstalled: {
type: Boolean,
default: false
},
countries: Object,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
isShoutcastInstalled: {
type: Boolean,
default: false
},
computed: {
frontendTypeOptions() {
let frontendOptions = [
{
text: this.$gettext('Use Icecast 2.4 on this server.'),
value: FRONTEND_ICECAST
},
];
if (this.isShoutcastInstalled) {
frontendOptions.push({
text: this.$gettext('Use Shoutcast DNAS 2 on this server.'),
value: FRONTEND_SHOUTCAST
});
}
frontendOptions.push({
text: this.$gettext('Only connect to a remote server.'),
value: FRONTEND_REMOTE
});
return frontendOptions;
},
countryOptions() {
return objectToFormOptions(this.countries);
},
isLocalFrontend() {
return this.form.frontend_type.$model !== FRONTEND_REMOTE;
},
isShoutcastFrontend() {
return this.form.frontend_type.$model === FRONTEND_SHOUTCAST;
}
countries: Object,
showAdvanced: {
type: Boolean,
default: true
},
methods: {
clearCountries() {
this.form.frontend_config.banned_countries.$model = [];
}
});
const {$gettext} = gettext;
const frontendTypeOptions = computed(() => {
let frontendOptions = [
{
text: $gettext('Use Icecast 2.4 on this server.'),
value: FRONTEND_ICECAST
},
];
if (props.isShoutcastInstalled) {
frontendOptions.push({
text: $gettext('Use Shoutcast DNAS 2 on this server.'),
value: FRONTEND_SHOUTCAST
});
}
frontendOptions.push({
text: $gettext('Only connect to a remote server.'),
value: FRONTEND_REMOTE
});
return frontendOptions;
});
const countryOptions = computed(() => {
return objectToFormOptions(props.countries);
});
const isLocalFrontend = computed(() => {
return props.form.frontend_type.$model !== FRONTEND_REMOTE;
});
const isShoutcastFrontend = computed(() => {
return props.form.frontend_type.$model === FRONTEND_SHOUTCAST;
});
const clearCountries = () => {
props.form.frontend_config.banned_countries.$model = [];
}
</script>

View File

@ -74,29 +74,24 @@
<backend-disabled v-else></backend-disabled>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {BACKEND_NONE} from "~/components/Entity/RadioAdapters";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import BFormMarkup from "~/components/Form/BFormMarkup";
import BackendDisabled from "./Common/BackendDisabled.vue";
import {computed} from "vue";
export default {
name: 'AdminStationsHlsForm',
components: {BackendDisabled, BFormMarkup, BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
computed: {
isBackendEnabled() {
return this.form.backend_type.$model !== BACKEND_NONE;
},
}
}
});
const isBackendEnabled = computed(() => {
return props.form.backend_type.$model !== BACKEND_NONE;
});
</script>

View File

@ -140,39 +140,39 @@
</b-form-fieldset>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import objectToFormOptions from "~/functions/objectToFormOptions";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import {computed} from "vue";
import gettext from "~/vendor/gettext";
export default {
name: 'AdminStationsProfileForm',
components: {BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
timezones: Object,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
timezones: Object,
showAdvanced: {
type: Boolean,
default: true
},
computed: {
timezoneOptions() {
return objectToFormOptions(this.timezones);
});
const timezoneOptions = computed(() => {
return objectToFormOptions(props.timezones);
});
const {$gettext} = gettext;
const historyItemsOptions = computed(() => {
return [
{
text: $gettext('Disabled'),
value: 0,
},
historyItemsOptions() {
return [
{
text: this.$gettext('Disabled'),
value: 0,
},
{text: '1', value: 1},
{text: '5', value: 5},
{text: '10', value: 10},
{text: '15', value: 15}
];
}
}
}
{text: '1', value: 1},
{text: '5', value: 5},
{text: '10', value: 10},
{text: '15', value: 15}
];
});
</script>

View File

@ -60,29 +60,24 @@
<backend-disabled v-else></backend-disabled>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {BACKEND_NONE} from "~/components/Entity/RadioAdapters";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import BFormMarkup from "~/components/Form/BFormMarkup";
import BackendDisabled from "./Common/BackendDisabled.vue";
import {computed} from "vue";
export default {
name: 'AdminStationsRequestsForm',
components: {BackendDisabled, BFormMarkup, BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
computed: {
isBackendEnabled() {
return this.form.backend_type.$model !== BACKEND_NONE;
},
}
}
});
const isBackendEnabled = computed(() => {
return props.form.backend_type.$model !== BACKEND_NONE;
});
</script>

View File

@ -134,61 +134,58 @@
<backend-disabled v-else></backend-disabled>
</template>
<script>
<script setup>
import BFormFieldset from "~/components/Form/BFormFieldset";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import {BACKEND_NONE} from "~/components/Entity/RadioAdapters";
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox";
import BFormMarkup from "~/components/Form/BFormMarkup";
import BackendDisabled from "./Common/BackendDisabled.vue";
import {computed} from "vue";
export default {
name: 'AdminStationsStreamersForm',
components: {BackendDisabled, BFormMarkup, BWrappedFormCheckbox, BWrappedFormGroup, BFormFieldset},
props: {
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
const props = defineProps({
form: Object,
station: Object,
showAdvanced: {
type: Boolean,
default: true
},
computed: {
isBackendEnabled() {
return this.form.backend_type.$model !== BACKEND_NONE;
});
const isBackendEnabled = computed(() => {
return props.form.backend_type.$model !== BACKEND_NONE;
});
const recordStreamsOptions = computed(() => {
return [
{
text: 'MP3',
value: 'mp3',
},
recordStreamsOptions() {
return [
{
text: 'MP3',
value: 'mp3',
},
{
text: 'OGG Vorbis',
value: 'ogg',
},
{
text: 'OGG Opus',
value: 'opus',
},
{
text: 'AAC+ (MPEG4 HE-AAC v2)',
value: 'aac'
}
];
{
text: 'OGG Vorbis',
value: 'ogg',
},
recordBitrateOptions() {
return [
{text: '32', value: 32},
{text: '48', value: 48},
{text: '64', value: 64},
{text: '96', value: 96},
{text: '128', value: 128},
{text: '192', value: 192},
{text: '256', value: 256},
{text: '320', value: 320}
];
{
text: 'OGG Opus',
value: 'opus',
},
}
}
{
text: 'AAC+ (MPEG4 HE-AAC v2)',
value: 'aac'
}
];
});
const recordBitrateOptions = computed(() => {
return [
{text: '32', value: 32},
{text: '48', value: 48},
{text: '64', value: 64},
{text: '96', value: 96},
{text: '128', value: 128},
{text: '192', value: 192},
{text: '256', value: 256},
{text: '320', value: 320}
];
});
</script>

View File

@ -9,7 +9,7 @@
{{ $gettext('Profile') }}
</template>
<admin-stations-profile-form :form="v$.form" :timezones="timezones"
<admin-stations-profile-form :form="v$" :timezones="timezones"
:show-advanced="showAdvanced"></admin-stations-profile-form>
</b-tab>
@ -18,7 +18,7 @@
{{ $gettext('Broadcasting') }}
</template>
<admin-stations-frontend-form :form="v$.form"
<admin-stations-frontend-form :form="v$"
:is-shoutcast-installed="isShoutcastInstalled"
:countries="countries"
:show-advanced="showAdvanced"></admin-stations-frontend-form>
@ -29,7 +29,7 @@
{{ $gettext('AutoDJ') }}
</template>
<admin-stations-backend-form :form="v$.form" :station="station"
<admin-stations-backend-form :form="v$" :station="station"
:is-stereo-tool-installed="isStereoToolInstalled"
:show-advanced="showAdvanced"></admin-stations-backend-form>
</b-tab>
@ -39,7 +39,7 @@
{{ $gettext('HLS') }}
</template>
<admin-stations-hls-form :form="v$.form" :station="station" :show-advanced="showAdvanced">
<admin-stations-hls-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-hls-form>
</b-tab>
@ -48,7 +48,7 @@
{{ $gettext('Song Requests') }}
</template>
<admin-stations-requests-form :form="v$.form" :station="station" :show-advanced="showAdvanced">
<admin-stations-requests-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-requests-form>
</b-tab>
@ -57,7 +57,7 @@
{{ $gettext('Streamers/DJs') }}
</template>
<admin-stations-streamers-form :form="v$.form" :station="station" :show-advanced="showAdvanced">
<admin-stations-streamers-form :form="v$" :station="station" :show-advanced="showAdvanced">
</admin-stations-streamers-form>
</b-tab>
@ -66,7 +66,7 @@
{{ $gettext('Administration') }}
</template>
<admin-stations-admin-form :form="v$.form"
<admin-stations-admin-form :form="v$"
:is-edit-mode="isEditMode"
:storage-location-api-url="storageLocationApiUrl"
:show-advanced="showAdvanced">
@ -87,7 +87,7 @@
</b-overlay>
</template>
<script>
<script setup>
import AdminStationsProfileForm from "./Form/ProfileForm";
import AdminStationsFrontendForm from "./Form/FrontendForm";
import AdminStationsBackendForm from "./Form/BackendForm";
@ -95,13 +95,356 @@ import AdminStationsAdminForm from "./Form/AdminForm";
import AdminStationsHlsForm from "./Form/HlsForm.vue";
import AdminStationsRequestsForm from "./Form/RequestsForm.vue";
import AdminStationsStreamersForm from "./Form/StreamersForm.vue";
import useVuelidate from "@vuelidate/core";
import {decimal, numeric, required, url} from '@vuelidate/validators';
import {AUDIO_PROCESSING_NONE, BACKEND_LIQUIDSOAP, FRONTEND_ICECAST} from "~/components/Entity/RadioAdapters";
import _ from "lodash";
import {computed, ref, watch} from "vue";
import useVuelidate from "@vuelidate/core";
import {useNotify} from "~/vendor/bootstrapVue";
import {useAxios} from "~/vendor/axios";
import mergeExisting from "~/functions/mergeExisting";
const props = defineProps({
...StationFormProps.props,
createUrl: String,
editUrl: String,
isEditMode: Boolean,
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 = ref({...blankForm});
const v$ = useVuelidate(validations, form);
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 = () => {
loading.value = false;
error.value = null;
station.value = {...blankStation};
form.value = {...blankForm};
};
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>
<script>
export const StationFormProps = {
props: {
// Global
@ -131,362 +474,6 @@ export const StationFormProps = {
};
export default {
name: 'AdminStationsForm',
inheritAttrs: false,
components: {
AdminStationsStreamersForm,
AdminStationsRequestsForm,
AdminStationsHlsForm,
AdminStationsAdminForm,
AdminStationsBackendForm,
AdminStationsFrontendForm,
AdminStationsProfileForm
},
emits: ['error', 'submitted', 'loadingUpdate', 'validUpdate'],
setup() {
return {
v$: useVuelidate()
};
},
props: {
createUrl: String,
editUrl: String,
isEditMode: Boolean,
isModal: {
type: Boolean,
default: false
}
},
mixins: [
StationFormProps
],
validations() {
let formValidations = {
form: {
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: [
'form.name', 'form.description', 'form.genre', 'form.url', 'form.timezone', 'form.enable_public_page',
'form.enable_on_demand', 'form.enable_on_demand_download', 'form.default_album_art_url'
],
frontendTab: [
'form.frontend_type', 'form.frontend_config'
],
backendTab: [
'form.backend_type', 'form.backend_config',
],
hlsTab: [
'form.enable_hls',
],
requestsTab: [
'form.enable_requests',
'form.request_delay',
'form.request_threshold'
],
streamersTab: [
'form.enable_streamers',
'form.disconnect_deactivate_streamer'
]
}
};
function mergeCustom(objValue, srcValue) {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
}
if (this.showAdvanced) {
const advancedValidations = {
form: {
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: [
'form.short_name', 'form.api_history_items'
],
}
};
_.mergeWith(formValidations, advancedValidations, mergeCustom);
}
if (this.showAdminTab) {
const adminValidations = {
form: {
media_storage_location: {},
recordings_storage_location: {},
podcasts_storage_location: {},
is_enabled: {},
},
$validationGroups: {
adminTab: [
'form.media_storage_location', 'form.recordings_storage_location',
'form.podcasts_storage_location', 'form.is_enabled'
]
}
};
_.mergeWith(formValidations, adminValidations, mergeCustom);
if (this.showAdvanced) {
const advancedAdminValidations = {
form: {
radio_base_dir: {},
},
$validationGroups: {
adminTab: [
'form.radio_base_dir'
]
}
}
_.mergeWith(formValidations, advancedAdminValidations, mergeCustom);
}
}
return formValidations;
},
data() {
return {
loading: true,
error: null,
form: this.getEmptyForm(),
station: {},
};
},
watch: {
loading(newValue) {
this.$emit('loadingUpdate', newValue);
},
isValid(newValue) {
this.$emit('validUpdate', newValue);
}
},
computed: {
isValid() {
return !this.v$?.form?.$invalid ?? true;
},
tabContentClass() {
return (this.isModal)
? 'mt-3'
: '';
}
},
methods: {
getTabClass(validationGroup) {
if (!this.loading && validationGroup.$invalid) {
return 'text-danger';
}
return null;
},
getEmptyForm() {
let form = {
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,
};
if (this.showAdvanced) {
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(form, advancedForm);
}
if (this.showAdminTab) {
const adminForm = {
media_storage_location: '',
recordings_storage_location: '',
podcasts_storage_location: '',
is_enabled: true,
};
_.merge(form, adminForm);
if (this.showAdvanced) {
const adminAdvancedForm = {
radio_base_dir: '',
};
_.merge(form, adminAdvancedForm);
}
}
return form;
},
clear() {
this.loading = false;
this.error = null;
this.station = {
stereo_tool_configuration_file_path: null,
links: {
stereo_tool_configuration: null
}
};
this.form = this.getEmptyForm();
},
reset() {
this.clear();
if (this.isEditMode) {
this.doLoad();
}
},
doLoad() {
this.$wrapWithLoading(
this.axios.get(this.editUrl)
).then((resp) => {
this.populateForm(resp.data);
}).catch((error) => {
this.$emit('error', error);
}).finally(() => {
this.loading = false;
});
},
populateForm(data) {
this.form = mergeExisting(this.form, data);
},
getSubmittableFormData() {
return this.form;
},
submit() {
this.v$.$touch();
if (this.v$.$errors.length > 0) {
return;
}
this.error = null;
this.$wrapWithLoading(
this.axios({
method: (this.isEditMode)
? 'PUT'
: 'POST',
url: (this.isEditMode)
? this.editUrl
: this.createUrl,
data: this.getSubmittableFormData()
})
).then(() => {
this.$notifySuccess();
this.$emit('submitted');
}).catch((error) => {
this.error = error.response.data.message;
});
},
}
inheritAttrs: false
}
</script>