Skip to content

Commit

Permalink
looks good
Browse files Browse the repository at this point in the history
  • Loading branch information
hrgdavor committed Jan 27, 2024
1 parent 3fa9e1e commit c42a448
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 73 deletions.
167 changes: 100 additions & 67 deletions apps/jscad-web/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { addToCache, extractEntries, fileDropped, getFile, registerServiceWorker } from '@jscadui/fs-provider'
import {
addToCache,
clearFs,
extractEntries,
fileDropped,
getFile,
getFileContent,
registerServiceWorker,
} from '@jscadui/fs-provider'
import { Gizmo } from '@jscadui/html-gizmo'
import { OrbitControl } from '@jscadui/orbit'
import { genParams } from '@jscadui/params'
Expand Down Expand Up @@ -45,20 +53,33 @@ ctrl.oninput = state => updateFromCtrl(state)
gizmo.oncam = ({ cam }) => ctrl.animateToCommonCamera(cam)

let sw
async function resetFileRefs(){
editor.setFiles([])
saveMap = {}
if(sw){
delete sw.fileToRun
await clearFs(sw)
}
}

async function initFs() {
const getFileWrapper = (path, sw) => {
const file = getFile(path, sw)
const file = getFileContent(path, sw)
// notify editor of active files
file.then(() => editor.setFiles(sw.filesToCheck))
return file
}
let scope = document.location.pathname
sw = await registerServiceWorker(`bundle.fs-serviceworker.js?prefix=${scope}swfs/`, getFileWrapper, {scope, prefix:scope+'swfs/'})
sw = await registerServiceWorker(`bundle.fs-serviceworker.js?prefix=${scope}swfs/`, getFileWrapper, {
scope,
prefix: scope + 'swfs/',
})
sw.defProjectName = 'jscad'
sw.onfileschange = files => {
sendNotify('clearFileCache', { files })
if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base })
}
sw.getFile = path => getFile(path, sw)
}
const dropModal = byId('dropModal')
const showDrop = show => {
Expand All @@ -68,19 +89,21 @@ const showDrop = show => {
document.body.ondrop = async ev => {
try {
ev.preventDefault()
let files = extractEntries(ev.dataTransfer)
let files = await extractEntries(ev.dataTransfer)
if (!files.length) return {}

await resetFileRefs()
if (!sw) await initFs()
showDrop(false)
sendCmd('clearTempCache', {})
saveMap = {}
const { alias, script } = await fileDropped(sw, files)
projectName = sw.projectName
if (alias.length) {
sendNotify('init', { alias })
}
runScript({ url: sw.fileToRun, base: sw.base })
editor.setSource(script, sw.fileToRun)
let url = sw.fileToRun
runScript({ url, base: sw.base })
editor.setSource(script, url)
editor.setFiles(sw.filesToCheck)
} catch (error) {
setError(error)
Expand Down Expand Up @@ -133,7 +156,7 @@ const exportModel = async (format, extension) => {

const worker = new Worker('./build/bundle.worker.js')
const handlers = {
entities: ({ entities, mainTime, convertTime}) => {
entities: ({ entities, mainTime, convertTime }) => {
if (!(entities instanceof Array)) entities = [entities]
viewState.setModel((model = entities))
console.log('Main execution:', mainTime?.toFixed(2), ', jscad mesh -> gl:', convertTime?.toFixed(2))
Expand All @@ -147,11 +170,11 @@ let jobs = 0
let firstJobTimer
async function sendCmdAndSpin(method, params) {
jobs++
if(jobs === 1){
if (jobs === 1) {
// do not show spinner for fast renders
firstJobTimer = setTimeout(()=>{
firstJobTimer = setTimeout(() => {
spinner.style.display = 'block'
},300)
}, 300)
}
try {
return await sendCmd(method, params)
Expand All @@ -167,7 +190,8 @@ async function sendCmdAndSpin(method, params) {
}

sendCmdAndSpin('init', {
bundles: {// local bundled alias for common libs.
bundles: {
// local bundled alias for common libs.
'@jscad/modeling': toUrl('./build/bundle.jscad_modeling.js'),
'@jscad/io': toUrl('./build/bundle.jscad_io.js'),
},
Expand All @@ -180,33 +204,34 @@ sendCmdAndSpin('init', {
let working
let lastParams
const paramChangeCallback = async params => {
if(!working){
if (!working) {
lastParams = null
}else{
} else {
lastParams = params
return
}
working = true
let result
try{
try {
result = await sendCmdAndSpin('runMain', { params })
} finally{
} finally {
working = false
}
handlers.entities(result)
if(lastParams && lastParams != params) paramChangeCallback(lastParams)
if (lastParams && lastParams != params) paramChangeCallback(lastParams)
}

const runScript = async ({ script, url = './index.js', base = currentBase, root }) => {
const runScript = async ({ script, url = './jscad.model.js', base = currentBase, root }) => {
currentBase = base
loadDefault = false // don't load default model if something else was loaded
const result = await sendCmdAndSpin('runScript', { script, url, base, root })
genParams({ target: byId('paramsDiv'), params: result.def || {}, callback: paramChangeCallback })
handlers.entities(result)
}

const loadExample = (source, base=appBase) => {
editor.setSource(source)
const loadExample = async (source, base = appBase) => {
await resetFileRefs()
editor.setSource(source, base)
runScript({ script: source, base })
}

Expand All @@ -215,57 +240,65 @@ engine.init().then(viewer => {
viewState.setEngine(viewer)
})

const saveMap = {}
let saveMap = {}

editor.init(defaultCode, async (script, path) => {
if (sw && sw.fileToRun) {
await addToCache(sw.cache, path, script)
// imported script will be also cached by require/import implementation
// it is expected if multiple files require same file/module that first time it is loaded
// but for others resolved module is returned
// if not cleared by calling clearFileCache, require will not try to reload the file
await sendCmd('clearFileCache', { files: [path] })
if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base })
} else {
runScript({ script })
}
},async (script, path) => {
console.log('save file', path)
let fileHandle = saveMap[path]
if(!fileHandle){
const opts = {
excludeAcceptAllOption: true,
types: [
{
description: "Javascript",
accept: { "application/javascript": [".js"] },
},
],
editor.init(
defaultCode,
async (script, path) => {
if (sw && sw.fileToRun) {
await addToCache(sw.cache, path, script)
// imported script will be also cached by require/import implementation
// it is expected if multiple files require same file/module that first time it is loaded
// but for others resolved module is returned
// if not cleared by calling clearFileCache, require will not try to reload the file
await sendCmd('clearFileCache', { files: [path] })
if (sw.fileToRun) runScript({ url: sw.fileToRun, base: sw.base })
} else {
runScript({ script })
}
saveMap[path] = fileHandle = await globalThis.showSaveFilePicker?.(opts)
}
if(fileHandle){
saveMap[path] = fileHandle
const writable = await fileHandle.createWritable()
await writable.write(script)
await writable.close()
console.log('fileHandle', fileHandle)
}

})
},
async (script, path) => {
console.log('save file', path)
let pathArr = path.split('/')
let fileHandle = (await sw?.getFile(path))?.fileHandle
if(!fileHandle) fileHandle = saveMap[path]
if (!fileHandle) {
const opts = {
suggestedName: pathArr[pathArr.length - 1],
excludeAcceptAllOption: true,
types: [
{
description: 'Javascript',
accept: { 'application/javascript': ['.js'] },
},
],
}
saveMap[path] = fileHandle = await globalThis.showSaveFilePicker?.(opts)
}
if (fileHandle) {
saveMap[path] = fileHandle
const writable = await fileHandle.createWritable()
await writable.write(script)
await writable.close()
}
},
)
menu.init(loadExample)
welcome.init()
remote.init((script, url) => {
// run remote script
editor.setSource(script)
runScript({ script, base:url })
welcome.dismiss()
}, (err) => {
// show remote script error
loadDefault = false
setError(err)
welcome.dismiss()
})
remote.init(
(script, url) => {
// run remote script
editor.setSource(script, url)
runScript({ script, base: url })
welcome.dismiss()
},
err => {
// show remote script error
loadDefault = false
setError(err)
welcome.dismiss()
},
)
exporter.init(exportModel)

try {
Expand Down
4 changes: 2 additions & 2 deletions apps/jscad-web/src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const init = (defaultCode, fn, _saveFn) => {
],
parent: editorDiv,
})
setSource(defaultCode)
setSource(defaultCode, 'jscad.example.js')

// Initialize drawer action
drawer.init()
Expand Down Expand Up @@ -88,7 +88,7 @@ export const setSource = (source, path = '/index.js') => {

export const setFiles = (files) => {
const editorFiles = document.getElementById('editor-files')
if (files.length === 1) {
if (files.length < 2) {
editorNav.classList.remove('visible')
} else {
// Update spinner
Expand Down
13 changes: 10 additions & 3 deletions packages/fs-provider/fs-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export function extractPathInfo(url) {
let ext = filename.substring(idx + 1)
return { url, filename, ext }
}

export const getFile = async (path, sw) => {
let arr = splitPath(path)
let match = await findFileInRoots(sw.roots, arr)
return await findFileInRoots(sw.roots, arr)
}

export const getFileContent = async (path, sw) => {
let match = await getFile(path, sw)
if (match) {
fileIsRequested(path, match, sw)
return readAsArrayBuffer(await filePromise(match))
Expand Down Expand Up @@ -75,7 +80,7 @@ export const addPreLoad = async (sw, path, ignoreMissing) => {
*/
export const registerServiceWorker = async (
workerScript,
_getFile = getFile,
_getFile = getFileContent,
{ prefix = '/swfs/', scope = '/' } = {},
) => {
if ('serviceWorker' in navigator) {
Expand Down Expand Up @@ -141,7 +146,7 @@ export const clearCache = async cache => {
;(await cache.keys()).forEach(key => cache.delete(key))
}

export const extractEntries = dt => {
export const extractEntries = async dt => {
let items = dt.items
if (!items) return []

Expand All @@ -156,6 +161,8 @@ export const extractEntries = dt => {
if (file.webkitGetAsEntry) file = file.webkitGetAsEntry()
else if (file.getAsEntry) file = file.getAsEntry()
else file = file.webkitGetAsFile()
// we need FileSystemHandle for writing, because old way using createWriter silently ignores writes
if(items[i].getAsFileSystemHandle) file.fileHandle = await items[i].getAsFileSystemHandle()
files.push(file)
}
}
Expand Down
18 changes: 17 additions & 1 deletion packages/fs-provider/src/FileEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,29 @@ export const readDir = async dir => {
let entries = []
let readEntries = await readEntriesPromise(directoryReader)
while (readEntries.length > 0) {
entries.push(...readEntries)
for(let i=0; i<readEntries.length; i++){
let entry = readEntries[i]
if(!entry) continue
addFileHandle(dir, entry)
entries.push(entry)
}
readEntries = await readEntriesPromise(directoryReader)
}
const fsDir = dir.fsPath && dir.fsPath !== '/' ? dir.fsPath + '/' : '/'
return entries.map(e => fileToFsEntry(e, fsDir))
}

async function addFileHandle(dir, entry){
// we may be reading too fast, wait for the promise to resolve
if(dir.fileHandle.then) await dir.fileHandle
let promise = dir.fileHandle[entry.isDirectory ? 'getDirectoryHandle':'getFileHandle' ](entry.name)
// in case of reading too fast, let those using fileHandle that it is not resolved yet
entry.fileHandle = promise
promise?.then(f=>{
entry.fileHandle = f
})
}

export const readEntriesPromise = async directoryReader => cbToPromise(directoryReader, 'readEntries')

export const filePromise = async entry =>{
Expand Down

0 comments on commit c42a448

Please sign in to comment.