fix ccolor one page

This commit is contained in:
hamid zarghami
2026-05-06 14:53:55 +03:30
parent b1068eec26
commit 8717afbced
7 changed files with 77 additions and 30 deletions
+8 -6
View File
@@ -32,6 +32,8 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
tool, tool,
objects, objects,
guides, guides,
pages,
currentPageId,
selectedObjectId, selectedObjectId,
selectedObjectIds, selectedObjectIds,
selectedCellId, selectedCellId,
@@ -40,17 +42,17 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
setLayerRef, setLayerRef,
setGuides, setGuides,
zoom, zoom,
documentSettings,
} = useEditorStore(); } = useEditorStore();
const currentPage = pages.find((page) => page.id === currentPageId);
const bgColor = const bgColor =
documentSettings.backgroundType === 'color' currentPage?.backgroundType === 'color'
? documentSettings.backgroundColor ? currentPage.backgroundColor
: '#ffffff'; : '#ffffff';
const [bgImage] = useImage( const [bgImage] = useImage(
documentSettings.backgroundType === 'image' currentPage?.backgroundType === 'image'
? documentSettings.backgroundImageUrl ? (currentPage.backgroundImageUrl || '')
: '', : '',
); );
const stageSize = useStageSize(catalogSize); const stageSize = useStageSize(catalogSize);
@@ -394,7 +396,7 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
fill={bgColor} fill={bgColor}
listening={false} listening={false}
/> />
{documentSettings.backgroundType === 'image' && bgImage && ( {currentPage?.backgroundType === 'image' && bgImage && (
<KonvaImage <KonvaImage
image={bgImage} image={bgImage}
x={0} x={0}
+19 -11
View File
@@ -27,11 +27,21 @@ const BACKGROUND_TYPE_OPTIONS: { value: BackgroundType; label: string }[] = [
] ]
const SettingsPanel = () => { const SettingsPanel = () => {
const { documentSettings, updateDocumentSettings } = useEditorStore() const {
documentSettings,
pages,
currentPageId,
updateDocumentSettings,
updateCurrentPageBackground,
} = useEditorStore()
const colorInputRef = useRef<HTMLInputElement>(null) const colorInputRef = useRef<HTMLInputElement>(null)
const { mutateAsync: uploadFile, isPending: imageLoading } = useSingleUpload() const { mutateAsync: uploadFile, isPending: imageLoading } = useSingleUpload()
const currentPage = pages.find((page) => page.id === currentPageId)
const backgroundType = currentPage?.backgroundType ?? 'color'
const backgroundColor = currentPage?.backgroundColor ?? '#ffffff'
const backgroundImageUrl = currentPage?.backgroundImageUrl ?? ''
const [uploadedPreviews, setUploadedPreviews] = useState<string[]>( const [uploadedPreviews, setUploadedPreviews] = useState<string[]>(
documentSettings.backgroundImageUrl ? [documentSettings.backgroundImageUrl] : [] backgroundImageUrl ? [backgroundImageUrl] : []
) )
const { const {
@@ -39,15 +49,13 @@ const SettingsPanel = () => {
autoPlay, autoPlay,
pageFlipSound, pageFlipSound,
leftToRightFlip, leftToRightFlip,
backgroundType,
backgroundColor,
} = documentSettings } = documentSettings
useEffect(() => { useEffect(() => {
setUploadedPreviews( setUploadedPreviews(
documentSettings.backgroundImageUrl ? [documentSettings.backgroundImageUrl] : [] backgroundImageUrl ? [backgroundImageUrl] : []
) )
}, [documentSettings.backgroundImageUrl]) }, [backgroundImageUrl])
const handleImageFiles = async (files: File[]) => { const handleImageFiles = async (files: File[]) => {
if (files.length === 0) return if (files.length === 0) return
@@ -59,7 +67,7 @@ const SettingsPanel = () => {
const imageUrl = result?.data?.url const imageUrl = result?.data?.url
if (!imageUrl) return if (!imageUrl) return
updateDocumentSettings({ backgroundImageUrl: imageUrl }) updateCurrentPageBackground({ backgroundImageUrl: imageUrl })
setUploadedPreviews([imageUrl]) setUploadedPreviews([imageUrl])
} catch { } catch {
// TODO: show error toast // TODO: show error toast
@@ -69,7 +77,7 @@ const SettingsPanel = () => {
const handlePreviewChange = (previews: string[]) => { const handlePreviewChange = (previews: string[]) => {
setUploadedPreviews(previews) setUploadedPreviews(previews)
if (previews.length === 0) { if (previews.length === 0) {
updateDocumentSettings({ backgroundImageUrl: '' }) updateCurrentPageBackground({ backgroundImageUrl: '' })
} }
} }
@@ -137,7 +145,7 @@ const SettingsPanel = () => {
<label key={opt.value} className="flex items-center gap-1.5 cursor-pointer select-none"> <label key={opt.value} className="flex items-center gap-1.5 cursor-pointer select-none">
<span className="text-xs text-gray-600">{opt.label}</span> <span className="text-xs text-gray-600">{opt.label}</span>
<div <div
onClick={() => updateDocumentSettings({ backgroundType: opt.value })} onClick={() => updateCurrentPageBackground({ backgroundType: opt.value })}
className={clx( className={clx(
'w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors cursor-pointer', 'w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors cursor-pointer',
backgroundType === opt.value backgroundType === opt.value
@@ -161,7 +169,7 @@ const SettingsPanel = () => {
{PRESET_COLORS.map((color) => ( {PRESET_COLORS.map((color) => (
<button <button
key={color} key={color}
onClick={() => updateDocumentSettings({ backgroundColor: color })} onClick={() => updateCurrentPageBackground({ backgroundColor: color })}
className={clx( className={clx(
'w-9 h-9 rounded-xl border-2 transition-all', 'w-9 h-9 rounded-xl border-2 transition-all',
backgroundColor === color backgroundColor === color
@@ -184,7 +192,7 @@ const SettingsPanel = () => {
ref={colorInputRef} ref={colorInputRef}
type="color" type="color"
value={backgroundColor} value={backgroundColor}
onChange={(e) => updateDocumentSettings({ backgroundColor: e.target.value })} onChange={(e) => updateCurrentPageBackground({ backgroundColor: e.target.value })}
className="absolute inset-0 opacity-0 w-full h-full cursor-pointer" className="absolute inset-0 opacity-0 w-full h-full cursor-pointer"
/> />
</button> </button>
@@ -78,7 +78,6 @@ export const useDrawingHandlers = () => {
opacity: defaults.opacity, opacity: defaults.opacity,
letterSpacing: defaults.letterSpacing, letterSpacing: defaults.letterSpacing,
wordSpacing: defaults.wordSpacing, wordSpacing: defaults.wordSpacing,
width: 150,
height: defaults.fontSize || 24, height: defaults.fontSize || 24,
}; };
useEditorStore.getState().commitObjectHistoryBeforeChange(); useEditorStore.getState().commitObjectHistoryBeforeChange();
+16 -12
View File
@@ -84,21 +84,25 @@ const TextShape = ({
textNode._setTextData(); textNode._setTextData();
const { width: rw, height: rh } = textNode.getClientRect({ // اندازه‌گیری طبیعی متن (بدون width تحمیلی) تا باکس واقعاً دور متن فیت شود.
const measureNode = textNode.clone({
width: undefined,
x: 0,
y: 0,
});
measureNode._setTextData();
const { width: rw, height: rh } = measureNode.getClientRect({
skipTransform: true, skipTransform: true,
}); });
const measuredWidth = Math.ceil(rw); measureNode.destroy();
const h = Math.ceil(rh);
const nextWidth = obj.width && obj.width > 0 ? obj.width : measuredWidth;
// For text alignment to be visible, keep an existing text box width const nextWidth = Math.max(1, Math.ceil(rw));
// and only auto-calc width when the object has no width yet. const nextHeight = Math.max(1, Math.ceil(rh));
if ( const widthChanged = Math.abs((obj.width || 0) - nextWidth) > 1;
nextWidth > 0 && const heightChanged = Math.abs((obj.height || 0) - nextHeight) > 1;
h > 0 &&
(nextWidth !== obj.width || h !== obj.height) if (widthChanged || heightChanged) {
) { onUpdate(obj.id, { width: nextWidth, height: nextHeight });
onUpdate(obj.id, { width: nextWidth, height: h });
} }
textNode.getLayer()?.batchDraw(); textNode.getLayer()?.batchDraw();
}; };
@@ -53,6 +53,9 @@ export const createInitialPage = (name: string): Page => ({
name, name,
objects: [], objects: [],
guides: [], guides: [],
backgroundType: "color",
backgroundColor: "#ffffff",
backgroundImageUrl: "",
}); });
export const reorderByIds = ( export const reorderByIds = (
+28
View File
@@ -44,6 +44,11 @@ export type {
BackgroundType, BackgroundType,
} from "./editorStore.types"; } from "./editorStore.types";
type PageBackgroundSettings = Pick<
Page,
"backgroundType" | "backgroundColor" | "backgroundImageUrl"
>;
type EditorStoreType = { type EditorStoreType = {
tool: ToolType; tool: ToolType;
setTool: (tool: ToolType) => void; setTool: (tool: ToolType) => void;
@@ -134,6 +139,7 @@ type EditorStoreType = {
sendObjectBackward: (id: string) => void; sendObjectBackward: (id: string) => void;
documentSettings: DocumentSettings; documentSettings: DocumentSettings;
updateDocumentSettings: (updates: Partial<DocumentSettings>) => void; updateDocumentSettings: (updates: Partial<DocumentSettings>) => void;
updateCurrentPageBackground: (updates: Partial<PageBackgroundSettings>) => void;
}; };
export const useEditorStore = create<EditorStoreType>((set, get) => { export const useEditorStore = create<EditorStoreType>((set, get) => {
@@ -191,6 +197,18 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
set((state) => ({ set((state) => ({
documentSettings: { ...state.documentSettings, ...updates }, documentSettings: { ...state.documentSettings, ...updates },
})), })),
updateCurrentPageBackground: (updates) =>
set((state) => {
if (!state.currentPageId) return {};
const currentPage = state.pages.find((p) => p.id === state.currentPageId);
if (!currentPage) return {};
const nextPage = { ...currentPage, ...updates };
return {
pages: state.pages.map((p) =>
p.id === state.currentPageId ? nextPage : p,
),
};
}),
tool: "select", tool: "select",
setTool: (tool) => setTool: (tool) =>
set((state) => ({ set((state) => ({
@@ -816,6 +834,9 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
name: `${pageToDuplicate.name} (کپی)`, name: `${pageToDuplicate.name} (کپی)`,
objects: duplicatedObjects, objects: duplicatedObjects,
guides: [...pageToDuplicate.guides], guides: [...pageToDuplicate.guides],
backgroundType: pageToDuplicate.backgroundType,
backgroundColor: pageToDuplicate.backgroundColor,
backgroundImageUrl: pageToDuplicate.backgroundImageUrl,
}; };
set((state) => ({ set((state) => ({
@@ -978,11 +999,18 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
}, },
loadPages: (pages, documentSettings) => { loadPages: (pages, documentSettings) => {
if (pages.length === 0) return; if (pages.length === 0) return;
const fallbackSettings = documentSettings ?? {};
const normalizedPages = pages.map((page) => ({ const normalizedPages = pages.map((page) => ({
...page, ...page,
id: isInvalidPageId(page.id) ? createScopedId("page") : page.id, id: isInvalidPageId(page.id) ? createScopedId("page") : page.id,
guides: page.guides || [], guides: page.guides || [],
objects: ensureMissingGroupObjects(page.objects || []), objects: ensureMissingGroupObjects(page.objects || []),
backgroundType:
page.backgroundType ?? fallbackSettings.backgroundType ?? "color",
backgroundColor:
page.backgroundColor ?? fallbackSettings.backgroundColor ?? "#ffffff",
backgroundImageUrl:
page.backgroundImageUrl ?? fallbackSettings.backgroundImageUrl ?? "",
})); }));
const firstPage = normalizedPages[0]; const firstPage = normalizedPages[0];
const currentSettings = get().documentSettings; const currentSettings = get().documentSettings;
@@ -80,6 +80,9 @@ export type Page = {
name: string; name: string;
objects: EditorObject[]; objects: EditorObject[];
guides: PageGuide[]; guides: PageGuide[];
backgroundType: BackgroundType;
backgroundColor: string;
backgroundImageUrl: string;
}; };
export type DisplayStyle = 'single' | 'double'; export type DisplayStyle = 'single' | 'double';