90e1460a1b
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 9s
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 47s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m6s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 38s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m5s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 2m48s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 3m8s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m43s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m16s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 58s
CI/CD Pipeline / Integration Tests (push) Successful in 4m39s
CI/CD Pipeline / Validate - Style (push) Successful in 5m20s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 5m43s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m49s
AI Code Review / AI Code Review (pull_request) Successful in 6m58s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m14s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 3m50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 10m31s
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Unit Tests (push) Successful in 11m34s
CI/CD Pipeline / Validate - Security (push) Successful in 20m18s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
99 lines
4.7 KiB
TypeScript
99 lines
4.7 KiB
TypeScript
/**
|
||
* 标题样式工具(#2001 / 模板系统 #2003)
|
||
*
|
||
* - snake_case TitleStyleConfig ↔ camelCase TitleSettings 互转
|
||
* - preset 归一化预览(修复"标题"两字大小不一)
|
||
*/
|
||
import type { TitleStyleConfig } from "./types"
|
||
import type { TitleSettings } from "../../pages/generate/types"
|
||
import { TITLE_PRESETS } from "./constants"
|
||
|
||
/** snake_case TitleStyleConfig → camelCase TitleSettings(仅覆盖已知字段) */
|
||
export function titleStyleConfigToCamel(s: Partial<TitleStyleConfig>): Partial<TitleSettings> {
|
||
const out: Partial<TitleSettings> = {}
|
||
if (s.font != null) out.font = s.font
|
||
if (s.size != null) out.size = s.size
|
||
if (s.color != null) out.color = s.color
|
||
if (s.bold != null) out.bold = s.bold
|
||
if (s.italic != null) out.italic = s.italic
|
||
if (s.position != null) out.position = s.position as TitleSettings["position"]
|
||
if (s.pos_x != null) out.posX = s.pos_x
|
||
if (s.pos_y != null) out.posY = s.pos_y
|
||
if (s.line_height != null) out.lineHeight = s.line_height
|
||
if (s.margin_top != null) out.marginTop = s.margin_top
|
||
if (s.max_chars_per_line != null) out.maxCharsPerLine = s.max_chars_per_line
|
||
if (s.stroke != null) out.stroke = s.stroke
|
||
if (s.stroke_width != null) out.strokeWidth = s.stroke_width
|
||
if (s.stroke_color != null) out.strokeColor = s.stroke_color
|
||
if (s.shadow != null) out.shadow = s.shadow
|
||
if (s.shadow_offset_x != null) out.shadowOffsetX = s.shadow_offset_x
|
||
if (s.shadow_offset_y != null) out.shadowOffsetY = s.shadow_offset_y
|
||
if (s.shadow_blur != null) out.shadowBlur = s.shadow_blur
|
||
if (s.shadow_color != null) out.shadowColor = s.shadow_color
|
||
if (s.bg_enabled != null) out.bgEnabled = s.bg_enabled
|
||
if (s.bg_color != null) out.bgColor = s.bg_color
|
||
if (s.bg_padding != null) out.bgPadding = s.bg_padding
|
||
if (s.bg_radius != null) out.bgRadius = s.bg_radius
|
||
if (s.line_overrides != null) out.lineOverrides = s.line_overrides
|
||
return out
|
||
}
|
||
|
||
/** camelCase TitleSettings patch → snake_case TitleStyleConfig patch */
|
||
export function camelToTitleStyleConfig(p: Partial<TitleSettings>): Partial<TitleStyleConfig> {
|
||
const out: Partial<TitleStyleConfig> = {}
|
||
if (p.font != null) out.font = p.font
|
||
if (p.size != null) out.size = p.size
|
||
if (p.color != null) out.color = p.color
|
||
if (p.bold != null) out.bold = p.bold
|
||
if (p.italic != null) out.italic = p.italic
|
||
if (p.position != null) out.position = p.position as TitleStyleConfig["position"]
|
||
if (p.posX != null) out.pos_x = p.posX
|
||
if (p.posY != null) out.pos_y = p.posY
|
||
if (p.lineHeight != null) out.line_height = p.lineHeight
|
||
if (p.marginTop != null) out.margin_top = p.marginTop
|
||
if (p.maxCharsPerLine != null) out.max_chars_per_line = p.maxCharsPerLine
|
||
if (p.stroke != null) out.stroke = p.stroke
|
||
if (p.strokeWidth != null) out.stroke_width = p.strokeWidth
|
||
if (p.strokeColor != null) out.stroke_color = p.strokeColor
|
||
if (p.shadow != null) out.shadow = p.shadow
|
||
if (p.shadowOffsetX != null) out.shadow_offset_x = p.shadowOffsetX
|
||
if (p.shadowOffsetY != null) out.shadow_offset_y = p.shadowOffsetY
|
||
if (p.shadowBlur != null) out.shadow_blur = p.shadowBlur
|
||
if (p.shadowColor != null) out.shadow_color = p.shadowColor
|
||
if (p.bgEnabled != null) out.bg_enabled = p.bgEnabled
|
||
if (p.bgColor != null) out.bg_color = p.bgColor
|
||
if (p.bgPadding != null) out.bg_padding = p.bgPadding
|
||
if (p.bgRadius != null) out.bg_radius = p.bgRadius
|
||
if (p.lineOverrides != null) out.line_overrides = p.lineOverrides
|
||
return out
|
||
}
|
||
|
||
/**
|
||
* 把 preset style(snake_case)归一化为固定字号的 TitleSettings,
|
||
* 用于"预设卡片"缩略预览——所有卡片视觉上"标题"两字大小一致,便于辨识。
|
||
* 描边/阴影/背景padding 按 fixedSize / 原始 size 比例缩放,避免粗描边爆框。
|
||
*/
|
||
export function buildPresetPreviewSettings(
|
||
base: TitleSettings,
|
||
presetKey: string,
|
||
fixedSize = 56,
|
||
): TitleSettings {
|
||
const preset = TITLE_PRESETS.find((p) => p.key === presetKey)
|
||
if (!preset) return base
|
||
const origSize = preset.style.size ?? fixedSize
|
||
const ratio = fixedSize / origSize
|
||
const scale = (v: number | undefined, fallback: number): number =>
|
||
v != null ? Math.round(v * ratio) : fallback
|
||
return {
|
||
...base,
|
||
...titleStyleConfigToCamel(preset.style),
|
||
size: fixedSize,
|
||
strokeWidth: scale(preset.style.stroke_width, base.strokeWidth) ?? base.strokeWidth,
|
||
shadowOffsetX: scale(preset.style.shadow_offset_x, base.shadowOffsetX) ?? base.shadowOffsetX,
|
||
shadowOffsetY: scale(preset.style.shadow_offset_y, base.shadowOffsetY) ?? base.shadowOffsetY,
|
||
shadowBlur: scale(preset.style.shadow_blur, base.shadowBlur) ?? base.shadowBlur,
|
||
bgPadding: scale(preset.style.bg_padding, base.bgPadding) ?? base.bgPadding,
|
||
lineOverrides: [],
|
||
}
|
||
}
|