Make Webhook Edit Modal Composition API.

This commit is contained in:
Buster Neece 2023-01-08 21:14:36 -06:00
parent 6a85fab94c
commit 469c89e0f6
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
6 changed files with 501 additions and 456 deletions

View File

@ -12,7 +12,7 @@
content-class="mt-3"
pills
>
<form-basic-info :form="v$"/>
<form-basic-info :form="v$" />
</b-tabs>
</modal-form>
</template>
@ -40,7 +40,6 @@ const {
loading,
error,
isEditMode,
form,
v$,
clearContents,
create,

View File

@ -1,10 +1,10 @@
<template>
<modal-form
ref="modal"
ref="$modal"
:loading="loading"
:title="langTitle"
:error="error"
:disable-save-button="v$.form.$invalid"
:disable-save-button="v$.$invalid"
@submit="doSubmit"
@hidden="clearContents"
>
@ -13,152 +13,165 @@
pills
>
<mount-form-basic-info
:form="v$.form"
:form="v$"
:station-frontend-type="stationFrontendType"
/>
<mount-form-auto-dj
:form="v$.form"
:form="v$"
:station-frontend-type="stationFrontendType"
/>
<mount-form-intro
v-model="v$.form.intro_file.$model"
v-model="v$.intro_file.$model"
:record-has-intro="record.intro_path !== null"
:new-intro-url="newIntroUrl"
:edit-intro-url="record.links.intro"
/>
<mount-form-advanced
v-if="showAdvanced"
:form="v$.form"
:form="v$"
:station-frontend-type="stationFrontendType"
/>
</b-tabs>
</modal-form>
</template>
<script>
import {required} from '@vuelidate/validators';
import BaseEditModal from '~/components/Common/BaseEditModal';
<script setup>
import {required} from '@vuelidate/validators';
import {FRONTEND_ICECAST, FRONTEND_SHOUTCAST} from '~/components/Entity/RadioAdapters';
import MountFormBasicInfo from './Form/BasicInfo';
import MountFormAutoDj from './Form/AutoDj';
import MountFormAdvanced from './Form/Advanced';
import MountFormIntro from "./Form/Intro";
import mergeExisting from "~/functions/mergeExisting";
import useVuelidate from "@vuelidate/core";
import {baseEditModalProps, useBaseEditModal} from "~/functions/useBaseEditModal";
import {computed, ref} from "vue";
import {useNotify} from "~/vendor/bootstrapVue";
import {useTranslate} from "~/vendor/gettext";
import {useResettableRef} from "~/functions/useResettableRef";
import ModalForm from "~/components/Common/ModalForm.vue";
/* TODO Options API */
const props = defineProps({
...baseEditModalProps,
stationFrontendType: {
type: String,
required: true
},
newIntroUrl: {
type: String,
required: true
},
showAdvanced: {
type: Boolean,
default: true
},
});
export default {
name: 'EditModal',
components: {MountFormIntro, MountFormAdvanced, MountFormAutoDj, MountFormBasicInfo},
mixins: [BaseEditModal],
props: {
stationFrontendType: {
type: String,
required: true
},
newIntroUrl: {
type: String,
required: true
},
showAdvanced: {
type: Boolean,
default: true
},
},
emits: ['relist', 'needs-restart'],
setup() {
return {v$: useVuelidate()}
},
data() {
return {
record: {
intro_path: null,
links: {
intro: null
}
}
}
},
validations() {
const emit = defineEmits(['relist', 'needs-restart']);
const $modal = ref(); // Template Ref
const {notifySuccess} = useNotify();
const {record, reset} = useResettableRef({
intro_path: null,
links: {
intro: null
}
});
const {
loading,
error,
isEditMode,
v$,
clearContents,
create,
edit,
doSubmit,
close
} = useBaseEditModal(
props,
emit,
$modal,
() => computed(() => {
let validations = {
form: {
name: {required},
display_name: {},
is_visible_on_public_pages: {},
is_default: {},
relay_url: {},
is_public: {},
enable_autodj: {},
autodj_format: {},
autodj_bitrate: {},
max_listener_duration: {required},
intro_file: {}
}
name: {required},
display_name: {},
is_visible_on_public_pages: {},
is_default: {},
relay_url: {},
is_public: {},
enable_autodj: {},
autodj_format: {},
autodj_bitrate: {},
max_listener_duration: {required},
intro_file: {}
};
if (this.showAdvanced) {
validations.form.custom_listen_url = {};
if (props.showAdvanced) {
validations.custom_listen_url = {};
}
if (FRONTEND_SHOUTCAST === this.stationFrontendType) {
validations.form.authhash = {};
if (FRONTEND_SHOUTCAST === props.stationFrontendType) {
validations.authhash = {};
}
if (FRONTEND_ICECAST === this.stationFrontendType) {
validations.form.fallback_mount = {};
if (this.showAdvanced) {
validations.form.frontend_config = {};
if (FRONTEND_ICECAST === props.stationFrontendType) {
validations.fallback_mount = {};
if (props.showAdvanced) {
validations.frontend_config = {};
}
}
return validations;
}),
{
name: null,
display_name: null,
is_visible_on_public_pages: true,
is_default: false,
relay_url: null,
is_public: true,
enable_autodj: true,
autodj_format: 'mp3',
autodj_bitrate: 128,
custom_listen_url: null,
authhash: null,
fallback_mount: '/error.mp3',
max_listener_duration: 0,
frontend_config: null,
intro_file: null
},
computed: {
langTitle () {
return this.isEditMode
? this.$gettext('Edit Mount Point')
: this.$gettext('Add Mount Point');
}
},
methods: {
resetForm () {
this.record = {
intro_path: null,
links: {
intro: null
}
};
this.form = {
name: null,
display_name: null,
is_visible_on_public_pages: true,
is_default: false,
relay_url: null,
is_public: true,
enable_autodj: true,
autodj_format: 'mp3',
autodj_bitrate: 128,
custom_listen_url: null,
authhash: null,
fallback_mount: '/error.mp3',
max_listener_duration: 0,
frontend_config: null,
intro_file: null
};
{
resetForm: (originalResetForm) => {
originalResetForm();
reset();
},
populateForm(d) {
this.record = d;
this.form = mergeExisting(this.form, d);
populateForm: (data, formRef) => {
record.value = data;
formRef.value = mergeExisting(formRef.value, data);
},
onSubmitSuccess() {
this.$notifySuccess();
this.$emit('needs-restart');
this.$emit('relist');
this.close();
onSubmitSuccess: () => {
notifySuccess();
emit('relist');
emit('needs-restart');
close();
},
}
};
);
const {$gettext} = useTranslate();
const langTitle = computed(() => {
return isEditMode.value
? $gettext('Edit Mount Point')
: $gettext('Add Mount Point');
});
defineExpose({
create,
edit,
close
});
</script>

View File

@ -1,10 +1,10 @@
<template>
<modal-form
ref="modal"
ref="$modal"
:loading="loading"
:title="langTitle"
:error="error"
:disable-save-button="v$.form.$invalid"
:disable-save-button="v$.$invalid"
@submit="doSubmit"
@hidden="clearContents"
>
@ -26,22 +26,22 @@
<basic-info
:trigger-options="triggerOptions"
:form="v$.form"
:form="v$"
/>
</b-tab>
<b-tab :title="typeTitle">
<component
:is="formComponent"
:now-playing-url="nowPlayingUrl"
:form="v$.form"
:form="v$"
/>
</b-tab>
</b-tabs>
</modal-form>
</template>
<script>
<script setup>
import {required} from '@vuelidate/validators';
import BaseEditModal from '~/components/Common/BaseEditModal';
import TypeSelect from "./Form/TypeSelect";
import BasicInfo from "./Form/BasicInfo";
import {get, map} from "lodash";
@ -54,356 +54,385 @@ import Twitter from "./Form/Twitter";
import GoogleAnalytics from "./Form/GoogleAnalytics";
import MatomoAnalytics from "./Form/MatomoAnalytics";
import Mastodon from "./Form/Mastodon";
import useVuelidate from "@vuelidate/core";
import {baseEditModalProps, useBaseEditModal} from "~/functions/useBaseEditModal";
import {computed, ref} from "vue";
import {useTranslate} from "~/vendor/gettext";
import ModalForm from "~/components/Common/ModalForm.vue";
/* TODO Options API */
const props = defineProps({
...baseEditModalProps,
nowPlayingUrl: {
type: String,
required: true
},
webhookTypes: {
type: Object,
required: true
},
triggerTitles: {
type: Object,
required: true
},
triggerDescriptions: {
type: Object,
required: true
}
});
export default {
name: 'EditModal',
components: {BasicInfo, TypeSelect},
mixins: [BaseEditModal],
props: {
nowPlayingUrl: {
type: String,
required: true
const emit = defineEmits(['relist']);
const type = ref(null);
const $modal = ref(); // Template Ref
const {$gettext} = useTranslate();
const langPoweredByAzuraCast = $gettext('Powered by AzuraCast');
const langDiscordDefaultContent = $gettext(
'Now playing on %{ station }:',
{'station': '{{ station.name }}'}
);
const langTelegramDefaultContent = $gettext(
'Now playing on %{ station }: %{ title } by %{ artist }! Tune in now.',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}'
}
);
const langTwitterDefaultMessage = $gettext(
'Now playing on %{ station }: %{ title } by %{ artist }! Tune in now: %{ url }',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}',
url: '{{ station.public_player_url }}'
}
);
const langTwitterSongChangedLiveMessage = $gettext(
'Now playing on %{ station }: %{ title } by %{ artist } with your host, %{ dj }! Tune in now: %{ url }',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}',
dj: '{{ live.streamer_name }}',
url: '{{ station.public_player_url }}'
}
);
const langTwitterDjOnMessage = $gettext(
'%{ dj } is now live on %{ station }! Tune in now: %{ url }',
{
dj: '{{ live.streamer_name }}',
station: '{{ station.name }}',
url: '{{ station.public_player_url }}'
}
);
const langTwitterDjOffMessage = $gettext(
'Thanks for listening to %{ station }!',
{
station: '{{ station.name }}',
}
);
const langTwitterStationOfflineMessage = $gettext(
'%{ station } is going offline for now.',
{
station: '{{ station.name }}'
}
);
const langTwitterStationOnlineMessage = $gettext(
'%{ station } is back online! Tune in now: %{ url }',
{
station: '{{ station.name }}',
url: '{{ station.public_player_url }}'
}
);
const webhookConfig = {
'generic': {
component: Generic,
validations: {
webhook_url: {required},
basic_auth_username: {},
basic_auth_password: {},
timeout: {},
},
webhookTypes: {
type: Object,
required: true
},
triggerTitles: {
type: Object,
required: true
},
triggerDescriptions: {
type: Object,
required: true
defaultConfig: {
webhook_url: '',
basic_auth_username: '',
basic_auth_password: '',
timeout: '5',
}
},
setup() {
return {v$: useVuelidate()}
},
data() {
return {
type: null,
'email': {
component: Email,
validations: {
to: {required},
subject: {required},
message: {required}
},
defaultConfig: {
to: '',
subject: '',
message: ''
}
},
validations() {
let validations = {
type: {required},
form: {
name: {required},
triggers: {},
config: {}
}
};
if (this.triggerOptions.length > 0) {
validations.form.triggers = {required};
}
if (this.type !== null) {
validations.form.config = get(this.webhookConfig, [this.type, 'validations'], {});
}
return validations;
},
computed: {
langTitle() {
return this.isEditMode
? this.$gettext('Edit Web Hook')
: this.$gettext('Add Web Hook');
'tunein': {
component: Tunein,
validations: {
station_id: {required},
partner_id: {required},
partner_key: {required},
},
triggerOptions() {
if (!this.type) {
return [];
}
let webhookKeys = get(this.webhookTypes, [this.type, 'triggers'], []);
return map(webhookKeys, (key) => {
return {
html:
'<h6 class="font-weight-bold mb-0">' + this.triggerTitles[key] + '</h6>'
+ '<p class="card-text small">' + this.triggerDescriptions[key] + '</p>',
value: key
};
});
},
typeTitle() {
return get(this.webhookTypes, [this.type, 'name'], '');
},
formComponent() {
return get(this.webhookConfig, [this.type, 'component'], Generic);
},
webhookConfig() {
return {
'generic': {
component: Generic,
validations: {
webhook_url: {required},
basic_auth_username: {},
basic_auth_password: {},
timeout: {},
},
defaultConfig: {
webhook_url: '',
basic_auth_username: '',
basic_auth_password: '',
timeout: '5',
}
},
'email': {
component: Email,
validations: {
to: {required},
subject: {required},
message: {required}
},
defaultConfig: {
to: '',
subject: '',
message: ''
}
},
'tunein': {
component: Tunein,
validations: {
station_id: {required},
partner_id: {required},
partner_key: {required},
},
defaultConfig: {
station_id: '',
partner_id: '',
partner_key: ''
}
},
'discord': {
component: Discord,
validations: {
webhook_url: {required},
content: {},
title: {},
description: {},
url: {},
author: {},
thumbnail: {},
footer: {},
},
defaultConfig: {
webhook_url: '',
content: this.langDiscordDefaultContent,
title: '{{ now_playing.song.title }}',
description: '{{ now_playing.song.artist }}',
url: '{{ station.listen_url }}',
author: '{{ live.streamer_name }}',
thumbnail: '{{ now_playing.song.art }}',
footer: this.langPoweredByAzuraCast,
}
},
'telegram': {
component: Telegram,
validations: {
bot_token: {required},
chat_id: {required},
api: {},
text: {required},
parse_mode: {required}
},
defaultConfig: {
bot_token: '',
chat_id: '',
api: '',
text: this.langTelegramDefaultContent,
parse_mode: 'Markdown'
}
},
'twitter': {
component: Twitter,
validations: {
consumer_key: {required},
consumer_secret: {required},
token: {required},
token_secret: {required},
rate_limit: {},
message: {},
message_song_changed_live: {},
message_live_connect: {},
message_live_disconnect: {},
message_station_offline: {},
message_station_online: {}
},
defaultConfig: {
consumer_key: '',
consumer_secret: '',
token: '',
token_secret: '',
rate_limit: 0,
message: this.langTwitterDefaultMessage,
message_song_changed_live: this.langTwitterSongChangedLiveMessage,
message_live_connect: this.langTwitterDjOnMessage,
message_live_disconnect: this.langTwitterDjOffMessage,
message_station_offline: this.langTwitterStationOfflineMessage,
message_station_online: this.langTwitterStationOnlineMessage
}
},
'mastodon': {
component: Mastodon,
validations: {
instance_url: {required},
access_token: {required},
rate_limit: {},
visibility: {required},
message: {},
message_song_changed_live: {},
message_live_connect: {},
message_live_disconnect: {},
message_station_offline: {},
message_station_online: {}
},
defaultConfig: {
instance_url: '',
access_token: '',
rate_limit: 0,
visibility: 'public',
message: this.langTwitterDefaultMessage,
message_song_changed_live: this.langTwitterSongChangedLiveMessage,
message_live_connect: this.langTwitterDjOnMessage,
message_live_disconnect: this.langTwitterDjOffMessage,
message_station_offline: this.langTwitterStationOfflineMessage,
message_station_online: this.langTwitterStationOnlineMessage
}
},
'google_analytics': {
component: GoogleAnalytics,
validations: {
tracking_id: {required}
},
defaultConfig: {
tracking_id: ''
}
},
'matomo_analytics': {
component: MatomoAnalytics,
validations: {
matomo_url: {required},
site_id: {required},
token: {},
},
defaultConfig: {
matomo_url: '',
site_id: '',
token: ''
}
}
};
},
langPoweredByAzuraCast() {
return this.$gettext('Powered by AzuraCast');
},
langDiscordDefaultContent() {
return this.$gettext(
'Now playing on %{ station }:',
{'station': '{{ station.name }}'}
);
},
langTelegramDefaultContent() {
return this.$gettext(
'Now playing on %{ station }: %{ title } by %{ artist }! Tune in now.',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}'
}
);
},
langTwitterDefaultMessage() {
return this.$gettext(
'Now playing on %{ station }: %{ title } by %{ artist }! Tune in now: %{ url }',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}',
url: '{{ station.public_player_url }}'
}
);
},
langTwitterSongChangedLiveMessage() {
return this.$gettext(
'Now playing on %{ station }: %{ title } by %{ artist } with your host, %{ dj }! Tune in now: %{ url }',
{
station: '{{ station.name }}',
title: '{{ now_playing.song.title }}',
artist: '{{ now_playing.song.artist }}',
dj: '{{ live.streamer_name }}',
url: '{{ station.public_player_url }}'
}
);
},
langTwitterDjOnMessage() {
return this.$gettext(
'%{ dj } is now live on %{ station }! Tune in now: %{ url }',
{
dj: '{{ live.streamer_name }}',
station: '{{ station.name }}',
url: '{{ station.public_player_url }}'
}
);
},
langTwitterDjOffMessage() {
return this.$gettext(
'Thanks for listening to %{ station }!',
{
station: '{{ station.name }}',
}
);
},
langTwitterStationOfflineMessage() {
return this.$gettext(
'%{ station } is going offline for now.',
{
station: '{{ station.name }}'
}
);
},
langTwitterStationOnlineMessage() {
return this.$gettext(
'%{ station } is back online! Tune in now: %{ url }',
{
station: '{{ station.name }}',
url: '{{ station.public_player_url }}'
}
);
defaultConfig: {
station_id: '',
partner_id: '',
partner_key: ''
}
},
methods: {
resetForm() {
this.type = null;
this.form = {
name: null,
triggers: [],
config: {}
};
'discord': {
component: Discord,
validations: {
webhook_url: {required},
content: {},
title: {},
description: {},
url: {},
author: {},
thumbnail: {},
footer: {},
},
setType(type) {
this.type = type;
this.form.config = get(this.webhookConfig, [type, 'defaultConfig'], {});
defaultConfig: {
webhook_url: '',
content: langDiscordDefaultContent,
title: '{{ now_playing.song.title }}',
description: '{{ now_playing.song.artist }}',
url: '{{ station.listen_url }}',
author: '{{ live.streamer_name }}',
thumbnail: '{{ now_playing.song.art }}',
footer: langPoweredByAzuraCast,
}
},
'telegram': {
component: Telegram,
validations: {
bot_token: {required},
chat_id: {required},
api: {},
text: {required},
parse_mode: {required}
},
getSubmittableFormData() {
let formData = this.form;
if (!this.isEditMode) {
formData.type = this.type;
}
return formData;
defaultConfig: {
bot_token: '',
chat_id: '',
api: '',
text: langTelegramDefaultContent,
parse_mode: 'Markdown'
}
},
'twitter': {
component: Twitter,
validations: {
consumer_key: {required},
consumer_secret: {required},
token: {required},
token_secret: {required},
rate_limit: {},
message: {},
message_song_changed_live: {},
message_live_connect: {},
message_live_disconnect: {},
message_station_offline: {},
message_station_online: {}
},
populateForm(d) {
this.type = d.type;
this.form = {
name: d.name,
triggers: d.triggers,
config: d.config
};
defaultConfig: {
consumer_key: '',
consumer_secret: '',
token: '',
token_secret: '',
rate_limit: 0,
message: langTwitterDefaultMessage,
message_song_changed_live: langTwitterSongChangedLiveMessage,
message_live_connect: langTwitterDjOnMessage,
message_live_disconnect: langTwitterDjOffMessage,
message_station_offline: langTwitterStationOfflineMessage,
message_station_online: langTwitterStationOnlineMessage
}
},
'mastodon': {
component: Mastodon,
validations: {
instance_url: {required},
access_token: {required},
rate_limit: {},
visibility: {required},
message: {},
message_song_changed_live: {},
message_live_connect: {},
message_live_disconnect: {},
message_station_offline: {},
message_station_online: {}
},
defaultConfig: {
instance_url: '',
access_token: '',
rate_limit: 0,
visibility: 'public',
message: langTwitterDefaultMessage,
message_song_changed_live: langTwitterSongChangedLiveMessage,
message_live_connect: langTwitterDjOnMessage,
message_live_disconnect: langTwitterDjOffMessage,
message_station_offline: langTwitterStationOfflineMessage,
message_station_online: langTwitterStationOnlineMessage
}
},
'google_analytics': {
component: GoogleAnalytics,
validations: {
tracking_id: {required}
},
defaultConfig: {
tracking_id: ''
}
},
'matomo_analytics': {
component: MatomoAnalytics,
validations: {
matomo_url: {required},
site_id: {required},
token: {},
},
defaultConfig: {
matomo_url: '',
site_id: '',
token: ''
}
}
};
const triggerOptions = computed(() => {
if (!type.value) {
return [];
}
let webhookKeys = get(props.webhookTypes, [type.value, 'triggers'], []);
return map(webhookKeys, (key) => {
return {
html:
'<h6 class="font-weight-bold mb-0">' + props.triggerTitles[key] + '</h6>'
+ '<p class="card-text small">' + props.triggerDescriptions[key] + '</p>',
value: key
};
});
});
const typeTitle = computed(() => {
return get(props.webhookTypes, [type.value, 'name'], '');
});
const formComponent = computed(() => {
return get(webhookConfig, [type.value, 'component'], Generic);
});
const {
loading,
error,
isEditMode,
v$,
resetForm,
clearContents: originalClearContents,
create,
edit,
doSubmit,
close
} = useBaseEditModal(
props,
emit,
$modal,
() => computed(() => {
let validations = {
name: {required},
triggers: {},
config: {}
};
const triggerOptionsValue = triggerOptions.value;
if (triggerOptionsValue.length > 0) {
validations.triggers = {required};
}
if (type.value !== null) {
validations.config = get(
webhookConfig,
[type.value, 'validations'],
{}
);
}
return validations;
}),
() => computed(() => {
let newForm = {
name: null,
triggers: [],
config: {}
};
if (type.value !== null) {
newForm.config = get(
webhookConfig,
[type.value, 'defaultConfig'],
{}
);
}
return newForm;
}),
{
populateForm: (data, formRef) => {
type.value = data.type;
formRef.value = {
name: data.name,
triggers: data.triggers,
config: data.config
};
},
getSubmittableFormData(formRef, isEditModeRef) {
let formData = formRef.value;
if (!isEditModeRef.value) {
formData.type = type.value;
}
return formData;
},
}
);
const langTitle = computed(() => {
return isEditMode.value
? $gettext('Edit Web Hook')
: $gettext('Add Web Hook');
});
const clearContents = () => {
type.value = null;
originalClearContents();
};
const setType = (newType) => {
type.value = newType;
resetForm();
};
defineExpose({
create,
edit,
close
});
</script>

View File

@ -182,6 +182,7 @@ export function useBaseEditModal(
isEditMode,
form,
v$,
resetForm,
clearContents,
create,
edit,

View File

@ -1,11 +1,12 @@
import {ref} from "vue";
import {cloneDeep} from "lodash";
import {resolveUnref} from "@vueuse/core";
export function useResettableRef(original) {
const record = ref(cloneDeep(original));
const record = ref(cloneDeep(resolveUnref(original)));
const reset = () => {
record.value = cloneDeep(original);
record.value = cloneDeep(resolveUnref(original));
}
return {record, reset};

View File

@ -5,10 +5,12 @@ import {useAzuraCast} from "~/vendor/azuracast";
import {useTranslate} from "~/vendor/gettext";
import {useNotify} from "~/vendor/bootstrapVue";
const injectKey = 'axios';
/* Composition API Axios utilities */
export function useAxios() {
return {
axios: inject('axios')
axios: inject(injectKey)
};
}
@ -57,5 +59,5 @@ export default function installAxios(vueApp) {
vueApp.use(VueAxios, axios);
vueApp.provide('axios', axios);
vueApp.provide(injectKey, axios);
}