This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import GridWrapper from "@/components/GridWrapper";
|
||||
import { usePageTitle } from "@/hooks/usePageTitle";
|
||||
import { type FC } from "react";
|
||||
import { useGetCatalog } from "../home/hooks/useHomeData";
|
||||
import ButtonAddCatalogue from "./components/ButtonAddCatalogue";
|
||||
import CatalogueItem from "./components/CatalogueItem";
|
||||
|
||||
const CatalogueList: FC = () => {
|
||||
usePageTitle("لیست کاتالوگها");
|
||||
const { data, refetch } = useGetCatalog();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { usePageTitle } from '@/hooks/usePageTitle'
|
||||
import { useMemo, useState, type FC } from 'react'
|
||||
import Button from '@/components/Button'
|
||||
import DatePicker from '@/components/DatePicker'
|
||||
@@ -17,6 +18,7 @@ const pageCountOptions = Array.from({ length: 20 }, (_, index) => {
|
||||
})
|
||||
|
||||
const DesignerRequest: FC = () => {
|
||||
usePageTitle('درخواست طراحی')
|
||||
const [catalogTitle, setCatalogTitle] = useState('')
|
||||
const [, setDeliveryDate] = useState('')
|
||||
const [pageCount, setPageCount] = useState('1')
|
||||
|
||||
+126
-103
@@ -1,118 +1,141 @@
|
||||
import { type FC, useEffect, useRef, useState } from 'react'
|
||||
import Konva from 'konva'
|
||||
import { clx } from '@/helpers/utils'
|
||||
import EditorSidebar from './components/EditorSidebar'
|
||||
import EditorCanvas from './components/EditorCanvas'
|
||||
import LayersPanel from './components/LayersPanel'
|
||||
import { useGetCatalogById, useUpdateCatalog } from '../home/hooks/useHomeData'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useEditorStore } from './store/editorStore'
|
||||
import { clx } from "@/helpers/utils";
|
||||
import { usePageTitle } from "@/hooks/usePageTitle";
|
||||
import Konva from "konva";
|
||||
import { type FC, useEffect, useRef, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useGetCatalogById, useUpdateCatalog } from "../home/hooks/useHomeData";
|
||||
import EditorCanvas from "./components/EditorCanvas";
|
||||
import EditorSidebar from "./components/EditorSidebar";
|
||||
import LayersPanel from "./components/LayersPanel";
|
||||
import { useEditorStore } from "./store/editorStore";
|
||||
|
||||
const Editor: FC = () => {
|
||||
const { id } = useParams();
|
||||
|
||||
const { id } = useParams()
|
||||
// Konva 10 با legacyTextRendering=false متن را با baseline جدید میکشد ولی getWidth/getHeight
|
||||
// و کادر ترنسفورمر هنوز بر اساس جعبهٔ قدیمیاند؛ نتیجه: متن از کادر آبی بیرون میزند.
|
||||
useEffect(() => {
|
||||
const prev = Konva.legacyTextRendering;
|
||||
Konva.legacyTextRendering = true;
|
||||
return () => {
|
||||
Konva.legacyTextRendering = prev;
|
||||
};
|
||||
}, []);
|
||||
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false);
|
||||
const [isInitialContentReady, setIsInitialContentReady] = useState(false);
|
||||
const loadedCatalogIdRef = useRef<string | null>(null);
|
||||
const skipNextAutosaveRef = useRef(true);
|
||||
const {
|
||||
loadPages,
|
||||
resetEditor,
|
||||
pages,
|
||||
documentSettings,
|
||||
gallery,
|
||||
objects,
|
||||
currentPageId,
|
||||
} = useEditorStore();
|
||||
const { data, isFetched } = useGetCatalogById(id!);
|
||||
const catalogName = data?.data?.name;
|
||||
usePageTitle(
|
||||
catalogName ? `ویرایش کاتالوگ ${catalogName}` : "ویرایش کاتالوگ",
|
||||
);
|
||||
const { scheduleUpdate, cancelPendingUpdate, isSaving } = useUpdateCatalog();
|
||||
|
||||
// Konva 10 با legacyTextRendering=false متن را با baseline جدید میکشد ولی getWidth/getHeight
|
||||
// و کادر ترنسفورمر هنوز بر اساس جعبهٔ قدیمیاند؛ نتیجه: متن از کادر آبی بیرون میزند.
|
||||
useEffect(() => {
|
||||
const prev = Konva.legacyTextRendering
|
||||
Konva.legacyTextRendering = true
|
||||
return () => {
|
||||
Konva.legacyTextRendering = prev
|
||||
}
|
||||
}, [])
|
||||
const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false)
|
||||
const [isInitialContentReady, setIsInitialContentReady] = useState(false)
|
||||
const loadedCatalogIdRef = useRef<string | null>(null)
|
||||
const skipNextAutosaveRef = useRef(true)
|
||||
const { loadPages, resetEditor, pages, documentSettings, gallery, objects, currentPageId } = useEditorStore()
|
||||
const { data, isFetched } = useGetCatalogById(id!)
|
||||
const { scheduleUpdate, cancelPendingUpdate, isSaving } = useUpdateCatalog()
|
||||
// هنگام تعویض کاتالوگ: لغو ذخیرهٔ معلق، پاکسازی store، و منتظر بارگذاری مجدد بمان
|
||||
useEffect(() => {
|
||||
cancelPendingUpdate();
|
||||
loadedCatalogIdRef.current = null;
|
||||
skipNextAutosaveRef.current = true;
|
||||
setIsInitialContentReady(false);
|
||||
resetEditor();
|
||||
}, [id, cancelPendingUpdate, resetEditor]);
|
||||
|
||||
// هنگام تعویض کاتالوگ: لغو ذخیرهٔ معلق، پاکسازی store، و منتظر بارگذاری مجدد بمان
|
||||
useEffect(() => {
|
||||
cancelPendingUpdate()
|
||||
loadedCatalogIdRef.current = null
|
||||
skipNextAutosaveRef.current = true
|
||||
setIsInitialContentReady(false)
|
||||
resetEditor()
|
||||
}, [id, cancelPendingUpdate, resetEditor])
|
||||
// بارگذاری محتوا فقط یکبار برای هر id (نه در هر refetch)
|
||||
useEffect(() => {
|
||||
if (!id || !isFetched) return;
|
||||
if (loadedCatalogIdRef.current === id) return;
|
||||
|
||||
// بارگذاری محتوا فقط یکبار برای هر id (نه در هر refetch)
|
||||
useEffect(() => {
|
||||
if (!id || !isFetched) return
|
||||
if (loadedCatalogIdRef.current === id) return
|
||||
|
||||
const rawContent = data?.data?.content
|
||||
if (rawContent) {
|
||||
try {
|
||||
const json = JSON.parse(rawContent)
|
||||
if (Array.isArray(json)) {
|
||||
loadPages(json)
|
||||
} else if (json?.pages) {
|
||||
loadPages(json.pages, json.documentSettings, json.gallery)
|
||||
} else {
|
||||
console.error('Invalid catalog content JSON: missing pages')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Invalid catalog content JSON:', error)
|
||||
return
|
||||
}
|
||||
const rawContent = data?.data?.content;
|
||||
if (rawContent) {
|
||||
try {
|
||||
const json = JSON.parse(rawContent);
|
||||
if (Array.isArray(json)) {
|
||||
loadPages(json);
|
||||
} else if (json?.pages) {
|
||||
loadPages(json.pages, json.documentSettings, json.gallery);
|
||||
} else {
|
||||
resetEditor()
|
||||
console.error("Invalid catalog content JSON: missing pages");
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Invalid catalog content JSON:", error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
resetEditor();
|
||||
}
|
||||
|
||||
loadedCatalogIdRef.current = id
|
||||
skipNextAutosaveRef.current = true
|
||||
setIsInitialContentReady(true)
|
||||
}, [id, isFetched, data?.data?.content, loadPages, resetEditor])
|
||||
loadedCatalogIdRef.current = id;
|
||||
skipNextAutosaveRef.current = true;
|
||||
setIsInitialContentReady(true);
|
||||
}, [id, isFetched, data?.data?.content, loadPages, resetEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return
|
||||
if (!isInitialContentReady) return
|
||||
if (!pages || pages.length === 0) return
|
||||
if (skipNextAutosaveRef.current) {
|
||||
skipNextAutosaveRef.current = false
|
||||
return
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
if (!isInitialContentReady) return;
|
||||
if (!pages || pages.length === 0) return;
|
||||
if (skipNextAutosaveRef.current) {
|
||||
skipNextAutosaveRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const pagesToSave = currentPageId
|
||||
? pages.map((p) =>
|
||||
p.id === currentPageId ? { ...p, objects } : p,
|
||||
)
|
||||
: pages
|
||||
const pagesToSave = currentPageId
|
||||
? pages.map((p) => (p.id === currentPageId ? { ...p, objects } : p))
|
||||
: pages;
|
||||
|
||||
scheduleUpdate({
|
||||
id,
|
||||
content: JSON.stringify({ pages: pagesToSave, documentSettings, gallery }),
|
||||
})
|
||||
// عمداً scheduleUpdate را در وابستگیها نیاوردیم تا از لوپ ذخیرهسازی جلوگیری شود
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, isInitialContentReady, pages, documentSettings, gallery, objects, currentPageId])
|
||||
scheduleUpdate({
|
||||
id,
|
||||
content: JSON.stringify({
|
||||
pages: pagesToSave,
|
||||
documentSettings,
|
||||
gallery,
|
||||
}),
|
||||
});
|
||||
// عمداً scheduleUpdate را در وابستگیها نیاوردیم تا از لوپ ذخیرهسازی جلوگیری شود
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
id,
|
||||
isInitialContentReady,
|
||||
pages,
|
||||
documentSettings,
|
||||
gallery,
|
||||
objects,
|
||||
currentPageId,
|
||||
]);
|
||||
|
||||
|
||||
return (
|
||||
<div className={clx(
|
||||
"flex h-full w-full",
|
||||
isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]"
|
||||
)}>
|
||||
{isSaving ? (
|
||||
<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">
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<LayersPanel
|
||||
isOpen={isLayersPanelOpen}
|
||||
setIsOpen={setIsLayersPanelOpen}
|
||||
catalogSize={data?.data?.size}
|
||||
/>
|
||||
<EditorCanvas catalogSize={data?.data?.size} />
|
||||
<EditorSidebar catalogSize={data?.data?.size} />
|
||||
return (
|
||||
<div
|
||||
className={clx(
|
||||
"flex h-full w-full",
|
||||
isLayersPanelOpen ? "xl:pl-[296px]" : "xl:pl-[80px]",
|
||||
)}
|
||||
>
|
||||
{isSaving ? (
|
||||
<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">
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-black" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
) : null}
|
||||
<LayersPanel
|
||||
isOpen={isLayersPanelOpen}
|
||||
setIsOpen={setIsLayersPanelOpen}
|
||||
catalogSize={data?.data?.size}
|
||||
/>
|
||||
<EditorCanvas catalogSize={data?.data?.size} />
|
||||
<EditorSidebar catalogSize={data?.data?.size} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Editor
|
||||
export default Editor;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Divider from '@/components/Divider'
|
||||
import { usePageTitle } from '@/hooks/usePageTitle'
|
||||
import { ArrowLeft, DocumentText } from 'iconsax-react'
|
||||
import { useState, type FC } from 'react'
|
||||
import PdfIcon from '@/assets/images/PDF.svg'
|
||||
@@ -18,6 +19,7 @@ const CATALOG_SIZES = [
|
||||
] as const
|
||||
|
||||
const Home: FC = () => {
|
||||
usePageTitle('ساخت کاتالوگ')
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [active, setActive] = useState<string>('a4')
|
||||
|
||||
+79
-71
@@ -1,81 +1,89 @@
|
||||
import { type FC, useEffect, useState } from 'react';
|
||||
import BookViewer from './components/BookViewer';
|
||||
import ViewerLoading from './components/ViewerLoading';
|
||||
import { transformViewerDataToPages } from './utils/dataTransformer';
|
||||
import type { PageData } from './types';
|
||||
import type { DocumentSettings } from '@/pages/editor/store/editorStore';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useGetCatalogById } from '@/pages/home/hooks/useHomeData';
|
||||
import { useBrowserFullscreen } from './hooks/useBrowserFullscreen';
|
||||
import { useInitialPagesPreload } from './hooks/useInitialPagesPreload';
|
||||
import { usePageTitle } from "@/hooks/usePageTitle";
|
||||
import type { DocumentSettings } from "@/pages/editor/store/editorStore";
|
||||
import { useGetCatalogById } from "@/pages/home/hooks/useHomeData";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import BookViewer from "./components/BookViewer";
|
||||
import ViewerLoading from "./components/ViewerLoading";
|
||||
import { useBrowserFullscreen } from "./hooks/useBrowserFullscreen";
|
||||
import { useInitialPagesPreload } from "./hooks/useInitialPagesPreload";
|
||||
import type { PageData } from "./types";
|
||||
import { transformViewerDataToPages } from "./utils/dataTransformer";
|
||||
|
||||
const Viewer: FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { data, isLoading, error: queryError } = useGetCatalogById(id ?? '');
|
||||
const [pages, setPages] = useState<PageData[]>([]);
|
||||
const [documentSettings, setDocumentSettings] = useState<Partial<DocumentSettings> | undefined>(undefined);
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { data, isLoading, error: queryError } = useGetCatalogById(id ?? "");
|
||||
usePageTitle(`کاتالوگ ${data?.data?.name}`);
|
||||
const [pages, setPages] = useState<PageData[]>([]);
|
||||
const [documentSettings, setDocumentSettings] = useState<
|
||||
Partial<DocumentSettings> | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data?.data?.content || !id) return;
|
||||
try {
|
||||
const parsed = JSON.parse(data.data.content);
|
||||
const pagesArray = Array.isArray(parsed) ? parsed : (parsed.pages ?? []);
|
||||
const settings: Partial<DocumentSettings> | undefined = Array.isArray(parsed)
|
||||
? undefined
|
||||
: parsed.documentSettings;
|
||||
const transformedPages = transformViewerDataToPages(
|
||||
{ pages: pagesArray } as Parameters<typeof transformViewerDataToPages>[0]
|
||||
);
|
||||
setPages(transformedPages);
|
||||
setDocumentSettings(settings);
|
||||
} catch (err) {
|
||||
console.error('خطا در پردازش محتوای کاتالوگ:', err);
|
||||
}
|
||||
}, [data?.data?.content, id]);
|
||||
|
||||
useBrowserFullscreen({ enabled: Boolean(id) });
|
||||
const { isReady: areInitialPagesReady, progress: preloadProgress } = useInitialPagesPreload(
|
||||
pages,
|
||||
documentSettings,
|
||||
);
|
||||
|
||||
if (!id) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-gray-600">شناسه کاتالوگ مشخص نیست</div>
|
||||
</div>
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!data?.data?.content || !id) return;
|
||||
try {
|
||||
const parsed = JSON.parse(data.data.content);
|
||||
const pagesArray = Array.isArray(parsed) ? parsed : (parsed.pages ?? []);
|
||||
const settings: Partial<DocumentSettings> | undefined = Array.isArray(
|
||||
parsed,
|
||||
)
|
||||
? undefined
|
||||
: parsed.documentSettings;
|
||||
const transformedPages = transformViewerDataToPages({
|
||||
pages: pagesArray,
|
||||
} as Parameters<typeof transformViewerDataToPages>[0]);
|
||||
setPages(transformedPages);
|
||||
setDocumentSettings(settings);
|
||||
} catch (err) {
|
||||
console.error("خطا در پردازش محتوای کاتالوگ:", err);
|
||||
}
|
||||
}, [data?.data?.content, id]);
|
||||
|
||||
if (isLoading) {
|
||||
return <ViewerLoading />;
|
||||
}
|
||||
|
||||
if (queryError) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-red-600">خطا: {queryError.message}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (pages.length === 0) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-gray-600">هیچ صفحهای یافت نشد</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!areInitialPagesReady) {
|
||||
return <ViewerLoading progress={preloadProgress} />;
|
||||
}
|
||||
useBrowserFullscreen({ enabled: Boolean(id) });
|
||||
const { isReady: areInitialPagesReady, progress: preloadProgress } =
|
||||
useInitialPagesPreload(pages, documentSettings);
|
||||
|
||||
if (!id) {
|
||||
return (
|
||||
<div className="w-full h-full min-h-0 flex flex-col">
|
||||
<BookViewer pages={pages} catalogSize={data?.data?.size} documentSettings={documentSettings} />
|
||||
</div>
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-gray-600">شناسه کاتالوگ مشخص نیست</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <ViewerLoading />;
|
||||
}
|
||||
|
||||
if (queryError) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-red-600">خطا: {queryError.message}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (pages.length === 0) {
|
||||
return (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<div className="text-gray-600">هیچ صفحهای یافت نشد</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!areInitialPagesReady) {
|
||||
return <ViewerLoading progress={preloadProgress} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full min-h-0 flex flex-col">
|
||||
<BookViewer
|
||||
pages={pages}
|
||||
catalogSize={data?.data?.size}
|
||||
documentSettings={documentSettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Viewer;
|
||||
export default Viewer;
|
||||
|
||||
Reference in New Issue
Block a user