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

Vue 3 WIP

This commit is contained in:
Buster Neece 2022-12-10 11:55:09 -06:00
parent 37dc95b7d9
commit a4e1f15e47
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
38 changed files with 1202 additions and 695 deletions

View File

@ -1,5 +1,5 @@
{ {
"vueCompilerOptions": { "vueCompilerOptions": {
"target": 2.7 "target": 3.2
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,9 @@
"@fullcalendar/daygrid": "^5.9.0", "@fullcalendar/daygrid": "^5.9.0",
"@fullcalendar/luxon2": "^5.10.2", "@fullcalendar/luxon2": "^5.10.2",
"@fullcalendar/timegrid": "^5.9.0", "@fullcalendar/timegrid": "^5.9.0",
"@fullcalendar/vue": "^5.9.0", "@fullcalendar/vue3": "^5.11",
"@vue-leaflet/vue-leaflet": "*",
"@vue/compat": "^3.2.45",
"@vuelidate/core": "^2.0.0", "@vuelidate/core": "^2.0.0",
"@vuelidate/validators": "^2.0.0", "@vuelidate/validators": "^2.0.0",
"axios": "^1", "axios": "^1",
@ -61,14 +63,12 @@
"sass-loader": "^13", "sass-loader": "^13",
"store": "^2", "store": "^2",
"sweetalert2": "11.4.8", "sweetalert2": "11.4.8",
"vue": "^2 <3", "vue": "^3.2",
"vue-axios": "^2 <3", "vue-axios": "^3.5",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-gettext": "^2.1.12", "vue-gettext": "^2.1.12",
"vue-loader": "^15 <16", "vue-loader": "^17",
"vue2-daterange-picker": "^0.6.6", "vue3-daterange-picker": "^1",
"vue2-leaflet": "^2.7.1",
"vue2-leaflet-fullscreen": "^1.0.1",
"vuedraggable": "^2.24.1", "vuedraggable": "^2.24.1",
"wavesurfer.js": "^6", "wavesurfer.js": "^6",
"webpack": "^5.52.1", "webpack": "^5.52.1",

View File

@ -0,0 +1,469 @@
$ranges-hover-bg-color: #eee !default;
$ranges-hover-text-color: #000 !default;
$ranges-active-bg-color: #08c !default;
$ranges-active-text-color: #fff !default;
//Apply/OK buttons
$primary-button-bg: #28a745 !default;
$primary-button-color: #fff !default;
//Cancel button
$secondary-button-bg: #6c757d !default;
$secondary-button-color: #fff !default;
$btn-border-width: 1px;
.daterangepicker {
.ranges {
text-align: left;
margin: 0;
width: 100%;
}
.ranges ul {
list-style: none;
margin: 0 auto;
padding: 0;
width: 100%;
}
.ranges li {
font-size: 12px;
padding: 8px 12px;
cursor: pointer;
}
.ranges li:hover {
background-color: $ranges-hover-bg-color;
color: $ranges-hover-text-color;
}
.ranges li.active {
background-color: $ranges-active-bg-color;
color: $ranges-active-text-color;
}
.monthselect, .yearselect {
font-size: 12px;
padding: 1px;
height: auto;
margin: 0;
cursor: default;
width: calc(50% - 1rem);
}
.monthselect {
margin-right: 1rem;
}
.calendar-time {
text-align: center;
margin: 4px auto 0 auto;
line-height: 30px;
position: relative;
display: flex;
justify-content: center;
}
.calendar-time select.disabled {
color: #ccc;
cursor: not-allowed;
}
select.hourselect, select.minuteselect, select.secondselect, select.ampmselect {
width: 50px;
margin: 2px;
background: #eee;
border: 1px solid #eee;
padding: 2px;
outline: 0;
font-size: 12px;
}
@import "variables";
.drp-buttons .btn {
margin-left: 8px;
font-size: 12px;
font-weight: bold;
padding: 4px 8px;
}
.btn {
display: inline-block;
//font-family: $btn-font-family;
//font-weight: $btn-font-weight;
//color: $body-color;
text-align: center;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: $btn-border-width solid transparent;
}
.btn-success, .btn-primary {
background-color: $primary-button-bg;
color: $primary-button-color;
}
.btn-secondary {
background-color: $secondary-button-bg;
color: $secondary-button-color;
}
}
.vue-daterange-picker {
*, ::after, ::before {
box-sizing: border-box;
}
}
.drp-calendar .col .left {
flex: 0 0 auto;
}
.daterangepicker {
&.hide-calendars.show-ranges {
.ranges {
width: 100%;
ul {
width: 100%;
}
}
}
.calendars-container {
display: flex;
}
}
.daterangepicker[readonly] {
pointer-events: none;
}
//imported
.daterangepicker {
position: absolute;
color: inherit;
background-color: #fff;
border-radius: 4px;
border: 1px solid #ddd;
width: 278px;
max-width: none;
padding: 0;
margin-top: 7px;
top: 100px;
left: 20px;
z-index: 3001;
display: none;
font-size: 15px;
line-height: 1em;
}
.daterangepicker:before, .daterangepicker:after {
position: absolute;
display: inline-block;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.daterangepicker:before {
top: -7px;
border-right: 7px solid transparent;
border-left: 7px solid transparent;
border-bottom: 7px solid #ccc;
}
.daterangepicker:after {
top: -6px;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
}
.daterangepicker.opensleft:before {
right: 9px;
}
.daterangepicker.opensleft:after {
right: 10px;
}
.daterangepicker.openscenter:before {
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
}
.daterangepicker.openscenter:after {
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
}
.daterangepicker.opensright:before {
left: 9px;
}
.daterangepicker.opensright:after {
left: 10px;
}
.daterangepicker.drop-up {
margin-top: -7px;
}
.daterangepicker.drop-up:before {
top: initial;
bottom: -7px;
border-bottom: initial;
border-top: 7px solid #ccc;
}
.daterangepicker.drop-up:after {
top: initial;
bottom: -6px;
border-bottom: initial;
border-top: 6px solid #fff;
}
.daterangepicker.single .drp-selected {
display: none;
}
.daterangepicker.show-calendar .drp-calendar {
display: block;
}
.daterangepicker.show-calendar .drp-buttons {
display: block;
}
.daterangepicker.auto-apply .drp-buttons {
display: none;
}
.daterangepicker .drp-calendar {
display: none;
max-width: 270px;
width: 270px;
}
.daterangepicker .drp-calendar.left {
padding: 8px 0 8px 8px;
}
.daterangepicker .drp-calendar.right {
padding: 8px;
}
.daterangepicker .drp-calendar.single .calendar-table {
border: none;
}
.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span {
color: #fff;
border: solid black;
border-width: 0 2px 2px 0;
border-radius: 0;
display: inline-block;
padding: 3px;
}
.daterangepicker .calendar-table .next span {
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
}
.daterangepicker .calendar-table .prev span {
transform: rotate(135deg);
-webkit-transform: rotate(135deg);
}
.daterangepicker .calendar-table th, .daterangepicker .calendar-table td {
white-space: nowrap;
text-align: center;
vertical-align: middle;
min-width: 32px;
width: 32px;
height: 24px;
line-height: 24px;
font-size: 12px;
border-radius: 4px;
border: 1px solid transparent;
cursor: pointer;
}
.daterangepicker .calendar-table {
border: 1px solid #fff;
border-radius: 4px;
background-color: #fff;
}
.daterangepicker .calendar-table table {
width: 100%;
margin: 0;
border-spacing: 0;
border-collapse: collapse;
display: table;
}
.daterangepicker td.available:hover, .daterangepicker th.available:hover {
background-color: #eee;
border-color: transparent;
color: inherit;
}
.daterangepicker td.week, .daterangepicker th.week {
font-size: 80%;
color: #ccc;
}
.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date {
background-color: #fff;
border-color: transparent;
color: #999;
}
.daterangepicker td.in-range {
background-color: #ebf4f8;
border-color: transparent;
color: #000;
border-radius: 0;
}
.daterangepicker td.start-date {
border-radius: 4px 0 0 4px;
}
.daterangepicker td.end-date {
border-radius: 0 4px 4px 0;
}
.daterangepicker td.start-date.end-date {
border-radius: 4px;
}
.daterangepicker td.active, .daterangepicker td.active:hover {
background-color: #357ebd;
border-color: transparent;
color: #fff;
}
.daterangepicker th.month {
width: auto;
}
.daterangepicker td.disabled, .daterangepicker option.disabled {
color: #999;
cursor: not-allowed;
text-decoration: line-through;
}
.daterangepicker select.yearselect {
width: 40%;
}
.daterangepicker .drp-buttons {
clear: both;
text-align: right;
padding: 8px;
border-top: 1px solid #ddd;
display: none;
line-height: 12px;
vertical-align: middle;
}
.daterangepicker .drp-selected {
display: inline-block;
font-size: 12px;
padding-right: 8px;
}
.daterangepicker.show-ranges .drp-calendar.left {
border-left: 1px solid #ddd;
}
.daterangepicker.show-calendar .ranges {
margin-top: 8px;
width: auto;
}
/* Larger Screen Styling */
@media (min-width: 564px) {
.daterangepicker {
width: auto;
}
.daterangepicker .ranges ul {
width: 140px;
}
.daterangepicker.single .ranges ul {
width: 100%;
}
.daterangepicker.single .drp-calendar.left {
clear: none;
}
.daterangepicker.ltr {
direction: ltr;
text-align: left;
}
.daterangepicker.ltr .drp-calendar.left {
clear: left;
margin-right: 0;
}
.daterangepicker.ltr .drp-calendar.left .calendar-table {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.daterangepicker.ltr .drp-calendar.right {
margin-left: 0;
}
.daterangepicker.ltr .drp-calendar.right .calendar-table {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.daterangepicker.ltr .drp-calendar.left .calendar-table {
padding-right: 8px;
}
.daterangepicker.rtl {
direction: rtl;
text-align: right;
}
.daterangepicker.rtl .drp-calendar.left {
clear: right;
margin-left: 0;
}
.daterangepicker.rtl .drp-calendar.left .calendar-table {
border-left: none;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.daterangepicker.rtl .drp-calendar.right {
margin-right: 0;
}
.daterangepicker.rtl .drp-calendar.right .calendar-table {
border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.daterangepicker.rtl .drp-calendar.left .calendar-table {
padding-left: 12px;
}
.daterangepicker.rtl .ranges, .daterangepicker.rtl .drp-calendar {
text-align: right;
}
}
@media (min-width: 730px) {
.daterangepicker .drp-calendar.left {
clear: none !important;
}
}

View File

@ -1,70 +1,65 @@
import Vue from 'vue';
import axios from 'axios'; import axios from 'axios';
import VueAxios from 'vue-axios'; import VueAxios from 'vue-axios';
import GetTextPlugin from 'vue-gettext'; import GetTextPlugin from 'vue-gettext';
import translations from '../../translations/translations.json'; import translations from '../../translations/translations.json';
document.addEventListener('DOMContentLoaded', function () { export default function (vueApp) {
// Configure localization return function (el, props) {
Vue.use(GetTextPlugin, { document.addEventListener('DOMContentLoaded', function () {
defaultLanguage: 'en_US', // Configure localization
translations: translations, vueApp.use(GetTextPlugin, {
silent: true defaultLanguage: 'en_US',
}); translations: translations,
silent: true
});
if (typeof App.locale !== 'undefined') { if (typeof App.locale !== 'undefined') {
Vue.config.language = App.locale; vueApp.config.language = App.locale;
} }
// Configure auto-CSRF on requests // Configure auto-CSRF on requests
if (typeof App.api_csrf !== 'undefined') { if (typeof App.api_csrf !== 'undefined') {
axios.defaults.headers.common['X-API-CSRF'] = App.api_csrf; axios.defaults.headers.common['X-API-CSRF'] = App.api_csrf;
} }
Vue.use(VueAxios, axios); vueApp.use(VueAxios, axios);
Vue.prototype.$eventHub = new Vue();
});
export default function (component) {
return function (el, props) {
return new Vue({
el: el,
created () {
let handleAxiosError = (error) => {
let notifyMessage = this.$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) => { vueApp.created(() => {
return response; let handleAxiosError = (error) => {
}, (error) => { let notifyMessage = this.$gettext('An error occurred and your request could not be completed.');
handleAxiosError(error); if (error.response) {
return Promise.reject(error); // 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);
});
}); });
},
render: createElement => createElement(component, { props: props }) vueApp.mount(el, {props: props});
}); };
};
} }

View File

@ -3,7 +3,7 @@
<permissions-form-station-row <permissions-form-station-row
v-for="(row, index) in form.permissions.$model.station" :key="index" v-for="(row, index) in form.permissions.$model.station" :key="index"
:stations="stations" :station-permissions="stationPermissions" :stations="stations" :station-permissions="stationPermissions"
:row.sync="row" @remove="remove(index)" v-model:row="form.permissions.$model.station[index]" @remove="remove(index)"
></permissions-form-station-row> ></permissions-form-station-row>
<b-button-group v-if="hasRemainingStations"> <b-button-group v-if="hasRemainingStations">

View File

@ -43,7 +43,7 @@
</data-table> </data-table>
</b-card> </b-card>
<admin-stations-edit-modal ref="editModal" :create-url="listUrl" v-bind="$props" <admin-stations-edit-modal v-bind="$props" ref="editModal" :create-url="listUrl"
@relist="relist"></admin-stations-edit-modal> @relist="relist"></admin-stations-edit-modal>
<admin-stations-clone-modal ref="cloneModal" @relist="relist"></admin-stations-clone-modal> <admin-stations-clone-modal ref="cloneModal" @relist="relist"></admin-stations-clone-modal>

View File

@ -1,7 +1,7 @@
<template> <template>
<b-modal size="lg" id="station_edit_modal" ref="modal" :title="langTitle" :busy="loading" <b-modal size="lg" id="station_edit_modal" ref="modal" :title="langTitle" :busy="loading"
@shown="resetForm" @hidden="clearContents"> @shown="resetForm" @hidden="clearContents">
<admin-stations-form ref="form" v-bind="$props" is-modal :create-url="createUrl" :edit-url="editUrl" <admin-stations-form v-bind="$props" ref="form" is-modal :create-url="createUrl" :edit-url="editUrl"
:is-edit-mode="isEditMode" @error="close" @submitted="onSubmit" :is-edit-mode="isEditMode" @error="close" @submitted="onSubmit"
@validUpdate="onValidUpdate" @loadingUpdate="onLoadingUpdate"> @validUpdate="onValidUpdate" @loadingUpdate="onLoadingUpdate">
<template #submitButton> <template #submitButton>

View File

@ -60,14 +60,14 @@
</div> </div>
<div class="datatable-main"> <div class="datatable-main">
<b-table ref="table" show-empty striped hover :selectable="selectable" :api-url="apiUrl" :per-page="perPage" <b-table ref="table" show-empty striped hover :selectable="selectable" :api-url="apiUrl" :per-page="perPage"
:current-page.sync="currentPage" @row-selected="onRowSelected" :items="itemProvider" v-model:current-page="currentPage" @row-selected="onRowSelected" :items="itemProvider"
:fields="visibleFields" :fields="visibleFields"
:empty-text="langNoRecords" :empty-filtered-text="langNoRecords" :responsive="responsive" :empty-text="langNoRecords" :empty-filtered-text="langNoRecords" :responsive="responsive"
:no-provider-paging="handleClientSide" :no-provider-sorting="handleClientSide" :no-provider-paging="handleClientSide" :no-provider-sorting="handleClientSide"
:no-provider-filtering="handleClientSide" :no-provider-filtering="handleClientSide"
tbody-tr-class="align-middle" thead-tr-class="align-middle" selected-variant="" tbody-tr-class="align-middle" thead-tr-class="align-middle" selected-variant=""
:filter="filter" @filtered="onFiltered" @refreshed="onRefreshed" :filter="filter" @filtered="onFiltered" @refreshed="onRefreshed"
:sort-by.sync="sortBy" :sort-desc.sync="sortDesc" @sort-changed="onSortChanged"> v-model:sort-by="sortBy" v-model:sort-desc="sortDesc" @sort-changed="onSortChanged">
<template #head(selected)="data"> <template #head(selected)="data">
<b-form-checkbox :aria-label="langSelectAll" :checked="allSelected" <b-form-checkbox :aria-label="langSelectAll" :checked="allSelected"
@change="toggleSelected"></b-form-checkbox> @change="toggleSelected"></b-form-checkbox>

View File

@ -1,7 +1,6 @@
<template> <template>
<date-range-picker <date-range-picker
ref="picker" controlContainerClass="" opens="left" show-dropdowns v-bind="$props" ref="picker" controlContainerClass="" opens="left" show-dropdowns
v-bind="$props"
:time-picker-increment="1" :ranges="ranges" @update="onUpdate"> :time-picker-increment="1" :ranges="ranges" @update="onUpdate">
<template #input="datePicker"> <template #input="datePicker">
<a class="btn btn-bg dropdown-toggle" id="reportrange" href="#" @click.prevent=""> <a class="btn btn-bg dropdown-toggle" id="reportrange" href="#" @click.prevent="">
@ -9,19 +8,19 @@
{{ datePicker.rangeText }} {{ datePicker.rangeText }}
</a> </a>
</template> </template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData"> <template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
<slot :name="name" v-bind="slotData"/> <slot :name="slot" v-bind="scope"></slot>
</template> </template>
</date-range-picker> </date-range-picker>
</template> </template>
<style lang="css"> <style lang="scss">
@import '../../../node_modules/vue2-daterange-picker/dist/vue2-daterange-picker.css'; @import '../../../node_modules/vue3-daterange-picker/src/assets/daterangepicker';
</style> </style>
<script> <script>
import DateRangePicker from 'vue2-daterange-picker'; import DateRangePicker from 'vue3-daterange-picker';
import Icon from "./Icon"; import Icon from "./Icon";
import {DateTime} from 'luxon'; import {DateTime} from 'luxon';

View File

@ -1,16 +1,18 @@
<template> <template>
<div class="flow-upload"> <div class="flow-upload">
<div class="upload-progress"> <div class="upload-progress">
<div class="uploading-file pt-1" v-for="(file, _) in files" :id="'file_upload_' + file.uniqueIdentifier" <template v-for="(file, _) in files">
v-if="file.is_visible" :class="{ 'text-success': file.is_completed, 'text-danger': file.error }"> <div v-if="file.is_visible" class="uploading-file pt-1" :id="'file_upload_' + file.uniqueIdentifier"
<h6 class="fileuploadname m-0">{{ file.name }}</h6> :class="{ 'text-success': file.is_completed, 'text-danger': file.error }">
<b-progress v-if="!file.is_completed" :value="file.progress_percent" :max="100" <h6 class="fileuploadname m-0">{{ file.name }}</h6>
show-progress class="h-15 my-1"></b-progress> <b-progress v-if="!file.is_completed" :value="file.progress_percent" :max="100"
<div class="upload-status" v-if="file.error"> show-progress class="h-15 my-1"></b-progress>
{{ file.error }} <div class="upload-status" v-if="file.error">
{{ file.error }}
</div>
<div class="size">{{ formatFileSize(file.size) }}</div>
</div> </div>
<div class="size">{{ formatFileSize(file.size) }}</div> </template>
</div>
</div> </div>
<div class="file-drop-target" ref="file_drop_target"> <div class="file-drop-target" ref="file_drop_target">
<translate key="lang_upload_target">Drag file(s) here to upload or</translate> <translate key="lang_upload_target">Drag file(s) here to upload or</translate>

View File

@ -27,9 +27,8 @@
</slot> </slot>
</template> </template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/> <template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData"> <slot :name="slot" v-bind="scope"></slot>
<slot :name="name" v-bind="slotData"/>
</template> </template>
</b-modal> </b-modal>
</template> </template>
@ -75,7 +74,7 @@ export default {
}, },
computed: { computed: {
filteredScopedSlots() { filteredScopedSlots() {
return _.filter(this.$scopedSlots, (slot, name) => { return _.filter(this.$slots, (slot, name) => {
return !_.includes([ return !_.includes([
'default', 'modal-footer' 'default', 'modal-footer'
], name); ], name);

View File

@ -4,7 +4,7 @@
<script> <script>
import '@fullcalendar/core/vdom'; import '@fullcalendar/core/vdom';
import FullCalendar from '@fullcalendar/vue'; import FullCalendar from '@fullcalendar/vue3';
import allLocales from '@fullcalendar/core/locales-all'; import allLocales from '@fullcalendar/core/locales-all';
import luxon2Plugin from '@fullcalendar/luxon2'; import luxon2Plugin from '@fullcalendar/luxon2';
import timeGridPlugin from '@fullcalendar/timegrid'; import timeGridPlugin from '@fullcalendar/timegrid';

View File

@ -3,7 +3,7 @@ export default {
name: 'BFormFieldset', name: 'BFormFieldset',
methods: { methods: {
getSlot(name, scope = {}) { getSlot(name, scope = {}) {
let slot = this.$scopedSlots[name] || this.$slots[name] let slot = this.$slots[name]
return typeof slot === 'function' ? slot(scope) : slot return typeof slot === 'function' ? slot(scope) : slot
} }
}, },

View File

@ -13,9 +13,8 @@
<slot name="description" v-bind="slotProps"></slot> <slot name="description" v-bind="slotProps"></slot>
</template> </template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/> <template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData"> <slot :name="slot" v-bind="scope"></slot>
<slot :name="name" v-bind="slotData"/>
</template> </template>
</b-form-group> </b-form-group>
</template> </template>
@ -33,7 +32,7 @@ export default {
}, },
computed: { computed: {
filteredScopedSlots() { filteredScopedSlots() {
return _.filter(this.$scopedSlots, (slot, name) => { return _.filter(this.$slots, (slot, name) => {
return !_.includes([ return !_.includes([
'default', 'label', 'description' 'default', 'label', 'description'
], name); ], name);

View File

@ -2,7 +2,7 @@
<b-form-group v-bind="$attrs" :label-for="id" :state="fieldState"> <b-form-group v-bind="$attrs" :label-for="id" :state="fieldState">
<template #default> <template #default>
<slot name="default" v-bind="{ id, field, state: fieldState }"> <slot name="default" v-bind="{ id, field, state: fieldState }">
<b-form-checkbox :id="id" :name="name" v-model="field.$model" v-bind="inputAttrs"> <b-form-checkbox v-bind="inputAttrs" v-model="field.$model" :id="id" :name="name">
<slot name="label" :lang="'lang_'+id"> <slot name="label" :lang="'lang_'+id">
</slot> </slot>
@ -25,9 +25,8 @@
<slot name="description" v-bind="slotProps" :lang="'lang_'+id+'_desc'"></slot> <slot name="description" v-bind="slotProps" :lang="'lang_'+id+'_desc'"></slot>
</template> </template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/> <template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData"> <slot :name="slot" v-bind="scope"></slot>
<slot :name="name" v-bind="slotData"/>
</template> </template>
</b-form-group> </b-form-group>
</template> </template>
@ -64,7 +63,7 @@ export default {
}, },
computed: { computed: {
filteredScopedSlots() { filteredScopedSlots() {
return _.filter(this.$scopedSlots, (slot, name) => { return _.filter(this.$slots, (slot, name) => {
return !_.includes([ return !_.includes([
'default', 'description' 'default', 'description'
], name); ], name);

View File

@ -2,13 +2,12 @@
<b-form-group v-bind="$attrs" :label-for="id" :state="fieldState"> <b-form-group v-bind="$attrs" :label-for="id" :state="fieldState">
<template #default> <template #default>
<slot name="default" v-bind="{ id, field, state: fieldState }"> <slot name="default" v-bind="{ id, field, state: fieldState }">
<b-form-textarea v-if="inputType === 'textarea'" ref="input" :id="id" :name="name" <b-form-textarea v-bind="inputAttrs" v-if="inputType === 'textarea'" ref="input" :id="id" :name="name"
v-model="modelValue" v-model="modelValue" :required="isRequired" :number="isNumeric" :trim="inputTrim"
:required="isRequired" :number="isNumeric" :trim="inputTrim" v-bind="inputAttrs"
:autofocus="autofocus" :state="fieldState"></b-form-textarea> :autofocus="autofocus" :state="fieldState"></b-form-textarea>
<b-form-input v-else ref="input" :type="inputType" :id="id" :name="name" v-model="modelValue" <b-form-input v-bind="inputAttrs" v-else ref="input" :type="inputType" :id="id" :name="name"
:required="isRequired" :number="isNumeric" :trim="inputTrim" v-model="modelValue" :required="isRequired" :number="isNumeric" :trim="inputTrim"
:autofocus="autofocus" v-bind="inputAttrs" :state="fieldState"></b-form-input> :autofocus="autofocus" :state="fieldState"></b-form-input>
</slot> </slot>
<b-form-invalid-feedback :state="fieldState"> <b-form-invalid-feedback :state="fieldState">
@ -17,7 +16,7 @@
</template> </template>
<template #label="slotProps"> <template #label="slotProps">
<slot name="label" v-bind="slotProps" :lang="'lang_'+id"></slot> <slot v-bind="slotProps" name="label" :lang="'lang_'+id"></slot>
<span v-if="isRequired" class="text-danger"> <span v-if="isRequired" class="text-danger">
<span aria-hidden="true">*</span> <span aria-hidden="true">*</span>
<span class="sr-only">Required</span> <span class="sr-only">Required</span>
@ -27,12 +26,11 @@
</span> </span>
</template> </template>
<template #description="slotProps"> <template #description="slotProps">
<slot name="description" v-bind="slotProps" :lang="'lang_'+id+'_desc'"></slot> <slot v-bind="slotProps" name="description" :lang="'lang_'+id+'_desc'"></slot>
</template> </template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/> <template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData"> <slot :name="slot" v-bind="scope"></slot>
<slot :name="name" v-bind="slotData"/>
</template> </template>
</b-form-group> </b-form-group>
</template> </template>
@ -101,7 +99,7 @@ export default {
} }
}, },
filteredScopedSlots() { filteredScopedSlots() {
return _.filter(this.$scopedSlots, (slot, name) => { return _.filter(this.$slots, (slot, name) => {
return !_.includes([ return !_.includes([
'default', 'label', 'description' 'default', 'label', 'description'
], name); ], name);

View File

@ -10,7 +10,7 @@
</div> </div>
<div class="break"></div> <div class="break"></div>
<small class="date-played text-muted"> <small class="date-played text-muted">
<span v-html="unixTimestampToDate(row.played_at)">{{ row.played_at }}</span> <span v-html="unixTimestampToDate(row.played_at)"></span>
</small> </small>
</div> </div>
</div> </div>

View File

@ -37,7 +37,7 @@
<div class="mt-3" v-if="playing"> <div class="mt-3" v-if="playing">
<div class="d-flex flex-row mb-2"> <div class="d-flex flex-row mb-2">
<div class="flex-shrink-0 pt-1 pr-2">{{ position | prettifyTime }}</div> <div class="flex-shrink-0 pt-1 pr-2">{{ prettifyTime(position) }}</div>
<div class="flex-fill"> <div class="flex-fill">
<input type="range" min="0" max="100" step="0.1" class="custom-range slider" <input type="range" min="0" max="100" step="0.1" class="custom-range slider"
v-bind:value="seekingPosition" v-bind:value="seekingPosition"
@ -45,7 +45,7 @@
v-on:mousemove="doSeek($event)" v-on:mousemove="doSeek($event)"
v-on:mouseup="isSeeking = false"> v-on:mouseup="isSeeking = false">
</div> </div>
<div class="flex-shrink-0 pt-1 pl-2">{{ duration | prettifyTime }}</div> <div class="flex-shrink-0 pt-1 pl-2">{{ prettifyTime(duration) }}</div>
</div> </div>
<div class="progress mb-1"> <div class="progress mb-1">
@ -93,7 +93,7 @@
<h5 class="mb-0">{{ <h5 class="mb-0">{{
rowFile.metadata.title ? rowFile.metadata.title : lang_unknown_title rowFile.metadata.title ? rowFile.metadata.title : lang_unknown_title
}}</h5> }}</h5>
<small class="pt-1">{{ rowFile.audio.length | prettifyTime }}</small> <small class="pt-1">{{ prettifyTime(rowFile.audio.length) }}</small>
</div> </div>
<p class="mb-0">{{ rowFile.metadata.artist ? rowFile.metadata.artist : lang_unknown_artist }}</p> <p class="mb-0">{{ rowFile.metadata.artist ? rowFile.metadata.artist : lang_unknown_artist }}</p>
</a> </a>
@ -155,16 +155,16 @@ export default {
this.$root.$on('new-mixer-value', this.setMixGain); this.$root.$on('new-mixer-value', this.setMixGain);
this.$root.$on('new-cue', this.onNewCue); this.$root.$on('new-cue', this.onNewCue);
}, },
filters: { methods: {
prettifyTime (time) { prettifyTime(time) {
if (typeof time === 'undefined') { if (typeof time === 'undefined') {
return 'N/A'; return 'N/A';
} }
var hours = parseInt(time / 3600); let hours = parseInt(time / 3600);
time %= 3600; time %= 3600;
var minutes = parseInt(time / 60); let minutes = parseInt(time / 60);
var seconds = parseInt(time % 60); let seconds = parseInt(time % 60);
if (minutes < 10) { if (minutes < 10) {
minutes = '0' + minutes; minutes = '0' + minutes;
@ -178,19 +178,17 @@ export default {
} else { } else {
return minutes + ':' + seconds; return minutes + ':' + seconds;
} }
} },
}, cue() {
methods: {
cue () {
this.resumeStream(); this.resumeStream();
this.$root.$emit('new-cue', (this.passThrough) ? 'off' : this.id); this.$root.$emit('new-cue', (this.passThrough) ? 'off' : this.id);
}, },
onNewCue (new_cue) { onNewCue(new_cue) {
this.passThrough = (new_cue === this.id); this.passThrough = (new_cue === this.id);
}, },
setMixGain (new_value) { setMixGain(new_value) {
if (this.id === 'playlist_1') { if (this.id === 'playlist_1') {
this.mixGainObj.gain.value = 1.0 - new_value; this.mixGainObj.gain.value = 1.0 - new_value;
} else { } else {

View File

@ -13,7 +13,7 @@
<translate key="lang_hdr_info">Continue the setup process by creating your first radio station below. You can edit any of these details later.</translate> <translate key="lang_hdr_info">Continue the setup process by creating your first radio station below. You can edit any of these details later.</translate>
</info-card> </info-card>
<admin-stations-form ref="form" v-bind="$props" :is-edit-mode="false" :create-url="createUrl" <admin-stations-form v-bind="$props" ref="form" :is-edit-mode="false" :create-url="createUrl"
@submitted="onSubmitted"> @submitted="onSubmitted">
<template #submitButtonText> <template #submitButtonText>
<translate key="lang_btn_create_and_continue">Create and Continue</translate> <translate key="lang_btn_create_and_continue">Create and Continue</translate>

View File

@ -24,7 +24,7 @@
<h5 class="m-0">{{ row.item.name }}</h5> <h5 class="m-0">{{ row.item.name }}</h5>
</template> </template>
<template #cell(format)="row"> <template #cell(format)="row">
{{ row.item.format|upper }} {{ upper(row.item.format) }}
</template> </template>
<template #cell(bitrate)="row"> <template #cell(bitrate)="row">
{{ row.item.bitrate }}kbps {{ row.item.bitrate }}kbps
@ -70,16 +70,14 @@ export default {
] ]
}; };
}, },
filters: { methods: {
upper(data) { upper(data) {
let upper = []; let upper = [];
data.split(' ').forEach((word) => { data.split(' ').forEach((word) => {
upper.push(word.toUpperCase()); upper.push(word.toUpperCase());
}); });
return upper.join(' '); return upper.join(' ');
} },
},
methods: {
relist() { relist() {
this.$refs.datatable.refresh(); this.$refs.datatable.refresh();
}, },

View File

@ -32,7 +32,7 @@
<template v-if="row.item.enable_autodj"> <template v-if="row.item.enable_autodj">
<translate key="lang_autodj_enabled">Enabled</translate> <translate key="lang_autodj_enabled">Enabled</translate>
- -
{{ row.item.autodj_bitrate }}kbps {{ row.item.autodj_format|upper }} {{ row.item.autodj_bitrate }}kbps {{ upper(row.item.autodj_format) }}
</template> </template>
<template v-else> <template v-else>
<translate key="lang_autodj_disabled">Disabled</translate> <translate key="lang_autodj_disabled">Disabled</translate>
@ -86,16 +86,14 @@ export default {
] ]
}; };
}, },
filters: { methods: {
upper(data) { upper(data) {
let upper = []; let upper = [];
data.split(' ').forEach((word) => { data.split(' ').forEach((word) => {
upper.push(word.toUpperCase()); upper.push(word.toUpperCase());
}); });
return upper.join(' '); return upper.join(' ');
} },
},
methods: {
relist() { relist() {
this.$refs.datatable.refresh(); this.$refs.datatable.refresh();
}, },

View File

@ -11,7 +11,7 @@
<playlists-form-schedule-row v-for="(row, index) in scheduleItems" :key="index" <playlists-form-schedule-row v-for="(row, index) in scheduleItems" :key="index"
:station-time-zone="stationTimeZone" :station-time-zone="stationTimeZone"
:index="index" :row.sync="row" @remove="remove(index)"> :index="index" v-model:row="scheduleItems[index]" @remove="remove(index)">
</playlists-form-schedule-row> </playlists-form-schedule-row>
<b-button-group> <b-button-group>

View File

@ -4,26 +4,26 @@
<div class="row" id="profile"> <div class="row" id="profile">
<div class="col-lg-7"> <div class="col-lg-7">
<profile-now-playing :np="np" v-bind="$props"></profile-now-playing> <profile-now-playing v-bind="$props" :np="np"></profile-now-playing>
<profile-schedule :station-time-zone="stationTimeZone" :schedule-items="np.schedule"></profile-schedule> <profile-schedule :station-time-zone="stationTimeZone" :schedule-items="np.schedule"></profile-schedule>
<profile-streams :np="np" v-bind="$props"></profile-streams> <profile-streams v-bind="$props" :np="np"></profile-streams>
<profile-public-pages v-bind="$props"></profile-public-pages> <profile-public-pages v-bind="$props"></profile-public-pages>
</div> </div>
<div class="col-lg-5"> <div class="col-lg-5">
<profile-requests v-if="stationSupportsRequests" v-bind="$props"></profile-requests> <profile-requests v-bind="$props" v-if="stationSupportsRequests"></profile-requests>
<profile-streamers v-if="stationSupportsStreamers" v-bind="$props"></profile-streamers> <profile-streamers v-bind="$props" v-if="stationSupportsStreamers"></profile-streamers>
<template v-if="hasActiveFrontend"> <template v-if="hasActiveFrontend">
<profile-frontend :np="np" v-bind="$props"></profile-frontend> <profile-frontend v-bind="$props" :np="np"></profile-frontend>
</template> </template>
<template v-if="hasActiveBackend"> <template v-if="hasActiveBackend">
<profile-backend :np="np" v-bind="$props"></profile-backend> <profile-backend v-bind="$props" :np="np"></profile-backend>
</template> </template>
<template v-else> <template v-else>
<profile-backend-none></profile-backend-none> <profile-backend-none></profile-backend-none>

View File

@ -55,7 +55,7 @@
<translate key="lang_public_pages_disable">Disable</translate> <translate key="lang_public_pages_disable">Disable</translate>
</a> </a>
</div> </div>
<embed-modal ref="embed_modal" v-bind="$props"></embed-modal> <embed-modal v-bind="$props" ref="embed_modal"></embed-modal>
</template> </template>
<template v-else> <template v-else>
<div class="card-header bg-primary-dark"> <div class="card-header bg-primary-dark">

View File

@ -6,7 +6,7 @@
</h3> </h3>
</div> </div>
<admin-stations-form ref="form" v-bind="$props" :is-edit-mode="true" :edit-url="editUrl" <admin-stations-form v-bind="$props" ref="form" :is-edit-mode="true" :edit-url="editUrl"
@submitted="onSubmitted"></admin-stations-form> @submitted="onSubmitted"></admin-stations-form>
</section> </section>
</template> </template>

View File

@ -28,7 +28,7 @@
<template v-if="row.item.enable_autodj"> <template v-if="row.item.enable_autodj">
<translate key="lang_autodj_enabled">Enabled</translate> <translate key="lang_autodj_enabled">Enabled</translate>
- -
{{ row.item.autodj_bitrate }}kbps {{ row.item.autodj_format|upper }} {{ row.item.autodj_bitrate }}kbps {{ upper(row.item.autodj_format) }}
</template> </template>
<template v-else> <template v-else>
<translate key="lang_autodj_disabled">Disabled</translate> <translate key="lang_autodj_disabled">Disabled</translate>
@ -77,7 +77,7 @@ export default {
] ]
}; };
}, },
filters: { methods: {
upper(data) { upper(data) {
if (!data) { if (!data) {
return ''; return '';
@ -88,9 +88,7 @@ export default {
upper.push(word.toUpperCase()); upper.push(word.toUpperCase());
}); });
return upper.join(' '); return upper.join(' ');
} },
},
methods: {
relist() { relist() {
this.$refs.datatable.refresh(); this.$refs.datatable.refresh();
}, },

View File

@ -1,6 +1,5 @@
<template> <template>
<l-map v-if="mapPoints.length < 3000" style="height: 300px; z-index: 0;" :zoom="1" :center="[40, 0]"> <l-map v-if="mapPoints.length < 3000" style="height: 300px; z-index: 0;" :zoom="1" :center="[40, 0]">
<l-control-fullscreen/>
<l-tile-layer :url="tileUrl" :attribution="tileAttribution"></l-tile-layer> <l-tile-layer :url="tileUrl" :attribution="tileAttribution"></l-tile-layer>
<l-marker v-for="l in mapPoints" :key="l.hash" <l-marker v-for="l in mapPoints" :key="l.hash"
:lat-lng="{lat: l.location.lat, lng: l.location.lon}"> :lat-lng="{lat: l.location.lat, lng: l.location.lon}">
@ -22,8 +21,7 @@
<script> <script>
import L from 'leaflet'; import L from 'leaflet';
import {LMap, LMarker, LTileLayer, LTooltip} from 'vue2-leaflet'; import {LMap, LMarker, LTileLayer, LTooltip} from '@vue-leaflet/vue-leaflet';
import LControlFullscreen from 'vue2-leaflet-fullscreen';
import _ from 'lodash'; import _ from 'lodash';
export default { export default {
@ -36,8 +34,7 @@ export default {
LMap, LMap,
LTileLayer, LTileLayer,
LMarker, LMarker,
LTooltip, LTooltip
LControlFullscreen
}, },
data() { data() {
return { return {

View File

@ -22,6 +22,7 @@
nonsubscription transmissions other than broadcast simulcasts and transmissions of non-music nonsubscription transmissions other than broadcast simulcasts and transmissions of non-music
programming." If your station does not fall within this category, update the transmission programming." If your station does not fall within this category, update the transmission
category field accordingly. category field accordingly.
</li>
<li>The data collected by AzuraCast meets the SoundExchange standard for Actual Total <li>The data collected by AzuraCast meets the SoundExchange standard for Actual Total
Performances (ATP) by tracking unique listeners across all song plays. All other information Performances (ATP) by tracking unique listeners across all song plays. All other information
is derived from the metadata of the uploaded songs themselves, and may not be completely is derived from the metadata of the uploaded songs themselves, and may not be completely

View File

@ -12,7 +12,7 @@
<streamers-form-schedule-row v-for="(row, index) in scheduleItems" :key="index" <streamers-form-schedule-row v-for="(row, index) in scheduleItems" :key="index"
:station-time-zone="stationTimeZone" :station-time-zone="stationTimeZone"
:index="index" :row.sync="row" @remove="remove(index)"> :index="index" v-model:row="scheduleItems[index]" @remove="remove(index)">
</streamers-form-schedule-row> </streamers-form-schedule-row>
<b-button-group> <b-button-group>

View File

@ -1,8 +1,13 @@
import initBase from '~/base.js'; import initBase from '~/base.js';
import {createApp} from "vue";
import '~/vendor/bootstrapVue.js'; import useSweetAlert from '~/vendor/sweetalert.js';
import '~/vendor/sweetalert.js'; import useBootstrapVue from '~/vendor/bootstrapVue.js';
import AdminApiKeys from '~/components/Admin/ApiKeys.vue'; import AdminApiKeys from '~/components/Admin/ApiKeys.vue';
export default initBase(AdminApiKeys); const vueApp = createApp(AdminApiKeys);
useSweetAlert(vueApp);
useBootstrapVue(vueApp);
export default initBase(vueApp);

View File

@ -1,10 +1,12 @@
import initBase import initBase from '~/base.js';
from '~/base.js'; import {createApp} from "vue";
import useBootstrapVue from '~/vendor/bootstrapVue.js';
import '~/vendor/bootstrapVue.js';
import '~/vendor/luxon.js'; import '~/vendor/luxon.js';
import AuditLog import AuditLog from '~/components/Admin/AuditLog.vue';
from '~/components/Admin/AuditLog.vue';
export default initBase(AuditLog); const vueApp = createApp(AuditLog);
useBootstrapVue(vueApp);
export default initBase(vueApp);

View File

@ -6,6 +6,8 @@ Vue.use(PiniaVuePlugin);
const pinia = createPinia(); const pinia = createPinia();
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
let inlinePlayer = new Vue({ let inlinePlayer = new Vue({
el: '#radio-player-controls', el: '#radio-player-controls',
render: createElement => createElement(InlinePlayer), render: createElement => createElement(InlinePlayer),

View File

@ -1,111 +1,106 @@
import Vue from 'vue';
import {BootstrapVue} from 'bootstrap-vue'; import {BootstrapVue} from 'bootstrap-vue';
import 'bootstrap-vue/dist/bootstrap-vue.css'; import 'bootstrap-vue/dist/bootstrap-vue.css';
Vue.use(BootstrapVue); export default function useBootstrapVue(vueApp) {
vueApp.use(BootstrapVue);
const BootstrapVueNotifiers = { vueApp.config.globalProperties.$notify = function (message = null, options = {}) {
install(Vue, opts) { if (!!document.hidden) {
Vue.prototype.$notify = function (message = null, options = {}) { return;
if (!!document.hidden) { }
return;
}
const defaults = {
variant: 'default',
toaster: 'b-toaster-top-right',
autoHideDelay: 3000,
solid: true
};
this.$bvToast.toast(message, {...defaults, ...options}); const defaults = {
variant: 'default',
toaster: 'b-toaster-top-right',
autoHideDelay: 3000,
solid: true
}; };
Vue.prototype.$notifyError = function (message = null, options = {}) { this.$bvToast.toast(message, {...defaults, ...options});
if (message === null) { };
message = this.$gettext('An error occurred and your request could not be completed.');
}
const defaults = { vueApp.config.globalProperties.$notifyError = function (message = null, options = {}) {
variant: 'danger', if (message === null) {
title: this.$gettext('Error') message = this.$gettext('An error occurred and your request could not be completed.');
}; }
this.$notify(message, {...defaults, ...options}); const defaults = {
variant: 'danger',
return message; title: this.$gettext('Error')
}; };
Vue.prototype.$notifySuccess = function (message = null, options = {}) { this.$notify(message, {...defaults, ...options});
if (message === null) {
message = this.$gettext('Changes saved.');
}
const defaults = { return message;
variant: 'success', };
title: this.$gettext('Success')
};
this.$notify(message, {...defaults, ...options}); vueApp.config.globalProperties.$notifySuccess = function (message = null, options = {}) {
if (message === null) {
message = this.$gettext('Changes saved.');
}
return message; const defaults = {
variant: 'success',
title: this.$gettext('Success')
}; };
const LOADING_TOAST_ID = 'toast-loading'; this.$notify(message, {...defaults, ...options});
Vue.prototype.$showLoading = function (message = null, options = {}) { return message;
if (message === null) { };
message = this.$gettext('Applying changes...');
}
const defaults = { const LOADING_TOAST_ID = 'toast-loading';
id: LOADING_TOAST_ID,
variant: 'warning',
title: this.$gettext('Please wait...'),
autoHideDelay: 10000,
isStatus: true
};
this.$notify(message, {...defaults, ...options}); vueApp.config.globalProperties.$showLoading = function (message = null, options = {}) {
return message; 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
}; };
Vue.prototype.$hideLoading = function () { this.$notify(message, {...defaults, ...options});
this.$bvToast.hide(LOADING_TOAST_ID); return message;
}; };
let $isAxiosLoading = false; vueApp.config.globalProperties.$hideLoading = function () {
let $axiosLoadCount = 0; this.$bvToast.hide(LOADING_TOAST_ID);
};
Vue.prototype.$setLoading = function (isLoading) { let $isAxiosLoading = false;
let prevIsLoading = $isAxiosLoading; let $axiosLoadCount = 0;
if (isLoading) {
$axiosLoadCount++;
$isAxiosLoading = true;
} else if ($axiosLoadCount > 0) {
$axiosLoadCount--;
$isAxiosLoading = ($axiosLoadCount > 0);
}
// Handle state changes vueApp.config.globalProperties.$setLoading = function (isLoading) {
if (!prevIsLoading && $isAxiosLoading) { let prevIsLoading = $isAxiosLoading;
this.$showLoading(); if (isLoading) {
} else if (prevIsLoading && !$isAxiosLoading) { $axiosLoadCount++;
this.$hideLoading(); $isAxiosLoading = true;
} } else if ($axiosLoadCount > 0) {
}; $axiosLoadCount--;
$isAxiosLoading = ($axiosLoadCount > 0);
}
Vue.prototype.$wrapWithLoading = function (promise) { // Handle state changes
this.$setLoading(true); if (!prevIsLoading && $isAxiosLoading) {
this.$showLoading();
} else if (prevIsLoading && !$isAxiosLoading) {
this.$hideLoading();
}
};
promise.finally(() => { vueApp.config.globalProperties.$wrapWithLoading = function (promise) {
this.$setLoading(false); this.$setLoading(true);
});
return promise; promise.finally(() => {
}; this.$setLoading(false);
} });
return promise;
};
}; };
Vue.use(BootstrapVueNotifiers);

View File

@ -1,8 +1,7 @@
import Vue import VueClipboard from 'vue-clipboard2';
from 'vue';
import VueClipboard
from 'vue-clipboard2';
VueClipboard.config.autoSetContainer = true; VueClipboard.config.autoSetContainer = true;
Vue.use(VueClipboard); export default function useVueClipboard(vueApp) {
vueApp.use(VueClipboard);
};

View File

@ -1,4 +1,5 @@
import { Settings } from 'luxon'; import {Settings} from 'luxon';
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
Settings.defaultLocale = App.locale_with_dashes; Settings.defaultLocale = App.locale_with_dashes;

View File

@ -1,25 +1,19 @@
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import Vue from 'vue'; export default function useSweetAlert(vueApp) {
vueApp.config.globalProperties.$swal = function (options = {}) {
return Swal.fire(options);
};
const ConfirmFunctions = { vueApp.config.globalProperties.$confirmDelete = function (options = {}) {
install(Vue, opts) { const defaults = {
Vue.prototype.$swal = function (options = {}) { title: this.$gettext('Delete Record?'),
return Swal.fire(options); confirmButtonText: this.$gettext('Delete'),
confirmButtonColor: '#e64942',
showCancelButton: true,
focusCancel: true
}; };
Vue.prototype.$confirmDelete = function (options = {}) { return this.$swal({...defaults, ...options});
const defaults = { };
title: this.$gettext('Delete Record?'), }
confirmButtonText: this.$gettext('Delete'),
confirmButtonColor: '#e64942',
showCancelButton: true,
focusCancel: true
};
return this.$swal({...defaults, ...options});
};
}
};
Vue.use(ConfirmFunctions);

View File

@ -60,7 +60,8 @@ module.exports = {
resolve: { resolve: {
enforceExtension: false, enforceExtension: false,
alias: { alias: {
'~': path.resolve(__dirname, './vue') '~': path.resolve(__dirname, './vue'),
vue: '@vue/compat'
}, },
extensions: ['.js', '.vue', '.json'] extensions: ['.js', '.vue', '.json']
}, },
@ -101,14 +102,18 @@ module.exports = {
rules: [ rules: [
{ {
test: /\.vue$/i, test: /\.vue$/i,
use: [ loader: 'vue-loader',
'vue-loader' options: {
] compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
}, },
{ {
test: /\.scss$/i, test: /\.scss$/i,
use: [ use: [
'vue-style-loader',
'css-loader', 'css-loader',
'sass-loader' 'sass-loader'
] ]
@ -116,7 +121,6 @@ module.exports = {
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: [
'vue-style-loader',
'css-loader' 'css-loader'
] ]
}, },