-
This commit is contained in:
@@ -178,6 +178,9 @@ export default function ReportEditorPage() {
|
||||
// Auto-save feature - enabled by default
|
||||
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
|
||||
|
||||
// Template version for collaboration sync (increments on each save)
|
||||
const templateVersionRef = useRef(0);
|
||||
|
||||
// ============ COLLABORATION (using global context) ============
|
||||
// Room key format: "report-template:{id}"
|
||||
const roomKey = id ? `report-template:${id}` : null;
|
||||
@@ -315,19 +318,68 @@ export default function ReportEditorPage() {
|
||||
}),
|
||||
);
|
||||
|
||||
// Template saved by remote user
|
||||
// Template sync received - apply template directly without server reload
|
||||
unsubscribers.push(
|
||||
collaboration.onTemplateSync((message) => {
|
||||
console.log(
|
||||
`[Collaboration] Received TemplateSync, version ${message.version}, size: ${message.templateJson.length} bytes`,
|
||||
);
|
||||
|
||||
// Only apply if the received version is newer
|
||||
if (message.version <= templateVersionRef.current) {
|
||||
console.log(
|
||||
`[Collaboration] Ignoring older template version ${message.version} (current: ${templateVersionRef.current})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
isApplyingRemoteChange.current = true;
|
||||
const receivedTemplate = JSON.parse(
|
||||
message.templateJson,
|
||||
) as AprtTemplate;
|
||||
|
||||
// Update version to received version
|
||||
templateVersionRef.current = message.version;
|
||||
|
||||
// Apply template without adding to history (it's a sync, not a user action)
|
||||
historyActions.setWithoutHistory(() => receivedTemplate);
|
||||
|
||||
// Show notification
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: "Template aggiornato da un altro utente",
|
||||
severity: "success",
|
||||
});
|
||||
|
||||
// Also update the query cache so it stays in sync
|
||||
queryClient.setQueryData(["report-template", id], (old: unknown) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...(old as Record<string, unknown>),
|
||||
templateJson: message.templateJson,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("[Collaboration] Error parsing received template:", e);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isApplyingRemoteChange.current = false;
|
||||
}, 0);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// Legacy DataSaved handler (for backwards compatibility - will be removed)
|
||||
unsubscribers.push(
|
||||
collaboration.onDataSaved((message) => {
|
||||
console.log(
|
||||
"[Collaboration] Received DataSaved from:",
|
||||
message.savedBy,
|
||||
"(legacy - should use TemplateSync instead)",
|
||||
);
|
||||
setSnackbar({
|
||||
open: true,
|
||||
message: `${message.savedBy} ha salvato il template`,
|
||||
severity: "success",
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["report-template", id] });
|
||||
// Only reload from server if we haven't received a TemplateSync
|
||||
// This is a fallback for older clients
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -529,16 +581,30 @@ export default function ReportEditorPage() {
|
||||
// Mark current state as saved
|
||||
setLastSavedUndoCount(templateHistory.undoCount);
|
||||
|
||||
// Notify collaborators of save
|
||||
console.log(
|
||||
"[AutoSave] Save success, collaboration.isConnected:",
|
||||
collaboration.isConnected,
|
||||
"currentRoom:",
|
||||
collaboration.currentRoom,
|
||||
);
|
||||
// Broadcast template to collaborators (instant sync without server reload)
|
||||
if (collaboration.isConnected && collaboration.currentRoom) {
|
||||
console.log("[AutoSave] Sending DataSaved notification");
|
||||
collaboration.sendDataSaved();
|
||||
// Update dataSources in template for broadcasting
|
||||
const updatedTemplateForSync = {
|
||||
...template,
|
||||
dataSources: selectedDatasets.reduce(
|
||||
(acc, ds) => {
|
||||
acc[ds.id] = { type: "object" as const, schema: ds.id };
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, { type: "object"; schema: string }>,
|
||||
),
|
||||
};
|
||||
|
||||
// Increment version and broadcast
|
||||
templateVersionRef.current += 1;
|
||||
const templateJson = JSON.stringify(updatedTemplateForSync);
|
||||
console.log(
|
||||
`[AutoSave] Broadcasting template sync, version ${templateVersionRef.current}, size: ${templateJson.length} bytes`,
|
||||
);
|
||||
collaboration.broadcastTemplateSync(
|
||||
templateJson,
|
||||
templateVersionRef.current,
|
||||
);
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
@@ -1452,38 +1518,49 @@ export default function ReportEditorPage() {
|
||||
selectedElementId,
|
||||
]);
|
||||
|
||||
// Auto-save effect - saves after 1 second of inactivity when there are unsaved changes
|
||||
// Use refs to avoid the effect re-running on every render due to saveMutation changing
|
||||
// Auto-save: simple debounced save on every template change
|
||||
const autoSaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const saveMutationRef = useRef(saveMutation);
|
||||
saveMutationRef.current = saveMutation;
|
||||
|
||||
const templateRef = useRef(template);
|
||||
templateRef.current = template;
|
||||
|
||||
const templateInfoRef = useRef(templateInfo);
|
||||
templateInfoRef.current = templateInfo;
|
||||
const templateForSaveRef = useRef(template);
|
||||
templateForSaveRef.current = template;
|
||||
const templateInfoForSaveRef = useRef(templateInfo);
|
||||
templateInfoForSaveRef.current = templateInfo;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!autoSaveEnabled ||
|
||||
!hasUnsavedChanges ||
|
||||
isNew ||
|
||||
saveMutationRef.current.isPending
|
||||
) {
|
||||
// Skip if disabled or new template
|
||||
if (!autoSaveEnabled || isNew) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
// Skip if no unsaved changes
|
||||
if (!hasUnsavedChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear previous timeout (debounce)
|
||||
if (autoSaveTimeoutRef.current) {
|
||||
clearTimeout(autoSaveTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Save after 500ms debounce
|
||||
autoSaveTimeoutRef.current = setTimeout(() => {
|
||||
// Check if not already saving at the moment of execution
|
||||
if (!saveMutationRef.current.isPending) {
|
||||
console.log("[AutoSave] Saving...");
|
||||
saveMutationRef.current.mutate({
|
||||
template: templateRef.current,
|
||||
info: templateInfoRef.current,
|
||||
template: templateForSaveRef.current,
|
||||
info: templateInfoForSaveRef.current,
|
||||
});
|
||||
}
|
||||
}, 1000); // 1 second debounce
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [autoSaveEnabled, hasUnsavedChanges, isNew]);
|
||||
return () => {
|
||||
if (autoSaveTimeoutRef.current) {
|
||||
clearTimeout(autoSaveTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [autoSaveEnabled, isNew, hasUnsavedChanges, templateHistory.undoCount]);
|
||||
|
||||
if (isLoadingTemplate && id) {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user