@tooee/view 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/launch.tsx ADDED
@@ -0,0 +1,35 @@
1
+ import { launchCli } from "@tooee/shell"
2
+ import type { ActionDefinition } from "@tooee/commands"
3
+ import { View } from "./View.jsx"
4
+ import { DirectoryView } from "./DirectoryView.jsx"
5
+ import type { ContentProvider, ContentRenderer } from "./types.js"
6
+
7
+ export interface ViewLaunchOptions {
8
+ contentProvider: ContentProvider
9
+ actions?: ActionDefinition[]
10
+ renderers?: Record<string, ContentRenderer>
11
+ }
12
+
13
+ export interface DirectoryLaunchOptions {
14
+ dirPath: string
15
+ actions?: ActionDefinition[]
16
+ }
17
+
18
+ export async function launch(options: ViewLaunchOptions): Promise<void> {
19
+ await launchCli(
20
+ <View
21
+ contentProvider={options.contentProvider}
22
+ actions={options.actions}
23
+ renderers={options.renderers}
24
+ />,
25
+ )
26
+ }
27
+
28
+ export async function launchDirectory(options: DirectoryLaunchOptions): Promise<void> {
29
+ await launchCli(
30
+ <DirectoryView
31
+ dirPath={options.dirPath}
32
+ actions={options.actions}
33
+ />,
34
+ )
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,151 @@
1
+ import type { ReactNode } from "react"
2
+ import type { ColumnDef, TableRow } from "@tooee/renderers"
3
+
4
+ // === Built-in content types ===
5
+
6
+ export type Content = MarkdownContent | CodeContent | TextContent | ImageContent | TableContent
7
+
8
+ export type ContentFormat = Content["format"]
9
+
10
+ interface BaseContent {
11
+ title?: string
12
+ getTextContent?: () => string
13
+ }
14
+
15
+ export interface MarkdownContent extends BaseContent {
16
+ format: "markdown"
17
+ markdown: string
18
+ }
19
+
20
+ export interface CodeContent extends BaseContent {
21
+ format: "code"
22
+ code: string
23
+ language?: string
24
+ }
25
+
26
+ export interface TextContent extends BaseContent {
27
+ format: "text"
28
+ text: string
29
+ }
30
+
31
+ export interface ImageContent extends BaseContent {
32
+ format: "image"
33
+ src: string
34
+ }
35
+
36
+ export interface TableContent extends BaseContent {
37
+ format: "table"
38
+ columns: ColumnDef[]
39
+ rows: TableRow[]
40
+ }
41
+
42
+ // === Custom content ===
43
+
44
+ export interface CustomContent<T = unknown> {
45
+ format: string
46
+ data: T
47
+ title?: string
48
+ getTextContent?: () => string
49
+ }
50
+
51
+ /** Content or custom content — used where custom formats are accepted */
52
+ export type AnyContent = Content | CustomContent
53
+
54
+ // === Custom renderers ===
55
+
56
+ export interface ContentRendererProps {
57
+ content: CustomContent
58
+ lineCount: number
59
+ cursor?: number
60
+ selectionStart?: number
61
+ selectionEnd?: number
62
+ }
63
+
64
+ export type ContentRenderer = (props: ContentRendererProps) => ReactNode
65
+
66
+ // === Streaming ===
67
+
68
+ export type AppendableFormat = Extract<ContentFormat, "markdown" | "code" | "text">
69
+
70
+ export type ContentChunk =
71
+ | {
72
+ type: "append"
73
+ format: AppendableFormat
74
+ data: string
75
+ language?: string
76
+ }
77
+ | {
78
+ type: "replace"
79
+ content: AnyContent
80
+ }
81
+ | {
82
+ type: "patch"
83
+ apply: (current: AnyContent | null) => AnyContent
84
+ }
85
+
86
+ // === Provider ===
87
+
88
+ export interface ContentProvider {
89
+ load(): AnyContent | Promise<AnyContent> | AsyncIterable<ContentChunk>
90
+ format?: string
91
+ title?: string
92
+ }
93
+
94
+ // === Compat aliases ===
95
+
96
+ export type ViewContent = Content
97
+ export type ViewContentProvider = ContentProvider
98
+ export type { ColumnDef, TableRow } from "@tooee/renderers"
99
+
100
+ // === Utilities ===
101
+
102
+ const BUILTIN_FORMATS: Set<string> = new Set(["markdown", "code", "text", "image", "table"])
103
+
104
+ export function isBuiltinContent(content: AnyContent): content is Content {
105
+ return BUILTIN_FORMATS.has(content.format)
106
+ }
107
+
108
+ export function isCustomContent(content: AnyContent): content is CustomContent {
109
+ return !BUILTIN_FORMATS.has(content.format)
110
+ }
111
+
112
+ export function getTextContent(content: AnyContent): string {
113
+ if (content.getTextContent) {
114
+ return content.getTextContent()
115
+ }
116
+
117
+ if (isCustomContent(content)) {
118
+ return ""
119
+ }
120
+
121
+ switch (content.format) {
122
+ case "markdown":
123
+ return content.markdown
124
+ case "code":
125
+ return content.code
126
+ case "text":
127
+ return content.text
128
+ case "image":
129
+ return content.src
130
+ case "table": {
131
+ const headers = content.columns.map((column) => column.header ?? column.key)
132
+ const rowLines = content.rows.map((row) =>
133
+ content.columns.map((column) => stringifyCell(row[column.key])).join("\t"),
134
+ )
135
+ return [headers.join("\t"), ...rowLines].join("\n")
136
+ }
137
+ default:
138
+ return ""
139
+ }
140
+ }
141
+
142
+ function stringifyCell(value: unknown): string {
143
+ if (value == null) return ""
144
+ if (typeof value === "string") return value
145
+ if (typeof value === "number" || typeof value === "boolean") return String(value)
146
+ try {
147
+ return JSON.stringify(value)
148
+ } catch {
149
+ return String(value)
150
+ }
151
+ }