Infinite scrolling works, localStorage works, progressive loading of information works, new subscription pattern works

This commit is contained in:
Logan Allen 2019-05-31 11:32:13 -07:00
parent 2a2221f03a
commit bc4f1968f2
14 changed files with 611 additions and 132 deletions

View File

@ -51,6 +51,15 @@ class UrbitApi {
}); });
} }
notify(aud, bool) {
this.hall({
notify: {
aud,
pes: !!bool ? 'hear' : 'gone'
}
});
}
permit(cir, aud, message) { permit(cir, aud, message) {
this.hall({ this.hall({
permit: { permit: {

View File

@ -16,14 +16,47 @@ export class ChatScreen extends Component {
station: props.match.params.ship + "/" + props.match.params.station, station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station, circle: props.match.params.station,
host: props.match.params.ship, host: props.match.params.ship,
numPeople: 0 numPeople: 0,
numPages: 1,
scrollLocked: false
}; };
this.topMessage = {};
this.buildMessage = this.buildMessage.bind(this); this.buildMessage = this.buildMessage.bind(this);
this.onScroll = this.onScroll.bind(this);
} }
componentDidMount() { componentDidMount() {
this.updateNumPeople(); this.updateNumPeople();
this.scrollElement.scrollIntoView(false);
}
scrollToBottom() {
if (!this.state.scrollLocked) {
console.log('scroll to bottom');
this.scrollElement.scrollIntoView({ behavior: 'smooth' });
}
}
onScroll(e) {
if (e.target.scrollTop === 0) {
let topMessage = this.topMessage;
this.setState({
numPages: this.state.numPages + 1,
scrollLocked: true
}, () => {
this.topMessage[1].scrollIntoView(true);
});
} else if (
(e.target.scrollHeight - Math.round(e.target.scrollTop)) ===
e.target.clientHeight
) {
this.setState({
numPages: 1,
scrollLocked: false
});
}
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@ -41,6 +74,7 @@ export class ChatScreen extends Component {
this.updateReadNumber(); this.updateReadNumber();
this.updateNumPeople(); this.updateNumPeople();
this.updateNumMessagesLoaded(prevProps, prevState); this.updateNumMessagesLoaded(prevProps, prevState);
this.scrollToBottom();
} }
updateReadNumber() { updateReadNumber() {
@ -77,7 +111,7 @@ export class ChatScreen extends Component {
} }
} }
buildMessage(msg) { buildMessage(msg, index) {
let details = msg.printship ? null : getMessageContent(msg.gam); let details = msg.printship ? null : getMessageContent(msg.gam);
if (msg.printship) { if (msg.printship) {
@ -89,13 +123,29 @@ export class ChatScreen extends Component {
</a> </a>
); );
} }
return (
<Message key={msg.gam.uid} msg={msg.gam} details={details} /> if (index % 50 === 0) {
); let pageNum = index / 50;
return (
<div ref={ el => { this.topMessage[pageNum] = el; }}>
<Message
key={msg.gam.uid} msg={msg.gam} details={details} />
</div>
);
} else {
return (
<Message key={msg.gam.uid} msg={msg.gam} details={details} />
);
}
} }
render() { render() {
const { props, state } = this;
let messages = this.props.messages[this.state.station] || []; let messages = this.props.messages[this.state.station] || [];
if (messages.length > 50 * state.numPages) {
messages =
messages.slice(messages.length - (50 * state.numPages), messages.length);
}
let chatMessages = messages.map(this.buildMessage); let chatMessages = messages.map(this.buildMessage);
return ( return (
@ -104,8 +154,12 @@ export class ChatScreen extends Component {
<h2>{this.state.circle}</h2> <h2>{this.state.circle}</h2>
<ChatTabBar {...this.props} station={this.state.station} /> <ChatTabBar {...this.props} station={this.state.station} />
</div> </div>
<div className="overflow-y-scroll" style={{ flexGrow: 1 }}> <div
className="overflow-y-scroll"
style={{ flexGrow: 1 }}
onScroll={this.onScroll}>
{chatMessages} {chatMessages}
<div ref={ el => { this.scrollElement = el; }}></div>
</div> </div>
<ChatInput <ChatInput
api={this.props.api} api={this.props.api}

View File

@ -105,6 +105,42 @@ export class ChatInput extends Component {
} }
render() { render() {
const { props, state } = this;
/*let closure = () => {
let aud, sep;
let wen = Date.now();
let uid = uuid();
let aut = window.ship;
let config = props.configs[state.station];
aud = [props.station];
sep = {
lin: {
msg: Date.now().toString(),
pat: false
}
}
let message = {
uid,
aut,
wen,
aud,
sep,
};
props.api.hall({
convey: [message]
});
setTimeout(closure, 1000);
};
closure();*/
return ( return (
<div className="mt2 pa3 cf flex black bt"> <div className="mt2 pa3 cf flex black bt">
<div className="fl" style={{ flexBasis: 35, height: 40 }}> <div className="fl" style={{ flexBasis: 35, height: 40 }}>
@ -114,8 +150,8 @@ export class ChatInput extends Component {
<input className="ml2 bn" <input className="ml2 bn"
style={{ flexGrow: 1 }} style={{ flexGrow: 1 }}
ref={this.textareaRef} ref={this.textareaRef}
placeholder={this.props.placeholder} placeholder={props.placeholder}
value={this.state.message} value={state.message}
onChange={this.messageChange} /> onChange={this.messageChange} />
<div className="pointer" onClick={this.messageSubmit}> <div className="pointer" onClick={this.messageSubmit}>
<IconSend /> <IconSend />

View File

@ -1,11 +1,10 @@
import _ from 'lodash'; import _ from 'lodash';
export class ChatReducer { export class ConfigReducer {
reduce(json, state) { reduce(json, state) {
let data = _.get(json, 'chat', false); let data = _.get(json, 'chat', false);
if (data) { if (data) {
state.messages = data.messages;
state.inbox = data.inbox; state.inbox = data.inbox;
state.configs = data.configs; state.configs = data.configs;
state.circles = data.circles; state.circles = data.circles;

View File

@ -0,0 +1,15 @@
import _ from 'lodash';
export class InitialReducer {
reduce(json, state) {
let data = _.get(json, 'initial', false);
if (data) {
state.messages = data.messages;
state.inbox = data.inbox;
state.configs = data.configs;
state.circles = data.circles;
}
}
}

View File

@ -7,6 +7,7 @@ export class UpdateReducer {
if (data) { if (data) {
this.reduceInbox(_.get(data, 'inbox', false), state); this.reduceInbox(_.get(data, 'inbox', false), state);
this.reduceMessage(_.get(data, 'message', false), state); this.reduceMessage(_.get(data, 'message', false), state);
this.reduceMessages(_.get(data, 'messages', false), state);
this.reduceConfig(_.get(data, 'config', false), state); this.reduceConfig(_.get(data, 'config', false), state);
this.reduceCircles(_.get(data, 'circles', false), state); this.reduceCircles(_.get(data, 'circles', false), state);
} }
@ -26,6 +27,22 @@ export class UpdateReducer {
} }
} }
reduceMessages(messages, state) {
if (messages.circle in state.messages) {
let station = state.messages[messages.circle];
if (
station.length > 0 &&
station[station.length - 1].num === station.length - 1 &&
messages.start === station.length
) {
state.messages[messages.circle] =
state.messages[messages.circle].concat(messages.envelopes);
} else {
console.error('%messages has indices inconsistent with localStorage');
}
}
}
reduceConfig(config, state) { reduceConfig(config, state) {
if (config) { if (config) {
state.configs[config.circle] = config.config; state.configs[config.circle] = config.config;

View File

@ -1,20 +1,34 @@
import _ from 'lodash'; import _ from 'lodash';
import { ChatReducer } from '/reducers/chat'; import { InitialReducer } from '/reducers/initial';
import { ConfigReducer } from '/reducers/config';
import { UpdateReducer } from '/reducers/update'; import { UpdateReducer } from '/reducers/update';
class Store { class Store {
constructor() { constructor() {
this.state = { let state = localStorage.getItem('store');
inbox: {},
messages: [],
configs: {},
circles: []
};
this.chatReducer = new ChatReducer(); if (!state) {
this.state = {
inbox: {},
messages: [],
configs: {},
circles: [],
local: false
};
} else {
this.state = JSON.parse(state);
// TODO: wtf???
delete this.state.messages[undefined];
console.log(this.state);
this.state.local = true;
}
this.initialReducer = new InitialReducer();
this.configReducer = new ConfigReducer();
this.updateReducer = new UpdateReducer(); this.updateReducer = new UpdateReducer();
this.setState = () => {}; this.setState = () => {};
} }
setStateHandler(setState) { setStateHandler(setState) {
@ -22,14 +36,17 @@ class Store {
} }
handleEvent(data) { handleEvent(data) {
console.log(data);
let json = data.data; let json = data.data;
this.chatReducer.reduce(json, this.state); this.initialReducer.reduce(json, this.state);
this.configReducer.reduce(json, this.state);
this.updateReducer.reduce(json, this.state); this.updateReducer.reduce(json, this.state);
this.setState(this.state); this.setState(this.state);
localStorage.setItem('store', JSON.stringify(this.state));
} }
} }
export let store = new Store(); export let store = new Store();
window.store = store;

View File

@ -7,33 +7,32 @@ export class Subscription {
start() { start() {
if (api.authTokens) { if (api.authTokens) {
this.initializeChat(); this.initializeChat();
//this.setCleanupTasks();
} else { } else {
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~"); console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
} }
} }
/*setCleanupTasks() {
window.addEventListener("beforeunload", e => {
api.bindPaths.forEach(p => {
this.wipeSubscription(p);
});
});
}
wipeSubscription(path) {
api.hall({
wipe: {
sub: [{
hos: api.authTokens.ship,
pax: path
}]
}
});
}*/
initializeChat() { initializeChat() {
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat', if (store.state.local) {
let path = [];
let msg = Object.keys(store.state.messages);
for (let i = 0; i < msg.length; i++) {
let cir = msg[i];
let len = store.state.messages[cir].length;
path.push(`${cir}/${len}`);
}
path = path.join('/');
api.bind(`/primary/${path}`, 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this),
this.handleError.bind(this));
} else {
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
api.bind('/updates', 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this), this.handleEvent.bind(this),
this.handleError.bind(this)); this.handleError.bind(this));
} }

View File

@ -44,9 +44,9 @@
|= old=(unit state) |= old=(unit state)
^- (quip move _this) ^- (quip move _this)
?~ old ?~ old
=/ inboxpat /circle/inbox/config/group =/ inboxpat /circle/inbox/config-l/config-r/group-r/group-l
=/ circlespat /circles/[(scot %p our.bol)] =/ circlespat /circles/[(scot %p our.bol)]
=/ inboxwir /circle/[(scot %p our.bol)]/inbox/config/group =/ inboxwir /circle/[(scot %p our.bol)]/inbox/config/group-r/group-l
=/ inboxi/poke =/ inboxi/poke
:- %hall-action :- %hall-action
[%source %inbox %.y (silt [[our.bol %i] ~]~)] [%source %inbox %.y (silt [[our.bol %i] ~]~)]
@ -58,13 +58,97 @@
== ==
[~ this(sta u.old)] [~ this(sta u.old)]
:: ::
:: +peer-primary: subscribe to our data and updates :: +peer-messages: subscribe to subset of messages and updates
::
+$ internal-state
$:
lis=(list [circle:hall @])
item=[cir=circle:hall count=@ud]
index=@
==
++ generate-circle-indices
|= wir=wire
^- (list [circle:hall @])
=/ data
%^ spin (swag [0 (lent wir)] wir) *internal-state
|= [a=@ta b=internal-state]
^- [* out=internal-state]
=/ switch (dvr index.b 3)
?: =(q.switch 0) :: remainder 0, should be a ship
?: =(index.b 0) :: if item is null, don't add to list
:- 0
%= b
hos.cir.item (slav %p a)
index +(index.b)
==
:: if item is not null, add to list
:- 0
%= b
hos.cir.item (slav %p a)
nom.cir.item *name:hall
count.item 0
lis (snoc lis.b item.b)
index +(index.b)
==
?: =(q.switch 1) :: remainder 1, should be a circle name
:- 0
%= b
nom.cir.item a
index +(index.b)
==
?: =(q.switch 2) :: remainder 2, should be a number
:- 0
%= b
count.item (need (rush a dem))
index +(index.b)
==
!! :: impossible
?: =(index.q.data 0)
~
(snoc lis.q.data item.q.data)
:: ::
++ peer-primary ++ peer-primary
|= wir=wire |= wir=wire
^- (quip move _this) ^- (quip move _this)
=/ indices (generate-circle-indices wir)
?~ indices
:_ this
[ost.bol %diff %chat-initial str.sta]~
=* messages messages.str.sta
=/ lisunitmov/(list (unit move))
%+ turn indices
|= [cir=circle:hall start=@ud]
^- (unit move)
=/ wholelist/(unit (list envelope:hall)) (~(get by messages) cir)
?~ wholelist
~
=/ end/@ (lent u.wholelist)
?: (gte start end)
~
:- ~
:* ost.bol
%diff
%chat-update
[%messages cir start end (swag [start end] u.wholelist)]
==
=/ lismov/(list move)
%+ turn
%+ skim lisunitmov
|= umov=(unit move)
^- ?
?~ umov
%.n
%.y
need
:_ this :_ this
[ost.bol %diff %chat-streams str.sta]~ %+ weld
[ost.bol %diff %chat-config str.sta]~
lismov
::
++ peer-updates
|= wir=wire
^- (quip move _this)
[~ this]
:: ::
++ poke-noun ++ poke-noun
|= a=* |= a=*
@ -93,7 +177,7 @@
++ send-chat-update ++ send-chat-update
|= upd=update |= upd=update
^- (list move) ^- (list move)
%+ turn (prey:pubsub:userlib /primary bol) %+ turn (prey:pubsub:userlib /updates bol)
|= [=bone *] |= [=bone *]
[bone %diff %chat-update upd] [bone %diff %chat-update upd]
:: ::
@ -123,19 +207,12 @@
:: %circle wire :: %circle wire
:: ::
%circle %circle
:: ?+ -.piz
:: ::
:: :: %peers prize
:: ::
:::: %peers
:::: ?> ?=(%peers -.piz)
:::: [~ this]
:: :: :: ::
:: :: %circle prize :: :: %circle prize
:: :: :: ::
:: %circle :: %circle
?> ?=(%circle -.piz) ?> ?=(%circle -.piz)
~& piz ~& pes.piz
=/ circle/circle:hall [our.bol &2:wir] =/ circle/circle:hall [our.bol &2:wir]
?: =(circle [our.bol %inbox]) ?: =(circle [our.bol %inbox])
:: ::
@ -145,27 +222,46 @@
%- ~(uni in configs.str.sta) %- ~(uni in configs.str.sta)
^- (map circle:hall (unit config:hall)) ^- (map circle:hall (unit config:hall))
(~(run by rem.cos.piz) |=(a=config:hall `a)) (~(run by rem.cos.piz) |=(a=config:hall `a))
~& pes.piz ::
=/ circles/(list circle:hall) =/ circles/(list circle:hall)
%+ turn ~(tap in src.loc.cos.piz) %+ turn ~(tap in src.loc.cos.piz)
|= src=source:hall |= src=source:hall
^- circle:hall ^- circle:hall
cir.src cir.src
::
=/ meslis/(list [circle:hall (list envelope:hall)]) =/ meslis/(list [circle:hall (list envelope:hall)])
%+ turn circles %+ turn circles
|= cir=circle:hall |= cir=circle:hall
^- [circle:hall (list envelope:hall)] ^- [circle:hall (list envelope:hall)]
[cir ~] [cir ~]
::
=/ localpeers/(set @p)
%- silt %+ turn ~(tap by loc.pes.piz)
|= [shp=@p stat=status:hall]
shp
::
=/ peers/(map circle:hall (set @p))
%- ~(rep by rem.pes.piz)
|= [[cir=circle:hall grp=group:hall] acc=(map circle:hall (set @p))]
^- (map circle:hall (set @p))
=/ newset
%- silt %+ turn ~(tap by grp)
|= [shp=@p stat=status:hall]
shp
(~(put by acc) cir newset)
::
:- :-
%+ turn ~(tap in (~(del in (silt circles)) [our.bol %inbox])) %+ turn ~(tap in (~(del in (silt circles)) [our.bol %inbox]))
|= cir=circle:hall |= cir=circle:hall
^- move ^- move
=/ pat/path /circle/[nom.cir]/config/grams =/ pat/path /circle/[nom.cir]/config-l/config-r/group-r/group-l
[ost.bol %peer pat [our.bol %hall] pat] [ost.bol %peer pat [our.bol %hall] pat]
::
%= this %= this
inbox.str.sta loc.cos.piz inbox.str.sta loc.cos.piz
configs.str.sta configs configs.str.sta configs
messages.str.sta (molt meslis) messages.str.sta (molt meslis)
peers.str.sta (~(put by peers) [our.bol %inbox] localpeers)
== ==
:: ::
:: fill remote configs with message data :: fill remote configs with message data
@ -224,22 +320,27 @@
messages.str.sta (~(put by messages) circle (snoc nes nev.sto)) messages.str.sta (~(put by messages) circle (snoc nes nev.sto))
== ==
:: ::
:: %peer: :: %status:
:: ::
%peer %status
?> ?=(%peer -.sto) ?> ?=(%status -.sto)
~& add.sto =/ peers/(set @p)
~& who.sto ?: =(%remove -.dif.sto)
~& qer.sto (~(del in (~(got by peers.str.sta) cir.sto)) who.sto)
[~ this] (~(put in (~(got by peers.str.sta) cir.sto)) who.sto)
:- (send-chat-update [%peers cir.sto peers])
%= this
peers.str.sta (~(put by peers.str.sta) cir.sto peers)
==
:: ::
:: %config: config has changed :: %config: config has changed
:: ::
%config %config
=* circ cir.sto =* circ cir.sto
:: ::
?+ -.dif.sto ?+ -.dif.sto
[~ this] [~ this]
:: ::
:: %full: set all of config without side effects :: %full: set all of config without side effects
:: ::
@ -277,8 +378,8 @@
[~ this] [~ this]
=* affectedcir cir.src.dif.sto =* affectedcir cir.src.dif.sto
=/ newwir/wire =/ newwir/wire
/circle/[(scot %p hos.affectedcir)]/[nom.affectedcir]/grams/config /circle/[(scot %p hos.affectedcir)]/[nom.affectedcir]/grams/config/group-r/group-l
=/ pat/path /circle/[nom.affectedcir]/grams/config =/ pat/path /circle/[nom.affectedcir]/grams/config/group-r/group-l
:: we've added a source to our inbox :: we've added a source to our inbox
:: ::
?: add.dif.sto ?: add.dif.sto
@ -411,7 +512,7 @@
:: ::
%circle %circle
=/ shp/@p (slav %p &2:wir) =/ shp/@p (slav %p &2:wir)
=/ pat /circle/[&3:wir]/grams/config =/ pat /circle/[&3:wir]/grams/config/group-r/group-l
:_ this :_ this
[ost.bol %peer wir [shp %hall] wir]~ [ost.bol %peer wir [shp %hall] wir]~
:: ::

View File

@ -17,6 +17,10 @@
return module = { exports: {} }, fn(module, module.exports), module.exports; return module = { exports: {} }, fn(module, module.exports), module.exports;
} }
function getCjsExportFromNamespace (n) {
return n && n.default || n;
}
/* /*
object-assign object-assign
(c) Sindre Sorhus (c) Sindre Sorhus
@ -47458,6 +47462,8 @@
isBuffer: isBuffer isBuffer: isBuffer
}); });
var require$$0 = getCjsExportFromNamespace(bufferEs6);
var bn = createCommonjsModule(function (module) { var bn = createCommonjsModule(function (module) {
(function (module, exports) { (function (module, exports) {
@ -47510,7 +47516,7 @@
var Buffer; var Buffer;
try { try {
Buffer = bufferEs6.Buffer; Buffer = require$$0.Buffer;
} catch (e) { } catch (e) {
} }
@ -51734,6 +51740,15 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
}); });
} }
notify(aud, bool) {
this.hall({
notify: {
aud,
pes: !!bool ? 'hear' : 'gone'
}
});
}
permit(cir, aud, message) { permit(cir, aud, message) {
this.hall({ this.hall({
permit: { permit: {
@ -51814,11 +51829,22 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
let api = new UrbitApi(); let api = new UrbitApi();
window.api = api; window.api = api;
class ChatReducer { class InitialReducer {
reduce(json, state) {
let data = lodash.get(json, 'initial', false);
if (data) {
state.messages = data.messages;
state.inbox = data.inbox;
state.configs = data.configs;
state.circles = data.circles;
}
}
}
class ConfigReducer {
reduce(json, state) { reduce(json, state) {
let data = lodash.get(json, 'chat', false); let data = lodash.get(json, 'chat', false);
if (data) { if (data) {
state.messages = data.messages;
state.inbox = data.inbox; state.inbox = data.inbox;
state.configs = data.configs; state.configs = data.configs;
state.circles = data.circles; state.circles = data.circles;
@ -51832,6 +51858,7 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
if (data) { if (data) {
this.reduceInbox(lodash.get(data, 'inbox', false), state); this.reduceInbox(lodash.get(data, 'inbox', false), state);
this.reduceMessage(lodash.get(data, 'message', false), state); this.reduceMessage(lodash.get(data, 'message', false), state);
this.reduceMessages(lodash.get(data, 'messages', false), state);
this.reduceConfig(lodash.get(data, 'config', false), state); this.reduceConfig(lodash.get(data, 'config', false), state);
this.reduceCircles(lodash.get(data, 'circles', false), state); this.reduceCircles(lodash.get(data, 'circles', false), state);
} }
@ -51851,6 +51878,22 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
} }
} }
reduceMessages(messages, state) {
if (messages.circle in state.messages) {
let station = state.messages[messages.circle];
if (
station.length > 0 &&
station[station.length - 1].num === station.length - 1 &&
messages.start === station.length
) {
state.messages[messages.circle] =
state.messages[messages.circle].concat(messages.envelopes);
} else {
console.error('%messages has indices inconsistent with localStorage');
}
}
}
reduceConfig(config, state) { reduceConfig(config, state) {
if (config) { if (config) {
state.configs[config.circle] = config.config; state.configs[config.circle] = config.config;
@ -51867,16 +51910,29 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
class Store { class Store {
constructor() { constructor() {
this.state = { let state = localStorage.getItem('store');
inbox: {},
messages: [],
configs: {},
circles: []
};
this.chatReducer = new ChatReducer(); if (!state) {
this.state = {
inbox: {},
messages: [],
configs: {},
circles: [],
local: false
};
} else {
this.state = JSON.parse(state);
// TODO: wtf???
delete this.state.messages[undefined];
console.log(this.state);
this.state.local = true;
}
this.initialReducer = new InitialReducer();
this.configReducer = new ConfigReducer();
this.updateReducer = new UpdateReducer(); this.updateReducer = new UpdateReducer();
this.setState = () => {}; this.setState = () => {};
} }
setStateHandler(setState) { setStateHandler(setState) {
@ -51884,16 +51940,20 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
} }
handleEvent(data) { handleEvent(data) {
console.log(data);
let json = data.data; let json = data.data;
this.chatReducer.reduce(json, this.state); this.initialReducer.reduce(json, this.state);
this.configReducer.reduce(json, this.state);
this.updateReducer.reduce(json, this.state); this.updateReducer.reduce(json, this.state);
this.setState(this.state); this.setState(this.state);
localStorage.setItem('store', JSON.stringify(this.state));
} }
} }
let store = new Store(); let store = new Store();
window.store = store;
const _jsxFileName = "/Users/logan/Dev/interface/apps/chat/src/js/components/skeleton.js"; const _jsxFileName = "/Users/logan/Dev/interface/apps/chat/src/js/components/skeleton.js";
@ -56957,20 +57017,56 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
} }
render() { render() {
const { props, state } = this;
/*let closure = () => {
let aud, sep;
let wen = Date.now();
let uid = uuid();
let aut = window.ship;
let config = props.configs[state.station];
aud = [props.station];
sep = {
lin: {
msg: Date.now().toString(),
pat: false
}
}
let message = {
uid,
aut,
wen,
aud,
sep,
};
props.api.hall({
convey: [message]
});
setTimeout(closure, 1000);
};
closure();*/
return ( return (
react.createElement('div', { className: "mt2 pa3 cf flex black bt" , __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 109}} react.createElement('div', { className: "mt2 pa3 cf flex black bt" , __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 145}}
, react.createElement('div', { className: "fl", style: { flexBasis: 35, height: 40 }, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 110}} , react.createElement('div', { className: "fl", style: { flexBasis: 35, height: 40 }, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 146}}
, react.createElement(Sigil, { ship: window.ship, size: 32, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 111}} ) , react.createElement(Sigil, { ship: window.ship, size: 32, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 147}} )
) )
, react.createElement('div', { className: "fr h-100 flex" , style: { flexGrow: 1, height: 40 }, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 113}} , react.createElement('div', { className: "fr h-100 flex" , style: { flexGrow: 1, height: 40 }, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 149}}
, react.createElement('input', { className: "ml2 bn" , , react.createElement('input', { className: "ml2 bn" ,
style: { flexGrow: 1 }, style: { flexGrow: 1 },
ref: this.textareaRef, ref: this.textareaRef,
placeholder: this.props.placeholder, placeholder: props.placeholder,
value: this.state.message, value: state.message,
onChange: this.messageChange, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 114}} ) onChange: this.messageChange, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 150}} )
, react.createElement('div', { className: "pointer", onClick: this.messageSubmit, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 120}} , react.createElement('div', { className: "pointer", onClick: this.messageSubmit, __self: this, __source: {fileName: _jsxFileName$9, lineNumber: 156}}
, react.createElement(IconSend, {__self: this, __source: {fileName: _jsxFileName$9, lineNumber: 121}} ) , react.createElement(IconSend, {__self: this, __source: {fileName: _jsxFileName$9, lineNumber: 157}} )
) )
) )
) )
@ -56987,14 +57083,47 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
station: props.match.params.ship + "/" + props.match.params.station, station: props.match.params.ship + "/" + props.match.params.station,
circle: props.match.params.station, circle: props.match.params.station,
host: props.match.params.ship, host: props.match.params.ship,
numPeople: 0 numPeople: 0,
numPages: 1,
scrollLocked: false
}; };
this.topMessage = {};
this.buildMessage = this.buildMessage.bind(this); this.buildMessage = this.buildMessage.bind(this);
this.onScroll = this.onScroll.bind(this);
} }
componentDidMount() { componentDidMount() {
this.updateNumPeople(); this.updateNumPeople();
this.scrollElement.scrollIntoView(false);
}
scrollToBottom() {
if (!this.state.scrollLocked) {
console.log('scroll to bottom');
this.scrollElement.scrollIntoView({ behavior: 'smooth' });
}
}
onScroll(e) {
if (e.target.scrollTop === 0) {
let topMessage = this.topMessage;
this.setState({
numPages: this.state.numPages + 1,
scrollLocked: true
}, () => {
this.topMessage[1].scrollIntoView(true);
});
} else if (
(e.target.scrollHeight - Math.round(e.target.scrollTop)) ===
e.target.clientHeight
) {
this.setState({
numPages: 1,
scrollLocked: false
});
}
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@ -57012,6 +57141,7 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
this.updateReadNumber(); this.updateReadNumber();
this.updateNumPeople(); this.updateNumPeople();
this.updateNumMessagesLoaded(prevProps, prevState); this.updateNumMessagesLoaded(prevProps, prevState);
this.scrollToBottom();
} }
updateReadNumber() { updateReadNumber() {
@ -57048,42 +57178,62 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
} }
} }
buildMessage(msg) { buildMessage(msg, index) {
let details = msg.printship ? null : getMessageContent(msg.gam); let details = msg.printship ? null : getMessageContent(msg.gam);
if (msg.printship) { if (msg.printship) {
return ( return (
react.createElement('a', { react.createElement('a', {
className: "vanilla hoverline text-600 text-mono" , className: "vanilla hoverline text-600 text-mono" ,
href: prettyShip(msg.gam.aut)[1], __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 85}} href: prettyShip(msg.gam.aut)[1], __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 119}}
, prettyShip(`~${msg.gam.aut}`)[0] , prettyShip(`~${msg.gam.aut}`)[0]
) )
); );
} }
return (
react.createElement(Message, { key: msg.gam.uid, msg: msg.gam, details: details, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 93}} ) if (index % 50 === 0) {
); let pageNum = index / 50;
return (
react.createElement('div', { ref: el => { this.topMessage[pageNum] = el; }, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 130}}
, react.createElement(Message, {
key: msg.gam.uid, msg: msg.gam, details: details, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 131}} )
)
);
} else {
return (
react.createElement(Message, { key: msg.gam.uid, msg: msg.gam, details: details, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 137}} )
);
}
} }
render() { render() {
const { props, state } = this;
let messages = this.props.messages[this.state.station] || []; let messages = this.props.messages[this.state.station] || [];
if (messages.length > 50 * state.numPages) {
messages =
messages.slice(messages.length - (50 * state.numPages), messages.length);
}
let chatMessages = messages.map(this.buildMessage); let chatMessages = messages.map(this.buildMessage);
return ( return (
react.createElement('div', { className: "h-100 w-100 overflow-hidden flex flex-column" , __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 102}} react.createElement('div', { className: "h-100 w-100 overflow-hidden flex flex-column" , __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 152}}
, react.createElement('div', { className: "pl2 pt2 bb mb3" , __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 103}} , react.createElement('div', { className: "pl2 pt2 bb mb3" , __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 153}}
, react.createElement('h2', {__self: this, __source: {fileName: _jsxFileName$a, lineNumber: 104}}, this.state.circle) , react.createElement('h2', {__self: this, __source: {fileName: _jsxFileName$a, lineNumber: 154}}, this.state.circle)
, react.createElement(ChatTabBar, { ...this.props, station: this.state.station, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 105}} ) , react.createElement(ChatTabBar, { ...this.props, station: this.state.station, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 155}} )
) )
, react.createElement('div', { className: "overflow-y-scroll", style: { flexGrow: 1 }, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 107}} , react.createElement('div', {
className: "overflow-y-scroll",
style: { flexGrow: 1 },
onScroll: this.onScroll, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 157}}
, chatMessages , chatMessages
, react.createElement('div', { ref: el => { this.scrollElement = el; }, __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 162}})
) )
, react.createElement(ChatInput, { , react.createElement(ChatInput, {
api: this.props.api, api: this.props.api,
configs: this.props.configs, configs: this.props.configs,
station: this.state.station, station: this.state.station,
circle: this.state.circle, circle: this.state.circle,
placeholder: "Message...", __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 110}} ) placeholder: "Message...", __self: this, __source: {fileName: _jsxFileName$a, lineNumber: 164}} )
) )
) )
} }
@ -57545,33 +57695,32 @@ lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes\
start() { start() {
if (api.authTokens) { if (api.authTokens) {
this.initializeChat(); this.initializeChat();
//this.setCleanupTasks();
} else { } else {
console.error("~~~ ERROR: Must set api.authTokens before operation ~~~"); console.error("~~~ ERROR: Must set api.authTokens before operation ~~~");
} }
} }
/*setCleanupTasks() {
window.addEventListener("beforeunload", e => {
api.bindPaths.forEach(p => {
this.wipeSubscription(p);
});
});
}
wipeSubscription(path) {
api.hall({
wipe: {
sub: [{
hos: api.authTokens.ship,
pax: path
}]
}
});
}*/
initializeChat() { initializeChat() {
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat', if (store.state.local) {
let path = [];
let msg = Object.keys(store.state.messages);
for (let i = 0; i < msg.length; i++) {
let cir = msg[i];
let len = store.state.messages[cir].length;
path.push(`${cir}/${len}`);
}
path = path.join('/');
api.bind(`/primary/${path}`, 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this),
this.handleError.bind(this));
} else {
api.bind('/primary', 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
api.bind('/updates', 'PUT', api.authTokens.ship, 'chat',
this.handleEvent.bind(this), this.handleEvent.bind(this),
this.handleError.bind(this)); this.handleError.bind(this));
} }

