feat(FE): webstreams and menu

This commit is contained in:
Michael 2025-07-02 13:13:43 +02:00
parent c6b07fa803
commit 438220a664
8 changed files with 481 additions and 0 deletions

View file

@ -0,0 +1,150 @@
<script setup lang="ts">
import {onBeforeMount, reactive, ref, watch} from "vue";
import Table from "@partials/Table.vue";
import ConfirmDelete from "@partials/dialogs/ConfirmDelete.vue";
import {webstream_page} from "@/composables/content/webstream_page.ts";
import {baseWebstream, type Webstream} from "@models/webstream.ts";
import WebstreamForm from "@partials/WebstreamForm.vue";
const {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem} = webstream_page()
const props = defineProps({
hideColumns: {
type: Array,
required: false
},
isDraggable: {
type: Boolean,
required: false
}
});
const webstreamCreateEditMode = ref(false);
let webstreamSelected = ref<Number | null>(null);
const visibleHeaders = ref(headers)
const bulk = ref({
state: false,
items: [] as Webstream[],
})
const dialog = reactive({
open: false,
type: '',
title: '',
text: ''
})
const openDialog = (type, title: string = '', text: string = '') => {
dialog.open = true
dialog.type = type
dialog.title = title
dialog.text = text
}
const edit = (webstreamSelectedFromUser) => {
webstreamSelected.value = webstreamSelectedFromUser.id
webstreamCreateEditMode.value = true
}
const create = () => {
webstreamSelected.value = null
webstreamCreateEditMode.value = true
}
const saveItem = (item) => {
const saved = editItem(item)
closeDialog()
}
const cancel = (item) => {
let deleteMessage = 'Vuoi cancellare il webstream selezionato?'
if(bulk.value.state) deleteMessage = 'Vuoi cancellare i webstream selezionati?'
bulk.value.items = item
webstreamSelected.value = item?.id
openDialog(
'delete',
'Cancella',
deleteMessage
)
}
const confirmDelete = (confirm) => {
if (confirm) {
const webstreamId = webstreamSelected.value == 0 ? null : webstreamSelected.value ;
deleteItem(webstreamId)
}
closeDialog()
}
const closeDialog = () => {
dialog.open = false
resetItemEdited()
}
const updateSearch = (text) => {
search.value = text
}
onBeforeMount(() => {
if(props.hideColumns != undefined) {
visibleHeaders.value = headers.filter(el => {
return !props.hideColumns.includes(el.value)
});
}
})
const resetItemEdited = () => {
webstreamSelected.value = null
}
watch(search, (newValue, oldValue) => {
const options = {...listData};
getItems(options)
})
const goBack = () => {
webstreamCreateEditMode.value = false
webstreamSelected.value = null
}
</script>
<template>
<WebstreamForm v-if="webstreamCreateEditMode" :webstreamId="webstreamSelected" @go-back="goBack"/>
<Table
v-else
:headers="visibleHeaders"
v-model:selected="selected"
v-model:search="search"
:list-data="listData"
:items="items"
:loading="loading"
:get-items="getItems"
:actions="true"
:show-select="true"
:is-draggable="isDraggable"
@update-table="getItems"
@update-search="updateSearch"
@delete-item="cancel"
@edit-item="edit"
>
<template v-slot:header-buttons>
<v-btn color="primary" @click="create">
Crea un nuovo webstream
</v-btn>
</template>
<template v-slot:dialog>
<v-dialog v-model="dialog.open">
<ConfirmDelete
v-if="dialog.type === 'delete'"
:title="dialog.title"
:text="dialog.text"
:bulk="bulk.state"
@confirm="confirmDelete"
@after-leave="closeDialog"
/>
</v-dialog>
</template>
</Table>
</template>

View file

