- check updates

- risolto problema della generazione dei PDF, avevo modificato in CMyPageElem , se si cambia qualcosa occorre stare attenti a mettere !hideHeader
This commit is contained in:
Surya Paolo
2025-11-01 12:00:49 +01:00
parent df98ec9471
commit d179581b23
78 changed files with 3593139 additions and 304 deletions

View File

@@ -0,0 +1,46 @@
.pwa-updater {
position: relative;
}
.pwa-update-badge {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: pulse 2s infinite;
&:hover {
transform: scale(1.05);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
}
}
.pwa-manual-check {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 9998;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.8;
}
}
/* Responsive */
@media (max-width: 600px) {
.pwa-update-badge {
bottom: 80px; // Sposta su per evitare FAB buttons
right: 16px;
font-size: 12px;
padding: 6px 12px;
}
}

View File

@@ -0,0 +1,401 @@
import { defineComponent, ref, onMounted, onBeforeUnmount } from 'vue';
import { useQuasar } from 'quasar';
import { tools } from '@tools'
import { useGlobalStore } from 'app/src/store';
export default defineComponent({
name: 'CCheckUpdatesPWA',
props: {
// Mostra pulsante per check manuale
showManualCheckButton: {
type: Boolean,
default: false
},
// Intervallo check automatico (in minuti)
autoCheckInterval: {
type: Number,
default: 60 // 60 minuti
},
// Mostra notifica successo dopo reload
showSuccessNotification: {
type: Boolean,
default: true
}
},
setup(props) {
const $q = useQuasar();
const globalStore = useGlobalStore();
// State
const refreshing = ref(false);
const swRegistration = ref<ServiceWorkerRegistration | null>(null);
const updateAvailable = ref(false);
const dialogVisible = ref(false);
let updateCheckInterval: NodeJS.Timeout | null = null;
/**
* Mostra dialog di conferma aggiornamento
*/
const showUpdateDialog = () => {
if (!swRegistration.value) {
console.error('❌ No service worker registration available');
return;
}
dialogVisible.value = true;
$q.dialog({
title: '🎉 Nuova Versione Disponibile',
message: 'È disponibile un aggiornamento dell\'applicazione. Vuoi installarlo ora?',
html: true,
persistent: false,
ok: {
label: 'Aggiorna Ora',
color: 'primary',
icon: 'system_update',
noCaps: true
},
cancel: {
label: 'Più Tardi',
color: 'grey',
flat: true,
noCaps: true
}
}).onOk(() => {
applyUpdate();
}).onCancel(() => {
dialogVisible.value = false;
console.log(' Aggiornamento rimandato dall\'utente');
showPersistentNotification();
}).onDismiss(() => {
dialogVisible.value = false;
});
};
/**
* Mostra notifica persistente per aggiornamento rimandato
*/
const showPersistentNotification = () => {
$q.notify({
message: 'Aggiornamento disponibile',
caption: 'Clicca per installare',
icon: 'info',
color: 'info',
position: 'bottom-right',
timeout: 0, // Persistente
actions: [
{
label: 'Aggiorna',
color: 'white',
handler: () => {
applyUpdate();
}
},
{
icon: 'close',
color: 'white',
round: true
}
]
});
};
/**
* Applica l'aggiornamento
*/
const applyUpdate = () => {
if (!swRegistration.value?.waiting) {
console.error('❌ No waiting service worker found');
$q.notify({
message: 'Errore: aggiornamento non disponibile',
icon: 'error',
color: 'negative',
timeout: 3000
});
return;
}
console.log('✅ Applying update...');
dialogVisible.value = false;
// Mostra loading
$q.loading.show({
message: 'Aggiornamento in corso...<br/>Non chiudere l\'app',
html: true,
spinnerColor: 'primary',
backgroundColor: 'dark',
messageColor: 'white'
});
// Salva stato prima del reload
saveStateBeforeUpdate();
// Invia messaggio al SW per attivare skipWaiting
swRegistration.value.waiting.postMessage({
action: 'skipWaiting',
type: 'SKIP_WAITING'
});
console.log('📤 Skip waiting message sent to service worker');
};
/**
* Salva stato dell'app prima dell'aggiornamento
*/
const saveStateBeforeUpdate = () => {
try {
sessionStorage.setItem('pwa_updating', 'true');
sessionStorage.setItem('pwa_update_timestamp', Date.now().toString());
// Salva posizione scroll
const scrollPos = window.scrollY || document.documentElement.scrollTop;
sessionStorage.setItem('pwa_scroll_position', scrollPos.toString());
// Salva percorso corrente
sessionStorage.setItem('pwa_current_path', window.location.pathname);
console.log('💾 State saved before update');
} catch (e) {
console.error('Failed to save state:', e);
}
};
/**
* Ripristina stato dopo l'aggiornamento
*/
const restoreStateAfterUpdate = () => {
try {
if (sessionStorage.getItem('pwa_updating') === 'true') {
sessionStorage.removeItem('pwa_updating');
if (props.showSuccessNotification) {
$q.notify({
message: 'App aggiornata con successo!',
caption: 'Stai usando l\'ultima versione',
icon: 'check_circle',
color: 'positive',
timeout: 4000,
position: 'top'
});
}
// Ripristina scroll position con delay
const scrollPos = sessionStorage.getItem('pwa_scroll_position');
if (scrollPos) {
setTimeout(() => {
window.scrollTo({
top: parseInt(scrollPos),
behavior: 'smooth'
});
sessionStorage.removeItem('pwa_scroll_position');
}, 300);
}
sessionStorage.removeItem('pwa_current_path');
sessionStorage.removeItem('pwa_update_timestamp');
}
} catch (e) {
console.error('Failed to restore state:', e);
}
};
/**
* Handler per evento swUpdated
*/
const handleSwUpdated = (event: Event) => {
const customEvent = event as CustomEvent<ServiceWorkerRegistration>;
console.log('🔄 SW Update detected:', customEvent.detail);
swRegistration.value = customEvent.detail;
updateAvailable.value = true;
// Mostra dialog automaticamente
setTimeout(() => {
showUpdateDialog();
}, 500);
};
/**
* Handler per cambio controller (SW attivato)
*/
const handleControllerChange = () => {
if (refreshing.value) {
console.log('⏭️ Already refreshing, skipping...');
return;
}
console.log('🔄 Service Worker controller changed, reloading...');
refreshing.value = true;
// Nascondi loading
$q.loading.hide();
// Reload con delay per smooth transition
setTimeout(() => {
window.location.reload();
}, 300);
};
/**
* Check manuale per aggiornamenti
*/
const checkForUpdates = async () => {
if (!('serviceWorker' in navigator)) {
console.warn('⚠️ Service Worker not supported in this browser');
$q.notify({
message: 'Service Worker non supportato',
icon: 'warning',
color: 'warning',
timeout: 3000
});
return;
}
try {
console.log('🔍 Checking for updates...');
$q.loading.show({
message: 'Controllo aggiornamenti...',
spinnerColor: 'primary'
});
const registration = await navigator.serviceWorker.ready;
if (!registration) {
throw new Error('No service worker registration found');
}
// Forza check aggiornamenti
await registration.update();
$q.loading.hide();
// Se c'è un SW in waiting, mostra dialog
if (registration.waiting) {
swRegistration.value = registration;
updateAvailable.value = true;
showUpdateDialog();
} else {
$q.notify({
message: 'Nessun aggiornamento disponibile',
caption: 'Stai usando l\'ultima versione',
icon: 'check_circle',
color: 'positive',
timeout: 3000
});
}
} catch (error) {
console.error('❌ Error checking for updates:', error);
$q.loading.hide();
$q.notify({
message: 'Errore nel controllo aggiornamenti',
caption: (error as Error).message || 'Errore sconosciuto',
icon: 'error',
color: 'negative',
timeout: 4000
});
}
};
/**
* Handler per messaggi dal Service Worker
*/
const handleSwMessage = (event: MessageEvent) => {
console.log('📨 Message from Service Worker:', event.data);
if (event.data?.type === 'SW_UPDATED') {
console.log('✅ SW updated to version:', event.data.version);
}
if (event.data?.type === 'SW_INSTALLED') {
console.log('✅ SW installed for the first time');
}
};
/**
* Setup automatico check aggiornamenti
*/
const setupAutoUpdateCheck = () => {
if (props.autoCheckInterval <= 0) return;
const intervalMs = props.autoCheckInterval * 60 * 1000;
console.log(`⏰ Setting up auto-update check every ${props.autoCheckInterval} minutes`);
updateCheckInterval = setInterval(() => {
console.log('⏰ Periodic update check triggered');
checkForUpdates();
}, intervalMs);
};
/**
* Cleanup
*/
const cleanup = () => {
console.log('🧹 Cleaning up PWA Updater');
// Rimuovi listeners
window.removeEventListener('swUpdated', handleSwUpdated);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
navigator.serviceWorker.removeEventListener('message', handleSwMessage);
}
// Clearinterval
if (updateCheckInterval) {
clearInterval(updateCheckInterval);
updateCheckInterval = null;
}
};
// Lifecycle Hooks
onMounted(() => {
console.log('🚀 PWA Updater component mounted');
// Listener per evento custom swUpdated
window.addEventListener('swUpdated', handleSwUpdated);
// Setup Service Worker listeners
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
navigator.serviceWorker.addEventListener('message', handleSwMessage);
// Check se c'è già un SW in waiting al mount
navigator.serviceWorker.ready.then((registration) => {
if (registration.waiting) {
console.log('⚠️ Service Worker already waiting at mount');
swRegistration.value = registration;
updateAvailable.value = true;
showUpdateDialog();
}
});
} else {
console.warn('⚠️ Service Workers not supported in this browser');
}
// Ripristina stato dopo aggiornamento
restoreStateAfterUpdate();
// Setup check automatico
setupAutoUpdateCheck();
});
onBeforeUnmount(() => {
cleanup();
});
return {
updateAvailable,
dialogVisible,
checkForUpdates,
showUpdateDialog,
tools,
globalStore,
};
}
});

