@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/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
- }
@@ -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
- }