Skip to content

Commit 46c5715

Browse files
committed
Merge branch 'bidirectional-web-editor' into code-view
This fixes the bidirectional web editor endpoint, as well as incorperating code views into its interface
2 parents 44c2da0 + 5795c93 commit 46c5715

File tree

11 files changed

+399
-152
lines changed

11 files changed

+399
-152
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ if flashing from a Mac] -- Ubuntu doesn't have a good kernel for Pi 5)
6060
If no `folk` user, then:
6161

6262
sudo useradd -m folk; sudo passwd folk;
63+
64+
After creating `folk` user, then:
65+
6366
for group in adm dialout cdrom sudo audio video plugdev games users input tty render netdev lpadmin gpio i2c spi; do sudo usermod -a -G $group folk; done; groups folk
6467

6568
1. `sudo apt update`

lib/folk.js

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// const WHITESPACE = /^[ \t]+/;
22
// const NEWLINE = /^[;\n]+/;
3-
const WORDSEP = /^[ \t;\n]+/;
3+
const WORDSEP = /^[ \t\n\r\f\v]+/;
44
const QUOTED = /^".*?[^\\]"/;
5-
const WORD = /^([^ \t;\n{}[\]"\\]|\\[ \t;\n{}[\]"\\])+/;
6-
const ESCAPED = /\\(.)/g;
7-
const ESCAPABLE = /([ \t;\n{}[\]"])/g;
5+
const WORD = /^([^ \t\n\r\f\v{}\\]|\\.)+/;
86

97
const eat = (regex, str) => {
108
const match = str.match(regex);
@@ -34,26 +32,84 @@ const eatBrace = (str) => {
3432
};
3533

3634
const checkBrace = (str) => {
37-
let bc = 0;
38-
let bk = 0;
35+
let curlies = 0;
36+
let brackets = 0;
3937
const l = str.length;
4038
for (let i = 0; i < l; i++) {
4139
switch (str[i]) {
42-
case '{': bc++; break;
43-
case '}': bc--; break;
44-
case '[': bk++; break;
45-
case ']': bk--; break;
40+
case '{': curlies++; break;
41+
case '}': curlies--; break;
42+
case '[': brackets++; break;
43+
case ']': brackets--; break;
44+
}
45+
46+
if (curlies < 0) return false;
47+
}
48+
49+
return curlies === 0 && brackets >= 0;
50+
};
51+
52+
// ported from JimEscape in jim.c
53+
const UNESCAPE_TABLE = {
54+
'a': '\a',
55+
'b': '\b',
56+
'n': '\n',
57+
'r': '\r',
58+
't': '\t',
59+
'f': '\f',
60+
'v': '\v',
61+
'0': '\x00',
62+
};
63+
64+
const unescape = (str) => {
65+
let output = "";
66+
67+
for (let i = 0; i < str.length; i++) {
68+
// used to prevent out-of-bounds issues with `str[i + 1]`
69+
if (i === str.length - 1) {
70+
output += str[i];
71+
break;
4672
}
4773

48-
if (bc < 0) return false;
49-
if (bk < 0) return false;
74+
if (str[i] === '\\') {
75+
// is the next character escapable?
76+
const unescaped = UNESCAPE_TABLE[str[i + 1]];
77+
if (unescaped) {
78+
output += unescaped;
79+
} else {
80+
// else just append it, sans the backslash
81+
output += str[i + 1];
82+
}
83+
84+
i++;
85+
// TODO: octal, \x \u \U \u{NNN}
86+
} else {
87+
output += str[i];
88+
}
5089
}
5190

52-
return bc === 0 && bk === 0;
91+
return output;
92+
};
93+
94+
// ported from BackslashQuoteString in jim.c
95+
const ESCAPE_TABLE = {
96+
' ': '\\ ',
97+
'$': '\\$',
98+
'"': '\\"',
99+
'[': '\\[',
100+
']': '\\]',
101+
'{': '\\{',
102+
'}': '\\}',
103+
';': '\\;',
104+
'\\': '\\\\',
105+
'\n': '\\n',
106+
'\r': '\\r',
107+
'\t': '\\t',
108+
'\f': '\\f',
109+
'\v': '\\v',
53110
};
54111

55-
const unescape = (str) => str.replaceAll(ESCAPED, '$1');
56-
const escape = (str) => str.replaceAll(ESCAPABLE, '\\$1');
112+
const escape = (str) => str.split("").map(char => char in ESCAPE_TABLE ? ESCAPE_TABLE[char] : char).join("");
57113

58114
// deserializtion
59115
const nextToken = (str) => {
@@ -69,14 +125,19 @@ const nextToken = (str) => {
69125
case ' ':
70126
case '\t':
71127
case '\n':
72-
case ';':
128+
case '\r':
129+
case '\f':
130+
case '\v':
73131
return [token, str];
74132

75-
case '[':
76-
throw new Error("this aint no interpreter");
77133
case '{':
78134
[word, str] = eatBrace(str);
79-
token += word.slice(1, -1);
135+
136+
if (token === '') {
137+
token = word.slice(1, -1);
138+
} else {
139+
token += word;
140+
}
80141
break;
81142
case '"':
82143
[word, str] = eat(QUOTED, str);
@@ -127,8 +188,13 @@ const loadDict = (str) => {
127188
const dumpString = (word) => {
128189
if (!word.length) return '{}';
129190
const match = word.match(WORD);
130-
if (match && match[0] === word) return word;
131-
if (checkBrace(word)) return `{${word}}`;
191+
if (word[0] !== "#" && match && match[0] === word) return word;
192+
if (checkBrace(word)) {
193+
// the one case that we can't handle with braces is a linefeed escape
194+
if (!(/\\\n/.test(word))) {
195+
return `{${word}}`;
196+
}
197+
}
132198
return escape(word);
133199
}
134200

@@ -280,7 +346,7 @@ class FolkWS {
280346
const channel = this.createChannel((message) => {
281347
const [action, match, matchId] = loadList(message);
282348
const callback = callbacks[action];
283-
callback && callback(loadDict(match), matchId);
349+
if (callback) callback(loadDict(match), matchId);
284350
});
285351

286352
const retractKey = await this.send(tcl`

virtual-programs/calibrate/calibrate.folk

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -487,22 +487,26 @@ Wish the web server handles route "/calibrate$" with handler [list apply {{UNIT_
487487
});
488488

489489
function advanceCamera() {
490-
cameraFrame.src = cameraFrame.src + '0'
490+
cameraFrame.src = '/camera-frame?' + Math.random()
491491
}
492492
</script>
493493

494494
<p>Use this camera preview to debug why printed and/or projected tags aren't being recognized (maybe overexposure, maybe your camera isn't in a good position): <button id="refreshButton" onclick="advanceCamera()">Refresh Preview</button> <input type="checkbox" value="true" id="auto-refresh-checkbox" checked>
495-
<label for="auto-refresh-checkbox">Automatically refresh preview (May not work well during calibration)</label> </p><br> <img src="/camera-frame?0" id="cameraFrame" style="max-width: 100%">
495+
<label for="auto-refresh-checkbox">Automatically refresh preview</label> </p><br> <img src="/camera-frame" id="cameraFrame" style="max-width: 100%">
496496

497497
<script>
498498
const refreshButton = document.getElementById('refreshButton');
499499
const autoRefreshCheckbox = document.getElementById('auto-refresh-checkbox');
500-
setInterval(() => {
501-
if (autoRefreshCheckbox.checked) {
502-
refreshButton.click()
503-
}
504-
console.log("checked?", autoRefreshCheckbox.checked)
505-
}, 100)
500+
cameraFrame.addEventListener('load', () => {
501+
if (autoRefreshCheckbox.checked) {
502+
advanceCamera();
503+
}
504+
});
505+
autoRefreshCheckbox.addEventListener('change', () => {
506+
if (autoRefreshCheckbox.checked) {
507+
advanceCamera();
508+
}
509+
});
506510
</script>
507511

508512

virtual-programs/camera-usb.folk

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,11 @@ When /someone/ wishes $::thisNode uses camera /cameraPath/ with /...options/ {
349349
set camera [Camera::new $cameraPath $width $height $bufferCount]
350350

351351
# TODO: report actual width and height from v4l2
352-
Claim camera $cameraPath has width $width height $height
352+
if {[info exists crop]} {
353+
Claim camera $cameraPath has width [dict get $crop width] height [dict get $crop height]
354+
} else {
355+
Claim camera $cameraPath has width $width height $height
356+
}
353357

354358
puts "camera-usb: $cameraPath ($options) (tid [getTid]) booted at [clock milliseconds]"
355359

virtual-programs/code-view.folk

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,10 @@ When /someone/ claims /view/ is a code view {
4141
When /view/ is a code view with /...options/ {
4242
set options [dict merge $codeView::defaults $options]
4343

44-
# select which program the view is viewing
45-
When $view points up at /program/ {
46-
Hold selected-program:$view {
47-
Claim $view has selected program $program
48-
}
49-
}
50-
51-
# reset selected program when it's not pointing at anything
52-
When /nobody/ claims $view points up at /anything/ {
53-
Hold selected-program:$view {}
44+
# reset selected program when it's not pointing at any program (or pointing at a wormhole)
45+
When $view points up at /program/ &\
46+
/nobody/ claims /program/ is a wormhole entry {
47+
Claim $view has selected program $program
5448
}
5549

5650
# load in defaults for the view if it hasn't been initialized
@@ -94,12 +88,12 @@ When /view/ is a code view with /...options/ {
9488
When /keyboard/ is a keyboard with path /kbPath/ /...anything/ &\
9589
/nobody/ claims /keyboard/ is an editor &\
9690
/keyboard/ points up at /view/ &\
97-
/view/ is a code view with /...anything/ {
91+
/view/ is a code view with /...anything/ &\
92+
/view/ has selected program /program/ {
9893
# TODO: is there a way to make this `Every time` less of a beast?
9994
Every time keyboard $kbPath claims key /key/ is /keyState/ with /...options/ &\
10095
$view has view dimensions /dims/ &\
101-
$view has selected program /program/ &\
102-
view code for /program/ is /code/ &\
96+
view code for $program is /code/ &\
10397
$view has max cursor x /maxCursorX/ &\
10498
$view has cursor /cursor/ &\
10599
$view has viewport scale with width /widthScale/ height /heightScale/ &\
@@ -127,22 +121,8 @@ When /keyboard/ is a keyboard with path /kbPath/ /...anything/ &\
127121
set resetCode true
128122
}
129123
Control_s {
130-
Hold new-code-for:$program {
131-
Wish program $program is replaced with code $code
132-
Wish $program is titled "EDITED"
133-
}
134-
135-
set fp [open "$::env(HOME)/folk-printed-programs/$program.folk.edited" w]
136-
puts -nonewline $fp $code
137-
close $fp
138-
139-
# give the user some feedback
140-
Hold saved-alert:$keyboard {
141-
Wish $keyboard is labelled "Saved!"
142-
}
143-
144-
After 250 milliseconds {
145-
Hold saved-alert:$keyboard {}
124+
Hold saving-code-for:$view {
125+
Wish to save code on view $view
146126
}
147127
}
148128
Control_p {
@@ -222,6 +202,37 @@ When /keyboard/ is a keyboard with path /kbPath/ /...anything/ &\
222202
}
223203
}
224204

205+
# TODO: replace with event firing
206+
When /someone/ wishes to save code on view /view/ &\
207+
/view/ has selected program /program/ &\
208+
view code for /program/ is /programCode/ {
209+
Hold new-code-for:$program {
210+
Wish program $program is replaced with code $programCode
211+
}
212+
213+
set fp [open "$::env(HOME)/folk-printed-programs/$program.folk.edited" w]
214+
puts -nonewline $fp $programCode
215+
close $fp
216+
217+
Hold saving-code-for:$view {}
218+
219+
# code may be saved even without an attached keyboard, hence
220+
# feedback being optional
221+
# TODO: maybe move feedback to code view instead?
222+
When /keyboard/ is a keyboard with path /kbPath/ /...anything/ &\
223+
/nobody/ claims /keyboard/ is an editor &\
224+
/keyboard/ points up at $view {
225+
# give the user some feedback
226+
Hold saved-alert:$keyboard {
227+
Wish $keyboard is labelled "Saved!"
228+
}
229+
230+
After 250 milliseconds {
231+
Hold saved-alert:$keyboard {}
232+
}
233+
}
234+
}
235+
225236
# calculate cursor position
226237
When /view/ is a code view with /...anything/ &\
227238
/view/ has cursor /cursor/ &\

virtual-programs/connections.folk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ When /anyone/ wishes /source/ is dynamically connected to /sink/ /...options/ &
6363
for {set p $offset} {$p < $distance} {incr p $spacing} {
6464
set c [vec2 add $source [vec2 scale $direction $p]]
6565
set s [expr {min($maxsize, 0.20*min($p, $distance - $p))}]
66-
Wish to draw a shape with sides 3 center $c radius $s radians $angle color $color filled true layer $layer
66+
Wish to draw a shape with sides 3 center $c radius $s angle $angle color $color filled true layer $layer
6767
}
6868
}
6969
}

0 commit comments

Comments
 (0)