This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { type FC, type InputHTMLAttributes, useState, useRef, useEffect } from 'react'
|
import { type FC, type InputHTMLAttributes, useState, useRef, useEffect } from 'react'
|
||||||
import { clx } from '../helpers/utils'
|
import { clx } from '../helpers/utils'
|
||||||
import Error from './Error'
|
import Error from './Error'
|
||||||
|
import Input from './Input'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label?: string
|
label?: string
|
||||||
@@ -9,17 +10,21 @@ type Props = {
|
|||||||
isNotRequired?: boolean
|
isNotRequired?: boolean
|
||||||
value?: string
|
value?: string
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
|
opacity?: number
|
||||||
|
onOpacityChange?: (value: number) => void
|
||||||
|
opacityLabel?: string
|
||||||
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'>
|
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'>
|
||||||
|
|
||||||
const ColorPicker: FC<Props> = (props: Props) => {
|
const ColorPicker: FC<Props> = (props: Props) => {
|
||||||
const [color, setColor] = useState<string>(props.value || '#000000')
|
const [color, setColor] = useState<string>(props.value || '#000000')
|
||||||
|
const [opacityDisplay, setOpacityDisplay] = useState<string>(`${props.opacity ?? 100}`)
|
||||||
const colorInputRef = useRef<HTMLInputElement>(null)
|
const colorInputRef = useRef<HTMLInputElement>(null)
|
||||||
const pickerOpenGuardRef = useRef(false)
|
const pickerOpenGuardRef = useRef(false)
|
||||||
|
const showOpacity = props.onOpacityChange !== undefined
|
||||||
|
|
||||||
const inputClass = clx(
|
const inputClass = clx(
|
||||||
'w-full bg-white h-[34px] text-black block pl-10 pr-10 text-xs rounded-xl border border-border',
|
'w-full bg-white h-[34px] text-black block pl-10 pr-10 text-xs rounded-xl border border-border',
|
||||||
props.readOnly && 'bg-gray-100 border-0 text-description',
|
props.readOnly && 'bg-gray-100 border-0 text-description',
|
||||||
props.className
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -53,8 +58,31 @@ const ColorPicker: FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [props.value])
|
}, [props.value])
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div className='w-full'>
|
if (props.opacity !== undefined) {
|
||||||
|
setOpacityDisplay(`${props.opacity}`)
|
||||||
|
}
|
||||||
|
}, [props.opacity])
|
||||||
|
|
||||||
|
const handleOpacityChange = (rawValue: string) => {
|
||||||
|
setOpacityDisplay(rawValue)
|
||||||
|
const numValue = parseInt(rawValue, 10)
|
||||||
|
if (!Number.isNaN(numValue) && numValue >= 0 && numValue <= 100) {
|
||||||
|
props.onOpacityChange?.(numValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOpacityBlur = (rawValue: string) => {
|
||||||
|
const numValue = parseInt(rawValue, 10) || 0
|
||||||
|
const clampedValue = Math.min(100, Math.max(0, numValue))
|
||||||
|
setOpacityDisplay(`${clampedValue}`)
|
||||||
|
props.onOpacityChange?.(clampedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorField = (
|
||||||
|
<div
|
||||||
|
className={clx('w-full', showOpacity && 'flex-1')}
|
||||||
|
>
|
||||||
<label className='text-xs'>
|
<label className='text-xs'>
|
||||||
{props.label}
|
{props.label}
|
||||||
</label>
|
</label>
|
||||||
@@ -115,6 +143,26 @@ const ColorPicker: FC<Props> = (props: Props) => {
|
|||||||
className='absolute opacity-0 pointer-events-none w-0 h-0'
|
className='absolute opacity-0 pointer-events-none w-0 h-0'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clx('w-full', showOpacity && 'flex gap-3', props.className)}>
|
||||||
|
{colorField}
|
||||||
|
{showOpacity && (
|
||||||
|
<div className='w-28 shrink-0'>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
label={props.opacityLabel ?? 'شفافیت'}
|
||||||
|
className='px-3'
|
||||||
|
value={opacityDisplay}
|
||||||
|
onChange={(e) => handleOpacityChange(e.target.value)}
|
||||||
|
onBlur={(e) => handleOpacityBlur(e.target.value)}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.error_text && (
|
{props.error_text && (
|
||||||
<Error errorText={props.error_text} />
|
<Error errorText={props.error_text} />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from "react";
|
|||||||
import { Group, Rect, Text } from "react-konva";
|
import { Group, Rect, Text } from "react-konva";
|
||||||
import Konva from "konva";
|
import Konva from "konva";
|
||||||
import type { EditorObject } from "@/pages/editor/store/editorStore";
|
import type { EditorObject } from "@/pages/editor/store/editorStore";
|
||||||
|
import { getColorWithOpacity } from "@/pages/editor/utils/colorOpacity";
|
||||||
|
|
||||||
type EditorTableProps = {
|
type EditorTableProps = {
|
||||||
obj: EditorObject;
|
obj: EditorObject;
|
||||||
@@ -80,7 +81,7 @@ const EditorTable = ({ obj, selectedCellId, onCellClick, onCellDblClick, onDragE
|
|||||||
y={y}
|
y={y}
|
||||||
width={cellWidth}
|
width={cellWidth}
|
||||||
height={cellHeight}
|
height={cellHeight}
|
||||||
fill={cell.background}
|
fill={getColorWithOpacity(cell.background, cell.backgroundOpacity ?? 100)}
|
||||||
stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"}
|
stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"}
|
||||||
strokeWidth={isCellSelected ? 3 : strokeWidth || 1}
|
strokeWidth={isCellSelected ? 3 : strokeWidth || 1}
|
||||||
cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0}
|
cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ const buildPencilObject = (
|
|||||||
points: DrawPoint[],
|
points: DrawPoint[],
|
||||||
stroke: string,
|
stroke: string,
|
||||||
strokeWidth: number,
|
strokeWidth: number,
|
||||||
|
opacity: number,
|
||||||
): EditorObject => {
|
): EditorObject => {
|
||||||
const xs = points.map((p) => p.x);
|
const xs = points.map((p) => p.x);
|
||||||
const ys = points.map((p) => p.y);
|
const ys = points.map((p) => p.y);
|
||||||
@@ -89,7 +90,7 @@ const buildPencilObject = (
|
|||||||
stroke,
|
stroke,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
tension: DEFAULT_TENSION,
|
tension: DEFAULT_TENSION,
|
||||||
opacity: 100,
|
opacity,
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -100,6 +101,7 @@ export const usePencilDrawing = (stageRef: React.RefObject<Konva.Stage | null>)
|
|||||||
const pointerIdRef = useRef<number | null>(null);
|
const pointerIdRef = useRef<number | null>(null);
|
||||||
const strokeRef = useRef("#000000");
|
const strokeRef = useRef("#000000");
|
||||||
const strokeWidthRef = useRef(3);
|
const strokeWidthRef = useRef(3);
|
||||||
|
const opacityRef = useRef(100);
|
||||||
|
|
||||||
const [drawingState, setDrawingState] = useState<PencilDrawingState>(INITIAL_STATE);
|
const [drawingState, setDrawingState] = useState<PencilDrawingState>(INITIAL_STATE);
|
||||||
|
|
||||||
@@ -141,7 +143,12 @@ export const usePencilDrawing = (stageRef: React.RefObject<Konva.Stage | null>)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = buildPencilObject(points, strokeRef.current, strokeWidthRef.current);
|
const obj = buildPencilObject(
|
||||||
|
points,
|
||||||
|
strokeRef.current,
|
||||||
|
strokeWidthRef.current,
|
||||||
|
opacityRef.current,
|
||||||
|
);
|
||||||
useEditorStore.getState().commitObjectHistoryBeforeChange();
|
useEditorStore.getState().commitObjectHistoryBeforeChange();
|
||||||
addObject(obj);
|
addObject(obj);
|
||||||
reset();
|
reset();
|
||||||
@@ -165,6 +172,7 @@ export const usePencilDrawing = (stageRef: React.RefObject<Konva.Stage | null>)
|
|||||||
(pos: DrawPoint, pointerId: number) => {
|
(pos: DrawPoint, pointerId: number) => {
|
||||||
strokeRef.current = defaults.stroke;
|
strokeRef.current = defaults.stroke;
|
||||||
strokeWidthRef.current = defaults.strokeWidth;
|
strokeWidthRef.current = defaults.strokeWidth;
|
||||||
|
opacityRef.current = defaults.opacity;
|
||||||
isDrawingRef.current = true;
|
isDrawingRef.current = true;
|
||||||
pointerIdRef.current = pointerId;
|
pointerIdRef.current = pointerId;
|
||||||
pointsRef.current = [pos];
|
pointsRef.current = [pos];
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ const PencilInstruction: FC = () => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ قلم"
|
label="رنگ قلم"
|
||||||
value={defaults.stroke}
|
value={defaults.stroke}
|
||||||
|
opacity={defaults.opacity}
|
||||||
|
onOpacityChange={(value) => updateDefaults({ opacity: value })}
|
||||||
onChange={(value) => updateDefaults({ stroke: value })}
|
onChange={(value) => updateDefaults({ stroke: value })}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -106,13 +106,6 @@ const TextInstruction: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// State محلی برای نمایش شفافیت
|
|
||||||
const [opacityDisplay, setOpacityDisplay] = useState<string>(`${formState.opacity}`);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOpacityDisplay(`${formState.opacity}`);
|
|
||||||
}, [formState.opacity]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -164,41 +157,14 @@ const TextInstruction: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-col gap-2">
|
<div className="mt-4">
|
||||||
<div className="flex gap-3">
|
<ColorPicker
|
||||||
<ColorPicker
|
label="رنگ"
|
||||||
label="رنگ"
|
value={formState.fill}
|
||||||
className="flex-1"
|
opacity={formState.opacity}
|
||||||
value={formState.fill}
|
onOpacityChange={(value) => handleChange("opacity", value)}
|
||||||
onChange={(value) => handleChange("fill", value)}
|
onChange={(value) => handleChange("fill", value)}
|
||||||
/>
|
/>
|
||||||
<div className="w-28">
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
label="شفافیت"
|
|
||||||
className="px-3"
|
|
||||||
value={opacityDisplay}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
setOpacityDisplay(value);
|
|
||||||
|
|
||||||
// اعمال تغییرات به صورت لایو اگر مقدار معتبر است
|
|
||||||
const numValue = parseInt(value);
|
|
||||||
if (!isNaN(numValue) && numValue >= 0 && numValue <= 100) {
|
|
||||||
handleChange("opacity", numValue);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onBlur={(e) => {
|
|
||||||
const numValue = parseInt(e.target.value) || 0;
|
|
||||||
const clampedValue = Math.min(100, Math.max(0, numValue));
|
|
||||||
setOpacityDisplay(`${clampedValue}`);
|
|
||||||
handleChange("opacity", clampedValue);
|
|
||||||
}}
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <div className="mt-4 flex gap-2">
|
{/* <div className="mt-4 flex gap-2">
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const CustomShapeSettings = ({ selectedObject, onUpdate }: CustomShapeSettingsPr
|
|||||||
};
|
};
|
||||||
const stroke = selectedObject.stroke ?? "#1e40af";
|
const stroke = selectedObject.stroke ?? "#1e40af";
|
||||||
const strokeWidth = selectedObject.strokeWidth ?? 2;
|
const strokeWidth = selectedObject.strokeWidth ?? 2;
|
||||||
|
const opacity = selectedObject.opacity ?? 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -29,6 +30,8 @@ const CustomShapeSettings = ({ selectedObject, onUpdate }: CustomShapeSettingsPr
|
|||||||
label="رنگ پر"
|
label="رنگ پر"
|
||||||
value={fill}
|
value={fill}
|
||||||
readOnly={fillType === "gradient"}
|
readOnly={fillType === "gradient"}
|
||||||
|
opacity={opacity}
|
||||||
|
onOpacityChange={(value) => onUpdate(selectedObject.id, { opacity: value })}
|
||||||
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => {
|
|||||||
removeRow,
|
removeRow,
|
||||||
removeColumn,
|
removeColumn,
|
||||||
changeCellBackground,
|
changeCellBackground,
|
||||||
|
changeCellBackgroundOpacity,
|
||||||
selectedCellId,
|
selectedCellId,
|
||||||
} = useEditorStore();
|
} = useEditorStore();
|
||||||
|
|
||||||
@@ -50,6 +51,12 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCellBackgroundOpacityChange = (opacity: number) => {
|
||||||
|
if (selectedCellId && tableDataObj) {
|
||||||
|
changeCellBackgroundOpacity(selectedObject.id, selectedCellId, opacity);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const selectedCell = selectedCellId && tableDataObj ? tableDataObj.cells[selectedCellId] : null;
|
const selectedCell = selectedCellId && tableDataObj ? tableDataObj.cells[selectedCellId] : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,6 +118,8 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ پسزمینه سلول"
|
label="رنگ پسزمینه سلول"
|
||||||
value={selectedCell.background}
|
value={selectedCell.background}
|
||||||
|
opacity={selectedCell.backgroundOpacity ?? 100}
|
||||||
|
onOpacityChange={handleCellBackgroundOpacityChange}
|
||||||
onChange={handleCellBackgroundChange}
|
onChange={handleCellBackgroundChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ const LineSettings = ({ selectedObject, onUpdate }: LineSettingsProps) => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ خط"
|
label="رنگ خط"
|
||||||
value={selectedObject.stroke || "#000000"}
|
value={selectedObject.stroke || "#000000"}
|
||||||
|
opacity={selectedObject.opacity ?? 100}
|
||||||
|
onOpacityChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, { opacity: value })
|
||||||
|
}
|
||||||
onChange={(value) => onUpdate(selectedObject.id, { stroke: value })}
|
onChange={(value) => onUpdate(selectedObject.id, { stroke: value })}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ const LinkSettings = ({ selectedObject, onUpdate }: LinkSettingsProps) => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ متن"
|
label="رنگ متن"
|
||||||
value={selectedObject.fill || "#0000ff"}
|
value={selectedObject.fill || "#0000ff"}
|
||||||
|
opacity={selectedObject.opacity ?? 100}
|
||||||
|
onOpacityChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, { opacity: value })
|
||||||
|
}
|
||||||
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type ShapeSettingsProps = {
|
|||||||
const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => {
|
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 baseOpacity = selectedObject.opacity ?? 100;
|
||||||
const baseFill = selectedObject.fill ?? "#3b82f6";
|
const baseFill = selectedObject.fill ?? "#3b82f6";
|
||||||
const fillType = selectedObject.fillType ?? "solid";
|
const fillType = selectedObject.fillType ?? "solid";
|
||||||
const gradient = selectedObject.gradient ?? {
|
const gradient = selectedObject.gradient ?? {
|
||||||
@@ -50,6 +51,12 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => {
|
|||||||
label="رنگ پسزمینه"
|
label="رنگ پسزمینه"
|
||||||
value={baseFill}
|
value={baseFill}
|
||||||
readOnly={fillType === "gradient"}
|
readOnly={fillType === "gradient"}
|
||||||
|
opacity={baseOpacity}
|
||||||
|
onOpacityChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, {
|
||||||
|
opacity: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
onUpdate(selectedObject.id, {
|
onUpdate(selectedObject.id, {
|
||||||
fill: value,
|
fill: value,
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ const TextSettings = ({ selectedObject, onUpdate }: TextSettingsProps) => {
|
|||||||
<ColorPicker
|
<ColorPicker
|
||||||
label="رنگ متن"
|
label="رنگ متن"
|
||||||
value={selectedObject.fill || "#000000"}
|
value={selectedObject.fill || "#000000"}
|
||||||
|
opacity={selectedObject.opacity ?? 100}
|
||||||
|
onOpacityChange={(value) =>
|
||||||
|
onUpdate(selectedObject.id, { opacity: value })
|
||||||
|
}
|
||||||
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
onChange={(value) => onUpdate(selectedObject.id, { fill: value })}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
{...blurProps}
|
{...blurProps}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const ArrowShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePro
|
|||||||
hitStrokeWidth={20}
|
hitStrokeWidth={20}
|
||||||
perfectDrawEnabled={false}
|
perfectDrawEnabled={false}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (shapeRef.current) {
|
if (shapeRef.current) {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePr
|
|||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
{...blurProps}
|
{...blurProps}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const LineShape = ({ obj, onSelect, onUpdate, draggable }: ShapeProps) => {
|
|||||||
hitStrokeWidth={20}
|
hitStrokeWidth={20}
|
||||||
perfectDrawEnabled={false}
|
perfectDrawEnabled={false}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (shapeRef.current) {
|
if (shapeRef.current) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const LinkShape = ({
|
|||||||
fontStyle={fontWeight}
|
fontStyle={fontWeight}
|
||||||
underline={true}
|
underline={true}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
// در حالت editor، لینکها قابل کلیک نیستند
|
// در حالت editor، لینکها قابل کلیک نیستند
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap
|
|||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
{...blurProps}
|
{...blurProps}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
resolveTextMaxWidth,
|
resolveTextMaxWidth,
|
||||||
usesWrappedLayout,
|
usesWrappedLayout,
|
||||||
} from "@/pages/editor/utils/textStyle";
|
} from "@/pages/editor/utils/textStyle";
|
||||||
|
import { getColorWithOpacity } from "@/pages/editor/utils/colorOpacity";
|
||||||
|
|
||||||
const getFontWeight = (fontWeight?: string): string => {
|
const getFontWeight = (fontWeight?: string): string => {
|
||||||
if (!fontWeight) return "normal";
|
if (!fontWeight) return "normal";
|
||||||
@@ -38,19 +39,6 @@ const getKonvaFontStyle = (fontWeight?: string): string => {
|
|||||||
return "normal";
|
return "normal";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getColorWithOpacity = (fill?: string, opacity?: number): string => {
|
|
||||||
if (!fill) return "#000000";
|
|
||||||
if (opacity === undefined || opacity === 100) return fill;
|
|
||||||
|
|
||||||
const hex = fill.replace("#", "");
|
|
||||||
const r = parseInt(hex.substring(0, 2), 16);
|
|
||||||
const g = parseInt(hex.substring(2, 4), 16);
|
|
||||||
const b = parseInt(hex.substring(4, 6), 16);
|
|
||||||
const alpha = opacity / 100;
|
|
||||||
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TextShape = ({
|
const TextShape = ({
|
||||||
obj,
|
obj,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape
|
|||||||
strokeWidth={displayStrokeWidth}
|
strokeWidth={displayStrokeWidth}
|
||||||
dash={isMask && showGuide ? [10, 5] : undefined}
|
dash={isMask && showGuide ? [10, 5] : undefined}
|
||||||
rotation={obj.rotation || 0}
|
rotation={obj.rotation || 0}
|
||||||
|
opacity={(obj.opacity ?? 100) / 100}
|
||||||
{...blurProps}
|
{...blurProps}
|
||||||
draggable={draggable}
|
draggable={draggable}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const createInitialCells = (
|
|||||||
id: cellId,
|
id: cellId,
|
||||||
text: "",
|
text: "",
|
||||||
background: "#f5f5f5",
|
background: "#f5f5f5",
|
||||||
|
backgroundOpacity: 100,
|
||||||
width: cellWidth,
|
width: cellWidth,
|
||||||
height: cellHeight,
|
height: cellHeight,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,6 +127,11 @@ type EditorStoreType = {
|
|||||||
cellId: string,
|
cellId: string,
|
||||||
color: string,
|
color: string,
|
||||||
) => void;
|
) => void;
|
||||||
|
changeCellBackgroundOpacity: (
|
||||||
|
tableId: string,
|
||||||
|
cellId: string,
|
||||||
|
opacity: number,
|
||||||
|
) => void;
|
||||||
applyTableResize: (
|
applyTableResize: (
|
||||||
tableId: string,
|
tableId: string,
|
||||||
newWidth: number,
|
newWidth: number,
|
||||||
@@ -640,6 +645,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
id: cellId,
|
id: cellId,
|
||||||
text: "",
|
text: "",
|
||||||
background: "#f5f5f5",
|
background: "#f5f5f5",
|
||||||
|
backgroundOpacity: 100,
|
||||||
width: tableData.cellWidth,
|
width: tableData.cellWidth,
|
||||||
height: tableData.cellHeight,
|
height: tableData.cellHeight,
|
||||||
};
|
};
|
||||||
@@ -670,6 +676,7 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
id: cellId,
|
id: cellId,
|
||||||
text: "",
|
text: "",
|
||||||
background: "#f5f5f5",
|
background: "#f5f5f5",
|
||||||
|
backgroundOpacity: 100,
|
||||||
width: tableData.cellWidth,
|
width: tableData.cellWidth,
|
||||||
height: tableData.cellHeight,
|
height: tableData.cellHeight,
|
||||||
};
|
};
|
||||||
@@ -776,6 +783,28 @@ export const useEditorStore = create<EditorStoreType>((set, get) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
changeCellBackgroundOpacity: (tableId, cellId, opacity) => {
|
||||||
|
get().commitObjectHistoryBeforeChange();
|
||||||
|
const state = get();
|
||||||
|
const obj = state.objects.find((o) => o.id === tableId);
|
||||||
|
if (!obj || !obj.tableData) return;
|
||||||
|
|
||||||
|
const { tableData } = obj;
|
||||||
|
const updatedCells = {
|
||||||
|
...tableData.cells,
|
||||||
|
[cellId]: {
|
||||||
|
...tableData.cells[cellId],
|
||||||
|
backgroundOpacity: Math.min(100, Math.max(0, opacity)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
state.updateObject(tableId, {
|
||||||
|
tableData: {
|
||||||
|
...tableData,
|
||||||
|
cells: updatedCells,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
applyTableResize: (tableId, newWidth, newHeight) => {
|
applyTableResize: (tableId, newWidth, newHeight) => {
|
||||||
const state = get();
|
const state = get();
|
||||||
const obj = state.objects.find((o) => o.id === tableId);
|
const obj = state.objects.find((o) => o.id === tableId);
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export type TableCell = {
|
|||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
background: string;
|
background: string;
|
||||||
|
/** شفافیت پسزمینه سلول (۰–۱۰۰) */
|
||||||
|
backgroundOpacity?: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { create } from "zustand";
|
|||||||
export type PencilDefaults = {
|
export type PencilDefaults = {
|
||||||
stroke: string;
|
stroke: string;
|
||||||
strokeWidth: number;
|
strokeWidth: number;
|
||||||
|
opacity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PencilDefaultsStoreType = {
|
type PencilDefaultsStoreType = {
|
||||||
@@ -14,6 +15,7 @@ export const usePencilDefaultsStore = create<PencilDefaultsStoreType>((set) => (
|
|||||||
defaults: {
|
defaults: {
|
||||||
stroke: "#000000",
|
stroke: "#000000",
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
|
opacity: 100,
|
||||||
},
|
},
|
||||||
updateDefaults: (updates) =>
|
updateDefaults: (updates) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/** Converts a hex color + 0–100 opacity to rgba (editor/viewer convention). */
|
||||||
|
export const getColorWithOpacity = (fill?: string, opacity?: number): string => {
|
||||||
|
if (!fill) return "#000000";
|
||||||
|
if (opacity === undefined || opacity >= 100) return fill;
|
||||||
|
|
||||||
|
const hex = fill.replace("#", "");
|
||||||
|
if (!/^[0-9A-Fa-f]{3,8}$/.test(hex)) return fill;
|
||||||
|
|
||||||
|
const paddedHex =
|
||||||
|
hex.length === 3
|
||||||
|
? hex
|
||||||
|
.split("")
|
||||||
|
.map((c) => c + c)
|
||||||
|
.join("")
|
||||||
|
: hex.length === 4
|
||||||
|
? hex
|
||||||
|
.split("")
|
||||||
|
.map((c) => c + c)
|
||||||
|
.join("")
|
||||||
|
: hex.padEnd(6, "0").slice(0, 6);
|
||||||
|
|
||||||
|
const r = parseInt(paddedHex.substring(0, 2), 16);
|
||||||
|
const g = parseInt(paddedHex.substring(2, 4), 16);
|
||||||
|
const b = parseInt(paddedHex.substring(4, 6), 16);
|
||||||
|
const alpha = opacity / 100;
|
||||||
|
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
|
};
|
||||||
@@ -950,7 +950,10 @@ const BookPage = memo(forwardRef<HTMLDivElement, BookPageProps>(
|
|||||||
key={cellId}
|
key={cellId}
|
||||||
style={{
|
style={{
|
||||||
border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : `${1 * scale}px solid #ccc`,
|
border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : `${1 * scale}px solid #ccc`,
|
||||||
backgroundColor: cell?.background || '#ffffff',
|
backgroundColor: getColorWithOpacity(
|
||||||
|
cell?.background || '#ffffff',
|
||||||
|
cell?.backgroundOpacity,
|
||||||
|
),
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|||||||
Reference in New Issue
Block a user