@jseeio/jsee 0.4.2 → 0.8.1
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/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +583 -55
- package/dist/2b3e1faf89f94a483539.png +0 -0
- package/dist/416d91365b44e4b4f477.png +0 -0
- package/dist/8f2c4d11474275fbc161.png +0 -0
- package/dist/jsee.core.js +1 -0
- package/dist/jsee.full.js +1 -0
- package/dist/jsee.runtime.js +2 -1
- package/package.json +84 -18
- package/src/app.js +127 -32
- package/src/browser-bundle-node.js +9 -0
- package/src/cli.js +479 -67
- package/src/extended-imports.js +11 -0
- package/src/main.js +232 -44
- package/src/overlay.js +26 -1
- package/src/utils.js +386 -16
- package/templates/common-inputs.js +88 -0
- package/templates/common-outputs.js +340 -4
- package/templates/minimal-app.vue +367 -0
- package/templates/minimal-input.vue +573 -0
- package/templates/minimal-output.vue +426 -0
- package/templates/virtual-table.vue +194 -0
- package/.claude/settings.local.json +0 -15
- package/.eslintrc.js +0 -38
- package/AGENTS.md +0 -65
- package/CLAUDE.md +0 -5
- package/CNAME +0 -1
- package/_config.yml +0 -26
- package/dist/jsee.js +0 -1
- package/dump.sh +0 -23
- package/jest-puppeteer.config.js +0 -14
- package/jest.config.js +0 -8
- package/jest.unit.config.js +0 -8
- package/jsee.dump.txt +0 -5459
- package/load/index.html +0 -52
- package/templates/bulma-app.vue +0 -242
- package/templates/bulma-input.vue +0 -125
- package/templates/bulma-output.vue +0 -101
- package/test/arrow-main.html +0 -18
- package/test/arrow-worker.html +0 -18
- package/test/class.html +0 -22
- package/test/code.html +0 -25
- package/test/codew.html +0 -25
- package/test/example-class.js +0 -8
- package/test/example-sum.js +0 -3
- package/test/fixtures/lodash-like.js +0 -15
- package/test/fixtures/upload-sample.csv +0 -3
- package/test/importw.html +0 -28
- package/test/minimal.html +0 -14
- package/test/minimal1.html +0 -13
- package/test/minimal2.html +0 -15
- package/test/minimal3.html +0 -10
- package/test/minimal4.html +0 -22
- package/test/pipeline.html +0 -52
- package/test/python.html +0 -41
- package/test/runtime-arrow.html +0 -18
- package/test/string.html +0 -26
- package/test/stringw.html +0 -29
- package/test/sum.schema.json +0 -17
- package/test/sumw.schema.json +0 -15
- package/test/test-basic.test.js +0 -630
- package/test/test-python.test.js +0 -23
- package/test/unit/cli-fetch.test.js +0 -229
- package/test/unit/utils.test.js +0 -908
- package/webpack.config.js +0 -101
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { saveAs } from 'file-saver'
|
|
2
2
|
import domtoimage from 'dom-to-image'
|
|
3
|
+
import MarkdownIt from 'markdown-it'
|
|
3
4
|
|
|
4
|
-
const { sanitizeName } = require('../src/utils.js')
|
|
5
|
+
const { sanitizeName, columnsToRows } = require('../src/utils.js')
|
|
6
|
+
|
|
7
|
+
const mdConverter = new MarkdownIt({
|
|
8
|
+
html: true,
|
|
9
|
+
linkify: true,
|
|
10
|
+
typographer: false
|
|
11
|
+
})
|
|
5
12
|
|
|
6
13
|
const Blob = window['Blob']
|
|
14
|
+
const UrlApi = window['URL'] || window['webkitURL']
|
|
7
15
|
|
|
8
16
|
function stringify (v) {
|
|
9
17
|
return typeof v === 'string'
|
|
@@ -18,19 +26,30 @@ const component = {
|
|
|
18
26
|
return {
|
|
19
27
|
outputName: 'output',
|
|
20
28
|
isFullScreen: false,
|
|
29
|
+
activeOutputTab: 0,
|
|
30
|
+
lightboxSrc: null,
|
|
31
|
+
_threeScene: null,
|
|
32
|
+
_threeRenderer: null,
|
|
33
|
+
_threeAnimId: null,
|
|
34
|
+
_leafletMap: null,
|
|
35
|
+
_pdfObjectUrl: null,
|
|
21
36
|
}
|
|
22
37
|
},
|
|
23
38
|
mounted() {
|
|
24
|
-
this.outputName = this.output.alias
|
|
39
|
+
this.outputName = this.output.alias
|
|
25
40
|
? this.output.alias
|
|
26
41
|
: this.output.name
|
|
27
|
-
? sanitizeName(this.output.name)
|
|
42
|
+
? sanitizeName(this.output.name)
|
|
28
43
|
: 'output_' + Math.floor(Math.random() * 1000000)
|
|
29
44
|
this.executeRenderFunction()
|
|
30
45
|
document.addEventListener('fullscreenchange', this.onFullScreenChange)
|
|
31
46
|
},
|
|
32
47
|
beforeUnmount() {
|
|
33
48
|
document.removeEventListener('fullscreenchange', this.onFullScreenChange)
|
|
49
|
+
if (this._threeAnimId) cancelAnimationFrame(this._threeAnimId)
|
|
50
|
+
if (this._threeRenderer) this._threeRenderer.dispose()
|
|
51
|
+
if (this._leafletMap) this._leafletMap.remove()
|
|
52
|
+
this.revokePdfObjectUrl()
|
|
34
53
|
},
|
|
35
54
|
// updated() {
|
|
36
55
|
// this.executeRenderFunction()
|
|
@@ -40,6 +59,20 @@ const component = {
|
|
|
40
59
|
if (newValue !== oldValue) {
|
|
41
60
|
this.$nextTick(() => {
|
|
42
61
|
this.executeRenderFunction()
|
|
62
|
+
if (this.output.type === 'chart') this.renderChart()
|
|
63
|
+
if (this.output.type === '3d') this.render3D()
|
|
64
|
+
if (this.output.type === 'map') this.renderMap()
|
|
65
|
+
if (this.output.type === 'pdf') this.renderPDF()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
'output._messages': {
|
|
70
|
+
deep: true,
|
|
71
|
+
handler () {
|
|
72
|
+
this.$nextTick(() => {
|
|
73
|
+
if (this.$refs.chatMessages) {
|
|
74
|
+
this.$refs.chatMessages.scrollTop = this.$refs.chatMessages.scrollHeight
|
|
75
|
+
}
|
|
43
76
|
})
|
|
44
77
|
}
|
|
45
78
|
}
|
|
@@ -47,6 +80,31 @@ const component = {
|
|
|
47
80
|
computed: {
|
|
48
81
|
isRenderFunction() {
|
|
49
82
|
return typeof this.output.value === 'function'
|
|
83
|
+
},
|
|
84
|
+
hasPlot() {
|
|
85
|
+
return typeof window !== 'undefined' && !!window.Plot
|
|
86
|
+
},
|
|
87
|
+
hasThree() {
|
|
88
|
+
return typeof window !== 'undefined' && !!window.THREE
|
|
89
|
+
},
|
|
90
|
+
hasLeaflet() {
|
|
91
|
+
return typeof window !== 'undefined' && !!window.L
|
|
92
|
+
},
|
|
93
|
+
viewerMedia () {
|
|
94
|
+
const url = this.output && this.output.value
|
|
95
|
+
if (typeof url !== 'string' || !url) return null
|
|
96
|
+
let filePath = url
|
|
97
|
+
try {
|
|
98
|
+
const parsed = new URL(url, 'http://x')
|
|
99
|
+
if (parsed.searchParams.has('path')) filePath = parsed.searchParams.get('path')
|
|
100
|
+
} catch (e) {}
|
|
101
|
+
const dot = filePath.lastIndexOf('.')
|
|
102
|
+
if (dot < 0) return 'iframe'
|
|
103
|
+
const ext = filePath.slice(dot).toLowerCase()
|
|
104
|
+
if (/^\.(png|jpe?g|gif|svg|webp|bmp|ico)$/.test(ext)) return 'image'
|
|
105
|
+
if (/^\.(mp3|wav|ogg|flac|aac)$/.test(ext)) return 'audio'
|
|
106
|
+
if (/^\.(mp4|webm|mov|avi)$/.test(ext)) return 'video'
|
|
107
|
+
return 'iframe'
|
|
50
108
|
}
|
|
51
109
|
},
|
|
52
110
|
methods: {
|
|
@@ -79,6 +137,21 @@ const component = {
|
|
|
79
137
|
onFullScreenChange() {
|
|
80
138
|
this.isFullScreen = !!document.fullscreenElement
|
|
81
139
|
},
|
|
140
|
+
tableToDelimited (delim) {
|
|
141
|
+
const d = this.output.value
|
|
142
|
+
if (!d || !d.columns) return ''
|
|
143
|
+
const esc = (v) => {
|
|
144
|
+
const s = String(v == null ? '' : v)
|
|
145
|
+
return s.indexOf(delim) >= 0 || s.indexOf('"') >= 0 || s.indexOf('\n') >= 0
|
|
146
|
+
? '"' + s.replace(/"/g, '""') + '"'
|
|
147
|
+
: s
|
|
148
|
+
}
|
|
149
|
+
const lines = [d.columns.map(esc).join(delim)]
|
|
150
|
+
for (const row of d.rows) {
|
|
151
|
+
lines.push(row.map(esc).join(delim))
|
|
152
|
+
}
|
|
153
|
+
return lines.join('\n')
|
|
154
|
+
},
|
|
82
155
|
save () {
|
|
83
156
|
// Prepare filename
|
|
84
157
|
let filename
|
|
@@ -94,6 +167,18 @@ const component = {
|
|
|
94
167
|
case 'svg':
|
|
95
168
|
extension = 'svg'
|
|
96
169
|
break
|
|
170
|
+
case 'table':
|
|
171
|
+
extension = 'csv'
|
|
172
|
+
break
|
|
173
|
+
case 'markdown':
|
|
174
|
+
extension = 'md'
|
|
175
|
+
break
|
|
176
|
+
case 'image':
|
|
177
|
+
extension = 'png'
|
|
178
|
+
break
|
|
179
|
+
case 'chart':
|
|
180
|
+
extension = 'svg'
|
|
181
|
+
break
|
|
97
182
|
default:
|
|
98
183
|
extension = 'txt'
|
|
99
184
|
}
|
|
@@ -101,13 +186,34 @@ const component = {
|
|
|
101
186
|
}
|
|
102
187
|
|
|
103
188
|
// Prepare blob
|
|
189
|
+
if (this.output.type === 'chart' && this.$refs.chartContainer) {
|
|
190
|
+
const svg = this.$refs.chartContainer.querySelector('svg')
|
|
191
|
+
if (svg) {
|
|
192
|
+
let blob = new Blob([svg.outerHTML], { type: 'image/svg+xml;charset=utf-8' })
|
|
193
|
+
saveAs(blob, filename)
|
|
194
|
+
}
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
if (this.output.type === 'image') {
|
|
198
|
+
fetch(this.output.value)
|
|
199
|
+
.then(r => r.blob())
|
|
200
|
+
.then(blob => saveAs(blob, filename))
|
|
201
|
+
.catch(() => {
|
|
202
|
+
let blob = new Blob([this.output.value], {type: 'text/plain;charset=utf-8'})
|
|
203
|
+
saveAs(blob, filename)
|
|
204
|
+
})
|
|
205
|
+
return
|
|
206
|
+
}
|
|
104
207
|
if (this.output.type === 'function') {
|
|
105
208
|
domtoimage.toBlob(this.$refs.customContainer)
|
|
106
209
|
.then(blob => {
|
|
107
210
|
saveAs(blob, filename)
|
|
108
211
|
})
|
|
212
|
+
return
|
|
109
213
|
}
|
|
110
|
-
let value =
|
|
214
|
+
let value = this.output.type === 'table'
|
|
215
|
+
? this.tableToDelimited(',')
|
|
216
|
+
: stringify(this.output.value)
|
|
111
217
|
let blob = new Blob([value], {type: 'text/plain;charset=utf-8'})
|
|
112
218
|
saveAs(blob, filename)
|
|
113
219
|
},
|
|
@@ -130,12 +236,242 @@ const component = {
|
|
|
130
236
|
console.error('Failed to generate image blob: ', err);
|
|
131
237
|
this.$emit('notification', 'Failed to generate image');
|
|
132
238
|
});
|
|
239
|
+
} else if (this.output.type === 'image') {
|
|
240
|
+
fetch(this.output.value)
|
|
241
|
+
.then(r => r.blob())
|
|
242
|
+
.then(blob => {
|
|
243
|
+
const item = new ClipboardItem({ [blob.type]: blob })
|
|
244
|
+
navigator.clipboard.write([item])
|
|
245
|
+
.then(() => this.$emit('notification', 'Image copied to clipboard'))
|
|
246
|
+
.catch(() => this.$emit('notification', 'Failed to copy image'))
|
|
247
|
+
})
|
|
248
|
+
.catch(() => {
|
|
249
|
+
navigator.clipboard.writeText(this.output.value)
|
|
250
|
+
this.$emit('notification', 'Copied image URL')
|
|
251
|
+
})
|
|
252
|
+
} else if (this.output.type === 'table') {
|
|
253
|
+
let value = this.tableToDelimited('\t')
|
|
254
|
+
navigator.clipboard.writeText(value)
|
|
255
|
+
this.$emit('notification', 'Copied as TSV')
|
|
133
256
|
} else {
|
|
134
257
|
let value = stringify(this.output.value)
|
|
135
258
|
navigator.clipboard.writeText(value)
|
|
136
259
|
this.$emit('notification', 'Copied')
|
|
137
260
|
}
|
|
138
261
|
},
|
|
262
|
+
downloadFile () {
|
|
263
|
+
let filename = this.output.filename || this.output.name || 'output'
|
|
264
|
+
let value = this.output.value
|
|
265
|
+
if (typeof value === 'string' && value.startsWith('data:')) {
|
|
266
|
+
fetch(value)
|
|
267
|
+
.then(r => r.blob())
|
|
268
|
+
.then(blob => saveAs(blob, filename))
|
|
269
|
+
.catch(() => {
|
|
270
|
+
let blob = new Blob([value], { type: 'application/octet-stream' })
|
|
271
|
+
saveAs(blob, filename)
|
|
272
|
+
})
|
|
273
|
+
} else {
|
|
274
|
+
let content = typeof value === 'string' ? value : JSON.stringify(value)
|
|
275
|
+
let blob = new Blob([content], { type: 'application/octet-stream' })
|
|
276
|
+
saveAs(blob, filename)
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
renderMarkdown (text) {
|
|
280
|
+
if (typeof text !== 'string') return ''
|
|
281
|
+
return mdConverter.render(text)
|
|
282
|
+
},
|
|
283
|
+
renderChart() {
|
|
284
|
+
if (!this.hasPlot || !this.$refs.chartContainer) return
|
|
285
|
+
const container = this.$refs.chartContainer
|
|
286
|
+
const data = this.output.value
|
|
287
|
+
if (!data) return
|
|
288
|
+
container.innerHTML = ''
|
|
289
|
+
try {
|
|
290
|
+
let plotConfig
|
|
291
|
+
if (data && data.marks) {
|
|
292
|
+
// Full Plot config passed directly
|
|
293
|
+
plotConfig = data
|
|
294
|
+
} else {
|
|
295
|
+
// Build config from schema props + data
|
|
296
|
+
let rows = Array.isArray(data) ? data : columnsToRows(data)
|
|
297
|
+
if (!Array.isArray(rows)) return
|
|
298
|
+
const mark = this.output.mark || 'dot'
|
|
299
|
+
const x = this.output.x || (rows[0] && Object.keys(rows[0])[0])
|
|
300
|
+
const y = this.output.y || (rows[0] && Object.keys(rows[0])[1])
|
|
301
|
+
const color = this.output.color
|
|
302
|
+
const markOpts = { x, y }
|
|
303
|
+
if (color) markOpts.fill = color
|
|
304
|
+
const Plot = window.Plot
|
|
305
|
+
const markFn = Plot[mark] || Plot.dot
|
|
306
|
+
plotConfig = {
|
|
307
|
+
marks: [markFn(rows, markOpts)],
|
|
308
|
+
width: this.output.width || 640,
|
|
309
|
+
height: this.output.height || 400
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const svg = window.Plot.plot(plotConfig)
|
|
313
|
+
container.appendChild(svg)
|
|
314
|
+
} catch (e) {
|
|
315
|
+
container.textContent = 'Chart error: ' + e.message
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
render3D() {
|
|
319
|
+
if (!this.hasThree || !this.$refs.threeDContainer) return
|
|
320
|
+
const container = this.$refs.threeDContainer
|
|
321
|
+
const data = this.output.value
|
|
322
|
+
if (!data) return
|
|
323
|
+
// Dispose previous scene
|
|
324
|
+
if (this._threeAnimId) cancelAnimationFrame(this._threeAnimId)
|
|
325
|
+
if (this._threeRenderer) {
|
|
326
|
+
this._threeRenderer.dispose()
|
|
327
|
+
if (this._threeRenderer.domElement && this._threeRenderer.domElement.parentNode) {
|
|
328
|
+
this._threeRenderer.domElement.parentNode.removeChild(this._threeRenderer.domElement)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const THREE = window.THREE
|
|
332
|
+
const width = container.clientWidth || 640
|
|
333
|
+
const height = this.output.height || 400
|
|
334
|
+
const scene = new THREE.Scene()
|
|
335
|
+
scene.background = new THREE.Color(0xf0f0f0)
|
|
336
|
+
const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000)
|
|
337
|
+
camera.position.set(0, 1, 3)
|
|
338
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
|
339
|
+
renderer.setSize(width, height)
|
|
340
|
+
// Remove only previous canvas, keep missing-message divs
|
|
341
|
+
const oldCanvas = container.querySelector('canvas')
|
|
342
|
+
if (oldCanvas) oldCanvas.remove()
|
|
343
|
+
container.appendChild(renderer.domElement)
|
|
344
|
+
scene.add(new THREE.AmbientLight(0xcccccc, 0.6))
|
|
345
|
+
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8)
|
|
346
|
+
dirLight.position.set(1, 2, 3)
|
|
347
|
+
scene.add(dirLight)
|
|
348
|
+
this._threeScene = scene
|
|
349
|
+
this._threeRenderer = renderer
|
|
350
|
+
const animate = () => {
|
|
351
|
+
this._threeAnimId = requestAnimationFrame(animate)
|
|
352
|
+
renderer.render(scene, camera)
|
|
353
|
+
}
|
|
354
|
+
if (typeof data === 'object' && data.vertices) {
|
|
355
|
+
// Programmatic geometry
|
|
356
|
+
const geom = new THREE.BufferGeometry()
|
|
357
|
+
geom.setAttribute('position', new THREE.Float32BufferAttribute(data.vertices, 3))
|
|
358
|
+
if (data.faces) geom.setIndex(data.faces)
|
|
359
|
+
geom.computeVertexNormals()
|
|
360
|
+
const mesh = new THREE.Mesh(geom, new THREE.MeshStandardMaterial({ color: 0x00d1b2 }))
|
|
361
|
+
scene.add(mesh)
|
|
362
|
+
animate()
|
|
363
|
+
} else if (typeof data === 'string') {
|
|
364
|
+
// URL to GLTF/GLB — needs GLTFLoader loaded via imports
|
|
365
|
+
if (THREE.GLTFLoader) {
|
|
366
|
+
const loader = new THREE.GLTFLoader()
|
|
367
|
+
loader.load(data, (gltf) => {
|
|
368
|
+
scene.add(gltf.scene)
|
|
369
|
+
// Auto-fit camera
|
|
370
|
+
const box = new THREE.Box3().setFromObject(gltf.scene)
|
|
371
|
+
const center = box.getCenter(new THREE.Vector3())
|
|
372
|
+
const size = box.getSize(new THREE.Vector3()).length()
|
|
373
|
+
camera.position.copy(center).add(new THREE.Vector3(0, size * 0.5, size))
|
|
374
|
+
camera.lookAt(center)
|
|
375
|
+
animate()
|
|
376
|
+
})
|
|
377
|
+
} else {
|
|
378
|
+
container.textContent = '3D URL loading requires GLTFLoader in imports'
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
renderMap() {
|
|
383
|
+
if (!this.hasLeaflet || !this.$refs.mapContainer) return
|
|
384
|
+
const container = this.$refs.mapContainer
|
|
385
|
+
const data = this.output.value
|
|
386
|
+
if (!data) return
|
|
387
|
+
const L = window.L
|
|
388
|
+
// Destroy previous map
|
|
389
|
+
if (this._leafletMap) {
|
|
390
|
+
this._leafletMap.remove()
|
|
391
|
+
this._leafletMap = null
|
|
392
|
+
}
|
|
393
|
+
// Remove any existing map container content except missing message
|
|
394
|
+
const existingMap = container.querySelector('.leaflet-container')
|
|
395
|
+
if (existingMap) existingMap.remove()
|
|
396
|
+
const mapDiv = document.createElement('div')
|
|
397
|
+
mapDiv.style.width = '100%'
|
|
398
|
+
mapDiv.style.height = '100%'
|
|
399
|
+
container.appendChild(mapDiv)
|
|
400
|
+
const zoom = this.output.zoom || data.zoom || 13
|
|
401
|
+
const tiles = this.output.tiles || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
|
402
|
+
const map = L.map(mapDiv)
|
|
403
|
+
L.tileLayer(tiles, { attribution: '© OpenStreetMap' }).addTo(map)
|
|
404
|
+
this._leafletMap = map
|
|
405
|
+
// GeoJSON
|
|
406
|
+
if (data.type === 'FeatureCollection' || data.type === 'Feature') {
|
|
407
|
+
const layer = L.geoJSON(data).addTo(map)
|
|
408
|
+
map.fitBounds(layer.getBounds())
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
// Markers from array or object
|
|
412
|
+
const markers = Array.isArray(data) ? data : (data.markers || [])
|
|
413
|
+
const center = this.output.center || data.center
|
|
414
|
+
const bounds = []
|
|
415
|
+
markers.forEach(m => {
|
|
416
|
+
const latlng = [m.lat, m.lng]
|
|
417
|
+
const marker = L.marker(latlng).addTo(map)
|
|
418
|
+
if (m.popup) marker.bindPopup(m.popup)
|
|
419
|
+
bounds.push(latlng)
|
|
420
|
+
})
|
|
421
|
+
if (center) {
|
|
422
|
+
map.setView(center, zoom)
|
|
423
|
+
} else if (bounds.length) {
|
|
424
|
+
map.fitBounds(bounds)
|
|
425
|
+
} else {
|
|
426
|
+
map.setView([0, 0], 2)
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
revokePdfObjectUrl() {
|
|
430
|
+
if (this._pdfObjectUrl && UrlApi) {
|
|
431
|
+
UrlApi.revokeObjectURL(this._pdfObjectUrl)
|
|
432
|
+
this._pdfObjectUrl = null
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
pdfDataToUrl(data) {
|
|
436
|
+
if (typeof data === 'string') return data
|
|
437
|
+
if (!Blob || !UrlApi) return null
|
|
438
|
+
if (data instanceof Blob) {
|
|
439
|
+
this.revokePdfObjectUrl()
|
|
440
|
+
this._pdfObjectUrl = UrlApi.createObjectURL(data)
|
|
441
|
+
return this._pdfObjectUrl
|
|
442
|
+
}
|
|
443
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
|
444
|
+
this.revokePdfObjectUrl()
|
|
445
|
+
const bytes = data instanceof ArrayBuffer
|
|
446
|
+
? new Uint8Array(data)
|
|
447
|
+
: new Uint8Array(data.buffer, data.byteOffset || 0, data.byteLength)
|
|
448
|
+
const blob = new Blob([bytes], { type: 'application/pdf' })
|
|
449
|
+
this._pdfObjectUrl = UrlApi.createObjectURL(blob)
|
|
450
|
+
return this._pdfObjectUrl
|
|
451
|
+
}
|
|
452
|
+
return null
|
|
453
|
+
},
|
|
454
|
+
renderNativePDF(container, data) {
|
|
455
|
+
const url = this.pdfDataToUrl(data)
|
|
456
|
+
if (!url) {
|
|
457
|
+
container.textContent = 'Unsupported PDF data format'
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
container.innerHTML = ''
|
|
461
|
+
const iframe = document.createElement('iframe')
|
|
462
|
+
iframe.className = 'jsee-pdf-frame'
|
|
463
|
+
iframe.title = this.output.title || this.output.name || 'PDF'
|
|
464
|
+
iframe.src = url
|
|
465
|
+
iframe.setAttribute('loading', 'lazy')
|
|
466
|
+
container.appendChild(iframe)
|
|
467
|
+
},
|
|
468
|
+
renderPDF() {
|
|
469
|
+
if (!this.$refs.pdfContainer) return
|
|
470
|
+
const container = this.$refs.pdfContainer
|
|
471
|
+
const data = this.output.value
|
|
472
|
+
if (!data) return
|
|
473
|
+
this.renderNativePDF(container, data)
|
|
474
|
+
},
|
|
139
475
|
executeRenderFunction() {
|
|
140
476
|
if (this.isRenderFunction && this.$refs.customContainer) {
|
|
141
477
|
// Clear previous content
|