44406b72db
Moteva-style AI design workbench replica. Home prompt creates a project; the project page provides an infinite canvas with node drag, zoom/pan, a design chat panel, history replay, image asset upload, and project save/regenerate. - Backend: go-zero, DDD layering, sqlc/pgx, optional PostgreSQL; memory or Redis cache; asynq job queue; MinIO/S3/R2/OSS object storage; sky-valley/pi agent runtime adapter. - Frontend: Next.js App Router + Vite artifact build, TypeScript, i18n, shadcn/ui components, auth (OTP/Turnstile/Google/WeChat). - Deploy: Docker Compose and k3s manifests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39 lines
1.5 KiB
TypeScript
39 lines
1.5 KiB
TypeScript
import type { CSSProperties } from "react";
|
|
import type { CanvasNode } from "@/domain/design";
|
|
|
|
export const defaultFrameFillColor = "#ffffff";
|
|
export const defaultFrameStrokeColor = "#2f80ff";
|
|
export const defaultFrameStrokeWidth = 1;
|
|
export const defaultFrameStrokeStyle = "solid";
|
|
|
|
export function framePreviewStyle(node: CanvasNode): CSSProperties {
|
|
const opacity = typeof node.opacity === "number" ? node.opacity : 1;
|
|
return {
|
|
background: colorWithOpacity(node.fillColor || defaultFrameFillColor, opacity),
|
|
borderColor: node.strokeColor || defaultFrameStrokeColor,
|
|
borderWidth: `${node.strokeWidth || defaultFrameStrokeWidth}px`,
|
|
borderStyle: normalizeStrokeStyle(node.strokeStyle)
|
|
};
|
|
}
|
|
|
|
export function normalizeStrokeStyle(value: string | undefined) {
|
|
if (value === "dashed" || value === "dotted") return value;
|
|
return defaultFrameStrokeStyle;
|
|
}
|
|
|
|
export function normalizeHexColor(value: string, fallback: string) {
|
|
const trimmed = value.trim();
|
|
if (/^#[0-9a-f]{6}$/i.test(trimmed)) return trimmed.toUpperCase();
|
|
if (/^[0-9a-f]{6}$/i.test(trimmed)) return `#${trimmed.toUpperCase()}`;
|
|
return fallback;
|
|
}
|
|
|
|
function colorWithOpacity(color: string, opacity: number) {
|
|
if (color === "transparent") return color;
|
|
const normalized = normalizeHexColor(color, defaultFrameFillColor);
|
|
const r = Number.parseInt(normalized.slice(1, 3), 16);
|
|
const g = Number.parseInt(normalized.slice(3, 5), 16);
|
|
const b = Number.parseInt(normalized.slice(5, 7), 16);
|
|
return `rgba(${r}, ${g}, ${b}, ${Math.min(Math.max(opacity, 0), 1)})`;
|
|
}
|