121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
import type { LayerProps } from "@/lib/studio-types";
|
|
|
|
export type ShapeKind = "rect" | "circle" | "line" | "arrow";
|
|
|
|
export type TextAlign = "left" | "center" | "right";
|
|
|
|
export type TextAnimation =
|
|
| "none"
|
|
| "fadeIn"
|
|
| "slideUp"
|
|
| "zoomIn"
|
|
| "typewriter";
|
|
|
|
export const FONT_FAMILY_OPTIONS = [
|
|
{ label: "Inter", value: "Inter, sans-serif" },
|
|
{ label: "Roboto", value: "Roboto, sans-serif" },
|
|
{ label: "Playfair", value: "Playfair Display, serif" },
|
|
{ label: "Montserrat", value: "Montserrat, sans-serif" },
|
|
{ label: "Oswald", value: "Oswald, sans-serif" },
|
|
] as const;
|
|
|
|
export const TEXT_ANIMATION_OPTIONS: { label: string; value: TextAnimation }[] =
|
|
[
|
|
{ label: "None", value: "none" },
|
|
{ label: "Fade In", value: "fadeIn" },
|
|
{ label: "Slide Up", value: "slideUp" },
|
|
{ label: "Zoom In", value: "zoomIn" },
|
|
{ label: "Typewriter", value: "typewriter" },
|
|
];
|
|
|
|
function asNumber(value: unknown, fallback: number): number {
|
|
return typeof value === "number" && !Number.isNaN(value) ? value : fallback;
|
|
}
|
|
|
|
function asString(value: unknown, fallback: string): string {
|
|
return typeof value === "string" ? value : fallback;
|
|
}
|
|
|
|
function asBoolean(value: unknown): boolean {
|
|
return value === true;
|
|
}
|
|
|
|
export function buildKonvaFontStyle(bold: boolean, italic: boolean): string {
|
|
if (bold && italic) return "bold italic";
|
|
if (bold) return "bold";
|
|
if (italic) return "italic";
|
|
return "normal";
|
|
}
|
|
|
|
export function getTextProps(props: LayerProps) {
|
|
const bold = asBoolean(props.bold);
|
|
const italic = asBoolean(props.italic);
|
|
const alignRaw = props.align;
|
|
const align: TextAlign =
|
|
alignRaw === "center" || alignRaw === "right" ? alignRaw : "left";
|
|
|
|
const animationRaw = props.animation;
|
|
const animation: TextAnimation =
|
|
animationRaw === "fadeIn" ||
|
|
animationRaw === "slideUp" ||
|
|
animationRaw === "zoomIn" ||
|
|
animationRaw === "typewriter"
|
|
? animationRaw
|
|
: "none";
|
|
|
|
return {
|
|
text: asString(props.text, "Text"),
|
|
fontSize: asNumber(props.fontSize, 48),
|
|
fill: asString(props.fill, "#111827"),
|
|
fontFamily: asString(props.fontFamily, "Inter, sans-serif"),
|
|
bold,
|
|
italic,
|
|
underline: asBoolean(props.underline),
|
|
align,
|
|
letterSpacing: asNumber(props.letterSpacing, 0),
|
|
lineHeight: asNumber(props.lineHeight, 1.2),
|
|
animation,
|
|
fontStyle: buildKonvaFontStyle(bold, italic),
|
|
};
|
|
}
|
|
|
|
export function getImageProps(props: LayerProps) {
|
|
return {
|
|
src:
|
|
typeof props.src === "string" && props.src.length > 0
|
|
? props.src
|
|
: undefined,
|
|
flipHorizontal: asBoolean(props.flipHorizontal),
|
|
flipVertical: asBoolean(props.flipVertical),
|
|
cornerRadius: asNumber(props.cornerRadius, 0),
|
|
};
|
|
}
|
|
|
|
export function getImageSrc(props: LayerProps): string | undefined {
|
|
return getImageProps(props).src;
|
|
}
|
|
|
|
export function getShapeProps(props: LayerProps) {
|
|
const shapeRaw = props.shape;
|
|
const shape: ShapeKind =
|
|
shapeRaw === "circle" ||
|
|
shapeRaw === "line" ||
|
|
shapeRaw === "arrow"
|
|
? shapeRaw
|
|
: "rect";
|
|
return {
|
|
shape,
|
|
fill: asString(props.fill, "#2563EB"),
|
|
stroke: asString(props.stroke, "#1E3A8A"),
|
|
strokeWidth: asNumber(props.strokeWidth, 0),
|
|
cornerRadius: asNumber(props.cornerRadius, 0),
|
|
};
|
|
}
|
|
|
|
export function mergeLayerProps(
|
|
current: LayerProps,
|
|
updates: LayerProps
|
|
): LayerProps {
|
|
return { ...current, ...updates };
|
|
}
|