View File

@ -16,8 +16,9 @@
:: ::
+$ diff +$ diff
$% [%hall-rumor rumor:hall] $% [%hall-rumor rumor:hall]
[%chat-streams streams] [%chat-initial streams]
[%chat-update update] [%chat-update update]
[%chat-config streams]
== ==
:: ::
+$ poke +$ poke
@ -49,6 +50,7 @@
+$ update +$ update
$% [%inbox con=config:hall] $% [%inbox con=config:hall]
[%message cir=circle:hall env=envelope:hall] [%message cir=circle:hall env=envelope:hall]
[%messages cir=circle:hall start=@ud end=@ud env=(list envelope:hall)]
[%config cir=circle:hall con=config:hall] [%config cir=circle:hall con=config:hall]
[%circles cir=(set name:hall)] [%circles cir=(set name:hall)]
[%peers cir=circle:hall per=(set @p)] [%peers cir=circle:hall per=(set @p)]

View File

@ -24,19 +24,20 @@
^- [@t ^json] ^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir)) :- (crip (circ:en-tape:hall-json cir))
?~(con ~ (conf:enjs:hall-json u.con)) ?~(con ~ (conf:enjs:hall-json u.con))
::
:- %messages
%- pairs
%+ turn ~(tap by messages.str)
|= [cir=circle:hall lis=(list envelope:hall)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
[%a (turn lis enve:enjs:hall-json)]
:: ::
:- %circles :- %a :- %circles :- %a
%+ turn ~(tap in circles.str) %+ turn ~(tap in circles.str)
|= nom=name:hall |= nom=name:hall
[%s nom] [%s nom]
::
:- %peers
%- pairs
%+ turn ~(tap by peers.str)
|= [cir=circle:hall per=(set @p)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
[%a (turn ~(tap in per) ship)]
::
== ==
-- --
:: ::

View File

@ -0,0 +1,56 @@
::
::
/? 309
::
/- hall
/+ chat, hall-json
::
|_ str=streams:chat
++ grow
|%
++ json
=, enjs:format
^- ^json
%+ frond %initial
%- pairs
:~
::
[%inbox (conf:enjs:hall-json inbox.str)]
::
:- %configs
%- pairs
%+ turn ~(tap by configs.str)
|= [cir=circle:hall con=(unit config:hall)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
?~(con ~ (conf:enjs:hall-json u.con))
::
:- %messages
%- pairs
%+ turn ~(tap by messages.str)
|= [cir=circle:hall lis=(list envelope:hall)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
[%a (turn lis enve:enjs:hall-json)]
::
:- %circles :- %a
%+ turn ~(tap in circles.str)
|= nom=name:hall
[%s nom]
::
:- %peers
%- pairs
%+ turn ~(tap by peers.str)
|= [cir=circle:hall per=(set @p)]
^- [@t ^json]
:- (crip (circ:en-tape:hall-json cir))
[%a (turn ~(tap in per) ship)]
::
==
--
::
++ grab
|%
++ noun streams:chat
--
--

View File

@ -30,6 +30,18 @@
[%envelope (enve:enjs:hall-json env.upd)] [%envelope (enve:enjs:hall-json env.upd)]
== ==
:: ::
:: %messages
?: =(%messages -.upd)
?> ?=(%messages -.upd)
:- %messages
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%start (numb start.upd)]
[%end (numb end.upd)]
[%envelopes [%a (turn env.upd enve:enjs:hall-json)]]
==
::
:: %config :: %config
?: =(%config -.upd) ?: =(%config -.upd)
?> ?=(%config -.upd) ?> ?=(%config -.upd)
@ -39,6 +51,8 @@
[%circle (circ:enjs:hall-json cir.upd)] [%circle (circ:enjs:hall-json cir.upd)]
[%config (conf:enjs:hall-json con.upd)] [%config (conf:enjs:hall-json con.upd)]
== ==
::
:: %circles
?: =(%circles -.upd) ?: =(%circles -.upd)
?> ?=(%circles -.upd) ?> ?=(%circles -.upd)
:- %circles :- %circles
@ -51,6 +65,16 @@
[%s nom] [%s nom]
== ==
:: ::
:: %peers
?: =(%peers -.upd)
?> ?=(%peers -.upd)
:- %peers
%- pairs
:~
[%circle (circ:enjs:hall-json cir.upd)]
[%peers [%a (turn ~(tap in per.upd) ship:enjs:format)]]
==
::
:: %noop :: %noop
[*@t *^json] [*@t *^json]
== ==