@jseeio/jsee 0.2.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/404.html +22 -0
- package/README.md +60 -0
- package/_config.yml +26 -0
- package/dist/fb93af989abd7020d0c6.svg +5 -0
- package/dist/jsee.js +1 -0
- package/dist/jsee.runtime.js +1 -0
- package/dist/port.js +1 -0
- package/dist/port.runtime.js +1 -0
- package/load.html +31 -0
- package/main.js +449 -0
- package/package.json +51 -0
- package/src/app.js +188 -0
- package/src/overlay.js +33 -0
- package/src/worker.js +113 -0
- package/templates/bulma-app.vue +65 -0
- package/templates/bulma-input.vue +114 -0
- package/templates/bulma-output.vue +38 -0
- package/templates/common-inputs.js +28 -0
- package/templates/common-outputs.js +39 -0
- package/webpack.config.js +94 -0
package/src/app.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// import Vue from 'vue'
|
|
2
|
+
// import { createApp } from 'vue'
|
|
3
|
+
// import { createApp } from 'vue/dist/vue.esm-bundler'
|
|
4
|
+
// import { createApp, h } from 'vue/dist/vue.runtime.esm-bundler.js'
|
|
5
|
+
|
|
6
|
+
import { createApp, h } from 'vue' // <- resolved in webpack.config based on RUNTIME
|
|
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'
|
|
11
|
+
|
|
12
|
+
const components = {
|
|
13
|
+
'bulma': {
|
|
14
|
+
'app': bulmaApp,
|
|
15
|
+
'input': bulmaInput,
|
|
16
|
+
'output': bulmaOutput
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const filtrex = require('filtrex')
|
|
21
|
+
const JsonViewer = require('vue3-json-viewer').default
|
|
22
|
+
|
|
23
|
+
function log () {
|
|
24
|
+
console.log(`[Vue]`, ...arguments)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resetInputs (inputs) {
|
|
28
|
+
log('Resetting inputs...')
|
|
29
|
+
inputs.forEach(input => {
|
|
30
|
+
if (input.default) {
|
|
31
|
+
input.value = input.default
|
|
32
|
+
} else {
|
|
33
|
+
switch (input.type) {
|
|
34
|
+
case 'int':
|
|
35
|
+
case 'float':
|
|
36
|
+
case 'number':
|
|
37
|
+
input.value = 0
|
|
38
|
+
break
|
|
39
|
+
case 'string':
|
|
40
|
+
case 'text':
|
|
41
|
+
input.value = ''
|
|
42
|
+
break
|
|
43
|
+
case 'color':
|
|
44
|
+
input.value = '#000000'
|
|
45
|
+
break
|
|
46
|
+
case 'categorical':
|
|
47
|
+
case 'select':
|
|
48
|
+
input.value = input.options ? input.options[0] : ''
|
|
49
|
+
break
|
|
50
|
+
case 'bool':
|
|
51
|
+
case 'checkbox':
|
|
52
|
+
input.value = false
|
|
53
|
+
break
|
|
54
|
+
case 'file':
|
|
55
|
+
input.file = null
|
|
56
|
+
input.value = ''
|
|
57
|
+
break
|
|
58
|
+
case 'group':
|
|
59
|
+
resetInputs(input.elements)
|
|
60
|
+
break
|
|
61
|
+
default:
|
|
62
|
+
input.value = ''
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createVueApp (env, dataInit, mountedCallback) {
|
|
69
|
+
// Reset input values to default ones
|
|
70
|
+
resetInputs(dataInit.inputs)
|
|
71
|
+
|
|
72
|
+
if (!('outputs' in dataInit)) {
|
|
73
|
+
dataInit.outputs = []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function len(s) {
|
|
77
|
+
return s.length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let filtrexOptions = {
|
|
81
|
+
extraFunctions: { len }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Prepare functions that determine if inputs should be displayed
|
|
85
|
+
const displayFunctions = dataInit.inputs.map(input => {
|
|
86
|
+
if (input.display && input.display.length) {
|
|
87
|
+
const f = filtrex.compileExpression(input.display.replace(/\'/g, '"'), filtrexOptions)
|
|
88
|
+
return function DisplayConditionally (data) {
|
|
89
|
+
const inputObj = {}
|
|
90
|
+
data.inputs.filter(input => input.name).forEach(input => {
|
|
91
|
+
inputObj[input.name] = input.value
|
|
92
|
+
})
|
|
93
|
+
return f(inputObj)
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
return function DisplayAlways () {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Determine a container for Vue app
|
|
103
|
+
const container = env.params.container
|
|
104
|
+
? (typeof env.params.container === 'string')
|
|
105
|
+
? document.querySelector(env.params.container)
|
|
106
|
+
: env.params.container
|
|
107
|
+
: document.body
|
|
108
|
+
|
|
109
|
+
// Determine a template and GUI framework
|
|
110
|
+
const framework = (env.schema.design && typeof env.schema.design.framework !== 'undefined')
|
|
111
|
+
? env.schema.design.framework
|
|
112
|
+
: 'bulma'
|
|
113
|
+
|
|
114
|
+
let template
|
|
115
|
+
let render
|
|
116
|
+
if (
|
|
117
|
+
env.schema.design
|
|
118
|
+
&& env.schema.design.template
|
|
119
|
+
&& (
|
|
120
|
+
typeof env.schema.design.template === 'string'
|
|
121
|
+
|| env.schema.design.template === false
|
|
122
|
+
)
|
|
123
|
+
) {
|
|
124
|
+
template = env.schema.design.template
|
|
125
|
+
render = null
|
|
126
|
+
} else {
|
|
127
|
+
template = null //'<vue-app/>'
|
|
128
|
+
render = () => {
|
|
129
|
+
return h(components[framework].app)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
log('Initializing Vue app...')
|
|
134
|
+
const app = createApp({
|
|
135
|
+
template,
|
|
136
|
+
render,
|
|
137
|
+
data () {
|
|
138
|
+
return dataInit
|
|
139
|
+
},
|
|
140
|
+
watch: {
|
|
141
|
+
inputs: {
|
|
142
|
+
deep: true,
|
|
143
|
+
immediate: false,
|
|
144
|
+
handler (v) {
|
|
145
|
+
if (this.model.autorun) {
|
|
146
|
+
env.run()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
mounted () {
|
|
152
|
+
mountedCallback(container)
|
|
153
|
+
},
|
|
154
|
+
methods: {
|
|
155
|
+
display (index) {
|
|
156
|
+
const res = displayFunctions[index](this.$data)
|
|
157
|
+
return res
|
|
158
|
+
},
|
|
159
|
+
reset () {
|
|
160
|
+
resetInputs(this.inputs)
|
|
161
|
+
},
|
|
162
|
+
run () {
|
|
163
|
+
env.run()
|
|
164
|
+
},
|
|
165
|
+
notify (msg) {
|
|
166
|
+
env.notify(msg)
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
if (framework !== false) {
|
|
172
|
+
app.component('vue-app', components[framework].app)
|
|
173
|
+
app.component('vue-input', components[framework].input)
|
|
174
|
+
app.component('vue-output', components[framework].output)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Json viewer
|
|
178
|
+
app.use(JsonViewer)
|
|
179
|
+
|
|
180
|
+
// Load Vue framework if present
|
|
181
|
+
if (framework in window) {
|
|
182
|
+
app.use(window[framework])
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return app.mount(container) // After app.mount() it's not the same app
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { createVueApp }
|
package/src/overlay.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class Overlay {
|
|
2
|
+
constructor (parent) {
|
|
3
|
+
this.element = document.createElement('div')
|
|
4
|
+
this.element.id = 'overlay'
|
|
5
|
+
this.element.className = 'valign-wrapper'
|
|
6
|
+
this.element.innerHTML = `
|
|
7
|
+
<div class="center-align" style="width:100%">
|
|
8
|
+
<div class="preloader-wrapper small active">
|
|
9
|
+
<div class="spinner-layer spinner-green-only">
|
|
10
|
+
<div class="circle-clipper left">
|
|
11
|
+
<div class="circle"></div>
|
|
12
|
+
</div><div class="gap-patch">
|
|
13
|
+
<div class="circle"></div>
|
|
14
|
+
</div><div class="circle-clipper right">
|
|
15
|
+
<div class="circle"></div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
`
|
|
21
|
+
parent.appendChild(this.element)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
show () {
|
|
25
|
+
this.element.style.display = 'flex'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
hide () {
|
|
29
|
+
this.element.style.display = 'none'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = Overlay
|
package/src/worker.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function log () {
|
|
2
|
+
const args = Array.prototype.slice.call(arguments);
|
|
3
|
+
args.unshift('[Worker]')
|
|
4
|
+
console.log(args)
|
|
5
|
+
postMessage({_status: 'log', _log: args})
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
onmessage = function (e) {
|
|
9
|
+
var data = e.data
|
|
10
|
+
log('Received message of type:', typeof data)
|
|
11
|
+
|
|
12
|
+
if ((typeof data === 'object') && ((data.url) || (data.code))) {
|
|
13
|
+
/*
|
|
14
|
+
INIT MESSAGE
|
|
15
|
+
*/
|
|
16
|
+
let model = data
|
|
17
|
+
|
|
18
|
+
if (model.type === 'py') {
|
|
19
|
+
// Python with Pyodide
|
|
20
|
+
importScripts('https://pyodide.cdn.iodide.io/pyodide.js')
|
|
21
|
+
// Check when all's loaded
|
|
22
|
+
/*
|
|
23
|
+
TODO: Implement same loaded as port
|
|
24
|
+
let pyCheck = setInterval(() => {
|
|
25
|
+
if (self.pyodide && self.pyodide.runPythonAsync && this.model && this.model.length) {
|
|
26
|
+
console.log('[Worker] Pyodide lib and model loaded. Try running to preload all imports')
|
|
27
|
+
self.pyodide.runPythonAsync(this.model, () => {})
|
|
28
|
+
.then((res) => {
|
|
29
|
+
postMessage({_status: 'loaded'})
|
|
30
|
+
})
|
|
31
|
+
.catch((err) => {
|
|
32
|
+
postMessage({_status: 'loaded'})
|
|
33
|
+
})
|
|
34
|
+
clearInterval(pyCheck)
|
|
35
|
+
}
|
|
36
|
+
}, 500)
|
|
37
|
+
*/
|
|
38
|
+
} else {
|
|
39
|
+
// Javascript
|
|
40
|
+
this.container = model.container
|
|
41
|
+
|
|
42
|
+
if (model.code) {
|
|
43
|
+
log('Load code from schema')
|
|
44
|
+
// https://github.com/altbdoor/blob-worker/blob/master/blobWorker.js
|
|
45
|
+
importScripts(URL.createObjectURL(new Blob([model.code], { type: 'text/javascript' })))
|
|
46
|
+
} else if (model.url) {
|
|
47
|
+
log('Load script from URL:', model.url)
|
|
48
|
+
importScripts(model.url)
|
|
49
|
+
} else {
|
|
50
|
+
log('No script provided')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (model.type === 'class') {
|
|
54
|
+
log('[Worker] Init class')
|
|
55
|
+
// this.modelFunc = (new this[model.name]())[model.method || 'predict']
|
|
56
|
+
|
|
57
|
+
const modelClass = new this[model.name]()
|
|
58
|
+
this.modelFunc = (...a) => {
|
|
59
|
+
return modelClass[model.method || 'predict'](...a)
|
|
60
|
+
}
|
|
61
|
+
} else if (model.type === 'async-init') {
|
|
62
|
+
log('Init function with promise')
|
|
63
|
+
log(this[model.name])
|
|
64
|
+
this[model.name]().then((m) => {
|
|
65
|
+
log('Async init resolved: ', m)
|
|
66
|
+
this.modelFunc = m
|
|
67
|
+
})
|
|
68
|
+
} else {
|
|
69
|
+
log('Init function')
|
|
70
|
+
this.modelFunc = this[model.name]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
postMessage({_status: 'loaded'})
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
/*
|
|
77
|
+
CALL MESSAGE
|
|
78
|
+
*/
|
|
79
|
+
var res
|
|
80
|
+
if (typeof this.modelFunc === 'string') {
|
|
81
|
+
// Python model:
|
|
82
|
+
log('Calling Python model')
|
|
83
|
+
/*
|
|
84
|
+
const keys = Object.keys(data)
|
|
85
|
+
for (let key of keys) {
|
|
86
|
+
self[key] = data[key];
|
|
87
|
+
}
|
|
88
|
+
self.pyodide.runPythonAsync(this.model, () => {})
|
|
89
|
+
.then((res) => {
|
|
90
|
+
console.log('[Worker] Py results: ', typeof res, res)
|
|
91
|
+
postMessage(res)
|
|
92
|
+
})
|
|
93
|
+
.catch((err) => {
|
|
94
|
+
// self.postMessage({error : err.message});
|
|
95
|
+
})
|
|
96
|
+
*/
|
|
97
|
+
} else {
|
|
98
|
+
// JavaScript model
|
|
99
|
+
log('Calling JavaScript model')
|
|
100
|
+
if (this.container === 'args') {
|
|
101
|
+
log('Applying inputs as arguments')
|
|
102
|
+
res = this.modelFunc.apply(null, data)
|
|
103
|
+
} else {
|
|
104
|
+
// JS object or array
|
|
105
|
+
log('Applying inputs as object/array')
|
|
106
|
+
res = this.modelFunc(data, log)
|
|
107
|
+
}
|
|
108
|
+
// Return promise value or just regular value
|
|
109
|
+
// Promise.resolve handles both cases
|
|
110
|
+
Promise.resolve(res).then(r => { postMessage(r) })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<style lang="scss" scoped>
|
|
2
|
+
:deep(.jsee-app) {
|
|
3
|
+
@import "../node_modules/vue3-json-viewer/dist/index.css";
|
|
4
|
+
@import "../node_modules/bulma/sass/base/_all.sass";
|
|
5
|
+
@import "../node_modules/bulma/sass/utilities/_all.sass";
|
|
6
|
+
@import "../node_modules/bulma/sass/form/_all.sass";
|
|
7
|
+
@import "../node_modules/bulma/sass/grid/_all.sass";
|
|
8
|
+
@import "../node_modules/bulma/sass/elements/_all.sass";
|
|
9
|
+
@import "../node_modules/bulma/sass/helpers/_all.sass";
|
|
10
|
+
@import "../node_modules/bulma/sass/components/card.sass";
|
|
11
|
+
@import "../node_modules/bulma/sass/layout/section.sass";
|
|
12
|
+
|
|
13
|
+
font-family: sans-serif;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
<template>
|
|
17
|
+
<section>
|
|
18
|
+
<div class="jsee-app">
|
|
19
|
+
<div class="columns">
|
|
20
|
+
<div class="column is-full" id="$parent.model" v-if="$parent.model">
|
|
21
|
+
<h2 class="title is-2" v-if="$parent.model.title">{{ $parent.model.title }}</h2>
|
|
22
|
+
<p v-if="$parent.model.description">{{ $parent.model.description }}</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="columns">
|
|
26
|
+
<div class="column" v-bind:class="($parent.design && $parent.design.grid && ($parent.design.grid.length > 0)) ? 'is-' + $parent.design.grid[0] : ''">
|
|
27
|
+
<div class="card bordered">
|
|
28
|
+
<div class="card-content" id="inputs">
|
|
29
|
+
<ul>
|
|
30
|
+
<li v-for="(input, index) in $parent.inputs">
|
|
31
|
+
<vue-input v-bind:input="input" v-if="$parent.display(index)" v-on:inchange="$parent.run()"></vue-input>
|
|
32
|
+
</li>
|
|
33
|
+
</ul>
|
|
34
|
+
<pre v-if="$parent.model.debug">{{ $parent.inputs }}</pre>
|
|
35
|
+
<!-- <button class="button is-primary" id="run"><span>▸</span> Run</button> -->
|
|
36
|
+
</div>
|
|
37
|
+
<div class="card">
|
|
38
|
+
<footer class="card-footer">
|
|
39
|
+
<button v-on:click="$parent.reset()" class="button icon card-footer-item is-danger is-small">
|
|
40
|
+
<span class="has-text-danger-dark">✕</span>
|
|
41
|
+
<span> Reset</span>
|
|
42
|
+
</button>
|
|
43
|
+
<button v-on:click="$parent.run()" class="button icon card-footer-item is-primary is-small">
|
|
44
|
+
<span class="has-text-primary-dark">▸</span>
|
|
45
|
+
<span> Run</span>
|
|
46
|
+
</button>
|
|
47
|
+
</footer>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="column" id="outputs">
|
|
52
|
+
<div v-if="$parent.outputs">
|
|
53
|
+
<div v-for="(output, index) in $parent.outputs">
|
|
54
|
+
<vue-output v-bind:output="output" v-on:notification="$parent.notify($event)"></vue-output>
|
|
55
|
+
</div>
|
|
56
|
+
<pre v-if="$parent.model.debug">{{ $parent.outputs }}</pre>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
</template>
|
|
63
|
+
<script>
|
|
64
|
+
export default {}
|
|
65
|
+
</script>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="field" v-if="input.type == 'int' || input.type == 'float' || input.type == 'number'">
|
|
3
|
+
<label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
|
|
4
|
+
<div class="control">
|
|
5
|
+
<input
|
|
6
|
+
v-model="input.value"
|
|
7
|
+
v-bind:id="input.name"
|
|
8
|
+
v-bind:step="input.type == 'int' ? 1 : 0.001"
|
|
9
|
+
v-bind:placeholder="input.placeholder ? input.placeholder : input.name"
|
|
10
|
+
v-bind:min="input.min"
|
|
11
|
+
v-bind:max="input.max"
|
|
12
|
+
v-on:change="changeHandler"
|
|
13
|
+
class="input"
|
|
14
|
+
type="number"
|
|
15
|
+
>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="field" v-if="input.type == 'string' || input.type == 'color'">
|
|
20
|
+
<label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
|
|
21
|
+
<div class="control">
|
|
22
|
+
<input
|
|
23
|
+
v-model="input.value"
|
|
24
|
+
v-bind:id="input.name"
|
|
25
|
+
v-bind:placeholder="input.placeholder ? input.placeholder : input.name"
|
|
26
|
+
v-on:change="changeHandler"
|
|
27
|
+
class="input"
|
|
28
|
+
>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="field" v-if="input.type == 'text'">
|
|
33
|
+
<label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
|
|
34
|
+
<div class="control">
|
|
35
|
+
<textarea
|
|
36
|
+
v-model="input.value"
|
|
37
|
+
v-bind:id="input.name"
|
|
38
|
+
v-bind:placeholder="input.placeholder ? input.placeholder : input.name"
|
|
39
|
+
v-on:change="changeHandler"
|
|
40
|
+
class="textarea"
|
|
41
|
+
></textarea>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="field" v-if="input.type == 'checkbox' || input.type == 'bool'">
|
|
46
|
+
<div class="control">
|
|
47
|
+
<label class="checkbox is-size-7">
|
|
48
|
+
<input
|
|
49
|
+
v-model="input.value"
|
|
50
|
+
v-bind:id="input.name"
|
|
51
|
+
v-on:change="changeHandler"
|
|
52
|
+
type="checkbox"
|
|
53
|
+
>
|
|
54
|
+
{{ input.name }}
|
|
55
|
+
</label>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="field" v-if="input.type == 'categorical' || input.type == 'select'">
|
|
60
|
+
<label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
|
|
61
|
+
<div class="control">
|
|
62
|
+
<div class="select is-fullwidth">
|
|
63
|
+
<select
|
|
64
|
+
v-model="input.value"
|
|
65
|
+
v-bind:id="input.name"
|
|
66
|
+
v-on:change="changeHandler"
|
|
67
|
+
>
|
|
68
|
+
<option
|
|
69
|
+
v-for="(option, oi) in input.options"
|
|
70
|
+
>{{ option }}</option>
|
|
71
|
+
</select>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div class="field" v-if="input.type == 'file'">
|
|
77
|
+
<label v-bind:for="input.name" class="is-size-7">{{ input.name }}</label>
|
|
78
|
+
<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-icon">
|
|
89
|
+
<span v-if="input.file && input.file.name">✓</span>
|
|
90
|
+
<span v-else>↥</span>
|
|
91
|
+
</span>
|
|
92
|
+
<span class="file-label">
|
|
93
|
+
Choose a file…
|
|
94
|
+
</span>
|
|
95
|
+
</span>
|
|
96
|
+
<span class="file-name">
|
|
97
|
+
<span v-if="input.file && input.file.name">{{ input.file.name }}</span>
|
|
98
|
+
<span v-else>No file selected</span>
|
|
99
|
+
</span>
|
|
100
|
+
</label>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div class="field is-horizontal" v-if="input.type == 'group'">
|
|
106
|
+
<div class="field-body">
|
|
107
|
+
<vue-input v-for="(el, index) in input.elements" v-bind:input="el" ></vue-input>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<script>
|
|
113
|
+
export { component as default } from "./common-inputs.js"
|
|
114
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="card mb-5" v-if="output.value">
|
|
3
|
+
<header class="card-header">
|
|
4
|
+
<p class="card-header-title is-size-6" v-if="output.name">
|
|
5
|
+
{{ output.name }}
|
|
6
|
+
</p>
|
|
7
|
+
<p class="card-header-icon">
|
|
8
|
+
<button class="button icon is-small" v-on:click="save()">Save</button>
|
|
9
|
+
<button class="button icon is-small" v-on:click="copy()">Copy</button>
|
|
10
|
+
</p>
|
|
11
|
+
</header>
|
|
12
|
+
<div class="card-content">
|
|
13
|
+
<div class="content" v-if="output.type == 'svg'">
|
|
14
|
+
<div v-html="output.value"></div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="content" v-else-if="output.type == 'object'">
|
|
17
|
+
<json-viewer :value="output.value" copyable sort />
|
|
18
|
+
</div>
|
|
19
|
+
<div class="content" v-if="output.type == 'code'">
|
|
20
|
+
<pre>{{ output.value }}</pre>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="content" v-else>
|
|
23
|
+
<pre>{{ output.value }}</pre>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<!--
|
|
27
|
+
<footer class="card-footer" v-if="output.filename">
|
|
28
|
+
<a v-on:click="save()" class="card-footer-item">Save</a>
|
|
29
|
+
<a v-on:click="copy()" class="card-footer-item">Copy</a>
|
|
30
|
+
</footer>
|
|
31
|
+
-->
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script>
|
|
36
|
+
export { component as default } from "./common-outputs.js"
|
|
37
|
+
</script>
|
|
38
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const FileReader = window['FileReader']
|
|
2
|
+
|
|
3
|
+
const component = {
|
|
4
|
+
props: ['input'],
|
|
5
|
+
emits: ['inchange'],
|
|
6
|
+
methods: {
|
|
7
|
+
changeHandler () {
|
|
8
|
+
if (this.input.reactive) {
|
|
9
|
+
this.$emit('inchange')
|
|
10
|
+
}
|
|
11
|
+
},
|
|
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
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { component }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { saveAs } from 'file-saver'
|
|
2
|
+
|
|
3
|
+
const Blob = window['Blob']
|
|
4
|
+
|
|
5
|
+
function stringify (v) {
|
|
6
|
+
return typeof v === 'string'
|
|
7
|
+
? v
|
|
8
|
+
: JSON.stringify(v)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const component = {
|
|
12
|
+
props: ['output'],
|
|
13
|
+
emits: ['notification'],
|
|
14
|
+
methods: {
|
|
15
|
+
save () {
|
|
16
|
+
// Prepare filename
|
|
17
|
+
let filename
|
|
18
|
+
if (this.output.filename) {
|
|
19
|
+
filename = this.output.filename
|
|
20
|
+
} else {
|
|
21
|
+
let name = this.output.name ? this.output.name : 'output'
|
|
22
|
+
let extension = this.output.type === 'svg' ? 'svg': 'txt'
|
|
23
|
+
filename = name + '.' + extension
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Prepare blob
|
|
27
|
+
let value = stringify(this.output.value)
|
|
28
|
+
let blob = new Blob([value], {type: 'text/plain;charset=utf-8'})
|
|
29
|
+
saveAs(blob, filename)
|
|
30
|
+
},
|
|
31
|
+
copy () {
|
|
32
|
+
let value = stringify(this.output.value)
|
|
33
|
+
navigator.clipboard.writeText(value)
|
|
34
|
+
this.$emit('notification', 'Copied')
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { component }
|