import { defineComponent, ref, computed, onMounted, watch, onBeforeUnmount, toRaw, nextTick, } from 'vue'; import { useUserStore } from '@store/UserStore'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; import { tools } from '@tools'; import { useRouter } from 'vue-router'; import { reactive } from 'vue'; import { IMyPage } from 'app/src/model'; import IconPicker from '../IconPicker/IconPicker.vue'; import { useGlobalStore } from 'app/src/store'; import { storeToRefs } from 'pinia'; export default defineComponent({ name: 'PageEditor', components: { IconPicker }, props: { modelValue: { type: Object as () => IMyPage, required: true, }, }, emits: ['update:modelValue', 'apply'], setup(props, { emit }) { const $q = useQuasar(); const globalStore = useGlobalStore(); const { mypage } = storeToRefs(globalStore); // DRaft locale const draft = reactive({ ...props.modelValue }); // UI helper: path mostrato con "/" iniziale const ui = reactive({ pathText: toUiPath(draft.path), }); const saving = ref(false) const syncingFromProps = ref(false) // <-- FLAG anti-loop // --- Watch input esterno: ricarica draft e UI path // --- Watch input esterno: ricarica draft e UI path (NO scritture nello store qui!) watch( () => props.modelValue, async (v) => { syncingFromProps.value = true; Object.assign(draft, v || {}); ui.pathText = toUiPath(draft.path); await nextTick(); syncingFromProps.value = false; }, { deep: false } ); // --- Ogni modifica del draft: aggiorna store.mypage e emetti update:modelValue (solo se modifica nasce da UI) watch( draft, (val) => { if (syncingFromProps.value) return; // evita ricorsione upsertIntoStore(val, mypage.value); emit('update:modelValue', { ...val }); }, { deep: true } ); // --- Helpers path function toUiPath(storePath?: string) { const p = (storePath || '').trim(); if (!p) return '/'; return p.startsWith('/') ? p : `/${p}`; } function toStorePath(uiPath?: string) { const p = (uiPath || '').trim(); if (!p) return ''; return p.startsWith('/') ? p.slice(1) : p; } function normalizeAndApplyPath() { // normalizza: niente spazi, minuscole, trattini let p = (ui.pathText || '/').trim(); p = p.replace(/\s+/g, '-'); if (!p.startsWith('/')) p = '/' + p; ui.pathText = p; draft.path = toStorePath(p); } function pathRule(v: string) { if (!v) return 'Percorso richiesto'; if (!v.startsWith('/')) return 'Deve iniziare con /'; if (/\s/.test(v)) return 'Nessuno spazio nel path'; return true; } // --- Upsert nello store.mypage function upsertIntoStore(page: IMyPage, arr: IMyPage[]) { if (!page) return; // chiave di matching: prima _id, altrimenti path const keyId = page._id; const keyPath = page.path || ''; let idx = -1; if (keyId) { idx = arr.findIndex((p) => p._id === keyId); } if (idx < 0 && keyPath) { idx = arr.findIndex((p) => (p.path || '') === keyPath); } if (idx >= 0) { // merge preservando reattività arr[idx] = { ...arr[idx], ...toRaw(page) }; } else { arr.push({ ...toRaw(page) }); } } async function save () { try { saving.value = true normalizeAndApplyPath() // assicura path coerente const payload: IMyPage = { ...toRaw(draft), path: draft.path || '' } const saved = await globalStore.savePage(payload) if (saved && typeof saved === 'object') { syncingFromProps.value = true Object.assign(draft, saved) upsertIntoStore(draft, mypage.value) await nextTick() syncingFromProps.value = false } emit('apply', { ...draft }) $q.notify({ type: 'positive', message: 'Pagina salvata' }) } catch (err: any) { console.error(err) $q.notify({ type: 'negative', message: 'Errore nel salvataggio' }) } finally { saving.value = false } } // --- Ricarica da sorgente async function reloadFromStore () { try { const absolute = ui.pathText || '/' const page = await globalStore.loadPage(absolute, '', true) if (page) { syncingFromProps.value = true Object.assign(draft, page) ui.pathText = toUiPath(draft.path) upsertIntoStore(draft, mypage.value) console.log('page', draft) emit('update:modelValue', { ...draft }) await nextTick() syncingFromProps.value = false $q.notify({ type: 'info', message: 'Pagina ricaricata' }) } else { $q.notify({ type: 'warning', message: 'Pagina non trovata' }) } } catch (err) { console.error(err) $q.notify({ type: 'negative', message: 'Errore nel ricaricare la pagina' }) } } function resetDraft() { console.log('resetDraft') syncingFromProps.value = true Object.assign(draft, props.modelValue || {}) ui.pathText = toUiPath(draft.path) // aggiorna i componenti emit('update:modelValue', { ...draft }) nextTick(() => { syncingFromProps.value = false }) } const absolutePath = computed(() => toUiPath(draft.path)) return { draft, ui, saving, pathRule, normalizeAndApplyPath, save, reloadFromStore, resetDraft, absolutePath, }; }, });