@jseeio/jsee 0.3.7 → 0.3.9

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/src/worker.js CHANGED
@@ -1,13 +1,28 @@
1
1
  const utils = require('./utils')
2
2
 
3
3
  function log () {
4
- const args = Array.prototype.slice.call(arguments);
4
+ const args = Array.prototype.slice.call(arguments)
5
5
  args.unshift('[Worker]')
6
- postMessage({_status: 'log', _log: args})
6
+ postMessage({ _status: 'log', _log: args })
7
7
  }
8
8
 
9
9
  function progress (value) {
10
- postMessage({_status: 'progress', _progress: value})
10
+ postMessage({ _status: 'progress', _progress: value })
11
+ }
12
+
13
+ let initialized = false
14
+ let cancelled = false
15
+ let streamInputConfig = {}
16
+
17
+ function isCancelled () {
18
+ return cancelled === true
19
+ }
20
+
21
+ function getStreamOptions () {
22
+ return {
23
+ isCancelled,
24
+ onProgress: progress
25
+ }
11
26
  }
12
27
 
13
28
  function initTF (model) {
@@ -15,7 +30,7 @@ function initTF (model) {
15
30
  }
16
31
 
17
32
  async function initPython (model) {
18
- importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js")
33
+ importScripts('https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js')
19
34
  const pyodide = await loadPyodide()
20
35
  if (model.imports && Array.isArray(model.imports) && model.imports.length) {
21
36
  await pyodide.loadPackage(model.imports.map(i => i.url))
@@ -26,7 +41,7 @@ async function initPython (model) {
26
41
  for (let key in data) {
27
42
  self[key] = data[key]
28
43
  }
29
- return await pyodide.runPythonAsync(model.code);
44
+ return await pyodide.runPythonAsync(model.code)
30
45
  }
31
46
  }
32
47
 
@@ -34,11 +49,9 @@ async function initJS (model) {
34
49
  log('Init JS')
35
50
  this.container = model.container
36
51
 
37
- // Load imports
38
52
  if (model.imports && model.imports.length) {
39
53
  log('Loading imports...')
40
54
  for (let imp of model.imports) {
41
- // Try creating an url
42
55
  if (imp.code) {
43
56
  log('Importing from DOM:', imp.url)
44
57
  importScripts(URL.createObjectURL(new Blob([imp.code], { type: 'text/javascript' })))
@@ -51,7 +64,6 @@ async function initJS (model) {
51
64
 
52
65
  if (model.code) {
53
66
  log('Load code as a string', model)
54
- // https://github.com/altbdoor/blob-worker/blob/master/blobWorker.js
55
67
  importScripts(URL.createObjectURL(new Blob([model.code], { type: 'text/javascript' })))
56
68
  } else if (model.url) {
57
69
  log('Load script from URL:', model.url)
@@ -60,14 +72,15 @@ async function initJS (model) {
60
72
  log('No script provided')
61
73
  }
62
74
 
63
- // Related:
64
- // https://stackoverflow.com/questions/37711603/javascript-es6-class-definition-not-accessible-in-window-global
65
75
  const target = model.type === 'class'
66
76
  ? eval(model.name)
67
77
  : this[model.name]
68
78
 
69
- // Need promise here in case of async init
70
- let modelFunc = await utils.getModelFuncJS(model, target, { log, progress })
79
+ let modelFunc = await utils.getModelFuncJS(model, target, {
80
+ log,
81
+ progress,
82
+ isCancelled
83
+ })
71
84
 
72
85
  return modelFunc
73
86
  }
@@ -81,10 +94,19 @@ onmessage = async function (e) {
81
94
  var data = e.data
82
95
  log('Received message of type:', typeof data)
83
96
 
84
- if ((typeof data === 'object') && ((data.url) || (data.code))) {
85
- // Init message
97
+ if ((typeof data === 'object') && (data._cmd === 'cancel')) {
98
+ cancelled = true
99
+ log('Cancel command received')
100
+ return
101
+ }
102
+
103
+ if (utils.isWorkerInitMessage(data, initialized)) {
86
104
  log('Init...')
87
105
  let m = data
106
+ streamInputConfig = (m && typeof m._streamInputConfig === 'object' && m._streamInputConfig)
107
+ ? m._streamInputConfig
108
+ : {}
109
+
88
110
  switch (m.type) {
89
111
  case 'tf':
90
112
  self.modelFunc = await initTF(m)
@@ -105,12 +127,14 @@ onmessage = async function (e) {
105
127
  default:
106
128
  throw new Error(`No type information: ${m.type}`)
107
129
  }
108
- postMessage({_status: 'loaded'})
130
+ initialized = true
131
+ postMessage({ _status: 'loaded' })
109
132
  } else {
110
- // Execution
111
133
  try {
112
- log('Run model with data:', data)
113
- const results = await self.modelFunc(data)
134
+ cancelled = false
135
+ const runData = utils.wrapStreamInputs(data, streamInputConfig, getStreamOptions())
136
+ log('Run model')
137
+ const results = await self.modelFunc(runData)
114
138
  log('Results:', results)
115
139
  postMessage(results)
116
140
  } catch (error) {
@@ -202,8 +202,9 @@
202
202
  <button
203
203
  v-on:click="$parent.reset(example)"
204
204
  class="button is-small example-button"
205
+ style="white-space: normal; width: 100%; text-align: left; font-family: monospace; font-size: 10px;"
205
206
  >
206
- {{ example }}
207
+ {{ JSON.stringify(example, null, 2) }}
207
208
  </button>
208
209
  </div>
209
210
  </div>
@@ -229,7 +230,7 @@
229
230
  ></vue-output>
230
231
  </div>
231
232
  </div>
232
- <pre v-if="$parent.model.debug">{{ $parent.outputs }}</pre>
233
+ <pre v-if="$parent.debug">{{ $parent.outputs }}</pre>
233
234
  </div>
234
235
  </div>
235
236
  </div>
@@ -1,3 +1,9 @@
1
+ <style lang="scss" scoped>
2
+ .control {
3
+ margin-top: -1px;
4
+ }
5
+ </style>
6
+
1
7
  <template>
2
8
  <div class="field" v-if="input.type == 'int' || input.type == 'float' || input.type == 'number'">
3
9
  <label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
@@ -9,6 +15,7 @@
9
15
  v-bind:placeholder="input.placeholder ? input.placeholder : input.name"
10
16
  v-bind:min="input.min"
11
17
  v-bind:max="input.max"
18
+ v-bind:disabled="input.disabled"
12
19
  v-on:change="changeHandler"
13
20
  class="input"
14
21
  type="number"
@@ -76,24 +83,22 @@
76
83
  <div class="field" v-if="input.type == 'file'">
77
84
  <label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
78
85
  <div class="control">
79
- <div class="file has-name is-fullwidth" v-bind:class="{ 'is-primary': !input.file }">
80
- <label class="file-label">
81
- <input
82
- v-bind:id="input.name"
83
- v-on:change="loadFile"
84
- class="file-input"
85
- type="file"
86
- >
87
- <span class="file-cta">
88
- <span class="file-label">
89
- Open
90
- </span>
91
- </span>
92
- <span class="file-name">
93
- <span v-if="input.file && input.file.name">{{ input.file.name }}</span>
94
- <span v-else>No file selected</span>
95
- </span>
96
- </label>
86
+ <div class="file has-name is-fullwidth" v-bind:class="{ 'is-primary': !input.file }" v-if="!input.disabled">
87
+ <file-picker
88
+ v-model="input.value"
89
+ v-model:url="input.url"
90
+ v-bind:raw="input.raw === true || input.stream === true"
91
+ v-bind:autoload="input.urlAutoLoad === true"
92
+ v-on:change="changeHandler"
93
+ ></file-picker>
94
+ </div>
95
+ <div class="file has-name is-fullwidth" v-bind:class="{ 'is-primary': !input.file }" v-else>
96
+ <input
97
+ class="input"
98
+ v-bind:id="input.name"
99
+ v-bind:value="input.default"
100
+ disabled
101
+ >
97
102
  </div>
98
103
  </div>
99
104
  </div>
@@ -1,5 +1,51 @@
1
+ <style scoped>
2
+ /* Quickly stretch the card when browser goes native full‑screen
3
+ (actual FS API above) OR when the helper class is present. */
4
+ /*
5
+ .is-fullscreen {
6
+ position: fixed;
7
+ inset: 0;
8
+ z-index: 9999;
9
+ width: 100vw;
10
+ height: 100vh;
11
+ overflow: auto;
12
+ background: #fff;
13
+ }
14
+ .is-fullscreen .card-content, .is-fullscreen .content {
15
+ height: 100% !important;
16
+ }
17
+ */
18
+
19
+ /* Full‑screen override */
20
+ .is-fullscreen {
21
+ position: fixed;
22
+ inset: 0;
23
+ z-index: 9999;
24
+ width: 100vw;
25
+ height: 100vh;
26
+ background: #fff;
27
+ display: flex;
28
+ flex-direction: column;
29
+ }
30
+
31
+ .is-fullscreen .card-content {
32
+ flex: 1 1 auto;
33
+ overflow: auto;
34
+ }
35
+
36
+ /* Ensure Plotly div or other content can grow */
37
+ .is-fullscreen .content, .is-fullscreen .custom-container {
38
+ height: 100% !important;
39
+ }
40
+ </style>
41
+
1
42
  <template>
2
- <div class="card mb-5" v-if="!(typeof output.value === 'undefined')">
43
+ <div
44
+ class="card mb-5"
45
+ v-show="!(typeof output.value === 'undefined')"
46
+ :class="{ 'is-fullscreen': isFullScreen }"
47
+ ref="cardRoot"
48
+ >
3
49
  <header class="card-header">
4
50
  <p class="card-header-title is-size-6" v-if="output.name">
5
51
  {{ output.name }}
@@ -8,22 +54,41 @@
8
54
  <p class="card-header-icon">
9
55
  <button class="button is-small" v-on:click="save()">Save</button>
10
56
  <button class="button is-small" v-on:click="copy()">Copy</button>
57
+ <button
58
+ class="button is-small"
59
+ v-if="!isFullScreen"
60
+ @click="toggleFullScreen"
61
+ title="Expand to full screen"
62
+ >
63
+ Fullscreen
64
+ </button>
65
+ <button
66
+ class="button is-small"
67
+ v-else
68
+ @click="toggleFullScreen"
69
+ title="Exit full screen"
70
+ >
71
+ Close
72
+ </button>
11
73
  </p>
12
74
  </header>
13
75
  <div class="card-content">
14
- <div class="content" v-if="(output.type == 'svg') || (output.type == 'html')">
76
+ <div class="content" :id="outputName" v-if="(output.type == 'svg') || (output.type == 'html')">
15
77
  <div v-html="output.value"></div>
16
78
  </div>
17
- <div class="content" v-else-if="output.type == 'object'">
79
+ <div class="content" :id="outputName" v-else-if="output.type == 'object'">
18
80
  <json-viewer :value="output.value" copyable sort />
19
81
  </div>
20
- <div class="content" v-else-if="output.type == 'code'">
82
+ <div class="content" :id="outputName" v-else-if="output.type == 'code'">
21
83
  <pre>{{ output.value }}</pre>
22
84
  </div>
23
- <div class="content" v-else-if="output.type == 'function'">
24
- <div ref="customContainer"></div>
85
+ <div class="content" :id="outputName" v-else-if="output.type == 'function'">
86
+ <div class="custom-container" ref="customContainer"></div>
87
+ </div>
88
+ <div class="content" :id="outputName" v-else-if="output.type == 'blank'">
89
+ <!-- will be filled by custom render function -->
25
90
  </div>
26
- <div class="content" v-else>
91
+ <div class="content" :id="outputName" v-else>
27
92
  <pre>{{ output.value }}</pre>
28
93
  </div>
29
94
  </div>
@@ -1,27 +1,16 @@
1
1
  const FileReader = window['FileReader']
2
+ import FilePicker from './file-picker.vue'
2
3
 
3
4
  const component = {
4
5
  props: ['input'],
5
6
  emits: ['inchange'],
7
+ components: { FilePicker },
6
8
  methods: {
7
9
  changeHandler () {
8
10
  if (this.input.reactive) {
9
11
  this.$emit('inchange')
10
12
  }
11
13
  },
12
- loadFile (e) {
13
- const reader = new FileReader()
14
- this.input.file = e.target.files[0]
15
- reader.readAsText(this.input.file)
16
- reader.onload = () => {
17
- this.input.value = reader.result
18
- if (typeof this.input.cb !== 'undefined') {
19
- this.input.cb.run()
20
- } else if (this.input.reactive) {
21
- this.$emit('inchange')
22
- }
23
- }
24
- },
25
14
  call (method) {
26
15
  console.log('calling: ', method)
27
16
  }
@@ -1,6 +1,8 @@
1
1
  import { saveAs } from 'file-saver'
2
2
  import domtoimage from 'dom-to-image'
3
3
 
4
+ const { sanitizeName } = require('../src/utils.js')
5
+
4
6
  const Blob = window['Blob']
5
7
 
6
8
  function stringify (v) {
@@ -12,11 +14,35 @@ function stringify (v) {
12
14
  const component = {
13
15
  props: ['output'],
14
16
  emits: ['notification'],
17
+ data () {
18
+ return {
19
+ outputName: 'output',
20
+ isFullScreen: false,
21
+ }
22
+ },
15
23
  mounted() {
24
+ this.outputName = this.output.alias
25
+ ? this.output.alias
26
+ : this.output.name
27
+ ? sanitizeName(this.output.name)
28
+ : 'output_' + Math.floor(Math.random() * 1000000)
16
29
  this.executeRenderFunction()
30
+ document.addEventListener('fullscreenchange', this.onFullScreenChange)
17
31
  },
18
- updated() {
19
- this.executeRenderFunction()
32
+ beforeUnmount() {
33
+ document.removeEventListener('fullscreenchange', this.onFullScreenChange)
34
+ },
35
+ // updated() {
36
+ // this.executeRenderFunction()
37
+ // },
38
+ watch: {
39
+ 'output.value': function (newValue, oldValue) {
40
+ if (newValue !== oldValue) {
41
+ this.$nextTick(() => {
42
+ this.executeRenderFunction()
43
+ })
44
+ }
45
+ }
20
46
  },
21
47
  computed: {
22
48
  isRenderFunction() {
@@ -24,6 +50,35 @@ const component = {
24
50
  }
25
51
  },
26
52
  methods: {
53
+ toggleFullScreen() {
54
+ const el = this.$refs.cardRoot || this.$el
55
+ if (!this.isFullScreen) {
56
+ if (el.requestFullscreen) {
57
+ el.requestFullscreen()
58
+ } else if (el.webkitRequestFullscreen) {
59
+ el.webkitRequestFullscreen()
60
+ } else if (el.mozRequestFullScreen) {
61
+ el.mozRequestFullScreen()
62
+ } else if (el.msRequestFullscreen) {
63
+ el.msRequestFullscreen()
64
+ }
65
+ // state will flip in onFullScreenChange
66
+ } else {
67
+ if (document.exitFullscreen) {
68
+ document.exitFullscreen()
69
+ } else if (document.webkitExitFullscreen) {
70
+ document.webkitExitFullscreen()
71
+ } else if (document.mozCancelFullScreen) {
72
+ document.mozCancelFullScreen()
73
+ } else if (document.msExitFullscreen) {
74
+ document.msExitFullscreen()
75
+ }
76
+ // state will flip in onFullScreenChange
77
+ }
78
+ },
79
+ onFullScreenChange() {
80
+ this.isFullScreen = !!document.fullscreenElement
81
+ },
27
82
  save () {
28
83
  // Prepare filename
29
84
  let filename
@@ -0,0 +1,169 @@
1
+ <template>
2
+ <!-- Based on https://github.com/rowanwins/vue-file-picker/ -->
3
+ <div
4
+ :id="id"
5
+ class="vfp"
6
+ >
7
+ <div
8
+ class="vfp-bgArea"
9
+ :class="{ 'vfp-active': isActive }"
10
+ @dragover="setActive"
11
+ @dragleave="cancelActive"
12
+ @drop="fileAdded"
13
+ >
14
+ <div class="vfp-iconHolder vfp-gridItem">
15
+ <slot name="icon">
16
+ <svg
17
+ slot="icon"
18
+ height="40"
19
+ viewBox="0 0 48 48"
20
+ xmlns="http://www.w3.org/2000/svg"
21
+ >
22
+ <path
23
+ d="M18 32h12v-12h8l-14-14-14 14h8zm-8 4h28v4h-28z"
24
+ fill="#CACFD2"
25
+ />
26
+ </svg>
27
+ </slot>
28
+ </div>
29
+ <input
30
+ id="vfp-filePicker"
31
+ class="vfp-inputfile vfp-gridItem"
32
+ type="file"
33
+ name="vfp-filePicker"
34
+ :accept="accept"
35
+ :multiple="allowMultiple"
36
+ @change="fileAdded"
37
+ >
38
+ <label
39
+ class="vfp-label vfp-gridItem"
40
+ for="vfp-filePicker"
41
+ >
42
+ <slot name="label">
43
+ <!-- <strong>Choose a file</strong> or drop it here -->
44
+ <strong>{{ label }}</strong>
45
+ </slot>
46
+ </label>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script>
52
+ export default {
53
+ name: 'FilePicker',
54
+ props: {
55
+ id: {
56
+ type: String,
57
+ required: true,
58
+ default: 'filePicker'
59
+ },
60
+ accept: {
61
+ type: String,
62
+ default: '*/*'
63
+ },
64
+ allowMultiple: {
65
+ type: Boolean,
66
+ default: false
67
+ }
68
+ },
69
+ data: function () {
70
+ return {
71
+ isActive: false,
72
+ label: 'Choose a file or drop it here'
73
+ }
74
+ },
75
+ computed: {
76
+ requiresTypeCheck: function () {
77
+ return this.accept !== '*/*'
78
+ },
79
+ acceptedTypes: function () {
80
+ return this.accept.split(',')
81
+ }
82
+ },
83
+ methods: {
84
+ cancelHandlers (e) {
85
+ e.preventDefault()
86
+ e.stopPropagation()
87
+ },
88
+ setActive (e) {
89
+ this.isActive = true
90
+ this.cancelHandlers(e)
91
+ },
92
+ cancelActive (e) {
93
+ this.isActive = false
94
+ this.cancelHandlers(e)
95
+ },
96
+ fileAdded (e) {
97
+ this.isActive = false
98
+ this.cancelHandlers(e)
99
+ const wasDropped = e.dataTransfer
100
+ const files = wasDropped ? e.dataTransfer.files : e.target.files
101
+ this.label = Array.from(files).map(file => file.name).join(', ')
102
+
103
+ if (wasDropped && !this.allowMultiple && files.length > 1) throw new Error('vue-file-picker: Multiple Files are not allowed')
104
+
105
+ if (wasDropped && this.requiresTypeCheck) {
106
+ for (var i = 0; i < files.length; i++) {
107
+ if (this.acceptedTypes.indexOf(files[i].type) === -1) throw new Error('vue-file-picker: File type not allowed')
108
+ }
109
+ }
110
+ this.$emit('vfp-file-added', files)
111
+ }
112
+ }
113
+ }
114
+ </script>
115
+
116
+ <style lang="scss">
117
+
118
+ .vfp {
119
+ display: flex;
120
+ height: 100px;
121
+ width: 100%;
122
+
123
+ .vfp-bgArea {
124
+ transition: 0.3s;
125
+ background: #F2F3F4;
126
+ display: grid;
127
+ grid-template-rows: 60% 40%;
128
+ padding: 25px 10px;
129
+ width: 100%;
130
+ outline: 2px dashed #CACFD2;
131
+ outline-offset: -10px;
132
+ color: #3b3e40;
133
+ text-align: center;
134
+ }
135
+
136
+ .vfp-inputfile {
137
+ width: 0.1px;
138
+ height: 0.1px;
139
+ opacity: 0;
140
+ overflow: hidden;
141
+ position: absolute;
142
+ }
143
+
144
+ .vfp-gridItem {
145
+ align-self: center;
146
+ justify-self: center;
147
+ }
148
+
149
+ .vfp-label {
150
+ cursor: pointer;
151
+ text-align: center;
152
+ font-size: 0.9rem;
153
+ }
154
+
155
+ .vfp-active {
156
+ background-color: #D7DBDD;
157
+ outline-color: #F2F3F4;
158
+ }
159
+
160
+ @media only screen and (max-width: 440px) {
161
+ .vfp-bgArea {
162
+ padding: 18px 10px;
163
+ grid-template-rows: 50% 50%;
164
+ grid-row-gap: 5px;
165
+ }
166
+ }
167
+ }
168
+
169
+ </style>