@jseeio/jsee 0.4.2 → 0.8.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/CHANGELOG.md +90 -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/cli.js +474 -64
- 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
package/package.json
CHANGED
|
@@ -1,29 +1,97 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jseeio/jsee",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "JavaScript Execution Environment",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "src/cli.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"browser": "./dist/jsee.core.js",
|
|
10
|
+
"node": "./src/cli.js",
|
|
11
|
+
"require": "./src/cli.js",
|
|
12
|
+
"default": "./dist/jsee.core.js"
|
|
13
|
+
},
|
|
14
|
+
"./cli": {
|
|
15
|
+
"node": "./src/cli.js",
|
|
16
|
+
"require": "./src/cli.js",
|
|
17
|
+
"default": "./src/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"./core": {
|
|
20
|
+
"require": "./dist/jsee.core.js",
|
|
21
|
+
"default": "./dist/jsee.core.js"
|
|
22
|
+
},
|
|
23
|
+
"./full": {
|
|
24
|
+
"require": "./dist/jsee.full.js",
|
|
25
|
+
"default": "./dist/jsee.full.js"
|
|
26
|
+
},
|
|
27
|
+
"./runtime": {
|
|
28
|
+
"require": "./dist/jsee.runtime.js",
|
|
29
|
+
"default": "./dist/jsee.runtime.js"
|
|
30
|
+
},
|
|
31
|
+
"./dist/jsee.core.js": {
|
|
32
|
+
"require": "./dist/jsee.core.js",
|
|
33
|
+
"default": "./dist/jsee.core.js"
|
|
34
|
+
},
|
|
35
|
+
"./dist/jsee.full.js": {
|
|
36
|
+
"require": "./dist/jsee.full.js",
|
|
37
|
+
"default": "./dist/jsee.full.js"
|
|
38
|
+
},
|
|
39
|
+
"./dist/jsee.runtime.js": {
|
|
40
|
+
"require": "./dist/jsee.runtime.js",
|
|
41
|
+
"default": "./dist/jsee.runtime.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"unpkg": "dist/jsee.core.js",
|
|
46
|
+
"jsdelivr": "dist/jsee.core.js",
|
|
47
|
+
"sideEffects": false,
|
|
48
|
+
"files": [
|
|
49
|
+
"bin/",
|
|
50
|
+
"dist/jsee.core.js",
|
|
51
|
+
"dist/jsee.full.js",
|
|
52
|
+
"dist/jsee.runtime.js",
|
|
53
|
+
"dist/*.png",
|
|
54
|
+
"dist/*.svg",
|
|
55
|
+
"src/",
|
|
56
|
+
"templates/",
|
|
57
|
+
"README.md",
|
|
58
|
+
"CHANGELOG.md",
|
|
59
|
+
"LICENSE"
|
|
60
|
+
],
|
|
8
61
|
"private": false,
|
|
9
62
|
"scripts": {
|
|
10
|
-
"build-dev": "webpack --mode=development --progress --stats-children --env DEVELOPMENT",
|
|
11
|
-
"build": "webpack --mode=production --progress
|
|
63
|
+
"build-dev": "webpack --mode=development --progress --stats-children --env DEVELOPMENT && node scripts/compat-runtime.js",
|
|
64
|
+
"build-full": "webpack --mode=production --progress --env FULL",
|
|
65
|
+
"build": "webpack --mode=production --progress && webpack --mode=production --progress --env FULL && node scripts/compat-runtime.js && npm test",
|
|
12
66
|
"watch-dev": "nodemon --watch . --ignore dist,.git --ext vue,js,css,html --exec 'npm run build-dev'",
|
|
13
67
|
"watch": "nodemon --watch . --ignore dist,.git --ext vue,js,css,html --exec 'npm run build-dev && npm run test:basic'",
|
|
14
68
|
"prepublishOnly": "npm run build",
|
|
15
|
-
"test": "npm run test:unit && npm run test:
|
|
69
|
+
"test": "npm run test:unit && npm run test:e2e",
|
|
16
70
|
"test:unit": "jest --config jest.unit.config.js",
|
|
17
|
-
"test:
|
|
18
|
-
"test:
|
|
71
|
+
"test:e2e": "npx playwright test test/test-basic.pw.js test/test-inputs.pw.js",
|
|
72
|
+
"test:basic": "npx playwright test test/test-basic.pw.js",
|
|
73
|
+
"test:inputs": "npx playwright test test/test-inputs.pw.js",
|
|
74
|
+
"test:python": "npx playwright test test/test-python.pw.js",
|
|
19
75
|
"test-head": "HEADLESS=false npm test",
|
|
20
|
-
"lint": "eslint src/ test/"
|
|
76
|
+
"lint": "eslint src/ test/",
|
|
77
|
+
"copy-runtime-py": "mkdir -p py/jsee/static && cp dist/jsee.core.js py/jsee/static/jsee.core.js && cp dist/jsee.full.js py/jsee/static/jsee.full.js"
|
|
21
78
|
},
|
|
22
79
|
"bin": {
|
|
23
80
|
"jsee": "./bin/jsee"
|
|
24
81
|
},
|
|
25
82
|
"author": "Anton Zemlyansky",
|
|
26
83
|
"license": "MIT",
|
|
84
|
+
"keywords": [
|
|
85
|
+
"jsee",
|
|
86
|
+
"schema",
|
|
87
|
+
"runtime",
|
|
88
|
+
"web-worker",
|
|
89
|
+
"visualization",
|
|
90
|
+
"offline"
|
|
91
|
+
],
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
94
|
+
},
|
|
27
95
|
"repository": {
|
|
28
96
|
"type": "git",
|
|
29
97
|
"url": "git+https://github.com/jseeio/jsee"
|
|
@@ -34,18 +102,18 @@
|
|
|
34
102
|
},
|
|
35
103
|
"dependencies": {
|
|
36
104
|
"@mdi/font": "^6.5.95",
|
|
37
|
-
"
|
|
105
|
+
"@observablehq/plot": "^0.6",
|
|
38
106
|
"csv-parse": "^5.6.0",
|
|
39
107
|
"dom-to-image": "^2.6.0",
|
|
40
108
|
"express": "^4.21.2",
|
|
41
109
|
"file-saver": "^2.0.2",
|
|
42
110
|
"filtrex": "^2.2.3",
|
|
43
|
-
"jsdoc-to-markdown": "^
|
|
44
|
-
"
|
|
111
|
+
"jsdoc-to-markdown": "^9.1.3",
|
|
112
|
+
"leaflet": "^1.9",
|
|
113
|
+
"markdown-it": "^14.2.0",
|
|
45
114
|
"minimist": "^1.2.8",
|
|
46
115
|
"notyf": "^3.10.0",
|
|
47
|
-
"
|
|
48
|
-
"showdown-katex": "^0.8.0",
|
|
116
|
+
"three": "^0.170",
|
|
49
117
|
"vue": "^3.2.47",
|
|
50
118
|
"vue-file-picker": "^0.0.2",
|
|
51
119
|
"vue-style-loader": "^4.1.3",
|
|
@@ -56,15 +124,13 @@
|
|
|
56
124
|
"@babel/core": "^7.21.4",
|
|
57
125
|
"@babel/plugin-transform-runtime": "^7.4.4",
|
|
58
126
|
"@babel/preset-env": "^7.16.5",
|
|
127
|
+
"@playwright/test": "^1.58.2",
|
|
59
128
|
"babel-loader": "^9.1.2",
|
|
60
129
|
"css-loader": "^6.7.3",
|
|
61
130
|
"eslint": "^8.57.1",
|
|
62
|
-
"expect-puppeteer": "^11.0.0",
|
|
63
131
|
"http-server": "^14.1.1",
|
|
64
132
|
"jest": "^29.7.0",
|
|
65
|
-
"jest-puppeteer": "^11.0.0",
|
|
66
133
|
"nodemon": "^3.1.10",
|
|
67
|
-
"puppeteer": "^24.10.2",
|
|
68
134
|
"sass": "^1.83.4",
|
|
69
135
|
"sass-loader": "^13.2.2",
|
|
70
136
|
"source-map-loader": "^4.0.1",
|
package/src/app.js
CHANGED
|
@@ -5,21 +5,62 @@
|
|
|
5
5
|
|
|
6
6
|
import { createApp, h } from 'vue' // <- resolved in webpack.config based on RUNTIME
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
8
|
+
import minimalApp from '../templates/minimal-app.vue'
|
|
9
|
+
import minimalInput from '../templates/minimal-input.vue'
|
|
10
|
+
import minimalOutput from '../templates/minimal-output.vue'
|
|
11
|
+
|
|
12
|
+
const defaultTheme = {
|
|
13
|
+
'app': minimalApp,
|
|
14
|
+
'input': minimalInput,
|
|
15
|
+
'output': minimalOutput
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
const components = {
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
'input': bulmaInput,
|
|
16
|
-
'output': bulmaOutput
|
|
17
|
-
}
|
|
19
|
+
'minimal': defaultTheme,
|
|
20
|
+
'bulma': defaultTheme // backward compat alias
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
const filtrex = require('filtrex')
|
|
21
24
|
const JsonViewer = require('vue3-json-viewer').default
|
|
22
|
-
const { sanitizeName, debounce } = require('./utils.js')
|
|
25
|
+
const { sanitizeName, debounce, createValidateFn, runValidation } = require('./utils.js')
|
|
26
|
+
|
|
27
|
+
const STORAGE_KEY = 'jsee:inputs'
|
|
28
|
+
|
|
29
|
+
function saveInputsToStorage (inputs) {
|
|
30
|
+
try {
|
|
31
|
+
const values = {}
|
|
32
|
+
inputs.forEach((input, index) => {
|
|
33
|
+
if (input.type === 'file' || input.type === 'folder' || input.type === 'action' || input.type === 'button') return
|
|
34
|
+
if (input.type === 'group') return // skip groups for now
|
|
35
|
+
const key = input.name || `input_${index}`
|
|
36
|
+
values[key] = input.value
|
|
37
|
+
})
|
|
38
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(values))
|
|
39
|
+
} catch (e) { /* quota exceeded or private mode */ }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadInputsFromStorage (inputs) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = localStorage.getItem(STORAGE_KEY)
|
|
45
|
+
if (!raw) return false
|
|
46
|
+
const values = JSON.parse(raw)
|
|
47
|
+
let restored = false
|
|
48
|
+
inputs.forEach((input, index) => {
|
|
49
|
+
if (input.type === 'file' || input.type === 'folder' || input.type === 'action' || input.type === 'button') return
|
|
50
|
+
if (input.type === 'group') return
|
|
51
|
+
const key = input.name || `input_${index}`
|
|
52
|
+
if (typeof values[key] !== 'undefined') {
|
|
53
|
+
input.value = values[key]
|
|
54
|
+
restored = true
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
return restored
|
|
58
|
+
} catch (e) { return false }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function clearInputsStorage () {
|
|
62
|
+
try { localStorage.removeItem(STORAGE_KEY) } catch (e) { /* ignore */ }
|
|
63
|
+
}
|
|
23
64
|
|
|
24
65
|
function setInputValue (input, value) {
|
|
25
66
|
if (input.type === 'file') {
|
|
@@ -35,16 +76,16 @@ function setInputValue (input, value) {
|
|
|
35
76
|
function resetInputs (inputs, example) {
|
|
36
77
|
inputs.forEach((input, index) => {
|
|
37
78
|
const inputName = input.name ? sanitizeName(input.name) : `input_${index}`
|
|
38
|
-
if (example && input.name && example[input.name]) {
|
|
79
|
+
if (example && input.name && typeof example[input.name] !== 'undefined') {
|
|
39
80
|
// Object (unsanitized)
|
|
40
81
|
setInputValue(input, example[input.name])
|
|
41
|
-
} else if (example && inputName && example[inputName]) {
|
|
82
|
+
} else if (example && inputName && typeof example[inputName] !== 'undefined') {
|
|
42
83
|
// Object (sanitized)
|
|
43
84
|
setInputValue(input, example[inputName])
|
|
44
85
|
} else if (example && Array.isArray(example) && typeof example[index] !== 'undefined') {
|
|
45
86
|
// Array
|
|
46
87
|
setInputValue(input, example[index])
|
|
47
|
-
} else if (input.default) {
|
|
88
|
+
} else if (typeof input.default !== 'undefined') {
|
|
48
89
|
// Default value
|
|
49
90
|
setInputValue(input, input.default)
|
|
50
91
|
} else {
|
|
@@ -73,6 +114,27 @@ function resetInputs (inputs, example) {
|
|
|
73
114
|
input.file = null
|
|
74
115
|
input.value = ''
|
|
75
116
|
break
|
|
117
|
+
case 'folder':
|
|
118
|
+
input.value = input.default || []
|
|
119
|
+
break
|
|
120
|
+
case 'slider':
|
|
121
|
+
input.value = input.min || 0
|
|
122
|
+
break
|
|
123
|
+
case 'range':
|
|
124
|
+
input.value = [input.min || 0, input.max || 100]
|
|
125
|
+
break
|
|
126
|
+
case 'radio':
|
|
127
|
+
input.value = input.options ? input.options[0] : ''
|
|
128
|
+
break
|
|
129
|
+
case 'toggle':
|
|
130
|
+
input.value = false
|
|
131
|
+
break
|
|
132
|
+
case 'date':
|
|
133
|
+
input.value = ''
|
|
134
|
+
break
|
|
135
|
+
case 'multi-select':
|
|
136
|
+
input.value = []
|
|
137
|
+
break
|
|
76
138
|
case 'group':
|
|
77
139
|
resetInputs(input.elements)
|
|
78
140
|
break
|
|
@@ -94,6 +156,12 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
94
156
|
// Reset input values to default ones
|
|
95
157
|
resetInputs(dataInit.inputs)
|
|
96
158
|
|
|
159
|
+
// Restore saved inputs from localStorage (unless URL params are present or persist: false)
|
|
160
|
+
const hasUrlParams = new URLSearchParams(window.location.search).toString().length > 0
|
|
161
|
+
if (env.schema.persist !== false && !hasUrlParams) {
|
|
162
|
+
loadInputsFromStorage(dataInit.inputs)
|
|
163
|
+
}
|
|
164
|
+
|
|
97
165
|
if (!('outputs' in dataInit)) {
|
|
98
166
|
dataInit.outputs = []
|
|
99
167
|
}
|
|
@@ -102,6 +170,8 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
102
170
|
dataInit.dataChanged = false
|
|
103
171
|
// Flag for autorun feedback
|
|
104
172
|
dataInit.clickRun = false
|
|
173
|
+
// Flag for running state (toggled by main.js run/stop)
|
|
174
|
+
dataInit.running = false
|
|
105
175
|
|
|
106
176
|
function len(s) {
|
|
107
177
|
return s.length;
|
|
@@ -111,6 +181,19 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
111
181
|
extraFunctions: { len }
|
|
112
182
|
}
|
|
113
183
|
|
|
184
|
+
// Initialize _error property on all inputs for reactivity
|
|
185
|
+
dataInit.inputs.forEach(input => {
|
|
186
|
+
input._error = null
|
|
187
|
+
if (input.type === 'group' && input.elements) {
|
|
188
|
+
input.elements.forEach(el => { el._error = null })
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Prepare validation functions for inputs with validate or required
|
|
193
|
+
const validateFunctions = dataInit.inputs.map(input =>
|
|
194
|
+
createValidateFn(input, filtrex.compileExpression, filtrexOptions)
|
|
195
|
+
)
|
|
196
|
+
|
|
114
197
|
// Prepare functions that determine if inputs should be displayed
|
|
115
198
|
const displayFunctions = dataInit.inputs.map(input => {
|
|
116
199
|
if (input.display && input.display.length) {
|
|
@@ -143,30 +226,14 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
143
226
|
// Determine a template and GUI framework
|
|
144
227
|
const framework = (env.schema.design && typeof env.schema.design.framework !== 'undefined')
|
|
145
228
|
? env.schema.design.framework
|
|
146
|
-
: '
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
env.schema.design
|
|
152
|
-
&& env.schema.design.template
|
|
153
|
-
&& (
|
|
154
|
-
typeof env.schema.design.template === 'string'
|
|
155
|
-
|| env.schema.design.template === false
|
|
156
|
-
)
|
|
157
|
-
) {
|
|
158
|
-
template = env.schema.design.template
|
|
159
|
-
render = null
|
|
160
|
-
} else {
|
|
161
|
-
template = null //'<vue-app/>'
|
|
162
|
-
render = () => {
|
|
163
|
-
return h(components[framework].app)
|
|
164
|
-
}
|
|
229
|
+
: 'minimal'
|
|
230
|
+
|
|
231
|
+
const render = () => {
|
|
232
|
+
return h(components[framework].app)
|
|
165
233
|
}
|
|
166
234
|
|
|
167
235
|
log('Initializing Vue app...')
|
|
168
236
|
const app = createApp({
|
|
169
|
-
template,
|
|
170
237
|
render,
|
|
171
238
|
data () {
|
|
172
239
|
return dataInit
|
|
@@ -179,6 +246,10 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
179
246
|
// Per-input reactivity uses the 'inchange' event from inputs with reactive: true.
|
|
180
247
|
handler: debounce(function (v) {
|
|
181
248
|
this.dataChanged = true
|
|
249
|
+
if (env.schema.persist !== false) {
|
|
250
|
+
saveInputsToStorage(this.inputs)
|
|
251
|
+
}
|
|
252
|
+
runValidation(this.inputs, validateFunctions)
|
|
182
253
|
if (env.schema.reactive) {
|
|
183
254
|
this.run('reactive')
|
|
184
255
|
}
|
|
@@ -199,11 +270,32 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
199
270
|
// Reset input values to default ones
|
|
200
271
|
// If example is provided, use it as a new default
|
|
201
272
|
resetInputs(this.inputs, example)
|
|
273
|
+
clearInputsStorage()
|
|
274
|
+
// Clear DOM file inputs so re-uploading the same file triggers change event
|
|
202
275
|
this.$nextTick(() => {
|
|
276
|
+
const fileInputs = container.querySelectorAll('input[type="file"]')
|
|
277
|
+
fileInputs.forEach(el => { el.value = '' })
|
|
203
278
|
this.dataChanged = false
|
|
204
279
|
})
|
|
280
|
+
// Clear dynamic select options (e.g. target populated from worker)
|
|
281
|
+
this.inputs.forEach(input => {
|
|
282
|
+
if ((input.type === 'select' || input.type === 'categorical') && input.options && !input._initialOptions) {
|
|
283
|
+
input._initialOptions = input.default ? undefined : []
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
// Clear outputs
|
|
287
|
+
if (this.outputs) {
|
|
288
|
+
this.outputs.forEach(output => {
|
|
289
|
+
if (output.type === 'chat') {
|
|
290
|
+
output._messages = []
|
|
291
|
+
} else {
|
|
292
|
+
output.value = undefined
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
}
|
|
205
296
|
},
|
|
206
297
|
run (caller) {
|
|
298
|
+
if (runValidation(this.inputs, validateFunctions)) return
|
|
207
299
|
this.clickRun = true
|
|
208
300
|
// Catch to prevent unhandled rejection from button/autorun clicks
|
|
209
301
|
env.run(caller).catch(err => console.error('Run error:', err))
|
|
@@ -211,6 +303,9 @@ function createVueApp (env, mountedCallback, logMain) {
|
|
|
211
303
|
this.clickRun = false
|
|
212
304
|
}, 150)
|
|
213
305
|
},
|
|
306
|
+
stop () {
|
|
307
|
+
env.cancelCurrentRun()
|
|
308
|
+
},
|
|
214
309
|
notify (msg) {
|
|
215
310
|
env.notify(msg)
|
|
216
311
|
}
|