feat: complete TASK-WP4-04 color and dynamic stickers
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
import type { CanvasState } from "@dada/shared-contracts";
|
||||
|
||||
import { fontOption, type FontOption } from "./text-assets.js";
|
||||
import type { DynamicTemplateId } from "./dynamic-provider.js";
|
||||
|
||||
type CanvasElement = CanvasState["elements"][number];
|
||||
|
||||
export type DynamicTextValue =
|
||||
| "city"
|
||||
| "city_en"
|
||||
| "day"
|
||||
| "hour"
|
||||
| "latitude_dms"
|
||||
| "longitude_dms"
|
||||
| "meridiem"
|
||||
| "minute"
|
||||
| "month"
|
||||
| "month_en"
|
||||
| "nickname"
|
||||
| "time"
|
||||
| "title"
|
||||
| "year_short";
|
||||
|
||||
export interface DynamicImageLayer {
|
||||
assetId: string;
|
||||
height: number;
|
||||
width: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface DynamicTextLayer {
|
||||
align: "center" | "left";
|
||||
color: string;
|
||||
fontId: string;
|
||||
fontSize: number;
|
||||
value: DynamicTextValue;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface DynamicRenderModel {
|
||||
halfSize: { height: number; width: number };
|
||||
imageLayers: readonly DynamicImageLayer[];
|
||||
sourceCandidateId: string;
|
||||
textLayers: readonly DynamicTextLayer[];
|
||||
}
|
||||
|
||||
export const DYNAMIC_RESOURCE_VERSION = "wp4-dynamic-source-v1";
|
||||
|
||||
const dynamicFont = (fontId: string): FontOption => ({
|
||||
displayName: fontId,
|
||||
fontId,
|
||||
url: `/api/v1/assets/public/${DYNAMIC_RESOURCE_VERSION}/${fontId}`,
|
||||
});
|
||||
|
||||
export const DYNAMIC_FONT_OPTIONS: readonly FontOption[] = [
|
||||
dynamicFont("15974853bc3294ef68e7e6d58fe74fd7"),
|
||||
dynamicFont("46f8336813e4c48d06a1aef294fdccf6"),
|
||||
dynamicFont("53ca6b704728520da50c145eabb2e635"),
|
||||
dynamicFont("cca5efc0e02fb1bf62349bd68ef30fc1"),
|
||||
dynamicFont("dd25b35dcb7ba4476cbaa9a9592e39e2"),
|
||||
dynamicFont("e4210c9872f0c279b35273f230809821"),
|
||||
dynamicFont("f4bfd4132df2d6be97ceabadf3853505"),
|
||||
] as const;
|
||||
|
||||
export const DYNAMIC_RENDER_MODELS: Readonly<Record<DynamicTemplateId, DynamicRenderModel>> = {
|
||||
DYN001: {
|
||||
halfSize: { height: 42, width: 130 },
|
||||
imageLayers: [{ assetId: "DYN001-image28", height: 67, width: 219, x: -17.562, y: 2.203 }],
|
||||
sourceCandidateId: "l_POI01",
|
||||
textLayers: [{ align: "center", color: "#FFFFFF", fontId: "dd25b35dcb7ba4476cbaa9a9592e39e2", fontSize: 21, value: "title", x: 0, y: 0 }],
|
||||
},
|
||||
DYN002: {
|
||||
halfSize: { height: 48, width: 215 },
|
||||
imageLayers: [{ assetId: "DYN002-image29", height: 77, width: 232, x: 94.5, y: 0.5 }],
|
||||
sourceCandidateId: "l_POI02",
|
||||
textLayers: [
|
||||
{ align: "left", color: "#FFFFFF", fontId: "15974853bc3294ef68e7e6d58fe74fd7", fontSize: 19, value: "title", x: -94.5, y: 10.5 },
|
||||
{ align: "left", color: "#FFFFFF", fontId: "15974853bc3294ef68e7e6d58fe74fd7", fontSize: 10, value: "city_en", x: 0, y: 36 },
|
||||
],
|
||||
},
|
||||
DYN003: {
|
||||
halfSize: { height: 54, width: 215 },
|
||||
imageLayers: [{ assetId: "DYN003-image30", height: 85, width: 235, x: -93.857, y: -23.635 }],
|
||||
sourceCandidateId: "l_POI03",
|
||||
textLayers: [
|
||||
{ align: "left", color: "#FFFFFF", fontId: "dd25b35dcb7ba4476cbaa9a9592e39e2", fontSize: 18, value: "title", x: -54, y: 9 },
|
||||
{ align: "left", color: "#FFFFFF", fontId: "dd25b35dcb7ba4476cbaa9a9592e39e2", fontSize: 24, value: "city", x: -53, y: -16 },
|
||||
],
|
||||
},
|
||||
DYN004: {
|
||||
halfSize: { height: 48, width: 180 },
|
||||
imageLayers: [{ assetId: "DYN004-image32", height: 76, width: 39, x: -21.067, y: -1 }],
|
||||
sourceCandidateId: "l_POI04",
|
||||
textLayers: [
|
||||
{ align: "left", color: "#FFFFFF", fontId: "dd25b35dcb7ba4476cbaa9a9592e39e2", fontSize: 26, value: "city", x: -28, y: 0 },
|
||||
{ align: "left", color: "#FFFFFF", fontId: "15974853bc3294ef68e7e6d58fe74fd7", fontSize: 14, value: "latitude_dms", x: -29.844, y: 36 },
|
||||
{ align: "left", color: "#FFFFFF", fontId: "15974853bc3294ef68e7e6d58fe74fd7", fontSize: 14, value: "longitude_dms", x: 71.523, y: 36 },
|
||||
],
|
||||
},
|
||||
DYN007: {
|
||||
halfSize: { height: 34, width: 190 },
|
||||
imageLayers: [],
|
||||
sourceCandidateId: "diaoyu",
|
||||
textLayers: [{ align: "center", color: "#295E8B", fontId: "53ca6b704728520da50c145eabb2e635", fontSize: 50, value: "nickname", x: 0, y: 0 }],
|
||||
},
|
||||
DYN008: {
|
||||
halfSize: { height: 180, width: 140 },
|
||||
imageLayers: [{ assetId: "DYN008-backendui0", height: 185, width: 5, x: -125, y: 0 }],
|
||||
sourceCandidateId: "l_shijian2",
|
||||
textLayers: [
|
||||
{ align: "left", color: "#FFFFFF", fontId: "f4bfd4132df2d6be97ceabadf3853505", fontSize: 70, value: "month_en", x: -112, y: -32 },
|
||||
{ align: "left", color: "#FFFFFF", fontId: "f4bfd4132df2d6be97ceabadf3853505", fontSize: 130, value: "time", x: -114.024, y: 97 },
|
||||
],
|
||||
},
|
||||
DYN011: {
|
||||
halfSize: { height: 40, width: 130 },
|
||||
imageLayers: [{ assetId: "DYN011-backendui0", height: 12, width: 8, x: -120, y: -19 }],
|
||||
sourceCandidateId: "l_shijian6",
|
||||
textLayers: [
|
||||
{ align: "center", color: "#FF6D2F", fontId: "e4210c9872f0c279b35273f230809821", fontSize: 64, value: "year_short", x: -85, y: 0 },
|
||||
{ align: "center", color: "#FF6D2F", fontId: "e4210c9872f0c279b35273f230809821", fontSize: 64, value: "month", x: 0, y: 0 },
|
||||
{ align: "center", color: "#FF6D2F", fontId: "e4210c9872f0c279b35273f230809821", fontSize: 64, value: "day", x: 85, y: 0 },
|
||||
],
|
||||
},
|
||||
DYN012: {
|
||||
halfSize: { height: 78, width: 118 },
|
||||
imageLayers: [],
|
||||
sourceCandidateId: "l_shijian7",
|
||||
textLayers: [],
|
||||
},
|
||||
DYN015: {
|
||||
halfSize: { height: 42, width: 300 },
|
||||
imageLayers: [{ assetId: "DYN015-imager2", height: 60, width: 60, x: -140, y: 0 }],
|
||||
sourceCandidateId: "0721userna",
|
||||
textLayers: [{ align: "left", color: "#666666", fontId: "cca5efc0e02fb1bf62349bd68ef30fc1", fontSize: 40, value: "nickname", x: -100, y: 0 }],
|
||||
},
|
||||
DYN016: {
|
||||
halfSize: { height: 48, width: 160 },
|
||||
imageLayers: [{ assetId: "DYN016-image21", height: 59, width: 186, x: -61.842, y: 16.483 }],
|
||||
sourceCandidateId: "l_username00",
|
||||
textLayers: [{ align: "left", color: "#FFFFFF", fontId: "46f8336813e4c48d06a1aef294fdccf6", fontSize: 27, value: "nickname", x: 0, y: 0 }],
|
||||
},
|
||||
};
|
||||
|
||||
export function dynamicFontOptionsFor(templateId: string) {
|
||||
if (templateId === "DYN012") {
|
||||
const replacement = fontOption("FONT081");
|
||||
return replacement ? [replacement] : [];
|
||||
}
|
||||
const model = DYNAMIC_RENDER_MODELS[templateId as DynamicTemplateId];
|
||||
if (!model) return [];
|
||||
const ids = new Set(model.textLayers.map((layer) => layer.fontId));
|
||||
return DYNAMIC_FONT_OPTIONS.filter((option) => ids.has(option.fontId));
|
||||
}
|
||||
|
||||
export function dynamicImageUrl(resourceVersion: string, assetId: string) {
|
||||
return `/api/v1/assets/public/${encodeURIComponent(resourceVersion)}/${encodeURIComponent(assetId)}`;
|
||||
}
|
||||
|
||||
function numericField(element: CanvasElement, key: string) {
|
||||
const value = element.dynamic_fields?.[key];
|
||||
return typeof value === "number" ? value : Number(value);
|
||||
}
|
||||
|
||||
function coordinate(value: number, positive: string, negative: string) {
|
||||
if (!Number.isFinite(value)) return "";
|
||||
const absolute = Math.abs(value);
|
||||
const degrees = Math.floor(absolute);
|
||||
const minutesFloat = (absolute - degrees) * 60;
|
||||
const minutes = Math.floor(minutesFloat);
|
||||
const seconds = (minutesFloat - minutes) * 60;
|
||||
return `${degrees}°${String(minutes).padStart(2, "0")}'${seconds.toFixed(2).padStart(5, "0")}\"${value >= 0 ? positive : negative}`;
|
||||
}
|
||||
|
||||
export function dynamicTextValue(element: CanvasElement, value: DynamicTextValue) {
|
||||
const fields = element.dynamic_fields ?? {};
|
||||
const text = (key: string) => String(fields[key] ?? "");
|
||||
const override = fields.display_override;
|
||||
if (typeof override === "string" && (value === "nickname" || value === "title")) return element.formatted_value ?? override;
|
||||
if (value === "time") return `${text("hour")}:${text("minute")}`;
|
||||
if (value === "month_en") {
|
||||
const month = Math.max(1, Math.min(12, numericField(element, "month")));
|
||||
return `${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][month - 1]}.`;
|
||||
}
|
||||
if (value === "year_short") return text("year").slice(-2);
|
||||
if (value === "latitude_dms") return coordinate(numericField(element, "latitude"), "N", "S");
|
||||
if (value === "longitude_dms") return coordinate(numericField(element, "longitude"), "E", "W");
|
||||
if (value === "meridiem") {
|
||||
const hour = numericField(element, "hour");
|
||||
const minute = numericField(element, "minute");
|
||||
return hour > 12 || (hour === 12 && minute > 0) ? "PM" : "AM";
|
||||
}
|
||||
return text(value);
|
||||
}
|
||||
Reference in New Issue
Block a user