1
1
import React from 'react' ;
2
2
import { Box } from '@mui/material' ;
3
- import { addAboveIcon , addBelowIcon } from '@jupyterlab/ui-components' ;
4
-
5
- import { CopyButton } from './copy-button' ;
3
+ import {
4
+ addAboveIcon ,
5
+ addBelowIcon ,
6
+ copyIcon
7
+ } from '@jupyterlab/ui-components' ;
6
8
import { replaceCellIcon } from '../../icons' ;
7
9
8
10
import {
@@ -11,20 +13,29 @@ import {
11
13
} from '../../contexts/active-cell-context' ;
12
14
import { TooltippedIconButton } from '../mui-extras/tooltipped-icon-button' ;
13
15
import { useReplace } from '../../hooks/use-replace' ;
16
+ import { useCopy } from '../../hooks/use-copy' ;
17
+ import { AiService } from '../../handler' ;
18
+ import { useTelemetry } from '../../contexts/telemetry-context' ;
19
+ import { TelemetryEvent } from '../../tokens' ;
14
20
15
21
export type CodeToolbarProps = {
16
22
/**
17
23
* The content of the Markdown code block this component is attached to.
18
24
*/
19
- content : string ;
25
+ code : string ;
26
+ /**
27
+ * Parent message which contains the code referenced by `content`.
28
+ */
29
+ parentMessage ?: AiService . ChatMessage ;
20
30
} ;
21
31
22
32
export function CodeToolbar ( props : CodeToolbarProps ) : JSX . Element {
23
33
const activeCell = useActiveCellContext ( ) ;
24
- const sharedToolbarButtonProps = {
25
- content : props . content ,
34
+ const sharedToolbarButtonProps : ToolbarButtonProps = {
35
+ code : props . code ,
26
36
activeCellManager : activeCell . manager ,
27
- activeCellExists : activeCell . exists
37
+ activeCellExists : activeCell . exists ,
38
+ parentMessage : props . parentMessage
28
39
} ;
29
40
30
41
return (
@@ -41,27 +52,68 @@ export function CodeToolbar(props: CodeToolbarProps): JSX.Element {
41
52
>
42
53
< InsertAboveButton { ...sharedToolbarButtonProps } />
43
54
< InsertBelowButton { ...sharedToolbarButtonProps } />
44
- < ReplaceButton value = { props . content } />
45
- < CopyButton value = { props . content } />
55
+ < ReplaceButton { ... sharedToolbarButtonProps } />
56
+ < CopyButton { ... sharedToolbarButtonProps } />
46
57
</ Box >
47
58
) ;
48
59
}
49
60
50
61
type ToolbarButtonProps = {
51
- content : string ;
62
+ code : string ;
52
63
activeCellExists : boolean ;
53
64
activeCellManager : ActiveCellManager ;
65
+ parentMessage ?: AiService . ChatMessage ;
66
+ // TODO: parentMessage should always be defined, but this can be undefined
67
+ // when the code toolbar appears in Markdown help messages in the Settings
68
+ // UI. The Settings UI should use a different component to render Markdown,
69
+ // and should never render code toolbars within it.
54
70
} ;
55
71
72
+ function buildTelemetryEvent (
73
+ type : string ,
74
+ props : ToolbarButtonProps
75
+ ) : TelemetryEvent {
76
+ const charCount = props . code . length ;
77
+ // number of lines = number of newlines + 1
78
+ const lineCount = ( props . code . match ( / \n / g) ?? [ ] ) . length + 1 ;
79
+
80
+ return {
81
+ type,
82
+ message : {
83
+ id : props . parentMessage ?. id ?? '' ,
84
+ type : props . parentMessage ?. type ?? 'human' ,
85
+ time : props . parentMessage ?. time ?? 0 ,
86
+ metadata :
87
+ props . parentMessage && 'metadata' in props . parentMessage
88
+ ? props . parentMessage . metadata
89
+ : { }
90
+ } ,
91
+ code : {
92
+ charCount,
93
+ lineCount
94
+ }
95
+ } ;
96
+ }
97
+
56
98
function InsertAboveButton ( props : ToolbarButtonProps ) {
99
+ const telemetryHandler = useTelemetry ( ) ;
57
100
const tooltip = props . activeCellExists
58
101
? 'Insert above active cell'
59
102
: 'Insert above active cell (no active cell)' ;
60
103
61
104
return (
62
105
< TooltippedIconButton
63
106
tooltip = { tooltip }
64
- onClick = { ( ) => props . activeCellManager . insertAbove ( props . content ) }
107
+ onClick = { ( ) => {
108
+ props . activeCellManager . insertAbove ( props . code ) ;
109
+
110
+ try {
111
+ telemetryHandler . onEvent ( buildTelemetryEvent ( 'insert-above' , props ) ) ;
112
+ } catch ( e ) {
113
+ console . error ( e ) ;
114
+ return ;
115
+ }
116
+ } }
65
117
disabled = { ! props . activeCellExists }
66
118
>
67
119
< addAboveIcon . react height = "16px" width = "16px" />
@@ -70,6 +122,7 @@ function InsertAboveButton(props: ToolbarButtonProps) {
70
122
}
71
123
72
124
function InsertBelowButton ( props : ToolbarButtonProps ) {
125
+ const telemetryHandler = useTelemetry ( ) ;
73
126
const tooltip = props . activeCellExists
74
127
? 'Insert below active cell'
75
128
: 'Insert below active cell (no active cell)' ;
@@ -78,23 +131,67 @@ function InsertBelowButton(props: ToolbarButtonProps) {
78
131
< TooltippedIconButton
79
132
tooltip = { tooltip }
80
133
disabled = { ! props . activeCellExists }
81
- onClick = { ( ) => props . activeCellManager . insertBelow ( props . content ) }
134
+ onClick = { ( ) => {
135
+ props . activeCellManager . insertBelow ( props . code ) ;
136
+
137
+ try {
138
+ telemetryHandler . onEvent ( buildTelemetryEvent ( 'insert-below' , props ) ) ;
139
+ } catch ( e ) {
140
+ console . error ( e ) ;
141
+ return ;
142
+ }
143
+ } }
82
144
>
83
145
< addBelowIcon . react height = "16px" width = "16px" />
84
146
</ TooltippedIconButton >
85
147
) ;
86
148
}
87
149
88
- function ReplaceButton ( props : { value : string } ) {
150
+ function ReplaceButton ( props : ToolbarButtonProps ) {
151
+ const telemetryHandler = useTelemetry ( ) ;
89
152
const { replace, replaceDisabled, replaceLabel } = useReplace ( ) ;
90
153
91
154
return (
92
155
< TooltippedIconButton
93
156
tooltip = { replaceLabel }
94
157
disabled = { replaceDisabled }
95
- onClick = { ( ) => replace ( props . value ) }
158
+ onClick = { ( ) => {
159
+ replace ( props . code ) ;
160
+
161
+ try {
162
+ telemetryHandler . onEvent ( buildTelemetryEvent ( 'replace' , props ) ) ;
163
+ } catch ( e ) {
164
+ console . error ( e ) ;
165
+ return ;
166
+ }
167
+ } }
96
168
>
97
169
< replaceCellIcon . react height = "16px" width = "16px" />
98
170
</ TooltippedIconButton >
99
171
) ;
100
172
}
173
+
174
+ export function CopyButton ( props : ToolbarButtonProps ) : JSX . Element {
175
+ const telemetryHandler = useTelemetry ( ) ;
176
+ const { copy, copyLabel } = useCopy ( ) ;
177
+
178
+ return (
179
+ < TooltippedIconButton
180
+ tooltip = { copyLabel }
181
+ placement = "top"
182
+ onClick = { ( ) => {
183
+ copy ( props . code ) ;
184
+
185
+ try {
186
+ telemetryHandler . onEvent ( buildTelemetryEvent ( 'copy' , props ) ) ;
187
+ } catch ( e ) {
188
+ console . error ( e ) ;
189
+ return ;
190
+ }
191
+ } }
192
+ aria-label = "Copy to clipboard"
193
+ >
194
+ < copyIcon . react height = "16px" width = "16px" />
195
+ </ TooltippedIconButton >
196
+ ) ;
197
+ }
0 commit comments