check fix data
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { type FC, useEffect, useState } from 'react'
|
import { type FC, useEffect, useRef, useState } from 'react'
|
||||||
import Konva from 'konva'
|
import Konva from 'konva'
|
||||||
import { clx } from '@/helpers/utils'
|
import { clx } from '@/helpers/utils'
|
||||||
import EditorSidebar from './components/EditorSidebar'
|
import EditorSidebar from './components/EditorSidebar'
|
||||||
@@ -23,33 +23,59 @@ const Editor: FC = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
||||||
const [isInitialContentReady, setIsInitialContentReady] = useState(false)
|
const [isInitialContentReady, setIsInitialContentReady] = useState(false)
|
||||||
const { loadPages, pages, documentSettings, objects, currentPageId } = useEditorStore()
|
const loadedCatalogIdRef = useRef<string | null>(null)
|
||||||
|
const skipNextAutosaveRef = useRef(true)
|
||||||
|
const { loadPages, resetEditor, pages, documentSettings, objects, currentPageId } = useEditorStore()
|
||||||
const { data, isFetched } = useGetCatalogById(id!)
|
const { data, isFetched } = useGetCatalogById(id!)
|
||||||
const { scheduleUpdate, isSaving } = useUpdateCatalog()
|
const { scheduleUpdate, cancelPendingUpdate, isSaving } = useUpdateCatalog()
|
||||||
|
|
||||||
|
// هنگام تعویض کاتالوگ: لغو ذخیرهٔ معلق، پاکسازی store، و منتظر بارگذاری مجدد بمان
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetched) return
|
cancelPendingUpdate()
|
||||||
|
loadedCatalogIdRef.current = null
|
||||||
|
skipNextAutosaveRef.current = true
|
||||||
|
setIsInitialContentReady(false)
|
||||||
|
resetEditor()
|
||||||
|
}, [id, cancelPendingUpdate, resetEditor])
|
||||||
|
|
||||||
if (data?.data?.content) {
|
// بارگذاری محتوا فقط یکبار برای هر id (نه در هر refetch)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!id || !isFetched) return
|
||||||
|
if (loadedCatalogIdRef.current === id) return
|
||||||
|
|
||||||
|
const rawContent = data?.data?.content
|
||||||
|
if (rawContent) {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(data.data.content)
|
const json = JSON.parse(rawContent)
|
||||||
if (Array.isArray(json)) {
|
if (Array.isArray(json)) {
|
||||||
loadPages(json)
|
loadPages(json)
|
||||||
} else if (json?.pages) {
|
} else if (json?.pages) {
|
||||||
loadPages(json.pages, json.documentSettings)
|
loadPages(json.pages, json.documentSettings)
|
||||||
|
} else {
|
||||||
|
console.error('Invalid catalog content JSON: missing pages')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Invalid catalog content JSON:', error)
|
console.error('Invalid catalog content JSON:', error)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resetEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadedCatalogIdRef.current = id
|
||||||
|
skipNextAutosaveRef.current = true
|
||||||
setIsInitialContentReady(true)
|
setIsInitialContentReady(true)
|
||||||
}, [data?.data?.content, isFetched, loadPages])
|
}, [id, isFetched, data?.data?.content, loadPages, resetEditor])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
if (!isInitialContentReady) return
|
if (!isInitialContentReady) return
|
||||||
if (!pages || pages.length === 0) return
|
if (!pages || pages.length === 0) return
|
||||||
|
if (skipNextAutosaveRef.current) {
|
||||||
|
skipNextAutosaveRef.current = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const pagesToSave = currentPageId
|
const pagesToSave = currentPageId
|
||||||
? pages.map((p) =>
|
? pages.map((p) =>
|
||||||
@@ -75,7 +101,6 @@ const Editor: FC = () => {
|
|||||||
<div className="fixed bottom-10 left-10 z-50 rounded-full shadow-sm">
|
<div className="fixed bottom-10 left-10 z-50 rounded-full shadow-sm">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-700">
|
<div className="flex items-center gap-2 text-sm text-gray-700">
|
||||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
|
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
|
||||||
{/* <span>در حال ذخیره ...</span> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ type EditorStoreType = {
|
|||||||
pages: Page[],
|
pages: Page[],
|
||||||
documentSettings?: Partial<DocumentSettings>,
|
documentSettings?: Partial<DocumentSettings>,
|
||||||
) => void;
|
) => void;
|
||||||
|
resetEditor: () => void;
|
||||||
clipboardObjects: EditorObject[];
|
clipboardObjects: EditorObject[];
|
||||||
objectHistoryPast: EditorObject[][];
|
objectHistoryPast: EditorObject[][];
|
||||||
objectHistoryFuture: EditorObject[][];
|
objectHistoryFuture: EditorObject[][];
|
||||||
@@ -167,6 +168,22 @@ type EditorStoreType = {
|
|||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_DOCUMENT_SETTINGS: DocumentSettings = {
|
||||||
|
displayStyle: "double",
|
||||||
|
autoPlay: false,
|
||||||
|
pageFlipSound: true,
|
||||||
|
leftToRightFlip: false,
|
||||||
|
smartGuide: true,
|
||||||
|
backgroundType: "color",
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
backgroundGradient: {
|
||||||
|
from: "#ffffff",
|
||||||
|
to: "#e2e8f0",
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
|
backgroundImageUrl: "",
|
||||||
|
};
|
||||||
|
|
||||||
export const useEditorStore = create<EditorStoreType>((set, get) => {
|
export const useEditorStore = create<EditorStoreType>((set, get) => {
|
||||||
const initialPage = createInitialPage("جلد");
|
const initialPage = createInitialPage("جلد");
|
||||||
const cloneObjectsForPaste = (
|
const cloneObjectsForPaste = (
|
||||||
@@ -209,21 +226,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
documentSettings: {
|
documentSettings: { ...DEFAULT_DOCUMENT_SETTINGS },
|
||||||
displayStyle: "double",
|
|
||||||
autoPlay: false,
|
|
||||||
pageFlipSound: true,
|
|
||||||
leftToRightFlip: false,
|
|
||||||
smartGuide: true,
|
|
||||||
backgroundType: "color",
|
|
||||||
backgroundColor: "#ffffff",
|
|
||||||
backgroundGradient: {
|
|
||||||
from: "#ffffff",
|
|
||||||
to: "#e2e8f0",
|
|
||||||
angle: 135,
|
|
||||||
},
|
|
||||||
backgroundImageUrl: "",
|
|
||||||
},
|
|
||||||
updateDocumentSettings: (updates) =>
|
updateDocumentSettings: (updates) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
documentSettings: { ...state.documentSettings, ...updates },
|
documentSettings: { ...state.documentSettings, ...updates },
|
||||||
@@ -984,6 +987,20 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
const state = get();
|
const state = get();
|
||||||
return state.objects.filter((obj) => obj.groupId === groupId);
|
return state.objects.filter((obj) => obj.groupId === groupId);
|
||||||
},
|
},
|
||||||
|
resetEditor: () => {
|
||||||
|
const page = createInitialPage("جلد");
|
||||||
|
set({
|
||||||
|
pages: [page],
|
||||||
|
currentPageId: page.id,
|
||||||
|
objects: page.objects,
|
||||||
|
guides: [],
|
||||||
|
selectedObjectId: null,
|
||||||
|
selectedObjectIds: [],
|
||||||
|
objectHistoryPast: [],
|
||||||
|
objectHistoryFuture: [],
|
||||||
|
documentSettings: { ...DEFAULT_DOCUMENT_SETTINGS },
|
||||||
|
});
|
||||||
|
},
|
||||||
loadPages: (pages, documentSettings) => {
|
loadPages: (pages, documentSettings) => {
|
||||||
if (pages.length === 0) return;
|
if (pages.length === 0) return;
|
||||||
const fallbackSettings = documentSettings ?? {};
|
const fallbackSettings = documentSettings ?? {};
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export const useGetCatalogById = (id: string) => {
|
|||||||
queryKey: ["catalog", id],
|
queryKey: ["catalog", id],
|
||||||
queryFn: () => api.getCatalogById(id),
|
queryFn: () => api.getCatalogById(id),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
|
// جلوگیری از بازنویسی state محلی با refetch هنگام فوکوس پنجره (مثلاً دو کاربر همزمان)
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,6 +41,15 @@ export const useUpdateCatalog = () => {
|
|||||||
const pendingParamsRef = useRef<Parameters<typeof api.updateCatalog>[0] | null>(null);
|
const pendingParamsRef = useRef<Parameters<typeof api.updateCatalog>[0] | null>(null);
|
||||||
const [isScheduledSaving, setIsScheduledSaving] = useState(false);
|
const [isScheduledSaving, setIsScheduledSaving] = useState(false);
|
||||||
|
|
||||||
|
const cancelPendingUpdate = useCallback(() => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
timeoutRef.current = null;
|
||||||
|
}
|
||||||
|
pendingParamsRef.current = null;
|
||||||
|
setIsScheduledSaving(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const scheduleUpdate = useCallback(
|
const scheduleUpdate = useCallback(
|
||||||
(params: Parameters<typeof api.updateCatalog>[0], delayMs = 3000) => {
|
(params: Parameters<typeof api.updateCatalog>[0], delayMs = 3000) => {
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
@@ -79,6 +91,7 @@ export const useUpdateCatalog = () => {
|
|||||||
return {
|
return {
|
||||||
...mutation,
|
...mutation,
|
||||||
scheduleUpdate,
|
scheduleUpdate,
|
||||||
|
cancelPendingUpdate,
|
||||||
isSaving: isScheduledSaving || mutation.isPending,
|
isSaving: isScheduledSaving || mutation.isPending,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user