Adding jotblk sample.
A web editor that embeds uEforth.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
out
|
out
|
||||||
build.ninja
|
build.ninja
|
||||||
|
attic/jotblk/env
|
||||||
|
|||||||
12
attic/jotblk/Makefile
Executable file
12
attic/jotblk/Makefile
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
all:
|
||||||
|
./run.sh
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
gcloud app deploy -q --project jotblk *.yaml
|
||||||
|
|
||||||
|
setup:
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install -r requirements.txt
|
||||||
|
|
||||||
|
datastore:
|
||||||
|
echo "Run: . dstore.sh"
|
||||||
20
attic/jotblk/README.md
Normal file
20
attic/jotblk/README.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# jotblk
|
||||||
|
|
||||||
|
A web based block based editor to run on AppEngine.
|
||||||
|
|
||||||
|
To run you need the AppEngine SDK.
|
||||||
|
|
||||||
|
To setup run:
|
||||||
|
```
|
||||||
|
make setup
|
||||||
|
```
|
||||||
|
|
||||||
|
In parallel run (for the datastore):
|
||||||
|
```
|
||||||
|
. dstore.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
And separately (to run a webserver on http://localhost:8080):
|
||||||
|
```
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
17
attic/jotblk/app.yaml
Normal file
17
attic/jotblk/app.yaml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
runtime: python39
|
||||||
|
|
||||||
|
default_expiration: "10m"
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /
|
||||||
|
static_files: static/index.html
|
||||||
|
upload: static/index.html
|
||||||
|
secure: always
|
||||||
|
|
||||||
|
- url: /static
|
||||||
|
static_dir: static
|
||||||
|
secure: always
|
||||||
|
|
||||||
|
- url: /io
|
||||||
|
script: main.app
|
||||||
|
secure: always
|
||||||
2
attic/jotblk/dstore.sh
Normal file
2
attic/jotblk/dstore.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Run this to start an datastore emulator.
|
||||||
|
gcloud emulators firestore start --database-mode=datastore-mode --project=jotblk --host-port=127.0.0.1:8099
|
||||||
81
attic/jotblk/main.py
Normal file
81
attic/jotblk/main.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Copyright 2025 Bradley D. Nelson
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
from flask import Flask, request, send_from_directory
|
||||||
|
from google.cloud import datastore
|
||||||
|
|
||||||
|
client = datastore.Client()
|
||||||
|
|
||||||
|
root_passwd_key = client.key('passwd', 'root')
|
||||||
|
root_passwd_entity = client.get(root_passwd_key)
|
||||||
|
if root_passwd_entity is None:
|
||||||
|
root_passwd = 'xyzzy'
|
||||||
|
else:
|
||||||
|
root_passwd = root_passwd_entity['secret']
|
||||||
|
|
||||||
|
def SaveBlock(index, data):
|
||||||
|
assert index >= 0
|
||||||
|
assert len(data) == 1024, len(data)
|
||||||
|
entity = datastore.Entity(key=client.key('block', index+1))
|
||||||
|
entity.update({'data': data})
|
||||||
|
client.put(entity)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def LoadBlocks(start, end):
|
||||||
|
assert end >= end
|
||||||
|
assert start >= 0
|
||||||
|
assert end - start <= 128
|
||||||
|
query = client.query(kind='block')
|
||||||
|
first_key = client.key('block', start+1)
|
||||||
|
last_key = client.key('block', end+1)
|
||||||
|
query.key_filter(first_key, '>=')
|
||||||
|
query.key_filter(last_key, '<')
|
||||||
|
entities = query.fetch()
|
||||||
|
blks = {}
|
||||||
|
for i in entities:
|
||||||
|
blks[i.key.path[0]['id']-1] = i['data']
|
||||||
|
result = []
|
||||||
|
for i in range(start, end+1):
|
||||||
|
if i in blks:
|
||||||
|
result.append(blks[i])
|
||||||
|
else:
|
||||||
|
result.append(b' ' * 1024)
|
||||||
|
return b''.join(result)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/<path:filename>')
|
||||||
|
def canned(filename):
|
||||||
|
return send_from_directory('static', filename)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
return canned('index.html')
|
||||||
|
|
||||||
|
@app.route('/io', methods=['POST'])
|
||||||
|
def io():
|
||||||
|
if root_passwd != request.form['passwd']:
|
||||||
|
return 'deny', 403
|
||||||
|
if request.form['command'] == 'read':
|
||||||
|
start = int(request.form['start'])
|
||||||
|
end = int(request.form['end'])
|
||||||
|
return LoadBlocks(start, end)
|
||||||
|
elif request.form['command'] == 'write':
|
||||||
|
index = int(request.form['index'])
|
||||||
|
data = request.files['data'].read()
|
||||||
|
return SaveBlock(index, data)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='127.0.0.1', port=8080, debug=True)
|
||||||
2
attic/jotblk/requirements.txt
Normal file
2
attic/jotblk/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Flask==3.0.0
|
||||||
|
google-cloud-datastore==2.15.1
|
||||||
8
attic/jotblk/run.sh
Executable file
8
attic/jotblk/run.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
export DATASTORE_DATASET=jotblk
|
||||||
|
export DATASTORE_EMULATOR_HOST=127.0.0.1:8099
|
||||||
|
export DATASTORE_EMULATOR_HOST_PATH=127.0.0.1:8099/datastore
|
||||||
|
export DATASTORE_HOST=http://127.0.0.1:8099
|
||||||
|
export DATASTORE_PROJECT_ID=jotblk
|
||||||
|
env/bin/python3 main.py
|
||||||
3
attic/jotblk/static/index.html
Normal file
3
attic/jotblk/static/index.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<body>
|
||||||
|
<script src="jotblk.js"></script>
|
||||||
562
attic/jotblk/static/jotblk.js
Normal file
562
attic/jotblk/static/jotblk.js
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
// Copyright 2025 Bradley D. Nelson
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const MAX_BLOCKS = 16 * 1024;
|
||||||
|
const SYNC_CHUNK = 64;
|
||||||
|
const DIRTY = 1;
|
||||||
|
const LOADED = 2;
|
||||||
|
var blocks = new Uint8Array(1024 * MAX_BLOCKS);
|
||||||
|
var flags = new Uint8Array(MAX_BLOCKS);
|
||||||
|
var clipboard = [];
|
||||||
|
var screen_history = [];
|
||||||
|
var keymap = {};
|
||||||
|
var scr = 0;
|
||||||
|
var pos = 0;
|
||||||
|
var marker = 0;
|
||||||
|
var passwd = '';
|
||||||
|
var ForthKeyDown = null;
|
||||||
|
var ForthKeyPress = null;
|
||||||
|
var forth_added = false;
|
||||||
|
var ueforth = null;
|
||||||
|
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
document.body.style.margin = '0';
|
||||||
|
document.body.style.border = '0';
|
||||||
|
document.body.style.padding = '0';
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
function SpaceIt() {
|
||||||
|
if (!(flags[scr] & LOADED)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (blocks[scr * 1024] == 0) {
|
||||||
|
for (var i = 0; i < 1024; ++i) {
|
||||||
|
blocks[i + scr * 1024] = 32;
|
||||||
|
}
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsEmpty(n) {
|
||||||
|
for (var i = 0; i < 1024; i++) {
|
||||||
|
var ch = blocks[i + n * 1024];
|
||||||
|
if (ch != 32 && ch != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FindEnd() {
|
||||||
|
var i = pos;
|
||||||
|
while (i < 1023 &&
|
||||||
|
(blocks[scr * 1024 + i] != 32 ||
|
||||||
|
blocks[scr * 1024 + i + 1] != 32)) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Insert() {
|
||||||
|
var end = FindEnd();
|
||||||
|
for (var i = end; i >= pos; i--) {
|
||||||
|
blocks[scr * 1024 + i + 1] = blocks[scr * 1024 + i];
|
||||||
|
}
|
||||||
|
blocks[scr * 1024 + pos] = 32;
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Delete() {
|
||||||
|
var end = FindEnd();
|
||||||
|
for (var i = pos; i < end; i++) {
|
||||||
|
blocks[scr * 1024 + i] = blocks[scr * 1024 + i + 1];
|
||||||
|
}
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FindSpan() {
|
||||||
|
var i = Math.floor(pos / 64) * 64;
|
||||||
|
var min = 63;
|
||||||
|
var max = 0;
|
||||||
|
for (var j = 0; j < 64; j++) {
|
||||||
|
if (blocks[scr * 1024 + i + j] != 32) {
|
||||||
|
min = Math.min(min, j);
|
||||||
|
max = Math.max(max, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (min > max) {
|
||||||
|
return [i, i + 63];
|
||||||
|
}
|
||||||
|
return [i + min, i + max];
|
||||||
|
}
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
var span = FindSpan();
|
||||||
|
var start = Math.floor(pos / 64) * 64;
|
||||||
|
if (pos == span[0]) {
|
||||||
|
pos = start;
|
||||||
|
} else {
|
||||||
|
pos = span[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function End() {
|
||||||
|
var span = FindSpan();
|
||||||
|
var end = Math.floor(pos / 64) * 64 + 63;
|
||||||
|
var span1 = Math.min(span[1] + 1, end);
|
||||||
|
if (pos == span1) {
|
||||||
|
pos = end;
|
||||||
|
} else {
|
||||||
|
pos = span1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Up() {
|
||||||
|
if (pos >= 64) {
|
||||||
|
pos -= 64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Down() {
|
||||||
|
if (pos + 64 < 1024) {
|
||||||
|
pos += 64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Left() {
|
||||||
|
pos = Math.max(0, pos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Right() {
|
||||||
|
pos = Math.min(1023, pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Copy() {
|
||||||
|
var row = Math.floor(pos / 64) * 64;
|
||||||
|
clipboard.push(blocks.slice(scr * 1024 + row, scr * 1024 + row + 64));
|
||||||
|
Up();
|
||||||
|
}
|
||||||
|
|
||||||
|
function Cut() {
|
||||||
|
var row = Math.floor(pos / 64) * 64;
|
||||||
|
clipboard.push(blocks.slice(scr * 1024 + row, scr * 1024 + row + 64));
|
||||||
|
for (var j = 0; j < 64; j++) {
|
||||||
|
blocks[scr * 1024 + row + j] = 32;
|
||||||
|
}
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
Up();
|
||||||
|
}
|
||||||
|
|
||||||
|
function Paste() {
|
||||||
|
if (clipboard.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var row = Math.floor(pos / 64) * 64;
|
||||||
|
var data = clipboard.pop();
|
||||||
|
for (var j = 0; j < 64; j++) {
|
||||||
|
blocks[scr * 1024 + row + j] = data[j];
|
||||||
|
}
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
Down();
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update() {
|
||||||
|
SpaceIt();
|
||||||
|
ctx.fillStyle = 'black';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.font = '16px consolas, Monaco, monospace';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.save();
|
||||||
|
ctx.scale(canvas.width / 64 , canvas.height / 17);
|
||||||
|
for (var j = 0; j < 16; ++j) {
|
||||||
|
for (var i = 0; i < 64; ++i) {
|
||||||
|
if (pos === i + j * 64) {
|
||||||
|
ctx.fillStyle = '#750';
|
||||||
|
ctx.fillRect(i, j, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.save();
|
||||||
|
var m = ctx.measureText('W');
|
||||||
|
var w = m.width;
|
||||||
|
var h = m.fontBoundingBoxAscent + m.fontBoundingBoxDescent;
|
||||||
|
ctx.scale(1 / w, 1 / h);
|
||||||
|
for (var j = 0; j < 16; ++j) {
|
||||||
|
for (var i = 0; i < 64; ++i) {
|
||||||
|
var ch = String.fromCharCode(blocks[i + j * 64 + scr * 1024]);
|
||||||
|
ctx.fillStyle = '#fb0';
|
||||||
|
ctx.fillText(ch, (i + 0.5) * w, (j + 0.5) * h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.fillStyle = '#750';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
var info = '';
|
||||||
|
if (flags[scr] & DIRTY) {
|
||||||
|
info += 'D ';
|
||||||
|
}
|
||||||
|
if (!(flags[scr] & LOADED)) {
|
||||||
|
info += 'L ';
|
||||||
|
}
|
||||||
|
info += scr;
|
||||||
|
ctx.fillText(info, 63.5 * w, 16.5 * h);
|
||||||
|
ctx.restore();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadChunk(n) {
|
||||||
|
if ((flags[n * SYNC_CHUNK] & LOADED)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
return LoadBlocks(n * SYNC_CHUNK, (n + 1) * SYNC_CHUNK).then(function() {
|
||||||
|
Update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MaybeLoad() {
|
||||||
|
var s = Math.floor(scr / SYNC_CHUNK);
|
||||||
|
return LoadChunk(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LineString(blk, row) {
|
||||||
|
var result = '';
|
||||||
|
for (var col = 0; col < 64; col++) {
|
||||||
|
result += String.fromCharCode(blocks[blk * 1024 + row * 64 + col]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function BlockString(blk, linebreaks) {
|
||||||
|
var result = '';
|
||||||
|
for (var row = 0; row < 16; row++) {
|
||||||
|
result += LineString(blk, row);
|
||||||
|
if (linebreaks) {
|
||||||
|
result += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Eval(n) {
|
||||||
|
eval(BlockString(n, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
function Print() {
|
||||||
|
var start = Math.min(marker, scr);
|
||||||
|
var end = Math.max(marker, scr);
|
||||||
|
var content = '';
|
||||||
|
content += '<!DOCTYPE html>\n';
|
||||||
|
for (var i = start; i <= end; i++) {
|
||||||
|
content += '<pre style="border: 1px solid; display: inline-block;">\n';
|
||||||
|
content += BlockString(i, true);
|
||||||
|
content += '<hr/>' + i;
|
||||||
|
content += '</pre><br/>\n';
|
||||||
|
}
|
||||||
|
var blob = new Blob([content], { type: 'text/html' });
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function Backspace() {
|
||||||
|
if (pos > 0) {
|
||||||
|
--pos;
|
||||||
|
Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Goto(n) {
|
||||||
|
scr = n;
|
||||||
|
MaybeLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
function Gosub(n) {
|
||||||
|
screen_history.push(scr);
|
||||||
|
Goto(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Adjust(n) {
|
||||||
|
Goto(Math.max(0, Math.min(MAX_BLOCKS - 1, scr + n)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function Enter() {
|
||||||
|
pos = Math.floor((pos + 64) / 64) * 64;
|
||||||
|
if (pos > 1023) {
|
||||||
|
pos -= 64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShiftUp() {
|
||||||
|
pos = (pos % 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShiftDown() {
|
||||||
|
pos = (pos % 64) + 15 * 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetLink() {
|
||||||
|
var s = BlockString(scr, false);
|
||||||
|
var at = s.indexOf('@', pos);
|
||||||
|
var paren = s.indexOf(' )', pos);
|
||||||
|
if (at >= 0 && (at < paren || paren < 0)) {
|
||||||
|
return s.slice(at).split(' ')[0];
|
||||||
|
}
|
||||||
|
if (paren >= 0) {
|
||||||
|
var p2 = s.lastIndexOf('( ', paren);
|
||||||
|
if (p2 >= 0) {
|
||||||
|
return s.slice(p2, paren + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function Find(s) {
|
||||||
|
for (var i = 0; i < MAX_BLOCKS; i++) {
|
||||||
|
var j = (scr + 1 + i) % MAX_BLOCKS;
|
||||||
|
if (BlockString(j, false).indexOf(s) >= 0) {
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FollowLink() {
|
||||||
|
var link = GetLink();
|
||||||
|
if (link.startsWith('@')) {
|
||||||
|
var n = parseInt(link.slice(1));
|
||||||
|
Gosub(n);
|
||||||
|
} else if (link.startsWith('( ')) {
|
||||||
|
var n = Find(link.slice(2, -2));
|
||||||
|
if (n !== null) {
|
||||||
|
Gosub(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Type(ch) {
|
||||||
|
Insert();
|
||||||
|
blocks[pos + scr * 1024] = ch;
|
||||||
|
pos = Math.min(1023, pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Key(e) {
|
||||||
|
if (e.ctrlKey && keymap['^' + e.key]) {
|
||||||
|
keymap['^' + e.key](e);
|
||||||
|
} else if (e.shiftKey && keymap['+' + e.key]) {
|
||||||
|
keymap['+' + e.key](e);
|
||||||
|
} else if (keymap[e.key]) {
|
||||||
|
keymap[e.key](e);
|
||||||
|
} else if (e.key.length == 1 && !e.ctrlKey) {
|
||||||
|
Type(e.key.charCodeAt(0));
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Update();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Login(e) {
|
||||||
|
if (e.key == 'Backspace') {
|
||||||
|
passwd = passwd.slice(0, -1);
|
||||||
|
} else if (e.key == 'Enter') {
|
||||||
|
window.onkeydown = Key;
|
||||||
|
MaybeLoad().then(function() {
|
||||||
|
Eval(63);
|
||||||
|
});
|
||||||
|
} else if (e.key.length == 1) {
|
||||||
|
passwd += e.key;
|
||||||
|
}
|
||||||
|
Update();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Resize() {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function SaveBlock(i) {
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('command', 'write');
|
||||||
|
fd.append('passwd', passwd);
|
||||||
|
fd.append('index', i);
|
||||||
|
fd.append('data', new Blob([blocks.slice(i * 1024, (i + 1) * 1024)],
|
||||||
|
{type: "application/octet-stream"}));
|
||||||
|
return fetch('/io', {'method': 'POST', body: fd}).then(function() {
|
||||||
|
flags[i] &= ~DIRTY;
|
||||||
|
Update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function Sync() {
|
||||||
|
for (var i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if ((flags[i] & LOADED) && (flags[i] & DIRTY)) {
|
||||||
|
SaveBlock(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FindEmpty() {
|
||||||
|
screen_history.push(scr);
|
||||||
|
while (!IsEmpty(scr)) {
|
||||||
|
Adjust(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function StampLog() {
|
||||||
|
var now = new Date();
|
||||||
|
var month = ('0' + (1 + now.getMonth())).slice(-2);
|
||||||
|
var day = ('0' + now.getDate()).slice(-2);
|
||||||
|
var dt = now.getFullYear() + '-' + month + '-' + day;
|
||||||
|
for (var i = 0; i < dt.length; i++) {
|
||||||
|
blocks[54 + i + scr * 1024] = dt.charCodeAt(i);
|
||||||
|
}
|
||||||
|
var mark = 'LOG: ';
|
||||||
|
for (var i = 0; i < mark.length; i++) {
|
||||||
|
blocks[i + scr * 1024] = mark.charCodeAt(i);
|
||||||
|
}
|
||||||
|
pos = mark.length;
|
||||||
|
flags[scr] |= DIRTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Back() {
|
||||||
|
if (screen_history.length) {
|
||||||
|
Goto(screen_history.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function LoadBlocks(start, end) {
|
||||||
|
var fd = new FormData();
|
||||||
|
fd.append('command', 'read');
|
||||||
|
fd.append('passwd', passwd);
|
||||||
|
fd.append('start', start);
|
||||||
|
fd.append('end', end);
|
||||||
|
return fetch('/io', {'method': 'POST', body: fd}).then(function(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw 'bad fetch';
|
||||||
|
}
|
||||||
|
return response.arrayBuffer().then(function(data) {
|
||||||
|
var u8 = new Uint8Array(data);
|
||||||
|
if (u8.length != (end + 1 - start) * 1024) {
|
||||||
|
throw 'bad load';
|
||||||
|
}
|
||||||
|
for (var i = start; i < end; i++) {
|
||||||
|
if (flags[i] & LOADED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dst = i * 1024;
|
||||||
|
var src = (i - start) * 1024;
|
||||||
|
for (var j = 0; j < 1024; j++) {
|
||||||
|
blocks[j + dst] = u8[j + src];
|
||||||
|
}
|
||||||
|
flags[i] |= LOADED;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ForthKeyFilter(e) {
|
||||||
|
if (e.key == 'f' && e.ctrlKey) {
|
||||||
|
ToggleForth();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ForthKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleForth() {
|
||||||
|
if (!forth_added) {
|
||||||
|
forth_added = true;
|
||||||
|
var fscript = document.createElement('script');
|
||||||
|
fscript.src = 'myforth.fs';
|
||||||
|
fscript.type = 'text/forth';
|
||||||
|
document.body.appendChild(fscript);
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = 'https://eforth.appspot.com/ueforth.js';
|
||||||
|
document.body.appendChild(script);
|
||||||
|
function Loader() {
|
||||||
|
if (ueforth !== null) {
|
||||||
|
ueforth.Start();
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
setTimeout(function() {
|
||||||
|
ForthKeyDown = window.onkeydown;
|
||||||
|
ForthKeyPress = window.onkeypress;
|
||||||
|
window.onkeydown = ForthKeyFilter;
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
setTimeout(Loader, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (window.onkeydown === Key) {
|
||||||
|
window.onkeydown = ForthKeyFilter;
|
||||||
|
window.onkeypress = ForthKeyPress;
|
||||||
|
canvas.style.display = 'none';
|
||||||
|
ueforth.screen.style.display = '';
|
||||||
|
} else {
|
||||||
|
window.onkeydown = Key;
|
||||||
|
window.onkeypress = null;
|
||||||
|
ueforth.screen.style.display = 'none';
|
||||||
|
canvas.style.display = '';
|
||||||
|
Resize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Init() {
|
||||||
|
keymap['Delete'] = Delete;
|
||||||
|
keymap['Backspace'] = Backspace;
|
||||||
|
keymap['PageUp'] = function() { Adjust(-1); };
|
||||||
|
keymap['PageDown'] = function() { Adjust(1); };
|
||||||
|
keymap['+PageUp'] = function() { Adjust(-16); };
|
||||||
|
keymap['+PageDown'] = function() { Adjust(16); };
|
||||||
|
keymap['Home'] = Home;
|
||||||
|
keymap['End'] = End;
|
||||||
|
keymap['Enter'] = Enter;
|
||||||
|
keymap['^Enter'] = FollowLink;
|
||||||
|
keymap['ArrowUp'] = Up;
|
||||||
|
keymap['ArrowDown'] = Down;
|
||||||
|
keymap['ArrowLeft'] = Left;
|
||||||
|
keymap['ArrowRight'] = Right;
|
||||||
|
keymap['+ArrowUp'] = ShiftUp;
|
||||||
|
keymap['+ArrowDown'] = ShiftDown;
|
||||||
|
keymap['+ArrowLeft'] = Home;
|
||||||
|
keymap['+ArrowRight'] = End;
|
||||||
|
keymap['^c'] = Copy;
|
||||||
|
keymap['^x'] = Cut;
|
||||||
|
keymap['^v'] = Paste;
|
||||||
|
keymap['^m'] = function() { marker = scr; };
|
||||||
|
keymap['^p'] = Print;
|
||||||
|
keymap['^g'] = function() { Eval(scr); };
|
||||||
|
keymap['^o'] = FindEmpty;
|
||||||
|
keymap['^l'] = StampLog;
|
||||||
|
keymap['^b'] = Back;
|
||||||
|
keymap['^f'] = ToggleForth;
|
||||||
|
keymap['^h'] = function() { Gosub(0); };
|
||||||
|
|
||||||
|
window.addEventListener('resize', Resize);
|
||||||
|
window.onkeydown = Login;
|
||||||
|
Resize();
|
||||||
|
}
|
||||||
|
Init();
|
||||||
|
|
||||||
|
setInterval(Sync, 3000);
|
||||||
43
attic/jotblk/static/myforth.fs
Normal file
43
attic/jotblk/static/myforth.fs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
\ Copyright 2021 Bradley D. Nelson
|
||||||
|
\
|
||||||
|
\ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
\ you may not use this file except in compliance with the License.
|
||||||
|
\ You may obtain a copy of the License at
|
||||||
|
\
|
||||||
|
\ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
\
|
||||||
|
\ Unless required by applicable law or agreed to in writing, software
|
||||||
|
\ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
\ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
\ See the License for the specific language governing permissions and
|
||||||
|
\ limitations under the License.
|
||||||
|
|
||||||
|
web definitions
|
||||||
|
|
||||||
|
JSWORD: block-read { n a -- }
|
||||||
|
for (var i = 0; i < 1024; i++) {
|
||||||
|
u8[a + i] = blocks[n * 1024 + i];
|
||||||
|
}
|
||||||
|
~
|
||||||
|
|
||||||
|
also internals
|
||||||
|
|
||||||
|
create block-buffer 1024 allot
|
||||||
|
|
||||||
|
also forth definitions
|
||||||
|
|
||||||
|
variable scr
|
||||||
|
: block ( n -- a ) block-buffer block-read block-buffer ;
|
||||||
|
: buffer ( n -- a ) block ;
|
||||||
|
|
||||||
|
only forth definitions
|
||||||
|
|
||||||
|
( Loading )
|
||||||
|
: load ( n -- ) block 1024 evaluate ;
|
||||||
|
: thru ( a b -- ) over - 1+ for aft dup >r load r> 1+ then next drop ;
|
||||||
|
|
||||||
|
( Listing )
|
||||||
|
: list ( n -- ) scr ! ." Block " scr @ . cr scr @ block
|
||||||
|
15 for dup 63 type [char] | emit space 15 r@ - . cr 64 + next drop ;
|
||||||
|
|
||||||
|
only forth definitions
|
||||||
Reference in New Issue
Block a user