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> | |