- creato editor di Pagine (iniziato)

- fix: mancano i "t," su alcuni componenti...
This commit is contained in:
Surya Paolo
2025-09-02 16:22:13 +02:00
parent 4f4297ca21
commit 574f389200
324 changed files with 5838 additions and 759 deletions

View File

@@ -0,0 +1,194 @@
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,
};
},
});

View File

@@ -0,0 +1,73 @@
<template>
<q-card flat bordered class="q-pa-md">
<div class="row q-col-gutter-md">
<div class="col-12 col-md-6">
<q-input
v-model="ui.pathText"
label="Percorso (relativo, es: /about)"
dense
:rules="[pathRule]"
@blur="normalizeAndApplyPath"
>
<template #prepend>
<q-icon name="fas fa-link" />
</template>
</q-input>
</div>
<div class="col-12 col-md-6">
<q-input
v-model="draft.title"
label="Titolo"
dense
:rules="[v => !!v || 'Titolo richiesto']"
/>
</div>
<div class="col-12 col-md-6">
<icon-picker v-model="draft.icon" />
</div>
<div class="col-12 col-md-6">
<q-input v-model="draft.iconsize" label="Dimensione icona (es: 24px)" dense />
</div>
<div class="col-12">
<q-separator spaced />
<div class="row items-center q-col-gutter-md">
<div class="col-auto">
<q-toggle v-model="draft.active" label="Attivo" />
</div>
<div class="col-auto">
<q-toggle v-model="draft.inmenu" label="Presente nel menu" />
</div>
<div class="col-auto">
<q-toggle v-model="draft.onlyif_logged" label="Solo se loggati" />
</div>
</div>
</div>
</div>
<div class="row q-col-gutter-sm q-mt-md">
<div class="col-auto">
<q-btn color="primary" label="Salva" :loading="saving" @click="save" />
</div>
<div class="col-auto">
<q-btn outline label="Ricarica" @click="reloadFromStore" />
</div>
<div class="col-auto">
<q-btn color="grey-7" label="Chiudi" @click="$emit('close', draft.path)" />
</div>
<div class="col-auto">
<q-btn flat color="grey-7" label="Reset draft" @click="resetDraft" />
</div>
</div>
</q-card>
</template>
<script lang="ts" src="./PageEditor.ts">
</script>
<style lang="scss" scoped>
@import './PageEditor.scss';
</style>

View File

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