@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/LICENSE +21 -0
  3. package/README.md +583 -55
  4. package/dist/2b3e1faf89f94a483539.png +0 -0
  5. package/dist/416d91365b44e4b4f477.png +0 -0
  6. package/dist/8f2c4d11474275fbc161.png +0 -0
  7. package/dist/jsee.core.js +1 -0
  8. package/dist/jsee.full.js +1 -0
  9. package/dist/jsee.runtime.js +2 -1
  10. package/package.json +84 -18
  11. package/src/app.js +127 -32
  12. package/src/cli.js +474 -64
  13. package/src/extended-imports.js +11 -0
  14. package/src/main.js +232 -44
  15. package/src/overlay.js +26 -1
  16. package/src/utils.js +386 -16
  17. package/templates/common-inputs.js +88 -0
  18. package/templates/common-outputs.js +340 -4
  19. package/templates/minimal-app.vue +367 -0
  20. package/templates/minimal-input.vue +573 -0
  21. package/templates/minimal-output.vue +426 -0
  22. package/templates/virtual-table.vue +194 -0
  23. package/.claude/settings.local.json +0 -15
  24. package/.eslintrc.js +0 -38
  25. package/AGENTS.md +0 -65
  26. package/CLAUDE.md +0 -5
  27. package/CNAME +0 -1
  28. package/_config.yml +0 -26
  29. package/dist/jsee.js +0 -1
  30. package/dump.sh +0 -23
  31. package/jest-puppeteer.config.js +0 -14
  32. package/jest.config.js +0 -8
  33. package/jest.unit.config.js +0 -8
  34. package/jsee.dump.txt +0 -5459
  35. package/load/index.html +0 -52
  36. package/templates/bulma-app.vue +0 -242
  37. package/templates/bulma-input.vue +0 -125
  38. package/templates/bulma-output.vue +0 -101
  39. package/test/arrow-main.html +0 -18
  40. package/test/arrow-worker.html +0 -18
  41. package/test/class.html +0 -22
  42. package/test/code.html +0 -25
  43. package/test/codew.html +0 -25
  44. package/test/example-class.js +0 -8
  45. package/test/example-sum.js +0 -3
  46. package/test/fixtures/lodash-like.js +0 -15
  47. package/test/fixtures/upload-sample.csv +0 -3
  48. package/test/importw.html +0 -28
  49. package/test/minimal.html +0 -14
  50. package/test/minimal1.html +0 -13
  51. package/test/minimal2.html +0 -15
  52. package/test/minimal3.html +0 -10
  53. package/test/minimal4.html +0 -22
  54. package/test/pipeline.html +0 -52
  55. package/test/python.html +0 -41
  56. package/test/runtime-arrow.html +0 -18
  57. package/test/string.html +0 -26
  58. package/test/stringw.html +0 -29
  59. package/test/sum.schema.json +0 -17
  60. package/test/sumw.schema.json +0 -15
  61. package/test/test-basic.test.js +0 -630
  62. package/test/test-python.test.js +0 -23
  63. package/test/unit/cli-fetch.test.js +0 -229
  64. package/test/unit/utils.test.js +0 -908
  65. package/webpack.config.js +0 -101
package/package.json CHANGED
@@ -1,29 +1,97 @@
1
1
  {
2
2
  "name": "@jseeio/jsee",
3
- "version": "0.4.2",
3
+ "version": "0.8.0",
4
4
  "description": "JavaScript Execution Environment",
5
- "main": "dist/jsee.js",
6
- "unpkg": "dist/jsee.js",
7
- "jsdelivr": "dist/jsee.js",
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 && webpack --mode=production --progress --env RUNTIME && npm test",
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:basic && npm run test:python",
69
+ "test": "npm run test:unit && npm run test:e2e",
16
70
  "test:unit": "jest --config jest.unit.config.js",
17
- "test:basic": "jest test/test-basic.test.js --detectOpenHandles",
18
- "test:python": "jest test/test-python.test.js --detectOpenHandles",
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
- "bulma": "^0.9.3",
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": "^8.0.1",
44
- "katex": "^0.16.22",
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
- "showdown": "^1.9.1",
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 bulmaApp from '../templates/bulma-app.vue'
9
- import bulmaInput from '../templates/bulma-input.vue'
10
- import bulmaOutput from '../templates/bulma-output.vue'
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
- 'bulma': {
14
- 'app': bulmaApp,
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
- : 'bulma'
147
-
148
- let template
149
- let render
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
  }