Merge remote-tracking branch 'origin/release/next-dill' into release/next-sys

This commit is contained in:
Philip Monk 2020-11-17 21:54:50 -08:00
commit eadc63938c
No known key found for this signature in database
GPG Key ID: B66E1F02604E44EC
29 changed files with 670 additions and 607 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c6371d3d2e283b26e64b9045c42de9de9c5df424d5f114b268a83f4ceab1c75b
size 6302447
oid sha256:bd48196a12826df04820aac63e5bb4ee0035d4effb9d2eca9ff06f22156052e4
size 6313147

101
pkg/arvo/app/herm.hoon Normal file
View File

@ -0,0 +1,101 @@
:: herm: stand-in for term.c with http interface
::
/+ default-agent, dbug, verb
=, able:jael
|%
+$ state-0 [%0 ~]
--
::
=| state-0
=* state -
%+ verb |
%- agent:dbug
^- agent:gall
=> |%
++ request-tube
|= [bowl:gall from=mark to=mark next=?]
^- card:agent:gall
:* %pass /tube/[from]/[to]
%arvo %c %warp
our q.byk ~
::
?: next
[%next %c da+now /[from]/[to]]
[%sing %c da+now /[from]/[to]]
==
--
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card:agent:gall _this)
:_ this
:: set up dill session subscription,
:: and ensure the tubes we use are in cache
::
:~ [%pass [%view %$ ~] %arvo %d %view ~]
(request-tube bowl %blit %json |)
(request-tube bowl %json %belt |)
==
::
++ on-save !>([%0 ~])
++ on-load
|= old=vase
^- (quip card:agent:gall _this)
[~ this(state [%0 ~])]
::
++ on-watch
|= =path
^- (quip card:agent:gall _this)
?> ?=([%session @ ~] path)
:_ this
:: scry prompt and cursor position out of dill for initial response
::
=/ base=^path
/dx/(scot %p our.bowl)//(scot %da now.bowl)/sessions
:~ [%give %fact ~ %blit !>(.^(blit:dill (weld base //line)))]
[%give %fact ~ %blit !>(`blit:dill`hop+.^(@ud (weld base //cursor)))]
==
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card:agent:gall _this)
?+ wire !!
:: pass on dill blits for the session
::
[%view %$ ~]
?. ?=([%d %blit *] sign-arvo)
~| [%unexpected-sign [- +<]:sign-arvo]
!!
:_ this
%+ turn p.sign-arvo
|= =blit:dill
[%give %fact [%session %$ ~]~ %blit !>(blit)]
::
:: ensure the tubes we need remain in cache
::
[%tube @ @ ~]
=* from i.t.wire
=* to i.t.t.wire
?. ?=([%c %writ *] sign-arvo)
~| [%unexpected-sign [- +<]:sign-arvo]
!!
:_ this
[(request-tube bowl from to &)]~
==
::
++ on-poke
|= [=mark =vase]
^- (quip card:agent:gall _this)
?. ?=(%belt mark)
~| [%unexpected-mark mark]
!!
:_ this
[%pass [%belt %$ ~] %arvo %d %belt !<(belt:dill vase)]~
::
++ on-leave on-leave:def
++ on-peek on-peek:def
++ on-agent on-agent:def
++ on-fail on-fail:def
--

View File

@ -12,6 +12,7 @@
[%3 *]
[%4 state-zero]
[%5 state-zero]
[%6 state-zero]
==
::
+$ state-zero
@ -21,7 +22,7 @@
==
--
::
=| [%5 state-zero]
=| [%6 state-zero]
=* state -
%- agent:dbug
^- agent:gall
@ -36,27 +37,42 @@
%_ new-state
tiles
%- ~(gas by *tiles:store)
%+ turn `(list term)`[%weather %clock %dojo ~]
%+ turn `(list term)`[%weather %clock %term ~]
|= =term
:- term
^- tile:store
?+ term [[%custom ~] %.y]
%dojo [[%basic 'Dojo' '/~landscape/img/Dojo.png' '/~dojo'] %.y]
?+ term [[%custom ~] %.y]
%term [[%basic 'Terminal' '/~landscape/img/term.png' '/~term'] %.y]
==
tile-ordering [%weather %clock %dojo ~]
tile-ordering [%weather %clock %term ~]
==
[~ this(state [%5 new-state])]
[~ this(state [%6 new-state])]
::
++ on-save !>(state)
++ on-load
|= old=vase
^- (quip card _this)
=/ old-state !<(versioned-state old)
|-
=| cards=(list card)
|- ^- (quip card _this)
?: ?=(%6 -.old-state)
[cards this(state old-state)]
?: ?=(%5 -.old-state)
`this(state old-state)
:: replace %dojo with %term
::
=. tiles.old-state
%+ ~(put by (~(del by tiles.old-state) %dojo))
%term
:_ is-shown:(~(gut by tiles.old-state) %dojo *tile:store)
[%basic 'Terminal' '/~landscape/img/term.png' '/~term']
=. tile-ordering.old-state
%+ turn tile-ordering.old-state
|=(t=term ?:(=(%dojo t) %term t))
$(old-state [%6 +.old-state])
?: ?=(%4 -.old-state)
:- [%pass / %arvo %e %disconnect [~ /]]~
=. cards
%+ snoc cards
[%pass / %arvo %e %disconnect [~ /]]
=. tiles.old-state
(~(del by tiles.old-state) %chat)
=. tiles.old-state
@ -65,7 +81,7 @@
(~(del by tiles.old-state) %links)
=. tile-ordering.old-state
(skip tile-ordering.old-state |=(=term ?=(?(%links %chat %publish) term)))
this(state [%5 +.old-state])
$(old-state [%5 +.old-state])
=/ new-state *state-zero
=. new-state
%_ new-state
@ -80,18 +96,22 @@
==
tile-ordering [%weather %clock %dojo ~]
==
:_ this(state [%5 new-state])
%+ welp
:~ [%pass / %arvo %e %disconnect [~ /]]
:* %pass /srv %agent [our.bowl %file-server]
%poke %file-server-action
!>([%serve-dir / /app/landscape %.n %.y])
==
==
%+ turn ~(tap by wex.bowl)
|= [[=wire =ship =term] *]
^- card
[%pass wire %agent [ship term] %leave ~]
%_ $
old-state [%5 new-state]
::
cards
%+ welp
:~ [%pass / %arvo %e %disconnect [~ /]]
:* %pass /srv %agent [our.bowl %file-server]
%poke %file-server-action
!>([%serve-dir / /app/landscape %.n %.y])
==
==
%+ turn ~(tap by wex.bowl)
|= [[=wire =ship =term] *]
^- card
[%pass wire %agent [ship term] %leave ~]
==
::
++ on-poke
|= [=mark =vase]

View File

@ -1,74 +1,4 @@
:: soto [tombstone]: former dojo relay for urbit's landscape interface
::
:: soto [landscape]: A Dojo relay for Urbit's Landscape interface
::
:: Relays sole-effects to subscribers and forwards sole-action pokes
::
/- sole
/+ *soto, default-agent
|%
+$ card card:agent:gall
::
+$ versioned-state
$@ state-null
state-zero
::
+$ state-null ~
::
+$ state-zero [%0 ~]
--
=| state-zero
=* state -
^- agent:gall
|_ bol=bowl:gall
+* this .
soto-core +>
sc ~(. soto-core bol)
def ~(. (default-agent this %|) bol)
::
++ on-init
:_ this
:_ ~
:* %pass /srv %agent [our.bol %file-server]
%poke %file-server-action
!>([%serve-dir /'~dojo' /app/landscape %.n %.y])
==
++ on-save !>(state)
::
++ on-load
|= old-vase=vase
=/ old
!<(versioned-state old-vase)
?^ old
[~ this(state old)]
:_ this(state [%0 ~])
:~ [%pass /bind/soto %arvo %e %disconnect [~ /'~dojo']]
:* %pass /srv %agent [our.bol %file-server]
%poke %file-server-action
!>([%serve-dir /'~dojo' /app/landscape %.n %.y])
==
==
::
++ on-poke on-poke:def
++ on-watch
|= pax=path
^- (quip card _this)
?+ pax (on-watch:def pax)
[%sototile ~]
:_ this
[%give %fact ~ %json !>(~)]~
==
::
++ on-agent on-agent:def
::
++ on-arvo
|= [wir=wire sin=sign-arvo]
^- (quip card _this)
?: ?=(%bound +<.sin)
[~ this]
(on-arvo:def wir sin)
::
++ on-fail on-fail:def
++ on-leave on-leave:def
++ on-peek on-peek:def
::
--
/+ default-agent
(default-agent *agent:gall %|)

View File

@ -91,7 +91,7 @@
%chat-hook
%chat-view
%chat-cli
%soto
%herm
%contact-store
%contact-hook
%contact-view
@ -242,7 +242,7 @@
=> (se-born | %home %group-push-hook)
(se-born | %home %group-pull-hook)
=? ..on-load (lte hood-version %9)
(se-born | %home %graph-store)
(se-born | %home %graph-store)
=? ..on-load (lte hood-version %10)
=> (se-born | %home %graph-push-hook)
(se-born | %home %graph-pull-hook)
@ -251,7 +251,8 @@
=> (se-born | %home %hark-group-hook)
=> (se-born | %home %hark-chat-hook)
=> (se-born | %home %hark-store)
(se-born | %home %observe-hook)
=> (se-born | %home %observe-hook)
(se-born | %home %herm)
..on-load
::
++ reap-phat :: ack connect
@ -567,7 +568,6 @@
++ se-show :: show buffer, raw
|= lin/(pair @ud stub)
^+ +>
=. p.lin (add p.lin (lent-stye:klr q.lin))
?: =(mir lin) +>
=. +> ?:(=(p.mir p.lin) +> (se-blit %hop p.lin))
=. +> ?:(=(q.mir q.lin) +> (se-blit %pom q.lin))
@ -1131,25 +1131,10 @@
(fall p.q.a p.q.b)
(fall q.q.a q.q.b)
::
++ lent-stye
|= a/stub ^- @
(roll (lnts-stye a) add)
::
++ lent-char
|= a/stub ^- @
(roll (lnts-char a) add)
::
++ lnts-stye :: stub pair head lengths
|= a/stub ^- (list @)
%+ turn a
|= a/(pair stye (list @c))
;: add :: presumes impl of cvrt:ansi in %dill
(mul 5 2) :: bg
(mul 5 2) :: fg
=+ b=~(wyt in p.p.a) :: effect
?:(=(0 b) 0 (mul 4 +(b)))
==
::
++ lnts-char :: stub pair tail lengths
|= a/stub ^- (list @)
%+ turn a

29
pkg/arvo/mar/belt.hoon Normal file
View File

@ -0,0 +1,29 @@
:: belt: runtime belt structure
::
|_ =belt:dill
++ grad %noun
:: +grab: convert from
::
++ grab
|%
++ noun belt:dill
++ json
^- $-(^json belt:dill)
=, dejs:format
%- of
:~ aro+(su (perk %d %l %r %u ~))
bac+ul
ctl+(cu taft so)
del+ul
met+(cu taft so)
ret+ul
txt+(ar (cu taft so))
==
--
:: +grow: convert to
::
++ grow
|%
++ noun belt
--
--

58
pkg/arvo/mar/blit.hoon Normal file
View File

@ -0,0 +1,58 @@
:: blit: runtime blit structure
::
/+ base64
::
|_ =blit:dill
++ grad %noun
:: +grab: convert from
::
++ grab
|%
++ noun blit:dill
--
:: +grow: convert to
::
++ grow
|%
++ noun blit
++ json
^- ^json
=, enjs:format
%+ frond -.blit
?- -.blit
%bel b+&
%clr b+&
%hop (numb p.blit)
%lin a+(turn p.blit |=(c=@c s+(tuft c)))
%mor b+&
%url s+p.blit
::
%sag
%- pairs
:~ 'path'^(path p.blit)
'file'^s+(en:base64 (as-octs:mimes:html (jam q.blit)))
==
::
%sav
%- pairs
:~ 'path'^(path p.blit)
'file'^s+(en:base64 (as-octs:mimes:html q.blit))
==
::
%klr
:- %a
%+ turn p.blit
|= [=stye text=(list @c)]
%- pairs
:~ 'text'^a+(turn text |=(c=@c s+(tuft c)))
::
:- 'stye'
%- pairs
:~ 'back'^[?~(. ~ s+.)]:p.q.stye
'fore'^[?~(. ~ s+.)]:q.q.stye
'deco'^a+(turn ~(tap in p.stye) |=(d=deco ?~(d ~ s+d)))
==
==
==
--
--

View File

@ -8,9 +8,10 @@
-- ::
=> |% :: console protocol
++ axle ::
$: %3 ::
$: %4 ::TODO replace ducts with session ids ::
hey/(unit duct) :: default duct
dug/(map duct axon) :: conversations
eye=(jug duct duct) :: outside listeners
lit/? :: boot in lite mode
$= veb :: vane verbosities
$~ (~(put by *(map @tas log-level)) %hole %soft) :: quiet packet crashes
@ -21,7 +22,7 @@
tem/(unit (list dill-belt)) :: pending, reverse
wid/_80 :: terminal width
pos/@ud :: cursor position
see/(list @c) :: current line
see=$%([%lin (list @c)] [%klr stub]) :: current line
== ::
+$ log-level ?(%hush %soft %loud) :: none, line, full
-- => ::
@ -151,7 +152,11 @@
::
++ done :: return gift
|= git/gift:able
+>(moz :_(moz [hen %give git]))
=- +>.$(moz (weld - moz))
%+ turn
:- hen
~(tap in (~(get ju eye.all) hen))
|=(=duct [duct %give git])
::
++ deal :: pass to %gall
|= [=wire =deal:gall]
@ -161,7 +166,7 @@
|= [=wire =note]
+>(moz :_(moz [hen %pass wire note]))
::
++ from :: receive belt
++ from :: receive blit
|= bit/dill-blit
^+ +>
?: ?=($mor -.bit)
@ -172,86 +177,33 @@
%+ done %blit
:~ [%lin p.bit]
[%mor ~]
[%lin see]
see
[%hop pos]
==
?: ?=($klr -.bit)
%+ done %blit
:~ [%lin (cvrt:ansi p.bit)]
:~ [%klr p.bit]
[%mor ~]
[%lin see]
see
[%hop pos]
==
?: ?=($pro -.bit)
(done(see p.bit) %blit [[%lin p.bit] [%hop pos] ~])
=. see [%lin p.bit]
(done %blit [see [%hop pos] ~])
?: ?=($pom -.bit)
=. see (cvrt:ansi p.bit)
(done %blit [[%lin see] [%hop pos] ~])
::NOTE treat "styled prompt" without style as plain prompt,
:: to allow rendering by older runtimes
::TODO remove me once v0.10.9+ has high/guaranteed adoption
::
?: (levy p.bit (cork head |*(s=stye =(*stye s))))
$(bit [%pro (zing (turn p.bit tail))])
=. see [%klr p.bit]
(done %blit [see [%hop pos] ~])
?: ?=($hop -.bit)
(done(pos p.bit) %blit [bit ~])
?: ?=($qit -.bit)
(dump %logo ~)
(done %blit [bit ~])
::
++ ansi
|%
++ cvrt :: stub to (list @c)
|= a/stub :: with ANSI codes
^- (list @c)
%- zing %+ turn a
|= a/(pair stye (list @c))
^- (list @c)
;: weld
?: =(0 ~(wyt in p.p.a)) ~
`(list @c)`(zing (turn ~(tap in p.p.a) ef))
(bg p.q.p.a)
(fg q.q.p.a)
q.a
?~(p.p.a ~ (ef ~))
(bg ~)
(fg ~)
==
::
++ ef |=(a/^deco (scap (deco a))) :: ANSI effect
::
++ fg |=(a/^tint (scap (tint a))) :: ANSI foreground
::
++ bg :: ANSI background
|= a/^tint
%- scap
=>((tint a) [+(p) q]) :: (add 10 fg)
::
++ scap :: ANSI escape seq
|= a/$@(@ (pair @ @))
%- (list @c)
:+ 27 '[' :: "\033[{a}m"
?@(a :~(a 'm') :~(p.a q.a 'm'))
::
++ deco :: ANSI effects
|= a/^deco ^- @
?- a
~ '0'
$br '1'
$un '4'
$bl '5'
==
::
++ tint :: ANSI colors (fg)
|= a/^tint
^- (pair @ @)
:- '3'
?- a
$k '0'
$r '1'
$g '2'
$y '3'
$b '4'
$m '5'
$c '6'
$w '7'
~ '9'
==
--
:: XX move
::
++ sein
@ -398,7 +350,7 @@
=* duc (need hey.all)
=/ app %hood
=/ see (tuba "<awaiting {(trip app)}, this may take a minute>")
=/ zon=axon [app input=[~ ~] width=80 cursor=(lent see) see]
=/ zon=axon [app input=[~ ~] width=80 cursor=(lent see) lin+see]
::
=^ moz all abet:(~(into as duc zon) ~)
[moz ..^$]
@ -422,7 +374,29 @@
=. veb.all (~(put by veb.all) tag.task level.task)
[~ ..^$]
::
?: ?=(%view -.task)
:: crash on viewing non-existent session
::
~| [%no-session session.task]
?> =(~ session.task)
=/ session (need hey.all)
=/ =axon (~(got by dug.all) session)
:: register the viewer and send them the prompt line
::
:- [hen %give %blit [see.axon]~]~
..^$(eye.all (~(put ju eye.all) session hen))
::
?: ?=(%flee -.task)
:- ~
~| [%no-session session.task]
?> =(~ session.task)
=/ session (need hey.all)
..^$(eye.all (~(del ju eye.all) session hen))
::
=/ nus (ax hen)
=? nus &(?=(~ nus) ?=(^ hey.all))
::TODO allow specifying target session in task
(ax u.hey.all)
?~ nus
:: :hen is an unrecognized duct
:: could be before %boot (or %boot failed)
@ -441,7 +415,7 @@
++ axle-1
$: $1
hey/(unit duct)
dug/(map duct axon)
dug/(map duct axon-3)
lit/?
$= hef
$: a/(unit mass)
@ -457,10 +431,11 @@
$~ (~(put by *(map @tas log-level)) %hole %soft)
(map @tas log-level)
==
::
++ axle-2
$: %2
hey/(unit duct)
dug/(map duct axon)
dug/(map duct axon-3)
lit/?
dog/_|
$= hef
@ -478,29 +453,68 @@
(map @tas log-level)
==
::
++ axle-any
$%(axle-1 axle-2 axle)
+$ axle-3
$: %3
hey=(unit duct)
dug=(map duct axon-3)
lit=?
$= veb
$~ (~(put by *(map @tas log-level)) %hole %soft)
(map @tas log-level)
==
+$ axon-3
$: ram=term
tem=(unit (list dill-belt))
wid=_80
pos=@ud
see=(list @c)
==
::
+$ axle-any
$%(axle-1 axle-2 axle-3 axle)
--
::
|= old=axle-any
?- -.old
%1 $(old [%2 [hey dug lit dog=& hef veb]:old])
%2 $(old [%3 [hey dug lit veb]:old])
%3 ..^$(all old)
%3 =- $(old [%4 hey.old - ~ lit.old veb.old])
(~(run by dug.old) |=(a=axon-3 a(see lin+see.a)))
%4 ..^$(all old)
==
::
++ scry
|= {fur/(unit (set monk)) ren/@tas why/shop syd/desk lot/coin tyl/path}
^- (unit (unit cage))
?. ?=(%& -.why) ~
=* his p.why
::TODO don't special-case whey scry
::
?: &(=(ren %$) =(tyl /whey))
=/ maz=(list mass)
:~ hey+&+hey.all
dug+&+dug.all
==
``mass+!>(maz)
[~ ~]
:: only respond for the local identity, %$ desk, current timestamp
::
?. ?& =(&+our why)
=([%$ %da now] lot)
=(%$ syd)
==
~
:: /dx/sessions//line blit current line (prompt) of default session
:: /dx/sessions//cursor @ud current cursor position of default session
::TODO support asking for specific sessions once session ids are real
::
?. ?=(%x ren) ~
?+ tyl ~
[%sessions %$ *]
?~ hey.all [~ ~]
?~ session=(~(get by dug.all) u.hey.all) [~ ~]
?+ t.t.tyl ~
[%line ~] ``blit+!>(`blit`see.u.session)
[%cursor ~] ``atom+!>(pos.u.session)
==
==
::
++ stay all
::

View File

@ -1120,6 +1120,7 @@
{$boot lit/? p/*} :: weird %dill boot
{$crop p/@ud} :: trim kernel state
$>(%crud vane-task) :: error with trace
[%flee session=~] :: unwatch session
{$flog p/flog} :: wrapped error
{$flow p/@tas q/(list gill:gall)} :: terminal config
{$hail ~} :: terminal refresh
@ -1134,6 +1135,7 @@
{$talk p/tank} ::
{$text p/tape} ::
{$veer p/@ta q/path r/@t} :: install vane
[%view session=~] :: watch session blits
$>(%trim vane-task) :: trim state
$>(%vega vane-task) :: report upgrade
{$verb ~} :: verbose mode
@ -1157,6 +1159,7 @@
$% {$bel ~} :: make a noise
{$clr ~} :: clear the screen
{$hop p/@ud} :: set cursor position
[%klr p=stub] :: set styled line
{$lin p/(list @c)} :: set current line
{$mor ~} :: newline
{$sag p/path q/*} :: save to jamfile

View File

@ -4851,6 +4851,11 @@
}
}
},
"file-saver": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",

View File

@ -16,6 +16,7 @@
"classnames": "^2.2.6",
"codemirror": "^5.55.0",
"css-loader": "^3.5.3",
"file-saver": "^2.0.2",
"formik": "^2.1.4",
"lodash": "^4.17.15",
"markdown-to-jsx": "^6.11.4",

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
const defaultApps = ['chat', 'dojo', 'groups', 'link', 'publish'];
const defaultApps = ['chat', 'term', 'groups', 'link', 'publish'];
export default defaultApps;

View File

@ -1,119 +0,0 @@
import React, { Component } from 'react';
import { cite } from '~/logic/lib/util';
import { Spinner } from '~/views/components/Spinner';
export class Input extends Component {
constructor(props) {
super(props);
this.state = {
awaiting: false,
type: 'Sending to Dojo'
};
this.keyPress = this.keyPress.bind(this);
this.inputRef = React.createRef();
}
componentDidUpdate() {
if (
!document.activeElement == document.body
|| document.activeElement == this.inputRef.current
) {
this.inputRef.current.focus();
this.inputRef.current.setSelectionRange(this.props.cursor, this.props.cursor);
}
}
keyPress(e) {
if ((e.getModifierState('Control') || event.getModifierState('Meta'))
&& e.key === 'v') {
return;
}
e.preventDefault();
const allowedKeys = [
'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab'
];
if ((e.key.length > 1) && (!(allowedKeys.includes(e.key)))) {
return;
}
// submit on enter
if (e.key === 'Enter') {
this.setState({ awaiting: true, type: 'Sending to Dojo' });
this.props.api.soto('ret').then(() => {
this.setState({ awaiting: false });
});
} else if ((e.key === 'Backspace') && (this.props.cursor > 0)) {
this.props.store.doEdit({ del: this.props.cursor - 1 });
return this.props.store.setState({ cursor: this.props.cursor - 1 });
} else if (e.key === 'Backspace') {
return;
} else if (e.key.startsWith('Arrow')) {
if (e.key === 'ArrowLeft') {
if (this.props.cursor > 0) {
this.props.store.setState({ cursor: this.props.cursor - 1 });
}
} else if (e.key === 'ArrowRight') {
if (this.props.cursor < this.props.input.length) {
this.props.store.setState({ cursor: this.props.cursor + 1 });
}
}
}
// tab completion
else if (e.key === 'Tab') {
this.setState({ awaiting: true, type: 'Getting suggestions' });
this.props.api.soto({ tab: this.props.cursor }).then(() => {
this.setState({ awaiting: false });
});
}
// capture and transmit most characters
else {
this.props.store.doEdit({ ins: { cha: e.key, at: this.props.cursor } });
this.props.store.setState({ cursor: this.props.cursor + 1 });
}
}
render() {
return (
<div className="flex flex-row flex-grow-1 relative">
<div className="flex-shrink-0"><span class="dn-s">{cite(this.props.ship)}:</span>dojo
</div>
<span id="prompt">
{this.props.prompt}
</span>
<input
autoFocus
autocorrect="off"
autocapitalize="off"
spellcheck="false"
tabindex="0"
wrap="off"
className="mono ml1 flex-auto dib w-100"
id="dojo"
cursor={this.props.cursor}
onClick={e => this.props.store.setState({ cursor: e.target.selectionEnd })}
onKeyDown={this.keyPress}
onPaste={(e) => {
const clipboardData = e.clipboardData || window.clipboardData;
const paste = Array.from(clipboardData.getData('Text'));
paste.reduce(async (previous, next) => {
await previous;
this.setState({ cursor: this.props.cursor + 1 });
return this.props.store.doEdit({ ins: { cha: next, at: this.props.cursor } });
}, Promise.resolve());
e.preventDefault();
}}
ref={this.inputRef}
defaultValue={this.props.input}
/>
<Spinner awaiting={this.state.awaiting} text={`${this.state.type}...`} classes="absolute right-0 bottom-0 inter pa ba pa2 b--gray1-d" />
</div>
);
}
}
export default Input;

View File

@ -1,157 +0,0 @@
// See /lib/sole/hoon
const str = JSON.stringify;
export class Share {
constructor(buf, ven, leg) {
if (buf == null) {
buf = '';
}
this.buf = buf;
if (ven == null) {
ven = [0, 0];
}
this.ven = ven;
if (leg == null) {
leg = [];
}
this.leg = leg;
}
abet() {
return {
buf: this.buf,
leg: this.leg.slice(),
ven: this.ven.slice()
};
}
apply(ted) {
switch (false) {
case 'nop' !== ted: return;
case !ted.map: return ted.map(this.apply, this);
default: switch (Object.keys(ted)[0]) {
case 'set': return this.buf = ted.set;
case 'del': return this.buf = this.buf.slice(0, ted.del) + this.buf.slice(ted.del + 1);
case 'ins':
var { at, cha } = ted.ins;
return this.buf = this.buf.slice(0, at) + cha + this.buf.slice(at);
default: throw `%sole-edit -lost.${str(ted)}`;
}
}
}
transmute(sin, dex) {
switch (false) {
case (sin !== 'nop') && (dex !== 'nop'): return dex;
case !sin.reduce:
return sin.reduce(((dex, syn) => this.transmute(syn, dex)), dex);
case !dex.map: return dex.map(dax => this.transmute(sin, dax));
case dex.set === undefined: return dex;
default: switch (Object.keys(sin)[0]) {
case 'set': return 'nop';
case 'del':
if (sin.del === dex.del) {
return 'nop';
}
dex = { ...dex };
switch (Object.keys(dex)[0]) {
case 'del': if (sin.del < dex.del) {
dex.del--;
}
break;
case 'ins': if (sin.del < dex.ins.at) {
dex.ins.at--;
}
break;
}
return dex;
case 'ins':
dex = { ...dex };
var { at, cha } = sin.ins;
switch (Object.keys(dex)[0]) {
case 'del': if (at < dex.del) {
dex.del++;
}
break;
case 'ins': if ((at < dex.ins.at) ||
((at === dex.ins.at) && !(cha <= dex.ins.cha))) {
dex.ins.at++;
} else if (at >= dex.ins.at) {
dex.ins.at = at; // NOTE possibly unpredictable behaviour
dex.ins.at++; // for sole inserts that aren't tabs
}
break;
}
return dex;
default: throw `%sole-edit -lost.${str(sin)}`;
}
}
}
commit(ted) {
this.ven[0]++;
this.leg.push(ted);
return this.apply(ted);
}
inverse(ted) {
switch (false) {
case 'nop' !== ted: return ted;
case !ted.map:
return ted.map((tad) => {
const res = this.inverse(tad);
this.apply(tad);
return res;
}).reverse();
default: switch (Object.keys(ted)[0]) {
case 'set': return { set: this.buf };
case 'ins': return { del: ted.ins };
case 'del': return { ins: { at: ted.del, cha: this.buf[ted.del] } };
default: throw `%sole-edit -lost.${str(ted)}`;
}
}
}
receive({ ler, ted }) {
if (!(ler[1] === this.ven[1])) {
throw `-out-of-sync.[${str(ler)} ${str(this.ven)}]`;
}
this.leg = this.leg.slice((this.leg.length + ler[0]) - this.ven[0]);
const dat = this.transmute(this.leg, ted);
this.ven[1]++;
this.apply(dat);
return dat;
}
remit() {
throw 'stub';
}
transmit(ted) {
const act = { ted, ler: [this.ven[1], this.ven[0]] };
this.commit(ted);
return act;
}
transceive({ ler, ted }) {
const old = new Share(this.buf);
const dat = this.receive({ ler, ted });
return old.inverse(dat);
}
transpose(ted, pos) {
if (pos === undefined) {
return this.transpose(this.leg, ted);
} else {
let left;
return ((left =
(this.transmute(
ted, { ins: { at: pos } })).ins) != null ?
left : { at: 0 }
).at;
}
}
};
export default Share;

View File

@ -1,11 +0,0 @@
input#dojo {
background-color: inherit;
color: inherit;
}
/* responsive */
@media all and (max-width: 34.375em) {
.h-100-m40-s {
height: calc(100% - 40px);
}
}

View File

@ -1,95 +0,0 @@
import Share from './components/lib/sole';
export default class Store {
constructor() {
this.state = this.initialState();
this.sync = this.sync.bind(this);
this.print = this.print.bind(this);
this.buffer = new Share();
}
initialState() {
return {
txt: [],
prompt: '',
cursor: 0,
input: ''
};
}
clear() {
this.handleEvent({
data: { clear: true }
});
}
handleEvent(data) {
// recursive handler
if (data.data) {
var dojoReply = data.data;
} else {
var dojoReply = data;
}
if (dojoReply.clear) {
this.setState(this.initialState(), (() => {
return;
}));
}
// %mor sole-effects are nested, so throw back to handler
if (dojoReply.map) {
return dojoReply.map(reply => this.handleEvent(reply));
}
switch (Object.keys(dojoReply)[0]) {
case 'txt':
return this.print(dojoReply.txt);
case 'tab':
this.print(dojoReply.tab.match + ' ' + dojoReply.tab.info);
return;
case 'tan':
return dojoReply.tan.split('\n').map(this.print);
case 'pro':
return this.setState({ prompt: dojoReply.pro.cad });
case 'hop':
return this.setState({ cursor: dojoReply.hop });
case 'det':
this.buffer.receive(dojoReply.det);
return this.sync(dojoReply.det.ted);
case 'act':
switch (dojoReply.act) {
case 'clr': return this.setState({ txt: [] });
case 'nex': return this.setState({
input: '',
cursor: 0
});
}
break;
default: console.log(dojoReply);
}
}
doEdit(ted) {
const detSend = this.buffer.transmit(ted);
this.sync(ted);
return this.api.soto({ det: detSend });
}
print(txt) {
const textLog = this.state.txt;
textLog.push(txt);
return this.setState({ txt: textLog });
}
sync(ted) {
return this.setState({
input: this.buffer.buf,
cursor: this.buffer.transpose(ted, this.state.cursor)
});
}
setStateHandler(setState) {
this.setState = setState;
}
}

View File

@ -9,11 +9,11 @@ export default class BasicTile extends React.PureComponent {
return (
<Tile
bg={props.title === 'Dojo' ? '#000000' : 'white'}
bg={props.title === 'Terminal' ? '#000000' : 'white'}
to={props.linkedUrl}
>
<Text color={props.title === 'Dojo' ? '#ffffff' : 'black'}>
{props.title === 'Dojo'
<Text color={props.title === 'Terminal' ? '#ffffff' : 'black'}>
{props.title === 'Terminal'
? <Icon
icon='ChevronEast'
color='#fff'

View File

@ -5,10 +5,9 @@ export default class Api {
this.ship = ship;
this.channel = channel;
this.bindPaths = [];
this.dojoId = 'soto-' + Math.random().toString(36).substring(2);
}
bind(path, method, ship = this.ship, appl = 'dojo', success, fail) {
bind(path, method, ship = this.ship, appl = 'herm', success, fail) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = this.channel.subscribe(ship, appl, path,
@ -29,8 +28,8 @@ export default class Api {
});
}
soto(data) {
return this.action('dojo', 'sole-action', { id: this.dojoId, dat: data });
belt(belt) {
return this.action('herm', 'belt', belt);
}
action(appl, mark, data) {

View File

@ -11,7 +11,7 @@ import Subscription from './subscription';
import './css/custom.css';
export default class DojoApp extends Component {
export default class TermApp extends Component {
constructor(props) {
super(props);
this.store = new Store();
@ -45,14 +45,14 @@ export default class DojoApp extends Component {
return (
<>
<Helmet>
<title>OS1 - Dojo</title>
<title>OS1 - Terminal</title>
</Helmet>
<div
style={{ height: '100%' }}
>
<Route
exact
path="/~dojo/"
path="/~term/"
render={(props) => {
return (
<div className="w-100 h-100 flex-m flex-l flex-xl">
@ -72,14 +72,13 @@ export default class DojoApp extends Component {
cursor: 'text'
}}
>
<History commandLog={this.state.txt} />
<History log={this.state.lines.slice(0, -1)} />
<Input
ship={this.props.ship}
cursor={this.state.cursor}
prompt={this.state.prompt}
input={this.state.input}
api={this.api}
store={this.store}
line={this.state.lines.slice(-1)[0]}
/>
</div>
</div>

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react';
import Line from './line';
export class History extends Component {
constructor(props) {
super(props);
@ -12,14 +14,8 @@ export class History extends Component {
style={{ resize: 'none' }}
>
<div style={{ marginTop: 'auto' }}>
{this.props.commandLog.map((text, index) => {
return (
<p className="mono" key={index}
style={{ overflowWrap: 'break-word', whiteSpace: 'pre' }}
>
{text}
</p>
);
{this.props.log.map((line, i) => {
return <Line key={i} line={line} />;
})}
</div>
</div>

View File

@ -0,0 +1,112 @@
import React, { Component } from 'react';
import { Row, Box, BaseInput } from '@tlon/indigo-react';
import { cite } from '~/logic/lib/util';
import { Spinner } from '~/views/components/Spinner';
export class Input extends Component {
constructor(props) {
super(props);
this.state = {};
this.keyPress = this.keyPress.bind(this);
this.paste = this.paste.bind(this);
this.click = this.click.bind(this);
this.inputRef = React.createRef();
}
componentDidUpdate() {
if (
!document.activeElement == document.body
|| document.activeElement == this.inputRef.current
) {
this.inputRef.current.focus();
this.inputRef.current.setSelectionRange(this.props.cursor, this.props.cursor);
}
}
keyPress(e) {
let key = e.key;
// let paste and leap events pass
if ((e.getModifierState('Control') || event.getModifierState('Meta'))
&& (e.key === 'v' || e.key === '/')) {
return;
}
let belt = null;
if (key === 'ArrowLeft') belt = {aro: 'l'};
else if (key === 'ArrowRight') belt = {aro: 'r'};
else if (key === 'ArrowUp') belt = {aro: 'u'};
else if (key === 'ArrowDown') belt = {aro: 'd'};
else if (key === 'Backspace') belt = {bac: null};
else if (key === 'Delete') belt = {del: null};
else if (key === 'Tab') belt = {ctl: 'i'};
else if (key === 'Enter') belt = {ret: null};
else if (key.length === 1) belt = {txt: [key]};
else belt = null;
if (belt && e.getModifierState('Control')) {
if (belt.txt !== undefined) belt = {ctl: belt.txt[0]};
} else
if (belt &&
(e.getModifierState('Meta') || e.getModifierState('Alt'))) {
if (belt.bac !== undefined) belt = {met: 'bac'};
}
if (belt !== null) {
this.props.api.belt(belt);
}
e.preventDefault();
}
paste(e) {
const clipboardData = e.clipboardData || window.clipboardData;
const clipboardText = clipboardData.getData('Text');
this.props.api.belt({ txt: [...clipboardText] });
e.preventDefault();
}
click(e) {
// prevent desynced cursor movement
e.preventDefault();
e.target.setSelectionRange(this.props.cursor, this.props.cursor);
}
render() {
const line = this.props.line;
let prompt = 'connecting...';
if (line) {
if (line.lin) {
prompt = line.lin.join('');
}
//TODO render prompt style
else if (line.klr) {
prompt = line.klr.reduce((l, p) => (l + p.text.join('')), '');
}
}
return (
<Row flexGrow='1' position='relative'>
<Box flexShrink='0' className="w-100">
<BaseInput
autoFocus
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
tabindex="0"
wrap="off"
className="mono ml1 flex-auto dib w-100"
id="term"
cursor={this.props.cursor}
onKeyDown={this.keyPress}
onClick={this.click}
onPaste={this.paste}
ref={this.inputRef}
defaultValue="connecting..."
value={prompt}
/>
</Box>
</Row>
);
}
}
export default Input;

View File

@ -0,0 +1,69 @@
import React, { Component, useMemo } from 'react';
import { Box, Text } from '@tlon/indigo-react';
export default React.memo(({line}) => {
// line body to jsx
//NOTE lines are lists of characters that might span multiple codepoints
//
let text = '';
if (line.lin) {
text = line.lin.join('');
}
else if (line.klr) {
text = line.klr.map((part, i) => {
let prop = part.stye.deco.reduce((prop, deco) => {
switch (deco) {
case null: return prop;
case 'br': return {bold: true, ...prop};
case 'bl': return {className: 'blink', ...prop};
case 'un': return {style: {textDecoration: 'underline'}, ...prop};
default: console.log('weird deco', deco); return prop;
}
}, {});
switch (part.stye.fore) {
case null: break;
case 'r': prop.color = 'red'; break;
case 'g': prop.color = 'green'; break;
case 'b': prop.color = 'blue'; break;
case 'c': prop.color = 'cyan'; break;
case 'm': prop.color = 'purple'; break;
case 'y': prop.color = 'yellow'; break;
case 'k': prop.color = 'black'; break;
case 'w': prop.color = 'white'; break;
default: console.log('weird fore', part.stye.fore);
}
switch (part.stye.back) {
case null: break;
case 'r': prop.backgroundColor = 'red'; break;
case 'g': prop.backgroundColor = 'green'; break;
case 'b': prop.backgroundColor = 'blue'; break;
case 'c': prop.backgroundColor = 'cyan'; break;
case 'm': prop.backgroundColor = 'purple'; break;
case 'y': prop.backgroundColor = 'yellow'; break;
case 'k': prop.backgroundColor = 'black'; break;
case 'w': prop.backgroundColor = 'white'; break;
default: console.log('weird back', part.stye.back);
}
if (Object.keys(prop).length === 0)
{
return part.text;
} else {
return (<Text mono fontSize='inherit' key={i} {...prop}>
{part.text.join('')}
</Text>);
}
});
}
// render line
//
return (
<Text mono display='block' fontSize='14px'
style={{ overflowWrap: 'break-word', whiteSpace: 'pre-wrap' }}
>
{text}
</Text>
);
});

View File

@ -0,0 +1,23 @@
input#term {
background-color: inherit;
color: inherit;
}
.blink {
animation: 4s ease-in-out infinite opacity_blink;
}
@keyframes opacity_blink {
0% { opacity: 0; }
10% { opacity: 1; }
80% { opacity: 1; }
90% { opacity: 0; }
100% { opacity: 0; }
}
/* responsive */
@media all and (max-width: 34.375em) {
.h-100-m40-s {
height: calc(100% - 40px);
}
}

View File

@ -0,0 +1,90 @@
import { saveAs } from 'file-saver';
import bel from '../../../logic/lib/bel'
export default class Store {
constructor() {
this.state = this.initialState();
}
initialState() {
return {
lines: [''],
cursor: 0,
};
}
clear() {
this.setState(this.initialState())
}
handleEvent(data) {
// process slogs
//
if (data.slog) {
this.state.lines.splice(this.state.lines.length-1, 0, {lin: [data.slog]});
this.setState({ lines: this.state.lines });
return;
}
// process blits
//
const blit = data.data;
switch (Object.keys(blit)[0]) {
case 'bel':
bel.play();
break;
case 'clr':
this.state.lines = this.state.lines.slice(-1);
this.setState({ lines: this.state.lines });
break;
case 'hop':
// since lines are lists of characters that might span multiple
// codepoints, we need to calculate the byte-wise cursor position
// to avoid incorrect cursor rendering.
//
let line = this.state.lines[this.state.lines.length - 1];
let hops;
if (line.lin) {
hops = line.lin.slice(0, blit.hop);
}
else if (line.klr) {
hops = line.klr.reduce((h, p) => {
if (h.length >= blit.hop) return h;
return [...h, ...p.text.slice(0, blit.hop - h.length)]
}, []);
}
this.setState({ cursor: hops.join('').length });
break;
case 'lin':
this.state.lines[this.state.lines.length - 1] = blit;
this.setState({ lines: this.state.lines });
break;
case 'klr':
this.state.lines[this.state.lines.length - 1] = blit;
this.setState({ lines: this.state.lines });
break;
case 'mor':
this.state.lines.push('');
this.setState({ lines: this.state.lines });
break;
case 'sag':
blit.sav = blit.sag;
case 'sav':
let name = blit.sav.path.split('/').slice(-2).join('.');
let buff = new Buffer(blit.sav.file, 'base64');
let blob = new Blob([buff], {type: 'application/octet-stream'});
saveAs(blob, name);
break;
case 'url':
//TODO too invasive? just print as <a>?
window.open(blit.url);
break;
default: console.log('weird blit', blit);
}
}
setStateHandler(setState) {
this.setState = setState;
}
}

View File

@ -27,7 +27,7 @@ export default class Subscription {
}
slog.onmessage = e => {
this.handleEvent({ txt: e.data });
this.handleEvent({ slog: e.data });
}
slog.onerror = e => {
@ -72,7 +72,7 @@ export default class Subscription {
}
firstRound() {
this.subscribe('/sole/' + this.api.dojoId, 'dojo');
this.subscribe('/session/', 'herm');
}
handleEvent(diff) {

View File

@ -117,7 +117,13 @@ export class Omnibox extends Component {
const { props } = this;
this.setState({ results: this.initialResults(), query: '' }, () => {
props.api.local.setOmnibox();
if (defaultApps.includes(app.toLowerCase()) || app === 'profile' || app === 'Links' || app === 'home' || app === 'inbox') {
if (defaultApps.includes(app.toLowerCase())
|| app === 'profile'
|| app === 'Links'
|| app === 'Terminal'
|| app === 'home'
|| app === 'inbox')
{
props.history.push(link);
} else {
window.location.href = link;

View File

@ -33,8 +33,12 @@ export class OmniboxResult extends Component {
const inviteCount = Object.keys(invites?.['contacts'] || {});
let graphic = <div />;
if (defaultApps.includes(icon.toLowerCase()) || icon.toLowerCase() === 'links') {
icon = (icon === 'Link') ? 'Links' : icon;
if (defaultApps.includes(icon.toLowerCase())
|| icon.toLowerCase() === 'links'
|| icon.toLowerCase() === 'terminal')
{
icon = (icon === 'Link') ? 'Links' :
(icon === 'Terminal') ? 'Dojo' : icon;
graphic = <Icon display="inline-block" verticalAlign="middle" icon={icon} mr='2' size='16px' color={iconFill} />;
} else if (icon === 'inbox') {
graphic = <Box display='flex' verticalAlign='middle'>

View File

@ -4,7 +4,7 @@ import { Route, Switch } from 'react-router-dom';
import styled from 'styled-components';
import LaunchApp from '~/views/apps/launch/app';
import DojoApp from '~/views/apps/dojo/app';
import TermApp from '~/views/apps/term/app';
import Landscape from '~/views/landscape/index';
import Profile from '~/views/apps/profile/profile';
import ErrorComponent from '~/views/components/Error';
@ -35,9 +35,9 @@ export const Content = (props) => {
)}
/>
<Route
path='/~dojo'
path='/~term'
render={p => (
<DojoApp
<TermApp
history={p.history}
location={p.location}
match={p.match}