@tooee/view 0.1.22 → 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 +67 -12
- 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 -89
- 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 -106
- 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 -123
- 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 +2 -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 +10 -42
- package/dist/hooks/useViewCommandContext.d.ts.map +1 -1
- package/dist/hooks/useViewCommandContext.js +3 -30
- package/dist/hooks/useViewCommandContext.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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 -120
- package/src/components/subviews/CustomSubview.tsx +32 -123
- package/src/components/subviews/MarkdownSubview.tsx +49 -130
- package/src/components/subviews/TableSubview.tsx +36 -164
- 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 +2 -7
- package/src/hooks/useContentCommands.ts +29 -0
- package/src/hooks/useViewCommandContext.ts +13 -81
- package/src/index.ts +1 -1
- 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,22 +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
|
|
8
|
+
type FlatBlock,
|
|
9
9
|
} from "@tooee/renderers"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
import { useCommand } from "@tooee/commands"
|
|
11
|
+
import {
|
|
12
|
+
actionsToContextMenuEntries,
|
|
13
|
+
useDocumentController,
|
|
14
|
+
type DocumentRowAdapter,
|
|
15
|
+
} from "@tooee/shell"
|
|
16
16
|
import type { MarkdownContent } from "../../types.js"
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import { SubviewLayout } from "../SubviewLayout.js"
|
|
17
|
+
import { useContentCommands } from "../../hooks/useContentCommands.js"
|
|
18
|
+
import { ViewScreen } from "../ViewScreen.js"
|
|
20
19
|
import type { SubviewProps } from "./types.js"
|
|
21
20
|
|
|
22
21
|
interface MarkdownSubviewProps extends SubviewProps {
|
|
@@ -27,68 +26,43 @@ interface MarkdownSubviewProps extends SubviewProps {
|
|
|
27
26
|
/** Columns moved per h/l press when scrolling a wide block horizontally. */
|
|
28
27
|
const BLOCK_HSCROLL_STEP = 4
|
|
29
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
|
+
|
|
30
39
|
export function MarkdownSubview({
|
|
31
40
|
content,
|
|
32
41
|
codeBlockRenderers,
|
|
33
|
-
|
|
34
|
-
userMarks,
|
|
35
|
-
setMarkSet,
|
|
36
|
-
clearMarkNamespace,
|
|
37
|
-
clearAllUserMarks,
|
|
38
|
-
reload,
|
|
39
|
-
streaming,
|
|
42
|
+
decorations,
|
|
40
43
|
actions,
|
|
44
|
+
...screen
|
|
41
45
|
}: MarkdownSubviewProps) {
|
|
42
|
-
const { theme } = useTheme()
|
|
43
|
-
const docRef = useRef<RowDocumentRenderable>(null)
|
|
44
|
-
const hasModalOverlay = useHasModalOverlay()
|
|
45
|
-
const contextMenu = useContextMenu()
|
|
46
|
-
const { invoke } = useCommandContext()
|
|
47
46
|
const textContent = content.markdown
|
|
48
47
|
const lineCount = useMemo(() => textContent.split("\n").length, [textContent])
|
|
49
|
-
const blocks = useMemo(() =>
|
|
48
|
+
const blocks = useMemo(() => flattenMarkdown(content.markdown), [content.markdown])
|
|
50
49
|
|
|
51
|
-
const
|
|
52
|
-
|
|
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,
|
|
53
56
|
multiSelect: true,
|
|
57
|
+
decorations,
|
|
58
|
+
contextMenu,
|
|
54
59
|
})
|
|
55
|
-
const search = useSearch({
|
|
56
|
-
match: (query) => {
|
|
57
|
-
const lowerQuery = query.toLowerCase()
|
|
58
|
-
return blocks.flatMap((block, index) => {
|
|
59
|
-
const { token } = block
|
|
60
|
-
const raw = "raw" in token && typeof token.raw === "string" ? token.raw : ""
|
|
61
|
-
return raw.toLowerCase().includes(lowerQuery) ? [index] : []
|
|
62
|
-
})
|
|
63
|
-
},
|
|
64
|
-
onJump: nav.setCursor,
|
|
65
|
-
})
|
|
66
|
-
useCopy({
|
|
67
|
-
getRowText: (index) => {
|
|
68
|
-
const block = blocks[index]
|
|
69
|
-
if (!block) return ""
|
|
70
|
-
const { token } = block
|
|
71
|
-
return "raw" in token && typeof token.raw === "string" ? token.raw : ""
|
|
72
|
-
},
|
|
73
|
-
cursor: nav.cursor,
|
|
74
|
-
selection: nav.selection,
|
|
75
|
-
toggledIndices: nav.toggledIndices,
|
|
76
|
-
})
|
|
77
|
-
const layoutNav = { ...nav, ...search }
|
|
78
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (nav.cursor !== null) {
|
|
81
|
-
docRef.current?.scrollToRow(nav.cursor, "nearest")
|
|
82
|
-
}
|
|
83
|
-
}, [nav.cursor])
|
|
84
60
|
|
|
85
|
-
// Horizontal panning for wide blocks (mermaid diagrams and code blocks).
|
|
86
|
-
// Blocks that can overflow horizontally register their text-buffer
|
|
87
|
-
// renderable by block index; h/l in cursor mode pans the block under the
|
|
88
|
-
// nav cursor via `scrollX` (the setter clamps to the content width).
|
|
89
61
|
const hScrollableBlocksRef = useRef<Map<number, TextBufferRenderable>>(new Map())
|
|
90
62
|
const cursorScrollable = () =>
|
|
91
|
-
|
|
63
|
+
document.activeIndex !== null
|
|
64
|
+
? hScrollableBlocksRef.current.get(document.activeIndex)
|
|
65
|
+
: undefined
|
|
92
66
|
useCommand({
|
|
93
67
|
id: "block-scroll-left",
|
|
94
68
|
title: "Scroll block left",
|
|
@@ -112,85 +86,30 @@ export function MarkdownSubview({
|
|
|
112
86
|
},
|
|
113
87
|
})
|
|
114
88
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
// Left-click selects the clicked block; right-click selects it and opens the
|
|
118
|
-
// same app-provided action menu as table/code rows. Both stand down while a
|
|
119
|
-
// modal overlay is up (theme picker, command palette, Ask/Choose), matching
|
|
120
|
-
// keyboard stand-down semantics.
|
|
121
|
-
const setCursor = nav.setCursor
|
|
122
|
-
const openContextMenu = contextMenu.open
|
|
123
|
-
const handleRowClick = useCallback(
|
|
124
|
-
(index: number) => {
|
|
125
|
-
if (hasModalOverlay) return
|
|
126
|
-
setCursor(index)
|
|
127
|
-
},
|
|
128
|
-
[hasModalOverlay, setCursor],
|
|
129
|
-
)
|
|
130
|
-
const handleRowContextMenu = useCallback(
|
|
131
|
-
(index: number, x: number, y: number) => {
|
|
132
|
-
if (hasModalOverlay) return
|
|
133
|
-
setCursor(index)
|
|
134
|
-
openContextMenu(x, y, menuEntries, invoke)
|
|
135
|
-
},
|
|
136
|
-
[hasModalOverlay, setCursor, openContextMenu, menuEntries, invoke],
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
const { themeName, showLineNumbers } = useViewCommands({ content, textContent, actions })
|
|
140
|
-
|
|
141
|
-
const markState = useMarkState({
|
|
142
|
-
nav,
|
|
143
|
-
search,
|
|
144
|
-
theme,
|
|
145
|
-
providerMarks,
|
|
146
|
-
userMarks,
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
useViewCommandContext({
|
|
150
|
-
content,
|
|
151
|
-
nav,
|
|
152
|
-
reload,
|
|
153
|
-
providerMarks,
|
|
154
|
-
userMarks,
|
|
155
|
-
setMarkSet,
|
|
156
|
-
clearMarkNamespace,
|
|
157
|
-
clearAllUserMarks,
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const extraStatusItems = useMemo(() => {
|
|
161
|
-
const selectionCount = nav.selection != null ? nav.selection.end - nav.selection.start + 1 : 0
|
|
162
|
-
const toggledCount = nav.toggledIndices.size
|
|
163
|
-
const selectionItems =
|
|
164
|
-
toggledCount > 0
|
|
165
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
166
|
-
: selectionCount > 0
|
|
167
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
168
|
-
: []
|
|
169
|
-
return [
|
|
89
|
+
const statusItems = useMemo(
|
|
90
|
+
() => [
|
|
170
91
|
{ label: "Format:", value: content.format },
|
|
171
92
|
{ label: "Lines:", value: String(lineCount) },
|
|
172
|
-
|
|
173
|
-
]
|
|
174
|
-
|
|
93
|
+
],
|
|
94
|
+
[content.format, lineCount],
|
|
95
|
+
)
|
|
175
96
|
|
|
176
97
|
return (
|
|
177
|
-
<
|
|
98
|
+
<ViewScreen
|
|
178
99
|
content={content}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
100
|
+
controller={document}
|
|
101
|
+
actions={actions}
|
|
102
|
+
statusItems={statusItems}
|
|
103
|
+
{...screen}
|
|
183
104
|
>
|
|
184
105
|
<MarkdownView
|
|
185
106
|
content={content.markdown}
|
|
107
|
+
blocks={blocks}
|
|
186
108
|
showLineNumbers={showLineNumbers}
|
|
187
|
-
|
|
188
|
-
docRef={docRef}
|
|
109
|
+
document={document}
|
|
189
110
|
hScrollableBlocksRef={hScrollableBlocksRef}
|
|
190
111
|
codeBlockRenderers={codeBlockRenderers}
|
|
191
|
-
onRowClick={handleRowClick}
|
|
192
|
-
onRowContextMenu={handleRowContextMenu}
|
|
193
112
|
/>
|
|
194
|
-
</
|
|
113
|
+
</ViewScreen>
|
|
195
114
|
)
|
|
196
115
|
}
|
|
@@ -1,184 +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 { actionsToContextMenuEntries, 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
|
-
|
|
45
|
-
|
|
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
|
-
}
|
|
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],
|
|
24
|
+
)
|
|
25
|
+
const contextMenu = useMemo(() => actionsToContextMenuEntries(actions), [actions])
|
|
99
26
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
setMarkSet,
|
|
107
|
-
clearMarkNamespace,
|
|
108
|
-
clearAllUserMarks,
|
|
109
|
-
extras: {
|
|
110
|
-
activeRow: getActiveRow(),
|
|
111
|
-
selectedRows: getSelectedRows(),
|
|
112
|
-
},
|
|
27
|
+
const document = useDocumentController<TableRow>({
|
|
28
|
+
rows,
|
|
29
|
+
adapter,
|
|
30
|
+
multiSelect: true,
|
|
31
|
+
decorations,
|
|
32
|
+
contextMenu,
|
|
113
33
|
})
|
|
114
34
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
// Row mouse handlers stand down while a modal overlay (theme picker, command
|
|
119
|
-
// palette, Ask/Choose overlays, the context menu itself) is up: centered
|
|
120
|
-
// overlays leave clickable margins around them, and mouse events route
|
|
121
|
-
// through the hit-grid, bypassing command-surface arbitration. Memoized so
|
|
122
|
-
// Table's rowElements memo keeps doing its job.
|
|
123
|
-
const setCursor = nav.setCursor
|
|
124
|
-
const openContextMenu = contextMenu.open
|
|
125
|
-
const handleRowClick = useCallback(
|
|
126
|
-
(index: number) => {
|
|
127
|
-
if (hasModalOverlay) return
|
|
128
|
-
setCursor(index)
|
|
129
|
-
},
|
|
130
|
-
[hasModalOverlay, setCursor],
|
|
131
|
-
)
|
|
132
|
-
const handleRowContextMenu = useCallback(
|
|
133
|
-
(index: number, x: number, y: number) => {
|
|
134
|
-
if (hasModalOverlay) return
|
|
135
|
-
setCursor(index)
|
|
136
|
-
openContextMenu(x, y, menuEntries, invoke)
|
|
137
|
-
},
|
|
138
|
-
[hasModalOverlay, setCursor, openContextMenu, menuEntries, invoke],
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
const extraStatusItems = useMemo(() => {
|
|
142
|
-
const selectionCount = nav.selection != null ? nav.selection.end - nav.selection.start + 1 : 0
|
|
143
|
-
const toggledCount = nav.toggledIndices.size
|
|
144
|
-
const selectionItems =
|
|
145
|
-
toggledCount > 0
|
|
146
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
147
|
-
: selectionCount > 0
|
|
148
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
149
|
-
: []
|
|
150
|
-
return [
|
|
35
|
+
const statusItems = useMemo(
|
|
36
|
+
() => [
|
|
151
37
|
{ label: "Format:", value: content.format },
|
|
152
|
-
{ label: "Rows:", value: String(
|
|
153
|
-
{ label: "Cols:", value: String(
|
|
154
|
-
|
|
155
|
-
]
|
|
156
|
-
|
|
157
|
-
content.format,
|
|
158
|
-
content.rows.length,
|
|
159
|
-
content.columns.length,
|
|
160
|
-
nav.selection,
|
|
161
|
-
nav.toggledIndices,
|
|
162
|
-
])
|
|
38
|
+
{ label: "Rows:", value: String(rows.length) },
|
|
39
|
+
{ label: "Cols:", value: String(columns.length) },
|
|
40
|
+
],
|
|
41
|
+
[content.format, rows.length, columns.length],
|
|
42
|
+
)
|
|
163
43
|
|
|
164
44
|
return (
|
|
165
|
-
<
|
|
45
|
+
<ViewScreen
|
|
166
46
|
content={content}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
47
|
+
controller={document}
|
|
48
|
+
actions={actions}
|
|
49
|
+
statusItems={statusItems}
|
|
50
|
+
{...screen}
|
|
171
51
|
>
|
|
172
|
-
<Table
|
|
173
|
-
|
|
174
|
-
rows={content.rows}
|
|
175
|
-
showLineNumbers={showLineNumbers}
|
|
176
|
-
marks={markState}
|
|
177
|
-
docRef={docRef}
|
|
178
|
-
onRowClick={handleRowClick}
|
|
179
|
-
onRowContextMenu={handleRowContextMenu}
|
|
180
|
-
/>
|
|
181
|
-
</SubviewLayout>
|
|
52
|
+
<Table columns={columns} rows={rows} showLineNumbers={showLineNumbers} document={document} />
|
|
53
|
+
</ViewScreen>
|
|
182
54
|
)
|
|
183
55
|
}
|
|
184
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
|