diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0baf83e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "python-envs.defaultEnvManager": "ms-python.python:system",
+ "python-envs.pythonProjects": []
+}
\ No newline at end of file
diff --git a/2/scratch.html b/2/scratch.html
new file mode 100644
index 0000000..e69de29
diff --git a/BarnyWarp Icon (Sturdy).svg b/BarnyWarp Icon (Sturdy).svg
new file mode 100644
index 0000000..63e129f
--- /dev/null
+++ b/BarnyWarp Icon (Sturdy).svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BarnyWarp Icon.svg b/BarnyWarp Icon.svg
new file mode 100644
index 0000000..aa31fb2
--- /dev/null
+++ b/BarnyWarp Icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Core/barny.js b/Core/barny.js
new file mode 100644
index 0000000..acd89c8
--- /dev/null
+++ b/Core/barny.js
@@ -0,0 +1,1385 @@
+import "./Core/settings.py";
+
+Object.extend(Squeak, {
+ vmPath: "/",
+ platformSubtype: "Browser",
+ osVersion: navigator.userAgent, // might want to parse
+ windowSystem: "HTML",
+});
+
+// UI namespace
+window.SqueakJS = {};
+
+//////////////////////////////////////////////////////////////////////////////
+// display & event setup
+//////////////////////////////////////////////////////////////////////////////
+
+function setupFullscreen(display, canvas, options) {
+ // Fullscreen can only be enabled in an event handler. So we check the
+ // fullscreen flag on every mouse down/up and keyboard event.
+ var box = canvas.parentElement,
+ fullscreenEvent = "fullscreenchange",
+ fullscreenElement = "fullscreenElement",
+ fullscreenEnabled = "fullscreenEnabled";
+
+ if (!box.requestFullscreen) {
+ [ // Fullscreen support is still very browser-dependent
+ {req: box.webkitRequestFullscreen, exit: document.webkitExitFullscreen,
+ evt: "webkitfullscreenchange", elem: "webkitFullscreenElement", enable: "webkitFullscreenEnabled"},
+ {req: box.mozRequestFullScreen, exit: document.mozCancelFullScreen,
+ evt: "mozfullscreenchange", elem: "mozFullScreenElement", enable: "mozFullScreenEnabled"},
+ {req: box.msRequestFullscreen, exit: document.msExitFullscreen,
+ evt: "MSFullscreenChange", elem: "msFullscreenElement", enable: "msFullscreenEnabled"},
+ ].forEach(function(browser) {
+ if (browser.req) {
+ box.requestFullscreen = browser.req;
+ document.exitFullscreen = browser.exit;
+ fullscreenEvent = browser.evt;
+ fullscreenElement = browser.elem;
+ fullscreenEnabled = browser.enable;
+ }
+ });
+ }
+
+ // If the user canceled fullscreen, turn off the fullscreen flag so
+ // we don't try to enable it again in the next event
+ function fullscreenChange(fullscreen) {
+ display.fullscreen = fullscreen;
+ var fullwindow = fullscreen || options.fullscreen;
+ box.style.background = fullwindow ? 'black' : '';
+ box.style.border = fullwindow ? 'none' : '';
+ box.style.borderRadius = fullwindow ? '0px' : '';
+ setTimeout(onresize, 0);
+ }
+
+ var checkFullscreen;
+
+ if (box.requestFullscreen) {
+ document.addEventListener(fullscreenEvent, function(){fullscreenChange(box == document[fullscreenElement]);});
+ checkFullscreen = function() {
+ if (document[fullscreenEnabled] && (box == document[fullscreenElement]) != display.fullscreen) {
+ if (display.fullscreen) box.requestFullscreen();
+ else document.exitFullscreen();
+ }
+ };
+ } else {
+ var isFullscreen = false;
+ checkFullscreen = function() {
+ if (isFullscreen != display.fullscreen) {
+ isFullscreen = display.fullscreen;
+ fullscreenChange(isFullscreen);
+ }
+ };
+ }
+
+ return checkFullscreen;
+}
+
+function recordModifiers(evt, display) {
+ var shiftPressed = evt.shiftKey,
+ ctrlPressed = evt.ctrlKey && !evt.altKey,
+ cmdPressed = (display.isMac ? evt.metaKey : evt.altKey && !evt.ctrlKey)
+ || display.cmdButtonTouched,
+ modifiers =
+ (shiftPressed ? Squeak.Keyboard_Shift : 0) +
+ (ctrlPressed ? Squeak.Keyboard_Ctrl : 0) +
+ (cmdPressed ? Squeak.Keyboard_Cmd : 0);
+ display.buttons = (display.buttons & ~Squeak.Keyboard_All) | modifiers;
+ return modifiers;
+}
+
+var canUseMouseOffset = null;
+
+function updateMousePos(evt, canvas, display) {
+ if (canUseMouseOffset === null) {
+ // Per https://caniuse.com/mdn-api_mouseevent_offsetx, essentially all *current*
+ // browsers support `offsetX`/`offsetY`, but it does little harm to fall back to the
+ // older `layerX`/`layerY` for now.
+ canUseMouseOffset = 'offsetX' in evt;
+ }
+ var evtX = canUseMouseOffset ? evt.offsetX : evt.layerX,
+ evtY = canUseMouseOffset ? evt.offsetY : evt.layerY;
+ if (display.cursorCanvas) {
+ display.cursorCanvas.style.left = (evtX + canvas.offsetLeft + display.cursorOffsetX) + "px";
+ display.cursorCanvas.style.top = (evtY + canvas.offsetTop + display.cursorOffsetY) + "px";
+ }
+ var x = (evtX * canvas.width / canvas.offsetWidth) | 0,
+ y = (evtY * canvas.height / canvas.offsetHeight) | 0,
+ w = display.width || canvas.width,
+ h = display.height || canvas.height;
+ // clamp to display size
+ display.mouseX = Math.max(0, Math.min(w, x));
+ display.mouseY = Math.max(0, Math.min(h, y));
+}
+
+function recordMouseEvent(what, evt, canvas, display, options) {
+ updateMousePos(evt, canvas, display);
+ if (!display.vm) return;
+ var buttons = display.buttons & Squeak.Mouse_All;
+ switch (what) {
+ case 'mousedown':
+ switch (evt.button || 0) {
+ case 0: buttons = Squeak.Mouse_Red; break; // left
+ case 1: buttons = Squeak.Mouse_Yellow; break; // middle
+ case 2: buttons = Squeak.Mouse_Blue; break; // right
+ }
+ if (buttons === Squeak.Mouse_Red && (evt.altKey || evt.metaKey) || display.cmdButtonTouched)
+ buttons = Squeak.Mouse_Yellow; // emulate middle-click
+ if (options.swapButtons)
+ if (buttons == Squeak.Mouse_Yellow) buttons = Squeak.Mouse_Blue;
+ else if (buttons == Squeak.Mouse_Blue) buttons = Squeak.Mouse_Yellow;
+ break;
+ case 'mousemove':
+ break; // nothing more to do
+ case 'mouseup':
+ buttons = 0;
+ break;
+ }
+ display.buttons = buttons | recordModifiers(evt, display);
+ if (display.eventQueue) {
+ display.eventQueue.push([
+ Squeak.EventTypeMouse,
+ evt.timeStamp, // converted to Squeak time in makeSqueakEvent()
+ display.mouseX,
+ display.mouseY,
+ display.buttons & Squeak.Mouse_All,
+ display.buttons >> 3,
+ ]);
+ if (display.signalInputEvent)
+ display.signalInputEvent();
+ }
+ display.idle = 0;
+ if (what === 'mouseup') display.runFor(100, what); // process copy/paste or fullscreen flag change
+ else display.runNow(what); // don't wait for timeout to run
+}
+
+function recordWheelEvent(evt, display) {
+ if (!display.vm) return;
+ if (!display.eventQueue || !display.vm.image.isSpur) {
+ // for old images, queue wheel events as ctrl+up/down
+ fakeCmdOrCtrlKey(evt.deltaY > 0 ? 31 : 30, evt.timeStamp, display);
+ return;
+ // TODO: use or set VM parameter 48 (see vmParameterAt)
+ }
+ var squeakEvt = [
+ Squeak.EventTypeMouseWheel,
+ evt.timeStamp, // converted to Squeak time in makeSqueakEvent()
+ evt.deltaX,
+ -evt.deltaY,
+ display.buttons & Squeak.Mouse_All,
+ display.buttons >> 3,
+ ];
+ display.eventQueue.push(squeakEvt);
+ if (display.signalInputEvent)
+ display.signalInputEvent();
+ display.idle = 0;
+ if (display.runNow) display.runNow('wheel'); // don't wait for timeout to run
+}
+
+// Squeak traditional keycodes are MacRoman
+var MacRomanToUnicode = [
+ 0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1,
+ 0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8,
+ 0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3,
+ 0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC,
+ 0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF,
+ 0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8,
+ 0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211,
+ 0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x03A9, 0x00E6, 0x00F8,
+ 0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB,
+ 0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153,
+ 0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA,
+ 0x00FF, 0x0178, 0x2044, 0x20AC, 0x2039, 0x203A, 0xFB01, 0xFB02,
+ 0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1,
+ 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4,
+ 0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC,
+ 0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7,
+];
+var UnicodeToMacRoman = {};
+for (var i = 0; i < MacRomanToUnicode.length; i++)
+ UnicodeToMacRoman[MacRomanToUnicode[i]] = i + 128;
+
+function recordKeyboardEvent(unicode, timestamp, display) {
+ if (!display.vm) return;
+ var macCode = UnicodeToMacRoman[unicode] || (unicode < 128 ? unicode : 0);
+ var modifiersAndKey = (display.buttons >> 3) << 8 | macCode;
+ if (display.eventQueue) {
+ display.eventQueue.push([
+ Squeak.EventTypeKeyboard,
+ timestamp, // converted to Squeak time in makeSqueakEvent()
+ macCode, // MacRoman
+ Squeak.EventKeyChar,
+ display.buttons >> 3,
+ unicode, // Unicode
+ ]);
+ if (display.signalInputEvent)
+ display.signalInputEvent();
+ // There are some old images that use both event-based
+ // and polling primitives. To make those work, keep the
+ // last key event
+ display.keys[0] = modifiersAndKey;
+ } else if (modifiersAndKey === display.vm.interruptKeycode) {
+ display.vm.interruptPending = true;
+ } else {
+ // no event queue, queue keys the old-fashioned way
+ display.keys.push(modifiersAndKey);
+ }
+ display.idle = 0;
+ if (display.runNow) display.runNow('keyboard'); // don't wait for timeout to run
+}
+
+function recordDragDropEvent(type, evt, canvas, display) {
+ if (!display.vm || !display.eventQueue) return;
+ updateMousePos(evt, canvas, display);
+ display.eventQueue.push([
+ Squeak.EventTypeDragDropFiles,
+ evt.timeStamp, // converted to Squeak time in makeSqueakEvent()
+ type,
+ display.mouseX,
+ display.mouseY,
+ display.buttons >> 3,
+ display.droppedFiles.length,
+ ]);
+ if (display.signalInputEvent)
+ display.signalInputEvent();
+ display.idle = 0;
+ if (display.runNow) display.runNow('drag-drop'); // don't wait for timeout to run
+}
+
+function fakeCmdOrCtrlKey(key, timestamp, display) {
+ // set both Cmd and Ctrl bit, because we don't know what the image wants
+ display.buttons &= ~Squeak.Keyboard_All; // remove all modifiers
+ display.buttons |= Squeak.Keyboard_Cmd | Squeak.Keyboard_Ctrl;
+ display.keys = []; // flush other keys
+ recordKeyboardEvent(key, timestamp, display);
+}
+
+function makeSqueakEvent(evt, sqEvtBuf, sqTimeOffset) {
+ sqEvtBuf[0] = evt[0];
+ sqEvtBuf[1] = (evt[1] - sqTimeOffset) & Squeak.MillisecondClockMask;
+ for (var i = 2; i < evt.length; i++)
+ sqEvtBuf[i] = evt[i];
+}
+
+function createSqueakDisplay(canvas, options) {
+ options = options || {};
+ if (options.fullscreen) {
+ document.body.style.margin = 0;
+ document.body.style.backgroundColor = 'black';
+ canvas.style.border = 'none';
+ canvas.style.borderRadius = '0px';
+ document.ontouchmove = function(evt) { evt.preventDefault(); };
+ }
+ var display = {
+ context: canvas.getContext("2d"),
+ fullscreen: false,
+ width: 0, // if 0, VM uses canvas.width
+ height: 0, // if 0, VM uses canvas.height
+ scale: 1, // VM will use window.devicePixelRatio if highdpi is enabled, also changes when touch-zooming
+ highdpi: options.highdpi, // TODO: use or set VM parameter 48 (see vmParameterAt)
+ mouseX: 0,
+ mouseY: 0,
+ buttons: 0,
+ keys: [],
+ cmdButtonTouched: null, // touchscreen button pressed (touch ID)
+ eventQueue: null, // only used if image uses event primitives
+ clipboardString: '',
+ clipboardStringChanged: false,
+ handlingEvent: '', // set to 'mouse' or 'keyboard' while handling an event
+ cursorCanvas: options.cursor !== false && document.getElementById("sqCursor") || document.createElement("canvas"),
+ cursorOffsetX: 0,
+ cursorOffsetY: 0,
+ droppedFiles: [],
+ signalInputEvent: null, // function set by VM
+ changedCallback: null, // invoked when display size/scale changes
+ // additional functions added below
+ };
+ if (options.pixelated) {
+ canvas.classList.add("pixelated");
+ display.cursorCanvas && display.cursorCanvas.classList.add("pixelated");
+ }
+
+ display.reset = function() {
+ display.eventQueue = null;
+ display.signalInputEvent = null;
+ display.lastTick = 0;
+ display.getNextEvent = function(firstEvtBuf, firstOffset) {
+ // might be called from VM to get queued event
+ display.eventQueue = []; // create queue on first call
+ display.eventQueue.push = function(evt) {
+ display.eventQueue.offset = Date.now() - evt[1]; // get epoch from first event
+ delete display.eventQueue.push; // use original push from now on
+ display.eventQueue.push(evt);
+ };
+ display.getNextEvent = function(evtBuf, timeOffset) {
+ var evt = display.eventQueue.shift();
+ if (evt) makeSqueakEvent(evt, evtBuf, timeOffset - display.eventQueue.offset);
+ else evtBuf[0] = Squeak.EventTypeNone;
+ };
+ display.getNextEvent(firstEvtBuf, firstOffset);
+ };
+ };
+ display.reset();
+
+ var checkFullscreen = setupFullscreen(display, canvas, options);
+ display.fullscreenRequest = function(fullscreen, thenDo) {
+ // called from primitive to change fullscreen mode
+ if (display.fullscreen != fullscreen) {
+ display.fullscreen = fullscreen;
+ display.resizeTodo = thenDo; // called after resizing
+ display.resizeTodoTimeout = setTimeout(display.resizeDone, 1000);
+ checkFullscreen();
+ } else thenDo();
+ };
+ display.resizeDone = function() {
+ clearTimeout(display.resizeTodoTimeout);
+ var todo = display.resizeTodo;
+ if (todo) {
+ display.resizeTodo = null;
+ todo();
+ }
+ };
+ display.clear = function() {
+ canvas.width = canvas.width;
+ };
+ display.setTitle = function(title) {
+ document.title = title;
+ };
+ display.showBanner = function(msg, style) {
+ style = style || display.context.canvas.style || {};
+ var ctx = display.context;
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = style.color || "#F90";
+ ctx.font = style.font || "bold 48px sans-serif";
+ if (!style.font && ctx.measureText(msg).width > canvas.width)
+ ctx.font = "bold 24px sans-serif";
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(msg, canvas.width / 2, canvas.height / 2);
+ };
+ display.showProgress = function(value, style) {
+ style = style || display.context.canvas.style || {};
+ var ctx = display.context,
+ w = (canvas.width / 3) | 0,
+ h = 24,
+ x = canvas.width * 0.5 - w / 2,
+ y = canvas.height * 0.5 + 2 * h;
+ ctx.fillStyle = style.background || "#000";
+ ctx.fillRect(x, y, w, h);
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = style.stroke || "#F90";
+ ctx.strokeRect(x, y, w, h);
+ ctx.fillStyle = style.fill || "#F90";
+ ctx.fillRect(x, y, w * value, h);
+ };
+ display.executeClipboardPasteKey = function(text, timestamp) {
+ if (!display.vm) return true;
+ try {
+ display.clipboardString = text;
+ // simulate paste event for Squeak
+ fakeCmdOrCtrlKey('v'.charCodeAt(0), timestamp, display);
+ } catch(err) {
+ console.error("paste error " + err);
+ }
+ };
+ display.executeClipboardCopyKey = function(key, timestamp) {
+ if (!display.vm) return true;
+ // simulate copy event for Squeak so it places its text in clipboard
+ display.clipboardStringChanged = false;
+ fakeCmdOrCtrlKey((key || 'c').charCodeAt(0), timestamp, display);
+ var start = Date.now();
+ // now interpret until Squeak has copied to the clipboard
+ while (!display.clipboardStringChanged && Date.now() - start < 500)
+ display.vm.interpret(20);
+ if (!display.clipboardStringChanged) return;
+ // got it, now copy to the system clipboard
+ try {
+ return display.clipboardString;
+ } catch(err) {
+ console.error("copy error " + err);
+ }
+ };
+ canvas.onmousedown = function(evt) {
+ checkFullscreen();
+ recordMouseEvent('mousedown', evt, canvas, display, options);
+ evt.preventDefault();
+ return false;
+ };
+ canvas.onmouseup = function(evt) {
+ recordMouseEvent('mouseup', evt, canvas, display, options);
+ checkFullscreen();
+ evt.preventDefault();
+ };
+ canvas.onmousemove = function(evt) {
+ recordMouseEvent('mousemove', evt, canvas, display, options);
+ evt.preventDefault();
+ };
+ canvas.onwheel = function(evt) {
+ recordWheelEvent(evt, display);
+ evt.preventDefault();
+ };
+ canvas.oncontextmenu = function() {
+ return false;
+ };
+ // touch event handling
+ var touch = {
+ state: 'idle',
+ button: 0,
+ x: 0,
+ y: 0,
+ dist: 0,
+ down: {},
+ };
+ function touchToMouse(evt) {
+ if (evt.touches.length) {
+ // average all touch positions
+ // but ignore the cmd button touch
+ var x = 0, y = 0, n = 0;
+ for (var i = 0; i < evt.touches.length; i++) {
+ if (evt.touches[i].identifier === display.cmdButtonTouched) continue;
+ x += evt.touches[i].pageX;
+ y += evt.touches[i].pageY;
+ n++;
+ }
+ if (n > 0) {
+ touch.x = x / n;
+ touch.y = y / n;
+ }
+ }
+ return {
+ timeStamp: evt.timeStamp,
+ button: touch.button,
+ offsetX: touch.x - canvas.offsetLeft,
+ offsetY: touch.y - canvas.offsetTop,
+ };
+ }
+ function dd(ax, ay, bx, by) {var x = ax - bx, y = ay - by; return Math.sqrt(x*x + y*y);}
+ function dist(a, b) {return dd(a.pageX, a.pageY, b.pageX, b.pageY);}
+ function dent(n, l, t, u) { return n < l ? n + t - l : n > u ? n + t - u : t; }
+ function adjustCanvas(l, t, w, h) {
+ var cursorCanvas = display.cursorCanvas,
+ cssScale = w / canvas.width,
+ ratio = display.highdpi ? window.devicePixelRatio : 1,
+ pixelScale = cssScale * ratio;
+ canvas.style.left = (l|0) + "px";
+ canvas.style.top = (t|0) + "px";
+ canvas.style.width = (w|0) + "px";
+ canvas.style.height = (h|0) + "px";
+ if (cursorCanvas) {
+ cursorCanvas.style.left = (l + display.cursorOffsetX + display.mouseX * cssScale|0) + "px";
+ cursorCanvas.style.top = (t + display.cursorOffsetY + display.mouseY * cssScale|0) + "px";
+ cursorCanvas.style.width = (cursorCanvas.width * pixelScale|0) + "px";
+ cursorCanvas.style.height = (cursorCanvas.height * pixelScale|0) + "px";
+ }
+ // if pixelation is not forced, turn it on for integer scales
+ if (!options.pixelated) {
+ if (pixelScale % 1 === 0 || pixelScale > 5) {
+ canvas.classList.add("pixelated");
+ cursorCanvas && cursorCanvas.classList.add("pixelated");
+ } else {
+ canvas.classList.remove("pixelated");
+ cursorCanvas && display.cursorCanvas.classList.remove("pixelated");
+ }
+ }
+ display.css = {
+ left: l,
+ top: t,
+ width: w,
+ height: h,
+ scale: cssScale,
+ pixelScale: pixelScale,
+ ratio: ratio,
+ };
+ if (display.changedCallback) display.changedCallback();
+ return cssScale;
+ }
+ // zooming/panning with two fingers
+ var maxZoom = 5;
+ function zoomStart(evt) {
+ touch.dist = dist(evt.touches[0], evt.touches[1]);
+ touch.down.x = touch.x;
+ touch.down.y = touch.y;
+ touch.down.dist = touch.dist;
+ touch.down.left = canvas.offsetLeft;
+ touch.down.top = canvas.offsetTop;
+ touch.down.width = canvas.offsetWidth;
+ touch.down.height = canvas.offsetHeight;
+ // store original canvas bounds
+ if (!touch.orig) touch.orig = {
+ left: touch.down.left,
+ top: touch.down.top,
+ right: touch.down.left + touch.down.width,
+ bottom: touch.down.top + touch.down.height,
+ width: touch.down.width,
+ height: touch.down.height,
+ };
+ }
+ function zoomMove(evt) {
+ if (evt.touches.length < 2) return;
+ touch.dist = dist(evt.touches[0], evt.touches[1]);
+ var minScale = touch.orig.width / touch.down.width,
+ //nowScale = dent(touch.dist / touch.down.dist, 0.8, 1, 1.5),
+ nowScale = touch.dist / touch.down.dist,
+ scale = Math.min(Math.max(nowScale, minScale * 0.95), minScale * maxZoom),
+ w = touch.down.width * scale,
+ h = touch.orig.height * w / touch.orig.width,
+ l = touch.down.left - (touch.down.x - touch.down.left) * (scale - 1) + (touch.x - touch.down.x),
+ t = touch.down.top - (touch.down.y - touch.down.top) * (scale - 1) + (touch.y - touch.down.y);
+ // allow to rubber-band by 20px for feedback
+ l = Math.max(Math.min(l, touch.orig.left + 20), touch.orig.right - w - 20);
+ t = Math.max(Math.min(t, touch.orig.top + 20), touch.orig.bottom - h - 20);
+ adjustCanvas(l, t, w, h);
+ }
+ function zoomEnd(evt) {
+ var l = canvas.offsetLeft,
+ t = canvas.offsetTop,
+ w = canvas.offsetWidth,
+ h = canvas.offsetHeight;
+ w = Math.min(Math.max(w, touch.orig.width), touch.orig.width * maxZoom);
+ h = touch.orig.height * w / touch.orig.width;
+ l = Math.max(Math.min(l, touch.orig.left), touch.orig.right - w);
+ t = Math.max(Math.min(t, touch.orig.top), touch.orig.bottom - h);
+ var scale = adjustCanvas(l, t, w, h);
+ if ((scale - display.scale) < 0.0001) {
+ touch.orig = null;
+ onresize();
+ }
+ }
+ // State machine to distinguish between 1st/2nd mouse button and zoom/pan:
+ // * if moved, or no 2nd finger within 100ms of 1st down, start mousing
+ // * if fingers moved significantly within 200ms of 2nd down, start zooming
+ // * if touch ended within this time, generate click (down+up)
+ // * otherwise, start mousing with 2nd button
+ // * also, ignore finger on cmd button
+ // When mousing, always generate a move event before down event so that
+ // mouseover eventhandlers in image work better
+ canvas.ontouchstart = function(evt) {
+ evt.preventDefault();
+ var e = touchToMouse(evt);
+ for (var i = 0; i < evt.changedTouches.length; i++) {
+ if (evt.changedTouches[i].identifier === display.cmdButtonTouched) continue;
+ switch (touch.state) {
+ case 'idle':
+ touch.state = 'got1stFinger';
+ touch.first = e;
+ setTimeout(function(){
+ if (touch.state !== 'got1stFinger') return;
+ touch.state = 'mousing';
+ touch.button = e.button = 0;
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ recordMouseEvent('mousedown', e, canvas, display, options);
+ }, 100);
+ break;
+ case 'got1stFinger':
+ touch.state = 'got2ndFinger';
+ zoomStart(evt);
+ setTimeout(function(){
+ if (touch.state !== 'got2ndFinger') return;
+ var didMove = Math.abs(touch.down.dist - touch.dist) > 10 ||
+ dd(touch.down.x, touch.down.y, touch.x, touch.y) > 10;
+ if (didMove) {
+ touch.state = 'zooming';
+ } else {
+ touch.state = 'mousing';
+ touch.button = e.button = 2;
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ recordMouseEvent('mousedown', e, canvas, display, options);
+ }
+ }, 200);
+ break;
+ }
+ }
+ };
+ canvas.ontouchmove = function(evt) {
+ evt.preventDefault();
+ var e = touchToMouse(evt);
+ switch (touch.state) {
+ case 'got1stFinger':
+ touch.state = 'mousing';
+ touch.button = e.button = 0;
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ recordMouseEvent('mousedown', e, canvas, display, options);
+ break;
+ case 'mousing':
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ break;
+ case 'got2ndFinger':
+ if (evt.touches.length > 1)
+ touch.dist = dist(evt.touches[0], evt.touches[1]);
+ break;
+ case 'zooming':
+ zoomMove(evt);
+ break;
+ }
+ };
+ canvas.ontouchend = function(evt) {
+ evt.preventDefault();
+ checkFullscreen();
+ var e = touchToMouse(evt);
+ var n = evt.touches.length;
+ if (Array.from(evt.touches).findIndex(t => t.identifier === display.cmdButtonTouched) >= 0) n--;
+ for (var i = 0; i < evt.changedTouches.length; i++) {
+ if (evt.changedTouches[i].identifier === display.cmdButtonTouched) {
+ continue;
+ }
+ switch (touch.state) {
+ case 'mousing':
+ if (n > 0) break;
+ touch.state = 'idle';
+ recordMouseEvent('mouseup', e, canvas, display, options);
+ break;
+ case 'got1stFinger':
+ touch.state = 'idle';
+ touch.button = e.button = 0;
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ recordMouseEvent('mousedown', e, canvas, display, options);
+ recordMouseEvent('mouseup', e, canvas, display, options);
+ break;
+ case 'got2ndFinger':
+ touch.state = 'mousing';
+ touch.button = e.button = 2;
+ recordMouseEvent('mousemove', e, canvas, display, options);
+ recordMouseEvent('mousedown', e, canvas, display, options);
+ break;
+ case 'zooming':
+ if (n > 0) break;
+ touch.state = 'idle';
+ zoomEnd(evt);
+ break;
+ }
+ }
+ };
+ canvas.ontouchcancel = function(evt) {
+ canvas.ontouchend(evt);
+ };
+ // cursorCanvas shows Squeak cursor
+ if (display.cursorCanvas) {
+ var absolute = window.getComputedStyle(canvas).position === "absolute";
+ display.cursorCanvas.style.display = "block";
+ display.cursorCanvas.style.position = absolute ? "absolute": "fixed";
+ display.cursorCanvas.style.cursor = "none";
+ display.cursorCanvas.style.background = "transparent";
+ display.cursorCanvas.style.pointerEvents = "none";
+ canvas.parentElement.appendChild(display.cursorCanvas);
+ canvas.style.cursor = "none";
+ }
+ // keyboard stuff
+ // create hidden input field to capture not only keyboard events
+ // but also copy/paste and input events (for dead keys)
+ var input = document.createElement("input");
+ input.setAttribute("autocomplete", "off");
+ input.setAttribute("autocorrect", "off");
+ input.setAttribute("autocapitalize", "off");
+ input.setAttribute("spellcheck", "false");
+ input.style.position = "absolute";
+ input.style.left = "-1000px";
+ canvas.parentElement.appendChild(input);
+ // touch-keyboard buttons
+ if ('ontouchstart' in document) {
+ // button to show on-screen keyboard
+ var keyboardButton = document.createElement('div');
+ keyboardButton.innerHTML = '';
+ keyboardButton.setAttribute('style', 'position:fixed;right:0;bottom:0;background-color:rgba(128,128,128,0.5);border-radius:5px');
+ canvas.parentElement.appendChild(keyboardButton);
+ keyboardButton.onmousedown = function(evt) {
+ // show on-screen keyboard
+ input.focus({ preventScroll: true });
+ evt.preventDefault();
+ }
+ keyboardButton.ontouchstart = keyboardButton.onmousedown;
+ // modifier button for CMD key
+ var cmdButton = document.createElement('div');
+ cmdButton.innerHTML = '⌘';
+ cmdButton.setAttribute('style', 'position:fixed;left:0;background-color:rgba(128,128,128,0.5);width:50px;height:50px;font-size:30px;text-align:center;vertical-align:middle;line-height:50px;border-radius:5px');
+ if (window.visualViewport) {
+ // fix position of button when virtual keyboard is shown
+ const vv = window.visualViewport;
+ const fixPosition = () => cmdButton.style.top = `${vv.height}px`;
+ vv.addEventListener('resize', fixPosition);
+ cmdButton.style.transform = `translateY(-100%)`;
+ fixPosition();
+ } else {
+ cmdButton.style.bottom = '0';
+ }
+ canvas.parentElement.appendChild(cmdButton);
+ cmdButton.ontouchstart = function(evt) {
+ display.cmdButtonTouched = evt.changedTouches[0].identifier;
+ cmdButton.style.backgroundColor = 'rgba(255,255,255,0.5)';
+ evt.preventDefault();
+ evt.stopPropagation();
+ }
+ cmdButton.ontouchend = function(evt) {
+ display.cmdButtonTouched = null;
+ cmdButton.style.backgroundColor = 'rgba(128,128,128,0.5)';
+ evt.preventDefault();
+ evt.stopPropagation();
+ }
+ cmdButton.ontouchcancel = cmdButton.ontouchend;
+ } else {
+ // keep focus on input field
+ input.onblur = function() { input.focus({ preventScroll: true }); };
+ input.focus({ preventScroll: true });
+ }
+ display.isMac = navigator.userAgent.includes("Mac");
+ // emulate keypress events
+ var deadKey = false, // true if last keydown was a dead key
+ deadChars = [];
+ input.oninput = function(evt) {
+ if (!display.vm) return true;
+ if (evt.inputType === "insertText" // regular key, or Chrome
+ || evt.inputType === "insertCompositionText" // Firefox, Chrome
+ || evt.inputType === "insertFromComposition") // Safari
+ {
+ // generate backspace to delete inserted dead chars
+ var hadDeadChars = deadChars.length > 0;
+ if (hadDeadChars) {
+ var oldButtons = display.buttons;
+ display.buttons &= ~Squeak.Keyboard_All; // remove all modifiers
+ for (var i = 0; i < deadChars.length; i++) {
+ recordKeyboardEvent(8, evt.timeStamp, display);
+ }
+ display.buttons = oldButtons;
+ deadChars = [];
+ }
+ // generate keyboard events for each character
+ // single input could be many characters, e.g. for emoji
+ var chars = Array.from(evt.data); // split by surrogate pairs
+ for (var i = 0; i < chars.length; i++) {
+ var unicode = chars[i].codePointAt(0); // codePointAt combines pair into unicode
+ recordKeyboardEvent(unicode, evt.timeStamp, display);
+ }
+ if (!hadDeadChars && evt.isComposing && evt.inputType === "insertCompositionText") {
+ deadChars = deadChars.concat(chars);
+ }
+ }
+ if (!deadChars.length) resetInput();
+ };
+ input.onkeydown = function(evt) {
+ checkFullscreen();
+ if (!display.vm) return true;
+ deadKey = evt.key === "Dead";
+ if (deadKey) return; // let browser handle dead keys
+ recordModifiers(evt, display);
+ var squeakCode = ({
+ 8: 8, // Backspace
+ 9: 9, // Tab
+ 13: 13, // Return
+ 27: 27, // Escape
+ 32: 32, // Space
+ 33: 11, // PageUp
+ 34: 12, // PageDown
+ 35: 4, // End
+ 36: 1, // Home
+ 37: 28, // Left
+ 38: 30, // Up
+ 39: 29, // Right
+ 40: 31, // Down
+ 45: 5, // Insert
+ 46: 127, // Delete
+ })[evt.keyCode];
+ if (squeakCode) { // special key pressed
+ recordKeyboardEvent(squeakCode, evt.timeStamp, display);
+ return evt.preventDefault();
+ }
+ // copy/paste new-style
+ if (display.isMac ? evt.metaKey : evt.ctrlKey) {
+ switch (evt.key) {
+ case "c":
+ case "x":
+ if (!navigator.clipboard) return; // fire document.oncopy/oncut
+ var text = display.executeClipboardCopyKey(evt.key, evt.timeStamp);
+ if (typeof text === 'string') {
+ navigator.clipboard.writeText(text)
+ .catch(function(err) { console.error("display: copy error " + err.message); });
+ }
+ return evt.preventDefault();
+ case "v":
+ if (!navigator.clipboard) return; // fire document.onpaste
+ navigator.clipboard.readText()
+ .then(function(text) {
+ display.executeClipboardPasteKey(text, evt.timeStamp);
+ })
+ .catch(function(err) { console.error("display: paste error " + err.message); });
+ return evt.preventDefault();
+ }
+ }
+ if (evt.key.length !== 1) return; // let browser handle other keys
+ if (display.buttons & (Squeak.Keyboard_Cmd | Squeak.Keyboard_Ctrl)) {
+ var code = evt.key.toLowerCase().charCodeAt(0);
+ if ((display.buttons & Squeak.Keyboard_Ctrl) && code >= 96 && code < 127) code &= 0x1F; // ctrl-
+ recordKeyboardEvent(code, evt.timeStamp, display);
+ return evt.preventDefault();
+ }
+ };
+ input.onkeyup = function(evt) {
+ if (!display.vm) return true;
+ recordModifiers(evt, display);
+ };
+ function resetInput() {
+ input.value = "**";
+ input.selectionStart = 1;
+ input.selectionEnd = 1;
+ }
+ resetInput();
+ // hack to generate arrow keys when moving the cursor (e.g. via spacebar on iPhone)
+ // we're not getting any events for that but the cursor (selection) changes
+ if ('ontouchstart' in document) {
+ let count = 0; // count how often the interval has run after the first move
+ setInterval(() => {
+ const direction = input.selectionStart - 1;
+ if (direction === 0) {
+ count = 0;
+ return;
+ }
+ // move cursor once, then not for 500ms, then every 250ms
+ if (count === 0 || count > 2) {
+ const key = direction < 1 ? 28 : 29; // arrow left or right
+ recordKeyboardEvent(key, Date.now(), display);
+ }
+ input.selectionStart = 1;
+ input.selectionEnd = 1;
+ count++;
+ }, 250);
+ }
+ // more copy/paste
+ if (navigator.clipboard) {
+ // new-style copy/paste (all modern browsers)
+ display.readFromSystemClipboard = () => display.handlingEvent &&
+ navigator.clipboard.readText()
+ .then(text => display.clipboardString = text)
+ .catch(err => console.error("readFromSystemClipboard " + err.message));
+ display.writeToSystemClipboard = () => display.handlingEvent &&
+ navigator.clipboard.writeText(display.clipboardString)
+ .then(() => display.clipboardStringChanged = false)
+ .catch(err => console.error("writeToSystemClipboard " + err.message));
+ } else {
+ // old-style copy/paste
+ document.oncopy = function(evt, key) {
+ var text = display.executeClipboardCopyKey(key, evt.timeStamp);
+ if (typeof text === 'string') {
+ evt.clipboardData.setData("Text", text);
+ }
+ evt.preventDefault();
+ };
+ document.oncut = function(evt) {
+ if (!display.vm) return true;
+ document.oncopy(evt, 'x');
+ };
+ document.onpaste = function(evt) {
+ var text = evt.clipboardData.getData('Text');
+ display.executeClipboardPasteKey(text, evt.timeStamp);
+ evt.preventDefault();
+ };
+ }
+ // do not use addEventListener, we want to replace any previous drop handler
+ function dragEventHasFiles(evt) {
+ for (var i = 0; i < evt.dataTransfer.types.length; i++)
+ if (evt.dataTransfer.types[i] == 'Files') return true;
+ return false;
+ }
+ document.ondragover = function(evt) {
+ evt.preventDefault();
+ if (!dragEventHasFiles(evt)) {
+ evt.dataTransfer.dropEffect = 'none';
+ } else {
+ evt.dataTransfer.dropEffect = 'copy';
+ recordDragDropEvent(Squeak.EventDragMove, evt, canvas, display);
+ }
+ };
+ document.ondragenter = function(evt) {
+ if (!dragEventHasFiles(evt)) return;
+ recordDragDropEvent(Squeak.EventDragEnter, evt, canvas, display);
+ };
+ document.ondragleave = function(evt) {
+ if (!dragEventHasFiles(evt)) return;
+ recordDragDropEvent(Squeak.EventDragLeave, evt, canvas, display);
+ };
+ document.ondrop = function(evt) {
+ evt.preventDefault();
+ if (!dragEventHasFiles(evt)) return false;
+ var files = [].slice.call(evt.dataTransfer.files),
+ loaded = [],
+ image, imageName = null;
+ display.droppedFiles = [];
+ files.forEach(function(f) {
+ var path = options.root + f.name;
+ display.droppedFiles.push(path);
+ var reader = new FileReader();
+ reader.onload = function () {
+ var buffer = this.result;
+ Squeak.filePut(path, buffer);
+ loaded.push(path);
+ if (!image && /.*image$/.test(path) && (!display.vm || confirm("Run " + f.name + " now?\n(cancel to use as file)"))) {
+ image = buffer;
+ imageName = path;
+ }
+ if (loaded.length == files.length) {
+ if (image) {
+ if (display.vm) {
+ display.quitFlag = true;
+ options.onQuit = function(vm, display, options) {
+ options.onQuit = null;
+ SqueakJS.appName = imageName.replace(/.*\//,'').replace(/\.image$/,'');
+ SqueakJS.runImage(image, imageName, display, options);
+ }
+ } else {
+ SqueakJS.appName = imageName.replace(/.*\//,'').replace(/\.image$/,'');
+ SqueakJS.runImage(image, imageName, display, options);
+ }
+ } else {
+ recordDragDropEvent(Squeak.EventDragDrop, evt, canvas, display);
+ }
+ }
+ };
+ reader.readAsArrayBuffer(f);
+ });
+ return false;
+ };
+
+ var debounceTimeout;
+ function onresize() {
+ if (touch.orig) return; // manually resized
+ // call resizeDone only if window size didn't change for 300ms
+ var debounceWidth = window.innerWidth,
+ debounceHeight = window.innerHeight;
+ clearTimeout(debounceTimeout);
+ debounceTimeout = setTimeout(function() {
+ if (debounceWidth == window.innerWidth && debounceHeight == window.innerHeight)
+ display.resizeDone();
+ else
+ onresize();
+ }, 300);
+ // CSS won't let us do what we want so we will layout the canvas ourselves.
+ var x = 0,
+ y = 0,
+ w = window.innerWidth,
+ h = window.innerHeight,
+ paddingX = 0, // padding outside canvas
+ paddingY = 0;
+ // above are the default values for laying out the canvas
+ if (!options.fixedWidth) { // set canvas resolution
+ if (!options.minWidth) options.minWidth = 700;
+ if (!options.minHeight) options.minHeight = 700;
+ var defaultScale = display.highdpi ? window.devicePixelRatio : 1,
+ scaleW = w < options.minWidth ? options.minWidth / w : defaultScale,
+ scaleH = h < options.minHeight ? options.minHeight / h : defaultScale,
+ scale = Math.max(scaleW, scaleH);
+ display.width = Math.floor(w * scale);
+ display.height = Math.floor(h * scale);
+ display.scale = w / display.width;
+ } else { // fixed resolution and aspect ratio
+ display.width = options.fixedWidth;
+ display.height = options.fixedHeight;
+ var wantRatio = display.width / display.height,
+ haveRatio = w / h;
+ if (haveRatio > wantRatio) {
+ paddingX = w - Math.floor(h * wantRatio);
+ } else {
+ paddingY = h - Math.floor(w / wantRatio);
+ }
+ display.scale = (w - paddingX) / display.width;
+ }
+ // set resolution
+ if (canvas.width != display.width || canvas.height != display.height) {
+ var preserveScreen = options.fixedWidth || !display.resizeTodo, // preserve unless changing fullscreen
+ imgData = preserveScreen && display.context.getImageData(0, 0, canvas.width, canvas.height);
+ canvas.width = display.width;
+ canvas.height = display.height;
+ if (imgData) display.context.putImageData(imgData, 0, 0);
+ }
+ // set canvas and cursor canvas size, position, pixelation
+ adjustCanvas(
+ x + Math.floor(paddingX / 2),
+ y + Math.floor(paddingY / 2),
+ w - paddingX,
+ h - paddingY
+ );
+ };
+
+ if (!options.embedded) {
+ onresize();
+ window.onresize = onresize;
+ }
+
+ return display;
+}
+
+function setupSpinner(vm, options) {
+ var spinner = options.spinner;
+ if (!spinner) return null;
+ spinner.onmousedown = function(evt) {
+ if (confirm(SqueakJS.appName + " is busy. Interrupt?"))
+ vm.interruptPending = true;
+ };
+ return spinner.style;
+}
+
+var spinnerAngle = 0,
+ becameBusy = 0;
+function updateSpinner(spinner, idleMS, vm, display) {
+ var busy = idleMS === 0,
+ animating = vm.lastTick - display.lastTick < 500;
+ if (!busy || animating) {
+ spinner.display = "none";
+ becameBusy = 0;
+ } else {
+ if (becameBusy === 0) {
+ becameBusy = vm.lastTick;
+ } else if (vm.lastTick - becameBusy > 1000) {
+ spinner.display = "block";
+ spinnerAngle = (spinnerAngle + 30) % 360;
+ spinner.webkitTransform = spinner.transform = "rotate(" + spinnerAngle + "deg)";
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// main loop
+//////////////////////////////////////////////////////////////////////////////
+
+var loop; // holds timeout for main loop
+
+SqueakJS.runImage = function(buffer, name, display, options) {
+ window.onbeforeunload = function(evt) {
+ var msg = SqueakJS.appName + " is still running";
+ evt.returnValue = msg;
+ return msg;
+ };
+ window.clearTimeout(loop);
+ display.reset();
+ display.clear();
+ display.showBanner("Loading " + SqueakJS.appName);
+ display.showProgress(0);
+ window.setTimeout(function readImageAsync() {
+ var image = new Squeak.Image(name);
+ image.readFromBuffer(buffer, function startRunning() {
+ display.quitFlag = false;
+ var vm = new Squeak.Interpreter(image, display, options);
+ SqueakJS.vm = vm;
+ Squeak.Settings["squeakImageName"] = name;
+ display.clear();
+ display.showBanner("Starting " + SqueakJS.appName);
+ var spinner = setupSpinner(vm, options);
+ function run() {
+ try {
+ if (display.quitFlag) SqueakJS.onQuit(vm, display, options);
+ else vm.interpret(50, function runAgain(ms) {
+ if (ms == "sleep") ms = 200;
+ if (spinner) updateSpinner(spinner, ms, vm, display);
+ loop = window.setTimeout(run, ms);
+ });
+ } catch(error) {
+ console.error(error);
+ alert(error);
+ }
+ }
+ display.runNow = function(event) {
+ window.clearTimeout(loop);
+ display.handlingEvent = event;
+ run();
+ display.handlingEvent = '';
+ };
+ display.runFor = function(milliseconds, event) {
+ var stoptime = Date.now() + milliseconds;
+ do {
+ if (display.quitFlag) return;
+ display.runNow(event);
+ } while (Date.now() < stoptime);
+ };
+ if (options.onStart) options.onStart(vm, display, options);
+ run();
+ },
+ function readProgress(value) {display.showProgress(value);});
+ }, 0);
+};
+
+function processOptions(options) {
+ var search = (location.hash || location.search).slice(1),
+ args = search && search.split("&");
+ if (args) for (var i = 0; i < args.length; i++) {
+ var keyAndVal = args[i].split("="),
+ key = keyAndVal[0],
+ val = true;
+ if (keyAndVal.length > 1) {
+ val = decodeURIComponent(keyAndVal.slice(1).join("="));
+ if (val.match(/^(true|false|null|[0-9"[{].*)$/))
+ try { val = JSON.parse(val); } catch(e) {
+ if (val[0] === "[") val = val.slice(1,-1).split(","); // handle string arrays
+ // if not JSON use string itself
+ }
+ }
+ options[key] = val;
+ }
+ var root = Squeak.splitFilePath(options.root || "/").fullname;
+ Squeak.dirCreate(root, true);
+ if (!/\/$/.test(root)) root += "/";
+ options.root = root;
+ if (options.w) options.fixedWidth = options.w;
+ if (options.h) options.fixedHeight = options.h;
+ if (options.fixedWidth && !options.fixedHeight) options.fixedHeight = options.fixedWidth * 3 / 4 | 0;
+ if (options.fixedHeight && !options.fixedWidth) options.fixedWidth = options.fixedHeight * 4 / 3 | 0;
+ if (options.fixedWidth && options.fixedHeight) options.fullscreen = true;
+ SqueakJS.options = options;
+}
+
+function fetchTemplates(options) {
+ if (options.templates) {
+ if (options.templates.constructor === Array) {
+ var templates = {};
+ options.templates.forEach(function(path){ templates[path] = path; });
+ options.templates = templates;
+ }
+ for (var path in options.templates) {
+ var dir = path[0] == "/" ? path : options.root + path,
+ baseUrl = new URL(options.url, document.baseURI).href.split(/[?#]/)[0],
+ url = Squeak.splitUrl(options.templates[path], baseUrl).full;
+ if (url.endsWith("/")) url = url.slice(0,-1);
+ if (url.endsWith("/.")) url = url.slice(0,-2);
+ Squeak.fetchTemplateDir(dir, url);
+ }
+ }
+}
+
+function processFile(file, display, options, thenDo) {
+ Squeak.filePut(options.root + file.name, file.data, function() {
+ console.log("Stored " + options.root + file.name);
+ if (file.zip) {
+ processZip(file, display, options, thenDo);
+ } else {
+ thenDo();
+ }
+ });
+}
+
+function processZip(file, display, options, thenDo) {
+ display.showBanner("Analyzing " + file.name);
+ JSZip.loadAsync(file.data, { createFolders: true }).then(function(zip) {
+ var todo = [];
+ zip.forEach(function(filename, meta) {
+ if (filename.startsWith("__MACOSX/") || filename.endsWith(".DS_Store")) return; // skip macOS metadata
+ if (meta.dir) {
+ filename = filename.replace(/\/$/, "");
+ Squeak.dirCreate(options.root + filename, true);
+ return;
+ }
+ if (!options.image.name && filename.match(/\.image$/))
+ options.image.name = filename;
+ if (options.forceDownload || !Squeak.fileExists(options.root + filename)) {
+ todo.push(filename);
+ } else if (options.image.name === filename) {
+ // image exists, need to fetch it from storage
+ var _thenDo = thenDo;
+ thenDo = function() {
+ Squeak.fileGet(options.root + filename, function(data) {
+ options.image.data = data;
+ return _thenDo();
+ }, function onError() {
+ Squeak.fileDelete(options.root + file.name);
+ return processZip(file, display, options, _thenDo);
+ });
+ }
+ }
+ });
+ if (todo.length === 0) return thenDo();
+ var done = 0;
+ display.showBanner("Unzipping " + file.name);
+ display.showProgress(0);
+ todo.forEach(function(filename){
+ console.log("Inflating " + file.name + ": " + filename);
+ function progress(x) { display.showProgress((x.percent / 100 + done) / todo.length); }
+ zip.file(filename).async("arraybuffer", progress).then(function(buffer){
+ console.log("Expanded size of " + filename + ": " + buffer.byteLength + " bytes");
+ var unzipped = {};
+ if (options.image.name === filename)
+ unzipped = options.image;
+ unzipped.name = filename;
+ unzipped.data = buffer;
+ processFile(unzipped, display, options, function() {
+ if (++done === todo.length) thenDo();
+ });
+ });
+ });
+ });
+}
+
+function checkExisting(file, display, options, ifExists, ifNotExists) {
+ if (!Squeak.fileExists(options.root + file.name))
+ return ifNotExists();
+ if (file.image || file.zip) {
+ // if it's the image or a zip, load from file storage
+ Squeak.fileGet(options.root + file.name, function(data) {
+ file.data = data;
+ if (file.zip) processZip(file, display, options, ifExists);
+ else ifExists();
+ }, function onError() {
+ // if error, download it
+ Squeak.fileDelete(options.root + file.name);
+ return ifNotExists();
+ });
+ } else {
+ // for all other files assume they're okay
+ ifExists();
+ }
+}
+
+function downloadFile(file, display, options, thenDo) {
+ display.showBanner("Downloading " + file.name);
+ var rq = new XMLHttpRequest(),
+ proxy = options.proxy || "";
+ rq.open('GET', proxy + file.url);
+ if (options.ajax) rq.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ rq.responseType = 'arraybuffer';
+ rq.onprogress = function(e) {
+ if (e.lengthComputable) display.showProgress(e.loaded / e.total);
+ };
+ rq.onload = function(e) {
+ if (this.status == 200) {
+ file.data = this.response;
+ processFile(file, display, options, thenDo);
+ }
+ else this.onerror(this.statusText);
+ };
+ rq.onerror = function(e) {
+ if (options.proxy) {
+ console.error(Squeak.bytesAsString(new Uint8Array(this.response)));
+ return alert("Failed to download:\n" + file.url);
+ }
+ var proxy = Squeak.defaultCORSProxy,
+ retry = new XMLHttpRequest();
+ console.warn('Retrying with CORS proxy: ' + proxy + file.url);
+ retry.open('GET', proxy + file.url);
+ if (options.ajax) retry.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ retry.responseType = rq.responseType;
+ retry.onprogress = rq.onprogress;
+ retry.onload = rq.onload;
+ retry.onerror = function() {
+ console.error(Squeak.bytesAsString(new Uint8Array(this.response)));
+ alert("Failed to download:\n" + file.url)};
+ retry.send();
+ };
+ rq.send();
+}
+
+function fetchFiles(files, display, options, thenDo) {
+ // check if files exist locally and download if nessecary
+ function getNextFile() {
+ if (files.length === 0) return thenDo();
+ var file = files.shift(),
+ forceDownload = options.forceDownload || file.forceDownload;
+ if (forceDownload) downloadFile(file, display, options, getNextFile);
+ else checkExisting(file, display, options,
+ function ifExists() {
+ getNextFile();
+ },
+ function ifNotExists() {
+ downloadFile(file, display, options, getNextFile);
+ });
+ }
+ getNextFile();
+}
+
+SqueakJS.runSqueak = function(imageUrl, canvas, options={}) {
+ if (!canvas) {
+ canvas = document.createElement("canvas");
+ canvas.style.position = "absolute";
+ canvas.style.left = "0";
+ canvas.style.top = "0";
+ canvas.style.width = "100%";
+ canvas.style.height = "100%";
+ document.body.appendChild(canvas);
+ }
+ // we need to fetch all files first, then run the image
+ processOptions(options);
+ if (imageUrl && imageUrl.endsWith(".zip")) {
+ options.zip = imageUrl.match(/[^\/]*$/)[0];
+ options.url = imageUrl.replace(/[^\/]*$/, "");
+ imageUrl = null;
+ }
+ if (!imageUrl && options.image) imageUrl = options.image;
+ var baseUrl = options.url || "";
+ if (!baseUrl && imageUrl && imageUrl.replace(/[^\/]*$/, "")) {
+ baseUrl = imageUrl.replace(/[^\/]*$/, "");
+ imageUrl = imageUrl.replace(/^.*\//, "");
+ }
+ options.url = baseUrl;
+ if (baseUrl[0] === "/" && baseUrl[1] !== "/" && baseUrl.length > 1 && options.root === "/") {
+ options.root = baseUrl;
+ }
+ fetchTemplates(options);
+ var display = createSqueakDisplay(canvas, options),
+ image = {url: null, name: null, image: true, data: null},
+ files = [];
+ display.argv = options.argv;
+ if (imageUrl) {
+ var url = Squeak.splitUrl(imageUrl, baseUrl);
+ image.url = url.full;
+ image.name = url.filename;
+ }
+ if (options.files) {
+ options.files.forEach(function(f) {
+ var url = Squeak.splitUrl(f, baseUrl);
+ if (image.name === url.filename) {/* pushed after other files */}
+ else if (!image.url && f.match(/\.image$/)) {
+ image.name = url.filename;
+ image.url = url.full;
+ } else {
+ files.push({url: url.full, name: url.filename});
+ }
+ });
+ }
+ if (options.zip) {
+ var zips = typeof options.zip === "string" ? [options.zip] : options.zip;
+ zips.forEach(function(zip) {
+ var url = Squeak.splitUrl(zip, baseUrl);
+ var prefix = "";
+ // if filename has no version info, but full url has it, use full url as prefix
+ if (!url.filename.match(/[0-9]/) && url.uptoslash.match(/[0-9]/)) {
+ prefix = url.uptoslash.replace(/^[^:]+:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_");
+ }
+ files.push({url: url.full, name: prefix + url.filename, zip: true});
+ });
+ }
+ if (image.url) files.push(image);
+ if (options.document) {
+ var url = Squeak.splitUrl(options.document, baseUrl);
+ files.push({url: url.full, name: url.filename, forceDownload: options.forceDownload !== false});
+ display.documentName = options.root + url.filename;
+ }
+ options.image = image;
+ fetchFiles(files, display, options, function thenDo() {
+ Squeak.fsck(); // will run async
+ var image = options.image;
+ if (!image.name) return alert("could not find an image");
+ if (!image.data) return alert("could not find image " + image.name);
+ SqueakJS.appName = options.appName || image.name.replace(/(.*\/|\.image$)/g, "");
+ SqueakJS.runImage(image.data, options.root + image.name, display, options);
+ });
+ return display;
+};
+
+SqueakJS.quitSqueak = function() {
+ SqueakJS.vm.quitFlag = true;
+};
+
+SqueakJS.onQuit = function(vm, display, options) {
+ window.onbeforeunload = null;
+ display.vm = null;
+ if (options.spinner) options.spinner.style.display = "none";
+ if (options.onQuit) options.onQuit(vm, display, options);
+ else display.showBanner(SqueakJS.appName + " stopped.");
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// browser stuff
+//////////////////////////////////////////////////////////////////////////////
+
+if (window.applicationCache) {
+ applicationCache.addEventListener('updateready', function() {
+ // use original appName from options
+ var appName = window.SqueakJS && SqueakJS.options && SqueakJS.options.appName || "SqueakJS";
+ if (confirm(appName + ' has been updated. Restart now?')) {
+ window.onbeforeunload = null;
+ window.location.reload();
+ }
+ });
+}
diff --git a/Core/setting.py b/Core/setting.py
new file mode 100644
index 0000000..0ce965f
--- /dev/null
+++ b/Core/setting.py
@@ -0,0 +1,10 @@
+# My first python file in this static folder.
+
+# I hope this is good. With Coding Giants I can learn more about this.
+
+# No.1: I'm making sure this executes correctly. I'm doing some print statements for test purposes... | No.2: I will add this to the index.html file. That file is out of the static folder. | No.3: Outside, there are just .html files, but why...?
+
+Setting_Variable = "this is some useless setting variable."
+print("Setting Variable loaded here.")
+for x in Setting_Variable:
+ print(x)
\ No newline at end of file
diff --git a/Core/yarn.mas b/Core/yarn.mas
new file mode 100644
index 0000000..5ab8c27
--- /dev/null
+++ b/Core/yarn.mas
@@ -0,0 +1,12 @@
+import === "./Core/yarn.roy"
+
+
+(--[[
+ cpu(GetCore,yarn) -- {
+ const(yarn) == "GetCoreFileRegisterSystem", 0
+ }
+ ;ranks -- {
+ Owner === ("arancia313",(0)yarn)
+ }
+]end]
+).(Mas)
diff --git a/Core/yarn.roy b/Core/yarn.roy
new file mode 100644
index 0000000..7eeb0c8
--- /dev/null
+++ b/Core/yarn.roy
@@ -0,0 +1 @@
+;print("yo")
\ No newline at end of file
diff --git a/README.md b/README.md
index baa2ad5..12238e0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
# BarnyWarp
BarnyWarp is a Arancia 3 Mod that compiles the projects in HTML.
-# Warning
-There's no a website, so you can download it.
+# What...
+What i was thinking??!! OF COURSE THERE IS A WEBSITE!!! you don't need to download anymore!
diff --git a/Recovery.txt b/Recovery.txt
new file mode 100644
index 0000000..72251e2
--- /dev/null
+++ b/Recovery.txt
@@ -0,0 +1,370 @@
+
+
+
+
+
+ BarnyWarp - Organizza. Programma. Inventa.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Recent Commits
+
+
+
Welcome to the page of BarnyWarp.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Space Ambience.wav b/Space Ambience.wav
new file mode 100644
index 0000000..e3a5ad0
Binary files /dev/null and b/Space Ambience.wav differ
diff --git a/ToothLess/R.A.T.txt b/ToothLess/R.A.T.txt
deleted file mode 100644
index 629af8c..0000000
--- a/ToothLess/R.A.T.txt
+++ /dev/null
@@ -1 +0,0 @@
-In the past, Spike has created a bug in the secure folder. That's because of his errors. He overheats the secure folder for doing a single error.
\ No newline at end of file
diff --git a/ToothLess/README.md b/ToothLess/README.md
deleted file mode 100644
index 0c24270..0000000
--- a/ToothLess/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# ToothLess
-
diff --git a/Windex Antivirus.html b/Windex Antivirus.html
deleted file mode 100644
index bd6da07..0000000
--- a/Windex Antivirus.html
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
- Windex Antivirus
-
-
-
-
Windex Antivirus
-
Windex Antivirus is a free program that protects you from malware, viruses, and other threats. It's easy to use and it contains secured Clogs. Windex Antivirus is a BarnyWarp mod.
-
-
Your connection is secured
-
-
-
\ No newline at end of file
diff --git a/Windex.html b/Windex.html
deleted file mode 100644
index 654702c..0000000
--- a/Windex.html
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-BarnyWarp
-
-