grediant in a shape
This commit is contained in:
@@ -17,6 +17,7 @@ import CellEditor from "@/components/CellEditor";
|
|||||||
import ZoomControls from "./ZoomControls";
|
import ZoomControls from "./ZoomControls";
|
||||||
import EditorCanvasToolbar from "./EditorCanvasToolbar";
|
import EditorCanvasToolbar from "./EditorCanvasToolbar";
|
||||||
import { createScopedId } from "../store/editorStore.helpers";
|
import { createScopedId } from "../store/editorStore.helpers";
|
||||||
|
import { getKonvaGradientProps } from "../utils/gradient";
|
||||||
|
|
||||||
type EditorCanvasProps = {
|
type EditorCanvasProps = {
|
||||||
catalogSize?: string;
|
catalogSize?: string;
|
||||||
@@ -46,9 +47,10 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
const currentPage = pages.find((page) => page.id === currentPageId);
|
const currentPage = pages.find((page) => page.id === currentPageId);
|
||||||
|
|
||||||
const bgColor =
|
const bgColor =
|
||||||
currentPage?.backgroundType === 'color'
|
currentPage?.backgroundType === 'color' || currentPage?.backgroundType === 'gradient'
|
||||||
? currentPage.backgroundColor
|
? currentPage.backgroundColor
|
||||||
: '#ffffff';
|
: '#ffffff';
|
||||||
|
const bgGradient = currentPage?.backgroundGradient;
|
||||||
|
|
||||||
const [bgImage] = useImage(
|
const [bgImage] = useImage(
|
||||||
currentPage?.backgroundType === 'image'
|
currentPage?.backgroundType === 'image'
|
||||||
@@ -331,6 +333,13 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
|
|
||||||
const scaledW = stageSize.width * finalScale;
|
const scaledW = stageSize.width * finalScale;
|
||||||
const scaledH = stageSize.height * finalScale;
|
const scaledH = stageSize.height * finalScale;
|
||||||
|
const backgroundGradientProps = getKonvaGradientProps(
|
||||||
|
currentPage?.backgroundType === "gradient" ? "gradient" : "solid",
|
||||||
|
bgGradient,
|
||||||
|
stageSize.width,
|
||||||
|
stageSize.height,
|
||||||
|
"rect",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-auto pb-8 xl:pl-6">
|
<div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-auto pb-8 xl:pl-6">
|
||||||
@@ -394,6 +403,7 @@ const EditorCanvas = ({ catalogSize }: EditorCanvasProps) => {
|
|||||||
width={stageSize.width}
|
width={stageSize.width}
|
||||||
height={stageSize.height}
|
height={stageSize.height}
|
||||||
fill={bgColor}
|
fill={bgColor}
|
||||||
|
{...backgroundGradientProps}
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
{currentPage?.backgroundType === 'image' && bgImage && (
|
{currentPage?.backgroundType === 'image' && bgImage && (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { DisplayStyle, BackgroundType } from '../store/editorStore'
|
|||||||
import UploadBoxDraggble from '@/components/UploadBoxDraggble'
|
import UploadBoxDraggble from '@/components/UploadBoxDraggble'
|
||||||
import { useSingleUpload } from '@/pages/uploader/hooks/useUploaderData'
|
import { useSingleUpload } from '@/pages/uploader/hooks/useUploaderData'
|
||||||
import ColorsImage from '@/assets/images/colors.png'
|
import ColorsImage from '@/assets/images/colors.png'
|
||||||
|
import ColorPicker from '@/components/ColorPicker'
|
||||||
|
|
||||||
const PRESET_COLORS = [
|
const PRESET_COLORS = [
|
||||||
'#a8edcf',
|
'#a8edcf',
|
||||||
@@ -23,6 +24,7 @@ const DISPLAY_STYLE_OPTIONS: { value: DisplayStyle; label: string }[] = [
|
|||||||
|
|
||||||
const BACKGROUND_TYPE_OPTIONS: { value: BackgroundType; label: string }[] = [
|
const BACKGROUND_TYPE_OPTIONS: { value: BackgroundType; label: string }[] = [
|
||||||
{ value: 'color', label: 'رنگ' },
|
{ value: 'color', label: 'رنگ' },
|
||||||
|
{ value: 'gradient', label: 'گرادیانت' },
|
||||||
{ value: 'image', label: 'تصویر' },
|
{ value: 'image', label: 'تصویر' },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -39,6 +41,11 @@ const SettingsPanel = () => {
|
|||||||
const currentPage = pages.find((page) => page.id === currentPageId)
|
const currentPage = pages.find((page) => page.id === currentPageId)
|
||||||
const backgroundType = currentPage?.backgroundType ?? 'color'
|
const backgroundType = currentPage?.backgroundType ?? 'color'
|
||||||
const backgroundColor = currentPage?.backgroundColor ?? '#ffffff'
|
const backgroundColor = currentPage?.backgroundColor ?? '#ffffff'
|
||||||
|
const backgroundGradient = currentPage?.backgroundGradient ?? {
|
||||||
|
from: '#ffffff',
|
||||||
|
to: '#e2e8f0',
|
||||||
|
angle: 135,
|
||||||
|
}
|
||||||
const backgroundImageUrl = currentPage?.backgroundImageUrl ?? ''
|
const backgroundImageUrl = currentPage?.backgroundImageUrl ?? ''
|
||||||
const [uploadedPreviews, setUploadedPreviews] = useState<string[]>(
|
const [uploadedPreviews, setUploadedPreviews] = useState<string[]>(
|
||||||
backgroundImageUrl ? [backgroundImageUrl] : []
|
backgroundImageUrl ? [backgroundImageUrl] : []
|
||||||
@@ -145,7 +152,20 @@ 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={() => updateCurrentPageBackground({ backgroundType: opt.value })}
|
onClick={() =>
|
||||||
|
updateCurrentPageBackground({
|
||||||
|
backgroundType: opt.value,
|
||||||
|
...(opt.value === 'gradient'
|
||||||
|
? {
|
||||||
|
backgroundGradient: currentPage?.backgroundGradient ?? {
|
||||||
|
from: '#ffffff',
|
||||||
|
to: '#e2e8f0',
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
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
|
||||||
@@ -209,6 +229,73 @@ const SettingsPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{backgroundType === 'gradient' && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-xs text-gray-500">گرادیانت خطی (سبک Figma)</p>
|
||||||
|
<ColorPicker
|
||||||
|
label="رنگ شروع"
|
||||||
|
value={backgroundGradient.from}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateCurrentPageBackground({
|
||||||
|
backgroundGradient: { ...backgroundGradient, from: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label="رنگ پایان"
|
||||||
|
value={backgroundGradient.to}
|
||||||
|
onChange={(value) =>
|
||||||
|
updateCurrentPageBackground({
|
||||||
|
backgroundGradient: { ...backgroundGradient, to: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label className="text-xs text-gray-500">زاویه</label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={360}
|
||||||
|
value={backgroundGradient.angle}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateCurrentPageBackground({
|
||||||
|
backgroundGradient: {
|
||||||
|
...backgroundGradient,
|
||||||
|
angle: Number(e.target.value),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={360}
|
||||||
|
value={backgroundGradient.angle}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateCurrentPageBackground({
|
||||||
|
backgroundGradient: {
|
||||||
|
...backgroundGradient,
|
||||||
|
angle: Number(e.target.value) || 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="w-16 h-9 rounded-lg border border-border px-2 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="h-12 rounded-xl border border-border"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.from} 0%, ${backgroundGradient.to} 100%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ─── آپلود تصویر ─── */}
|
{/* ─── آپلود تصویر ─── */}
|
||||||
{backgroundType === 'image' && (
|
{backgroundType === 'image' && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const ObjectRenderer = ({
|
|||||||
if (rafId1) cancelAnimationFrame(rafId1);
|
if (rafId1) cancelAnimationFrame(rafId1);
|
||||||
groupNode?.clearCache();
|
groupNode?.clearCache();
|
||||||
};
|
};
|
||||||
}, [shouldApplyMask, isSelected, maskShape?.x, maskShape?.y, maskShape?.width, maskShape?.height, maskShape?.rotation, obj.x, obj.y, obj.width, obj.height, obj.rotation, obj.maskInvert, obj.strokeWidth, obj.stroke, obj.fill]);
|
}, [shouldApplyMask, isSelected, maskShape?.x, maskShape?.y, maskShape?.width, maskShape?.height, maskShape?.rotation, obj.x, obj.y, obj.width, obj.height, obj.rotation, obj.maskInvert, obj.strokeWidth, obj.stroke, obj.fill, obj.fillType, obj.gradient]);
|
||||||
|
|
||||||
// Refresh cache after transformer is attached (when isSelected changes)
|
// Refresh cache after transformer is attached (when isSelected changes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => {
|
|||||||
const baseWidth = selectedObject.width ?? 100;
|
const baseWidth = selectedObject.width ?? 100;
|
||||||
const baseHeight = selectedObject.height ?? 100;
|
const baseHeight = selectedObject.height ?? 100;
|
||||||
const baseFill = selectedObject.fill ?? "#3b82f6";
|
const baseFill = selectedObject.fill ?? "#3b82f6";
|
||||||
|
const fillType = selectedObject.fillType ?? "solid";
|
||||||
|
const gradient = selectedObject.gradient ?? {
|
||||||
|
from: "#3b82f6",
|
||||||
|
to: "#1d4ed8",
|
||||||
|
angle: 135,
|
||||||
|
};
|
||||||
const baseStroke = selectedObject.stroke ?? "#1e40af";
|
const baseStroke = selectedObject.stroke ?? "#1e40af";
|
||||||
const baseStrokeWidth = selectedObject.strokeWidth ?? 0;
|
const baseStrokeWidth = selectedObject.strokeWidth ?? 0;
|
||||||
const baseBorderRadius = selectedObject.borderRadius ?? 0;
|
const baseBorderRadius = selectedObject.borderRadius ?? 0;
|
||||||
@@ -42,12 +48,84 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ پسزمینه"
|
label="رنگ پسزمینه"
|
||||||
value={baseFill}
|
value={baseFill}
|
||||||
|
readOnly={fillType === "gradient"}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
onUpdate(selectedObject.id, {
|
onUpdate(selectedObject.id, {
|
||||||
fill: value,
|
fill: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm">نوع Fill</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
fillType: "solid",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={`px-3 py-1.5 rounded-lg border text-xs ${fillType === "solid" ? "border-gray-800 text-gray-900" : "border-border text-gray-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
رنگ ساده
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
fillType: "gradient",
|
||||||
|
gradient: selectedObject.gradient ?? {
|
||||||
|
from: "#3b82f6",
|
||||||
|
to: "#1d4ed8",
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className={`px-3 py-1.5 rounded-lg border text-xs ${fillType === "gradient" ? "border-gray-800 text-gray-900" : "border-border text-gray-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
گرادیانت
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{fillType === "gradient" && (
|
||||||
|
<div className="space-y-3 border border-border rounded-xl p-3">
|
||||||
|
<ColorPicker
|
||||||
|
label="رنگ شروع"
|
||||||
|
value={gradient.from}
|
||||||
|
onChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
gradient: { ...gradient, from: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ColorPicker
|
||||||
|
label="رنگ پایان"
|
||||||
|
value={gradient.to}
|
||||||
|
onChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
gradient: { ...gradient, to: value },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="زاویه گرادیانت"
|
||||||
|
type="number"
|
||||||
|
value={gradient.angle}
|
||||||
|
onChange={(e) =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
gradient: {
|
||||||
|
...gradient,
|
||||||
|
angle: Number(e.target.value) || 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
min={0}
|
||||||
|
max={360}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ خط"
|
label="رنگ خط"
|
||||||
value={baseStroke}
|
value={baseStroke}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useRef } from "react";
|
|||||||
import { Star } from "react-konva";
|
import { Star } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { ShapeProps } from "./types";
|
import type { ShapeProps } from "./types";
|
||||||
|
import { getKonvaGradientProps } from "../../utils/gradient";
|
||||||
|
|
||||||
const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
||||||
const shapeRef = useRef<Konva.Star>(null);
|
const shapeRef = useRef<Konva.Star>(null);
|
||||||
@@ -30,6 +31,15 @@ const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
const displayStrokeWidth = isSelected
|
const displayStrokeWidth = isSelected
|
||||||
? (hasStroke ? actualStrokeWidth : 3)
|
? (hasStroke ? actualStrokeWidth : 3)
|
||||||
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
||||||
|
const gradientProps = isMask
|
||||||
|
? {}
|
||||||
|
: getKonvaGradientProps(
|
||||||
|
obj.fillType,
|
||||||
|
obj.gradient,
|
||||||
|
obj.width || 100,
|
||||||
|
obj.height || 100,
|
||||||
|
"centered",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Star
|
<Star
|
||||||
@@ -42,6 +52,7 @@ const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
innerRadius={innerRadius}
|
innerRadius={innerRadius}
|
||||||
outerRadius={radius}
|
outerRadius={radius}
|
||||||
fill={isMask ? "transparent" : obj.fill}
|
fill={isMask ? "transparent" : obj.fill}
|
||||||
|
{...gradientProps}
|
||||||
stroke={displayStroke}
|
stroke={displayStroke}
|
||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useRef } from "react";
|
|||||||
import { Circle } from "react-konva";
|
import { Circle } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { ShapeProps } from "./types";
|
import type { ShapeProps } from "./types";
|
||||||
|
import { getKonvaGradientProps } from "../../utils/gradient";
|
||||||
|
|
||||||
const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
||||||
const shapeRef = useRef<Konva.Circle>(null);
|
const shapeRef = useRef<Konva.Circle>(null);
|
||||||
@@ -26,6 +27,10 @@ const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePr
|
|||||||
const displayStrokeWidth = isSelected
|
const displayStrokeWidth = isSelected
|
||||||
? (hasStroke ? actualStrokeWidth : 3)
|
? (hasStroke ? actualStrokeWidth : 3)
|
||||||
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
||||||
|
const diameter = obj.width || 100;
|
||||||
|
const gradientProps = isMask
|
||||||
|
? {}
|
||||||
|
: getKonvaGradientProps(obj.fillType, obj.gradient, diameter, diameter, "centered");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Circle
|
<Circle
|
||||||
@@ -36,6 +41,7 @@ const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePr
|
|||||||
y={obj.y}
|
y={obj.y}
|
||||||
radius={(obj.width || 50) / 2}
|
radius={(obj.width || 50) / 2}
|
||||||
fill={isMask ? "transparent" : obj.fill}
|
fill={isMask ? "transparent" : obj.fill}
|
||||||
|
{...gradientProps}
|
||||||
stroke={displayStroke}
|
stroke={displayStroke}
|
||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useRef } from "react";
|
|||||||
import { Rect } from "react-konva";
|
import { Rect } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { ShapeProps } from "./types";
|
import type { ShapeProps } from "./types";
|
||||||
|
import { getKonvaGradientProps } from "../../utils/gradient";
|
||||||
|
|
||||||
const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
||||||
const shapeRef = useRef<Konva.Rect>(null);
|
const shapeRef = useRef<Konva.Rect>(null);
|
||||||
@@ -27,6 +28,15 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap
|
|||||||
const displayStrokeWidth = isSelected
|
const displayStrokeWidth = isSelected
|
||||||
? (hasStroke ? actualStrokeWidth : 3)
|
? (hasStroke ? actualStrokeWidth : 3)
|
||||||
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
||||||
|
const gradientProps = isMask
|
||||||
|
? {}
|
||||||
|
: getKonvaGradientProps(
|
||||||
|
obj.fillType,
|
||||||
|
obj.gradient,
|
||||||
|
obj.width || 100,
|
||||||
|
obj.height || 100,
|
||||||
|
"rect",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Rect
|
<Rect
|
||||||
@@ -39,6 +49,7 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap
|
|||||||
height={obj.height || 100}
|
height={obj.height || 100}
|
||||||
cornerRadius={cornerRadius}
|
cornerRadius={cornerRadius}
|
||||||
fill={isMask ? "transparent" : obj.fill}
|
fill={isMask ? "transparent" : obj.fill}
|
||||||
|
{...gradientProps}
|
||||||
stroke={displayStroke}
|
stroke={displayStroke}
|
||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useRef } from "react";
|
|||||||
import { RegularPolygon } from "react-konva";
|
import { RegularPolygon } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { ShapeProps } from "./types";
|
import type { ShapeProps } from "./types";
|
||||||
|
import { getKonvaGradientProps } from "../../utils/gradient";
|
||||||
|
|
||||||
const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapeProps) => {
|
||||||
const shapeRef = useRef<Konva.RegularPolygon>(null);
|
const shapeRef = useRef<Konva.RegularPolygon>(null);
|
||||||
@@ -27,6 +28,15 @@ const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
const displayStrokeWidth = isSelected
|
const displayStrokeWidth = isSelected
|
||||||
? (hasStroke ? actualStrokeWidth : 3)
|
? (hasStroke ? actualStrokeWidth : 3)
|
||||||
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
: (isMask && showGuide ? 2 : actualStrokeWidth);
|
||||||
|
const gradientProps = isMask
|
||||||
|
? {}
|
||||||
|
: getKonvaGradientProps(
|
||||||
|
obj.fillType,
|
||||||
|
obj.gradient,
|
||||||
|
obj.width || 100,
|
||||||
|
obj.height || 100,
|
||||||
|
"centered",
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RegularPolygon
|
<RegularPolygon
|
||||||
@@ -38,6 +48,7 @@ const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
sides={3}
|
sides={3}
|
||||||
radius={radius}
|
radius={radius}
|
||||||
fill={isMask ? "transparent" : obj.fill}
|
fill={isMask ? "transparent" : obj.fill}
|
||||||
|
{...gradientProps}
|
||||||
stroke={displayStroke}
|
stroke={displayStroke}
|
||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ export const createInitialPage = (name: string): Page => ({
|
|||||||
guides: [],
|
guides: [],
|
||||||
backgroundType: "color",
|
backgroundType: "color",
|
||||||
backgroundColor: "#ffffff",
|
backgroundColor: "#ffffff",
|
||||||
|
backgroundGradient: {
|
||||||
|
from: "#ffffff",
|
||||||
|
to: "#e2e8f0",
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
backgroundImageUrl: "",
|
backgroundImageUrl: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,8 +123,8 @@ export const getObjectBounds = (obj: EditorObject) => {
|
|||||||
return { minX, minY, maxX, maxY };
|
return { minX, minY, maxX, maxY };
|
||||||
}
|
}
|
||||||
|
|
||||||
let w = obj.width || 0;
|
const w = obj.width || 0;
|
||||||
let h = obj.height || 0;
|
const h = obj.height || 0;
|
||||||
|
|
||||||
if (obj.type === "rectangle" && obj.shapeType === "circle") {
|
if (obj.type === "rectangle" && obj.shapeType === "circle") {
|
||||||
const radius = w / 2;
|
const radius = w / 2;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export type {
|
|||||||
|
|
||||||
type PageBackgroundSettings = Pick<
|
type PageBackgroundSettings = Pick<
|
||||||
Page,
|
Page,
|
||||||
"backgroundType" | "backgroundColor" | "backgroundImageUrl"
|
"backgroundType" | "backgroundColor" | "backgroundGradient" | "backgroundImageUrl"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type EditorStoreType = {
|
type EditorStoreType = {
|
||||||
@@ -191,6 +191,11 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
leftToRightFlip: false,
|
leftToRightFlip: false,
|
||||||
backgroundType: 'color',
|
backgroundType: 'color',
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
|
backgroundGradient: {
|
||||||
|
from: "#ffffff",
|
||||||
|
to: "#e2e8f0",
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
backgroundImageUrl: '',
|
backgroundImageUrl: '',
|
||||||
},
|
},
|
||||||
updateDocumentSettings: (updates) =>
|
updateDocumentSettings: (updates) =>
|
||||||
@@ -836,6 +841,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
guides: [...pageToDuplicate.guides],
|
guides: [...pageToDuplicate.guides],
|
||||||
backgroundType: pageToDuplicate.backgroundType,
|
backgroundType: pageToDuplicate.backgroundType,
|
||||||
backgroundColor: pageToDuplicate.backgroundColor,
|
backgroundColor: pageToDuplicate.backgroundColor,
|
||||||
|
backgroundGradient: pageToDuplicate.backgroundGradient,
|
||||||
backgroundImageUrl: pageToDuplicate.backgroundImageUrl,
|
backgroundImageUrl: pageToDuplicate.backgroundImageUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1009,6 +1015,12 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
page.backgroundType ?? fallbackSettings.backgroundType ?? "color",
|
page.backgroundType ?? fallbackSettings.backgroundType ?? "color",
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
page.backgroundColor ?? fallbackSettings.backgroundColor ?? "#ffffff",
|
page.backgroundColor ?? fallbackSettings.backgroundColor ?? "#ffffff",
|
||||||
|
backgroundGradient: page.backgroundGradient ??
|
||||||
|
fallbackSettings.backgroundGradient ?? {
|
||||||
|
from: "#ffffff",
|
||||||
|
to: "#e2e8f0",
|
||||||
|
angle: 135,
|
||||||
|
},
|
||||||
backgroundImageUrl:
|
backgroundImageUrl:
|
||||||
page.backgroundImageUrl ?? fallbackSettings.backgroundImageUrl ?? "",
|
page.backgroundImageUrl ?? fallbackSettings.backgroundImageUrl ?? "",
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ShapeType } from "./shapeStore";
|
import type { ShapeType } from "./shapeStore";
|
||||||
|
import type { FillType, LinearGradient } from "../utils/gradient";
|
||||||
|
|
||||||
export type ToolType =
|
export type ToolType =
|
||||||
| "select"
|
| "select"
|
||||||
@@ -41,6 +42,8 @@ export type EditorObject = {
|
|||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
fill?: string;
|
fill?: string;
|
||||||
|
fillType?: FillType;
|
||||||
|
gradient?: LinearGradient;
|
||||||
stroke?: string;
|
stroke?: string;
|
||||||
strokeWidth?: number;
|
strokeWidth?: number;
|
||||||
text?: string;
|
text?: string;
|
||||||
@@ -82,11 +85,12 @@ export type Page = {
|
|||||||
guides: PageGuide[];
|
guides: PageGuide[];
|
||||||
backgroundType: BackgroundType;
|
backgroundType: BackgroundType;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
|
backgroundGradient?: LinearGradient;
|
||||||
backgroundImageUrl: string;
|
backgroundImageUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DisplayStyle = 'single' | 'double';
|
export type DisplayStyle = 'single' | 'double';
|
||||||
export type BackgroundType = 'color' | 'image';
|
export type BackgroundType = 'color' | 'gradient' | 'image';
|
||||||
|
|
||||||
export type DocumentSettings = {
|
export type DocumentSettings = {
|
||||||
displayStyle: DisplayStyle;
|
displayStyle: DisplayStyle;
|
||||||
@@ -95,5 +99,6 @@ export type DocumentSettings = {
|
|||||||
leftToRightFlip: boolean;
|
leftToRightFlip: boolean;
|
||||||
backgroundType: BackgroundType;
|
backgroundType: BackgroundType;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
|
backgroundGradient?: LinearGradient;
|
||||||
backgroundImageUrl: string;
|
backgroundImageUrl: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
export type FillType = "solid" | "gradient";
|
||||||
|
|
||||||
|
export type LinearGradient = {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
angle: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Point = { x: number; y: number };
|
||||||
|
|
||||||
|
type GradientPointMode = "rect" | "centered";
|
||||||
|
|
||||||
|
const toRadians = (angle: number) => (angle * Math.PI) / 180;
|
||||||
|
|
||||||
|
const normalizeAngle = (angle: number) => {
|
||||||
|
if (!Number.isFinite(angle)) return 0;
|
||||||
|
return ((angle % 360) + 360) % 360;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGradientPoints = (
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
angle: number,
|
||||||
|
mode: GradientPointMode,
|
||||||
|
): { start: Point; end: Point } => {
|
||||||
|
const safeWidth = Math.max(1, width);
|
||||||
|
const safeHeight = Math.max(1, height);
|
||||||
|
const rad = toRadians(normalizeAngle(angle));
|
||||||
|
const dx = Math.cos(rad);
|
||||||
|
const dy = Math.sin(rad);
|
||||||
|
const half = Math.sqrt(safeWidth * safeWidth + safeHeight * safeHeight) / 2;
|
||||||
|
|
||||||
|
if (mode === "centered") {
|
||||||
|
return {
|
||||||
|
start: { x: -dx * half, y: -dy * half },
|
||||||
|
end: { x: dx * half, y: dy * half },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cx = safeWidth / 2;
|
||||||
|
const cy = safeHeight / 2;
|
||||||
|
return {
|
||||||
|
start: { x: cx - dx * half, y: cy - dy * half },
|
||||||
|
end: { x: cx + dx * half, y: cy + dy * half },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKonvaGradientProps = (
|
||||||
|
fillType: FillType | undefined,
|
||||||
|
gradient: LinearGradient | undefined,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
mode: GradientPointMode,
|
||||||
|
) => {
|
||||||
|
if (fillType !== "gradient" || !gradient) return {};
|
||||||
|
const points = getGradientPoints(width, height, gradient.angle, mode);
|
||||||
|
return {
|
||||||
|
fillPriority: "linear-gradient" as const,
|
||||||
|
fillLinearGradientStartPoint: points.start,
|
||||||
|
fillLinearGradientEndPoint: points.end,
|
||||||
|
fillLinearGradientColorStops: [0, gradient.from, 1, gradient.to] as [
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toCssLinearGradient = (gradient: LinearGradient | undefined) => {
|
||||||
|
if (!gradient) return undefined;
|
||||||
|
return `linear-gradient(${normalizeAngle(gradient.angle)}deg, ${gradient.from} 0%, ${gradient.to} 100%)`;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { type PageData } from '../types';
|
import { type PageData } from '../types';
|
||||||
import type { EditorObject } from '@/pages/editor/store/editorStore';
|
import type { EditorObject } from '@/pages/editor/store/editorStore';
|
||||||
|
import { toCssLinearGradient } from '@/pages/editor/utils/gradient';
|
||||||
|
|
||||||
type BookPageProps = {
|
type BookPageProps = {
|
||||||
page: PageData;
|
page: PageData;
|
||||||
@@ -8,8 +9,13 @@ type BookPageProps = {
|
|||||||
pageWidth?: number;
|
pageWidth?: number;
|
||||||
pageHeight?: number;
|
pageHeight?: number;
|
||||||
onLinkClick?: (linkUrl: string) => void;
|
onLinkClick?: (linkUrl: string) => void;
|
||||||
backgroundType?: 'color' | 'image';
|
backgroundType?: 'color' | 'gradient' | 'image';
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
backgroundGradient?: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
angle: number;
|
||||||
|
};
|
||||||
backgroundImageUrl?: string;
|
backgroundImageUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -21,7 +27,7 @@ type BookPageProps = {
|
|||||||
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
|
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
|
||||||
*/
|
*/
|
||||||
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
||||||
({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundImageUrl }, ref) => {
|
({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl }, ref) => {
|
||||||
// تابع برای تبدیل opacity به رنگ (مثل editor)
|
// تابع برای تبدیل opacity به رنگ (مثل editor)
|
||||||
// در editor، getColorWithOpacity انتظار opacity 0-100 دارد
|
// در editor، getColorWithOpacity انتظار opacity 0-100 دارد
|
||||||
// در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
|
// در dataTransformer، opacity از 0-1 به 0-100 تبدیل شده است
|
||||||
@@ -69,6 +75,11 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const objectFillStyle: React.CSSProperties =
|
||||||
|
obj.fillType === 'gradient' && obj.gradient
|
||||||
|
? { backgroundImage: toCssLinearGradient(obj.gradient) }
|
||||||
|
: { backgroundColor: obj.fill || 'transparent' };
|
||||||
|
|
||||||
switch (obj.type) {
|
switch (obj.type) {
|
||||||
case 'text': {
|
case 'text': {
|
||||||
// برای text، opacity در رنگ اعمال میشود، پس باید opacity را از baseStyle حذف کنیم
|
// برای text، opacity در رنگ اعمال میشود، پس باید opacity را از baseStyle حذف کنیم
|
||||||
@@ -234,7 +245,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
width: `${radius * 2}px`,
|
width: `${radius * 2}px`,
|
||||||
height: `${radius * 2}px`,
|
height: `${radius * 2}px`,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
backgroundColor: obj.fill || 'transparent',
|
...objectFillStyle,
|
||||||
border: hasStroke && obj.stroke
|
border: hasStroke && obj.stroke
|
||||||
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
||||||
: 'none',
|
: 'none',
|
||||||
@@ -280,6 +291,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
const strokeWidth = hasStroke && obj.stroke ? actualStrokeWidth * scale : 0;
|
const strokeWidth = hasStroke && obj.stroke ? actualStrokeWidth * scale : 0;
|
||||||
const fillColor = obj.fill || '#000000';
|
const fillColor = obj.fill || '#000000';
|
||||||
const strokeColor = obj.stroke || 'transparent';
|
const strokeColor = obj.stroke || 'transparent';
|
||||||
|
const gradientId = `triangle-grad-${obj.id}-${index}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@@ -298,9 +310,25 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
}}
|
}}
|
||||||
viewBox={`0 0 ${size} ${size}`}
|
viewBox={`0 0 ${size} ${size}`}
|
||||||
>
|
>
|
||||||
|
{obj.fillType === 'gradient' && obj.gradient ? (
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id={gradientId}
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2={size}
|
||||||
|
y2={size}
|
||||||
|
gradientTransform={`rotate(${obj.gradient.angle}, ${size / 2}, ${size / 2})`}
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor={obj.gradient.from} />
|
||||||
|
<stop offset="100%" stopColor={obj.gradient.to} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
) : null}
|
||||||
<polygon
|
<polygon
|
||||||
points={`${topX},${topY} ${bottomLeftX},${bottomY} ${bottomRightX},${bottomY}`}
|
points={`${topX},${topY} ${bottomLeftX},${bottomY} ${bottomRightX},${bottomY}`}
|
||||||
fill={fillColor}
|
fill={obj.fillType === 'gradient' && obj.gradient ? `url(#${gradientId})` : fillColor}
|
||||||
stroke={strokeColor}
|
stroke={strokeColor}
|
||||||
strokeWidth={strokeWidth}
|
strokeWidth={strokeWidth}
|
||||||
/>
|
/>
|
||||||
@@ -328,7 +356,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
width: `${radius * 2}px`,
|
width: `${radius * 2}px`,
|
||||||
height: `${radius * 2}px`,
|
height: `${radius * 2}px`,
|
||||||
clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
clipPath: 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
|
||||||
backgroundColor: obj.fill || 'transparent',
|
...objectFillStyle,
|
||||||
border: obj.stroke
|
border: obj.stroke
|
||||||
? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
|
? `${(obj.strokeWidth || 1) * scale}px solid ${obj.stroke}`
|
||||||
: 'none',
|
: 'none',
|
||||||
@@ -346,7 +374,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
...baseStyle,
|
...baseStyle,
|
||||||
width: `${(obj.width || 100) * scale}px`,
|
width: `${(obj.width || 100) * scale}px`,
|
||||||
height: `${(obj.height || 100) * scale}px`,
|
height: `${(obj.height || 100) * scale}px`,
|
||||||
backgroundColor: obj.fill || 'transparent',
|
...objectFillStyle,
|
||||||
borderRadius: `${Math.max(0, (obj.borderRadius || 0) * scale)}px`,
|
borderRadius: `${Math.max(0, (obj.borderRadius || 0) * scale)}px`,
|
||||||
border: hasStroke && obj.stroke
|
border: hasStroke && obj.stroke
|
||||||
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
? `${actualStrokeWidth * scale}px solid ${obj.stroke}`
|
||||||
@@ -489,6 +517,7 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
|
|
||||||
const effectiveBackgroundType = backgroundType ?? page.backgroundType ?? 'color';
|
const effectiveBackgroundType = backgroundType ?? page.backgroundType ?? 'color';
|
||||||
const effectiveBackgroundColor = backgroundColor ?? page.backgroundColor ?? '#ffffff';
|
const effectiveBackgroundColor = backgroundColor ?? page.backgroundColor ?? '#ffffff';
|
||||||
|
const effectiveBackgroundGradient = backgroundGradient ?? page.backgroundGradient;
|
||||||
const effectiveBackgroundImageUrl = backgroundImageUrl ?? page.backgroundImageUrl ?? '';
|
const effectiveBackgroundImageUrl = backgroundImageUrl ?? page.backgroundImageUrl ?? '';
|
||||||
|
|
||||||
const bgStyle: React.CSSProperties =
|
const bgStyle: React.CSSProperties =
|
||||||
@@ -499,6 +528,10 @@ const BookPage = forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
}
|
}
|
||||||
|
: effectiveBackgroundType === 'gradient' && effectiveBackgroundGradient
|
||||||
|
? {
|
||||||
|
backgroundImage: toCssLinearGradient(effectiveBackgroundGradient),
|
||||||
|
}
|
||||||
: { backgroundColor: effectiveBackgroundColor };
|
: { backgroundColor: effectiveBackgroundColor };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
|||||||
const displayStyle = documentSettings?.displayStyle ?? 'double';
|
const displayStyle = documentSettings?.displayStyle ?? 'double';
|
||||||
const backgroundType = documentSettings?.backgroundType ?? 'color';
|
const backgroundType = documentSettings?.backgroundType ?? 'color';
|
||||||
const backgroundColor = documentSettings?.backgroundColor ?? '#ffffff';
|
const backgroundColor = documentSettings?.backgroundColor ?? '#ffffff';
|
||||||
|
const backgroundGradient = documentSettings?.backgroundGradient;
|
||||||
const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? '';
|
const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -486,6 +487,7 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
|
|||||||
onLinkClick={handleLinkClick}
|
onLinkClick={handleLinkClick}
|
||||||
backgroundType={page.backgroundType ?? backgroundType}
|
backgroundType={page.backgroundType ?? backgroundType}
|
||||||
backgroundColor={page.backgroundColor ?? backgroundColor}
|
backgroundColor={page.backgroundColor ?? backgroundColor}
|
||||||
|
backgroundGradient={page.backgroundGradient ?? backgroundGradient}
|
||||||
backgroundImageUrl={page.backgroundImageUrl ?? backgroundImageUrl}
|
backgroundImageUrl={page.backgroundImageUrl ?? backgroundImageUrl}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ export type PageData = {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
elements: EditorObject[];
|
elements: EditorObject[];
|
||||||
backgroundType?: "color" | "image";
|
backgroundType?: "color" | "gradient" | "image";
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
backgroundGradient?: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
angle: number;
|
||||||
|
};
|
||||||
backgroundImageUrl?: string;
|
backgroundImageUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ import type { PageData } from "../types";
|
|||||||
type ViewerDataPage = {
|
type ViewerDataPage = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
backgroundType?: "color" | "image";
|
backgroundType?: "color" | "gradient" | "image";
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
|
backgroundGradient?: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
angle: number;
|
||||||
|
};
|
||||||
backgroundImageUrl?: string;
|
backgroundImageUrl?: string;
|
||||||
objects: Array<{
|
objects: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,6 +26,12 @@ type ViewerDataPage = {
|
|||||||
lineHeight?: number;
|
lineHeight?: number;
|
||||||
textAlign?: "left" | "center" | "right";
|
textAlign?: "left" | "center" | "right";
|
||||||
fill?: string;
|
fill?: string;
|
||||||
|
fillType?: "solid" | "gradient";
|
||||||
|
gradient?: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
angle: number;
|
||||||
|
};
|
||||||
stroke?: string;
|
stroke?: string;
|
||||||
strokeWidth?: number;
|
strokeWidth?: number;
|
||||||
shapeType?: string;
|
shapeType?: string;
|
||||||
@@ -82,6 +93,8 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
|||||||
// baseObject.y = obj.y + (obj.height || 100) / 2;
|
// baseObject.y = obj.y + (obj.height || 100) / 2;
|
||||||
// اما فعلاً JSON به صورت مرکز است، پس تبدیل نمیکنیم
|
// اما فعلاً JSON به صورت مرکز است، پس تبدیل نمیکنیم
|
||||||
if (obj.fill !== undefined) baseObject.fill = obj.fill;
|
if (obj.fill !== undefined) baseObject.fill = obj.fill;
|
||||||
|
if (obj.fillType !== undefined) baseObject.fillType = obj.fillType;
|
||||||
|
if (obj.gradient !== undefined) baseObject.gradient = obj.gradient;
|
||||||
if (obj.stroke !== undefined) baseObject.stroke = obj.stroke;
|
if (obj.stroke !== undefined) baseObject.stroke = obj.stroke;
|
||||||
if (obj.strokeWidth !== undefined)
|
if (obj.strokeWidth !== undefined)
|
||||||
baseObject.strokeWidth = obj.strokeWidth;
|
baseObject.strokeWidth = obj.strokeWidth;
|
||||||
@@ -131,6 +144,7 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
|
|||||||
elements: objects,
|
elements: objects,
|
||||||
backgroundType: page.backgroundType,
|
backgroundType: page.backgroundType,
|
||||||
backgroundColor: page.backgroundColor,
|
backgroundColor: page.backgroundColor,
|
||||||
|
backgroundGradient: page.backgroundGradient,
|
||||||
backgroundImageUrl: page.backgroundImageUrl,
|
backgroundImageUrl: page.backgroundImageUrl,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user