color opacity
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-08 09:21:41 +03:30
parent 3c764a1652
commit a2a591420c
25 changed files with 178 additions and 62 deletions
+51 -3
View File
@@ -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 -1
View File
@@ -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="رنگ"
className="flex-1"
value={formState.fill} value={formState.fill}
opacity={formState.opacity}
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,
}; };
+29
View File
@@ -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) => ({
+28
View File
@@ -0,0 +1,28 @@
/** Converts a hex color + 0100 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})`;
};
+4 -1
View File
@@ -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',