landscape: moved chat files to landscape directory and updated to use landscape SPA
@ -26,11 +26,10 @@
|
||||
[%pass /group-bind %arvo %e %connect [~ /'~groups'] %landscape]
|
||||
[%pass /link-bind %arvo %e %connect [~ /'~link'] %landscape]
|
||||
[%pass /dojo-bind %arvo %e %connect [~ /'~dojo'] %landscape]
|
||||
[%pass /modulo-bind %arvo %e %connect [~ /'~modulo'] %landscape]
|
||||
[%pass /channel-bind %arvo %e %connect [~ /'~channel'] %landscape]
|
||||
[%pass /launch-bind %arvo %e %connect [~ /'~launch'] %landscape]
|
||||
[%pass /landscape-bind %arvo %e %connect [~ /'~landscape'] %landscape]
|
||||
[%pass /index-bind %arvo %e %connect [~ /] %landscape]
|
||||
==
|
||||
::
|
||||
++ on-save !>(state)
|
||||
++ on-load
|
||||
|= old-vase=vase
|
||||
@ -44,6 +43,7 @@
|
||||
?+ mark (on-poke:def mark vase)
|
||||
%handle-http-request
|
||||
=+ !<([id=@ta req=inbound-request:eyre] vase)
|
||||
~& req
|
||||
:_ this
|
||||
%+ give-simple-payload:app id
|
||||
%+ require-authorization:app req
|
||||
@ -53,6 +53,7 @@
|
||||
++ handle-http-request
|
||||
|= =inbound-request:eyre
|
||||
^- simple-payload:http
|
||||
~& inbound-request
|
||||
?. =(src.bowl our.bowl) [[403 ~] ~]
|
||||
=* req request.inbound-request
|
||||
=/ req-line (parse-request-line url.req)
|
||||
@ -63,6 +64,8 @@
|
||||
++ handle-get
|
||||
|= [headers=header-list:http req-line=request-line]
|
||||
^- simple-payload:http
|
||||
~& headers
|
||||
~& req-line
|
||||
?~ ext.req-line
|
||||
$(req-line [[[~ %html] (snoc site.req-line 'index')] args.req-line])
|
||||
?~ site.req-line not-found:gen
|
||||
@ -73,21 +76,16 @@
|
||||
not-found:gen
|
||||
=/ file=(unit octs)
|
||||
?+ site.req-line ~
|
||||
[%'' %'index' ~] (get-file-at /app/launch [t.site u.ext]:req-line)
|
||||
[%'~launch' *] (get-file-at /app/launch [t.site u.ext]:req-line)
|
||||
[%'~link' *] (get-file-at /app/link [t.site u.ext]:req-line)
|
||||
[%'~chat' *] (get-file-at /app/chat [t.site u.ext]:req-line)
|
||||
[%'~dojo' *] (get-file-at /app/soto [t.site u.ext]:req-line)
|
||||
[%'~groups' *] (get-file-at /app/groups [t.site u.ext]:req-line)
|
||||
[%'~modulo' %session ~]
|
||||
%- some
|
||||
%- as-octt:mimes:html
|
||||
%+ weld
|
||||
"window.ship = '{+:(scow %p our.bowl)}';"
|
||||
"window.urb = new Channel();"
|
||||
[%'' %'index' ~] (get-file-at /app/landscape [t.site u.ext]:req-line)
|
||||
[%'~link' *] (get-file-at /app/landscape [t.site u.ext]:req-line)
|
||||
[%'~chat' *] (get-file-at /app/landscape [t.site u.ext]:req-line)
|
||||
[%'~dojo' *] (get-file-at /app/landscape [t.site u.ext]:req-line)
|
||||
[%'~groups' *] (get-file-at /app/landscape [t.site u.ext]:req-line)
|
||||
[%'~landscape' %session ~]
|
||||
(some (as-octt:mimes:html "window.ship = '{+:(scow %p our.bowl)}';"))
|
||||
::
|
||||
[%'~channel' %channel ~]
|
||||
(get-file-at /app/launch/js /channel u.ext.req-line)
|
||||
[%'~landscape' %channel ~]
|
||||
(get-file-at /app/landscape/js /channel u.ext.req-line)
|
||||
==
|
||||
?~ file not-found:gen
|
||||
?+ u.ext.req-line not-found:gen
|
||||
@ -121,6 +119,7 @@
|
||||
++ on-arvo
|
||||
|= [=wire sign=sign-arvo]
|
||||
^- (quip card _this)
|
||||
~& [wire sign]
|
||||
?+ +<.sign (on-arvo:def wire sign)
|
||||
%bound [~ this]
|
||||
==
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 865 B |
Before Width: | Height: | Size: 1010 B After Width: | Height: | Size: 1010 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@ -8,8 +8,8 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-touch-fullscreen" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<link rel="apple-touch-icon" href="/~chat/img/touch_icon.png">
|
||||
<link rel="icon" type="image/png" href="/~launch/img/Favicon.png">
|
||||
<link rel="apple-touch-icon" href="/~landscape/img/touch_icon.png">
|
||||
<link rel="icon" type="image/png" href="/~landscape/img/Favicon.png">
|
||||
<link rel="manifest"
|
||||
href='data:application/manifest+json,{
|
||||
"name": "Chat",
|
||||
@ -18,13 +18,12 @@
|
||||
"display": "standalone",
|
||||
"background_color": "%23FFFFFF",
|
||||
"theme_color": "%23000000"}' />
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"/>
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~chat/js/index.js"></script>
|
||||
<script src="/~landscape/js/channel.js"></script>
|
||||
<script src="/~landscape/js/session.js"></script>
|
||||
<script src="/~landscape/js/index.js"></script>
|
||||
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
|
||||
</body>
|
||||
</html>
|
230
pkg/arvo/app/landscape/js/channel.js
Normal file
@ -0,0 +1,230 @@
|
||||
class Channel {
|
||||
constructor() {
|
||||
this.init();
|
||||
this.deleteOnUnload();
|
||||
|
||||
// a way to handle channel errors
|
||||
//
|
||||
//
|
||||
this.onChannelError = (err) => {
|
||||
console.error('event source error: ', err);
|
||||
};
|
||||
}
|
||||
|
||||
init() {
|
||||
// unique identifier: current time and random number
|
||||
//
|
||||
this.uid =
|
||||
new Date().getTime().toString() +
|
||||
"-" +
|
||||
Math.random().toString(16).slice(-6);
|
||||
|
||||
this.requestId = 1;
|
||||
|
||||
// the currently connected EventSource
|
||||
//
|
||||
this.eventSource = null;
|
||||
|
||||
// the id of the last EventSource event we received
|
||||
//
|
||||
this.lastEventId = 0;
|
||||
|
||||
// this last event id acknowledgment sent to the server
|
||||
//
|
||||
this.lastAcknowledgedEventId = 0;
|
||||
|
||||
// a registry of requestId to successFunc/failureFunc
|
||||
//
|
||||
// These functions are registered during a +poke and are executed
|
||||
// in the onServerEvent()/onServerError() callbacks. Only one of
|
||||
// the functions will be called, and the outstanding poke will be
|
||||
// removed after calling the success or failure function.
|
||||
//
|
||||
|
||||
this.outstandingPokes = new Map();
|
||||
|
||||
// a registry of requestId to subscription functions.
|
||||
//
|
||||
// These functions are registered during a +subscribe and are
|
||||
// executed in the onServerEvent()/onServerError() callbacks. The
|
||||
// event function will be called whenever a new piece of data on this
|
||||
// subscription is available, which may be 0, 1, or many times. The
|
||||
// disconnect function may be called exactly once.
|
||||
//
|
||||
this.outstandingSubscriptions = new Map();
|
||||
}
|
||||
|
||||
setOnChannelError(onError = (err) => {}) {
|
||||
this.onChannelError = onError;
|
||||
}
|
||||
|
||||
deleteOnUnload() {
|
||||
window.addEventListener("unload", (event) => {
|
||||
this.delete();
|
||||
});
|
||||
}
|
||||
|
||||
// sends a poke to an app on an urbit ship
|
||||
//
|
||||
poke(ship, app, mark, json, successFunc, failureFunc) {
|
||||
let id = this.nextId();
|
||||
this.outstandingPokes.set(
|
||||
id,
|
||||
{
|
||||
success: successFunc,
|
||||
fail: failureFunc
|
||||
}
|
||||
);
|
||||
|
||||
this.sendJSONToChannel({
|
||||
id,
|
||||
action: "poke",
|
||||
ship,
|
||||
app,
|
||||
mark,
|
||||
json
|
||||
});
|
||||
}
|
||||
|
||||
// subscribes to a path on an specific app and ship.
|
||||
//
|
||||
// Returns a subscription id, which is the same as the same internal id
|
||||
// passed to your Urbit.
|
||||
subscribe(
|
||||
ship,
|
||||
app,
|
||||
path,
|
||||
connectionErrFunc = () => {},
|
||||
eventFunc = () => {},
|
||||
quitFunc = () => {}) {
|
||||
let id = this.nextId();
|
||||
this.outstandingSubscriptions.set(
|
||||
id,
|
||||
{
|
||||
err: connectionErrFunc,
|
||||
event: eventFunc,
|
||||
quit: quitFunc
|
||||
}
|
||||
);
|
||||
|
||||
this.sendJSONToChannel({
|
||||
id,
|
||||
action: "subscribe",
|
||||
ship,
|
||||
app,
|
||||
path
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
// quit the channel
|
||||
//
|
||||
delete() {
|
||||
let id = this.nextId();
|
||||
navigator.sendBeacon(this.channelURL(), JSON.stringify([{
|
||||
id,
|
||||
action: "delete"
|
||||
}]));
|
||||
}
|
||||
|
||||
// unsubscribe to a specific subscription
|
||||
//
|
||||
unsubscribe(subscription) {
|
||||
let id = this.nextId();
|
||||
this.sendJSONToChannel({
|
||||
id,
|
||||
action: "unsubscribe",
|
||||
subscription
|
||||
});
|
||||
}
|
||||
|
||||
// sends a JSON command command to the server.
|
||||
//
|
||||
sendJSONToChannel(j) {
|
||||
let req = new XMLHttpRequest();
|
||||
req.open("PUT", this.channelURL());
|
||||
req.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
if (this.lastEventId == this.lastAcknowledgedEventId) {
|
||||
let x = JSON.stringify([j]);
|
||||
req.send(x);
|
||||
} else {
|
||||
// we add an acknowledgment to clear the server side queue
|
||||
//
|
||||
// The server side puts messages it sends us in a queue until we
|
||||
// acknowledge that we received it.
|
||||
//
|
||||
let x = JSON.stringify(
|
||||
[{action: "ack", "event-id": parseInt(this.lastEventId)}, j]
|
||||
);
|
||||
req.send(x);
|
||||
|
||||
this.lastEventId = this.lastAcknowledgedEventId;
|
||||
}
|
||||
|
||||
this.connectIfDisconnected();
|
||||
}
|
||||
|
||||
// connects to the EventSource if we are not currently connected
|
||||
//
|
||||
connectIfDisconnected() {
|
||||
if (this.eventSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventSource = new EventSource(this.channelURL(), {withCredentials:true});
|
||||
this.eventSource.onmessage = e => {
|
||||
this.lastEventId = e.lastEventId;
|
||||
|
||||
let obj = JSON.parse(e.data);
|
||||
let pokeFuncs = this.outstandingPokes.get(obj.id);
|
||||
let subFuncs = this.outstandingSubscriptions.get(obj.id);
|
||||
|
||||
if (obj.response == "poke" && !!pokeFuncs) {
|
||||
let funcs = pokeFuncs;
|
||||
if (obj.hasOwnProperty("ok")) {
|
||||
funcs["success"]();
|
||||
} else if (obj.hasOwnProperty("err")) {
|
||||
funcs["fail"](obj.err);
|
||||
} else {
|
||||
console.error("Invalid poke response: ", obj);
|
||||
}
|
||||
this.outstandingPokes.delete(obj.id);
|
||||
|
||||
} else if (obj.response == "subscribe" ||
|
||||
(obj.response == "poke" && !!subFuncs)) {
|
||||
let funcs = subFuncs;
|
||||
// on a response to a subscribe, we only notify the caller on err
|
||||
//
|
||||
if (obj.hasOwnProperty("err")) {
|
||||
funcs["err"](obj.err);
|
||||
this.outstandingSubscriptions.delete(obj.id);
|
||||
}
|
||||
} else if (obj.response == "diff") {
|
||||
let funcs = subFuncs;
|
||||
funcs["event"](obj.json);
|
||||
} else if (obj.response == "quit") {
|
||||
let funcs = subFuncs;
|
||||
funcs["quit"](obj);
|
||||
this.outstandingSubscriptions.delete(obj.id);
|
||||
} else {
|
||||
console.log("Unrecognized response: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
this.eventSource.onerror = e => {
|
||||
this.delete();
|
||||
this.init();
|
||||
this.onChannelError(e);
|
||||
}
|
||||
}
|
||||
|
||||
channelURL() {
|
||||
return "/~/channel/" + this.uid;
|
||||
}
|
||||
|
||||
nextId() {
|
||||
return this.requestId++;
|
||||
}
|
||||
};
|
0
pkg/arvo/app/landscape/js/index.js
Normal file
@ -8,6 +8,7 @@
|
||||
[%3 *]
|
||||
[%4 state-zero]
|
||||
==
|
||||
::
|
||||
+$ state-zero
|
||||
$: tiles=(map term tile:launch)
|
||||
tile-ordering=(list term)
|
||||
@ -30,7 +31,7 @@
|
||||
::
|
||||
++ on-init
|
||||
^- (quip card _this)
|
||||
:_ this(state *[%3 state-two])
|
||||
:_ this(state *[%4 state-zero])
|
||||
[(launch-who q.byk.bol)]~
|
||||
::
|
||||
++ on-save !>(state)
|
||||
@ -42,8 +43,8 @@
|
||||
?: ?=(%4 -.old-state)
|
||||
[~ this(state old-state)]
|
||||
:_ this
|
||||
%+ weld
|
||||
[]~ :: TODO: kill all subscriptions
|
||||
::%+ weld
|
||||
:: []~ :: TODO: kill all subscriptions
|
||||
:~ (launch-who q.byk.bol)
|
||||
[%pass / %arvo %e %disconnect [~ /]]
|
||||
==
|
||||
@ -59,51 +60,27 @@
|
||||
?. =(jon [%s 'disable welcome message'])
|
||||
this
|
||||
this(first-time %.n)
|
||||
::
|
||||
%launch-action
|
||||
=/ act !<(action:launch vas)
|
||||
?- -.act
|
||||
%add
|
||||
=/ beforedata (~(get by data) name.act)
|
||||
=/ newdata
|
||||
?~ beforedata
|
||||
(~(put by data) name.act [*json url.act])
|
||||
(~(put by data) name.act [jon.u.beforedata url.act])
|
||||
=/ new-tile `tile:launch`[`@tas`name.act `path`subscribe.act]
|
||||
:- [%pass subscribe.act %agent [our.bol name.act] %watch subscribe.act]~
|
||||
%= this
|
||||
tiles (~(put in tiles) new-tile)
|
||||
data newdata
|
||||
path-to-tile (~(put by path-to-tile) subscribe.act name.act)
|
||||
==
|
||||
::
|
||||
%remove
|
||||
:- [%pass subscribe.act %agent [our.bol name.act] %leave ~]~
|
||||
%= this
|
||||
tiles (~(del in tiles) [name.act subscribe.act])
|
||||
data (~(del by data) name.act)
|
||||
path-to-tile (~(del by path-to-tile) subscribe.act)
|
||||
==
|
||||
==
|
||||
==
|
||||
::
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card _this)
|
||||
|^
|
||||
?> (team:title our.bowl src.bowl)
|
||||
=/ cards=(list card)
|
||||
?+ path (on-watch:def path)
|
||||
[%keys ~] (give %chat-update !>([%keys ~(key by inbox)]))
|
||||
[%all ~] (give %chat-initial !>(inbox))
|
||||
==
|
||||
[cards this]
|
||||
::
|
||||
++ give
|
||||
|= =cage
|
||||
^- (list card)
|
||||
[%give %fact ~ cage]~
|
||||
--
|
||||
(on-watch:def path)
|
||||
::
|
||||
:: |^
|
||||
:: ?> (team:title our.bowl src.bowl)
|
||||
:: =/ cards=(list card)
|
||||
:: ?+ path (on-watch:def path)
|
||||
:: [%keys ~] (give %chat-update !>([%keys ~(key by inbox)]))
|
||||
:: [%all ~] (give %chat-initial !>(inbox))
|
||||
:: ==
|
||||
:: [cards this]
|
||||
:: ::
|
||||
:: ++ give
|
||||
:: |= =cage
|
||||
:: ^- (list card)
|
||||
:: [%give %fact ~ cage]~
|
||||
:: --
|
||||
++ on-peek on-peek:def
|
||||
::
|
||||
++ on-arvo
|
||||
@ -111,6 +88,7 @@
|
||||
^- (quip card:agent:gall _this)
|
||||
?: ?=(%bound +<.sin) [~ this]
|
||||
(on-arvo:def wir sin)
|
||||
::
|
||||
++ on-agent on-agent:def
|
||||
++ on-leave on-leave:def
|
||||
++ on-fail on-fail:def
|
||||
|
Before Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.5 KiB |
@ -1,17 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dojo</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
|
||||
<link rel="icon" type="image/png" href="/~launch/img/Favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
<script src="/~channel/channel.js"></script>
|
||||
<script src="/~modulo/session.js"></script>
|
||||
<script src="/~launch/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -58,7 +58,7 @@ module.exports = {
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
chunkFilename: 'index.js',
|
||||
path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/launch/', 'js'),
|
||||
path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/landscape/', 'js'),
|
||||
publicPath: '/'
|
||||
},
|
||||
optimization: {
|
||||
|
@ -57,7 +57,7 @@ module.exports = {
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
chunkFilename: 'index.js',
|
||||
path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/launch/', 'js'),
|
||||
path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/landscape/', 'js'),
|
||||
publicPath: '/'
|
||||
},
|
||||
optimization: {
|
||||
|