46 lines
3.3 KiB
TypeScript
46 lines
3.3 KiB
TypeScript
import type { ArchivedFontStatus } from "./text-font-loader.js";
|
|
import { searchTextTemplates, type TextTemplateCategory, type TextTemplateDefinition } from "./text-assets.js";
|
|
|
|
const categories: Array<{ id?: TextTemplateCategory; label: string }> = [
|
|
{ label: "全部" }, { id: "flower", label: "花字" }, { id: "title", label: "标题" }, { id: "tag", label: "标签" }, { id: "simple", label: "简约" },
|
|
];
|
|
|
|
export function TextTemplatePanel(props: {
|
|
canAdd: boolean;
|
|
category?: TextTemplateCategory;
|
|
fontStatuses: Readonly<Record<string, ArchivedFontStatus>>;
|
|
onAdd: (template: TextTemplateDefinition) => void;
|
|
onCategory: (category?: TextTemplateCategory) => void;
|
|
onQuery: (query: string) => void;
|
|
onRetry: () => void;
|
|
query: string;
|
|
recentIds: readonly string[];
|
|
templates: readonly TextTemplateDefinition[];
|
|
}) {
|
|
const visible = searchTextTemplates(props.templates, { ...(props.category ? { category: props.category } : {}), query: props.query });
|
|
const recent = props.recentIds.map((id) => props.templates.find((template) => template.templateId === id)).filter((item): item is TextTemplateDefinition => Boolean(item));
|
|
const loadFailed = props.templates.some((template) => template.available && props.fontStatuses[template.defaultFontId] === "unavailable");
|
|
return <section className="editor-text-assets">
|
|
<h2>文字模板</h2>
|
|
<input aria-label="搜索文字模板显示名称" className="editor-template-search" onChange={(event) => props.onQuery(event.target.value)} placeholder="搜索文字模板显示名称" type="search" value={props.query} />
|
|
<div aria-label="文字模板分类" className="editor-template-categories" role="group">
|
|
{categories.map((item) => <button aria-pressed={item.id === props.category || (!item.id && !props.category)} className={item.id === props.category || (!item.id && !props.category) ? "active" : ""} key={item.label} onClick={() => props.onCategory(item.id)} type="button">{item.label}</button>)}
|
|
</div>
|
|
{loadFailed ? <button className="editor-template-retry" onClick={props.onRetry} type="button">重试文字素材</button> : null}
|
|
{recent.length > 0 ? <div aria-label="最近使用文字模板" className="editor-template-recent"><h3>最近使用</h3><div>{recent.map((template) => <span key={template.templateId}><strong>{template.templateId}</strong>{template.displayName}</span>)}</div></div> : null}
|
|
<div className="editor-template-grid">
|
|
{visible.map((template) => {
|
|
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>
|
|
<strong>{template.templateId}</strong>
|
|
<span>{template.displayName}</span>
|
|
{unavailable ? <small>素材暂不可用</small> : status === "loading" ? <small>正在加载字体</small> : null}
|
|
</button>;
|
|
})}
|
|
</div>
|
|
{visible.length === 0 ? <p className="editor-muted">没有匹配的文字模板</p> : null}
|
|
</section>;
|
|
}
|