From 7b746e3b6fb9105476f17b3b311b3c63bf7b577d Mon Sep 17 00:00:00 2001 From: Surya Paolo Date: Fri, 19 Dec 2025 22:59:13 +0100 Subject: [PATCH] - Caricamento Video --- quasar.config.ts | 2 + .../src/db/lang/ws_it.js | 2 + src/components/video/VideoGallery.scss | 150 +++++++++ src/components/video/VideoGallery.ts | 291 ++++++++++++++++++ src/components/video/VideoGallery.vue | 271 ++++++++++++++++ src/components/video/VideoUploader.scss | 73 +++++ src/components/video/VideoUploader.ts | 199 ++++++++++++ src/components/video/VideoUploader.vue | 181 +++++++++++ src/db/lang/ws_it.js | 1 + src/model/UserStore.ts | 1 + src/pages/VideosPage.scss | 16 + src/pages/VideosPage.ts | 25 ++ src/pages/VideosPage.vue | 11 + src/rootgen/admin/userPanel/userPanel.vue | 9 + src/router/routesAdmin.ts | 13 + src/services/videoService.js | 167 ++++++++++ src/store/Api/Inst-Pao.ts | 33 +- src/store/Api/Instance.ts | 23 +- src/store/Api/index.ts | 12 +- src/types/video.types.ts | 100 ++++++ tsconfig.json | 7 +- 21 files changed, 1562 insertions(+), 25 deletions(-) create mode 100644 src/components/video/VideoGallery.scss create mode 100644 src/components/video/VideoGallery.ts create mode 100644 src/components/video/VideoGallery.vue create mode 100644 src/components/video/VideoUploader.scss create mode 100644 src/components/video/VideoUploader.ts create mode 100644 src/components/video/VideoUploader.vue create mode 100644 src/pages/VideosPage.scss create mode 100644 src/pages/VideosPage.ts create mode 100644 src/pages/VideosPage.vue create mode 100644 src/services/videoService.js create mode 100644 src/types/video.types.ts diff --git a/quasar.config.ts b/quasar.config.ts index a21cc419..c570aa4f 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -74,6 +74,8 @@ export default defineConfig((ctx) => { '@paths': path.resolve(__dirname, 'src/store/Api/ApiRoutes.ts'), '@images': path.resolve(__dirname, 'src/assets/images'), '@icons': path.resolve(__dirname, 'src/public/myicons'), + '@types': path.resolve(__dirname, 'src/types'), + '@services': path.resolve(__dirname, 'src/services'), }, }; diff --git a/scripts/_ALL_SITES/comunitanuovomondo.app/src/db/lang/ws_it.js b/scripts/_ALL_SITES/comunitanuovomondo.app/src/db/lang/ws_it.js index a150a93a..c524ce8c 100755 --- a/scripts/_ALL_SITES/comunitanuovomondo.app/src/db/lang/ws_it.js +++ b/scripts/_ALL_SITES/comunitanuovomondo.app/src/db/lang/ws_it.js @@ -25,6 +25,7 @@ const msg_website_it = { Ammetti: 'Ammetti', AbilitaCircuito: 'Abilita Circuito', installaApp: 'Installa App', + VideoPage: 'Video', fundraising: 'Sostieni il Progetto', notifs: 'Configura le Notifiche', unsubscribe: 'Disiscriviti', @@ -88,6 +89,7 @@ const msg_website_it = { eventodef: 'Evento:', prova: 'prova', dbop: 'Operazioni', + VideoPage: 'Video', dbopmacro: 'Operazioni Macro', projall: 'Comunitari', groups: 'Lista Gruppi', diff --git a/src/components/video/VideoGallery.scss b/src/components/video/VideoGallery.scss new file mode 100644 index 00000000..a0c3ed69 --- /dev/null +++ b/src/components/video/VideoGallery.scss @@ -0,0 +1,150 @@ +.video-gallery { + max-width: 1400px; + margin: 0 auto; + + .breadcrumb-section { + background: rgba(0, 0, 0, 0.02); + } + + .section-title { + font-weight: 500; + } + + .cursor-pointer { + cursor: pointer; + } + + // Folder Cards + .folder-card { + transition: all 0.3s ease; + + &:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + } + + // Video Cards + .video-card { + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + } + + .video-container { + position: relative; + padding-top: 56.25%; // 16:9 + background: #000; + overflow: hidden; + cursor: pointer; + } + + .video-preview { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + .play-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.3); + transition: background 0.3s ease; + + &:hover { + background: rgba(0, 0, 0, 0.5); + } + } + + // List View + .video-list { + .video-list-item { + transition: background 0.2s ease; + + &:hover { + background: rgba(0, 0, 0, 0.02); + } + } + } + + .video-thumbnail { + position: relative; + overflow: hidden; + border-radius: 4px; + + video { + width: 100%; + height: 100%; + object-fit: cover; + } + + .thumbnail-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.3); + } + } + + // Empty State + .empty-state { + text-align: center; + padding: 60px 20px; + } + + // Dialogs + .video-player-dialog { + background: #000; + + .video-player-container { + height: calc(100vh - 50px); + display: flex; + align-items: center; + justify-content: center; + padding: 0; + } + + .video-player { + max-width: 100%; + max-height: 100%; + } + } + + .dialog-move { + min-width: 350px; + + @media (max-width: 400px) { + min-width: 90vw; + } + } +} + +// Dark Mode +.body--dark { + .video-gallery { + .breadcrumb-section { + background: rgba(255, 255, 255, 0.05); + } + + .video-list-item:hover { + background: rgba(255, 255, 255, 0.05); + } + } +} \ No newline at end of file diff --git a/src/components/video/VideoGallery.ts b/src/components/video/VideoGallery.ts new file mode 100644 index 00000000..9e77f489 --- /dev/null +++ b/src/components/video/VideoGallery.ts @@ -0,0 +1,291 @@ +import { defineComponent, ref, computed, onMounted, watch } from 'vue'; +import { useQuasar } from 'quasar'; +import { videoService } from '@/services/videoService'; +import type { IVideo, IFolder, IFolderOption } from '@/types/video.types'; + +interface IBreadcrumb { + name: string; + path: string; +} + +export default defineComponent({ + name: 'VideoGallery', + + props: { + refreshTrigger: { + type: Number, + default: 0 + } + }, + + setup(props) { + const $q = useQuasar(); + + // State + const loading = ref(false); + const currentPath = ref(''); + const videos = ref([]); + const subfolders = ref([]); + const allFolders = ref([]); + const viewMode = ref<'grid' | 'list'>('grid'); + + // Video player + const showVideoDialog = ref(false); + const currentVideo = ref(null); + const videoPlayer = ref(null); + + // Move dialog + const showMoveDialog = ref(false); + const moveDestination = ref(''); + const videoToMove = ref(null); + const moving = ref(false); + + // Computed + const breadcrumbs = computed(() => { + if (!currentPath.value) return []; + + const parts = currentPath.value.split('/'); + return parts.map((part, index) => ({ + name: part, + path: parts.slice(0, index + 1).join('/') + })); + }); + + // Methods + const loadContent = async (): Promise => { + loading.value = true; + try { + const response = await videoService.getVideos(currentPath.value); + videos.value = response.data?.videos || []; + subfolders.value = response.data?.folders || []; + } catch (error) { + $q.notify({ + type: 'negative', + message: 'Errore nel caricamento dei contenuti' + }); + } finally { + loading.value = false; + } + }; + + const loadAllFolders = async (): Promise => { + try { + const response = await videoService.getFolders(); + const folders = response.data?.folders || []; + allFolders.value = [ + { label: 'Root', value: '' }, + ...folders.map(f => ({ + label: f.path, + value: f.path + })) + ]; + } catch (error) { + console.error('Error loading folders:', error); + } + }; + + const navigateTo = (path: string): void => { + currentPath.value = path; + loadContent(); + }; + + const toggleViewMode = (): void => { + viewMode.value = viewMode.value === 'grid' ? 'list' : 'grid'; + }; + + const openVideo = (video: IVideo): void => { + currentVideo.value = video; + showVideoDialog.value = true; + }; + + const getVideoUrl = (path: string): string => { + return videoService.getVideoUrl(path); + }; + + const downloadVideo = (video: IVideo): void => { + const link = document.createElement('a'); + link.href = getVideoUrl(video.path); + link.download = video.filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + const renameVideo = (video: IVideo): void => { + $q.dialog({ + title: 'Rinomina Video', + message: 'Inserisci il nuovo nome del file:', + prompt: { + model: video.filename, + type: 'text' + }, + cancel: true, + persistent: true + }).onOk(async (newName: string) => { + if (!newName.trim() || newName === video.filename) return; + + try { + await videoService.renameVideo(video.folder || currentPath.value, video.filename, newName); + $q.notify({ type: 'positive', message: 'Video rinominato!' }); + loadContent(); + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.response?.data?.error || 'Errore durante la rinomina' + }); + } + }); + }; + + const moveVideo = async (video: IVideo): Promise => { + await loadAllFolders(); + videoToMove.value = video; + moveDestination.value = ''; + showMoveDialog.value = true; + }; + + const confirmMoveVideo = async (): Promise => { + if (!videoToMove.value) return; + + moving.value = true; + try { + await videoService.moveVideo( + videoToMove.value.folder || currentPath.value, + videoToMove.value.filename, + moveDestination.value + ); + $q.notify({ type: 'positive', message: 'Video spostato!' }); + showMoveDialog.value = false; + loadContent(); + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.response?.data?.error || 'Errore durante lo spostamento' + }); + } finally { + moving.value = false; + } + }; + + const confirmDeleteVideo = (video: IVideo): void => { + $q.dialog({ + title: 'Conferma Eliminazione', + message: `Sei sicuro di voler eliminare "${video.filename}"?`, + cancel: true, + persistent: true, + color: 'negative' + }).onOk(async () => { + try { + await videoService.deleteVideo(video.folder || currentPath.value, video.filename); + $q.notify({ type: 'positive', message: 'Video eliminato!' }); + loadContent(); + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.response?.data?.error || 'Errore durante l\'eliminazione' + }); + } + }); + }; + + const renameFolder = (folder: IFolder): void => { + $q.dialog({ + title: 'Rinomina Cartella', + message: 'Inserisci il nuovo nome:', + prompt: { + model: folder.name, + type: 'text' + }, + cancel: true, + persistent: true + }).onOk(async (newName: string) => { + if (!newName.trim() || newName === folder.name) return; + + try { + await videoService.renameFolder(folder.path, newName); + $q.notify({ type: 'positive', message: 'Cartella rinominata!' }); + loadContent(); + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.response?.data?.error || 'Errore durante la rinomina' + }); + } + }); + }; + + const confirmDeleteFolder = (folder: IFolder): void => { + $q.dialog({ + title: 'Conferma Eliminazione', + message: `Sei sicuro di voler eliminare la cartella "${folder.name}" e tutto il suo contenuto?`, + cancel: true, + persistent: true, + color: 'negative' + }).onOk(async () => { + try { + await videoService.deleteFolder(folder.path); + $q.notify({ type: 'positive', message: 'Cartella eliminata!' }); + loadContent(); + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.response?.data?.error || 'Errore durante l\'eliminazione' + }); + } + }); + }; + + const formatFileSize = (bytes: number): string => { + return videoService.formatFileSize(bytes); + }; + + const formatDate = (dateString: string): string => { + return videoService.formatDate(dateString); + }; + + // Watchers + watch(() => props.refreshTrigger, () => { + loadContent(); + }); + + // Lifecycle + onMounted(() => { + loadContent(); + }); + + return { + // State + loading, + currentPath, + videos, + subfolders, + allFolders, + viewMode, + showVideoDialog, + currentVideo, + videoPlayer, + showMoveDialog, + moveDestination, + moving, + + // Computed + breadcrumbs, + + // Methods + loadContent, + navigateTo, + toggleViewMode, + openVideo, + getVideoUrl, + downloadVideo, + renameVideo, + moveVideo, + confirmMoveVideo, + confirmDeleteVideo, + renameFolder, + confirmDeleteFolder, + formatFileSize, + formatDate + }; + } +}); diff --git a/src/components/video/VideoGallery.vue b/src/components/video/VideoGallery.vue new file mode 100644 index 00000000..556dc5b3 --- /dev/null +++ b/src/components/video/VideoGallery.vue @@ -0,0 +1,271 @@ + + +