feat(POSTV1-ASSET-ALL-16): 在编辑器接入全部复杂素材
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run

This commit is contained in:
suyx
2026-08-06 11:55:06 +08:00
parent 9de0d2a63c
commit 2cd85cd7cd
17 changed files with 309 additions and 167 deletions
+3 -7
View File
@@ -1,13 +1,8 @@
import { useEffect, useRef } from "react";
import { P0A_COLOR_CARDS, createColorCardElement, drawColorCard, type ColorCardDefinition } from "./palette-provider.js";
import { COLOR_CARD_HALF_SIZES, P0A_COLOR_CARDS, createColorCardElement, drawColorCard, type ColorCardDefinition } from "./palette-provider.js";
const previewPalette = ["#04D960", "#0ABF58", "#5FD994", "#A0F2C4", "#D5F2E2"] as const;
const previewHalfSize = {
style_01: { height: 76, width: 26 }, style_02: { height: 77, width: 18 },
style_08: { height: 10, width: 73 }, style_16: { height: 9, width: 78 },
} as const;
function ColorCardPreview({ definition }: { definition: ColorCardDefinition }) {
const ref = useRef<HTMLCanvasElement>(null);
useEffect(() => {
@@ -18,7 +13,8 @@ function ColorCardPreview({ definition }: { definition: ColorCardDefinition }) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#30343b";
context.fillRect(0, 0, canvas.width, canvas.height);
const half = previewHalfSize[definition.styleId];
const half = COLOR_CARD_HALF_SIZES[definition.styleId];
if (!half) return;
const scale = Math.min(1, 146 / (half.width * 2), 62 / (half.height * 2));
context.translate(canvas.width / 2, canvas.height / 2);
context.scale(scale, scale);
+33 -16
View File
@@ -3,6 +3,7 @@ import { P0A_DYNAMIC_STICKER_IDS } from "@dada/template-registry";
import { DYNAMIC_RESOURCE_VERSION } from "./dynamic-render-models.js";
import type { CanvasElementIdentity } from "./editor-elements.js";
import complexAssetCatalog from "./generated/complex-assets.json";
type CanvasElement = CanvasState["elements"][number];
@@ -45,23 +46,17 @@ export function dyn012DisplayParts(element: CanvasElement) {
} as const;
}
const dynamicDefinitions: readonly DynamicStickerDefinition[] = [
{ category: "location", displayName: "地点标题", requiresLocationConsent: false, templateId: "DYN001" },
{ category: "location", displayName: "英文地点", requiresLocationConsent: false, templateId: "DYN002" },
{ category: "location", displayName: "城市地点", requiresLocationConsent: false, templateId: "DYN003" },
{ category: "location", displayName: "经纬地点", requiresLocationConsent: true, templateId: "DYN004" },
{ category: "other", displayName: "用户名组合", requiresLocationConsent: false, templateId: "DYN007" },
{ category: "time", displayName: "月与时间", requiresLocationConsent: false, templateId: "DYN008" },
{ category: "time", displayName: "完整日期", requiresLocationConsent: false, templateId: "DYN011" },
{ category: "time", displayName: "数字时间", requiresLocationConsent: false, templateId: "DYN012" },
{ category: "identity", displayName: "创作署名", requiresLocationConsent: false, templateId: "DYN015" },
{ category: "identity", displayName: "社交 ID", requiresLocationConsent: false, templateId: "DYN016" },
] as const;
const dynamicCatalogById = new Map(complexAssetCatalog.dynamic_stickers.map((item) => [item.template_id, item]));
export const P0A_DYNAMIC_STICKERS: readonly DynamicStickerDefinition[] = P0A_DYNAMIC_STICKER_IDS.map((templateId) => {
const definition = dynamicDefinitions.find((item) => item.templateId === templateId);
if (!definition) throw new Error(`missing dynamic sticker definition ${templateId}`);
return definition;
const item = dynamicCatalogById.get(templateId);
if (!item) throw new Error(`missing dynamic sticker definition ${templateId}`);
return {
category: item.category as DynamicCategory,
displayName: item.display_name,
requiresLocationConsent: item.requires_location_consent,
templateId,
};
});
function twoDigits(value: number) {
@@ -96,7 +91,29 @@ function snapshotFor(templateId: DynamicTemplateId, context: DynamicProviderCont
if (templateId === "DYN011") return { fields: { day, month, year }, value: `${year}.${month}.${day}` };
if (templateId === "DYN012") return { fields: { font_substitution: "FONT081", hour, minute }, value: `${hour}:${minute}` };
if (templateId === "DYN015") return { fields: { nickname: context.profile.creatorName }, value: context.profile.creatorName };
return { fields: { nickname: normalizeSocialId(context.profile.socialId) }, value: normalizeSocialId(context.profile.socialId) };
if (templateId === "DYN016") return { fields: { nickname: normalizeSocialId(context.profile.socialId) }, value: normalizeSocialId(context.profile.socialId) };
const definition = dynamicCatalogById.get(templateId);
if (!definition) throw new Error("dynamic_template_unavailable");
const location = context.location?.formattedValue ?? "输入地点";
const values: Record<string, string | number> = {
city: location,
city_en: location.toUpperCase(),
day,
hour,
latitude: context.location?.latitude ?? 0,
longitude: context.location?.longitude ?? 0,
minute,
month,
nickname: normalizeSocialId(context.profile.socialId),
title: location,
year,
};
const fields = Object.fromEntries(definition.required_fields.map((field) => [field, values[field] ?? ""]));
if (definition.category === "identity") return { fields, value: normalizeSocialId(context.profile.socialId) };
if (definition.category === "location") return { fields, value: location };
if (definition.category === "time") return { fields, value: `${year}.${month}.${day} ${hour}:${minute}` };
return { fields, value: context.profile.creatorName };
}
export function createDynamicStickerElement(
+42 -12
View File
@@ -1,8 +1,9 @@
import type { CanvasState } from "@dada/shared-contracts";
import { P0A_COMPLEX_RELEASE_VERSION } from "@dada/template-registry";
import { P0A_COMPLEX_RELEASE_VERSION, P0A_DYNAMIC_STICKER_IDS } from "@dada/template-registry";
import { fontOption, type FontOption } from "./text-assets.js";
import type { DynamicTemplateId } from "./dynamic-provider.js";
import complexAssetCatalog from "./generated/complex-assets.json";
type CanvasElement = CanvasState["elements"][number];
@@ -55,17 +56,13 @@ const dynamicFont = (fontId: string): FontOption => ({
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;
const dynamicFontIds = [...new Set(complexAssetCatalog.dynamic_stickers.flatMap((item) => item.font_ids))]
.filter((fontId) => fontId !== "FONT081")
.toSorted();
export const DYNAMIC_RENDER_MODELS: Readonly<Record<DynamicTemplateId, DynamicRenderModel>> = {
export const DYNAMIC_FONT_OPTIONS: readonly FontOption[] = dynamicFontIds.map(dynamicFont);
const EXACT_DYNAMIC_RENDER_MODELS: Readonly<Record<string, DynamicRenderModel>> = {
DYN001: {
halfSize: { height: 42, width: 130 },
imageLayers: [{ assetId: "DYN001-image28", height: 67, width: 219, x: -17.562, y: 2.203 }],
@@ -145,6 +142,38 @@ export const DYNAMIC_RENDER_MODELS: Readonly<Record<DynamicTemplateId, DynamicRe
},
};
const dynamicCatalogById = new Map(complexAssetCatalog.dynamic_stickers.map((item) => [item.template_id, item]));
function genericTextValue(item: (typeof complexAssetCatalog.dynamic_stickers)[number]): DynamicTextValue {
if (item.category === "identity") return "nickname";
if (item.category === "location") return item.required_fields.includes("title") ? "title" : "city";
if (item.category === "time") return item.required_fields.includes("hour") && item.required_fields.includes("minute") ? "time" : "day";
return "nickname";
}
function genericDynamicModel(templateId: string): DynamicRenderModel {
const item = dynamicCatalogById.get(templateId);
if (!item) throw new Error(`missing dynamic render model ${templateId}`);
return {
halfSize: { height: 42, width: 170 },
imageLayers: [],
sourceCandidateId: item.source_candidate_id,
textLayers: [{
align: "center",
color: "#FFFFFF",
fontId: item.font_ids[0] ?? "FONT081",
fontSize: 34,
value: genericTextValue(item),
x: 0,
y: 0,
}],
};
}
export const DYNAMIC_RENDER_MODELS: Readonly<Record<DynamicTemplateId, DynamicRenderModel>> = Object.fromEntries(
P0A_DYNAMIC_STICKER_IDS.map((templateId) => [templateId, EXACT_DYNAMIC_RENDER_MODELS[templateId] ?? genericDynamicModel(templateId)]),
);
export function dynamicFontOptionsFor(templateId: string) {
if (templateId === "DYN012") {
const replacement = fontOption("FONT081");
@@ -153,7 +182,8 @@ export function dynamicFontOptionsFor(templateId: string) {
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));
return [...ids].map((fontId) => fontOption(fontId) ?? DYNAMIC_FONT_OPTIONS.find((option) => option.fontId === fontId))
.filter((option): option is FontOption => option !== undefined);
}
export function dynamicImageUrl(resourceVersion: string, assetId: string) {
+1
View File
@@ -22,6 +22,7 @@ function DynamicPreview(props: { fontStatuses: Readonly<Record<string, ArchivedF
useEffect(() => {
let active = true;
const model = DYNAMIC_RENDER_MODELS[props.templateId];
if (!model) return () => { active = false; };
const element = createDynamicStickerElement(props.templateId, {
location: { formattedValue: "温州", latitude: 27.9943, longitude: 120.6994 },
now: new Date("2026-08-03T09:07:00+08:00"), profile: { creatorName: "Dada Creator", socialId: "@dada" },
+1
View File
@@ -399,6 +399,7 @@
.editor-template-grid button:disabled { border-style: dashed; background: #e8e8e5; color: #62625d; cursor: not-allowed; }
.editor-template-grid strong { overflow: hidden; font-family: Consolas, monospace; font-size: 10px; text-overflow: ellipsis; }
.editor-template-grid small { color: #8f1d14; font-size: 9px; }
.editor-template-preview { width: 100%; height: 44px; object-fit: contain; border: 1px solid #111111; background: #30343b; }
.editor-template-mark { display: grid; width: 100%; height: 44px; place-items: center; border: 1px solid #111111; background: #f2f400; font-size: 18px; font-weight: 800; }
.editor-template-mark.title { background: #111111; color: #ffffff; }
.editor-template-mark.tag { background: #dbeafe; }
+3 -5
View File
@@ -8,7 +8,7 @@ import { DYNAMIC_RENDER_MODELS, dynamicFontOptionsFor, dynamicImageUrl, dynamicT
import type { CanvasPoint, CanvasRect } from "./editor-elements.js";
import { fontFamilyName, type ArchivedFontStatus } from "./text-font-loader.js";
import { fontIdForTextElement } from "./text-assets.js";
import { drawColorCard } from "./palette-provider.js";
import { COLOR_CARD_HALF_SIZES, drawColorCard } from "./palette-provider.js";
interface Gesture {
append: boolean;
@@ -186,10 +186,8 @@ function elementSelectionHalfSize(
fontStatuses: Readonly<Record<string, ArchivedFontStatus>>,
) {
if (element.type === "color_card") {
if (element.style_id === "style_01") return { height: 76 * element.scale.y, width: 26 * element.scale.x };
if (element.style_id === "style_02") return { height: 77 * element.scale.y, width: 18 * element.scale.x };
if (element.style_id === "style_08") return { height: 10 * element.scale.y, width: 73 * element.scale.x };
return { height: 9 * element.scale.y, width: 78 * element.scale.x };
const half = COLOR_CARD_HALF_SIZES[element.style_id ?? ""] ?? { height: 24, width: 78 };
return { height: half.height * element.scale.y, width: half.width * element.scale.x };
}
if (element.type === "dynamic_sticker") {
const model = DYNAMIC_RENDER_MODELS[element.template_or_asset_id as DynamicTemplateId];
+3
View File
@@ -40,6 +40,7 @@
.reference-input,
.editor-sticker-preview,
.editor-template-mark,
.editor-template-preview,
.editor-color-card-preview,
.editor-dynamic-preview,
.editor-source-preview-canvas,
@@ -114,6 +115,7 @@
}
.editor-template-grid button:not(:disabled):hover .editor-template-mark,
.editor-template-grid button:not(:disabled):hover .editor-template-preview,
.editor-provider-grid button:not(:disabled):hover > :first-child {
transform: scale(1.03);
}
@@ -142,6 +144,7 @@
.reference-input,
.editor-sticker-preview,
.editor-template-mark,
.editor-template-preview,
.editor-color-card-preview,
.editor-dynamic-preview,
.editor-source-preview-canvas,
+125
View File
@@ -27,6 +27,17 @@ export const COLOR_CARD_SOURCE_GEOMETRY = {
style_16: { bounds: { bottom: 9, left: -78, right: 78, top: -9 } },
} as const;
export const COLOR_CARD_HALF_SIZES: Readonly<Record<string, { height: number; width: number }>> = {
style_01: { height: 76, width: 26 }, style_02: { height: 77, width: 18 },
style_03: { height: 75, width: 22 }, style_04: { height: 75, width: 18 },
style_05: { height: 18, width: 78 }, style_06: { height: 16, width: 78 },
style_07: { height: 20, width: 78 }, style_08: { height: 10, width: 73 },
style_09: { height: 75, width: 58 }, style_10: { height: 75, width: 60 },
style_11: { height: 18, width: 78 }, style_12: { height: 34, width: 70 },
style_13: { height: 75, width: 18 }, style_14: { height: 28, width: 60 },
style_15: { height: 16, width: 78 }, style_16: { height: 9, width: 78 },
};
function normalizedHex(value: string) {
return value.toUpperCase();
}
@@ -139,6 +150,65 @@ export function drawColorCard(context: CanvasRenderingContext2D, element: Canvas
});
return;
}
if (element.style_id === "style_03") {
colors.forEach((color, index) => {
context.fillStyle = color;
context.fillRect(-22, -75 + index * 30, 44, 29);
});
return;
}
if (element.style_id === "style_04") {
context.strokeStyle = "#ffffff";
context.lineWidth = 2;
context.beginPath();
context.moveTo(0, -75);
context.lineTo(0, 75);
context.stroke();
colors.forEach((color, index) => {
context.fillStyle = color;
context.beginPath();
context.arc(0, -60 + index * 30, 9, 0, Math.PI * 2);
context.fill();
});
return;
}
if (["style_05", "style_06", "style_07", "style_11"].includes(element.style_id ?? "")) {
if (element.style_id === "style_11") {
context.strokeStyle = "#ffffff";
context.lineWidth = 3;
context.strokeRect(-78, -18, 156, 36);
}
colors.forEach((color, index) => {
const left = -72 + index * 29;
context.fillStyle = color;
context.fillRect(left, -10, 28, 20);
if (element.style_id === "style_07") {
context.fillStyle = "#ffffff";
context.beginPath();
context.arc(left + 14, -16, 4, 0, Math.PI * 2);
context.fill();
}
});
if (element.style_id === "style_05") {
context.fillStyle = "#111111";
context.fillRect(-78, -18, 20, 36);
context.fillStyle = "#ffffff";
context.font = "700 7px Arial, sans-serif";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("C", -68, 0);
}
if (element.style_id === "style_06") {
context.fillStyle = "#ffffff";
context.beginPath();
context.moveTo(-4, -16);
context.lineTo(4, -16);
context.lineTo(0, -10);
context.closePath();
context.fill();
}
return;
}
if (element.style_id === "style_08") {
colors.forEach((color, index) => {
const left = -73 + index * 29.2;
@@ -159,6 +229,61 @@ export function drawColorCard(context: CanvasRenderingContext2D, element: Canvas
});
return;
}
if (element.style_id === "style_09" || element.style_id === "style_10") {
context.fillStyle = "#ffffff";
context.fillRect(-58, -75, 116, 150);
colors.forEach((color, index) => {
context.fillStyle = color;
context.fillRect(-50, -64 + index * 23, 100, 22);
});
context.fillStyle = "#111111";
context.font = "700 8px Arial, sans-serif";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText(element.style_id === "style_09" ? "COLOR PALETTE" : "FIVE COLORS", 0, 63);
return;
}
if (element.style_id === "style_12") {
const positions = [[-52, -18], [0, -18], [52, -18], [-26, 18], [26, 18]] as const;
colors.forEach((color, index) => {
const position = positions[index]!;
context.fillStyle = color;
context.fillRect(position[0] - 24, position[1] - 14, 48, 28);
});
return;
}
if (element.style_id === "style_13") {
colors.forEach((color, index) => {
context.fillStyle = color;
context.beginPath();
context.arc(0, -60 + index * 30, 11, 0, Math.PI * 2);
context.fill();
});
return;
}
if (element.style_id === "style_14") {
colors.forEach((color, index) => {
context.globalAlpha = 0.9;
context.fillStyle = color;
context.beginPath();
context.arc(-40 + index * 20, 0, 24, 0, Math.PI * 2);
context.fill();
});
context.globalAlpha = 1;
return;
}
if (element.style_id === "style_15") {
colors.forEach((color, index) => {
context.fillStyle = color;
context.strokeStyle = "#ffffff";
context.lineWidth = 3;
context.beginPath();
context.arc(-58 + index * 29, 0, 12, 0, Math.PI * 2);
context.fill();
context.stroke();
});
return;
}
context.fillStyle = "#ffffff";
context.beginPath();
context.moveTo(-78, -9);
+24 -66
View File
@@ -2,6 +2,7 @@ import type { CanvasState } from "@dada/shared-contracts";
import { P0A_COMPLEX_RELEASE_VERSION, P0A_REQUIRED_FONT_PANEL_IDS, P0A_TEXT_TEMPLATE_IDS } from "@dada/template-registry";
import type { CanvasElementIdentity } from "./editor-elements.js";
import complexAssetCatalog from "./generated/complex-assets.json";
type CanvasElement = CanvasState["elements"][number];
@@ -17,6 +18,7 @@ export interface TextTemplateDefinition {
defaultText: string;
displayName: string;
fontUrl?: string;
previewUrl?: string;
resourceClass: "parameter_only" | "zip_template";
resourceVersion: string;
templateId: string;
@@ -56,82 +58,38 @@ const defaults = {
text_align: "center",
} as const;
type CatalogSeed = [id: string, category: TextTemplateCategory, displayName: string, defaultText: string, defaultFontId: string, available?: boolean, resourceClass?: "parameter_only"];
const seeds: readonly CatalogSeed[] = [
["FLOWER001", "flower", "春日计划", "春日计划", "FONT011", true],
["FLOWER002", "flower", "笑不活了", "笑不活了", "FLOWER002_FONT"],
["FLOWER003", "flower", "人生照片", "人生照片", "FONT008"],
["FLOWER004", "flower", "我的日常生活", "我的日常生活", "FLOWER004_FONT"],
["FLOWER005", "flower", "碎片生活", "碎片生活", "FONT008"],
["FLOWER006", "flower", "闪光瞬间", "闪光瞬间", "FLOWER006_FONT"],
["FLOWER007", "flower", "好柿花生", "好柿花生", "FONT046", false, "parameter_only"],
["FLOWER008", "flower", "Vlog.", "Vlog.", "FONT005"],
["H001", "title", "电影生活记录", "电影生活记录", "H001_FONT"],
["H002", "title", "30°C", "30°C", "H002_FONT"],
["H003", "title", "生活分享家", "生活分享家", "FONT039", true],
["H004", "title", "快乐充值成功", "快乐充值成功", "FONT046"],
["H005", "title", "日常的镜头", "日常的镜头", "H005_FONT"],
["H006", "title", "慢生活指南", "慢生活指南", "FONT052"],
["H007", "title", "做个有闲人", "做个有闲人", "H007_FONT"],
["H008", "title", "海滩日记", "海滩日记", "H008_FONT"],
["TAG001", "tag", "自定义标签", "自定义标签", "FONT027"],
["TAG002", "tag", "自定义标签", "自定义标签", "FONT043"],
["TAG003", "tag", "打卡x1", "打卡x1", "FONT043"],
["TAG004", "tag", "自定义标签", "自定义标签", "TAG004_FONT"],
["TAG005", "tag", "自定义标签", "自定义标签", "FONT008"],
["TAG006", "tag", "City Walk", "City Walk", "TAG006_FONT"],
["TAG007", "tag", "打卡x1", "打卡x1", "FONT043"],
["TAG051", "tag", "自定义标签", "自定义标签", "FONT022"],
["SIMPLE001", "simple", "碎片回忆录", "碎片回忆录", "SIMPLE001_FONT"],
["SIMPLE002", "simple", "秋天的信笺", "秋天的信笺", "SIMPLE002_FONT"],
["SIMPLE003", "simple", "返航时海鸟追着船盘旋", "返航时海鸟追着船盘旋", "SIMPLE003_FONT"],
["SIMPLE004", "simple", "下段旅程,幸福丰盛。", "下段旅程,幸福丰盛。", "SIMPLE002_FONT"],
["SIMPLE005", "simple", "见信好。", "见信好。", "SIMPLE005_FONT"],
["SIMPLE006", "simple", "万物回春", "万物回春", "SIMPLE001_FONT"],
["SIMPLE007", "simple", "周而复始。", "周而复始。", "SIMPLE007_FONT"],
["SIMPLE008", "simple", "周五愉快", "周五愉快", "SIMPLE008_FONT"],
];
const seedById = new Map(seeds.map((seed) => [seed[0], seed]));
const textCatalogById = new Map(complexAssetCatalog.text_templates.map((item) => [item.template_id, item]));
export const P0A_TEXT_TEMPLATES: readonly TextTemplateDefinition[] = P0A_TEXT_TEMPLATE_IDS.map((templateId, catalogOrder) => {
const seed = seedById.get(templateId);
if (!seed) throw new Error(`missing text template definition ${templateId}`);
const item = textCatalogById.get(templateId);
if (!item) throw new Error(`missing text template definition ${templateId}`);
return {
available: seed[5] === true,
available: item.available,
catalogOrder,
category: seed[1],
defaultFontId: seed[4],
defaultFontSize: 48,
defaultText: seed[3],
displayName: seed[2],
...(seed[5] === true ? { fontUrl: `/api/v1/assets/public/${resourceVersion}/${seed[4]}` } : {}),
resourceClass: seed[6] ?? "zip_template",
category: item.category as TextTemplateCategory,
defaultFontId: item.default_font_id,
defaultFontSize: item.default_font_size,
defaultText: item.default_text,
displayName: item.display_name,
fontUrl: `/api/v1/assets/public/${resourceVersion}/${item.default_font_id}`,
...(item.preview_asset_id ? { previewUrl: `/api/v1/assets/public/${resourceVersion}/${item.preview_asset_id}` } : {}),
resourceClass: item.resource_class as "parameter_only" | "zip_template",
resourceVersion,
templateId,
};
});
const fontOptionDefinitions: Readonly<Record<typeof P0A_REQUIRED_FONT_PANEL_IDS[number], string>> = {
FONT005: "Rammetto",
FONT008: "正圆体",
FONT011: "默陌手写",
FONT021: "喜月体",
FONT022: "素白体",
FONT027: "锐正圆",
FONT039: "字由油漆",
FONT043: "喜脉体",
FONT046: "可口可乐",
FONT052: "Oraqle Script",
FONT081: "Lexend Deca",
};
const fontCatalogById = new Map(complexAssetCatalog.font_panel_items.map((item) => [item.font_id, item]));
export const P0A_FONT_OPTIONS: readonly FontOption[] = P0A_REQUIRED_FONT_PANEL_IDS.map((fontId) => ({
displayName: fontOptionDefinitions[fontId],
fontId,
url: `/api/v1/assets/public/${resourceVersion}/${fontId}`,
}));
export const P0A_FONT_OPTIONS: readonly FontOption[] = P0A_REQUIRED_FONT_PANEL_IDS.map((fontId) => {
const item = fontCatalogById.get(fontId);
if (!item) throw new Error(`missing font panel definition ${fontId}`);
return {
displayName: item.display_name,
fontId,
url: `/api/v1/assets/public/${resourceVersion}/${fontId}`,
};
});
export function fontOption(fontId: string) {
return P0A_FONT_OPTIONS.find((option) => option.fontId === fontId);
+3 -1
View File
@@ -33,7 +33,9 @@ export function TextTemplatePanel(props: {
const status = props.fontStatuses[template.defaultFontId] ?? "idle";
const unavailable = !template.available || status === "unavailable";
return <button aria-label={`${template.templateId} ${template.displayName}${unavailable ? " 素材暂不可用" : ""}`} disabled={!props.canAdd || unavailable || status === "loading"} key={template.templateId} onClick={() => props.onAdd(template)} type="button">
<span className={`editor-template-mark ${template.category}`}>{template.displayName.slice(0, 2)}</span>
{template.previewUrl
? <img alt="" className="editor-template-preview" decoding="async" loading="lazy" src={template.previewUrl} />
: <span className={`editor-template-mark ${template.category}`}>{template.displayName.slice(0, 2)}</span>}
<strong>{template.templateId}</strong>
<span>{template.displayName}</span>
{unavailable ? <small></small> : status === "loading" ? <small></small> : null}