tuix
Version:
A performant TUI framework for Bun with JSX and reactive state management
135 lines (111 loc) • 4.02 kB
text/typescript
/**
* Screenshot Reconstruction
*
* Recreates Views from .cks screenshot files
*/
import { Effect } from "effect"
import type { View } from "@/core/types.ts"
import type { CliKitScreenshot, ComponentSnapshot } from "./types.ts"
import { text, styledText, hstack, vstack, box } from "@/core/view.ts"
import { spacer } from "@/layout/index.ts"
import { style, Colors } from "@/styling/index.ts"
import { Borders } from "@/styling/index.ts"
/**
* Reconstruct a View from a screenshot
*/
export function reconstructView(screenshot: CliKitScreenshot): Effect.Effect<View, Error> {
return Effect.try({
try: () => reconstructComponentTree(screenshot.components),
catch: (error) => new Error(`Failed to reconstruct view: ${error}`)
})
}
/**
* Reconstruct a component tree
*/
function reconstructComponentTree(snapshot: ComponentSnapshot): View {
switch (snapshot.type) {
case 'text':
return text(snapshot.content || '')
case 'styledText':
// TODO: Properly deserialize style
const textStyle = snapshot.props?.style ? reconstructStyle(snapshot.props.style) : style()
return styledText(snapshot.content || '', textStyle)
case 'hstack':
const hChildren = snapshot.children?.map(reconstructComponentTree) || []
return hstack(...hChildren)
case 'vstack':
const vChildren = snapshot.children?.map(reconstructComponentTree) || []
return vstack(...vChildren)
case 'box':
const borderStyle = snapshot.props?.border || Borders.single
const boxStyle = snapshot.props?.style ? reconstructStyle(snapshot.props.style) : undefined
const child = snapshot.children?.[0] ? reconstructComponentTree(snapshot.children[0]) : text('')
return box(child, borderStyle, boxStyle)
case 'spacer':
return spacer(snapshot.props?.size || 1)
case 'custom':
// For custom components, we'll need a registry or fallback
return text('[Custom Component]')
default:
return text('[Unknown Component]')
}
}
/**
* Reconstruct a style object from serialized data
*/
function reconstructStyle(styleData: any) {
let s = style()
if (styleData.foreground) {
// Map color names to Colors object
const colorName = styleData.foreground
const color = (Colors as any)[colorName]
if (color) {
s = s.foreground(color)
}
}
if (styleData.background) {
const colorName = styleData.background
const color = (Colors as any)[colorName]
if (color) {
s = s.background(color)
}
}
if (styleData.bold) s = s.bold()
if (styleData.italic) s = s.italic()
if (styleData.underline) s = s.underline()
return s
}
/**
* Create a visual-only view from screenshot (without component tree)
*/
export function createVisualView(screenshot: CliKitScreenshot): View {
const views: View[] = []
screenshot.visual.lines.forEach((line, lineIndex) => {
const styleMap = screenshot.visual.styles.find(s => s.line === lineIndex)
if (!styleMap || styleMap.segments.length === 0) {
// No styles, just plain text
views.push(text(line))
} else {
// Build line with styled segments
const segments: View[] = []
let lastEnd = 0
styleMap.segments.forEach(segment => {
// Add unstyled text before this segment
if (segment.start > lastEnd) {
segments.push(text(line.substring(lastEnd, segment.start)))
}
// Add styled segment
const segmentText = line.substring(segment.start, segment.end)
const segmentStyle = segment.style ? reconstructStyle(segment.style) : style()
segments.push(styledText(segmentText, segmentStyle))
lastEnd = segment.end
})
// Add remaining unstyled text
if (lastEnd < line.length) {
segments.push(text(line.substring(lastEnd)))
}
views.push(hstack(...segments))
}
})
return vstack(...views)
}