diff --git a/src/common/shared_vuejs.ts b/src/common/shared_vuejs.ts index a44937f5..478ca853 100755 --- a/src/common/shared_vuejs.ts +++ b/src/common/shared_vuejs.ts @@ -125,6 +125,7 @@ export const shared_consts = { IMAGEUPLOAD: 35, SEPARATOR: 40, VIDEO: 50, + VIDEO_YOUTUBE: 52, PAGE: 55, PAGEINTRO: 58, CALENDAR: 70, @@ -1816,66 +1817,82 @@ export const shared_consts = { { value: 5, label: 'Titolo', + icon: 'fas fa-heading', }, { value: 8, label: 'ImgTitolo', + icon: 'fas fa-image', }, { value: 10, label: 'Testo semplice', + icon: 'fas fa-file-alt', }, { value: 30, label: 'Immagine (nomefile)', + icon: 'fas fa-image', }, { value: 130, label: 'MainView', + icon: 'fas fa-eye', }, { value: 140, label: 'Dashboard', + icon: 'fas fa-tachometer-alt', }, { value: 145, label: 'DashGroup', + icon: 'fas fa-users', }, { value: 148, label: 'Lista Movimenti', + icon: 'fas fa-list', }, { value: 150, label: 'SendCoinTo', + icon: 'fas fa-wallet', }, { value: 280, label: 'Tutorial', + icon: 'fas fa-book', }, { value: 400, label: 'Visualizzatore Tabelle', + icon: 'fas fa-table', }, { value: 410, label: 'Qr Code', + icon: 'fas fa-qrcode', }, { value: 420, label: 'Lista Cataloghi', + icon: 'fas fa-list', }, { value: 450, label: 'Raccolte Cataloghi', + icon: 'fas fa-list', }, { value: 460, label: 'Statistiche Pagine', + icon: 'fas fa-chart-pie', }, { value: 430, // SEARCHPRODUCT label: 'Cerca Prodotto', + icon: 'fas fa-search', }, ], @@ -1883,130 +1900,162 @@ export const shared_consts = { { value: 100, label: 'Check Email', + icon: 'fas fa-envelope', }, { value: 120, label: 'OpenStreetMap', + icon: 'fas fa-globe', }, { value: 160, label: 'Stato Registrati', + icon: 'fas fa-user', }, { value: 170, label: 'CheckIfIsLogged', + icon: 'fas fa-user-lock', }, { value: 180, label: 'Info Versione', + icon: 'fas fa-info-circle', }, { value: 190, label: 'Bottone Condividi', + icon: 'fas fa-share-alt', }, { value: 192, label: 'Bottone Chat Territoriale', + icon: 'fas fa-comments', }, { value: 200, label: 'Presentazione', + icon: 'fas fa-presentation', }, { value: 205, label: 'Attività', + icon: 'fas fa-briefcase', }, { value: 210, label: 'Notifiche in Top', + icon: 'fas fa-bell', }, { value: 135, label: 'Check App Running', + icon: 'fas fa-spinner', }, { value: 258, label: 'Registration', + icon: 'fas fa-user-plus', }, { value: 220, label: 'CHART', + icon: 'fas fa-chart-pie', }, { value: 230, label: 'Check New Version', + icon: 'fas fa-sync-alt', }, { value: 240, label: 'Check Test Version', + icon: 'fas fa-flask', }, { value: 250, label: 'Butt Registrati', + icon: 'fas fa-user-plus', }, { value: 255, label: 'Butt Registrati col Bot', + icon: 'fas fa-user-plus', }, { value: 260, label: 'Butt Login', + icon: 'fas fa-sign-in-alt', }, { value: 270, label: 'Footer', + icon: 'fas fa-copyright', }, { value: 280, label: 'Visu Promo and PDF', + icon: 'fas fa-file-pdf', }, { value: 40, label: 'Separatore', + icon: 'fas fa-minus', }, { value: 70, label: 'Calendario', + icon: 'fas fa-calendar', }, { value: 300, label: 'E-COMMERCE', + icon: 'fas fa-shopping-cart', }, { value: 310, label: 'CATALOGO', + icon: 'fas fa-list', }, { value: 315, label: 'RACCOLTA CATALOGHI', + icon: 'fas fa-list', }, { value: 320, label: 'TOOLS AI', + icon: 'fas fa-robot', }, { value: 325, label: 'CHATBOT', + icon: 'fas fa-robot', }, { value: 350, label: 'MAPPA', + icon: 'fas fa-globe', }, { value: 360, label: 'MAPPAUTENTI', + icon: 'fas fa-globe', }, { value: 370, label: 'MAPPACOMUNI', + icon: 'fas fa-globe', }, { value: 380, label: 'MAPPA GET COORD', + icon: 'fas fa-globe', }, { value: 390, label: 'EDIT ADDRESS BY COORD', + icon: 'fas fa-globe', }, ], @@ -2024,11 +2073,12 @@ export const shared_consts = { { value: 35, label: 'Immagine', - icon: '', + icon: 'fas fa-image', }, { value: 7, label: 'Scheda (IMG + Testo)', + icon: 'fas fa-id-card', }, /* Disattivato perchè attualmente non funziona bene @@ -2039,26 +2089,37 @@ export const shared_consts = { { value: 195, label: 'Bottone', + icon: 'fas fa-hand-point-right', }, { value: 50, label: 'Video', + icon: 'fas fa-video', + }, + { + value: 52, + label: 'Video Youtube', + icon: 'fab fa-youtube', }, { value: 55, label: 'Pagina', + icon: 'fas fa-file-alt', }, { value: 58, label: 'Pagina (solo Intro)', + icon: 'fas fa-file-alt', }, { value: 110, label: "Galleria d'Immagini", + icon: 'fas fa-images', }, { value: 6, label: 'Margine', + icon: 'fas fa-arrows-alt-h', }, ], diff --git a/src/components/CMyEditElem/CMyEditElem.vue b/src/components/CMyEditElem/CMyEditElem.vue index 2755f370..ea049298 100755 --- a/src/components/CMyEditElem/CMyEditElem.vue +++ b/src/components/CMyEditElem/CMyEditElem.vue @@ -1097,6 +1097,53 @@ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+ +
, @@ -76,6 +73,16 @@ export default defineComponent({ const catalogStore = useCatalogStore(); const router = useRouter(); + const sections = computed(() => [ + { label: 'Principali', icon: 'fas fa-eye', items: shared_consts.TypesElem }, + { label: 'Gestione', icon: 'fas fa-cog', items: shared_consts.TypesElemAdmin }, + { + label: 'Avanzati', + icon: 'fas fa-star', + items: shared_consts.TypesElemAdminTools, + }, + ]); + const { setmeta, getsrcbyimg } = MixinMetaTags(); const { setValDb, getValDb } = MixinBase(); @@ -107,7 +114,6 @@ export default defineComponent({ const selectedClasses = ref([]); - async function addNewElem(elemsel: any, direz: number) { // Nascondi la visualizzazione di aggiunta (presumo sia una variabile reattiva) visuadd.value = false; @@ -148,7 +154,7 @@ export default defineComponent({ let sectionId = ''; let rowId = ''; - console.log('sectionId', sectionId, 'rowId', rowId) + console.log('sectionId', sectionId, 'rowId', rowId); // Aggiungi un nuovo elemento alla sezione o riga usando il metodo preparato const newrec = await globalStore.prepareAddNewElem( @@ -157,7 +163,7 @@ export default defineComponent({ t, myelem, props.myElemParent, - newtype.value, + newtype.value ); // Emitti l'evento per la selezione del nuovo elemento @@ -167,7 +173,6 @@ export default defineComponent({ // emit('updateAll', newrec); } - return { tools, shared_consts, @@ -191,6 +196,7 @@ export default defineComponent({ Products, globalStore, myel, + sections, }; }, }); diff --git a/src/components/CMyElemAdd/CMyElemAdd.vue b/src/components/CMyElemAdd/CMyElemAdd.vue index 5b1a9a96..b784224b 100755 --- a/src/components/CMyElemAdd/CMyElemAdd.vue +++ b/src/components/CMyElemAdd/CMyElemAdd.vue @@ -1,107 +1,46 @@ diff --git a/src/components/CMyVideoYoutube/CMyVideoYoutube.scss b/src/components/CMyVideoYoutube/CMyVideoYoutube.scss new file mode 100755 index 00000000..06ca867e --- /dev/null +++ b/src/components/CMyVideoYoutube/CMyVideoYoutube.scss @@ -0,0 +1,72 @@ +.cmy-yt { + width: 100%; +} + +.cmy-yt__error { + display: flex; + align-items: center; + padding: 8px 12px; + border-radius: 8px; + background: rgba(255, 171, 0, 0.12); + color: #7a4f01; +} + +.cmy-yt__frame-wrapper { + border-radius: 12px; + overflow: hidden; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.10); +} + +.cmy-yt__iframe { + width: 100%; + height: 100%; + display: block; +} + +.cmy-yt__thumb-wrapper { + position: relative; +} + +.cmy-yt__thumb-img { + border-radius: 12px; + overflow: hidden; + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.10); +} + +.cmy-yt__overlay { + position: absolute; + inset: 0; + display: grid; + place-items: center; + background: linear-gradient(0deg, rgba(0,0,0,0.35), rgba(0,0,0,0.15)); +} + +.cmy-yt__play-btn { + appearance: none; + border: none; + border-radius: 999px; + padding: 14px 16px; + background: rgba(255, 255, 255, 0.92); + color: #111; + cursor: pointer; + transform: scale(1); + transition: transform .15s ease, box-shadow .15s ease, background .2s ease; + box-shadow: 0 6px 20px rgba(0,0,0,.18); + display: inline-flex; + align-items: center; + justify-content: center; +} + +.cmy-yt__play-btn:hover, +.cmy-yt__play-btn:focus-visible { + transform: scale(1.05); + box-shadow: 0 10px 28px rgba(0,0,0,.22); + background: #fff; +} + +@media (max-width: 599px) { + .cmy-yt__frame-wrapper, + .cmy-yt__thumb-img { + border-radius: 10px; + } +} diff --git a/src/components/CMyVideoYoutube/CMyVideoYoutube.ts b/src/components/CMyVideoYoutube/CMyVideoYoutube.ts new file mode 100755 index 00000000..d8c38f64 --- /dev/null +++ b/src/components/CMyVideoYoutube/CMyVideoYoutube.ts @@ -0,0 +1,134 @@ +import { defineComponent, computed, ref, watch } from 'vue'; + +type Nullable = T | null | undefined; + +function extractYouTubeId(url: string): string | null { + if (!url) return null; + + // URL normalizzati, rimuovi spazi + const u = url.trim(); + + // youtu.be/ + const short = u.match(/^https?:\/\/(?:www\.)?youtu\.be\/([A-Za-z0-9_-]{11})/i); + if (short?.[1]) return short[1]; + + // youtube.com/watch?v= (&…) + const watchMatch = u.match(/[?&]v=([A-Za-z0-9_-]{11})/i); + if (watchMatch?.[1]) return watchMatch[1]; + + // youtube.com/embed/ + const embed = u.match(/\/embed\/([A-Za-z0-9_-]{11})/i); + if (embed?.[1]) return embed[1]; + + // shorts + const shorts = u.match(/\/shorts\/([A-Za-z0-9_-]{11})/i); + if (shorts?.[1]) return shorts[1]; + + // fallback debole: sequenza di 11 char nel path o query + const loose = u.match(/([A-Za-z0-9_-]{11})/); + return loose?.[1] ?? null; +} + +export default defineComponent({ + name: 'CMyVideoYoutube', + props: { + /** Link completo YouTube (watch, youtu.be, embed, shorts) */ + url: { type: String, required: true }, + + /** Rapporto d'aspetto (es. 16/9) */ + ratio: { type: Number, default: 16 / 9 }, + + /** Modalità privacy (usa youtube-nocookie.com) */ + privacyMode: { type: Boolean, default: true }, + + /** Caricamento lazy dell'iframe */ + lazy: { type: Boolean, default: true }, + + /** Mostra thumbnail e avvia iframe solo al click */ + thumbnailClickToPlay: { type: Boolean, default: true }, + + /** Titolo accessibile (fallback al titolo standard) */ + title: { type: String, default: '' }, + + // ---- Parametri player utili ---- + autoplay: { type: Boolean, default: false }, + controls: { type: Boolean, default: true }, + mute: { type: Boolean, default: false }, + loop: { type: Boolean, default: false }, + start: { type: Number, default: 0 }, + end: { type: Number, default: 0 }, // 0 = nessun end + rel: { type: Boolean, default: false }, // video correlati (false = solo stesso canale) + modestBranding: { type: Boolean, default: true }, + playsinline: { type: Boolean, default: true }, + /** Lingua sottotitoli ("it", "en", ...) */ + ccLang: { type: String, default: '' }, + /** Forza sottotitoli abilitati */ + ccLoad: { type: Boolean, default: false } + }, + emits: ['started'], + setup(props, { emit }) { + const isPlaying = ref(false); + + const videoId = computed(() => extractYouTubeId(props.url)); + + const computedTitle = computed(() => { + if (props.title) return props.title; + return 'Video YouTube'; + }); + + const baseDomain = computed(() => + props.privacyMode ? 'https://www.youtube-nocookie.com' : 'https://www.youtube.com' + ); + + const thumbUrl = computed(() => { + if (!videoId.value) return ''; + // hqdefault.jpg è un buon compromesso tra qualità e peso + return `https://i.ytimg.com/vi/${videoId.value}/hqdefault.jpg`; + }); + + const embedUrl = computed(() => { + if (!videoId.value) return ''; + const params = new URLSearchParams(); + + params.set('autoplay', (isPlaying.value || props.autoplay) ? '1' : '0'); + params.set('controls', props.controls ? '1' : '0'); + params.set('mute', props.mute ? '1' : '0'); + params.set('modestbranding', props.modestBranding ? '1' : '0'); + params.set('playsinline', props.playsinline ? '1' : '0'); + params.set('rel', props.rel ? '1' : '0'); + + if (props.start > 0) params.set('start', String(props.start)); + if (props.end > 0) params.set('end', String(props.end)); + if (props.ccLang) params.set('cc_lang_pref', props.ccLang); + if (props.ccLoad) params.set('cc_load_policy', '1'); + + // Loop su singolo video: serve anche playlist=id + if (props.loop) { + params.set('loop', '1'); + params.set('playlist', videoId.value); + } + + const path = `/embed/${videoId.value}`; + return `${baseDomain.value}${path}?${params.toString()}`; + }); + + function startPlayback() { + isPlaying.value = true; + emit('started'); + } + + // Se cambia URL, resetta stato play + watch(() => props.url, () => { + isPlaying.value = false; + }); + + return { + videoId, + computedTitle, + embedUrl, + thumbUrl, + isPlaying, + startPlayback + }; + } +}); diff --git a/src/components/CMyVideoYoutube/CMyVideoYoutube.vue b/src/components/CMyVideoYoutube/CMyVideoYoutube.vue new file mode 100755 index 00000000..b7aa0cd0 --- /dev/null +++ b/src/components/CMyVideoYoutube/CMyVideoYoutube.vue @@ -0,0 +1,54 @@ + + + + diff --git a/src/components/CMyVideoYoutube/index.ts b/src/components/CMyVideoYoutube/index.ts new file mode 100755 index 00000000..b98dd5f4 --- /dev/null +++ b/src/components/CMyVideoYoutube/index.ts @@ -0,0 +1 @@ +export {default as CMyVideoYoutube} from './CMyVideoYoutube.vue'