bg opcity
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-08 15:50:21 +03:30
parent a2a591420c
commit 33cce93064
13 changed files with 230 additions and 167 deletions
+11
View File
@@ -23,6 +23,7 @@
"moment-jalaali": "^0.10.4", "moment-jalaali": "^0.10.4",
"page-flip": "^2.0.7", "page-flip": "^2.0.7",
"react": "^19.2.0", "react": "^19.2.0",
"react-colorful": "^5.7.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
"react-i18next": "^16.3.0", "react-i18next": "^16.3.0",
@@ -4556,6 +4557,16 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
} }
}, },
"node_modules/react-colorful": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.7.0.tgz",
"integrity": "sha512-fuesYIemttah97XmsIHmz4OORDHiSFzyc9HMAIrCHJou2jaRQmL8cFJ76K4zQhhj8jzwOBlOi4BaGTjjOZCfTg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-date-object": { "node_modules/react-date-object": {
"version": "2.1.9", "version": "2.1.9",
"resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz", "resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz",
+1
View File
@@ -25,6 +25,7 @@
"moment-jalaali": "^0.10.4", "moment-jalaali": "^0.10.4",
"page-flip": "^2.0.7", "page-flip": "^2.0.7",
"react": "^19.2.0", "react": "^19.2.0",
"react-colorful": "^5.7.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
"react-i18next": "^16.3.0", "react-i18next": "^16.3.0",
+176 -145
View File
@@ -1,175 +1,206 @@
import { type FC, type InputHTMLAttributes, useState, useRef, useEffect } from 'react' import { type FC, type InputHTMLAttributes, useEffect, useRef, useState } from "react";
import { clx } from '../helpers/utils' import { HexAlphaColorPicker, HexColorInput, HexColorPicker } from "react-colorful";
import Error from './Error' import { clx } from "../helpers/utils";
import Input from './Input' import Error from "./Error";
type Props = { type Props = {
label?: string label?: string;
className?: string className?: string;
error_text?: string error_text?: string;
isNotRequired?: boolean isNotRequired?: boolean;
value?: string value?: string;
onChange?: (value: string) => void onChange?: (value: string) => void;
opacity?: number opacity?: number;
onOpacityChange?: (value: number) => void onOpacityChange?: (value: number) => void;
opacityLabel?: string 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 [opacityDisplay, setOpacityDisplay] = useState<string>(`${props.opacity ?? 100}`);
const colorInputRef = useRef<HTMLInputElement>(null) const [isOpen, setIsOpen] = useState(false);
const pickerOpenGuardRef = useRef(false) const wrapperRef = useRef<HTMLDivElement>(null);
const showOpacity = props.onOpacityChange !== undefined const showOpacity = props.onOpacityChange !== undefined;
const inputClass = clx( const clampOpacity = (rawValue: number): number => Math.min(100, Math.max(0, rawValue));
'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',
)
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => { const normalizeHex = (value: string): string => {
const newColor = event.target.value if (!value) return "#000000";
setColor(newColor) const withHash = value.startsWith("#") ? value : `#${value}`;
props.onChange?.(newColor) const shortMatch = /^#([0-9A-Fa-f]{3})$/.exec(withHash);
if (shortMatch) {
const [r, g, b] = shortMatch[1]!.split("");
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
} }
const longMatch = /^#([0-9A-Fa-f]{6})$/.exec(withHash);
const handleTextChange = (event: React.ChangeEvent<HTMLInputElement>) => { if (longMatch) {
const value = event.target.value return `#${longMatch[1]}`.toLowerCase();
if (/^#[0-9A-Fa-f]{0,6}$/.test(value) || value === '') {
const finalColor = value || '#000000'
setColor(finalColor)
props.onChange?.(finalColor)
}
} }
return "#000000";
};
const openNativeColorPicker = () => { const toHexAlpha = (hex: string, opacity: number): string => {
if (props.readOnly) return const normalizedHex = normalizeHex(hex);
if (pickerOpenGuardRef.current) return const alpha = Math.round((clampOpacity(opacity) / 100) * 255)
pickerOpenGuardRef.current = true .toString(16)
colorInputRef.current?.click() .padStart(2, "0");
window.setTimeout(() => { return `${normalizedHex}${alpha}`;
pickerOpenGuardRef.current = false };
}, 400)
const fromHexAlpha = (hexAlpha: string): { hex: string; opacity: number } => {
const match = /^#([0-9A-Fa-f]{8})$/.exec(hexAlpha);
if (!match) {
return {
hex: normalizeHex(hexAlpha),
opacity: 100,
};
} }
const rgbaHex = match[1]!.toLowerCase();
const rgb = `#${rgbaHex.slice(0, 6)}`;
const alphaHex = rgbaHex.slice(6, 8);
const opacity = Math.round((parseInt(alphaHex, 16) / 255) * 100);
return { hex: rgb, opacity: clampOpacity(opacity) };
};
useEffect(() => { const inputClass = clx("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");
if (props.value !== undefined) {
setColor(props.value)
}
}, [props.value])
useEffect(() => { const handleColorChange = (newColor: string) => {
if (props.opacity !== undefined) { const normalized = normalizeHex(newColor);
setOpacityDisplay(`${props.opacity}`) setColor(normalized);
} props.onChange?.(normalized);
}, [props.opacity]) };
const handleOpacityChange = (rawValue: string) => { const togglePicker = () => {
setOpacityDisplay(rawValue) if (props.readOnly) return;
const numValue = parseInt(rawValue, 10) setIsOpen((prev) => !prev);
if (!Number.isNaN(numValue) && numValue >= 0 && numValue <= 100) { };
props.onOpacityChange?.(numValue)
} useEffect(() => {
if (props.value !== undefined) {
setColor(normalizeHex(props.value));
} }
}, [props.value]);
const handleOpacityBlur = (rawValue: string) => { useEffect(() => {
const numValue = parseInt(rawValue, 10) || 0 if (props.opacity !== undefined) {
const clampedValue = Math.min(100, Math.max(0, numValue)) setOpacityDisplay(`${props.opacity}`);
setOpacityDisplay(`${clampedValue}`)
props.onOpacityChange?.(clampedValue)
} }
}, [props.opacity]);
const colorField = ( useEffect(() => {
<div if (!isOpen) return;
className={clx('w-full', showOpacity && 'flex-1')}
const handlePointerDownOutside = (event: MouseEvent) => {
if (!wrapperRef.current?.contains(event.target as Node)) {
setIsOpen(false);
}
};
window.addEventListener("mousedown", handlePointerDownOutside);
return () => window.removeEventListener("mousedown", handlePointerDownOutside);
}, [isOpen]);
const handleOpacityChange = (rawValue: string) => {
setOpacityDisplay(rawValue);
const numValue = parseInt(rawValue, 10);
if (!Number.isNaN(numValue)) {
props.onOpacityChange?.(clampOpacity(numValue));
}
};
const handleOpacityBlur = (rawValue: string) => {
const numValue = parseInt(rawValue, 10) || 0;
const clampedValue = clampOpacity(numValue);
setOpacityDisplay(`${clampedValue}`);
props.onOpacityChange?.(clampedValue);
};
const handleAlphaPickerChange = (hexAlpha: string) => {
const { hex, opacity } = fromHexAlpha(hexAlpha);
setColor(hex);
setOpacityDisplay(`${opacity}`);
props.onChange?.(hex);
props.onOpacityChange?.(opacity);
};
const colorField = (
<div className={clx("w-full", showOpacity && "flex-1")}>
<label className="text-xs">{props.label}</label>
<div className={clx("w-full relative mt-1 rounded-xl", !props.readOnly && "cursor-pointer")}>
<input
type="text"
value={color}
onChange={(event) => handleColorChange(event.target.value)}
placeholder="#000000"
readOnly={props.readOnly}
title={!props.readOnly ? "کلیک برای باز کردن انتخابگر رنگ" : undefined}
onClick={togglePicker}
className={`${inputClass} ${props.readOnly ? "" : "cursor-pointer"}`}
/>
<span
aria-hidden
className={clx("absolute top-0 bottom-0 my-auto left-1 flex items-center justify-center min-h-[34px] min-w-[34px] rounded-lg pointer-events-none", props.readOnly && "opacity-50")}
> >
<label className='text-xs'> {/* <img
{props.label}
</label>
<div
className={clx(
'w-full relative mt-1 rounded-xl',
!props.readOnly && 'cursor-pointer'
)}
onClick={!props.readOnly ? openNativeColorPicker : undefined}
>
<input
type='text'
value={color}
onChange={handleTextChange}
placeholder='#000000'
readOnly={props.readOnly}
title={
!props.readOnly
? 'کلیک: پالت رنگ — با Tab وارد فیلد شوید تا کد را تایپ کنید'
: undefined
}
className={`${inputClass} cursor-pointer`}
/>
<span
aria-hidden
className={clx(
'absolute top-0 bottom-0 my-auto left-1 flex items-center justify-center min-h-[34px] min-w-[34px] rounded-lg pointer-events-none',
props.readOnly && 'opacity-50'
)}
>
{/* <img
src={ColorfilterIcon} src={ColorfilterIcon}
alt='' alt=''
className='size-7 select-none' className='size-7 select-none'
/> */} /> */}
</span> </span>
<span <span
aria-hidden aria-hidden
className={clx( className={clx("absolute top-0 bottom-0 my-auto right-1 flex items-center justify-center min-h-[34px] min-w-[34px] rounded-lg pointer-events-none", props.readOnly && "opacity-50")}
'absolute top-0 bottom-0 my-auto right-1 flex items-center justify-center min-h-[34px] min-w-[34px] rounded-lg pointer-events-none', >
props.readOnly && 'opacity-50' <span className="block w-5 h-5 rounded-full border border-border shadow-sm" style={{ backgroundColor: color }} />
)} </span>
>
<span
className='block w-5 h-5 rounded-full border border-border shadow-sm'
style={{ backgroundColor: color }}
/>
</span>
<input {isOpen && !props.readOnly && (
ref={colorInputRef} <div className="absolute right-0 z-50 mt-2 w-full rounded-xl border border-border bg-white p-3 shadow-lg space-y-3">
type='color' <div className="space-y-1">
value={color} {showOpacity ? (
onChange={handleColorChange} <HexAlphaColorPicker color={toHexAlpha(color, Number(opacityDisplay) || 100)} onChange={handleAlphaPickerChange} />
className='absolute opacity-0 pointer-events-none w-0 h-0' ) : (
/> <HexColorPicker color={color} onChange={handleColorChange} />
)}
</div> </div>
</div> <div className="flex items-center gap-2">
) <HexColorInput color={color} onChange={handleColorChange} prefixed className="h-9 max-w-[115px] flex-1 rounded-lg border border-border px-2 text-xs" />
{showOpacity && (
return ( <div className="flex-1">
<div className={clx('w-full', showOpacity && 'flex gap-3', props.className)}> <input
{colorField} type="number"
{showOpacity && ( min={0}
<div className='w-28 shrink-0'> max={100}
<Input value={opacityDisplay}
type='number' onChange={(e) => handleOpacityChange(e.target.value)}
label={props.opacityLabel ?? 'شفافیت'} onBlur={(e) => handleOpacityBlur(e.target.value)}
className='px-3' className="h-9 w-full rounded-lg border border-border px-2 text-xs"
value={opacityDisplay} title={props.opacityLabel ?? "شفافیت"}
onChange={(e) => handleOpacityChange(e.target.value)} />
onBlur={(e) => handleOpacityBlur(e.target.value)}
min={0}
max={100}
/>
</div> </div>
)}
</div>
{showOpacity && (
<div className="text-xs text-gray-500">
{props.opacityLabel ?? "شفافیت"}: {opacityDisplay}%
</div>
)} )}
</div>
)}
</div>
</div>
);
{props.error_text && ( return (
<Error errorText={props.error_text} /> <div ref={wrapperRef} className={clx("w-full", props.className)}>
)} {colorField}
</div>
)
}
export default ColorPicker {props.error_text && <Error errorText={props.error_text} />}
</div>
);
};
export default ColorPicker;
+7 -1
View File
@@ -27,6 +27,7 @@ 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"; import { getKonvaGradientProps } from "../utils/gradient";
import { getColorWithOpacity } from "../utils/colorOpacity";
import { STICKER_DRAG_MIME } from "../types/IconTypes"; import { STICKER_DRAG_MIME } from "../types/IconTypes";
import { GALLERY_DRAG_MIME } from "../types/GalleryTypes"; import { GALLERY_DRAG_MIME } from "../types/GalleryTypes";
import { import {
@@ -72,7 +73,12 @@ 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 === 'gradient' currentPage?.backgroundType === 'color'
? getColorWithOpacity(
currentPage.backgroundColor,
currentPage.backgroundOpacity ?? 100,
)
: currentPage?.backgroundType === 'gradient'
? currentPage.backgroundColor ? currentPage.backgroundColor
: '#ffffff'; : '#ffffff';
const bgGradient = currentPage?.backgroundGradient; const bgGradient = currentPage?.backgroundGradient;
+15 -17
View File
@@ -1,10 +1,9 @@
import { useEffect, useRef, useState } from 'react' import { useEffect, useState } from 'react'
import { clx } from '@/helpers/utils' import { clx } from '@/helpers/utils'
import { useEditorStore } from '../store/editorStore' import { useEditorStore } from '../store/editorStore'
import type { DisplayStyle, BackgroundType } from '../store/editorStore' 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 ColorPicker from '@/components/ColorPicker' import ColorPicker from '@/components/ColorPicker'
import Select from '@/components/Select' import Select from '@/components/Select'
import { toCssLinearGradient } from '../utils/gradient' import { toCssLinearGradient } from '../utils/gradient'
@@ -39,11 +38,11 @@ const SettingsPanel = () => {
updateDocumentSettings, updateDocumentSettings,
updateCurrentPageBackground, updateCurrentPageBackground,
} = useEditorStore() } = useEditorStore()
const colorInputRef = useRef<HTMLInputElement>(null)
const { mutateAsync: uploadFile, isPending: mediaLoading } = useSingleUpload() const { mutateAsync: uploadFile, isPending: mediaLoading } = useSingleUpload()
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 backgroundOpacity = currentPage?.backgroundOpacity ?? 100
const backgroundGradient = currentPage?.backgroundGradient ?? { const backgroundGradient = currentPage?.backgroundGradient ?? {
from: '#ffffff', from: '#ffffff',
to: '#e2e8f0', to: '#e2e8f0',
@@ -234,30 +233,29 @@ const SettingsPanel = () => {
/> />
))} ))}
{/* Color Wheel */} <div className="w-full mt-2">
<button <ColorPicker
onClick={() => colorInputRef.current?.click()} label="رنگ دلخواه"
className="w-9 h-9 rounded-xl border-2 border-transparent hover:scale-105 transition-all overflow-hidden relative"
title="رنگ دلخواه"
>
<img src={ColorsImage} alt="انتخاب رنگ" className="w-full h-full object-cover" />
<input
ref={colorInputRef}
type="color"
value={backgroundColor} value={backgroundColor}
onChange={(e) => updateCurrentPageBackground({ backgroundColor: e.target.value })} opacity={backgroundOpacity}
className="absolute inset-0 opacity-0 w-full h-full cursor-pointer" onChange={(value) =>
updateCurrentPageBackground({ backgroundColor: value })
}
onOpacityChange={(value) =>
updateCurrentPageBackground({ backgroundOpacity: value })
}
/> />
</button> </div>
</div> </div>
{/* نمایش رنگ انتخاب‌شده */} {/* نمایش رنگ انتخاب‌شده */}
<div className="mt-3 flex items-center gap-2"> <div className="mt-3 flex items-center gap-2">
<div <div
className="w-6 h-6 rounded-lg border border-border" className="w-6 h-6 rounded-lg border border-border"
style={{ backgroundColor }} style={{ backgroundColor, opacity: backgroundOpacity / 100 }}
/> />
<span className="text-xs text-gray-500 font-mono">{backgroundColor}</span> <span className="text-xs text-gray-500 font-mono">{backgroundColor}</span>
<span className="text-xs text-gray-400">{backgroundOpacity}%</span>
</div> </div>
</div> </div>
)} )}
@@ -60,6 +60,7 @@ export const createInitialPage = (name: string): Page => ({
guides: [], guides: [],
backgroundType: "color", backgroundType: "color",
backgroundColor: "#ffffff", backgroundColor: "#ffffff",
backgroundOpacity: 100,
backgroundGradient: { backgroundGradient: {
from: "#ffffff", from: "#ffffff",
to: "#e2e8f0", to: "#e2e8f0",
+5
View File
@@ -68,6 +68,7 @@ type PageBackgroundSettings = Pick<
Page, Page,
| "backgroundType" | "backgroundType"
| "backgroundColor" | "backgroundColor"
| "backgroundOpacity"
| "backgroundGradient" | "backgroundGradient"
| "backgroundImageUrl" | "backgroundImageUrl"
| "backgroundVideoUrl" | "backgroundVideoUrl"
@@ -191,6 +192,7 @@ const DEFAULT_DOCUMENT_SETTINGS: DocumentSettings = {
smartGuide: true, smartGuide: true,
backgroundType: "color", backgroundType: "color",
backgroundColor: "#ffffff", backgroundColor: "#ffffff",
backgroundOpacity: 100,
backgroundGradient: { backgroundGradient: {
from: "#ffffff", from: "#ffffff",
to: "#e2e8f0", to: "#e2e8f0",
@@ -887,6 +889,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,
backgroundOpacity: pageToDuplicate.backgroundOpacity ?? 100,
backgroundGradient: pageToDuplicate.backgroundGradient, backgroundGradient: pageToDuplicate.backgroundGradient,
backgroundImageUrl: pageToDuplicate.backgroundImageUrl, backgroundImageUrl: pageToDuplicate.backgroundImageUrl,
backgroundVideoUrl: pageToDuplicate.backgroundVideoUrl, backgroundVideoUrl: pageToDuplicate.backgroundVideoUrl,
@@ -1082,6 +1085,8 @@ 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",
backgroundOpacity:
page.backgroundOpacity ?? fallbackSettings.backgroundOpacity ?? 100,
backgroundGradient: page.backgroundGradient ?? backgroundGradient: page.backgroundGradient ??
fallbackSettings.backgroundGradient ?? { fallbackSettings.backgroundGradient ?? {
from: "#ffffff", from: "#ffffff",
@@ -123,6 +123,7 @@ export type Page = {
guides: PageGuide[]; guides: PageGuide[];
backgroundType: BackgroundType; backgroundType: BackgroundType;
backgroundColor: string; backgroundColor: string;
backgroundOpacity?: number;
backgroundGradient?: LinearGradient; backgroundGradient?: LinearGradient;
backgroundImageUrl: string; backgroundImageUrl: string;
backgroundVideoUrl: string; backgroundVideoUrl: string;
@@ -139,6 +140,7 @@ export type DocumentSettings = {
smartGuide: boolean; smartGuide: boolean;
backgroundType: BackgroundType; backgroundType: BackgroundType;
backgroundColor: string; backgroundColor: string;
backgroundOpacity?: number;
backgroundGradient?: LinearGradient; backgroundGradient?: LinearGradient;
backgroundImageUrl: string; backgroundImageUrl: string;
backgroundVideoUrl: string; backgroundVideoUrl: string;
+4 -2
View File
@@ -57,6 +57,7 @@ type BookPageProps = {
onLinkClick?: (linkUrl: string) => void; onLinkClick?: (linkUrl: string) => void;
backgroundType?: 'color' | 'gradient' | 'image' | 'video'; backgroundType?: 'color' | 'gradient' | 'image' | 'video';
backgroundColor?: string; backgroundColor?: string;
backgroundOpacity?: number;
backgroundGradient?: { backgroundGradient?: {
from: string; from: string;
to: string; to: string;
@@ -94,9 +95,10 @@ type BookPageProps = {
* نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند * نیاز به forwardRef دارد تا react-pageflip بتواند ref را مدیریت کند
*/ */
const BookPage = memo(forwardRef<HTMLDivElement, BookPageProps>( const BookPage = memo(forwardRef<HTMLDivElement, BookPageProps>(
({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, backgroundVideoUrl, entrancePhase = 'idle', disableEntranceAnimations = false, onDeferredEntranceReady, hideMediaLoadingOverlay = false, staticMedia = false, showPaperShadow = true, useHardPage = false, idSuffix = '' }, ref) => { ({ page, scale = 1, pageWidth, pageHeight, onLinkClick, backgroundType, backgroundColor, backgroundOpacity, backgroundGradient, backgroundImageUrl, backgroundVideoUrl, entrancePhase = 'idle', disableEntranceAnimations = false, onDeferredEntranceReady, hideMediaLoadingOverlay = false, staticMedia = false, showPaperShadow = true, useHardPage = false, idSuffix = '' }, ref) => {
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 effectiveBackgroundOpacity = backgroundOpacity ?? page.backgroundOpacity ?? 100;
const effectiveBackgroundGradient = backgroundGradient ?? page.backgroundGradient; const effectiveBackgroundGradient = backgroundGradient ?? page.backgroundGradient;
const effectiveBackgroundImageUrl = backgroundImageUrl ?? page.backgroundImageUrl ?? ''; const effectiveBackgroundImageUrl = backgroundImageUrl ?? page.backgroundImageUrl ?? '';
const effectiveBackgroundVideoUrl = backgroundVideoUrl ?? page.backgroundVideoUrl ?? ''; const effectiveBackgroundVideoUrl = backgroundVideoUrl ?? page.backgroundVideoUrl ?? '';
@@ -1034,7 +1036,7 @@ const BookPage = memo(forwardRef<HTMLDivElement, BookPageProps>(
? { ? {
backgroundImage: toCssLinearGradient(effectiveBackgroundGradient), backgroundImage: toCssLinearGradient(effectiveBackgroundGradient),
} }
: { backgroundColor: effectiveBackgroundColor }; : { backgroundColor: getColorWithOpacity(effectiveBackgroundColor, effectiveBackgroundOpacity) };
const showLoadingOverlay = !hideMediaLoadingOverlay && !isMediaReady; const showLoadingOverlay = !hideMediaLoadingOverlay && !isMediaReady;
+3 -2
View File
@@ -193,12 +193,13 @@ const BookViewer: FC<BookViewerProps> = ({ pages, catalogSize, documentSettings
); );
const backgroundType = documentSettings?.backgroundType ?? "color"; const backgroundType = documentSettings?.backgroundType ?? "color";
const backgroundColor = documentSettings?.backgroundColor ?? "#ffffff"; const backgroundColor = documentSettings?.backgroundColor ?? "#ffffff";
const backgroundOpacity = documentSettings?.backgroundOpacity ?? 100;
const backgroundGradient = documentSettings?.backgroundGradient; const backgroundGradient = documentSettings?.backgroundGradient;
const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? ""; const backgroundImageUrl = documentSettings?.backgroundImageUrl ?? "";
const backgroundVideoUrl = documentSettings?.backgroundVideoUrl ?? ""; const backgroundVideoUrl = documentSettings?.backgroundVideoUrl ?? "";
const pageBackgroundDefaults = useMemo( const pageBackgroundDefaults = useMemo(
() => ({ backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, backgroundVideoUrl }), () => ({ backgroundType, backgroundColor, backgroundOpacity, backgroundGradient, backgroundImageUrl, backgroundVideoUrl }),
[backgroundType, backgroundColor, backgroundGradient, backgroundImageUrl, backgroundVideoUrl], [backgroundType, backgroundColor, backgroundOpacity, backgroundGradient, backgroundImageUrl, backgroundVideoUrl],
); );
useEffect(() => { useEffect(() => {
+1
View File
@@ -7,6 +7,7 @@ export type PageData = {
elements: EditorObject[]; elements: EditorObject[];
backgroundType?: "color" | "gradient" | "image" | "video"; backgroundType?: "color" | "gradient" | "image" | "video";
backgroundColor?: string; backgroundColor?: string;
backgroundOpacity?: number;
backgroundGradient?: { backgroundGradient?: {
from: string; from: string;
to: string; to: string;
@@ -7,6 +7,7 @@ type ViewerDataPage = {
name: string; name: string;
backgroundType?: "color" | "gradient" | "image" | "video"; backgroundType?: "color" | "gradient" | "image" | "video";
backgroundColor?: string; backgroundColor?: string;
backgroundOpacity?: number;
backgroundGradient?: { backgroundGradient?: {
from: string; from: string;
to: string; to: string;
@@ -203,6 +204,7 @@ export function transformViewerDataToPages(data: ViewerData): PageData[] {
elements: objects, elements: objects,
backgroundType: page.backgroundType, backgroundType: page.backgroundType,
backgroundColor: page.backgroundColor, backgroundColor: page.backgroundColor,
backgroundOpacity: page.backgroundOpacity,
backgroundGradient: page.backgroundGradient, backgroundGradient: page.backgroundGradient,
backgroundImageUrl: page.backgroundImageUrl, backgroundImageUrl: page.backgroundImageUrl,
backgroundVideoUrl: page.backgroundVideoUrl, backgroundVideoUrl: page.backgroundVideoUrl,
+2
View File
@@ -3,6 +3,7 @@ import type { PageData } from "@/pages/viewer/types";
export type PageBackgroundDefaults = { export type PageBackgroundDefaults = {
backgroundType?: "color" | "gradient" | "image" | "video"; backgroundType?: "color" | "gradient" | "image" | "video";
backgroundColor?: string; backgroundColor?: string;
backgroundOpacity?: number;
backgroundGradient?: { from: string; to: string; angle: number }; backgroundGradient?: { from: string; to: string; angle: number };
backgroundImageUrl?: string; backgroundImageUrl?: string;
backgroundVideoUrl?: string; backgroundVideoUrl?: string;
@@ -13,6 +14,7 @@ export function resolvePageBackground(page: PageData, defaults: PageBackgroundDe
return { return {
backgroundType: page.backgroundType ?? defaults.backgroundType ?? "color", backgroundType: page.backgroundType ?? defaults.backgroundType ?? "color",
backgroundColor: page.backgroundColor ?? defaults.backgroundColor ?? "#ffffff", backgroundColor: page.backgroundColor ?? defaults.backgroundColor ?? "#ffffff",
backgroundOpacity: page.backgroundOpacity ?? defaults.backgroundOpacity ?? 100,
backgroundGradient: page.backgroundGradient ?? defaults.backgroundGradient ?? { from: "#ffffff", to: "#ffffff", angle: 0 }, backgroundGradient: page.backgroundGradient ?? defaults.backgroundGradient ?? { from: "#ffffff", to: "#ffffff", angle: 0 },
backgroundImageUrl: page.backgroundImageUrl ?? defaults.backgroundImageUrl ?? "", backgroundImageUrl: page.backgroundImageUrl ?? defaults.backgroundImageUrl ?? "",
backgroundVideoUrl: page.backgroundVideoUrl ?? defaults.backgroundVideoUrl ?? "", backgroundVideoUrl: page.backgroundVideoUrl ?? defaults.backgroundVideoUrl ?? "",