@tooee/view 0.1.21 → 0.2.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/README.md +83 -0
- package/dist/View.d.ts.map +1 -1
- package/dist/View.js +5 -1
- package/dist/View.js.map +1 -1
- package/dist/components/ViewScreen.d.ts +28 -0
- package/dist/components/ViewScreen.d.ts.map +1 -0
- package/dist/components/ViewScreen.js +29 -0
- package/dist/components/ViewScreen.js.map +1 -0
- package/dist/components/subviews/CodeSubview.d.ts +1 -1
- package/dist/components/subviews/CodeSubview.d.ts.map +1 -1
- package/dist/components/subviews/CodeSubview.js +20 -91
- package/dist/components/subviews/CodeSubview.js.map +1 -1
- package/dist/components/subviews/CustomSubview.d.ts +1 -1
- package/dist/components/subviews/CustomSubview.d.ts.map +1 -1
- package/dist/components/subviews/CustomSubview.js +19 -94
- package/dist/components/subviews/CustomSubview.js.map +1 -1
- package/dist/components/subviews/MarkdownSubview.d.ts +1 -1
- package/dist/components/subviews/MarkdownSubview.d.ts.map +1 -1
- package/dist/components/subviews/MarkdownSubview.js +32 -108
- package/dist/components/subviews/MarkdownSubview.js.map +1 -1
- package/dist/components/subviews/TableSubview.d.ts +1 -1
- package/dist/components/subviews/TableSubview.d.ts.map +1 -1
- package/dist/components/subviews/TableSubview.js +22 -125
- package/dist/components/subviews/TableSubview.js.map +1 -1
- package/dist/components/subviews/index.d.ts +0 -1
- package/dist/components/subviews/index.d.ts.map +1 -1
- package/dist/components/subviews/index.js +0 -1
- package/dist/components/subviews/index.js.map +1 -1
- package/dist/components/subviews/types.d.ts +6 -0
- package/dist/components/subviews/types.d.ts.map +1 -1
- package/dist/default-provider.d.ts +6 -3
- package/dist/default-provider.d.ts.map +1 -1
- package/dist/default-provider.js +24 -34
- package/dist/default-provider.js.map +1 -1
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +2 -3
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useContentCommands.d.ts +15 -0
- package/dist/hooks/useContentCommands.d.ts.map +1 -0
- package/dist/hooks/useContentCommands.js +21 -0
- package/dist/hooks/useContentCommands.js.map +1 -0
- package/dist/hooks/useViewCommandContext.d.ts +24 -25
- package/dist/hooks/useViewCommandContext.d.ts.map +1 -1
- package/dist/hooks/useViewCommandContext.js +26 -21
- package/dist/hooks/useViewCommandContext.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +15 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +14 -14
- package/src/View.tsx +6 -1
- package/src/components/ViewScreen.tsx +76 -0
- package/src/components/subviews/CodeSubview.tsx +30 -126
- package/src/components/subviews/CustomSubview.tsx +32 -123
- package/src/components/subviews/MarkdownSubview.tsx +49 -137
- package/src/components/subviews/TableSubview.tsx +36 -170
- package/src/components/subviews/index.ts +0 -1
- package/src/components/subviews/types.ts +6 -0
- package/src/default-provider.ts +37 -34
- package/src/hooks/index.ts +7 -3
- package/src/hooks/useContentCommands.ts +29 -0
- package/src/hooks/useViewCommandContext.ts +62 -49
- package/src/index.ts +10 -0
- package/src/types.ts +15 -16
- package/dist/components/SubviewLayout.d.ts +0 -16
- package/dist/components/SubviewLayout.d.ts.map +0 -1
- package/dist/components/SubviewLayout.js +0 -18
- package/dist/components/SubviewLayout.js.map +0 -1
- package/dist/hooks/useMarkState.d.ts +0 -14
- package/dist/hooks/useMarkState.d.ts.map +0 -1
- package/dist/hooks/useMarkState.js +0 -71
- package/dist/hooks/useMarkState.js.map +0 -1
- package/dist/hooks/useViewCommands.d.ts +0 -13
- package/dist/hooks/useViewCommands.d.ts.map +0 -1
- package/dist/hooks/useViewCommands.js +0 -20
- package/dist/hooks/useViewCommands.js.map +0 -1
- package/src/components/SubviewLayout.tsx +0 -48
- package/src/hooks/useMarkState.ts +0 -100
- package/src/hooks/useViewCommands.ts +0 -36
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CodeView, type
|
|
3
|
-
import {
|
|
4
|
-
import { useHasModalOverlay } from "@tooee/overlays"
|
|
5
|
-
import { useViewCommandContext } from "../../hooks/useViewCommandContext.js"
|
|
6
|
-
import { useCopy, useNavigation } from "@tooee/shell"
|
|
7
|
-
import { findMatchingLines, useSearch } from "@tooee/search"
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { CodeView, sourceLines, sourceLineAdapter, type SourceLineRow } from "@tooee/renderers"
|
|
3
|
+
import { useDocumentController } from "@tooee/shell"
|
|
8
4
|
import { getTextContent, type CustomContent, type ContentRenderer } from "../../types.js"
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { SubviewLayout } from "../SubviewLayout.js"
|
|
5
|
+
import { useContentCommands } from "../../hooks/useContentCommands.js"
|
|
6
|
+
import { ViewScreen } from "../ViewScreen.js"
|
|
12
7
|
import type { SubviewProps } from "./types.js"
|
|
13
8
|
|
|
14
9
|
interface CustomSubviewProps extends SubviewProps {
|
|
@@ -18,134 +13,48 @@ interface CustomSubviewProps extends SubviewProps {
|
|
|
18
13
|
|
|
19
14
|
export function CustomSubview({
|
|
20
15
|
content,
|
|
21
|
-
|
|
22
|
-
userMarks,
|
|
23
|
-
setMarkSet,
|
|
24
|
-
clearMarkNamespace,
|
|
25
|
-
clearAllUserMarks,
|
|
26
|
-
reload,
|
|
27
|
-
streaming,
|
|
16
|
+
decorations,
|
|
28
17
|
actions,
|
|
29
18
|
renderers,
|
|
19
|
+
...screen
|
|
30
20
|
}: CustomSubviewProps) {
|
|
31
|
-
const { theme } = useTheme()
|
|
32
|
-
const docRef = useRef<RowDocumentRenderable>(null)
|
|
33
|
-
const hasModalOverlay = useHasModalOverlay()
|
|
34
21
|
const textContent = useMemo(() => getTextContent(content), [content])
|
|
35
|
-
|
|
36
|
-
const
|
|
22
|
+
// Fallback rows are source lines; row index is the source line.
|
|
23
|
+
const lineRows = useMemo(() => sourceLines(textContent), [textContent])
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
rowCount: lineCount,
|
|
40
|
-
multiSelect: true,
|
|
41
|
-
})
|
|
42
|
-
const search = useSearch({
|
|
43
|
-
match: (query) => findMatchingLines(textContent, query),
|
|
44
|
-
onJump: nav.setCursor,
|
|
45
|
-
})
|
|
46
|
-
useCopy({
|
|
47
|
-
getRowText: (index) => lines[index] ?? "",
|
|
48
|
-
cursor: nav.cursor,
|
|
49
|
-
selection: nav.selection,
|
|
50
|
-
toggledIndices: nav.toggledIndices,
|
|
51
|
-
})
|
|
52
|
-
const layoutNav = { ...nav, ...search }
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (nav.cursor !== null) {
|
|
56
|
-
docRef.current?.scrollToRow(nav.cursor, "nearest")
|
|
57
|
-
}
|
|
58
|
-
}, [nav.cursor])
|
|
59
|
-
|
|
60
|
-
const { themeName } = useViewCommands({ content, textContent, actions })
|
|
25
|
+
useContentCommands({ content, textContent })
|
|
61
26
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
useViewCommandContext({
|
|
71
|
-
content,
|
|
72
|
-
nav,
|
|
73
|
-
reload,
|
|
74
|
-
providerMarks,
|
|
75
|
-
userMarks,
|
|
76
|
-
setMarkSet,
|
|
77
|
-
clearMarkNamespace,
|
|
78
|
-
clearAllUserMarks,
|
|
27
|
+
// Custom content has no action rows of its own, so no context menu is bound.
|
|
28
|
+
const document = useDocumentController<SourceLineRow>({
|
|
29
|
+
rows: lineRows,
|
|
30
|
+
adapter: sourceLineAdapter,
|
|
31
|
+
multiSelect: true,
|
|
32
|
+
decorations,
|
|
79
33
|
})
|
|
80
34
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
const toggledCount = nav.toggledIndices.size
|
|
84
|
-
const selectionItems =
|
|
85
|
-
toggledCount > 0
|
|
86
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
87
|
-
: selectionCount > 0
|
|
88
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
89
|
-
: []
|
|
90
|
-
return [
|
|
35
|
+
const statusItems = useMemo(
|
|
36
|
+
() => [
|
|
91
37
|
{ label: "Format:", value: content.format },
|
|
92
|
-
{ label: "Lines:", value: String(
|
|
93
|
-
|
|
94
|
-
]
|
|
95
|
-
}, [content.format, lineCount, nav.selection, nav.toggledIndices])
|
|
96
|
-
|
|
97
|
-
// Left-click selection for custom renderers. The framework does not own the
|
|
98
|
-
// renderer's markup, so it cannot map clicks to rows itself; instead it hands
|
|
99
|
-
// the renderer this callback (the mouse equivalent of the keyboard cursor
|
|
100
|
-
// move) to call from its own handlers. Guarded like the built-in views so a
|
|
101
|
-
// click never mutates the covered app while a modal overlay is up.
|
|
102
|
-
const setCursor = nav.setCursor
|
|
103
|
-
const handleSelectRow = useCallback(
|
|
104
|
-
(index: number) => {
|
|
105
|
-
if (hasModalOverlay) return
|
|
106
|
-
setCursor(index)
|
|
107
|
-
},
|
|
108
|
-
[hasModalOverlay, setCursor],
|
|
38
|
+
{ label: "Lines:", value: String(lineRows.length) },
|
|
39
|
+
],
|
|
40
|
+
[content.format, lineRows.length],
|
|
109
41
|
)
|
|
110
42
|
|
|
111
43
|
const customRenderer = renderers?.[content.format]
|
|
112
|
-
if (customRenderer) {
|
|
113
|
-
const cursorLine = nav.cursor ?? undefined
|
|
114
|
-
const selectionStart = nav.selection?.start ?? undefined
|
|
115
|
-
const selectionEnd = nav.selection?.end ?? undefined
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<SubviewLayout
|
|
119
|
-
content={content}
|
|
120
|
-
nav={layoutNav}
|
|
121
|
-
streaming={streaming}
|
|
122
|
-
themeName={themeName}
|
|
123
|
-
extraStatusItems={extraStatusItems}
|
|
124
|
-
>
|
|
125
|
-
{customRenderer({
|
|
126
|
-
content,
|
|
127
|
-
lineCount,
|
|
128
|
-
cursor: cursorLine,
|
|
129
|
-
selectionStart,
|
|
130
|
-
selectionEnd,
|
|
131
|
-
marks: markState,
|
|
132
|
-
onSelectRow: handleSelectRow,
|
|
133
|
-
})}
|
|
134
|
-
</SubviewLayout>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
44
|
|
|
138
|
-
// No renderer for this custom format -- fall back to text
|
|
139
|
-
const text = getTextContent(content)
|
|
140
45
|
return (
|
|
141
|
-
<
|
|
46
|
+
<ViewScreen
|
|
142
47
|
content={content}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
48
|
+
controller={document}
|
|
49
|
+
actions={actions}
|
|
50
|
+
statusItems={statusItems}
|
|
51
|
+
{...screen}
|
|
147
52
|
>
|
|
148
|
-
|
|
149
|
-
|
|
53
|
+
{customRenderer ? (
|
|
54
|
+
customRenderer({ content, document })
|
|
55
|
+
) : (
|
|
56
|
+
<CodeView content={textContent} showLineNumbers={false} document={document} />
|
|
57
|
+
)}
|
|
58
|
+
</ViewScreen>
|
|
150
59
|
)
|
|
151
60
|
}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { marked } from "marked"
|
|
1
|
+
import { useMemo, useRef } from "react"
|
|
3
2
|
import type { TextBufferRenderable } from "@opentui/core"
|
|
4
3
|
import {
|
|
5
4
|
MarkdownView,
|
|
6
|
-
|
|
5
|
+
flattenMarkdown,
|
|
6
|
+
getFlatBlockText,
|
|
7
7
|
type CodeBlockRenderer,
|
|
8
|
-
type
|
|
9
|
-
type RowDocumentRenderable,
|
|
8
|
+
type FlatBlock,
|
|
10
9
|
} from "@tooee/renderers"
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
import { useCommand } from "@tooee/commands"
|
|
11
|
+
import {
|
|
12
|
+
actionsToContextMenuEntries,
|
|
13
|
+
useDocumentController,
|
|
14
|
+
type DocumentRowAdapter,
|
|
15
|
+
} from "@tooee/shell"
|
|
17
16
|
import type { MarkdownContent } from "../../types.js"
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { SubviewLayout } from "../SubviewLayout.js"
|
|
17
|
+
import { useContentCommands } from "../../hooks/useContentCommands.js"
|
|
18
|
+
import { ViewScreen } from "../ViewScreen.js"
|
|
21
19
|
import type { SubviewProps } from "./types.js"
|
|
22
20
|
|
|
23
21
|
interface MarkdownSubviewProps extends SubviewProps {
|
|
@@ -28,68 +26,43 @@ interface MarkdownSubviewProps extends SubviewProps {
|
|
|
28
26
|
/** Columns moved per h/l press when scrolling a wide block horizontally. */
|
|
29
27
|
const BLOCK_HSCROLL_STEP = 4
|
|
30
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Blocks are the row unit. `getFlatBlockText` keeps search/copy in step with the
|
|
31
|
+
* source mapping (notably for synthetic bullet rows), and `getSource` projects
|
|
32
|
+
* each block's Markdown provenance onto the controller's anchors.
|
|
33
|
+
*/
|
|
34
|
+
const MARKDOWN_BLOCK_ADAPTER: DocumentRowAdapter<FlatBlock> = {
|
|
35
|
+
getText: (block) => getFlatBlockText(block),
|
|
36
|
+
getSource: (block) => block.source,
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export function MarkdownSubview({
|
|
32
40
|
content,
|
|
33
41
|
codeBlockRenderers,
|
|
34
|
-
|
|
35
|
-
userMarks,
|
|
36
|
-
setMarkSet,
|
|
37
|
-
clearMarkNamespace,
|
|
38
|
-
clearAllUserMarks,
|
|
39
|
-
reload,
|
|
40
|
-
streaming,
|
|
42
|
+
decorations,
|
|
41
43
|
actions,
|
|
44
|
+
...screen
|
|
42
45
|
}: MarkdownSubviewProps) {
|
|
43
|
-
const { theme } = useTheme()
|
|
44
|
-
const docRef = useRef<RowDocumentRenderable>(null)
|
|
45
|
-
const hasModalOverlay = useHasModalOverlay()
|
|
46
|
-
const contextMenu = useContextMenu()
|
|
47
|
-
const { invoke } = useCommandContext()
|
|
48
46
|
const textContent = content.markdown
|
|
49
47
|
const lineCount = useMemo(() => textContent.split("\n").length, [textContent])
|
|
50
|
-
const blocks = useMemo(() =>
|
|
48
|
+
const blocks = useMemo(() => flattenMarkdown(content.markdown), [content.markdown])
|
|
51
49
|
|
|
52
|
-
const
|
|
53
|
-
|
|
50
|
+
const { showLineNumbers } = useContentCommands({ content, textContent })
|
|
51
|
+
const contextMenu = useMemo(() => actionsToContextMenuEntries(actions), [actions])
|
|
52
|
+
|
|
53
|
+
const document = useDocumentController<FlatBlock>({
|
|
54
|
+
rows: blocks,
|
|
55
|
+
adapter: MARKDOWN_BLOCK_ADAPTER,
|
|
54
56
|
multiSelect: true,
|
|
57
|
+
decorations,
|
|
58
|
+
contextMenu,
|
|
55
59
|
})
|
|
56
|
-
const search = useSearch({
|
|
57
|
-
match: (query) => {
|
|
58
|
-
const lowerQuery = query.toLowerCase()
|
|
59
|
-
return blocks.flatMap((block, index) => {
|
|
60
|
-
const { token } = block
|
|
61
|
-
const raw = "raw" in token && typeof token.raw === "string" ? token.raw : ""
|
|
62
|
-
return raw.toLowerCase().includes(lowerQuery) ? [index] : []
|
|
63
|
-
})
|
|
64
|
-
},
|
|
65
|
-
onJump: nav.setCursor,
|
|
66
|
-
})
|
|
67
|
-
useCopy({
|
|
68
|
-
getRowText: (index) => {
|
|
69
|
-
const block = blocks[index]
|
|
70
|
-
if (!block) return ""
|
|
71
|
-
const { token } = block
|
|
72
|
-
return "raw" in token && typeof token.raw === "string" ? token.raw : ""
|
|
73
|
-
},
|
|
74
|
-
cursor: nav.cursor,
|
|
75
|
-
selection: nav.selection,
|
|
76
|
-
toggledIndices: nav.toggledIndices,
|
|
77
|
-
})
|
|
78
|
-
const layoutNav = { ...nav, ...search }
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (nav.cursor !== null) {
|
|
82
|
-
docRef.current?.scrollToRow(nav.cursor, "nearest")
|
|
83
|
-
}
|
|
84
|
-
}, [nav.cursor])
|
|
85
60
|
|
|
86
|
-
// Horizontal panning for wide blocks (mermaid diagrams and code blocks).
|
|
87
|
-
// Blocks that can overflow horizontally register their text-buffer
|
|
88
|
-
// renderable by block index; h/l in cursor mode pans the block under the
|
|
89
|
-
// nav cursor via `scrollX` (the setter clamps to the content width).
|
|
90
61
|
const hScrollableBlocksRef = useRef<Map<number, TextBufferRenderable>>(new Map())
|
|
91
62
|
const cursorScrollable = () =>
|
|
92
|
-
|
|
63
|
+
document.activeIndex !== null
|
|
64
|
+
? hScrollableBlocksRef.current.get(document.activeIndex)
|
|
65
|
+
: undefined
|
|
93
66
|
useCommand({
|
|
94
67
|
id: "block-scroll-left",
|
|
95
68
|
title: "Scroll block left",
|
|
@@ -113,91 +86,30 @@ export function MarkdownSubview({
|
|
|
113
86
|
},
|
|
114
87
|
})
|
|
115
88
|
|
|
116
|
-
const
|
|
117
|
-
() =>
|
|
118
|
-
(actions ?? [])
|
|
119
|
-
.filter((action) => !action.hidden)
|
|
120
|
-
.map((action) => ({ id: action.id, title: action.title, hotkey: action.hotkey })),
|
|
121
|
-
[actions],
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
// Left-click selects the clicked block; right-click selects it and opens the
|
|
125
|
-
// same app-provided action menu as table/code rows. Both stand down while a
|
|
126
|
-
// modal overlay is up (theme picker, command palette, Ask/Choose), matching
|
|
127
|
-
// keyboard stand-down semantics.
|
|
128
|
-
const setCursor = nav.setCursor
|
|
129
|
-
const openContextMenu = contextMenu.open
|
|
130
|
-
const handleRowClick = useCallback(
|
|
131
|
-
(index: number) => {
|
|
132
|
-
if (hasModalOverlay) return
|
|
133
|
-
setCursor(index)
|
|
134
|
-
},
|
|
135
|
-
[hasModalOverlay, setCursor],
|
|
136
|
-
)
|
|
137
|
-
const handleRowContextMenu = useCallback(
|
|
138
|
-
(index: number, x: number, y: number) => {
|
|
139
|
-
if (hasModalOverlay) return
|
|
140
|
-
setCursor(index)
|
|
141
|
-
openContextMenu(x, y, menuEntries, invoke)
|
|
142
|
-
},
|
|
143
|
-
[hasModalOverlay, setCursor, openContextMenu, menuEntries, invoke],
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
const { themeName, showLineNumbers } = useViewCommands({ content, textContent, actions })
|
|
147
|
-
|
|
148
|
-
const markState = useMarkState({
|
|
149
|
-
nav,
|
|
150
|
-
search,
|
|
151
|
-
theme,
|
|
152
|
-
providerMarks,
|
|
153
|
-
userMarks,
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
useViewCommandContext({
|
|
157
|
-
content,
|
|
158
|
-
nav,
|
|
159
|
-
reload,
|
|
160
|
-
providerMarks,
|
|
161
|
-
userMarks,
|
|
162
|
-
setMarkSet,
|
|
163
|
-
clearMarkNamespace,
|
|
164
|
-
clearAllUserMarks,
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const extraStatusItems = useMemo(() => {
|
|
168
|
-
const selectionCount = nav.selection != null ? nav.selection.end - nav.selection.start + 1 : 0
|
|
169
|
-
const toggledCount = nav.toggledIndices.size
|
|
170
|
-
const selectionItems =
|
|
171
|
-
toggledCount > 0
|
|
172
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
173
|
-
: selectionCount > 0
|
|
174
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
175
|
-
: []
|
|
176
|
-
return [
|
|
89
|
+
const statusItems = useMemo(
|
|
90
|
+
() => [
|
|
177
91
|
{ label: "Format:", value: content.format },
|
|
178
92
|
{ label: "Lines:", value: String(lineCount) },
|
|
179
|
-
|
|
180
|
-
]
|
|
181
|
-
|
|
93
|
+
],
|
|
94
|
+
[content.format, lineCount],
|
|
95
|
+
)
|
|
182
96
|
|
|
183
97
|
return (
|
|
184
|
-
<
|
|
98
|
+
<ViewScreen
|
|
185
99
|
content={content}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
100
|
+
controller={document}
|
|
101
|
+
actions={actions}
|
|
102
|
+
statusItems={statusItems}
|
|
103
|
+
{...screen}
|
|
190
104
|
>
|
|
191
105
|
<MarkdownView
|
|
192
106
|
content={content.markdown}
|
|
107
|
+
blocks={blocks}
|
|
193
108
|
showLineNumbers={showLineNumbers}
|
|
194
|
-
|
|
195
|
-
docRef={docRef}
|
|
109
|
+
document={document}
|
|
196
110
|
hScrollableBlocksRef={hScrollableBlocksRef}
|
|
197
111
|
codeBlockRenderers={codeBlockRenderers}
|
|
198
|
-
onRowClick={handleRowClick}
|
|
199
|
-
onRowContextMenu={handleRowContextMenu}
|
|
200
112
|
/>
|
|
201
|
-
</
|
|
113
|
+
</ViewScreen>
|
|
202
114
|
)
|
|
203
115
|
}
|
|
@@ -1,190 +1,56 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Table, type
|
|
3
|
-
import {
|
|
4
|
-
import { useCommandContext } from "@tooee/commands"
|
|
5
|
-
import { useHasModalOverlay } from "@tooee/overlays"
|
|
6
|
-
import { useViewCommandContext } from "../../hooks/useViewCommandContext.js"
|
|
7
|
-
import { useContextMenu, useCopy, useNavigation } from "@tooee/shell"
|
|
8
|
-
import { useSearch } from "@tooee/search"
|
|
1
|
+
import { useMemo } from "react"
|
|
2
|
+
import { Table, type TableRow } from "@tooee/renderers"
|
|
3
|
+
import { actionsToContextMenuEntries, useDocumentController } from "@tooee/shell"
|
|
9
4
|
import { getTextContent, type TableContent } from "../../types.js"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { SubviewLayout } from "../SubviewLayout.js"
|
|
5
|
+
import { useContentCommands } from "../../hooks/useContentCommands.js"
|
|
6
|
+
import { ViewScreen } from "../ViewScreen.js"
|
|
13
7
|
import type { SubviewProps } from "./types.js"
|
|
14
8
|
|
|
15
9
|
interface TableSubviewProps extends SubviewProps {
|
|
16
10
|
content: TableContent
|
|
17
11
|
}
|
|
18
12
|
|
|
19
|
-
export function TableSubview({
|
|
20
|
-
content,
|
|
21
|
-
providerMarks,
|
|
22
|
-
userMarks,
|
|
23
|
-
setMarkSet,
|
|
24
|
-
clearMarkNamespace,
|
|
25
|
-
clearAllUserMarks,
|
|
26
|
-
reload,
|
|
27
|
-
streaming,
|
|
28
|
-
actions,
|
|
29
|
-
}: TableSubviewProps) {
|
|
30
|
-
const { theme } = useTheme()
|
|
31
|
-
const docRef = useRef<RowDocumentRenderable>(null)
|
|
13
|
+
export function TableSubview({ content, decorations, actions, ...screen }: TableSubviewProps) {
|
|
32
14
|
const textContent = useMemo(() => getTextContent(content), [content])
|
|
33
|
-
const
|
|
34
|
-
const { invoke } = useCommandContext()
|
|
35
|
-
const hasModalOverlay = useHasModalOverlay()
|
|
15
|
+
const { showLineNumbers } = useContentCommands({ content, textContent })
|
|
36
16
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return content.rows.flatMap((row, index) =>
|
|
45
|
-
content.columns.some((column) =>
|
|
46
|
-
stringifyRowCell(row[column.key]).toLowerCase().includes(lowerQuery),
|
|
47
|
-
)
|
|
48
|
-
? [index]
|
|
49
|
-
: [],
|
|
50
|
-
)
|
|
51
|
-
},
|
|
52
|
-
onJump: nav.setCursor,
|
|
53
|
-
})
|
|
54
|
-
useCopy({
|
|
55
|
-
getRowText: (index) =>
|
|
56
|
-
content.columns
|
|
57
|
-
.map((column) => stringifyRowCell(content.rows[index]?.[column.key]))
|
|
58
|
-
.join("\t"),
|
|
59
|
-
cursor: nav.cursor,
|
|
60
|
-
selection: nav.selection,
|
|
61
|
-
toggledIndices: nav.toggledIndices,
|
|
62
|
-
})
|
|
63
|
-
const layoutNav = { ...nav, ...search }
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
if (nav.cursor !== null) {
|
|
67
|
-
docRef.current?.scrollToRow(nav.cursor, "nearest")
|
|
68
|
-
}
|
|
69
|
-
}, [nav.cursor])
|
|
70
|
-
|
|
71
|
-
const { themeName, showLineNumbers } = useViewCommands({ content, textContent, actions })
|
|
72
|
-
|
|
73
|
-
const markState = useMarkState({
|
|
74
|
-
nav,
|
|
75
|
-
search,
|
|
76
|
-
theme,
|
|
77
|
-
providerMarks,
|
|
78
|
-
userMarks,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
function getActiveRow(): Record<string, unknown> | undefined {
|
|
82
|
-
if (nav.cursor === null) return undefined
|
|
83
|
-
return content.rows[nav.cursor]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function getSelectedRows(): Record<string, unknown>[] {
|
|
87
|
-
if (nav.toggledIndices.size) {
|
|
88
|
-
return Array.from(nav.toggledIndices)
|
|
89
|
-
.map((i) => content.rows[i])
|
|
90
|
-
.filter(Boolean)
|
|
91
|
-
}
|
|
92
|
-
if (nav.selection) {
|
|
93
|
-
const start = nav.selection.start
|
|
94
|
-
const end = nav.selection.end
|
|
95
|
-
return content.rows.slice(start, end + 1)
|
|
96
|
-
}
|
|
97
|
-
return []
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
useViewCommandContext({
|
|
101
|
-
content,
|
|
102
|
-
nav,
|
|
103
|
-
reload,
|
|
104
|
-
providerMarks,
|
|
105
|
-
userMarks,
|
|
106
|
-
setMarkSet,
|
|
107
|
-
clearMarkNamespace,
|
|
108
|
-
clearAllUserMarks,
|
|
109
|
-
extras: {
|
|
110
|
-
activeRow: getActiveRow(),
|
|
111
|
-
selectedRows: getSelectedRows(),
|
|
112
|
-
},
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// Row-scoped context-menu entries come from the app-provided actions.
|
|
116
|
-
const menuEntries = useMemo<ContextMenuEntry[]>(
|
|
117
|
-
() =>
|
|
118
|
-
(actions ?? [])
|
|
119
|
-
.filter((action) => !action.hidden)
|
|
120
|
-
.map((action) => ({ id: action.id, title: action.title, hotkey: action.hotkey })),
|
|
121
|
-
[actions],
|
|
17
|
+
const { columns, rows } = content
|
|
18
|
+
const adapter = useMemo(
|
|
19
|
+
() => ({
|
|
20
|
+
getText: (row: TableRow) =>
|
|
21
|
+
columns.map((column) => stringifyRowCell(row[column.key])).join("\t"),
|
|
22
|
+
}),
|
|
23
|
+
[columns],
|
|
122
24
|
)
|
|
25
|
+
const contextMenu = useMemo(() => actionsToContextMenuEntries(actions), [actions])
|
|
123
26
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const handleRowClick = useCallback(
|
|
132
|
-
(index: number) => {
|
|
133
|
-
if (hasModalOverlay) return
|
|
134
|
-
setCursor(index)
|
|
135
|
-
},
|
|
136
|
-
[hasModalOverlay, setCursor],
|
|
137
|
-
)
|
|
138
|
-
const handleRowContextMenu = useCallback(
|
|
139
|
-
(index: number, x: number, y: number) => {
|
|
140
|
-
if (hasModalOverlay) return
|
|
141
|
-
setCursor(index)
|
|
142
|
-
openContextMenu(x, y, menuEntries, invoke)
|
|
143
|
-
},
|
|
144
|
-
[hasModalOverlay, setCursor, openContextMenu, menuEntries, invoke],
|
|
145
|
-
)
|
|
27
|
+
const document = useDocumentController<TableRow>({
|
|
28
|
+
rows,
|
|
29
|
+
adapter,
|
|
30
|
+
multiSelect: true,
|
|
31
|
+
decorations,
|
|
32
|
+
contextMenu,
|
|
33
|
+
})
|
|
146
34
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
const toggledCount = nav.toggledIndices.size
|
|
150
|
-
const selectionItems =
|
|
151
|
-
toggledCount > 0
|
|
152
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
153
|
-
: selectionCount > 0
|
|
154
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
155
|
-
: []
|
|
156
|
-
return [
|
|
35
|
+
const statusItems = useMemo(
|
|
36
|
+
() => [
|
|
157
37
|
{ label: "Format:", value: content.format },
|
|
158
|
-
{ label: "Rows:", value: String(
|
|
159
|
-
{ label: "Cols:", value: String(
|
|
160
|
-
|
|
161
|
-
]
|
|
162
|
-
|
|
163
|
-
content.format,
|
|
164
|
-
content.rows.length,
|
|
165
|
-
content.columns.length,
|
|
166
|
-
nav.selection,
|
|
167
|
-
nav.toggledIndices,
|
|
168
|
-
])
|
|
38
|
+
{ label: "Rows:", value: String(rows.length) },
|
|
39
|
+
{ label: "Cols:", value: String(columns.length) },
|
|
40
|
+
],
|
|
41
|
+
[content.format, rows.length, columns.length],
|
|
42
|
+
)
|
|
169
43
|
|
|
170
44
|
return (
|
|
171
|
-
<
|
|
45
|
+
<ViewScreen
|
|
172
46
|
content={content}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
47
|
+
controller={document}
|
|
48
|
+
actions={actions}
|
|
49
|
+
statusItems={statusItems}
|
|
50
|
+
{...screen}
|
|
177
51
|
>
|
|
178
|
-
<Table
|
|
179
|
-
|
|
180
|
-
rows={content.rows}
|
|
181
|
-
showLineNumbers={showLineNumbers}
|
|
182
|
-
marks={markState}
|
|
183
|
-
docRef={docRef}
|
|
184
|
-
onRowClick={handleRowClick}
|
|
185
|
-
onRowContextMenu={handleRowContextMenu}
|
|
186
|
-
/>
|
|
187
|
-
</SubviewLayout>
|
|
52
|
+
<Table columns={columns} rows={rows} showLineNumbers={showLineNumbers} document={document} />
|
|
53
|
+
</ViewScreen>
|
|
188
54
|
)
|
|
189
55
|
}
|
|
190
56
|
|
|
@@ -2,5 +2,4 @@ export { MarkdownSubview } from "./MarkdownSubview.js"
|
|
|
2
2
|
export { CodeSubview } from "./CodeSubview.js"
|
|
3
3
|
export { TableSubview } from "./TableSubview.js"
|
|
4
4
|
export { CustomSubview } from "./CustomSubview.js"
|
|
5
|
-
export { SubviewLayout } from "../SubviewLayout.js"
|
|
6
5
|
export type { SubviewProps } from "./types.js"
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { MarkSet } from "@tooee/marks"
|
|
2
2
|
import type { ActionDefinition } from "@tooee/commands"
|
|
3
|
+
import type { DecorationLayer } from "@tooee/renderers"
|
|
3
4
|
import type { AnyContent } from "../../types.js"
|
|
4
5
|
|
|
5
6
|
export interface SubviewProps {
|
|
6
7
|
content: AnyContent
|
|
8
|
+
/**
|
|
9
|
+
* Provider and user mark sets as decoration layers. They compose with the
|
|
10
|
+
* interaction layers (search, selection, cursor) the controller generates.
|
|
11
|
+
*/
|
|
12
|
+
decorations: DecorationLayer[]
|
|
7
13
|
providerMarks: MarkSet[]
|
|
8
14
|
userMarks: MarkSet[]
|
|
9
15
|
setMarkSet: (set: MarkSet) => void
|