62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
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', '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}` },
|
|
]
|