@ -2,6 +2,7 @@
import {ref} from "vue";
import Archive from "@/components/content/Archive.vue";
import Blocks from "@components/content/SmartBlock.vue";
import Webstream from "@components/content/Webstream.vue";
const tab = ref(null)
const tabs = [
@ -13,6 +14,10 @@ const tabs = [
id: 'blocks',
title: 'Blocchi dinamici',
},
{
id: 'webstream',
title: 'Webstream',
},
]
</script>
@ -42,6 +47,11 @@ const tabs = [
:is-draggable="true"
:hideColumns="['mtime', 'utime', 'actions']"
/>
<Webstream
v-if="tab.id === 'webstream'"
:show-select="false"
:is-draggable="true"
/>
</v-tabs-window-item>
</v-tabs-window>
</template>

View file

@ -65,6 +65,22 @@ const rehydratateTracks = () => {
track_info.block = track
track_info.id = track.id
}
if (Object.hasOwn(track, 'webstream') && track.webstream !== null) {
track_info.type = 'webstream'
track_info.title = track.webstream.name
track_info.subtitle = track.webstream.description
track_info.db_element = track.webstream
track_info.webstream = track.webstream
track_info.id = track.stream_id
}
if (!Object.hasOwn(track, 'webstream') && track.type === 'webstream') {
track_info.type = 'webstream'
track_info.title = track.name
track_info.subtitle = track.description
track_info.db_element = track
track_info.webstream = track
track_info.id = track.id
}
return track_info
})
}
@ -102,6 +118,15 @@ const change = (event) => {
trackList.value[event.added.newIndex].title = trackList.value[event.added.newIndex].name;
trackList.value[event.added.newIndex].subtitle = trackList.value[event.added.newIndex].creator.login;
}
// webstream
if (
Object.hasOwn(trackList.value[event.added.newIndex], 'url') &&
!Object.hasOwn(trackList.value[event.added.newIndex], 'track_title')
) {
trackList.value[event.added.newIndex].type = 'webstream';
trackList.value[event.added.newIndex].title = trackList.value[event.added.newIndex].name;
trackList.value[event.added.newIndex].subtitle = trackList.value[event.added.newIndex].description;
}
}
emit('updateTracks', trackList.value)
}

View file

@ -0,0 +1,141 @@
<script setup lang="ts">
import {ref, onMounted, type PropType, reactive} from "vue";
import {baseWebstream, createWebstream, getWebstream, updateWebstream, type Webstream} from "@models/webstream.ts";
// Props and emits
const props = defineProps({
webstreamId: {
type: Number as PropType<number | null>,
required: true,
},
});
const emits = defineEmits(['go-back']);
// Data
const loading = ref(false);
const isFormValid = ref(false);
const currentWebstream = ref<Webstream>(baseWebstream());
// Funcs
onMounted(async () => {
loading.value = true;
if (props.webstreamId === null) {
currentWebstream.value = baseWebstream();
} else {
currentWebstream.value = await getWebstream(props.webstreamId);
}
loading.value = false;
});
const goBack = () => {
emits('go-back');
};
const saveWebstream = async () => {
loading.value = true;
try {
if (currentWebstream.value.id) {
await updateWebstream(currentWebstream.value);
goBack();
return
}
await createWebstream(currentWebstream.value);
goBack()
return
} catch (error) {
console.error("Error saving webstream:", error);
} finally {
loading.value = false;
}
};
</script>
<template>
<v-card
:disabled="loading"
:loading="loading"
>
<template v-slot:loader="{ isActive }">
<v-progress-linear
:active="isActive"
color="deep-purple"
height="4"
indeterminate
></v-progress-linear>
</template>
<v-card-title>
<h3>Webstream</h3>
</v-card-title>
<v-form ref="form" v-model="isFormValid">
<v-card-text>
<v-row no-gutters>
<!-- Name Field -->
<v-col cols="12" md="6">
<v-card>
<v-text-field
v-model="currentWebstream.name"
label="Nome"
density="compact"
:rules="[v => !!v || 'Nome è obbligatorio']"
required="true"
/>
</v-card>
</v-col>
<!-- URL Field -->
<v-col cols="12" md="6">7
<v-card>
<v-text-field
v-model="currentWebstream.url"
label="URL"
density="compact"
:rules="[v => !!v || 'URL è obbligatorio', v => /^http?:\/\//.test(v) || 'URL deve iniziare con http://']"
required="true"
/>
</v-card>
</v-col>
<!-- Description Field -->
<v-col cols="12">
<v-card>
<v-textarea
v-model="currentWebstream.description"
label="Descrizione"
density="compact"
rows="2"
:rules="[v => !!v || 'Descrizione è obbligatoria']"
required="true"
/>
</v-card>
</v-col>
<!-- Length Field -->
<v-col cols="12">
<v-card>
<v-textarea
v-model="currentWebstream.length"
label="Durata"
density="compact"
rows="2"
:rules="[v => !!v || 'Durata è obbligatoria']"
required="true"
/>
</v-card>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-btn color="accent" @click="goBack">Torna indietro</v-btn>
<v-btn color="primary" @click="saveWebstream" :disabled="!isFormValid">
{{ currentWebstream.id ? 'Aggiorna' : 'Crea' }}
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</template>
<style scoped></style>