View File

@@ -0,0 +1,35 @@
<template>
<div v-if="globalStore.showHeader" class="pwa-updater">
<!-- Badge floating per aggiornamento disponibile -->
<q-badge
v-if="updateAvailable && !dialogVisible"
color="primary"
floating
class="pwa-update-badge cursor-pointer"
@click="showUpdateDialog"
>
<q-icon name="system_update" size="sm" class="q-mr-xs" />
Aggiorna
</q-badge>
<!-- Pulsante manuale (opzionale - rimuovi se non serve) -->
<q-btn
v-if="showManualCheckButton"
round
dense
icon="refresh"
color="grey-7"
class="pwa-manual-check"
@click="checkForUpdates"
>
<q-tooltip>Controlla Aggiornamenti</q-tooltip>
</q-btn>
</div>
</template>
<script lang="ts" src="./CCheckUpdatesPWA.ts">
</script>
<style lang="scss" scoped>
@import './CCheckUpdatesPWA.scss';
</style>

View File

@@ -0,0 +1 @@
export {default as CCheckUpdatesPWA} from './CCheckUpdatesPWA.vue'

View File

@@ -1,5 +1,14 @@
import type { PropType } from 'vue';
import { computed, defineComponent, onMounted, ref, toRef, watch, nextTick } from 'vue';
import {
computed,
defineComponent,
onMounted,
ref,
toRef,
watch,
nextTick,
onUnmounted,
} from 'vue';
import type { IOptCatalogo, ICoordGPS, IMyElem, ISocial } from '@src/model';
import { IMyCard, IMyPage, IOperators } from '@src/model';
@@ -195,6 +204,8 @@ export default defineComponent({
const newtype = ref(<any>'');
const canShowVersion = ref(false);
const isAppRunning = computed(() => globalStore.isAppRunning);
const cardGroupMaxWidth = computed(() => {
@@ -307,6 +318,10 @@ export default defineComponent({
myel.value = props.myelem;
neworder.value = props.myelem.order;
setTimeout(() => {
canShowVersion.value = true;
}, 60000);
if (props.myelem) newtype.value = props.myelem.type;
nextTick(() => {
@@ -394,8 +409,23 @@ export default defineComponent({
}
}
const updateApp = async () => {
// Invia il messaggio al Service Worker per saltare l'attesa
const registration = await navigator.serviceWorker.getRegistration();
if (registration?.waiting) {
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
// Ricarica la pagina
window.location.reload();
};
onMounted(mounted);
const isNewVersionAvailable = computed(() => {
return canShowVersion.value ? globalStore.isNewVersionAvailable : false;
});
return {
tools,
shared_consts,
@@ -444,6 +474,7 @@ export default defineComponent({
cardGroupMaxWidth,
cardScroller,
scrollCards,
isNewVersionAvailable,
};
},
});

View File

@@ -196,7 +196,7 @@
>
<CTitle
:imgbackground="myel.imgback"
:headtitle="myel.title"
:headtitle="myel.container"
:sizes="myel.size"
:styleadd="myel.styleadd"
>
@@ -1086,7 +1086,7 @@
Controllo Nuova Versione
</div>
<q-banner
v-if="globalStore.isNewVersionAvailable"
v-if="isNewVersionAvailable"
rounded
dense
class="bg-green text-white"
@@ -1107,7 +1107,7 @@
</div>
<div v-else>
<span class="mybanner"
>Aggiornamento APP in corso ... Se dopo 1 minuto non dovesse scomparire
>* Aggiornamento APP in corso ... Se dopo 1 minuto non dovesse scomparire
questo messaggio, chiudere e riaprire la pagina.</span
>
</div>

View File

@@ -590,6 +590,7 @@ export default defineComponent({
openAdd,
addAtEnd,
showOrder,
globalStore,
};
},
});

View File

@@ -65,42 +65,44 @@
<!-- Contenuto pagina -->
<div
:class="[{ 'q-gutter-xs': !hideHeader }, 'q-mx-auto', 'q-pb-lg']"
:style="containerStyle"
:class="[{ 'q-gutter-xs': !hideHeader, 'q-mx-auto': !hideHeader, 'q-pb-lg': !hideHeader}]"
:style="hideHeader ? [{ 'margin-left': 0, 'margin-right': 0 }] : containerStyle"
>
<!-- Media/Content blocks (1..3) -->
<section
v-for="(blk, i) in mediaBlocks"
:key="`mblk-${i}`"
class="q-mb-md"
>
<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"
<div v-if="globalStore.showHeader">
<!-- Media/Content blocks (1..3) -->
<section
v-for="(blk, i) in mediaBlocks"
:key="`mblk-${i}`"
class="q-mb-md"
/>
</section>
>
<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>
</div>
<!-- Contenuti extra HTML -->
<div
v-if="rec.content4"
v-if="rec.content4 && globalStore.showHeader"
v-html="rec.content4"
class="q-mb-md content-html"
></div>
@@ -109,7 +111,6 @@
<div
v-for="myelem in myelems"
:key="myelem._id"
class="q-mb-lg"
>
<transition
appear

View File

@@ -23,7 +23,7 @@
:strfido="t('account.fido_casa', { fido: account ? account.fidoConcesso : '', symbol })"
:color="color"
v-model="saldo"
:before_str="t('account.saldo') + `:`"
:before_str="$q.screen.lt.sm ? '' : t('account.saldo') + `:`"
:valueextra="
account && account.notifspending && account.notifspending.length > 0
? `* `

View File

@@ -85,6 +85,8 @@ export default defineComponent({
const products = useProducts();
const notifStore = useNotifStore();
const canShowVersion = ref(false);
const { getnumItemsCart } = MixinUsers();
const site = computed(() => globalStore.site);
@@ -116,7 +118,8 @@ export default defineComponent({
});
const getColorText = computed(() => {
if (globalStore.site && globalStore.site.confpages?.col_toolbar === 'white') return 'black';
if (globalStore.site && globalStore.site.confpages?.col_toolbar === 'white')
return 'black';
else return 'white';
});
@@ -158,11 +161,46 @@ export default defineComponent({
const stateconn = ref(globalStore.stateConnection);
function refreshApp() {
// ✅ 1. Se siamo su iOS: NON toccare il Service Worker!
if (Platform.is.ios) {
// Su iOS, Safari non risponde bene a unregister() o skipWaiting()
// L'unico modo affidabile è: forzare un reload con cache busting
console.log('[PWA] iOS: Forzo reload senza toccare il Service Worker...');
// Aggiungi un parametro casuale per bypassare la cache del browser
const url = new URL(window.location.href);
url.searchParams.set('refresh', Date.now().toString());
window.location.href = url.toString();
return; // ✅ Esce subito — non esegue altro
}
// ✅ 2. Se siamo su Android / Chrome / Desktop: usa SKIP_WAITING
if (data.value.registration && data.value.registration.waiting) {
console.log('[PWA] Invio SKIP_WAITING al Service Worker...');
data.value.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
// ✅ Aspetta che il nuovo SW diventi attivo prima di ricaricare
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('[PWA] Nuovo Service Worker attivo — ricarico la pagina...');
window.location.reload();
});
// ✅ Imposta lo stato come risolto
data.value.updateExists = false;
} else {
// Se non c'è un SW in attesa, forza un semplice reload (caso raro)
console.log('[PWA] Nessun SW in attesa — ricarico comunque...');
window.location.reload();
}
}
function updateAvailable(event: any) {
console.log(event);
data.value.registration = event.detail;
data.value.updateExists = true;
RefreshApp(); // update automatically
refreshApp(); // update automatically
}
function created() {
@@ -173,16 +211,19 @@ export default defineComponent({
try {
if (window) {
// Ascolta evento custom 'swUpdated' per notifica aggiornamento
/*
window.addEventListener(
'swUpdated',
async (event) => {
async (event: any) => {
// Chiedi conferma allutente (qui con confirm, sostituisci con dialog Quasar se vuoi)
const doUpdate = confirm('È disponibile una nuova versione. Vuoi aggiornare ora?');
const doUpdate = confirm(
'È disponibile una nuova versione. Vuoi aggiornare ora?'
);
if (doUpdate) {
// Invia messaggio al service worker per skipWaiting
if (event.detail?.swWaiting) {
event.detail.swWaiting.postMessage({ action: 'skipWaiting' });
if (event.detail?.waiting) {
event.detail.waiting.postMessage({ action: 'skipWaiting' });
}
}
},
@@ -196,9 +237,10 @@ export default defineComponent({
window.location.reload();
});
}
*/
}
} catch (e) {
console.error('Err', e.message);
console.error('Error in created() function:', e.message ? e.message : e);
}
}
@@ -281,11 +323,15 @@ export default defineComponent({
});
}
function isNewVersionAvailable() {
return globalStore.isNewVersionAvailable;
}
const isNewVersionAvailable = computed(() => {
return canShowVersion.value ? globalStore.isNewVersionAvailable : false;
});
function closeAll() {
/*if (timeoutId) {
window.clearTimeout(timeoutId);
timeoutId = null;
}*/
globalStore.rightNotifOpen = false;
globalStore.rightCartOpen = false;
globalStore.rightDrawerOpen = false;
@@ -363,10 +409,14 @@ export default defineComponent({
);
watch(conndata_changed, (value, oldValue) => {
clCloudUpload.value = value.uploading_server === 1 ? 'clCloudUpload send' : 'clCloudUpload';
clCloudUpload.value = value.downloading_server === 1 ? 'clCloudUpload receive' : 'clCloudUpload';
clCloudUp_Indexeddb.value = value.uploading_indexeddb === 1 ? 'clIndexeddb send' : 'clIndexeddb';
clCloudUp_Indexeddb.value = value.downloading_indexeddb === 1 ? 'clIndexeddb receive' : 'clIndexeddb';
clCloudUpload.value =
value.uploading_server === 1 ? 'clCloudUpload send' : 'clCloudUpload';
clCloudUpload.value =
value.downloading_server === 1 ? 'clCloudUpload receive' : 'clCloudUpload';
clCloudUp_Indexeddb.value =
value.uploading_indexeddb === 1 ? 'clIndexeddb send' : 'clIndexeddb';
clCloudUp_Indexeddb.value =
value.downloading_indexeddb === 1 ? 'clIndexeddb receive' : 'clIndexeddb';
/* clCloudUpload.value = (value.uploading_server === -1) ? 'clCloudUpload error' : clCloudUpload
clCloudUpload.value = (value.downloading_server === -1) ? 'clCloudUpload error' : clCloudDownload
@@ -402,30 +452,10 @@ export default defineComponent({
*/
function RefreshApp() {
if (Platform.is.ios) {
// Unregister Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then((registrations) => {
for (const registration of registrations) {
registration.unregister();
}
});
}
// window.location.reload()
} else {
data.value.updateExists = false;
// Make sure we only send a 'skip waiting' message if the SW is waiting
if (!data.value.registration || !data.value.registration.waiting) return;
// Send message to SW to skip the waiting and activate the new SW
data.value.registration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
}
function changeIconConn() {
iconConn.value = globalStore.stateConnection === 'online' ? 'wifi' : 'wifi_off';
clIconConn.value = globalStore.stateConnection === 'online' ? 'clIconOnline' : 'clIconOffline';
clIconConn.value =
globalStore.stateConnection === 'online' ? 'clIconOnline' : 'clIconOffline';
}
function getAppVersion() {
@@ -475,6 +505,10 @@ export default defineComponent({
// Test this by running the code snippet below and then
// use the "TableOnlyView" checkbox in DevTools Network panel
setTimeout(() => {
canShowVersion.value = true;
}, 60000);
// console.log('Event LOAD')
if (window) {
window.addEventListener('load', () => {
@@ -560,11 +594,6 @@ export default defineComponent({
return 0;
}
function getcart() {
// return Products.cart
return null;
}
function toHome() {
$router.push('/');
}
@@ -581,7 +610,6 @@ export default defineComponent({
tools.setCookie('menu3oriz', globalStore.leftDrawerOpen ? '1' : '0');
}
onBeforeMount(BeforeMount);
onMounted(mounted);
@@ -608,13 +636,12 @@ export default defineComponent({
getcolormenu,
isNewVersionAvailable,
getAppVersion,
RefreshApp,
refreshApp,
changecmd,
imglogo,
getappname,
toggleanimation,
getClassColorHeader,
getcart,
getnumItemsCart,
isFacilitatore,
isZoomeri,
@@ -654,6 +681,7 @@ export default defineComponent({
clickMenu3Orizz,
isEditor,
editOn,
canShowVersion,
};
},
});

View File

@@ -38,12 +38,12 @@
<q-btn
size="md"
id="newvers"
v-if="isNewVersionAvailable() || data.updateExists"
v-if="isNewVersionAvailable || data.updateExists"
color="secondary"
rounded
icon="refresh"
class="btnNewVersShow"
@click="RefreshApp()"
@click="refreshApp()"
:label="t('notification.newVersionAvailable')"
>
</q-btn>