@jscad/web 2.5.5 → 2.5.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/CHANGELOG.md +38 -0
- package/README.md +7 -7
- package/css/codemirror.css +10 -15
- package/css/demo.css +32 -34
- package/css/style.css +0 -124
- package/demo.html +3 -2
- package/dist/jscad-web.min.js +1362 -1341
- package/examples/CHANGELOG.md +8 -0
- package/examples/README.md +4 -4
- package/examples/package.json +2 -1
- package/locales/de.json +0 -6
- package/locales/en.json +0 -6
- package/locales/fr.json +0 -6
- package/locales/hr.json +0 -6
- package/locales/ja.json +0 -6
- package/package.json +13 -12
- package/src/ui/flow/design.js +0 -3
- package/src/ui/views/editor.js +116 -195
- package/src/ui/views/main.js +1 -1
- package/src/ui/views/options.js +0 -14
- package/src/ui/views/toolbar.js +3 -3
- package/css/big.css +0 -107
- package/css/editor.css +0 -428
- package/css/min.css +0 -107
- package/css/openjscad.css +0 -135
- package/imgs/closeButton.png +0 -0
- package/imgs/editHandle.png +0 -0
- package/imgs/editHandleIn.png +0 -0
- package/imgs/editHandleOut.png +0 -0
- package/imgs/menuHandle.png +0 -0
- package/imgs/menuHandleHD.png +0 -0
- package/imgs/menuHandleHU.png +0 -0
- package/imgs/menuHandleVL.png +0 -0
- package/imgs/menuHandleVLIn.png +0 -0
- package/imgs/menuHandleVLOut.png +0 -0
- package/imgs/menuHandleVR.png +0 -0
- package/imgs/misc.svg +0 -1038
- package/imgs/title.png +0 -0
- package/src/sideEffects/localFs/walkFileTree.js +0 -224
- package/src/ui/views/editor2.js +0 -130
package/imgs/title.png
DELETED
|
Binary file
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
const { formats } = require('@jscad/io/formats')
|
|
2
|
-
|
|
3
|
-
const getFileExtensionFromString = require('@jscad/core/utils/getFileExtensionFromString')
|
|
4
|
-
|
|
5
|
-
const { flatten } = require('@jscad/array-utils')
|
|
6
|
-
|
|
7
|
-
const binaryMimetypes = {
|
|
8
|
-
bmp: 'image/bmp',
|
|
9
|
-
gif: 'image/gif',
|
|
10
|
-
jpg: 'image/jpeg',
|
|
11
|
-
jpeg: 'image/jpeg',
|
|
12
|
-
png: 'image/png',
|
|
13
|
-
tif: 'image/tiff',
|
|
14
|
-
tiff: 'image/tiff',
|
|
15
|
-
|
|
16
|
-
otc: 'font/otf',
|
|
17
|
-
otf: 'font/otf',
|
|
18
|
-
ttc: 'font/ttf',
|
|
19
|
-
ttf: 'font/ttf',
|
|
20
|
-
woff: 'font/woff',
|
|
21
|
-
woff2: 'font/woff',
|
|
22
|
-
|
|
23
|
-
stl: 'application/sla'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/*
|
|
27
|
-
* Read the given file asyncronously via a promise.
|
|
28
|
-
* @param {File} file
|
|
29
|
-
* @param {Object} fileMeta - meta information about file
|
|
30
|
-
* @returns {Promise} new promise to read and convert the file
|
|
31
|
-
*/
|
|
32
|
-
const readFileAsync = (file, fileMeta) => {
|
|
33
|
-
const fullPath = fileMeta && fileMeta.fullPath ? fileMeta.fullPath : ''
|
|
34
|
-
const ext = getFileExtensionFromString(file.name)
|
|
35
|
-
const mimetype = file.mimetype
|
|
36
|
-
|
|
37
|
-
const promiseReader = new Promise((resolve, reject) => {
|
|
38
|
-
const reader = new FileReader()
|
|
39
|
-
|
|
40
|
-
reader.onload = (event) => {
|
|
41
|
-
const result = event.target.result
|
|
42
|
-
if (result.byteLength) {
|
|
43
|
-
resolve({ name: file.name, ext, fullPath, mimetype, source: result })
|
|
44
|
-
} else if (typeof result === 'string') {
|
|
45
|
-
resolve({ name: file.name, ext, fullPath, mimetype, source: result })
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
reader.onerror = (event) => {
|
|
50
|
-
reject(new Error(`Failed to load file: ${fullPath} [${reader.error}]`))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (binaryMimetypes[ext]) {
|
|
54
|
-
reader.readAsArrayBuffer(file) // result is ArrayBuffer
|
|
55
|
-
} else {
|
|
56
|
-
reader.readAsText(file) // result is String
|
|
57
|
-
}
|
|
58
|
-
// readAsDataURL() - result is data URI
|
|
59
|
-
// readAsBinaryString() - result is raw binary data (OLD)
|
|
60
|
-
})
|
|
61
|
-
return promiseReader
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// all known formats are supported
|
|
65
|
-
const isSupportedFormat = (file) => {
|
|
66
|
-
const ext = getFileExtensionFromString(file.name)
|
|
67
|
-
const mimetype = formats[ext] ? formats[ext].mimetype : binaryMimetypes[ext]
|
|
68
|
-
file.mimetype = file.type && file.type.length ? file.type : mimetype
|
|
69
|
-
return file.mimetype && file.mimetype.length
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const pseudoArraytoArray = (pseudoArray) => {
|
|
73
|
-
const array = []
|
|
74
|
-
for (let i = 0; i < pseudoArray.length; i++) {
|
|
75
|
-
const item = pseudoArray[i]
|
|
76
|
-
if (item) array.push(item.webkitGetAsEntry ? item.webkitGetAsEntry() : item)
|
|
77
|
-
}
|
|
78
|
-
return array
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const isEmpty = (x) => x !== null && x !== undefined // skip empty items
|
|
82
|
-
|
|
83
|
-
/*
|
|
84
|
-
* Process the given directory entries into a series of promises
|
|
85
|
-
* @returns {Promise} one promise to resolve them all
|
|
86
|
-
*/
|
|
87
|
-
const processEntries = (items) => {
|
|
88
|
-
const results = pseudoArraytoArray(items.filter(isEmpty))
|
|
89
|
-
.filter(isEmpty) // skip empty items
|
|
90
|
-
.reduce((result, item) => {
|
|
91
|
-
if (item.name.startsWith('.')) return result // skip hidden files and directories
|
|
92
|
-
if (item.isFile) {
|
|
93
|
-
result.push(processFile(item))
|
|
94
|
-
} else if (item.isDirectory) {
|
|
95
|
-
result.push(processDirectory(item))
|
|
96
|
-
} else if (item instanceof File) {
|
|
97
|
-
const fullPath = item.webkitRelativePath ? item.webkitRelativePath : undefined
|
|
98
|
-
const file = isSupportedFormat(item) ? readFileAsync(item, { fullPath }) : undefined
|
|
99
|
-
if (!file) {
|
|
100
|
-
throw new Error('Unsuported format (or folder in Safari)!')
|
|
101
|
-
}
|
|
102
|
-
result.push(file)
|
|
103
|
-
}
|
|
104
|
-
return result
|
|
105
|
-
}, [])
|
|
106
|
-
|
|
107
|
-
return Promise.all(results)
|
|
108
|
-
.then((x) => x.filter((x) => x !== null && x !== undefined))
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/*
|
|
112
|
-
* Process the given file
|
|
113
|
-
* @param {FileSytemFileEntry} file
|
|
114
|
-
* @returns {Promise} new promise to read and process the file
|
|
115
|
-
*/
|
|
116
|
-
const processFile = (fileItem) => {
|
|
117
|
-
const promiseFile = new Promise((resolve, reject) => {
|
|
118
|
-
fileItem.file(
|
|
119
|
-
(fileData) => {
|
|
120
|
-
isSupportedFormat(fileData) ? resolve(readFileAsync(fileData, fileItem)) : resolve(undefined)
|
|
121
|
-
},
|
|
122
|
-
(fileError) => {
|
|
123
|
-
const message = `${fileError.message} (${fileError.code})`
|
|
124
|
-
reject(new Error(`Failed to load file: ${fileItem.fullPath} [${message}]`))
|
|
125
|
-
}
|
|
126
|
-
)
|
|
127
|
-
})
|
|
128
|
-
return promiseFile
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/*
|
|
132
|
-
* Process the given directory
|
|
133
|
-
* @param {FileSytemDirectoryEntry} directory
|
|
134
|
-
* @returns {Promise} new promise to read and process the directory
|
|
135
|
-
*/
|
|
136
|
-
const processDirectory = (directory) => {
|
|
137
|
-
const promiseDirectory = new Promise((resolve, reject) => {
|
|
138
|
-
if (directory.entries) {
|
|
139
|
-
directory.entries.length ? processEntries(directory.entries).then(resolve) : resolve([])
|
|
140
|
-
} else {
|
|
141
|
-
const reader = directory.createReader()
|
|
142
|
-
reader.readEntries((entries) => {
|
|
143
|
-
entries.length ? processEntries(entries).then(resolve) : resolve([])
|
|
144
|
-
}, reject)
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
.then(flatten)
|
|
148
|
-
.then((children) => {
|
|
149
|
-
children = children.map((child) => {
|
|
150
|
-
if (!child.fullPath.startsWith('/')) {
|
|
151
|
-
child.fullPath = directory.fullPath + '/' + child.name
|
|
152
|
-
}
|
|
153
|
-
return child
|
|
154
|
-
})
|
|
155
|
-
return { children, fullPath: directory.fullPath, name: directory.name }
|
|
156
|
-
})
|
|
157
|
-
return promiseDirectory
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/*
|
|
161
|
-
* Transform the flat list of files (from HTML input) to a heiarchy of files (from drag-n-drop).
|
|
162
|
-
*/
|
|
163
|
-
const transformFileList = (fileList) => {
|
|
164
|
-
const path = require('path')
|
|
165
|
-
|
|
166
|
-
let rootDirectory
|
|
167
|
-
const directories = new Map()
|
|
168
|
-
|
|
169
|
-
const addDirectory = (fullPath, name) => {
|
|
170
|
-
if (!directories.has(fullPath)) {
|
|
171
|
-
const directory = { fullPath, name, isDirectory: true, entries: [] }
|
|
172
|
-
if (!rootDirectory) rootDirectory = directory
|
|
173
|
-
directories.set(fullPath, directory)
|
|
174
|
-
|
|
175
|
-
const pathParts = fullPath.split(path.sep)
|
|
176
|
-
if (pathParts.length > 1) {
|
|
177
|
-
const basePath = path.sep + path.join(...pathParts.slice(0, -1))
|
|
178
|
-
const baseDir = directories.get(basePath)
|
|
179
|
-
if (baseDir) baseDir.entries.push(directory)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (let i = 0; i < fileList.length; i++) {
|
|
185
|
-
const file = fileList[i]
|
|
186
|
-
const filePath = file.webkitRelativePath ? file.webkitRelativePath : file.name
|
|
187
|
-
const fileParts = filePath.split(path.sep)
|
|
188
|
-
|
|
189
|
-
const hidden = fileParts.reduce((acc, part) => acc || part.startsWith('.'), false)
|
|
190
|
-
if (hidden) continue
|
|
191
|
-
|
|
192
|
-
if (!isSupportedFormat(file)) continue
|
|
193
|
-
|
|
194
|
-
const dirParts = fileParts.slice(0, -1)
|
|
195
|
-
for (let i = 0; i < dirParts.length; i++) {
|
|
196
|
-
const dirPath = path.sep + path.join(...dirParts.slice(0, i + 1))
|
|
197
|
-
addDirectory(dirPath, dirParts[i])
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const dirPath = path.sep + path.join(...dirParts)
|
|
201
|
-
|
|
202
|
-
const directory = directories.get(dirPath)
|
|
203
|
-
if (directory) directory.entries.push(file)
|
|
204
|
-
}
|
|
205
|
-
directories.clear()
|
|
206
|
-
return [rootDirectory]
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// this is the core of the drag'n'drop:
|
|
210
|
-
// 1) walk the tree
|
|
211
|
-
// 2) read the files (readFileAsync)
|
|
212
|
-
// 3) return a flattened list of promises containing all file entries
|
|
213
|
-
const walkFileTree = (filelist) => {
|
|
214
|
-
let items = filelist
|
|
215
|
-
if (filelist.length && (filelist[0] instanceof File)) {
|
|
216
|
-
// transform the flat list of File entries
|
|
217
|
-
items = transformFileList(filelist)
|
|
218
|
-
}
|
|
219
|
-
return processEntries(items)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
module.exports = {
|
|
223
|
-
walkFileTree
|
|
224
|
-
}
|
package/src/ui/views/editor2.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
const html = require('nanohtml')
|
|
2
|
-
|
|
3
|
-
const CodeMirror = require('codemirror')
|
|
4
|
-
require('codemirror/mode/javascript/javascript')
|
|
5
|
-
require('codemirror/addon/hint/javascript-hint')
|
|
6
|
-
|
|
7
|
-
const editorOptions = {
|
|
8
|
-
mode: 'javascript',
|
|
9
|
-
indentUnit: 2,
|
|
10
|
-
smartIndent: false,
|
|
11
|
-
indentWithTabs: false,
|
|
12
|
-
lineNumbers: true,
|
|
13
|
-
autofocus: true
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
let editor
|
|
17
|
-
let wrapper
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
* Create a file tree from the contents of the editor
|
|
21
|
-
*/
|
|
22
|
-
const createFileTree = (editor) => {
|
|
23
|
-
const source = editor.getValue()
|
|
24
|
-
if (source && source.length > 0) {
|
|
25
|
-
return [{ ext: 'js', fullPath: '/changes.js', mimetype: 'javascript', name: 'changes.js', source }]
|
|
26
|
-
}
|
|
27
|
-
return null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/*
|
|
31
|
-
* Create a HTML wrapper for the editor (single instance)
|
|
32
|
-
*/
|
|
33
|
-
const createWrapper = (state, callbackToStream) => {
|
|
34
|
-
if (!wrapper) {
|
|
35
|
-
wrapper = html`
|
|
36
|
-
<section class='popup-menu' id='editor' key='editor' style='visibility:${state.activeTool === 'editor' ? 'visible' : 'hidden'}'>
|
|
37
|
-
<textarea></textarea>
|
|
38
|
-
</section>
|
|
39
|
-
`
|
|
40
|
-
wrapper.onkeydown = (e) => e.stopPropagation()
|
|
41
|
-
wrapper.onkeyup = (e) => e.stopPropagation()
|
|
42
|
-
|
|
43
|
-
// and add the editor
|
|
44
|
-
editor = CodeMirror.fromTextArea(wrapper.firstChild, editorOptions)
|
|
45
|
-
|
|
46
|
-
editor.setOption('extraKeys', {
|
|
47
|
-
Tab: (cm) => {
|
|
48
|
-
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ')
|
|
49
|
-
cm.replaceSelection(spaces)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// inject style sheet for the editor
|
|
54
|
-
const head = document.getElementsByTagName('HEAD')[0]
|
|
55
|
-
const link = document.createElement('LINK')
|
|
56
|
-
link.rel = 'stylesheet'
|
|
57
|
-
link.href = './css/codemirror.css'
|
|
58
|
-
head.appendChild(link)
|
|
59
|
-
}
|
|
60
|
-
return wrapper
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/*
|
|
64
|
-
* Create the editor wrapper for handling changes to file contents.
|
|
65
|
-
*
|
|
66
|
-
* Note: Only the contents of a single file are loaded into the editor. No projects.
|
|
67
|
-
* Note: Only the contents of javascript files are loaded into the editor. No external formats.
|
|
68
|
-
*/
|
|
69
|
-
const editorWrapper = (state, editorCallbackToStream) => {
|
|
70
|
-
const el = createWrapper(state, editorCallbackToStream)
|
|
71
|
-
|
|
72
|
-
// and adjust the state
|
|
73
|
-
if (state.activeTool === 'editor') {
|
|
74
|
-
el.style.visibility = 'visible'
|
|
75
|
-
el.focus()
|
|
76
|
-
|
|
77
|
-
let compileShortcut = state.shortcuts.find((shortcut) => shortcut.args === 'reevaluate')
|
|
78
|
-
if (!compileShortcut) compileShortcut = { args: 'Shift-Enter' }
|
|
79
|
-
let key = compileShortcut.key.toUpperCase()
|
|
80
|
-
// can you say PAIN? codemirror has very specific control prefixes!
|
|
81
|
-
key = key.replace(/enter/i, 'Enter')
|
|
82
|
-
key = key.replace(/alt[+-]/i, 'Alt-')
|
|
83
|
-
key = key.replace(/cmd[+-]/i, 'Cmd-')
|
|
84
|
-
key = key.replace(/control[+-]/i, 'Ctrl-')
|
|
85
|
-
key = key.replace(/shift[+-]/i, 'Shift-')
|
|
86
|
-
|
|
87
|
-
const extraKeys = {
|
|
88
|
-
Tab: (cm) => {
|
|
89
|
-
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ')
|
|
90
|
-
cm.replaceSelection(spaces)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
extraKeys[key] = (cm) => {
|
|
94
|
-
const fileTree = createFileTree(cm)
|
|
95
|
-
if (fileTree) editorCallbackToStream.callback({ type: 'read', id: 'loadRemote', data: fileTree })
|
|
96
|
-
}
|
|
97
|
-
editor.setOption('extraKeys', extraKeys)
|
|
98
|
-
|
|
99
|
-
editor.focus()
|
|
100
|
-
editor.scrollIntoView({ line: 0, ch: 0 })
|
|
101
|
-
} else {
|
|
102
|
-
el.style.visibility = 'hidden'
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// and adjust the contents if any
|
|
106
|
-
if (state.design && state.design.filesAndFolders) {
|
|
107
|
-
if (state.design.filesAndFolders.length === 1) {
|
|
108
|
-
const file0 = state.design.filesAndFolders[0]
|
|
109
|
-
let source = file0.source ? file0.source : ''
|
|
110
|
-
if (file0.mimetype) {
|
|
111
|
-
if (file0.mimetype.indexOf('javascript') < 0) source = '// imported from external format'
|
|
112
|
-
} else {
|
|
113
|
-
source = '// imported from project'
|
|
114
|
-
}
|
|
115
|
-
const prevsource = editor.getValue()
|
|
116
|
-
if (source !== prevsource) {
|
|
117
|
-
editor.focus()
|
|
118
|
-
editor.setValue(source)
|
|
119
|
-
editor.setCursor(0, 0)
|
|
120
|
-
editor.refresh()
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return el
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = {
|
|
129
|
-
editorWrapper
|
|
130
|
-
}
|