fix ccolor one page
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 = (
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user