mirror of
https://github.com/AzuraCast/AzuraCast.git
synced 2024-06-14 13:16:37 +00:00
Vue Composition API updates.
This commit is contained in:
parent
d2c7ff6a82
commit
5d1e46a620
|
@ -1,21 +1,18 @@
|
|||
<template>
|
||||
<audio
|
||||
v-if="isPlaying"
|
||||
ref="audio"
|
||||
ref="$audio"
|
||||
:title="title"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import getLogarithmicVolume from '~/functions/getLogarithmicVolume.js';
|
||||
import Hls from 'hls.js';
|
||||
import {usePlayerStore} from "~/store.js";
|
||||
import {defineComponent} from "vue";
|
||||
import {nextTick, onMounted, ref, toRef, watch} from "vue";
|
||||
|
||||
/* TODO Options API */
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null
|
||||
|
@ -28,156 +25,154 @@ export default defineComponent({
|
|||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
store: usePlayerStore()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
'audio': null,
|
||||
'hls': null,
|
||||
'duration': 0,
|
||||
'currentTime': 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isPlaying() {
|
||||
return this.store.isPlaying;
|
||||
},
|
||||
current() {
|
||||
return this.store.current;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
volume(volume) {
|
||||
if (this.audio !== null) {
|
||||
this.audio.volume = getLogarithmicVolume(volume);
|
||||
}
|
||||
},
|
||||
isMuted(muted) {
|
||||
if (this.audio !== null) {
|
||||
this.audio.muted = muted;
|
||||
}
|
||||
},
|
||||
current(newCurrent) {
|
||||
let url = newCurrent.url;
|
||||
if (url === null) {
|
||||
this.stop();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Allow pausing from the mobile metadata update.
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
this.stop();
|
||||
});
|
||||
|
||||
const $audio = ref(null);
|
||||
const hls = ref(null);
|
||||
const duration = ref(0);
|
||||
const currentTime = ref(0);
|
||||
|
||||
const store = usePlayerStore();
|
||||
const isPlaying = toRef(store, 'isPlaying');
|
||||
const current = toRef(store, 'current');
|
||||
|
||||
watch(toRef(props, 'volume'), (newVol) => {
|
||||
if ($audio.value !== null) {
|
||||
$audio.value.volume = getLogarithmicVolume(newVol);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
stop() {
|
||||
if (this.audio !== null) {
|
||||
this.audio.pause();
|
||||
this.audio.src = '';
|
||||
});
|
||||
|
||||
watch(toRef(props, 'isMuted'), (newMuted) => {
|
||||
if ($audio.value !== null) {
|
||||
$audio.value.muted = newMuted;
|
||||
}
|
||||
});
|
||||
|
||||
const stop = () => {
|
||||
if ($audio.value !== null) {
|
||||
$audio.value.pause();
|
||||
$audio.value.src = '';
|
||||
}
|
||||
|
||||
if (this.hls !== null) {
|
||||
this.hls.destroy();
|
||||
this.hls = null;
|
||||
if (hls.value !== null) {
|
||||
hls.value.destroy();
|
||||
hls.value = null;
|
||||
}
|
||||
|
||||
this.duration = 0;
|
||||
this.currentTime = 0;
|
||||
duration.value = 0;
|
||||
currentTime.value = 0;
|
||||
|
||||
this.store.stopPlaying();
|
||||
},
|
||||
play() {
|
||||
if (this.isPlaying) {
|
||||
this.stop();
|
||||
this.$nextTick(() => {
|
||||
this.play();
|
||||
store.stopPlaying();
|
||||
};
|
||||
|
||||
const play = () => {
|
||||
if (isPlaying.value) {
|
||||
stop();
|
||||
nextTick(() => {
|
||||
play();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.startPlaying();
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.audio = this.$refs.audio;
|
||||
store.startPlaying();
|
||||
|
||||
nextTick(() => {
|
||||
// Handle audio errors.
|
||||
this.audio.onerror = (e) => {
|
||||
if (e.target.error.code === e.target.error.MEDIA_ERR_NETWORK && this.audio.src !== '') {
|
||||
$audio.value.onerror = (e) => {
|
||||
if (e.target.error.code === e.target.error.MEDIA_ERR_NETWORK && $audio.value.src !== '') {
|
||||
console.log('Network interrupted stream. Automatically reconnecting shortly...');
|
||||
setTimeout(() => {
|
||||
this.play();
|
||||
play();
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
this.audio.onended = () => {
|
||||
this.stop();
|
||||
$audio.value.onended = () => {
|
||||
stop();
|
||||
};
|
||||
|
||||
this.audio.ontimeupdate = () => {
|
||||
this.duration = (this.audio.duration !== Infinity && !isNaN(this.audio.duration)) ? this.audio.duration : 0;
|
||||
this.currentTime = this.audio.currentTime;
|
||||
$audio.value.ontimeupdate = () => {
|
||||
duration.value = ($audio.value.duration !== Infinity && !isNaN($audio.value.duration)) ? $audio.value.duration : 0;
|
||||
currentTime.value = $audio.value.currentTime;
|
||||
};
|
||||
|
||||
this.audio.volume = getLogarithmicVolume(this.volume);
|
||||
this.audio.muted = this.isMuted;
|
||||
$audio.value.volume = getLogarithmicVolume(props.volume);
|
||||
$audio.value.muted = props.isMuted;
|
||||
|
||||
if (this.current.isHls) {
|
||||
if (current.value.isHls) {
|
||||
// HLS playback support
|
||||
if (Hls.isSupported()) {
|
||||
this.hls = new Hls();
|
||||
this.hls.loadSource(this.current.url);
|
||||
this.hls.attachMedia(this.audio);
|
||||
hls.value = new Hls();
|
||||
hls.value.loadSource(current.value.url);
|
||||
hls.value.attachMedia($audio.value);
|
||||
} else if (this.audio.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
this.audio.src = this.current.url;
|
||||
$audio.value.src = current.value.url;
|
||||
} else {
|
||||
console.log('Your browser does not support HLS.');
|
||||
}
|
||||
} else {
|
||||
// Standard streams
|
||||
this.audio.src = this.current.url;
|
||||
$audio.value.src = current.value.url;
|
||||
|
||||
// Firefox caches the downloaded stream, this causes playback issues.
|
||||
// Giving the browser a new url on each start bypasses the old cache/buffer
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
this.audio.src += "?refresh=" + Date.now();
|
||||
$audio.value.src += "?refresh=" + Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
this.audio.load();
|
||||
this.audio.play();
|
||||
$audio.value.load();
|
||||
$audio.value.play();
|
||||
});
|
||||
},
|
||||
toggle(url, isStream, isHls) {
|
||||
this.store.toggle({
|
||||
};
|
||||
|
||||
const toggle = (url, isStream, isHls) => {
|
||||
store.toggle({
|
||||
url: url,
|
||||
isStream: isStream,
|
||||
isHls: isHls,
|
||||
});
|
||||
},
|
||||
getCurrentTime() {
|
||||
return this.currentTime;
|
||||
},
|
||||
getDuration() {
|
||||
return this.duration;
|
||||
},
|
||||
getProgress() {
|
||||
return (this.duration !== 0) ? +((this.currentTime / this.duration) * 100).toFixed(2) : 0;
|
||||
},
|
||||
setProgress(progress) {
|
||||
if (this.audio !== null) {
|
||||
this.audio.currentTime = (progress / 100) * this.duration;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
watch(current, (newCurrent) => {
|
||||
if (newCurrent.url === null) {
|
||||
stop();
|
||||
} else {
|
||||
play();
|
||||
}
|
||||
});
|
||||
|
||||
const getCurrentTime = () => currentTime.value;
|
||||
const getDuration = () => duration.value;
|
||||
|
||||
const getProgress = () => {
|
||||
return (duration.value !== 0)
|
||||
? +((currentTime.value / duration.value) * 100).toFixed(2)
|
||||
: 0;
|
||||
};
|
||||
|
||||
const setProgress = (progress) => {
|
||||
if ($audio.value !== null) {
|
||||
$audio.value.currentTime = (progress / 100) * duration.value;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Allow pausing from the mobile metadata update.
|
||||
if ('mediaSession' in navigator) {
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
stop();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
play,
|
||||
stop,
|
||||
toggle,
|
||||
getCurrentTime,
|
||||
getDuration,
|
||||
getProgress,
|
||||
setProgress
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -35,22 +35,14 @@
|
|||
</date-range-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import DateRangePicker from 'vue3-daterange-picker';
|
||||
import Icon from "./Icon";
|
||||
import {DateTime} from 'luxon';
|
||||
import {computed} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
|
||||
/* TODO Options API */
|
||||
|
||||
export default {
|
||||
name: 'DateRangeDropdown',
|
||||
components: {DateRangePicker, Icon},
|
||||
inheritAttrs: false,
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'update:modelValue'
|
||||
},
|
||||
props: {
|
||||
const props = defineProps({
|
||||
tz: {
|
||||
type: String,
|
||||
default: 'system'
|
||||
|
@ -78,70 +70,81 @@ export default {
|
|||
customRanges: {
|
||||
type: [Object, Boolean],
|
||||
default: null,
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'update']);
|
||||
|
||||
const dateRange = computed({
|
||||
get: () => {
|
||||
return props.modelValue;
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'update'],
|
||||
computed: {
|
||||
dateRange: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set() {
|
||||
set: () => {
|
||||
// Noop
|
||||
}
|
||||
},
|
||||
ranges() {
|
||||
let ranges = {};
|
||||
});
|
||||
|
||||
if (null !== this.customRanges) {
|
||||
return this.customRanges;
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const ranges = computed(() => {
|
||||
if (null !== props.customRanges) {
|
||||
return props.customRanges;
|
||||
}
|
||||
|
||||
let nowTz = DateTime.now().setZone(this.tz);
|
||||
let nowTz = DateTime.now().setZone(props.tz);
|
||||
let nowAtMidnightDate = nowTz.endOf('day').toJSDate();
|
||||
|
||||
ranges[this.$gettext('Last 24 Hours')] = [
|
||||
let ranges = {};
|
||||
|
||||
ranges[$gettext('Last 24 Hours')] = [
|
||||
nowTz.minus({days: 1}).toJSDate(),
|
||||
nowTz.toJSDate()
|
||||
];
|
||||
ranges[this.$gettext('Today')] = [
|
||||
ranges[$gettext('Today')] = [
|
||||
nowTz.minus({days: 1}).startOf('day').toJSDate(),
|
||||
nowAtMidnightDate
|
||||
];
|
||||
ranges[this.$gettext('Yesterday')] = [
|
||||
ranges[$gettext('Yesterday')] = [
|
||||
nowTz.minus({days: 2}).startOf('day').toJSDate(),
|
||||
nowTz.minus({days: 1}).endOf('day').toJSDate()
|
||||
];
|
||||
ranges[this.$gettext('Last 7 Days')] = [
|
||||
ranges[$gettext('Last 7 Days')] = [
|
||||
nowTz.minus({days: 7}).startOf('day').toJSDate(),
|
||||
nowAtMidnightDate
|
||||
];
|
||||
ranges[this.$gettext('Last 14 Days')] = [
|
||||
ranges[$gettext('Last 14 Days')] = [
|
||||
nowTz.minus({days: 14}).startOf('day').toJSDate(),
|
||||
nowAtMidnightDate
|
||||
];
|
||||
ranges[this.$gettext('Last 30 Days')] = [
|
||||
ranges[$gettext('Last 30 Days')] = [
|
||||
nowTz.minus({days: 30}).startOf('day').toJSDate(),
|
||||
nowAtMidnightDate
|
||||
];
|
||||
ranges[this.$gettext('This Month')] = [
|
||||
ranges[$gettext('This Month')] = [
|
||||
nowTz.startOf('month').startOf('day').toJSDate(),
|
||||
nowTz.endOf('month').endOf('day').toJSDate()
|
||||
];
|
||||
ranges[this.$gettext('Last Month')] = [
|
||||
ranges[$gettext('Last Month')] = [
|
||||
nowTz.minus({months: 1}).startOf('month').startOf('day').toJSDate(),
|
||||
nowTz.minus({months: 1}).endOf('month').endOf('day').toJSDate()
|
||||
];
|
||||
|
||||
return ranges;
|
||||
}
|
||||
});
|
||||
|
||||
const onSelect = (range) => {
|
||||
emit('update:modelValue', range);
|
||||
emit('update', range);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'update:modelValue'
|
||||
},
|
||||
methods: {
|
||||
onSelect(range) {
|
||||
this.$emit('update:modelValue', range);
|
||||
this.$emit('update', range);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -13,41 +13,27 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* TODO Options API */
|
||||
<script setup>
|
||||
import {useAsyncState} from "@vueuse/core";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
|
||||
export default {
|
||||
name: 'LogList',
|
||||
props: {
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
emits: ['view'],
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
logs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.relist();
|
||||
},
|
||||
methods: {
|
||||
relist() {
|
||||
this.loading = true;
|
||||
this.$wrapWithLoading(
|
||||
this.axios.get(this.url)
|
||||
).then((resp) => {
|
||||
this.logs = resp.data.logs;
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
viewLog(url) {
|
||||
this.$emit('view', url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['view']);
|
||||
|
||||
const {axios} = useAxios();
|
||||
|
||||
const {state: logs} = useAsyncState(
|
||||
() => axios.get(props.url).then((r) => r.data.logs),
|
||||
[]
|
||||
);
|
||||
|
||||
const viewLog = (url) => {
|
||||
emit('view', url);
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<b-modal
|
||||
:id="id"
|
||||
ref="modal"
|
||||
ref="$modal"
|
||||
:size="size"
|
||||
:centered="centered"
|
||||
:title="title"
|
||||
|
@ -44,7 +44,7 @@
|
|||
<b-button
|
||||
variant="default"
|
||||
type="button"
|
||||
@click="close"
|
||||
@click="hide"
|
||||
>
|
||||
{{ $gettext('Close') }}
|
||||
</b-button>
|
||||
|
@ -61,7 +61,7 @@
|
|||
</template>
|
||||
|
||||
<template
|
||||
v-for="(_, slot) of filteredScopedSlots"
|
||||
v-for="(_, slot) of useSlotsExcept($slots, ['default', 'modal-footer'])"
|
||||
#[slot]="scope"
|
||||
>
|
||||
<slot
|
||||
|
@ -72,16 +72,12 @@
|
|||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import InvisibleSubmitButton from "~/components/Common/InvisibleSubmitButton.vue";
|
||||
import {defineComponent} from "vue";
|
||||
import {filter, includes} from "lodash";
|
||||
import {ref} from "vue";
|
||||
import useSlotsExcept from "~/functions/useSlotsExcept";
|
||||
|
||||
/* TODO Options API */
|
||||
|
||||
export default defineComponent({
|
||||
components: {InvisibleSubmitButton},
|
||||
props: {
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
|
@ -114,36 +110,33 @@ export default defineComponent({
|
|||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['submit', 'shown', 'hidden'],
|
||||
computed: {
|
||||
filteredScopedSlots() {
|
||||
return filter(this.$slots, (slot, name) => {
|
||||
return !includes([
|
||||
'default', 'modal-footer'
|
||||
], name);
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
doSubmit() {
|
||||
this.$emit('submit');
|
||||
},
|
||||
onShown() {
|
||||
this.$emit('shown');
|
||||
},
|
||||
onHidden() {
|
||||
this.$emit('hidden');
|
||||
},
|
||||
close() {
|
||||
this.hide();
|
||||
},
|
||||
hide() {
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
show() {
|
||||
this.$refs.modal.show();
|
||||
}
|
||||
}
|
||||
|
||||
const emit = defineEmits(['submit', 'shown', 'hidden']);
|
||||
|
||||
const doSubmit = () => {
|
||||
emit('submit');
|
||||
};
|
||||
|
||||
const onShown = () => {
|
||||
emit('shown');
|
||||
};
|
||||
|
||||
const onHidden = () => {
|
||||
emit('hidden');
|
||||
};
|
||||
|
||||
const $modal = ref(); // Template Ref
|
||||
|
||||
const hide = () => {
|
||||
$modal.value.hide();
|
||||
};
|
||||
|
||||
const show = () => {
|
||||
$modal.value.show();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
show
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -39,16 +39,16 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import DataTable from '~/components/Common/DataTable';
|
||||
import {forEach} from 'lodash';
|
||||
import AlbumArt from '~/components/Common/AlbumArt';
|
||||
import {computed} from "vue";
|
||||
import {useTranslate} from "~/vendor/gettext";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
|
||||
/* TODO Options API */
|
||||
|
||||
export default {
|
||||
components: {AlbumArt, DataTable},
|
||||
props: {
|
||||
const props = defineProps({
|
||||
requestListUri: {
|
||||
type: String,
|
||||
required: true
|
||||
|
@ -62,42 +62,52 @@ export default {
|
|||
required: false,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['submitted'],
|
||||
data () {
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submitted']);
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const fields = computed(() => {
|
||||
let fields = [
|
||||
{key: 'name', isRowHeader: true, label: this.$gettext('Name'), sortable: true, selectable: true},
|
||||
{
|
||||
key: 'name',
|
||||
isRowHeader: true,
|
||||
label: $gettext('Name'),
|
||||
sortable: true,
|
||||
selectable: true
|
||||
},
|
||||
{
|
||||
key: 'song.title',
|
||||
label: this.$gettext('Title'),
|
||||
label: $gettext('Title'),
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'song.artist',
|
||||
label: this.$gettext('Artist'),
|
||||
label: $gettext('Artist'),
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'song.album',
|
||||
label: this.$gettext('Album'),
|
||||
label: $gettext('Album'),
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false
|
||||
},
|
||||
{
|
||||
key: 'song.genre',
|
||||
label: this.$gettext('Genre'),
|
||||
label: $gettext('Genre'),
|
||||
sortable: true,
|
||||
selectable: true,
|
||||
visible: false
|
||||
}
|
||||
];
|
||||
|
||||
forEach(this.customFields.slice(), (field) => {
|
||||
forEach({...props.customFields}, (field) => {
|
||||
fields.push({
|
||||
key: 'song.custom_fields.' + field.short_name,
|
||||
label: field.name,
|
||||
|
@ -108,26 +118,25 @@ export default {
|
|||
});
|
||||
|
||||
fields.push(
|
||||
{key: 'actions', label: this.$gettext('Actions'), class: 'shrink', sortable: false}
|
||||
{key: 'actions', label: $gettext('Actions'), class: 'shrink', sortable: false}
|
||||
);
|
||||
|
||||
return {
|
||||
fields: fields,
|
||||
pageOptions: [
|
||||
10, 25
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
doSubmitRequest (url) {
|
||||
this.axios.post(url).then((resp) => {
|
||||
this.$notifySuccess(resp.data.message);
|
||||
this.$emit('submitted');
|
||||
}).catch(() => {
|
||||
this.$emit('submitted');
|
||||
return fields;
|
||||
});
|
||||
|
||||
const pageOptions = [10, 25];
|
||||
|
||||
const {wrapWithLoading, notifySuccess} = useNotify();
|
||||
const {axios} = useAxios();
|
||||
|
||||
const doSubmitRequest = (url) => {
|
||||
wrapWithLoading(
|
||||
axios.post(url)
|
||||
).then((resp) => {
|
||||
notifySuccess(resp.data.message);
|
||||
}).finally(() => {
|
||||
emit('submitted');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user