195 lines
5.6 KiB
TypeScript
195 lines
5.6 KiB
TypeScript
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<IMyPage>({ ...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,
|
|
};
|
|
},
|
|
});
|