View file

@ -0,0 +1,96 @@
import axios, {type AxiosResponse} from "axios";
import {cleanOptions} from "@/helpers/AxiosHelper.ts";
export interface Webstream {
id?: number;
name: string;
description: string;
url: string;
length?: string;
creator_id?: number;
mtime?: Date;
utime?: Date;
lptime?: Date;
mime?: string;
}
export const baseWebstream = (): Webstream => {
return {
id: null,
name: 'test',
description: '',
url: '',
length: '00:00:00',
creator_id: null,
mtime: null,
utime: null,
lptime: null,
mime: null
}
}
export const webstreamTableHeader = [
{title: 'Nome', value: 'name'},
{title: 'Descrizione', value: 'description'},
{title: 'URL', value: 'url'},
{title: 'Durata', value: 'length'},
{title: 'Azioni', value: 'actions'}
]
export const getWebstreams = async (options: {
name?: string | null;
description?: string | null;
url?: string | null;
mime?: string | null;
page?: Number | null;
per_page?: Number | null;
}): Promise<Webstream[]> => {
const filteredParams = cleanOptions(options);
return await axios.get(`/webstream`, {params: filteredParams})
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
})
}
export const getWebstream = async (id: number): Promise<Webstream> => {
return await axios.get(`/webstream/${id}`)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
throw error;
})
}
export const createWebstream = async (webstream: Webstream): Promise<Webstream> => {
return await axios.post('/webstream', webstream)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
throw error;
})
}
export const updateWebstream = async (webstream: Webstream): Promise<Webstream> => {
return await axios.put(`/webstream/${webstream.id}`, webstream)
.then((response: AxiosResponse) => {
return response.data
}).catch((error: Error) => {
console.log("Error: " + error);
throw error;
})
}
export const deleteWebstream = async (webstreamIds: Number[]) => {
try {
for (const webstreamId of webstreamIds) {
await axios.delete(`/webstream/${webstreamId}`)
}
} catch (error) {
console.error('Error deleting webstream:', error);
throw error;
}
}

View file

@ -0,0 +1,52 @@
import {reactive, ref} from "vue";
import {deleteWebstream, getWebstreams, updateWebstream, type Webstream, webstreamTableHeader} from "@models/webstream.ts";
export function webstream_page() {
const items = ref([])
const selected = ref([])
const loading = ref(false)
const search = ref('')
const listData = reactive({
'itemsPerPage': 5,
'first_page': null,
'last_page': null,
'total_items': 0,
'page': 1,
})
const headers = webstreamTableHeader
const getItems = async (options) => {
loading.value = true;
return getWebstreams(options).then(webstreamList => {
items.value = webstreamList.data
loading.value = false;
}).catch(error => {
console.log("Error: " + error);
loading.value = false;
})
};
const editItem = (item: Webstream) => {
return updateWebstream(item)
.then((response) => {
console.log(response);
return response;
})
.catch(error => {
console.error("Error updating webstream:", error);
throw error;
});
}
const deleteItem = async (itemId: number | null = null) => {
const webstreamId = itemId ? [itemId] : selected.value.map(item => item.id)
await deleteWebstream(webstreamId).then(async () => {
await getItems(listData)
}).catch(error => {
console.error("Error deleting webstream:", error);
})
};
return {items, listData, headers, selected, loading, search, getItems, editItem, deleteItem}
}

View file

@ -16,6 +16,7 @@ const tabs = {
archive: defineAsyncComponent(() => import('@components/content/Archive.vue')),
playlist: defineAsyncComponent(() => import('@components/content/Playlist.vue')),
blocks: defineAsyncComponent(() => import('@components/content/SmartBlock.vue')),
webstream: defineAsyncComponent(() => import('@components/content/Webstream.vue')),
podcast: defineAsyncComponent(() => import('@components/content/Podcast.vue')),
}
</script>

View file

@ -30,6 +30,12 @@ const pages = [
id: 'podcast',
name: 'Podcast',
component: '@components/content/Podcast.vue',
component: '@components/content/Blocks.vue',
},
{
id: 'webstream',
name: 'Webstream',
component: '@components/content/Webstream.vue',
}
];
</script>