Composables
useSpreadsheet
Composable principale dell'editor. Gestisce load/save snapshot, lock pessimistico con heartbeat, autosave, undo/redo e cella API.
Signature
function useSpreadsheet(options: UseSpreadsheetOptions): UseSpreadsheetReturn
Composable centrale dell'editor. Orchestra:
- carica/salva il foglio (metadati + snapshot)
- acquisisce/rilascia/heartbeat del lock pessimistico
- gestisce
FortuneSheetAdapterper interazione con il workbook - traccia stato dirty + autosave opzionale
- espone API celle (
getCellValue/setCellValue)
Opzioni
interface UseSpreadsheetOptions {
foglioId: Ref<number | null>
mode: Ref<'edit' | 'view'>
autosaveInterval?: Ref<number | null> // secondi
postgrestUrlOverride?: Ref<string | null> // override URL puntuale
onSave?: (event: SpreadsheetSaveEvent) => void
onError?: (event: SpreadsheetErrorEvent) => void
}
| Opzione | Tipo | Descrizione |
|---|---|---|
foglioId | Ref<number | null> | ID foglio reattivo. Cambio → load() automatico |
mode | Ref<'edit' | 'view'> | Modalità reattiva. view forza read-only |
autosaveInterval | Ref<number | null> | Secondi. Se valorizzato, setInterval(save, n*1000) |
postgrestUrlOverride | Ref<string | null> | Override URL PostgREST (raramente usato) |
onSave | callback | Notifica salvataggio riuscito |
onError | callback | Notifica errori (LOAD/SAVE/LOCK/ACL/...) |
Risultato
interface UseSpreadsheetReturn {
state: Ref<FoglioState>
snapshot: Ref<FortuneSheetSnapshot | null>
isSaving: Ref<boolean>
isLoading: Ref<boolean>
isDirty: Ref<boolean>
error: Ref<SpreadsheetErrorEvent | null>
lockInfo: Ref<LockInfo | null>
isReadOnly: ComputedRef<boolean>
canSave: ComputedRef<boolean>
isLockOwner: ComputedRef<boolean>
load(idfolio: number): Promise<void>
save(): Promise<SpreadsheetSaveEvent>
markDirty(): void
setSnapshot(snapshot: FortuneSheetSnapshot): void
acquisisciLock(): Promise<AcquisisciLockResult>
rilasciaLock(): Promise<void>
exportXlsx(filename?: string): Promise<void>
undo(): void
redo(): void
getCellValue(row: number, col: number, sheet?: number): CellValue
setCellValue(row: number, col: number, value: CellValue, sheet?: number): void
adapter: FortuneSheetAdapter
}
Computed
isReadOnly = computed(() =>
mode.value === 'view' || state.permessoEffettivo === 'lettura'
)
canSave = computed(() =>
isDirty.value && !isReadOnly.value && !isSaving.value
)
isLockOwner = computed(() =>
lockInfo.value !== null && !lockInfo.value.islocked
)
isLockOwner è true quando lockInfo è valorizzato ma islocked === false — significa "noi abbiamo il lock attivo, nessun altro lo blocca". È un'invariante del DB: chi possiede il lock vede islocked === false, chi non lo possiede vede islocked === true.Lifecycle interno
new useSpreadsheet({ foglioId, mode, ... })
├─ Istanzia: FogliPostgrestService + LockService + FortuneSheetAdapter
└─ Inizializza state, refs e watchers
load(idfolio)
├─ state.isLoading = true
├─ FogliPostgrestService.caricaFoglio(idfolio)
│ → metadati: nome, permessoEffettivo, filtriGlobali, sorgentierp, lock
├─ FogliPostgrestService.caricaUltimaVersione(idfolio)
│ → ultimo snapshot + numerorevisione
├─ adapter.loadSnapshot(snapshot)
├─ Se mode='edit' && permesso !== 'lettura':
│ ├─ LockService.acquisisciLock(idfolio)
│ │ ├─ successo: lockInfo aggiornato, heartbeat avviato
│ │ └─ LOCK_OCCUPIED: state.isReadOnly forzato (UI)
│ └─ Avvia heartbeatInterval (5 min) → LockService.rinnovaLock
├─ Se autosaveInterval valorizzato: setInterval(save, n*1000)
└─ state.isLoading = false
save()
├─ state.isSaving = true
├─ snapshot = adapter.getSnapshot()
├─ numerorevisione = (state.ultimaRevisione ?? 0) + 1
├─ FogliPostgrestService.salvaVersione({ idfolio, numerorevisione, snapshot })
├─ state.ultimaRevisione = numerorevisione
├─ state.isDirty = false
├─ state.lastSavedAt = new Date()
└─ onSave?({ idfolio, nome, revisione, timestamp })
onUnmounted (gestito esternamente da SpreadsheetEditor)
├─ clearInterval(heartbeat)
├─ clearInterval(autosave)
└─ rilasciaLock() (se isLockOwner)
Lock pessimistico
const result = await spreadsheet.acquisisciLock()
if (result.successo === false && result.errore === 'LOCK_OCCUPIED') {
console.warn('Foglio bloccato da altro utente:', result)
}
Vedi LockService per dettagli sulle RPC.
Autosave
Se autosaveInterval.value === 60:
setInterval(() => {
if (canSave.value) await save()
}, 60_000)
L'autosave non chiama save() se isDirty === false, isSaving === true, o isReadOnly === true.
API celle
spreadsheet.getCellValue(0, 0, 0) // → CellValue (string | number | boolean | null)
spreadsheet.setCellValue(0, 0, 'Ciao', 0)
spreadsheet.setCellValue(0, 1, 42)
Wrapper dell'adapter, comodi quando vuoi scriptare il foglio da fuori.
Undo/Redo
spreadsheet.undo() // adapter.undo()
spreadsheet.redo() // adapter.redo()
L'adapter espone anche canUndo() / canRedo() per disabilitare UI. Se vuoi usarli, accedi direttamente all'adapter:
if (spreadsheet.adapter.canUndo()) {
spreadsheet.undo()
}
markDirty() e onChange
L'adapter.onChange() è registrato esternamente (da SpreadsheetEditor) e chiama spreadsheet.markDirty() quando cambia una cella. Da quel momento isDirty.value === true finché save() non lo riporta a false.
Esempio: editor minimale custom
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useSpreadsheet } from '@pzeta/vue-foglidicalcolo'
const props = defineProps<{ foglioId: number }>()
const container = ref<HTMLElement | null>(null)
const foglioId = ref<number | null>(props.foglioId)
const mode = ref<'edit' | 'view'>('edit')
const spreadsheet = useSpreadsheet({
foglioId,
mode,
autosaveInterval: ref(60),
onSave: (e) => console.log('saved rev.', e.revisione),
onError: (e) => alert(`Errore: ${e.message}`),
})
onMounted(async () => {
if (container.value) {
await spreadsheet.adapter.initialize(container.value)
spreadsheet.adapter.onChange(() => spreadsheet.markDirty())
await spreadsheet.load(props.foglioId)
}
})
onUnmounted(() => {
spreadsheet.adapter.offChange()
spreadsheet.adapter.destroy()
})
</script>
<template>
<div ref="container" style="height: 600px; width: 100%" />
<button :disabled="!spreadsheet.canSave.value" @click="spreadsheet.save()">
Salva
</button>
</template>
Tipi correlati
FoglioStateFortuneSheetSnapshot— blob opacoLockInfoFortuneSheetAdapterSpreadsheetEditor— consumatore principale