@@ -3,7 +3,7 @@ globalThis.qwebrIsObjectEmpty = function (arr) {
3
3
return Object . keys ( arr ) . length === 0 ;
4
4
}
5
5
6
- // Global version of the Escape HTML function that converts HTML
6
+ // Global version of the Escape HTML function that converts HTML
7
7
// characters to their HTML entities.
8
8
globalThis . qwebrEscapeHTMLCharacters = function ( unsafe ) {
9
9
return unsafe
@@ -12,7 +12,7 @@ globalThis.qwebrEscapeHTMLCharacters = function(unsafe) {
12
12
. replace ( / > / g, ">" )
13
13
. replace ( / " / g, """ )
14
14
. replace ( / ' / g, "'" ) ;
15
- } ;
15
+ } ;
16
16
17
17
// Passthrough results
18
18
globalThis . qwebrIdentity = function ( x ) {
@@ -29,7 +29,7 @@ globalThis.qwebrLogCodeToHistory = function(codeToRun, options) {
29
29
qwebrRCommandHistory . push (
30
30
`# Ran code in ${ options . label } at ${ new Date ( ) . toLocaleString ( ) } ----\n${ codeToRun } `
31
31
) ;
32
- }
32
+ } ;
33
33
34
34
// Function to attach a download button onto the canvas
35
35
// allowing the user to download the image.
@@ -49,14 +49,14 @@ function qwebrImageCanvasDownloadButton(canvas, canvasContainer) {
49
49
link . download = 'qwebr-canvas-image.png' ;
50
50
link . click ( ) ;
51
51
} ) ;
52
- }
53
-
52
+ }
53
+
54
54
55
55
// Function to parse the pager results
56
- globalThis . qwebrParseTypePager = async function ( msg ) {
56
+ globalThis . qwebrParseTypePager = async function ( msg ) {
57
57
58
58
// Split out the event data
59
- const { path, title, deleteFile } = msg . data ;
59
+ const { path, title, deleteFile } = msg . data ;
60
60
61
61
// Process the pager data by reading the information from disk
62
62
const paged_data = await mainWebR . FS . readFile ( path ) . then ( ( data ) => {
@@ -65,30 +65,49 @@ globalThis.qwebrParseTypePager = async function (msg) {
65
65
66
66
// Remove excessive backspace characters until none remain
67
67
while ( content . match ( / .[ \b ] / ) ) {
68
- content = content . replace ( / .[ \b ] / g, '' ) ;
68
+ content = content . replace ( / .[ \b ] / g, '' ) ;
69
69
}
70
70
71
71
// Returned cleaned data
72
72
return content ;
73
73
} ) ;
74
74
75
75
// Unlink file if needed
76
- if ( deleteFile ) {
77
- await mainWebR . FS . unlink ( path ) ;
78
- }
76
+ if ( deleteFile ) {
77
+ await mainWebR . FS . unlink ( path ) ;
78
+ }
79
79
80
80
// Return extracted data with spaces
81
81
return paged_data ;
82
- }
82
+ } ;
83
+
84
+
85
+ // Function to parse the browse results
86
+ globalThis . qwebrParseTypeBrowse = async function ( msg ) {
87
+
88
+ // msg.type === "browse"
89
+ const path = msg . data . url ;
90
+
91
+ // Process the browse data by reading the information from disk
92
+ const browse_data = await mainWebR . FS . readFile ( path ) . then ( ( data ) => {
93
+ // Obtain the file content
94
+ let content = new TextDecoder ( ) . decode ( data ) ;
95
+
96
+ return content ;
97
+ } ) ;
98
+
99
+ // Return extracted data as-is
100
+ return browse_data ;
101
+ } ;
83
102
84
103
// Function to run the code using webR and parse the output
85
104
globalThis . qwebrComputeEngine = async function (
86
- codeToRun ,
87
- elements ,
105
+ codeToRun ,
106
+ elements ,
88
107
options ) {
89
108
90
109
// Call into the R compute engine that persists within the document scope.
91
- // To be prepared for all scenarios, the following happens:
110
+ // To be prepared for all scenarios, the following happens:
92
111
// 1. We setup a canvas device to write to by making a namespace call into the {webr} package
93
112
// 2. We use values inside of the options array to set the figure size.
94
113
// 3. We capture the output stream information (STDOUT and STERR)
@@ -108,11 +127,11 @@ globalThis.qwebrComputeEngine = async function(
108
127
processOutput = qwebrIdentity ;
109
128
}
110
129
111
- // ----
130
+ // ----
112
131
// Convert from Inches to Pixels by using DPI (dots per inch)
113
132
// for bitmap devices (dpi * inches = pixels)
114
- let fig_width = options [ "fig-width" ] * options [ "dpi" ]
115
- let fig_height = options [ "fig-height" ] * options [ "dpi" ]
133
+ let fig_width = options [ "fig-width" ] * options [ "dpi" ] ;
134
+ let fig_height = options [ "fig-height" ] * options [ "dpi" ] ;
116
135
117
136
// Initialize webR
118
137
await mainWebR . init ( ) ;
@@ -124,7 +143,7 @@ globalThis.qwebrComputeEngine = async function(
124
143
captureConditions : false ,
125
144
// env: webR.objs.emptyEnv, // maintain a global environment for webR v0.2.0
126
145
} ;
127
-
146
+
128
147
// Determine if the browser supports OffScreen
129
148
if ( qwebrOffScreenCanvasSupport ( ) ) {
130
149
// Mirror default options of webr::canvas()
@@ -156,18 +175,18 @@ globalThis.qwebrComputeEngine = async function(
156
175
157
176
// Start attempting to parse the result data
158
177
processResultOutput:try {
159
-
178
+
160
179
// Avoid running through output processing
161
- if ( options . results === "hide" || options . output === "false" ) {
162
- break processResultOutput;
180
+ if ( options . results === "hide" || options . output === "false" ) {
181
+ break processResultOutput;
163
182
}
164
183
165
184
// Merge output streams of STDOUT and STDErr (messages and errors are combined.)
166
- // Require both `warning` and `message` to be true to display `STDErr`.
185
+ // Require both `warning` and `message` to be true to display `STDErr`.
167
186
const out = result . output
168
187
. filter (
169
- evt => evt . type === "stdout" ||
170
- ( evt . type === "stderr" && ( options . warning === "true" && options . message === "true" ) )
188
+ evt => evt . type === "stdout" ||
189
+ ( evt . type === "stderr" && ( options . warning === "true" && options . message === "true" ) )
171
190
)
172
191
. map ( ( evt , index ) => {
173
192
const className = `qwebr-output-code-${ evt . type } ` ;
@@ -179,15 +198,31 @@ globalThis.qwebrComputeEngine = async function(
179
198
180
199
// Clean the state
181
200
// We're now able to process pager events.
182
- // As a result, we cannot maintain a true 1-to-1 output order
201
+ // As a result, we cannot maintain a true 1-to-1 output order
183
202
// without individually feeding each line
184
203
const msgs = await mainWebR . flush ( ) ;
185
204
186
205
// Use `map` to process the filtered "pager" events asynchronously
187
- const pager = await Promise . all (
188
- msgs . filter ( msg => msg . type === 'pager' ) . map (
206
+ const pager = [ ] ;
207
+ const browse = [ ] ;
208
+
209
+ await Promise . all (
210
+ msgs . map (
189
211
async ( msg ) => {
190
- return await qwebrParseTypePager ( msg ) ;
212
+
213
+ const msgType = msg . type || "unknown" ;
214
+
215
+ switch ( msgType ) {
216
+ case 'pager' :
217
+ const pager_data = await qwebrParseTypePager ( msg ) ;
218
+ pager . push ( pager_data ) ;
219
+ break ;
220
+ case 'browse' :
221
+ const browse_data = await qwebrParseTypeBrowse ( msg ) ;
222
+ browse . push ( browse_data ) ;
223
+ break ;
224
+ }
225
+ return ;
191
226
}
192
227
)
193
228
) ;
@@ -250,40 +285,57 @@ globalThis.qwebrComputeEngine = async function(
250
285
// Draw image onto Canvas
251
286
const ctx = canvas . getContext ( "2d" ) ;
252
287
ctx . drawImage ( img , 0 , 0 , img . width , img . height ) ;
253
-
288
+
254
289
// Append canvas to figure output area
255
290
figureElement . appendChild ( canvas ) ;
256
291
257
292
} ) ;
258
-
293
+
259
294
if ( options [ 'fig-cap' ] ) {
260
295
// Create figcaption element
261
296
const figcaptionElement = document . createElement ( 'figcaption' ) ;
262
297
figcaptionElement . innerText = options [ 'fig-cap' ] ;
263
298
// Append figcaption to figure
264
- figureElement . appendChild ( figcaptionElement ) ;
299
+ figureElement . appendChild ( figcaptionElement ) ;
265
300
}
266
-
301
+
267
302
elements . outputGraphDiv . appendChild ( figureElement ) ;
268
303
269
304
}
270
305
271
306
// Display the pager data
272
- if ( pager ) {
273
- // Use the `pre` element to preserve whitespace.
274
- pager . forEach ( ( paged_data , index ) => {
275
- let pre_pager = document . createElement ( "pre" ) ;
276
- pre_pager . innerText = paged_data ;
277
- pre_pager . classList . add ( "qwebr-output-code-pager" ) ;
278
- pre_pager . setAttribute ( "id" , `qwebr-output-code-pager-editor-${ elements . id } -result-${ index + 1 } ` ) ;
279
- elements . outputCodeDiv . appendChild ( pre_pager ) ;
280
- } ) ;
307
+ if ( pager . length > 0 ) {
308
+ // Use the `pre` element to preserve whitespace.
309
+ pager . forEach ( ( paged_data , index ) => {
310
+ const pre_pager = document . createElement ( "pre" ) ;
311
+ pre_pager . innerText = paged_data ;
312
+ pre_pager . classList . add ( "qwebr-output-code-pager" ) ;
313
+ pre_pager . setAttribute ( "id" , `qwebr-output-code-pager-editor-${ elements . id } -result-${ index + 1 } ` ) ;
314
+ elements . outputCodeDiv . appendChild ( pre_pager ) ;
315
+ } ) ;
316
+ }
317
+
318
+ // Display the browse data
319
+ if ( browse . length > 0 ) {
320
+ // Use the `pre` element to preserve whitespace.
321
+ browse . forEach ( ( browse_data , index ) => {
322
+ const iframe_browse = document . createElement ( 'iframe' ) ;
323
+ iframe_browse . classList . add ( "qwebr-output-code-browse" ) ;
324
+ iframe_browse . setAttribute ( "id" , `qwebr-output-code-browse-editor-${ elements . id } -result-${ index + 1 } ` ) ;
325
+ iframe_browse . style . width = "100%" ;
326
+ iframe_browse . style . minHeight = "500px" ;
327
+ elements . outputCodeDiv . appendChild ( iframe_browse ) ;
328
+
329
+ iframe_browse . contentWindow . document . open ( ) ;
330
+ iframe_browse . contentWindow . document . write ( browse_data ) ;
331
+ iframe_browse . contentWindow . document . close ( ) ;
332
+ } ) ;
281
333
}
282
334
} finally {
283
335
// Clean up the remaining code
284
336
mainWebRCodeShelter . purge ( ) ;
285
337
}
286
- }
338
+ } ;
287
339
288
340
// Function to execute the code (accepts code as an argument)
289
341
globalThis . qwebrExecuteCode = async function (
@@ -293,12 +345,12 @@ globalThis.qwebrExecuteCode = async function (
293
345
294
346
// If options are not passed, we fall back on the bare minimum to handle the computation
295
347
if ( qwebrIsObjectEmpty ( options ) ) {
296
- options = {
297
- "context" : "interactive" ,
298
- "fig-width" : 7 , "fig-height" : 5 ,
299
- "out-width" : "700px" , "out-height" : "" ,
348
+ options = {
349
+ "context" : "interactive" ,
350
+ "fig-width" : 7 , "fig-height" : 5 ,
351
+ "out-width" : "700px" , "out-height" : "" ,
300
352
"dpi" : 72 ,
301
- "results" : "markup" ,
353
+ "results" : "markup" ,
302
354
"warning" : "true" , "message" : "true" ,
303
355
} ;
304
356
}
0 commit comments