Merge commit '79df3bb8b426bd747e6b7bfc621cda06dfef7e5f'
This commit is contained in:
commit
4b72fbe3b7
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"vueCompilerOptions": {
|
||||
"target": 2.7
|
||||
"target": 3.2
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,9 +16,11 @@
|
|||
"@fullcalendar/daygrid": "^5.9.0",
|
||||
"@fullcalendar/luxon2": "^5.10.2",
|
||||
"@fullcalendar/timegrid": "^5.9.0",
|
||||
"@fullcalendar/vue": "^5.9.0",
|
||||
"@fullcalendar/vue3": "^5.11",
|
||||
"@vue/compat": "^3.2.45",
|
||||
"@vuelidate/core": "^2.0.0",
|
||||
"@vuelidate/validators": "^2.0.0",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"axios": "^1",
|
||||
"bootstrap": "^4.6.0 <5",
|
||||
"bootstrap-notify": "^3.1.3",
|
||||
|
@ -61,14 +63,13 @@
|
|||
"sass-loader": "^13",
|
||||
"store": "^2",
|
||||
"sweetalert2": "11.4.8",
|
||||
"vue": "^2 <3",
|
||||
"vue-axios": "^2 <3",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-gettext": "^2.1.12",
|
||||
"vue-loader": "^15 <16",
|
||||
"vue2-daterange-picker": "^0.6.6",
|
||||
"vue2-leaflet": "^2.7.1",
|
||||
"vue2-leaflet-fullscreen": "^1.0.1",
|
||||
"vue": "^3.2",
|
||||
"vue-axios": "^3.5",
|
||||
"vue-loader": "^17",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue3-clipboard": "^1.0.0",
|
||||
"vue3-daterange-picker": "^1",
|
||||
"vue3-gettext": "^2.3.4",
|
||||
"vuedraggable": "^2.24.1",
|
||||
"wavesurfer.js": "^6",
|
||||
"webpack": "^5.52.1",
|
||||
|
|
|
@ -95,6 +95,7 @@ $enable-print-styles: true !default;
|
|||
|
||||
// Overrides for the Daemonite Material theme
|
||||
@import 'azuracast/overrides/body';
|
||||
@import 'azuracast/overrides/buttons';
|
||||
@import 'azuracast/overrides/card';
|
||||
@import 'azuracast/overrides/footer';
|
||||
@import 'azuracast/overrides/forms';
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
.btn > .material-icons {
|
||||
margin-right: calc($btn-padding-x / 2);
|
||||
}
|
||||
|
||||
.btn-lg > .material-icons {
|
||||
margin-right: calc($btn-padding-x-lg / 2);
|
||||
}
|
||||
|
||||
.btn-sm > .material-icons {
|
||||
margin-right: calc($btn-padding-x-sm / 2);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
& > * {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
& > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
|
@ -3,4 +3,16 @@
|
|||
@include reset-material-icons($material-icon-size * 2);
|
||||
line-height: 0.5em;
|
||||
}
|
||||
|
||||
.btn > & {
|
||||
margin-right: calc($btn-padding-x / 2);
|
||||
}
|
||||
|
||||
.btn-lg > & {
|
||||
margin-right: calc($btn-padding-x-lg / 2);
|
||||
}
|
||||
|
||||
.btn-sm > & {
|
||||
margin-right: calc($btn-padding-x-sm / 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,72 +1,89 @@
|
|||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import VueAxios from 'vue-axios';
|
||||
import GetTextPlugin from 'vue-gettext';
|
||||
import translations from '../../translations/translations.json';
|
||||
import pinia from './vendor/pinia';
|
||||
import usePinia from './vendor/pinia';
|
||||
import gettext from './vendor/gettext';
|
||||
import {createApp} from "vue";
|
||||
import useBootstrapVue from "./vendor/bootstrapVue";
|
||||
import useSweetAlert from "./vendor/sweetalert";
|
||||
import useVueClipboard from "~/vendor/clipboard";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Configure localization
|
||||
Vue.use(GetTextPlugin, {
|
||||
defaultLanguage: 'en_US',
|
||||
translations: translations,
|
||||
silent: true
|
||||
});
|
||||
export default function (component, options) {
|
||||
return function (el, props) {
|
||||
const vueApp = createApp(
|
||||
component,
|
||||
{
|
||||
...options,
|
||||
...props
|
||||
}
|
||||
);
|
||||
|
||||
if (typeof App.locale !== 'undefined') {
|
||||
Vue.config.language = App.locale;
|
||||
}
|
||||
/* Gettext */
|
||||
if (typeof App.locale !== 'undefined') {
|
||||
vueApp.config.language = App.locale;
|
||||
}
|
||||
|
||||
// Configure auto-CSRF on requests
|
||||
if (typeof App.api_csrf !== 'undefined') {
|
||||
axios.defaults.headers.common['X-API-CSRF'] = App.api_csrf;
|
||||
}
|
||||
vueApp.use(gettext);
|
||||
|
||||
Vue.use(VueAxios, axios);
|
||||
/* Axios */
|
||||
|
||||
Vue.prototype.$eventHub = new Vue();
|
||||
});
|
||||
// Configure auto-CSRF on requests
|
||||
if (typeof App.api_csrf !== 'undefined') {
|
||||
axios.defaults.headers.common['X-API-CSRF'] = App.api_csrf;
|
||||
}
|
||||
|
||||
export default function (component) {
|
||||
return function (el, props) {
|
||||
return new Vue({
|
||||
el: el,
|
||||
pinia,
|
||||
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);
|
||||
}
|
||||
vueApp.use(VueAxios, axios);
|
||||
|
||||
if (typeof this.$notifyError === 'function') {
|
||||
this.$notifyError(notifyMessage);
|
||||
}
|
||||
};
|
||||
vueApp.mixin({
|
||||
mounted() {
|
||||
const handleAxiosError = (error) => {
|
||||
const {$gettext} = gettext;
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
return config;
|
||||
}, (error) => {
|
||||
handleAxiosError(error);
|
||||
return Promise.reject(error);
|
||||
let notifyMessage = $gettext('An error occurred and your request could not be completed.');
|
||||
if (error.response) {
|
||||
// Request made and server responded
|
||||
notifyMessage = error.response.data.message;
|
||||
console.error(notifyMessage);
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
console.error(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
}
|
||||
|
||||
if (typeof this.$notifyError === 'function') {
|
||||
this.$notifyError(notifyMessage);
|
||||
}
|
||||
};
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
return config;
|
||||
}, (error) => {
|
||||
handleAxiosError(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
|
||||
axios.interceptors.response.use((response) => {
|
||||
return response;
|
||||
}, (error) => {
|
||||
handleAxiosError(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
axios.interceptors.response.use((response) => {
|
||||
return response;
|
||||
}, (error) => {
|
||||
handleAxiosError(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
},
|
||||
render: createElement => createElement(component, { props: props })
|
||||
});
|
||||
};
|
||||
/* Pinia */
|
||||
usePinia(vueApp);
|
||||
|
||||
/* Bootstrap Vue */
|
||||
useBootstrapVue(vueApp);
|
||||
|
||||
/* SweetAlert */
|
||||
useSweetAlert(vueApp);
|
||||
|
||||
/* Clipboard */
|
||||
useVueClipboard(vueApp);
|
||||
|
||||
vueApp.mount(el);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<permissions-form-station-row
|
||||
v-for="(row, index) in form.permissions.$model.station" :key="index"
|
||||
: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>
|
||||
|
||||
<b-button-group v-if="hasRemainingStations">
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
</data-table>
|
||||
</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>
|
||||
|
||||
<admin-stations-clone-modal ref="cloneModal" @relist="relist"></admin-stations-clone-modal>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<b-modal size="lg" id="station_edit_modal" ref="modal" :title="langTitle" :busy="loading"
|
||||
@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"
|
||||
@validUpdate="onValidUpdate" @loadingUpdate="onLoadingUpdate">
|
||||
<template #submitButton>
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
<template>
|
||||
<a :href="src" class="album-art" target="_blank" data-fancybox="gallery">
|
||||
<b-img class="album_art" :src="src" loading="lazy"
|
||||
:style="{ width: this.width+'px', height: 'auto', 'border-radius': '5px' }"></b-img>
|
||||
<a v-if="src" :href="src" class="album-art" target="_blank" data-fancybox="gallery">
|
||||
<img class="album_art" :src="src" loading="lazy" alt="">
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
src: String,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
<style scoped>
|
||||
img.album_art {
|
||||
width: v-bind(widthPx);
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import {computed} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
src: String,
|
||||
width: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const widthPx = computed(() => {
|
||||
return props.width + 'px';
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,29 +5,22 @@
|
|||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '~/vendor/clipboard.js';
|
||||
import Icon from './Icon';
|
||||
<script setup>
|
||||
import Icon from "~/components/Common/Icon.vue";
|
||||
import {copyToClipboard} from "~/vendor/clipboard";
|
||||
|
||||
export default {
|
||||
components: { Icon },
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
hideText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
doCopy() {
|
||||
this.$copyText(this.text).then(function (e) {
|
||||
}, function (e) {
|
||||
console.error(e);
|
||||
})
|
||||
}
|
||||
hideText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const doCopy = () => {
|
||||
copyToClipboard(props.text);
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -60,14 +60,14 @@
|
|||
</div>
|
||||
<div class="datatable-main">
|
||||
<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"
|
||||
:empty-text="langNoRecords" :empty-filtered-text="langNoRecords" :responsive="responsive"
|
||||
:no-provider-paging="handleClientSide" :no-provider-sorting="handleClientSide"
|
||||
:no-provider-filtering="handleClientSide"
|
||||
tbody-tr-class="align-middle" thead-tr-class="align-middle" selected-variant=""
|
||||
: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">
|
||||
<b-form-checkbox :aria-label="langSelectAll" :checked="allSelected"
|
||||
@change="toggleSelected"></b-form-checkbox>
|
||||
|
@ -105,9 +105,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
|
||||
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</b-table>
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,38 @@
|
|||
<template>
|
||||
<date-range-picker
|
||||
ref="picker" controlContainerClass="" opens="left" show-dropdowns
|
||||
v-bind="$props"
|
||||
:time-picker-increment="1" :ranges="ranges" @update="onUpdate">
|
||||
v-bind="$props" ref="picker" controlContainerClass="" opens="left" show-dropdowns
|
||||
:time-picker-increment="1" :ranges="ranges" v-model="dateRange" v-model:date-range="dateRange"
|
||||
@select="onSelect">
|
||||
<template #input="datePicker">
|
||||
<a class="btn btn-bg dropdown-toggle" id="reportrange" href="#" @click.prevent="">
|
||||
<icon icon="date_range"></icon>
|
||||
{{ datePicker.rangeText }}
|
||||
</a>
|
||||
</template>
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
|
||||
<template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</date-range-picker>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
@import '../../../node_modules/vue2-daterange-picker/dist/vue2-daterange-picker.css';
|
||||
<style lang="scss">
|
||||
@import 'vue3-daterange-picker/src/assets/daterangepicker';
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import DateRangePicker from 'vue2-daterange-picker';
|
||||
import DateRangePicker from 'vue3-daterange-picker';
|
||||
import Icon from "./Icon";
|
||||
import {DateTime} from 'luxon';
|
||||
|
||||
export default {
|
||||
name: 'DateRangeDropdown',
|
||||
components: {DateRangePicker, Icon},
|
||||
emits: ['update', 'input'],
|
||||
emits: ['update:modelValue', 'update'],
|
||||
inheritAttrs: false,
|
||||
model: {
|
||||
prop: 'dateRange',
|
||||
event: 'update',
|
||||
prop: 'modelValue',
|
||||
event: 'update:modelValue'
|
||||
},
|
||||
props: {
|
||||
tz: {
|
||||
|
@ -54,9 +55,8 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dateRange: { // for v-model
|
||||
type: [Object],
|
||||
default: null,
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
customRanges: {
|
||||
|
@ -65,7 +65,14 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
|
||||
dateRange: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(newValue) {
|
||||
// Noop
|
||||
}
|
||||
},
|
||||
ranges() {
|
||||
let ranges = {};
|
||||
|
||||
|
@ -113,8 +120,9 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
onUpdate(newValue) {
|
||||
this.$emit('update', newValue);
|
||||
onSelect(range) {
|
||||
this.$emit('update:modelValue', range);
|
||||
this.$emit('update', range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,24 @@
|
|||
<template>
|
||||
<div class="flow-upload">
|
||||
<div class="upload-progress">
|
||||
<div class="uploading-file pt-1" v-for="(file, _) in files" :id="'file_upload_' + file.uniqueIdentifier"
|
||||
v-if="file.is_visible" :class="{ 'text-success': file.is_completed, 'text-danger': file.error }">
|
||||
<h6 class="fileuploadname m-0">{{ file.name }}</h6>
|
||||
<b-progress v-if="!file.is_completed" :value="file.progress_percent" :max="100"
|
||||
show-progress class="h-15 my-1"></b-progress>
|
||||
<div class="upload-status" v-if="file.error">
|
||||
{{ file.error }}
|
||||
<template v-for="(file, _) in files">
|
||||
<div v-if="file.is_visible" class="uploading-file pt-1" :id="'file_upload_' + file.uniqueIdentifier"
|
||||
:class="{ 'text-success': file.is_completed, 'text-danger': file.error }">
|
||||
<h6 class="fileuploadname m-0">{{ file.name }}</h6>
|
||||
<b-progress v-if="!file.is_completed" :value="file.progress_percent" :max="100"
|
||||
show-progress class="h-15 my-1"></b-progress>
|
||||
<div class="upload-status" v-if="file.error">
|
||||
{{ file.error }}
|
||||
</div>
|
||||
<div class="size">{{ formatFileSize(file.size) }}</div>
|
||||
</div>
|
||||
<div class="size">{{ formatFileSize(file.size) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="file-drop-target" ref="file_drop_target">
|
||||
<translate key="lang_upload_target">Drag file(s) here to upload or</translate>
|
||||
<button ref="file_browse_target" class="file-upload btn btn-primary text-center ml-1" type="button">
|
||||
<translate key="lang_select_file">Select File</translate>
|
||||
<icon icon="cloud_upload"></icon>
|
||||
<translate key="lang_select_file">Select File</translate>
|
||||
</button>
|
||||
<small class="file-name"></small>
|
||||
<input type="file" :accept="validMimeTypesList" :multiple="allowMultiple"
|
||||
|
|
|
@ -27,9 +27,8 @@
|
|||
</slot>
|
||||
</template>
|
||||
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
@ -75,7 +74,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
filteredScopedSlots() {
|
||||
return _.filter(this.$scopedSlots, (slot, name) => {
|
||||
return _.filter(this.$slots, (slot, name) => {
|
||||
return !_.includes([
|
||||
'default', 'modal-footer'
|
||||
], name);
|
||||
|
|
|
@ -87,7 +87,6 @@ export default {
|
|||
}
|
||||
|
||||
this.$emit('np_updated', np_new);
|
||||
this.$eventHub.$emit('np_updated', np_new);
|
||||
|
||||
document.dispatchEvent(new CustomEvent("now-playing", {
|
||||
detail: np_new
|
||||
|
|
|
@ -4,60 +4,54 @@
|
|||
</canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import {Chart} from 'chart.js';
|
||||
import {Tableau20} from '~/vendor/chartjs-colorschemes/colorschemes.tableau.js';
|
||||
<script setup>
|
||||
import {get, templateRef, watchOnce} from "@vueuse/core";
|
||||
import {Tableau20} from "~/vendor/chartjs-colorschemes/colorschemes.tableau";
|
||||
import {Chart} from "chart.js";
|
||||
import {onUnmounted} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'PieChart',
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
options: Object,
|
||||
data: Array,
|
||||
labels: Array,
|
||||
aspectRatio: {
|
||||
type: Number,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
_chart: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
},
|
||||
methods: {
|
||||
renderChart() {
|
||||
const defaultOptions = {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: this.labels,
|
||||
datasets: this.data
|
||||
},
|
||||
options: {
|
||||
aspectRatio: this.aspectRatio,
|
||||
plugins: {
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this._chart)
|
||||
this._chart.destroy();
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, this.options, defaultOptions);
|
||||
this._chart = new Chart(this.$refs.canvas.getContext('2d'), chartOptions);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
}
|
||||
const props = defineProps({
|
||||
options: Object,
|
||||
data: Array,
|
||||
labels: Array,
|
||||
aspectRatio: {
|
||||
type: Number,
|
||||
default: 2
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const $canvas = templateRef('canvas');
|
||||
let $chart = null;
|
||||
|
||||
watchOnce($canvas, () => {
|
||||
const defaultOptions = {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: props.labels,
|
||||
datasets: props.data
|
||||
},
|
||||
options: {
|
||||
aspectRatio: props.aspectRatio,
|
||||
plugins: {
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, props.options, defaultOptions);
|
||||
$chart = new Chart(get($canvas).getContext('2d'), chartOptions);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<script>
|
||||
import '@fullcalendar/core/vdom';
|
||||
import FullCalendar from '@fullcalendar/vue';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import allLocales from '@fullcalendar/core/locales-all';
|
||||
import luxon2Plugin from '@fullcalendar/luxon2';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script>
|
||||
import '~/vendor/clipboard.js';
|
||||
import StreamingLogView from "~/components/Common/StreamingLogView";
|
||||
import {copyToClipboard} from "~/vendor/clipboard";
|
||||
|
||||
export default {
|
||||
name: 'StreamingLogModal',
|
||||
|
@ -36,7 +37,7 @@ export default {
|
|||
this.$refs.modal.show();
|
||||
},
|
||||
doCopy() {
|
||||
this.$copyText(this.$refs.logView.getContents());
|
||||
copyToClipboard(this.$refs.logView.getContents());
|
||||
},
|
||||
close() {
|
||||
this.$refs.modal.hide();
|
||||
|
|
|
@ -4,103 +4,100 @@
|
|||
</canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
<script setup>
|
||||
import {get, templateRef, watchOnce} from "@vueuse/core";
|
||||
import {Tableau20} from "~/vendor/chartjs-colorschemes/colorschemes.tableau";
|
||||
import {DateTime} from "luxon";
|
||||
import {Chart} from 'chart.js';
|
||||
import _ from "lodash";
|
||||
import {Chart} from "chart.js";
|
||||
import {onUnmounted} from "vue";
|
||||
import gettext from "~/vendor/gettext";
|
||||
|
||||
import {Tableau20} from '~/vendor/chartjs-colorschemes/colorschemes.tableau.js';
|
||||
const props = defineProps({
|
||||
options: Object,
|
||||
data: Array
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'TimeSeriesChart',
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
options: Object,
|
||||
data: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
_chart: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.renderChart();
|
||||
},
|
||||
methods: {
|
||||
renderChart () {
|
||||
const defaultOptions = {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: this.data
|
||||
const $canvas = templateRef('canvas');
|
||||
let $chart = null;
|
||||
|
||||
const {$gettext} = gettext;
|
||||
|
||||
watchOnce($canvas, () => {
|
||||
const defaultOptions = {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: props.data
|
||||
},
|
||||
options: {
|
||||
aspectRatio: 3,
|
||||
plugins: {
|
||||
zoom: {
|
||||
// Container for pan options
|
||||
pan: {
|
||||
enabled: true,
|
||||
mode: 'x'
|
||||
}
|
||||
},
|
||||
options: {
|
||||
aspectRatio: 3,
|
||||
plugins: {
|
||||
zoom: {
|
||||
// Container for pan options
|
||||
pan: {
|
||||
enabled: true,
|
||||
mode: 'x'
|
||||
}
|
||||
},
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
display: true,
|
||||
min: DateTime.now().minus({days: 30}).toJSDate(),
|
||||
max: DateTime.now().toJSDate(),
|
||||
time: {
|
||||
unit: 'day',
|
||||
tooltipFormat: DateTime.DATE_SHORT,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
distribution: 'linear',
|
||||
display: true,
|
||||
min: DateTime.now().minus({days: 30}).toJSDate(),
|
||||
max: DateTime.now().toJSDate(),
|
||||
time: {
|
||||
unit: 'day',
|
||||
tooltipFormat: DateTime.DATE_SHORT,
|
||||
},
|
||||
ticks: {
|
||||
source: 'data',
|
||||
autoSkip: true
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: this.$gettext('Listeners')
|
||||
},
|
||||
ticks: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
ticks: {
|
||||
source: 'data',
|
||||
autoSkip: true
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: $gettext('Listeners')
|
||||
},
|
||||
tooltips: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
callbacks: {
|
||||
label: function (tooltipItem, myData) {
|
||||
let label = myData.datasets[tooltipItem.datasetIndex].label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += parseFloat(tooltipItem.value).toFixed(2);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
ticks: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this._chart) this._chart.destroy();
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, this.options, defaultOptions);
|
||||
this._chart = new Chart(this.$refs.canvas.getContext('2d'), chartOptions);
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
},
|
||||
tooltips: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
callbacks: {
|
||||
label: function (tooltipItem, myData) {
|
||||
let label = myData.datasets[tooltipItem.datasetIndex].label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += parseFloat(tooltipItem.value).toFixed(2);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, props.options, defaultOptions);
|
||||
$chart = new Chart(get($canvas).getContext('2d'), chartOptions);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
<h3 class="card-subtitle">{{ user.email }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="flex-md-shrink-0 mt-3 mt-md-0">
|
||||
<div class="flex-md-shrink-0 mt-3 mt-md-0 buttons">
|
||||
<a class="btn btn-bg" role="button" :href="profileUrl">
|
||||
<icon icon="account_circle"></icon>
|
||||
<translate key="dashboard_btn_my_account">My Account</translate>
|
||||
</a>
|
||||
<a v-if="showAdmin" class="btn btn-bg ml-2" role="button" :href="adminUrl">
|
||||
<a v-if="showAdmin" class="btn btn-bg" role="button" :href="adminUrl">
|
||||
<icon icon="settings"></icon>
|
||||
<translate key="dashboard_btn_administration">Administration</translate>
|
||||
</a>
|
||||
|
@ -144,7 +144,9 @@
|
|||
</template>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
<span class="pr-1">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
</span>
|
||||
<template v-if="item.links.listeners">
|
||||
<a :href="item.links.listeners">
|
||||
{{ item.listeners.total }}
|
||||
|
|
|
@ -3,7 +3,7 @@ export default {
|
|||
name: 'BFormFieldset',
|
||||
methods: {
|
||||
getSlot(name, scope = {}) {
|
||||
let slot = this.$scopedSlots[name] || this.$slots[name]
|
||||
let slot = this.$slots[name]
|
||||
return typeof slot === 'function' ? slot(scope) : slot
|
||||
}
|
||||
},
|
||||
|
|
|
@ -13,9 +13,8 @@
|
|||
<slot name="description" v-bind="slotProps"></slot>
|
||||
</template>
|
||||
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
@ -33,7 +32,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
filteredScopedSlots() {
|
||||
return _.filter(this.$scopedSlots, (slot, name) => {
|
||||
return _.filter(this.$slots, (slot, name) => {
|
||||
return !_.includes([
|
||||
'default', 'label', 'description'
|
||||
], name);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<b-form-group v-bind="$attrs" :label-for="id" :state="fieldState">
|
||||
<template #default>
|
||||
<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>
|
||||
|
@ -25,9 +25,8 @@
|
|||
<slot name="description" v-bind="slotProps" :lang="'lang_'+id+'_desc'"></slot>
|
||||
</template>
|
||||
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
@ -64,7 +63,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
filteredScopedSlots() {
|
||||
return _.filter(this.$scopedSlots, (slot, name) => {
|
||||
return _.filter(this.$slots, (slot, name) => {
|
||||
return !_.includes([
|
||||
'default', 'description'
|
||||
], name);
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
<b-form-group v-bind="$attrs" :label-for="id" :state="fieldState">
|
||||
<template #default>
|
||||
<slot name="default" v-bind="{ id, field, state: fieldState }">
|
||||
<b-form-textarea v-if="inputType === 'textarea'" ref="input" :id="id" :name="name"
|
||||
v-model="modelValue"
|
||||
:required="isRequired" :number="isNumeric" :trim="inputTrim" v-bind="inputAttrs"
|
||||
<b-form-textarea v-bind="inputAttrs" v-if="inputType === 'textarea'" ref="input" :id="id" :name="name"
|
||||
v-model="modelValue" :required="isRequired" :number="isNumeric" :trim="inputTrim"
|
||||
:autofocus="autofocus" :state="fieldState"></b-form-textarea>
|
||||
<b-form-input v-else ref="input" :type="inputType" :id="id" :name="name" v-model="modelValue"
|
||||
:required="isRequired" :number="isNumeric" :trim="inputTrim"
|
||||
:autofocus="autofocus" v-bind="inputAttrs" :state="fieldState"></b-form-input>
|
||||
<b-form-input v-bind="inputAttrs" v-else ref="input" :type="inputType" :id="id" :name="name"
|
||||
v-model="modelValue" :required="isRequired" :number="isNumeric" :trim="inputTrim"
|
||||
:autofocus="autofocus" :state="fieldState"></b-form-input>
|
||||
</slot>
|
||||
|
||||
<b-form-invalid-feedback :state="fieldState">
|
||||
|
@ -17,7 +16,7 @@
|
|||
</template>
|
||||
|
||||
<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 aria-hidden="true">*</span>
|
||||
<span class="sr-only">Required</span>
|
||||
|
@ -27,12 +26,11 @@
|
|||
</span>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
|
||||
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData">
|
||||
<slot :name="name" v-bind="slotData"/>
|
||||
<template v-for="(_, slot) of filteredScopedSlots" v-slot:[slot]="scope">
|
||||
<slot :name="slot" v-bind="scope"></slot>
|
||||
</template>
|
||||
</b-form-group>
|
||||
</template>
|
||||
|
@ -101,7 +99,7 @@ export default {
|
|||
}
|
||||
},
|
||||
filteredScopedSlots() {
|
||||
return _.filter(this.$scopedSlots, (slot, name) => {
|
||||
return _.filter(this.$slots, (slot, name) => {
|
||||
return !_.includes([
|
||||
'default', 'label', 'description'
|
||||
], name);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<div class="break"></div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<div class="mt-3" v-if="playing">
|
||||
|
||||
<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">
|
||||
<input type="range" min="0" max="100" step="0.1" class="custom-range slider"
|
||||
v-bind:value="seekingPosition"
|
||||
|
@ -45,7 +45,7 @@
|
|||
v-on:mousemove="doSeek($event)"
|
||||
v-on:mouseup="isSeeking = false">
|
||||
</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 class="progress mb-1">
|
||||
|
@ -93,7 +93,7 @@
|
|||
<h5 class="mb-0">{{
|
||||
rowFile.metadata.title ? rowFile.metadata.title : lang_unknown_title
|
||||
}}</h5>
|
||||
<small class="pt-1">{{ rowFile.audio.length | prettifyTime }}</small>
|
||||
<small class="pt-1">{{ prettifyTime(rowFile.audio.length) }}</small>
|
||||
</div>
|
||||
<p class="mb-0">{{ rowFile.metadata.artist ? rowFile.metadata.artist : lang_unknown_artist }}</p>
|
||||
</a>
|
||||
|
@ -155,16 +155,16 @@ export default {
|
|||
this.$root.$on('new-mixer-value', this.setMixGain);
|
||||
this.$root.$on('new-cue', this.onNewCue);
|
||||
},
|
||||
filters: {
|
||||
prettifyTime (time) {
|
||||
methods: {
|
||||
prettifyTime(time) {
|
||||
if (typeof time === 'undefined') {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
var hours = parseInt(time / 3600);
|
||||
let hours = parseInt(time / 3600);
|
||||
time %= 3600;
|
||||
var minutes = parseInt(time / 60);
|
||||
var seconds = parseInt(time % 60);
|
||||
let minutes = parseInt(time / 60);
|
||||
let seconds = parseInt(time % 60);
|
||||
|
||||
if (minutes < 10) {
|
||||
minutes = '0' + minutes;
|
||||
|
@ -178,19 +178,17 @@ export default {
|
|||
} else {
|
||||
return minutes + ':' + seconds;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cue () {
|
||||
},
|
||||
cue() {
|
||||
this.resumeStream();
|
||||
this.$root.$emit('new-cue', (this.passThrough) ? 'off' : this.id);
|
||||
},
|
||||
|
||||
onNewCue (new_cue) {
|
||||
onNewCue(new_cue) {
|
||||
this.passThrough = (new_cue === this.id);
|
||||
},
|
||||
|
||||
setMixGain (new_value) {
|
||||
setMixGain(new_value) {
|
||||
if (this.id === 'playlist_1') {
|
||||
this.mixGainObj.gain.value = 1.0 - new_value;
|
||||
} else {
|
||||
|
|
|
@ -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>
|
||||
</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">
|
||||
<template #submitButtonText>
|
||||
<translate key="lang_btn_create_and_continue">Create and Continue</translate>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<h5 class="m-0">{{ row.item.name }}</h5>
|
||||
</template>
|
||||
<template #cell(format)="row">
|
||||
{{ row.item.format|upper }}
|
||||
{{ upper(row.item.format) }}
|
||||
</template>
|
||||
<template #cell(bitrate)="row">
|
||||
{{ row.item.bitrate }}kbps
|
||||
|
@ -70,16 +70,14 @@ export default {
|
|||
]
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
methods: {
|
||||
upper(data) {
|
||||
let upper = [];
|
||||
data.split(' ').forEach((word) => {
|
||||
upper.push(word.toUpperCase());
|
||||
});
|
||||
return upper.join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
relist() {
|
||||
this.$refs.datatable.refresh();
|
||||
},
|
||||
|
|
|
@ -11,10 +11,14 @@ import {required} from '@vuelidate/validators';
|
|||
import BaseEditModal from '~/components/Common/BaseEditModal';
|
||||
import FormBasicInfo from './Form/BasicInfo';
|
||||
import mergeExisting from "~/functions/mergeExisting";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
emits: ['needs-restart'],
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
components: {FormBasicInfo},
|
||||
validations() {
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
|
||||
<data-table ref="datatable" id="station_media" selectable paginated select-fields
|
||||
@row-selected="onRowSelected" @refreshed="onRefreshed" :fields="fields" :api-url="listUrl"
|
||||
@row-selected="onRowSelected" :fields="fields" :api-url="listUrl"
|
||||
:request-config="requestConfig">
|
||||
<template #cell(path)="row">
|
||||
<div class="d-flex align-items-center">
|
||||
|
@ -312,7 +312,7 @@ export default {
|
|||
|
||||
window.addEventListener('hashchange', this.onHashChange);
|
||||
},
|
||||
destroyed() {
|
||||
unmounted() {
|
||||
window.removeEventListener('hashchange', this.onHashChange);
|
||||
},
|
||||
computed: {
|
||||
|
@ -342,9 +342,6 @@ export default {
|
|||
directories: _.map(splitItems[0], 'path')
|
||||
};
|
||||
},
|
||||
onRefreshed() {
|
||||
this.$eventHub.$emit('refreshed');
|
||||
},
|
||||
onTriggerNavigate() {
|
||||
this.$refs.datatable.navigate();
|
||||
},
|
||||
|
@ -367,9 +364,6 @@ export default {
|
|||
isFilterString(str) {
|
||||
return str.substring(0, 9) === 'playlist:' || str.substring(0, 8) === 'special:';
|
||||
},
|
||||
playAudio(url) {
|
||||
this.$eventHub.$emit('player_toggle', url);
|
||||
},
|
||||
changeDirectory(newDir) {
|
||||
window.location.hash = newDir;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="row pt-4" id="app-toolbar">
|
||||
<div class="col-md-8">
|
||||
<div class="col-md-8 buttons">
|
||||
<div class="btn-group dropdown allow-focus">
|
||||
<b-dropdown size="sm" variant="primary" ref="setPlaylistsDropdown" v-b-tooltip.hover
|
||||
:title="langPlaylistDropdown">
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<template v-if="row.item.enable_autodj">
|
||||
<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 v-else>
|
||||
<translate key="lang_autodj_disabled">Disabled</translate>
|
||||
|
@ -86,16 +86,14 @@ export default {
|
|||
]
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
methods: {
|
||||
upper(data) {
|
||||
let upper = [];
|
||||
data.split(' ').forEach((word) => {
|
||||
upper.push(word.toUpperCase());
|
||||
});
|
||||
return upper.join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
relist() {
|
||||
this.$refs.datatable.refresh();
|
||||
},
|
||||
|
|
|
@ -26,10 +26,14 @@ 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";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
emits: ['needs-restart'],
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
components: {MountFormIntro, MountFormAdvanced, MountFormAutoDj, MountFormBasicInfo},
|
||||
props: {
|
||||
|
|
|
@ -18,11 +18,15 @@ import FormBasicInfo from './Form/BasicInfo';
|
|||
import FormSchedule from './Form/Schedule';
|
||||
import FormAdvanced from './Form/Advanced';
|
||||
import BaseEditModal from '~/components/Common/BaseEditModal';
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
emits: ['needs-restart'],
|
||||
components: {FormSchedule, FormBasicInfo, FormAdvanced},
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
props: {
|
||||
stationTimeZone: String,
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<playlists-form-schedule-row v-for="(row, index) in scheduleItems" :key="index"
|
||||
: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>
|
||||
|
||||
<b-button-group>
|
||||
|
|
|
@ -24,10 +24,14 @@ import PodcastCommonArtwork from './Common/Artwork';
|
|||
import EpisodeFormMedia from './EpisodeForm/Media';
|
||||
import {DateTime} from 'luxon';
|
||||
import mergeExisting from "~/functions/mergeExisting";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
components: {EpisodeFormMedia, PodcastCommonArtwork, EpisodeFormBasicInfo},
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
props: {
|
||||
stationTimeZone: String,
|
||||
|
|
|
@ -20,10 +20,14 @@ import BaseEditModal from '~/components/Common/BaseEditModal';
|
|||
import PodcastFormBasicInfo from './PodcastForm/BasicInfo';
|
||||
import PodcastCommonArtwork from './Common/Artwork';
|
||||
import mergeExisting from "~/functions/mergeExisting";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
components: {PodcastCommonArtwork, PodcastFormBasicInfo},
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
props: {
|
||||
stationTimeZone: String,
|
||||
|
|
|
@ -4,26 +4,26 @@
|
|||
|
||||
<div class="row" id="profile">
|
||||
<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-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>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<profile-frontend :np="np" v-bind="$props"></profile-frontend>
|
||||
<profile-frontend v-bind="$props" :np="np"></profile-frontend>
|
||||
</template>
|
||||
|
||||
<template v-if="hasActiveBackend">
|
||||
<profile-backend :np="np" v-bind="$props"></profile-backend>
|
||||
<profile-backend v-bind="$props" :np="np"></profile-backend>
|
||||
</template>
|
||||
<template v-else>
|
||||
<profile-backend-none></profile-backend-none>
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
<h3 class="flex-shrink card-title my-0" key="lang_profile_nowplaying_onair" v-translate>On the Air</h3>
|
||||
<h6 class="card-subtitle text-right flex-fill my-0" style="line-height: 1;">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
{{ langListeners }}
|
||||
<span class="pl-1">
|
||||
{{ langListeners }}
|
||||
</span>
|
||||
|
||||
<br>
|
||||
<small>
|
||||
<span>{{ np.listeners.unique }}</span>
|
||||
<span class="pr-1">{{ np.listeners.unique }}</span>
|
||||
<translate key="lang_profile_nowplaying_unique">Unique</translate>
|
||||
</small>
|
||||
</h6>
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<translate key="lang_public_pages_disable">Disable</translate>
|
||||
</a>
|
||||
</div>
|
||||
<embed-modal ref="embed_modal" v-bind="$props"></embed-modal>
|
||||
<embed-modal v-bind="$props" ref="embed_modal"></embed-modal>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="card-header bg-primary-dark">
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
</td>
|
||||
<td class="pl-1 text-right">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
<span class="listeners-total">{{ mount.listeners.total }}</span><br>
|
||||
<span class="listeners-total pl-1">{{ mount.listeners.total }}</span><br>
|
||||
<small>
|
||||
<span class="listeners-unique">{{ mount.listeners.unique }}</span>
|
||||
<span class="listeners-unique pr-1">{{ mount.listeners.unique }}</span>
|
||||
<translate key="lang_streams_unique">Unique</translate>
|
||||
</small>
|
||||
</td>
|
||||
|
@ -55,9 +55,9 @@
|
|||
</td>
|
||||
<td class="pl-1 text-right">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
<span class="listeners-total">{{ remote.listeners.total }}</span><br>
|
||||
<span class="listeners-total pl-1">{{ remote.listeners.total }}</span><br>
|
||||
<small>
|
||||
<span class="listeners-unique">{{ remote.listeners.unique }}</span>
|
||||
<span class="listeners-unique pr-1">{{ remote.listeners.unique }}</span>
|
||||
<translate key="lang_streams_unique">Unique</translate>
|
||||
</small>
|
||||
</td>
|
||||
|
@ -82,7 +82,7 @@
|
|||
</td>
|
||||
<td class="pl-1 text-right">
|
||||
<icon class="sm align-middle" icon="headset"></icon>
|
||||
<span class="listeners-total">{{ np.station.hls_listeners }}</span>
|
||||
<span class="listeners-total pl-1">{{ np.station.hls_listeners }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</h3>
|
||||
</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>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
<script>
|
||||
import '~/vendor/clipboard.js';
|
||||
import {copyToClipboard} from "~/vendor/clipboard";
|
||||
|
||||
export default {
|
||||
name: 'QueueLogsModal',
|
||||
|
@ -39,7 +40,7 @@ export default {
|
|||
this.$refs.modal.show();
|
||||
},
|
||||
doCopy() {
|
||||
this.$copyText(this.logs);
|
||||
copyToClipboard(this.logs);
|
||||
},
|
||||
close() {
|
||||
this.$refs.modal.hide();
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<template v-if="row.item.enable_autodj">
|
||||
<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 v-else>
|
||||
<translate key="lang_autodj_disabled">Disabled</translate>
|
||||
|
@ -77,7 +77,7 @@ export default {
|
|||
]
|
||||
};
|
||||
},
|
||||
filters: {
|
||||
methods: {
|
||||
upper(data) {
|
||||
if (!data) {
|
||||
return '';
|
||||
|
@ -88,9 +88,7 @@ export default {
|
|||
upper.push(word.toUpperCase());
|
||||
});
|
||||
return upper.join(' ');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
relist() {
|
||||
this.$refs.datatable.refresh();
|
||||
},
|
||||
|
|
|
@ -16,10 +16,14 @@ import BaseEditModal from '~/components/Common/BaseEditModal';
|
|||
import RemoteFormBasicInfo from "./Form/BasicInfo";
|
||||
import RemoteFormAutoDj from "./Form/AutoDj";
|
||||
import {REMOTE_ICECAST} from "~/components/Entity/RadioAdapters";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'RemoteEditModal',
|
||||
emits: ['needs-restart'],
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
components: {
|
||||
RemoteFormAutoDj,
|
||||
|
|
|
@ -9,14 +9,15 @@
|
|||
<translate key="lang_header">Listeners</translate>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="flex-shrink">
|
||||
<div class="flex-shrink buttons">
|
||||
<a class="btn btn-bg" id="btn-export" :href="exportUrl" target="_blank">
|
||||
<icon icon="file_download"></icon>
|
||||
<translate key="lang_download_csv_button">Download CSV</translate>
|
||||
</a>
|
||||
|
||||
<date-range-dropdown v-if="!isLive" time-picker :min-date="minDate" :max-date="maxDate"
|
||||
:tz="stationTimeZone" v-model="dateRange" @update="updateListeners">
|
||||
:tz="stationTimeZone" v-model="dateRange"
|
||||
@update="updateListeners">
|
||||
</date-range-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div id="leaflet-container" ref="map">
|
||||
<slot v-if="$map" :map="$map"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'leaflet/dist/leaflet.css';
|
||||
|
||||
.leaflet-container {
|
||||
height: 300px;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, provide, ref} from "vue";
|
||||
import L from "leaflet";
|
||||
import {get, set, templateRef} from "@vueuse/core";
|
||||
|
||||
const props = defineProps({
|
||||
attribution: String
|
||||
});
|
||||
|
||||
const $container = templateRef('map');
|
||||
const $map = ref();
|
||||
|
||||
provide('map', $map);
|
||||
|
||||
onMounted(() => {
|
||||
// Fix issue with Leaflet icons being built in Webpack
|
||||
// https://github.com/Leaflet/Leaflet/issues/4968
|
||||
delete L.Icon.Default.prototype._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
||||
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
||||
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
|
||||
});
|
||||
|
||||
// Init map
|
||||
const map = L.map(get($container));
|
||||
map.setView([40, 0], 1);
|
||||
set($map, map);
|
||||
|
||||
// Add tile layer
|
||||
const tileUrl = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/{theme}_all/{z}/{x}/{y}.png';
|
||||
const tileAttribution = 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.';
|
||||
|
||||
L.tileLayer(tileUrl, {
|
||||
theme: App.theme,
|
||||
attribution: tileAttribution,
|
||||
}).addTo(map);
|
||||
|
||||
/*
|
||||
// Add fullscreen control
|
||||
const fullscreenControl = new L.Control.Fullscreen();
|
||||
map.addControl(fullscreenControl)
|
||||
*/
|
||||
|
||||
});
|
||||
</script>
|
|
@ -1,66 +1,38 @@
|
|||
<template>
|
||||
<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-marker v-for="l in mapPoints" :key="l.hash"
|
||||
:lat-lng="{lat: l.location.lat, lng: l.location.lon}">
|
||||
<l-tooltip>
|
||||
IP: {{ l.ip }}<br>
|
||||
Country: {{ l.location.country }}<br>
|
||||
Region: {{ l.location.region }}<br>
|
||||
City: {{ l.location.city }}<br>
|
||||
Time connected: {{ l.connected_time }}<br>
|
||||
User Agent: {{ l.user_agent }}
|
||||
</l-tooltip>
|
||||
</l-marker>
|
||||
</l-map>
|
||||
<inner-map v-if="visibleListeners.length < 3000"
|
||||
:attribution="attribution">
|
||||
<map-point v-for="l in visibleListeners" :key="l.hash"
|
||||
:position="[l.location.lat, l.location.lon]">
|
||||
<translate key="l-ip">IP</translate>
|
||||
: {{ l.ip }}<br>
|
||||
<translate key="l-country">Country</translate>
|
||||
: {{ l.location.country }}<br>
|
||||
<translate key="l-region">Region</translate>
|
||||
: {{ l.location.region }}<br>
|
||||
<translate key="l-city">City</translate>
|
||||
: {{ l.location.city }}<br>
|
||||
<translate key="l-time">Time</translate>
|
||||
: {{ l.connected_time }}<br>
|
||||
<translate key="l-ua">User Agent</translate>
|
||||
: {{ l.user_agent }}
|
||||
</map-point>
|
||||
</inner-map>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
@import '../../../../../node_modules/leaflet/dist/leaflet.css';
|
||||
</style>
|
||||
<script setup>
|
||||
import InnerMap from "./InnerMap.vue";
|
||||
import MapPoint from "./MapPoint.vue";
|
||||
import {computed} from "vue";
|
||||
import _ from "lodash";
|
||||
|
||||
<script>
|
||||
import L from 'leaflet';
|
||||
import {LMap, LMarker, LTileLayer, LTooltip} from 'vue2-leaflet';
|
||||
import LControlFullscreen from 'vue2-leaflet-fullscreen';
|
||||
import _ from 'lodash';
|
||||
const props = defineProps({
|
||||
attribution: String,
|
||||
listeners: Array,
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'StationReportsListenersMap',
|
||||
props: {
|
||||
attribution: String,
|
||||
listeners: Array,
|
||||
},
|
||||
components: {
|
||||
LMap,
|
||||
LTileLayer,
|
||||
LMarker,
|
||||
LTooltip,
|
||||
LControlFullscreen
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tileUrl: 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/' + App.theme + '_all/{z}/{x}/{y}.png',
|
||||
tileAttribution: 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Fix issue with Leaflet icons being built in Webpack
|
||||
// https://github.com/Leaflet/Leaflet/issues/4968
|
||||
delete L.Icon.Default.prototype._getIconUrl;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
||||
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
||||
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
mapPoints() {
|
||||
return _.filter(this.listeners, function (l) {
|
||||
return null !== l.location.lat && null !== l.location.lon;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const visibleListeners = computed(() => {
|
||||
return _.filter(props.listeners, function (l) {
|
||||
return null !== l.location.lat && null !== l.location.lon;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div ref="popup-content">
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {get, set, templateRef} from '@vueuse/core';
|
||||
import {inject, onUnmounted, ref, toRaw, watch} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
position: Array
|
||||
});
|
||||
|
||||
const $map = inject('map');
|
||||
const $marker = ref();
|
||||
|
||||
const map = toRaw(get($map));
|
||||
const marker = L.marker(props.position);
|
||||
marker.addTo(map);
|
||||
set($marker, marker);
|
||||
|
||||
const popup = new L.Popup();
|
||||
const $popupContent = templateRef('popup-content');
|
||||
watch(
|
||||
$popupContent,
|
||||
(content) => {
|
||||
popup.setContent(content);
|
||||
marker.bindPopup(popup);
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
get($marker).remove();
|
||||
});
|
||||
</script>
|
|
@ -6,7 +6,8 @@
|
|||
<translate key="hdr">Station Statistics</translate>
|
||||
</h2>
|
||||
<div class="flex-shrink">
|
||||
<date-range-dropdown time-picker v-model="dateRange" :tz="stationTimeZone"></date-range-dropdown>
|
||||
<date-range-dropdown time-picker v-model="dateRange"
|
||||
:tz="stationTimeZone"></date-range-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,71 +4,68 @@
|
|||
</canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Chart} from 'chart.js';
|
||||
import {Tableau20} from '~/vendor/chartjs-colorschemes/colorschemes.tableau.js';
|
||||
<script setup>
|
||||
import {get, templateRef, watchOnce} from "@vueuse/core";
|
||||
import {Tableau20} from "~/vendor/chartjs-colorschemes/colorschemes.tableau";
|
||||
import {Chart} from "chart.js";
|
||||
import gettext from "~/vendor/gettext";
|
||||
import {onUnmounted} from "vue";
|
||||
|
||||
export default {
|
||||
name: 'HourChart',
|
||||
inheritAttrs: true,
|
||||
props: {
|
||||
options: Object,
|
||||
data: Array,
|
||||
labels: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
_chart: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.renderChart();
|
||||
},
|
||||
methods: {
|
||||
renderChart() {
|
||||
const defaultOptions = {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: this.labels,
|
||||
datasets: this.data
|
||||
const props = defineProps({
|
||||
options: Object,
|
||||
data: Array,
|
||||
labels: Array
|
||||
});
|
||||
|
||||
let $chart = null;
|
||||
const $canvas = templateRef('canvas');
|
||||
const {$gettext} = gettext;
|
||||
|
||||
watchOnce($canvas, () => {
|
||||
const defaultOptions = {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: props.labels,
|
||||
datasets: props.data
|
||||
},
|
||||
options: {
|
||||
aspectRatio: 2,
|
||||
plugins: {
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: $gettext('Hour')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
aspectRatio: 2,
|
||||
plugins: {
|
||||
colorschemes: {
|
||||
scheme: Tableau20
|
||||
}
|
||||
y: {
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: $gettext('Listeners')
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: this.$gettext('Hour')
|
||||
}
|
||||
},
|
||||
y: {
|
||||
scaleLabel: {
|
||||
display: true,
|
||||
labelString: this.$gettext('Listeners')
|
||||
},
|
||||
ticks: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
ticks: {
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this._chart) this._chart.destroy();
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, this.options, defaultOptions);
|
||||
this._chart = new Chart(this.$refs.canvas.getContext('2d'), chartOptions);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this._chart) {
|
||||
this._chart.destroy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
let chartOptions = _.defaultsDeep({}, props.options, defaultOptions);
|
||||
$chart = new Chart(get($canvas).getContext('2d'), chartOptions);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if ($chart) {
|
||||
$chart.destroy();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
nonsubscription transmissions other than broadcast simulcasts and transmissions of non-music
|
||||
programming." If your station does not fall within this category, update the transmission
|
||||
category field accordingly.
|
||||
</li>
|
||||
<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
|
||||
is derived from the metadata of the uploaded songs themselves, and may not be completely
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
import {required} from '@vuelidate/validators';
|
||||
import BaseEditModal from '~/components/Common/BaseEditModal';
|
||||
import SftpUsersForm from "./Form";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'SftpUsersEditModal',
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
components: {SftpUsersForm},
|
||||
validations() {
|
||||
|
|
|
@ -19,9 +19,13 @@ import FormSchedule from './Form/Schedule';
|
|||
import FormArtwork from './Form/Artwork';
|
||||
import BaseEditModal from '~/components/Common/BaseEditModal';
|
||||
import mergeExisting from "~/functions/mergeExisting";
|
||||
import useVuelidate from "@vuelidate/core";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
mixins: [BaseEditModal],
|
||||
components: {FormBasicInfo, FormSchedule, FormArtwork},
|
||||
props: {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<streamers-form-schedule-row v-for="(row, index) in scheduleItems" :key="index"
|
||||
: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>
|
||||
|
||||
<b-button-group>
|
||||
|
|
|
@ -33,9 +33,13 @@ 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";
|
||||
|
||||
export default {
|
||||
name: 'EditModal',
|
||||
setup() {
|
||||
return {v$: useVuelidate()}
|
||||
},
|
||||
components: {BasicInfo, TypeSelect},
|
||||
mixins: [BaseEditModal],
|
||||
props: {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default function (seconds) {
|
||||
seconds = parseInt(seconds);
|
||||
|
||||
let d = Math.floor(seconds / 86400),
|
||||
h = Math.floor(seconds / 3600) % 24,
|
||||
m = Math.floor(seconds / 60) % 60,
|
||||
|
@ -6,6 +8,6 @@ export default function (seconds) {
|
|||
|
||||
return (d > 0 ? d + 'd ' : '')
|
||||
+ (h > 0 ? ('0' + h).slice(-2) + ':' : '')
|
||||
+ (m > 0 ? ('0' + m).slice(-2) + ':' : '')
|
||||
+ (seconds > 60 ? ('0' + s).slice(-2) : s);
|
||||
+ ('0' + m).slice(-2) + ':'
|
||||
+ ('0' + s).slice(-2);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import Account from '~/components/Account';
|
||||
|
||||
export default initBase(Account);
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminApiKeys from '~/components/Admin/ApiKeys.vue';
|
||||
|
||||
export default initBase(AdminApiKeys);
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import initBase from '~/base.js';
|
||||
import '~/vendor/luxon.js';
|
||||
|
||||
import AuditLog
|
||||
from '~/components/Admin/AuditLog.vue';
|
||||
import AuditLog from '~/components/Admin/AuditLog.vue';
|
||||
|
||||
export default initBase(AuditLog);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminBackups from '~/components/Admin/Backups.vue';
|
||||
|
||||
export default initBase(AdminBackups);
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
|
||||
import AdminBranding
|
||||
from '~/components/Admin/Branding.vue';
|
||||
import AdminBranding from '~/components/Admin/Branding.vue';
|
||||
|
||||
export default initBase(AdminBranding);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminCustomFields from '~/components/Admin/CustomFields.vue';
|
||||
|
||||
export default initBase(AdminCustomFields);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import AdminGeoLite from '~/components/Admin/GeoLite.vue';
|
||||
|
||||
export default initBase(AdminGeoLite);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminIndex from '~/components/Admin/Index.vue';
|
||||
|
||||
export default initBase(AdminIndex);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import AdminLogs from '~/components/Admin/Logs.vue';
|
||||
|
||||
export default initBase(AdminLogs);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminPermissions from '~/components/Admin/Permissions.vue';
|
||||
|
||||
export default initBase(AdminPermissions);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import AdminSettings from '~/components/Admin/Settings.vue';
|
||||
|
||||
export default initBase(AdminSettings);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import AdminShoutcast from '~/components/Admin/Shoutcast.vue';
|
||||
|
||||
export default initBase(AdminShoutcast);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminStations from '~/components/Admin/Stations.vue';
|
||||
|
||||
export default initBase(AdminStations);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import AdminStereoTool from '~/components/Admin/StereoTool.vue';
|
||||
|
||||
export default initBase(AdminStereoTool);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import StorageLocations from '~/components/Admin/StorageLocations.vue';
|
||||
|
||||
export default initBase(StorageLocations);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import AdminUsers from '~/components/Admin/Users.vue';
|
||||
|
||||
export default initBase(AdminUsers);
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
import '~/vendor/chartjs.js';
|
||||
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import {createApp} from 'vue';
|
||||
import InlinePlayer from '~/components/InlinePlayer.vue';
|
||||
import pinia from '../vendor/pinia';
|
||||
import usePinia from '../vendor/pinia';
|
||||
import gettext from "../vendor/gettext";
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let inlinePlayer = new Vue({
|
||||
el: '#radio-player-controls',
|
||||
render: createElement => createElement(InlinePlayer),
|
||||
pinia
|
||||
});
|
||||
const inlineApp = createApp(InlinePlayer);
|
||||
|
||||
/* Gettext */
|
||||
if (typeof App.locale !== 'undefined') {
|
||||
inlineApp.config.language = App.locale;
|
||||
}
|
||||
|
||||
inlineApp.use(gettext);
|
||||
|
||||
/* Pinia */
|
||||
usePinia(inlineApp);
|
||||
|
||||
inlineApp.mount('#radio-player-controls');
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
import '~/vendor/luxon.js';
|
||||
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import OnDemand
|
||||
from '~/components/Public/OnDemand.vue';
|
||||
import OnDemand from '~/components/Public/OnDemand.vue';
|
||||
|
||||
export default initBase(OnDemand);
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import Requests
|
||||
from '~/components/Public/Requests.vue';
|
||||
import Requests from '~/components/Public/Requests.vue';
|
||||
|
||||
export default initBase(Requests);
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/luxon.js';
|
||||
|
||||
import Schedule
|
||||
from '~/components/Public/Schedule.vue';
|
||||
import Schedule from '~/components/Public/Schedule.vue';
|
||||
|
||||
export default initBase(Schedule);
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import initBase
|
||||
from '~/base.js';
|
||||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import WebDJ
|
||||
from '~/components/Public/WebDJ.vue';
|
||||
import WebDJ from '~/components/Public/WebDJ.vue';
|
||||
|
||||
export default initBase(WebDJ);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import Recover from '~/components/Recover.vue';
|
||||
|
||||
export default initBase(Recover);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import SetupRegister from '~/components/Setup/Register.vue';
|
||||
|
||||
export default initBase(SetupRegister);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import SetupSettings from '~/components/Setup/Settings.vue';
|
||||
|
||||
export default initBase(SetupSettings);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import SetupStation from '~/components/Setup/Station.vue';
|
||||
|
||||
export default initBase(SetupStation);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import BulkMedia from '~/components/Stations/BulkMedia.vue';
|
||||
|
||||
export default initBase(BulkMedia);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import Fallback from '~/components/Stations/Fallback.vue';
|
||||
|
||||
export default initBase(Fallback);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import Help from '~/components/Stations/Help.vue';
|
||||
|
||||
export default initBase(Help);
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import HlsStreams from '~/components/Stations/HlsStreams.vue';
|
||||
|
||||
export default initBase(HlsStreams);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import LiquidsoapConfig from '~/components/Stations/LiquidsoapConfig.vue';
|
||||
|
||||
export default initBase(LiquidsoapConfig);
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
import '~/vendor/luxon.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import '~/pages/InlinePlayer.js';
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import Mounts from '~/components/Stations/Mounts.vue';
|
||||
|
||||
export default initBase(Mounts);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/luxon.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
import '~/store';
|
||||
|
||||
import Playlists from '~/components/Stations/Playlists.vue';
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
import '~/vendor/luxon.js';
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/fancybox.js';
|
||||
import '~/vendor/luxon.js';
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
|
||||
import ProfileEdit from '~/components/Stations/ProfileEdit.vue';
|
||||
|
||||
export default initBase(ProfileEdit);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/luxon.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import Queue from '~/components/Stations/Queue.vue';
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import initBase from '~/base.js';
|
||||
|
||||
import '~/vendor/bootstrapVue.js';
|
||||
import '~/vendor/sweetalert.js';
|
||||
|
||||
import Remotes from '~/components/Stations/Remotes.vue';
|
||||
|
||||
export default initBase(Remotes);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue