- migliorata la grafica dell'aggiungi elemento.
This commit is contained in:
@@ -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',
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@@ -1097,6 +1097,53 @@
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="myel.type === shared_consts.ELEMTYPE.VIDEO_YOUTUBE">
|
||||
<div
|
||||
v-if="enableEdit"
|
||||
class="row q-col-gutter-sm"
|
||||
>
|
||||
<!-- Link YouTube -->
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
label="Link YouTube"
|
||||
v-model="myel.container"
|
||||
@update:model-value="modifElem"
|
||||
v-on:keyup.enter="saveElem"
|
||||
:rules="[(v) => !!v || 'Inserisci un link YouTube']"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Titolo accessibile (facoltativo) -->
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
label="Titolo (facoltativo)"
|
||||
v-model="myel.container2"
|
||||
@update:model-value="modifElem"
|
||||
v-on:keyup.enter="saveElem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Ratio -->
|
||||
<div class="col-12 col-sm-6">
|
||||
<q-input
|
||||
dense
|
||||
filled
|
||||
type="number"
|
||||
step="any"
|
||||
min="0.2"
|
||||
label="Ratio (es. 1.777 per 16/9)"
|
||||
v-model.number="myel.ratio"
|
||||
@update:model-value="modifElem"
|
||||
v-on:keyup.enter="saveElem"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="myel.type === shared_consts.ELEMTYPE.FOOTER"></div>
|
||||
<div v-else-if="myel.type === shared_consts.ELEMTYPE.PAGE">
|
||||
<div
|
||||
|
||||
@@ -20,6 +20,7 @@ import { shared_consts } from '@src/common/shared_vuejs';
|
||||
import { LandingFooter } from '@src/components/LandingFooter';
|
||||
import { CMyActivities } from '@src/components/CMyActivities';
|
||||
import { CECommerce } from '@src/components/CECommerce';
|
||||
import { CMyVideoYoutube } from '@src/components/CMyVideoYoutube';
|
||||
import { CStatMacro } from '@src/components/CStatMacro';
|
||||
import { CSearchProduct } from '@src/components/CSearchProduct';
|
||||
import { CPageViewStats } from '@src/components/CPageViewStats';
|
||||
@@ -117,6 +118,7 @@ export default defineComponent({
|
||||
CSection,
|
||||
CRow,
|
||||
CColumn,
|
||||
CMyVideoYoutube,
|
||||
// , //CMapMarker,
|
||||
},
|
||||
emits: ['selElemClick'],
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
:height="myel.heightimg ? myel.heightimg : undefined"
|
||||
></q-img>
|
||||
<q-img
|
||||
v-else
|
||||
v-else
|
||||
src="images/noimg.png"
|
||||
:fit="myel.fit ? myel.fit : 'contain'"
|
||||
class="img"
|
||||
@@ -335,6 +335,26 @@
|
||||
</q-video>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="myel.type === shared_consts.ELEMTYPE.VIDEO_YOUTUBE">
|
||||
<CMyVideoYoutube
|
||||
:url="myelem.container"
|
||||
:title="myelem.container2 || ''"
|
||||
:ratio="myelem.ratio || 16 / 9"
|
||||
:privacyMode="myelem.privacyMode ?? true"
|
||||
:thumbnailClickToPlay="myelem.thumbnailClickToPlay ?? true"
|
||||
:autoplay="myelem.autoplay ?? false"
|
||||
:controls="myelem.controls ?? true"
|
||||
:mute="myelem.mute ?? false"
|
||||
:loop="myelem.loop ?? false"
|
||||
:start="myelem.start || 0"
|
||||
:end="myelem.end || 0"
|
||||
:rel="myelem.rel ?? false"
|
||||
:modestBranding="myelem.modestBranding ?? true"
|
||||
:playsinline="myelem.playsinline ?? true"
|
||||
:ccLang="myelem.ccLang || ''"
|
||||
:ccLoad="myelem.ccLoad ?? false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="myel.type === shared_consts.ELEMTYPE.PAGE">
|
||||
<div
|
||||
:class="myel.class + (editOn ? ` clEdit` : ``) + getClass()"
|
||||
|
||||
@@ -459,4 +459,43 @@ h1 {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.elementor-btn {
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease;
|
||||
&:hover {
|
||||
background-color: rgba($primary, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Migliora il look delle espansioni
|
||||
.q-expansion-item {
|
||||
&__content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Risposta mobile
|
||||
@media (max-width: 600px) {
|
||||
.elementor-btn {
|
||||
q-icon {
|
||||
size: 32px;
|
||||
}
|
||||
.text-caption {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.my-stacked-btn .q-btn__label {
|
||||
white-space: normal;
|
||||
line-height: 1.4;
|
||||
font-size: 0.85em;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -31,11 +31,8 @@ import { useProducts } from '@src/store/Products';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CMyElemAdd',
|
||||
components: {
|
||||
},
|
||||
emits: [
|
||||
'AddedNewElem',
|
||||
],
|
||||
components: {},
|
||||
emits: ['AddedNewElem'],
|
||||
props: {
|
||||
myelem: {
|
||||
type: Object as PropType<IMyElem>,
|
||||
@@ -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(<any>[]);
|
||||
|
||||
|
||||
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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,107 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-card class="">
|
||||
<q-bar
|
||||
dense
|
||||
class="bg-primary text-white"
|
||||
>
|
||||
<q-card class="shadow-6 rounded-lg" style="overflow: hidden">
|
||||
<!-- Barra superiore -->
|
||||
<q-bar class="bg-primary text-white">
|
||||
Aggiungi Elemento:
|
||||
<q-space />
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
color="white"
|
||||
icon="close"
|
||||
v-close-popup
|
||||
></q-btn>
|
||||
<q-btn flat round icon="close" v-close-popup />
|
||||
</q-bar>
|
||||
|
||||
<div class="q-pa-md row justify-center">
|
||||
<div style="width: 100%; max-width: 600px">
|
||||
<q-list
|
||||
padding
|
||||
bordered
|
||||
class="rounded-borders"
|
||||
>
|
||||
<!-- Contenuto principale -->
|
||||
<div class="q-pa-sm row justify-center">
|
||||
<div style="width: 100%; max-width: 350px">
|
||||
<q-list padding bordered class="rounded-borders shadow-sm">
|
||||
<!-- Sezioni generate dinamicamente -->
|
||||
<q-expansion-item
|
||||
label="Principali"
|
||||
icon="fas fa-eye"
|
||||
dense
|
||||
dense-toggle
|
||||
v-for="(sec, i) in sections"
|
||||
:key="sec.label"
|
||||
:label="sec.label"
|
||||
:icon="sec.icon"
|
||||
:default-opened="i === 0"
|
||||
expand-separator
|
||||
default-opened
|
||||
header-class="text-subtitle1 text-weight-bold"
|
||||
>
|
||||
<div class="row q-pa-sm">
|
||||
<div class="row q-pa-xs" v-if="enableAdd">
|
||||
<div
|
||||
v-for="(rec, index) in shared_consts.TypesElem"
|
||||
:key="index"
|
||||
class="col-6 q-pa-xs"
|
||||
v-for="(rec, idx) in sec.items"
|
||||
:key="idx"
|
||||
class="col-6"
|
||||
>
|
||||
<q-btn
|
||||
v-if="enableAdd"
|
||||
flat
|
||||
no-caps
|
||||
stack
|
||||
class="elementor-btn full-width q-py-sm q-px-sm my-stacked-btn"
|
||||
:icon="rec.icon"
|
||||
:label="rec.label"
|
||||
color="primary"
|
||||
class="full-width uniform-button q-px-sm"
|
||||
text-color="primary"
|
||||
@click="
|
||||
newtype = rec.value;
|
||||
addNewElem(myel, direzadd);
|
||||
"
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
dense
|
||||
dense-toggle
|
||||
expand-separator
|
||||
label="Gestione"
|
||||
icon="fas fa-cog"
|
||||
>
|
||||
<div class="row q-pa-sm">
|
||||
<div
|
||||
v-for="(rec, index) in shared_consts.TypesElemAdmin"
|
||||
:key="index"
|
||||
class="col-6 q-pa-xs"
|
||||
>
|
||||
<q-btn
|
||||
v-if="enableAdd"
|
||||
:label="rec.label"
|
||||
color="primary"
|
||||
class="full-width uniform-button q-px-sm"
|
||||
@click="
|
||||
newtype = rec.value;
|
||||
addNewElem(myel, direzadd);
|
||||
"
|
||||
>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
dense
|
||||
dense-toggle
|
||||
expand-separator
|
||||
label="Avanzati"
|
||||
icon="fas fa-star"
|
||||
>
|
||||
<div class="row q-pa-sm">
|
||||
<div
|
||||
v-for="(rec, index) in shared_consts.TypesElemAdminTools"
|
||||
:key="index"
|
||||
class="col-6 q-pa-sm"
|
||||
>
|
||||
<q-btn
|
||||
v-if="enableAdd"
|
||||
:label="rec.label"
|
||||
color="primary"
|
||||
class="full-width uniform-button q-px-sm"
|
||||
@click="
|
||||
newtype = rec.value;
|
||||
addNewElem(myel, direzadd);
|
||||
"
|
||||
>
|
||||
</q-btn>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
||||
@@ -115,6 +115,66 @@ export default defineComponent({
|
||||
|
||||
const onloading = ref(false);
|
||||
|
||||
// Blocchi media DRY (img/content/video x 1..3) + fix img3
|
||||
const mediaBlocks = computed(() => {
|
||||
if (!rec.value) return [];
|
||||
const r = rec.value;
|
||||
return [
|
||||
{
|
||||
img: r.img1 || null,
|
||||
html: r.content || null,
|
||||
video: r.video1 || null,
|
||||
ratio: r.ratio1 || null,
|
||||
},
|
||||
{
|
||||
img: r.img2 || null,
|
||||
html: r.content2 || null,
|
||||
video: r.video2 || null,
|
||||
ratio: r.ratio2 || null,
|
||||
},
|
||||
{
|
||||
img: r.img3 || null,
|
||||
html: r.content3 || null,
|
||||
video: r.video3 || null,
|
||||
ratio: r.ratio3 || null,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// Larghezza drawer responsiva (usa mobile/tablet/desktop)
|
||||
const drawerWidth = computed(() => {
|
||||
// se hai $q disponibile: const s = $q.screen;
|
||||
// fallback semplice:
|
||||
return tools.isMobile() ? 340 : Math.min(mywidthEditor.value || 420, 560);
|
||||
});
|
||||
|
||||
const containerStyle = computed(() => ({
|
||||
maxWidth: '980px', // comodo per lettura
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
}));
|
||||
|
||||
const closeEditor = () => {
|
||||
visuEditor.value = false;
|
||||
selElem.value = {};
|
||||
};
|
||||
|
||||
const openAdd = (col: any, parent: any) => {
|
||||
visuadd.value = true;
|
||||
myElemSel.value = col;
|
||||
myElemParent.value = parent;
|
||||
};
|
||||
|
||||
const addAtEnd = () => {
|
||||
visuadd.value = true;
|
||||
const last =
|
||||
myelems.value.length > 0 ? myelems.value[myelems.value.length - 1] : null;
|
||||
myElemSel.value = last;
|
||||
myElemParent.value = last;
|
||||
};
|
||||
|
||||
const showOrder = ref(false)
|
||||
|
||||
const myelems = computed(() => {
|
||||
if (myidPage.value) return globalStore.getMyElemsByIdPage(myidPage.value);
|
||||
else if (mypathin.value) return globalStore.getMyElems(mypathin.value);
|
||||
@@ -376,11 +436,9 @@ export default defineComponent({
|
||||
idRowToAddDown?: string,
|
||||
neword?: number
|
||||
) {
|
||||
|
||||
const section = newElem
|
||||
const section = newElem;
|
||||
// trova la section “vera” nello store (per sicurezza)
|
||||
const newRow =
|
||||
newElem.rows[newElem.rows.length - 1]
|
||||
const newRow = newElem.rows[newElem.rows.length - 1];
|
||||
if (!section) return;
|
||||
|
||||
if (!Array.isArray(section.rows)) section.rows = [];
|
||||
@@ -515,6 +573,13 @@ export default defineComponent({
|
||||
myElemSel,
|
||||
myElemParent,
|
||||
getColClasses,
|
||||
mediaBlocks,
|
||||
drawerWidth,
|
||||
containerStyle,
|
||||
closeEditor,
|
||||
openAdd,
|
||||
addAtEnd,
|
||||
showOrder,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,48 +8,45 @@
|
||||
<q-spinner-tail
|
||||
color="primary"
|
||||
size="4em"
|
||||
>
|
||||
</q-spinner-tail>
|
||||
/>
|
||||
</q-inner-loading>
|
||||
|
||||
<div v-if="!onloading">
|
||||
<!-- Toggle edit solo manager -->
|
||||
<q-toggle
|
||||
v-if="tools.isManager()"
|
||||
v-model="editOn"
|
||||
dense
|
||||
color="green"
|
||||
size="sm"
|
||||
@update:model-value="changeVisuDrawer(mypathin, editOn)"
|
||||
icon="fas fa-pencil-alt"
|
||||
>
|
||||
</q-toggle>
|
||||
@update:model-value="changeVisuDrawer(mypathin, editOn)"
|
||||
/>
|
||||
|
||||
<!-- Drawer Editor -->
|
||||
<q-drawer
|
||||
v-model="visuEditor"
|
||||
v-if="selElem && editOn && !tools.isObjectEmpty(selElem)"
|
||||
show-if-above
|
||||
:breakpoint="350"
|
||||
side="right"
|
||||
:width="tools.isMobile() ? 350 : mywidthEditor"
|
||||
:breakpoint="420"
|
||||
:width="drawerWidth"
|
||||
elevated
|
||||
style="transition: 'width 0.3s ease'"
|
||||
:style="{ transition: 'width 0.3s ease' }"
|
||||
>
|
||||
<q-bar
|
||||
dense
|
||||
class="q-ma-xs bg-primary text-white"
|
||||
>
|
||||
<q-toolbar-title> Editor </q-toolbar-title>
|
||||
<q-toolbar-title>Editor</q-toolbar-title>
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
size="md"
|
||||
color="white"
|
||||
icon="close"
|
||||
@click="
|
||||
visuEditor = false;
|
||||
selElem = {};
|
||||
"
|
||||
></q-btn>
|
||||
@click="closeEditor"
|
||||
/>
|
||||
</q-bar>
|
||||
|
||||
<CMyEditElem
|
||||
@@ -61,365 +58,340 @@
|
||||
@deleteElem="deleteElem"
|
||||
@toggleSize="toggleSize"
|
||||
@dupPage="duplicatePage"
|
||||
@expPage="showexportPage = !showexportPage"
|
||||
@impPage="showimportPage = !showimportPage"
|
||||
>
|
||||
</CMyEditElem>
|
||||
@expPage="showexportPage = true"
|
||||
@impPage="showimportPage = true"
|
||||
/>
|
||||
</q-drawer>
|
||||
|
||||
<!-- Contenuto pagina -->
|
||||
<div
|
||||
:class="{ 'q-gutter-xs': !hideHeader }"
|
||||
:style="[
|
||||
{
|
||||
'margin-left': hideHeader ? 0 : 1 + 'px',
|
||||
'margin-right': hideHeader ? 0 : 1 + 'px',
|
||||
},
|
||||
]"
|
||||
:class="[{ 'q-gutter-xs': !hideHeader }, 'q-mx-auto', 'q-px-sm', 'q-pb-lg']"
|
||||
:style="containerStyle"
|
||||
>
|
||||
<div
|
||||
v-if="!!rec.img1"
|
||||
class="text-center"
|
||||
<!-- Media/Content blocks (1..3) -->
|
||||
<section
|
||||
v-for="(blk, i) in mediaBlocks"
|
||||
:key="`mblk-${i}`"
|
||||
class="q-mb-md"
|
||||
>
|
||||
<q-img
|
||||
:src="`` + rec.img1"
|
||||
class="img"
|
||||
></q-img>
|
||||
</div>
|
||||
<div
|
||||
v-if="blk.img"
|
||||
class="text-center q-mb-sm"
|
||||
>
|
||||
<q-img
|
||||
:src="blk.img"
|
||||
class="page-img"
|
||||
:ratio="16 / 9"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="blk.html"
|
||||
v-html="blk.html"
|
||||
class="q-mb-sm content-html"
|
||||
></div>
|
||||
<q-video
|
||||
v-if="blk.video"
|
||||
:src="blk.video"
|
||||
:ratio="blk.ratio || 16 / 9"
|
||||
class="q-mb-md"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<!-- Contenuti extra HTML -->
|
||||
<div
|
||||
v-if="!!rec.content"
|
||||
v-html="rec.content"
|
||||
></div>
|
||||
<q-video
|
||||
v-if="!!rec.video1"
|
||||
:src="rec.video1"
|
||||
:ratio="rec.ratio1"
|
||||
>
|
||||
</q-video>
|
||||
|
||||
<div
|
||||
v-if="!!rec.img2"
|
||||
class="text-center"
|
||||
>
|
||||
<q-img
|
||||
:src="`` + rec.img2"
|
||||
class="img"
|
||||
></q-img>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!!rec.content2"
|
||||
v-html="rec.content2"
|
||||
></div>
|
||||
<q-video
|
||||
v-if="!!rec.video2"
|
||||
:src="rec.video2"
|
||||
:ratio="rec.ratio2"
|
||||
></q-video>
|
||||
|
||||
<div
|
||||
v-if="!!rec.img3"
|
||||
class="text-center"
|
||||
>
|
||||
<q-img
|
||||
:src="`` + rec.img2"
|
||||
class="img"
|
||||
></q-img>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!!rec.content3"
|
||||
v-html="rec.content3"
|
||||
></div>
|
||||
<q-video
|
||||
v-if="!!rec.video3"
|
||||
:src="rec.video3"
|
||||
:ratio="rec.ratio3"
|
||||
></q-video>
|
||||
<div
|
||||
v-if="!!rec.content4"
|
||||
v-if="rec.content4"
|
||||
v-html="rec.content4"
|
||||
class="q-mb-md content-html"
|
||||
></div>
|
||||
|
||||
<!-- Lista elementi -->
|
||||
<div
|
||||
v-for="myelem in myelems"
|
||||
:key="myelem._id"
|
||||
class="q-mb-lg"
|
||||
>
|
||||
<div>
|
||||
<transition
|
||||
:duration="1000"
|
||||
appear
|
||||
>
|
||||
<div>
|
||||
<CTitleBanner
|
||||
v-if="(myelem.active || editOn) && !!rec.path && myelem.titleBanner"
|
||||
:class="`q-pa-xs`"
|
||||
:title="myelem.titleBanner"
|
||||
bgcolor="bg-primary"
|
||||
:clcolor="myelem.color ? `` : `text-white`"
|
||||
:mystyle="myelem.color ? `color: ${myelem.color} !important;` : ``"
|
||||
:myclass="myelem.classBanner"
|
||||
:canopen="true"
|
||||
>
|
||||
</CTitleBanner>
|
||||
|
||||
<transition
|
||||
appear
|
||||
:duration="300"
|
||||
enter-active-class="animated fadeInUp"
|
||||
>
|
||||
<div>
|
||||
<CTitleBanner
|
||||
v-if="(myelem.active || editOn) && !!rec.path && myelem.titleBanner"
|
||||
class="q-pa-xs"
|
||||
:title="myelem.titleBanner"
|
||||
bgcolor="bg-primary"
|
||||
:clcolor="myelem.color ? '' : 'text-white'"
|
||||
:mystyle="myelem.color ? `color: ${myelem.color} !important;` : ''"
|
||||
:myclass="myelem.classBanner"
|
||||
:canopen="true"
|
||||
/>
|
||||
<div
|
||||
v-if="showOrder"
|
||||
class="text-caption text-grey q-mb-xs"
|
||||
>
|
||||
order: {{ myelem.order }}
|
||||
</div>
|
||||
|
||||
<!-- Sezione -->
|
||||
<div v-if="myelem.type === shared_consts.ELEMTYPE.SECTION">
|
||||
<!-- Sezione -->
|
||||
<div v-if="myelem.type === shared_consts.ELEMTYPE.SECTION">
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center text-caption q-mb-sm"
|
||||
>
|
||||
SEZIONE
|
||||
</div>
|
||||
|
||||
<CMyElem
|
||||
:myelem="myelem"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="rec.path || ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
>
|
||||
<!-- Righe della sezione -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center"
|
||||
v-for="(row, indriga) in myelem.rows"
|
||||
:key="row._id"
|
||||
class="q-mb-md"
|
||||
>
|
||||
<div v-if="editOn">SEZIONE:</div>
|
||||
</div>
|
||||
|
||||
<CMyElem
|
||||
:myelem="myelem"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="!!rec.path ? rec.path : ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
>
|
||||
<!-- Rendering righe dentro la sezione -->
|
||||
<div
|
||||
v-for="(row, indriga) in myelem.rows"
|
||||
:key="row._id"
|
||||
class="row-container"
|
||||
>
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mb-md"
|
||||
>
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
label="Riga"
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
@click="
|
||||
addNewElemSectRow(
|
||||
myelem.order + 1,
|
||||
myelem,
|
||||
shared_consts.ELEMTYPE.ROW,
|
||||
row._id
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip> Aggiungi Riga </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="row.type === shared_consts.ELEMTYPE.ROW">
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center"
|
||||
>
|
||||
<div v-if="editOn">RIGA {{ indriga + 1 }}:</div>
|
||||
</div>
|
||||
<CMyElem
|
||||
:myelem="row"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="!!rec.path ? rec.path : ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
>
|
||||
<!-- Rendering colonne dentro la riga -->
|
||||
<div class="row q-col-gutter-md items-stretch">
|
||||
<template
|
||||
v-for="(col, index) in row.columns"
|
||||
:key="col._id"
|
||||
>
|
||||
<div
|
||||
v-if="col.type === shared_consts.ELEMTYPE.COLUMN"
|
||||
:class="getColClasses(col, row, index)"
|
||||
>
|
||||
<div
|
||||
:style="editOn ? `border: 2px dashed #1976d2` : ``"
|
||||
>
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center"
|
||||
>
|
||||
Colonna {{ index + 1 }}:
|
||||
</div>
|
||||
<div
|
||||
v-for="el in col.elems"
|
||||
:key="el._id"
|
||||
>
|
||||
<CMyElem
|
||||
:myelem="el"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="!!rec.path ? rec.path : ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
/>
|
||||
<div class="text-center q-mb-md">
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
@click="deleteElemento(el)"
|
||||
>
|
||||
<q-tooltip> Elimina Elemento </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center q-mb-md">
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
@click="
|
||||
visuadd = true;
|
||||
myElemSel = col;
|
||||
myElemParent = myelem;
|
||||
"
|
||||
>
|
||||
<q-tooltip> Aggiungi Elemento </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<div class="text-center q-mb-md">
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
label="Colonna"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
@click="deleteCol(col)"
|
||||
>
|
||||
<q-tooltip> Elimina Colonna </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</CMyElem>
|
||||
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mb-md"
|
||||
>
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
label="Colonna"
|
||||
color="primary"
|
||||
icon="add"
|
||||
@click="
|
||||
addNewElemSectRow(
|
||||
row.order + 1,
|
||||
row,
|
||||
shared_consts.ELEMTYPE.COLUMN
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip> Aggiungi Colonna </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center q-mb-md">
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
label="Riga"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
@click="deleteRow(row)"
|
||||
>
|
||||
<q-tooltip> Elimina Riga </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mb-md"
|
||||
class="text-center q-mb-sm"
|
||||
>
|
||||
<q-btn
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
label="Riga"
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
label="Riga"
|
||||
@click="
|
||||
addNewElemSectRow(
|
||||
myelem.order + 1,
|
||||
myelem,
|
||||
shared_consts.ELEMTYPE.ROW
|
||||
shared_consts.ELEMTYPE.ROW,
|
||||
row._id
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip> Aggiungi Riga </q-tooltip>
|
||||
<q-tooltip>Aggiungi Riga</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</CMyElem>
|
||||
</div>
|
||||
|
||||
<!-- Elementi senza Sezione (retrocompatibilità) -->
|
||||
<div v-if="myelem.type !== shared_consts.ELEMTYPE.SECTION">
|
||||
<CMyElem
|
||||
:myelem="myelem"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="!!rec.path ? rec.path : ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="row.type === shared_consts.ELEMTYPE.ROW">
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center text-caption q-mb-xs"
|
||||
>
|
||||
RIGA {{ indriga + 1 }}
|
||||
</div>
|
||||
|
||||
<div class="text-center q-mb-md">
|
||||
<q-btn
|
||||
<CMyElem
|
||||
:myelem="row"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="rec.path || ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
>
|
||||
<!-- Colonne della riga -->
|
||||
<div class="row q-col-gutter-md items-stretch">
|
||||
<template
|
||||
v-for="(col, index) in row.columns"
|
||||
:key="col._id"
|
||||
>
|
||||
<div
|
||||
v-if="col.type === shared_consts.ELEMTYPE.COLUMN"
|
||||
:class="getColClasses(col, row, index)"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
{ 'editor-border': editOn },
|
||||
'q-pa-xs q-mb-sm',
|
||||
]"
|
||||
>
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center text-caption q-mb-xs"
|
||||
>
|
||||
Colonna {{ index + 1 }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="el in col.elems"
|
||||
:key="el._id"
|
||||
class="q-mb-sm"
|
||||
>
|
||||
<CMyElem
|
||||
:myelem="el"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="rec.path || ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
/>
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mt-xs"
|
||||
>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
@click="deleteElemento(el)"
|
||||
>
|
||||
<q-tooltip>Elimina Elemento</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Azioni colonna -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="row justify-center q-gutter-sm q-mb-sm"
|
||||
>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
@click="openAdd(col, myelem)"
|
||||
>
|
||||
<q-tooltip>Aggiungi Elemento</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
label="Colonna"
|
||||
@click="deleteCol(col)"
|
||||
>
|
||||
<q-tooltip>Elimina Colonna</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</CMyElem>
|
||||
|
||||
<!-- Aggiungi colonna -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mb-sm"
|
||||
>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="primary"
|
||||
icon="add"
|
||||
label="Colonna"
|
||||
@click="
|
||||
addNewElemSectRow(
|
||||
row.order + 1,
|
||||
row,
|
||||
shared_consts.ELEMTYPE.COLUMN
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip>Aggiungi Colonna</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Elimina riga -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center"
|
||||
>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
label="Riga"
|
||||
@click="deleteRow(row)"
|
||||
>
|
||||
<q-tooltip>Elimina Riga</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aggiungi riga -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
@click="
|
||||
visuadd = true;
|
||||
myElemSel =
|
||||
myelems.length > 0 ? myelems[myelems.length - 1] : null;
|
||||
myElemParent =
|
||||
myelems.length > 0 ? myelems[myelems.length - 1] : null;
|
||||
"
|
||||
class="text-center q-mt-sm"
|
||||
>
|
||||
<q-tooltip> Aggiungi Elemento </q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
label="Riga"
|
||||
@click="
|
||||
addNewElemSectRow(
|
||||
myelem.order + 1,
|
||||
myelem,
|
||||
shared_consts.ELEMTYPE.ROW
|
||||
)
|
||||
"
|
||||
>
|
||||
<q-tooltip>Aggiungi Riga</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</CMyElem>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- Elementi fuori sezione (retrocompatibilità) -->
|
||||
<div v-else>
|
||||
<CMyElem
|
||||
:myelem="myelem"
|
||||
:idPage="rec._id"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
:path="rec.path || ''"
|
||||
:selElem="selElem"
|
||||
@selElemClick="selElemClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Aggiungi elemento al fondo lista -->
|
||||
<div
|
||||
v-if="editOn"
|
||||
class="text-center q-mt-sm"
|
||||
>
|
||||
<q-btn
|
||||
dense
|
||||
rounded
|
||||
size="sm"
|
||||
color="positive"
|
||||
icon="add"
|
||||
@click="addAtEnd()"
|
||||
>
|
||||
<q-tooltip>Aggiungi Elemento</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<div v-if="myelems.length === 0">
|
||||
<!-- Stato vuoto -->
|
||||
<div v-if="myelems.length === 0 && editOn">
|
||||
<CMyElem
|
||||
v-if="editOn && !!rec.path"
|
||||
:myelem="myelemVoid"
|
||||
:editOn="editOn"
|
||||
:addOn="addOn"
|
||||
@@ -427,13 +399,15 @@
|
||||
:selElem="selElem"
|
||||
:path="rec.path"
|
||||
@selElemClick="selElemClick"
|
||||
>
|
||||
</CMyElem>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LandingFooter v-if="rec.showFooter"></LandingFooter>
|
||||
|
||||
<LandingFooter v-if="rec.showFooter" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header di fallback -->
|
||||
<div v-else>
|
||||
<div v-if="!!title">
|
||||
<CTitle
|
||||
@@ -442,81 +416,69 @@
|
||||
:headtitle="title"
|
||||
:sizes="sizes"
|
||||
:styleadd="styleadd"
|
||||
></CTitle>
|
||||
<div v-if="!imgbackground">
|
||||
/>
|
||||
<div v-else-if="img">
|
||||
<CImgTitle
|
||||
v-if="img"
|
||||
:src="img"
|
||||
:title="title"
|
||||
>
|
||||
</CImgTitle>
|
||||
/>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
<div v-if="!nofooter"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dialog Export/Import -->
|
||||
<q-dialog v-model="showexportPage">
|
||||
<q-card class="dialog_card">
|
||||
<q-toolbar class="bg-primary text-white">
|
||||
<q-toolbar-title> Esporta Pagina </q-toolbar-title>
|
||||
<q-toolbar-title>Esporta Pagina</q-toolbar-title>
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
color="white"
|
||||
icon="close"
|
||||
v-close-popup
|
||||
></q-btn>
|
||||
/>
|
||||
</q-toolbar>
|
||||
<q-card-section class="q-pa-xs inset-shadow">
|
||||
<br />
|
||||
<CExportImportPage
|
||||
:idPage="rec._id"
|
||||
:nomefileprop="`esporta_${rec.path}.json`"
|
||||
:esporta="true"
|
||||
>
|
||||
</CExportImportPage>
|
||||
<br />
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="showimportPage">
|
||||
<q-card class="dialog_card">
|
||||
<q-toolbar class="bg-primary text-white">
|
||||
<q-toolbar-title> Esporta Pagina </q-toolbar-title>
|
||||
<q-toolbar-title>Importa Pagina</q-toolbar-title>
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
color="white"
|
||||
icon="close"
|
||||
v-close-popup
|
||||
></q-btn>
|
||||
/>
|
||||
</q-toolbar>
|
||||
<q-card-section class="q-pa-xs inset-shadow">
|
||||
<br />
|
||||
<CExportImportPage
|
||||
:idPage="rec._id"
|
||||
:nomefileprop="`esporta_${rec.path}.json`"
|
||||
>
|
||||
</CExportImportPage>
|
||||
<br />
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<div>
|
||||
<q-dialog
|
||||
v-model="visuadd"
|
||||
style="
|
||||
width: 600px;
|
||||
max-width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
"
|
||||
transition-show="slide-up"
|
||||
transition-hide="slide-down"
|
||||
>
|
||||
|
||||
<!-- Dialog Add Element -->
|
||||
<q-dialog
|
||||
v-model="visuadd"
|
||||
transition-show="slide-up"
|
||||
transition-hide="slide-down"
|
||||
>
|
||||
<div class="full-height-dialog">
|
||||
<CMyElemAdd
|
||||
v-if="visuadd"
|
||||
:myelem="myElemSel"
|
||||
@@ -527,10 +489,9 @@
|
||||
:addonlyinMem="true"
|
||||
@AddedNewElem="AddedNewElem"
|
||||
@close="visuadd = false"
|
||||
>
|
||||
</CMyElemAdd>
|
||||
</q-dialog>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</q-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
72
src/components/CMyVideoYoutube/CMyVideoYoutube.scss
Executable file
72
src/components/CMyVideoYoutube/CMyVideoYoutube.scss
Executable file
@@ -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;
|
||||
}
|
||||
}
|
||||
134
src/components/CMyVideoYoutube/CMyVideoYoutube.ts
Executable file
134
src/components/CMyVideoYoutube/CMyVideoYoutube.ts
Executable file
@@ -0,0 +1,134 @@
|
||||
import { defineComponent, computed, ref, watch } from 'vue';
|
||||
|
||||
type Nullable<T> = T | null | undefined;
|
||||
|
||||
function extractYouTubeId(url: string): string | null {
|
||||
if (!url) return null;
|
||||
|
||||
// URL normalizzati, rimuovi spazi
|
||||
const u = url.trim();
|
||||
|
||||
// youtu.be/<id>
|
||||
const short = u.match(/^https?:\/\/(?:www\.)?youtu\.be\/([A-Za-z0-9_-]{11})/i);
|
||||
if (short?.[1]) return short[1];
|
||||
|
||||
// youtube.com/watch?v=<id> (&…)
|
||||
const watchMatch = u.match(/[?&]v=([A-Za-z0-9_-]{11})/i);
|
||||
if (watchMatch?.[1]) return watchMatch[1];
|
||||
|
||||
// youtube.com/embed/<id>
|
||||
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
|
||||
};
|
||||
}
|
||||
});
|
||||
54
src/components/CMyVideoYoutube/CMyVideoYoutube.vue
Executable file
54
src/components/CMyVideoYoutube/CMyVideoYoutube.vue
Executable file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="cmy-yt">
|
||||
<!-- Stato errore URL non valido -->
|
||||
<div v-if="!videoId" class="cmy-yt__error">
|
||||
<q-icon name="warning" class="q-mr-sm" />
|
||||
Link YouTube non valido.
|
||||
<div class="text-caption text-grey-7 q-mt-xs">
|
||||
Esempi accettati: https://youtu.be/ID, https://www.youtube.com/watch?v=ID
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modalità thumbnail -> click per avviare -->
|
||||
<div v-else-if="thumbnailClickToPlay && !isPlaying" class="cmy-yt__thumb-wrapper">
|
||||
<q-responsive :ratio="ratio">
|
||||
<q-img
|
||||
:src="thumbUrl"
|
||||
:alt="computedTitle"
|
||||
class="cmy-yt__thumb-img"
|
||||
spinner-color="primary"
|
||||
loading="lazy"
|
||||
>
|
||||
<div class="cmy-yt__overlay">
|
||||
<button
|
||||
class="cmy-yt__play-btn"
|
||||
type="button"
|
||||
:aria-label="`Riproduci video: ${computedTitle}`"
|
||||
@click="startPlayback"
|
||||
>
|
||||
<q-icon name="play_arrow" size="40px" />
|
||||
</button>
|
||||
</div>
|
||||
</q-img>
|
||||
</q-responsive>
|
||||
</div>
|
||||
|
||||
<!-- Iframe diretto -->
|
||||
<q-responsive v-else :ratio="ratio" class="cmy-yt__frame-wrapper">
|
||||
<iframe
|
||||
class="cmy-yt__iframe"
|
||||
:title="computedTitle"
|
||||
:src="embedUrl"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
:loading="lazy ? 'lazy' : 'eager'"
|
||||
></iframe>
|
||||
</q-responsive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./CMyVideoYoutube.ts"></script>
|
||||
<style lang="scss" scoped>
|
||||
@import './CMyVideoYoutube.scss';
|
||||
</style>
|
||||
1
src/components/CMyVideoYoutube/index.ts
Executable file
1
src/components/CMyVideoYoutube/index.ts
Executable file
@@ -0,0 +1 @@
|
||||
export {default as CMyVideoYoutube} from './CMyVideoYoutube.vue'
|
||||
Reference in New Issue
Block a user