mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
import { useMemo } from 'preact/hooks'
|
|
import { useLocale } from '../contexts/Locale.jsx'
|
|
import { useVersion } from '../contexts/Version.jsx'
|
|
import { useAsync } from '../hooks/useAsync.js'
|
|
import { getLanguage, replaceTranslation } from '../services/Resources.js'
|
|
|
|
interface StyleData {
|
|
color?: string,
|
|
bold?: boolean,
|
|
italic?: boolean,
|
|
underlined?: boolean,
|
|
strikethrough?: boolean,
|
|
}
|
|
|
|
interface PartData extends StyleData {
|
|
text?: string,
|
|
translate?: string,
|
|
fallback?: string,
|
|
with?: string[],
|
|
}
|
|
|
|
interface Props {
|
|
component: unknown,
|
|
base?: StyleData,
|
|
oneline?: boolean,
|
|
}
|
|
export function TextComponent({ component, base = { color: 'white' }, oneline }: Props) {
|
|
const { version } = useVersion()
|
|
const { lang } = useLocale()
|
|
|
|
const state = JSON.stringify(component)
|
|
const parts = useMemo(() => {
|
|
const parts: PartData[] = []
|
|
visitComponent(component, el => parts.push(inherit(el, base)))
|
|
return parts
|
|
}, [state, base])
|
|
|
|
const { value: language } = useAsync(() => getLanguage(version, lang), [version, lang])
|
|
|
|
return <div class="text-component">
|
|
{parts.map(p => <TextPart part={p} lang={language ?? {}} oneline={oneline} />)}
|
|
</div>
|
|
}
|
|
|
|
function visitComponent(component: unknown, consumer: (c: PartData) => void) {
|
|
if (typeof component === 'string' || typeof component === 'number') {
|
|
consumer({ text: component.toString() })
|
|
} else if (Array.isArray(component)) {
|
|
const base = component[0] ?? {}
|
|
visitComponent(base, consumer)
|
|
for (const c of component.slice(1)) {
|
|
visitComponent(c, d => consumer(inherit(d, base)))
|
|
}
|
|
} else if (typeof component === 'object' && component !== null) {
|
|
if ('text' in component) {
|
|
consumer(component)
|
|
} else if ('translate' in component) {
|
|
consumer(component)
|
|
} else if ('score' in component) {
|
|
consumer({ ...component, text: '123' })
|
|
} else if ('selector' in component) {
|
|
consumer({ ...component, text: 'Steve' })
|
|
} else if ('keybind' in component) {
|
|
consumer({ ...component, text: (component as any).keybind })
|
|
} else if ('nbt' in component) {
|
|
consumer({ ...component, text: (component as any).nbt })
|
|
}
|
|
if ('extra' in component) {
|
|
for (const e of (component as any).extra) {
|
|
visitComponent(e, c => consumer(inherit(c, component)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function inherit(component: any, base: PartData) {
|
|
return {
|
|
...component,
|
|
color: component.color ?? base.color,
|
|
bold: component.bold ?? base.bold,
|
|
italic: component.italic ?? base.italic,
|
|
underlined: component.underlined ?? base.underlined,
|
|
strikethrough: component.strikethrough ?? base.strikethrough,
|
|
}
|
|
}
|
|
|
|
const TextColors: Record<string, [string, string]> = {
|
|
black: ['#000', '#000'],
|
|
dark_blue: ['#00A', '#00002A'],
|
|
dark_green: ['#0A0', '#002A00'],
|
|
dark_aqua: ['#0AA', '#002A2A'],
|
|
dark_red: ['#A00', '#2A0000'],
|
|
dark_purple: ['#A0A', '#2A002A'],
|
|
gold: ['#FA0', '#2A2A00'],
|
|
gray: ['#AAA', '#2A2A2A'],
|
|
dark_gray: ['#555', '#151515'],
|
|
blue: ['#55F', '#15153F'],
|
|
green: ['#5F5', '#153F15'],
|
|
aqua: ['#5FF', '#153F3F'],
|
|
red: ['#F55', '#3F1515'],
|
|
light_purple: ['#F5F', '#3F153F'],
|
|
yellow: ['#FF5', '#3F3F15'],
|
|
white: ['#FFF', '#3F3F3F'],
|
|
}
|
|
|
|
function TextPart({ part, lang, oneline }: { part: PartData, lang: Record<string, string>, oneline?: boolean }) {
|
|
let text = part.translate
|
|
? resolveTranslate(part.translate, part.fallback, part.with, lang)
|
|
: (part.text ?? '')
|
|
text = oneline ? text.replaceAll('\n', '␊') : text
|
|
return <span style={createStyle(part)}>{text}</span>
|
|
}
|
|
|
|
function resolveTranslate(translate: string, fallback: string | undefined, with_: any[] | undefined, lang: Record<string, string>): string {
|
|
const str = lang[translate] ?? fallback ?? translate
|
|
if (typeof str !== 'string') return translate
|
|
const params = with_?.map((c): string => {
|
|
if (typeof c === 'string' || typeof c === 'number') return `${c}`
|
|
if (c.text) return c.text
|
|
if (c.translate) return resolveTranslate(c.translate, c.fallback, c.with, lang)
|
|
return ''
|
|
})
|
|
return replaceTranslation(str, params)
|
|
}
|
|
|
|
function createStyle(style: StyleData) {
|
|
return {
|
|
color: style.color ? (TextColors[style.color]?.[0] ?? style.color) : undefined,
|
|
'--shadow-color': style.color ? TextColors[style.color]?.[1] : undefined,
|
|
fontWeight: (style.bold === true) ? 'bold' : undefined,
|
|
fontStyle: (style.italic === true) ? 'italic' : undefined,
|
|
textDecoration: (style.underlined === true)
|
|
? (style.strikethrough === true) ? 'underline line-through' : 'underline'
|
|
: (style.strikethrough === true) ? 'line-through' : undefined,
|
|
}
|
|
}
|