export type Color = 'green' | 'yellow' | 'red' | 'cyan' | undefined export type Part = { text: string; color?: Color; dim?: boolean } export type Segment = { id: string; parts: Part[] } export const BAR_LEN = 8 export const textWidth = (s: string): number => { let w = 0 for (const ch of s) { const c = ch.codePointAt(0)! w += c >= 0x1100 && (c <= 0x115f || (c >= 0x2e80 && c <= 0xa4cf) || (c >= 0xac00 && c <= 0xd7a3) || (c >= 0xf900 && c <= 0xfaff) || (c >= 0xfe30 && c <= 0xfe6f) || (c >= 0xff00 && c <= 0xff60) || (c >= 0xffe0 && c <= 0xffe6)) ? 2 : 1 } return w } export const bar = (pct: number, style: 'block' | 'dot' = 'block'): string => { const n = Math.round((Math.max(0, Math.min(100, pct)) / 100) * BAR_LEN) const [on, off] = style === 'dot' ? ['●', '○'] : ['█', '░'] return on.repeat(n) + off.repeat(BAR_LEN - n) } // 使用率:越高越紅 export const usedColor = (pct: number, warn: number, danger: number): Color => pct >= danger ? 'red' : pct >= warn ? 'yellow' : 'green' // 剩餘:量表越少越紅 export const remainColor = (remain: number, warn: number, danger: number): Color => usedColor(100 - remain, warn, danger) export const FIELD_IDS = ['character', 'love', 'model', 'dir', 'branch', '5h', '7d', 'ctx', 'sys'] as const // 窄終端機時整欄隱藏的順序 export const HIDE_ORDER = ['sys', '7d', 'dir', 'branch'] as const export const parseFields = (s: string): string[] => { const ids = s.split(',').map(x => x.trim()).filter(x => (FIELD_IDS as readonly string[]).includes(x)) return ids.length ? [...new Set(ids)] : [...FIELD_IDS] } const SEP = ' │ ' export const segmentWidth = (s: Segment): number => s.parts.reduce((n, p) => n + textWidth(p.text), 0) // 整欄隱藏,絕不截斷。 export const layout = (segments: Segment[], columns: number): Segment[] => { const list = [...segments] const total = () => list.reduce((n, s) => n + segmentWidth(s), 0) + Math.max(0, list.length - 1) * textWidth(SEP) for (const id of HIDE_ORDER) { if (total() <= columns) break const i = list.findIndex(s => s.id === id) if (i >= 0) list.splice(i, 1) } return list } export const SEPARATOR = SEP export const meter = (label: string, pct: number, color: Color, style: 'block' | 'dot', suffix: string): Segment['parts'] => [ { text: `${label} ` }, { text: bar(pct, style), color }, { text: ` ${Math.round(pct)}%${suffix}` }, ]