Spaces:
Running
Running
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | |
| <title>SqueakJS</title> | |
| <link rel="icon" type="image/png" href="squeakjs.png"> | |
| <link rel="stylesheet" href="../lib/gh-fork-ribbon.css"> | |
| <link rel="stylesheet" href="squeakjs.css"> | |
| <script type="module" src="../squeak.js"></script> | |
| <script> | |
| function runSqueak(imageName) { | |
| // Squeak.debugFiles = true; | |
| sqText.style.display = "none"; | |
| sqCanvas.style.display = "block"; | |
| SqueakJS.runSqueak(imageName, sqCanvas,{ | |
| spinner: sqSpinner, | |
| appName: imageName && imageName.replace(/.*\//, "").replace(/\.image$/, ""), | |
| onStart: function(vm, display, options) { | |
| // debugger | |
| // vm.breakOn("FileDirectory class>>activeDirectoryClass"); | |
| // vm.breakOnMessageNotUnderstood = true; | |
| }, | |
| onQuit: function(vm, display, options) { | |
| display.showBanner(SqueakJS.appName + " stopped."); | |
| setTimeout(function() { | |
| location.hash = ""; // show index page | |
| location.href = location.href.replace(/\?.*/, ""); | |
| }, 0); | |
| }, | |
| }); | |
| } | |
| function importItems(items) { | |
| var entries = []; | |
| for (var i = 0; i < items.length; i++) { | |
| var item = items[i]; | |
| if (item.kind === "file") { | |
| entries.push(item.webkitGetAsEntry()); | |
| } | |
| } | |
| importEntriesRecursively(entries, []); | |
| } | |
| function importEntriesRecursively(entries, todo) { | |
| var imageName = null; | |
| entries.forEach(function(entry) { | |
| if (entry.isFile) { | |
| var path = entry.fullPath; | |
| entry.file(function(file) { | |
| var reader = new FileReader(); | |
| reader.onload = function () { | |
| var buffer = this.result; | |
| console.log("Storing " + path + " (" + buffer.byteLength + " bytes)"); | |
| if (/.*image$/.test(path)) imageName = path; | |
| todo.push(path); | |
| Squeak.filePut(path, buffer, function success() { | |
| var index = todo.indexOf(path); | |
| todo.splice(index, 1); | |
| if (todo.length > 0) return; | |
| afterImport(imageName); | |
| }); | |
| } | |
| reader.onerror = function() { | |
| alert("Failed to read " + path); | |
| drop.style.borderColor = ""; | |
| } | |
| reader.readAsArrayBuffer(file); | |
| }); | |
| } else if (entry.isDirectory) { | |
| Squeak.dirCreate(entry.fullPath, true); | |
| var reader = entry.createReader(); | |
| reader.readEntries(function(entries) { | |
| importEntriesRecursively(entries, todo); | |
| }); | |
| } | |
| }); | |
| } | |
| function importFiles(files) { | |
| files = [].slice.call(files); | |
| var todo = files.length, | |
| imageName = null; | |
| files.forEach(function(f) { | |
| var reader = new FileReader(); | |
| reader.onload = function () { | |
| var buffer = this.result; | |
| console.log("Storing " + f.name + " (" + buffer.byteLength + " bytes)"); | |
| if (/.*image$/.test(f.name)) imageName = f.name; | |
| Squeak.filePut(f.name, buffer, function success() { | |
| if (--todo > 0) return; | |
| afterImport(imageName); | |
| }); | |
| } | |
| reader.onerror = function() { | |
| alert("Failed to read " + f.name); | |
| drop.style.borderColor = ""; | |
| } | |
| reader.readAsArrayBuffer(f); | |
| }); | |
| } | |
| function afterImport(imageName) { | |
| setTimeout(() => drop.style.borderColor = "", 500); | |
| if (!imageName) showFiles(); | |
| else { | |
| const hash = '#' + paramsForImage(imageName); | |
| if (location.hash !== hash) history.pushState({}, "", hash); | |
| runSqueak(imageName); | |
| } | |
| } | |
| function exportFile(a) { | |
| var path = Squeak.splitFilePath(a.innerText); | |
| Squeak.fileGet(path.fullname, function(buffer) { | |
| var blob = new Blob([buffer], {type: 'application/octet-stream'}); | |
| FileSaver_saveAs(blob, path.basename, true); | |
| }, alert); | |
| return false; | |
| } | |
| function paramsForImage(imageName) { | |
| var params = new URLSearchParams(location.hash.slice(1)); | |
| params.set("image", imageName); | |
| return params.toString(); | |
| } | |
| function showFiles() { | |
| var imgList = [], | |
| fileList = [], | |
| dirs = [''], | |
| nFiles = 0, | |
| nDirs = 0, | |
| nBytes = 0; | |
| while (dirs.length > 0) { | |
| var dir = dirs.pop(), | |
| entries = Squeak.dirList(dir); | |
| for (var f in entries) { | |
| var entry = entries[f], | |
| path = dir + '/' + f; | |
| if (!entry[3] && f.match(/\.image$/)) | |
| imgList.push('<li><a href="#' + paramsForImage(path) + '">' + path + '</a> (' + (entry[4]/1000000).toFixed(1) + ' MB)</li>'); | |
| if (entry[3]) { | |
| dirs.push(path); | |
| nDirs++; | |
| } else { | |
| nFiles++; | |
| nBytes += entry[4]; | |
| fileList.push('<li><a href="squeak:' + path + '" onclick="return exportFile(this)" target="_blank">' + path + '</a>' + | |
| (entry[4] >= 100000 ? ' (' + (entry[4]/1000000).toFixed(1) + ' MB)' | |
| : ' (' + (entry[4]/1000).toFixed(1) + ' KB)') + '</li>'); | |
| } | |
| }; | |
| } | |
| function fdir(s) { return s.replace(/<[^>]*>/gi,'').replace(/[^\/]*$/, ''); } | |
| function fsort(a, b) { return fdir(a).localeCompare(fdir(b)) || a.localeCompare(b); } | |
| if (fileList.length) { | |
| files.innerHTML = "<ul>" + fileList.sort(fsort).join("") + "</ul>"; | |
| filestats.innerHTML = nFiles + " files in " + nDirs + " directories, " + | |
| (nBytes/1000000).toFixed(1) + " MBytes total"; | |
| } | |
| if (imgList.length) images.innerHTML = "<p>Select a local image to run:</p><ul>" + imgList.sort(fsort).join("") + "</ul>"; | |
| else images.innerHTML = "<ul>[Once you have dropped local images to this page they will be listed here.]</ul>" | |
| } | |
| window.onhashchange = function() { | |
| window.location.reload(); | |
| } | |
| window.onload = function() { | |
| // if we have an image or zip in hash then we run Squeak with the options provided in the url | |
| if ((location.hash || location.search).match(/(\.image(\W|$)|\Wzip=)/)) { | |
| return runSqueak(); | |
| } | |
| // otherwise, we generate the text to display | |
| var links = document.getElementsByTagName("a"); | |
| for (var i = 0; i < links.length; i++) { | |
| var link = links[i], | |
| href = link.getAttribute("href"); | |
| if (href[0] === "#") { | |
| link.innerHTML = href; | |
| link.onclick = (function(href) { | |
| return function onclick() { window.location = href; } | |
| })(href); | |
| } | |
| } | |
| // show stored files and images. Update after fsck (which takes a while) | |
| showFiles(); | |
| setTimeout(function() { | |
| Squeak.fsck(function(stats) { if (stats.deleted) showFiles(); }); | |
| }, 0); | |
| // also, enable drag-and-drop even if no image loaded yet | |
| // (squeak.js will replace these when an image is running) | |
| document.body.ondragover = function(evt) { | |
| evt.preventDefault(); | |
| }; | |
| document.body.ondragenter = function(evt) { | |
| drop.style.borderColor = "#0E0"; | |
| evt.dataTransfer.dropEffect = "copy"; | |
| }; | |
| document.body.ondragleave = function(evt) { | |
| drop.style.borderColor = ""; | |
| }; | |
| document.body.ondrop = function(evt) { | |
| evt.preventDefault(); | |
| drop.style.borderColor = "#080"; | |
| importItems(evt.dataTransfer.items); | |
| return false; | |
| }; | |
| fileInput.onchange = function(evt) { | |
| drop.style.borderColor = "#0F0"; | |
| importFiles(evt.target.files); | |
| }; | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <div id="sqText"> | |
| <h1><img id="logo" src="squeakjs.png">SqueakJS</h1> | |
| <div class="github-fork-ribbon-wrapper right"> | |
| <div class="github-fork-ribbon"> | |
| <a href="https://github.com/codefrau/SqueakJS" target="_blank">Fork me on GitHub</a> | |
| </div> | |
| </div> | |
| SqueakJS is an HTML5 runtime engine for <a href="http://squeak.org/" target="_blank">Squeak</a> Smalltalk written in pure JavaScript by Vanessa Freudenberg. | |
| See the <a href="../" target="_blank">SqueakJS Project</a> page for more information. | |
| <h2>Run Squeak images from your local machine</h2> | |
| Use drag-and-drop to run a Squeak image from your machine: drop the image (perhaps together with a changes and sources file) into this page. | |
| <div id="images"></div> | |
| All images and related files are stored persistently in a database inside your browser. | |
| The box below shows the files that are currently in your database. Inside Squeak, you can use a FileList | |
| to manage them. | |
| <div id="drop">Drop Squeak images and other files here, or <input id="fileInput" type="file" multiple> | |
| <div id="files" class="filelist"></div> | |
| <div id="filestats"></div> | |
| </div> | |
| Clicking on a name in the box will export that file to your browser's downloads folder. | |
| <h2>Run Squeak images from the internet</h2> | |
| Construct a URL linking to this page and pass the image and options. | |
| <em>Beware that the server needs to allow script access via <a href="http://enable-cors.org/server.html" target="_blank">CORS</a>.</em> | |
| <br>Here are a few examples: | |
| <ul> | |
| <li>Squeak 1.13 (1996) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&files=[Squeak1.13u.image,Squeak1.13u.changes,SqueakV1.sources]&swapButtons=true">link</a></li> | |
| <li>Squeak 2.8 (2000) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&files=[Squeak2.8.image,Squeak2.8.changes,SqueakV2.sources]&swapButtons=true">link</a></li> | |
| <li>Squeak 3.8 (2006) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&files=[Squeak3.8.1-6747full.image,Squeak3.8.1-6747full.changes,SqueakV3.sources]&swapButtons=true">link</a></li> | |
| <li>Squeak 3.9 (2008) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&files=[Squeak3.9.1-final-7075.image,Squeak3.9.1-final-7075.changes,SqueakV39.sources]&swapButtons=true">link</a></li> | |
| <li>Pharo 1.0 (2010) <a href="#unix&zip=[https://files.pharo.org/image/10/latest.zip,https://files.pharo.org/sources/PharoV10.sources.zip]">link</a></li> | |
| <li>Squeak 4.5 (2014) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&files=[Squeak4.5-13680.image,Squeak4.5-13680.changes,SqueakV41.sources]">link</a></li> | |
| <li>Squeak 5.0 (2015) <a href="#url=https://freudenbergs.de/vanessa/squeakjs&zip=[Squeak5.0-15113.zip,SqueakV50.sources.zip]">link</a></li> | |
| <li>SpeechPlugin Demo (2016) <a href="#zip=https://www.hpi.uni-potsdam.de/hirschfeld/artefacts/squeakjs/SpeechPluginDemo.zip">link</a></li> | |
| <li>Squeak 6.0 64 bit (2022) <a href="#zip=https://files.squeak.org/6.0/Squeak6.0-22104-64bit/Squeak6.0-22104-64bit.zip&wizard=false">link</a></li> | |
| <li>Cuis 6.2 (2023) <a href="#highdpi&url=https://cdn.jsdelivr.net/gh/Cuis-Smalltalk/Cuis6-2@6f984e8c/CuisImage&files=[32BitImages/Cuis6.2-32.image,32BitImages/Cuis6.2-32.changes,Cuis6.2.sources]">link</a></li> | |
| <li>Squeak Trunk 6.1α (2024) <a href="#zip=https://files.squeak.org/6.1alpha/Squeak6.1alpha-22998-32bit/Squeak6.1alpha-22998-32bit.zip&wizard=false">link</a></li> | |
| <li>Squeak Trunk 6.1α (hi-res: save image after first load for faster startup) <a href="#highdpi&zip=https://files.squeak.org/6.1alpha/Squeak6.1alpha-22998-32bit/Squeak6.1alpha-22998-32bit.zip&wizard=false">link</a></li> | |
| </ul> | |
| On the first run these will be stored locally. Subsequent starts are much faster since there is no download. | |
| <h2>Run SqueakJS apps</h2> | |
| SqueakJS can be used to run Squeak apps. Here are some examples. | |
| Note how they differ when you resize the browser window—Etoys is scaled, | |
| whereas Scratch is resized. | |
| The apps are configured to use template files (e.g. example projects and artwork) | |
| that are loaded from a server on demand. | |
| <ul> | |
| <li><a href="../etoys/" target="_blank">Etoys</a></li> | |
| <li><a href="../scratch/" target="_blank">Scratch</a></li> | |
| <li>demo of the <a href="../demo/simple.html#document=JSBridge.st" target="_blank">JavaScript bridge</a> (not really an app)</li> | |
| <li><a href="https://codefrau.github.io/jasmine/" target="_blank">Croquet Jasmine</a> (2004)</li> | |
| </ul> | |
| On some mobile devices you can save these apps to the home screen, and run them like a real app. | |
| <h2>Use SqueakJS on your own website</h2> | |
| Instead of passing options by URL to this page, you can | |
| <a href="https://github.com/codefrau/SqueakJS">download SqueakJS</a> | |
| from GitHub and use it on your own website like this: | |
| <pre> | |
| <html> | |
| <head> | |
| <script src="squeak.js"></script> | |
| <script> | |
| window.onload = function() { | |
| SqueakJS.runSqueak("my.image", sqCanvas, { /*put options here*/ }); | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <canvas id="sqCanvas"></canvas> | |
| </body> | |
| </html> | |
| </pre> | |
| Options include display resolution, template file URLs, etc. | |
| For example usage take a look at the demo pages included in the GitHub repo: | |
| <a href="../etoys/" target="_blank">Etoys</a> | |
| (<a href="https://github.com/codefrau/SqueakJS/tree/master/etoys" target="_blank">source</a>) or | |
| <a href="../scratch/" target="_blank">Scratch</a> | |
| (<a href="https://github.com/codefrau/SqueakJS/tree/master/scratch" target="_blank">source</a>) | |
| <h2>Modify SqueakJS</h2> | |
| I am developing SqueakJS using “Lively”, a browser-based development environment for JavaScript inspired by Smalltalk. | |
| Instead of having to constantly reload the page after every source code change, | |
| I am executing Squeak in my <a href= "https://smalltalkzoo.thechm.org/HOPL-Squeak.html">Lively SqueakJS Debugger</a> | |
| and can change its code while it is running. | |
| That's why the SqueakJS source code has a somewhat unusual layout, it fits the Lively way of developing. | |
| You can still use a plain text editor if you feel that's simpler. | |
| <h2>Contribute to SqueakJS</h2> | |
| SqueakJS is free software (MIT license). | |
| You're very welcome to <a href="http://lists.squeakfoundation.org/mailman/listinfo/vm-dev">discuss</a>, | |
| <a href="https://github.com/codefrau/SqueakJS/issues">report bugs</a>, | |
| and <a href="https://github.com/codefrau/SqueakJS/">contribute code</a>. | |
| <p>Have fun! — Vanessa Freudenberg</p> | |
| </div> | |
| <canvas id="sqCanvas"></canvas> | |
| <div id="sqSpinner"><div></div></div> | |
| </body> | |
| </html> | |