Merge pull request #2606 from urbit/la-scrollback

Scrollback fixed and tested in Brave, Safari, and Chrome
This commit is contained in:
ixv 2020-03-26 09:02:24 -07:00 committed by GitHub
commit b8a222ea75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 296 additions and 283 deletions

View File

@ -101,7 +101,7 @@
?: ?=([%http-response *] path)
[~ this]
?: =(/primary path)
:: create inbox with 100 messages max per mailbox and send that along
:: create inbox with 20 messages max per mailbox and send that along
:: then quit the subscription
:_ this
[%give %fact ~ %json !>((inbox-to-json truncated-inbox-scry))]~
@ -109,7 +109,7 @@
[[%give %fact ~ %json !>(*json)]~ this]
(on-watch:def path)
::
++ message-limit 25
++ message-limit 20
::
++ truncated-inbox-scry
^- inbox

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,288 +14,299 @@ import { deSig } from '/lib/util';
export class ChatScreen extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {
numPages: 1,
scrollLocked: false
};
this.hasAskedForMessages = false;
this.onScroll = this.onScroll.bind(this);
this.updateReadInterval = setInterval(
this.updateReadNumber.bind(this),
1000
);
}
componentDidMount() {
this.updateReadNumber();
this.askForMessages();
}
componentWillUnmount() {
if (this.updateReadInterval) {
clearInterval(this.updateReadInterval);
this.updateReadInterval = null;
}
}
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (
prevProps.match.params.station !== props.match.params.station ||
prevProps.match.params.ship !== props.match.params.ship
) {
this.hasAskedForMessages = false;
if (props.envelopes.length < 100) {
this.askForMessages();
}
clearInterval(this.updateReadInterval);
this.setState(
{ scrollLocked: false },
() => {
this.scrollToBottom();
this.updateReadInterval = setInterval(
this.updateReadNumber.bind(this),
1000
);
this.updateReadNumber();
}
);
} else if (props.chatInitialized && !(props.station in props.inbox)) {
props.history.push("/~chat");
} else if (
props.envelopes.length - prevProps.envelopes.length >=
200
) {
this.hasAskedForMessages = false;
}
}
updateReadNumber() {
const { props, state } = this;
if (props.read < props.length) {
props.api.chat.read(props.station);
}
}
askForMessages() {
const { props, state } = this;
this.state = {
numPages: 1,
scrollLocked: false
};
if (props.envelopes.length === 0) {
setTimeout(() => {
this.askForMessages();
}, 500);
return;
}
this.hasAskedForMessages = false;
this.onScroll = this.onScroll.bind(this);
if (
state.numPages * 100 > props.length - 400 ||
this.hasAskedForMessages ||
props.length <= 0
) {
return;
}
this.updateReadInterval = setInterval(
this.updateReadNumber.bind(this),
1000
);
}
let end = props.envelopes[0].number;
if (end > 0) {
let start = end - 400 > 0 ? end - 400 : 0;
componentDidMount() {
this.updateReadNumber();
}
if (start === 0 && end === 1) {
return;
}
componentWillUnmount() {
if (this.updateReadInterval) {
clearInterval(this.updateReadInterval);
this.updateReadInterval = null;
}
}
this.hasAskedForMessages = true;
componentDidUpdate(prevProps, prevState) {
const { props, state } = this;
if (
prevProps.match.params.station !== props.match.params.station ||
prevProps.match.params.ship !== props.match.params.ship
) {
this.hasAskedForMessages = false;
clearInterval(this.updateReadInterval);
this.setState(
{ scrollLocked: false },
() => {
this.scrollToBottom();
this.updateReadInterval = setInterval(
this.updateReadNumber.bind(this),
1000
);
this.updateReadNumber();
}
);
} else if (props.chatInitialized && !(props.station in props.inbox)) {
props.history.push("/~chat");
} else if (
props.envelopes.length - prevProps.envelopes.length >=
200
) {
this.hasAskedForMessages = false;
}
}
updateReadNumber() {
const { props, state } = this;
if (props.read < props.length) {
props.api.chat.read(props.station);
}
}
askForMessages() {
const { props, state } = this;
if (
state.numPages * 100 < props.length - 400 ||
this.hasAskedForMessages
) {
return;
}
if (props.length > 0) {
let end = props.envelopes[0].number;
if (end > 0) {
let start = end - 400 > 0 ? end - 400 : 0;
if (start === 0 && end === 1) {
return;
}
this.hasAskedForMessages = true;
props.subscription.fetchMessages(start, end - 1, props.station);
}
}
}
scrollToBottom() {
if (!this.state.scrollLocked && this.scrollElement) {
this.scrollElement.scrollIntoView({ behavior: "smooth" });
}
}
onScroll(e) {
if (
navigator.userAgent.includes("Safari") &&
navigator.userAgent.includes("Chrome")
) {
// Google Chrome
if (e.target.scrollTop === 0) {
this.setState(
{
numPages: this.state.numPages + 1,
scrollLocked: true
},
() => {
this.askForMessages();
}
);
} else if (
e.target.scrollHeight - Math.round(e.target.scrollTop) ===
e.target.clientHeight
) {
this.setState({
numPages: 1,
scrollLocked: false
});
}
} else if (navigator.userAgent.includes("Safari")) {
// Safari
if (e.target.scrollTop === 0) {
this.setState({
numPages: 1,
scrollLocked: false
});
} else if (
e.target.scrollHeight + Math.round(e.target.scrollTop) <=
e.target.clientHeight + 10
) {
this.setState(
{
numPages: this.state.numPages + 1,
scrollLocked: true
},
() => {
this.askForMessages();
}
);
}
} else {
console.log("Your browser is not supported.");
}
}
render() {
const { props, state } = this;
let messages = props.envelopes.slice(0);
let lastMsgNum = messages.length > 0 ? messages.length : 0;
if (messages.length > 100 * state.numPages) {
messages = messages.slice(
messages.length - 100 * state.numPages,
messages.length
);
}
let pendingMessages = props.pendingMessages.has(props.station)
? props.pendingMessages.get(props.station)
: [];
pendingMessages.map(function(value) {
return (value.pending = true);
});
let reversedMessages = messages.concat(pendingMessages);
reversedMessages = reversedMessages.reverse();
reversedMessages = reversedMessages.map((msg, i) => {
// Render sigil if previous message is not by the same sender
let aut = ["author"];
let renderSigil =
_.get(reversedMessages[i + 1], aut) !==
_.get(msg, aut, msg.author);
let paddingTop = renderSigil;
let paddingBot =
_.get(reversedMessages[i - 1], aut) !==
_.get(msg, aut, msg.author);
return (
<Message
key={msg.uid}
msg={msg}
contacts={props.contacts}
renderSigil={renderSigil}
paddingTop={paddingTop}
paddingBot={paddingBot}
pending={!!msg.pending}
/>
);
});
let group = Array.from(props.group.values());
const isinPopout = props.popout ? "popout/" : "";
let ownerContact = (window.ship in props.contacts)
? props.contacts[window.ship] : false;
let title = props.station.substr(1);
if (props.association && "metadata" in props.association) {
title =
props.association.metadata.title !== ""
? props.association.metadata.title
: props.station.substr(1);
}
return (
<div
key={props.station}
className="h-100 w-100 overflow-hidden flex flex-column">
<div
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
style={{ height: "1rem" }}>
<Link to="/~chat/">{"⟵ All Chats"}</Link>
</div>
<div
className={"pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative" +
"overflow-x-scroll overflow-x-auto-l overflow-x-auto-xl flex-shrink-0"}
style={{ height: 48 }}>
<SidebarSwitcher
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2 white-d">
<h2
className={"dib f9 fw4 lh-solid v-top " +
((title === props.station.substr(1)) ? "mono" : "")}
style={{ width: "max-content" }}>
{title}
</h2>
</Link>
<ChatTabBar
{...props}
station={props.station}
numPeers={group.length}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}
api={props.api}
/>
</div>
<div
className="overflow-y-scroll bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse"
style={{ height: "100%", resize: "vertical" }}
onScroll={this.onScroll}>
<div
ref={el => {
this.scrollElement = el;
}}></div>
{ (
!(props.station in props.chatSynced) &&
(reversedMessages.length > 0)
) ? (
<ResubscribeElement
api={props.api}
host={props.match.params.ship}
station={props.station} />
) : (<div/>)
}
{reversedMessages}
</div>
<ChatInput
api={props.api}
numMsgs={lastMsgNum}
station={props.station}
owner={deSig(props.match.params.ship)}
ownerContact={ownerContact}
permissions={props.permissions}
envelopes={props.envelopes}
contacts={props.contacts}
placeholder="Message..."
/>
</div>
);
}
props.subscription.fetchMessages(start, end - 1, props.station);
}
}
scrollToBottom() {
if (!this.state.scrollLocked && this.scrollElement) {
this.scrollElement.scrollIntoView({ behavior: "smooth" });
}
}
onScroll(e) {
if (
navigator.userAgent.includes("Safari") &&
navigator.userAgent.includes("Chrome")
) {
// Google Chrome
if (e.target.scrollTop === 0) {
this.setState(
{
numPages: this.state.numPages + 1,
scrollLocked: true
},
() => {
this.askForMessages();
}
);
} else if (
e.target.scrollHeight - Math.round(e.target.scrollTop) ===
e.target.clientHeight
) {
this.setState({
numPages: 1,
scrollLocked: false
});
}
} else if (navigator.userAgent.includes("Safari")) {
// Safari
if (e.target.scrollTop === 0) {
this.setState({
numPages: 1,
scrollLocked: false
});
} else if (
e.target.scrollHeight + Math.round(e.target.scrollTop) <=
e.target.clientHeight + 10
) {
this.setState(
{
numPages: this.state.numPages + 1,
scrollLocked: true
},
() => {
this.askForMessages();
}
);
}
} else {
console.log("Your browser is not supported.");
}
}
render() {
const { props, state } = this;
let messages = props.envelopes.slice(0);
let lastMsgNum = messages.length > 0 ? messages.length : 0;
if (messages.length > 100 * state.numPages) {
messages = messages.slice(
messages.length - 100 * state.numPages,
messages.length
);
}
let pendingMessages = props.pendingMessages.has(props.station)
? props.pendingMessages.get(props.station)
: [];
pendingMessages.map(function(value) {
return (value.pending = true);
});
let reversedMessages = messages.concat(pendingMessages);
reversedMessages = reversedMessages.reverse();
reversedMessages = reversedMessages.map((msg, i) => {
// Render sigil if previous message is not by the same sender
let aut = ["author"];
let renderSigil =
_.get(reversedMessages[i + 1], aut) !==
_.get(msg, aut, msg.author);
let paddingTop = renderSigil;
let paddingBot =
_.get(reversedMessages[i - 1], aut) !==
_.get(msg, aut, msg.author);
return (
<Message
key={msg.uid}
msg={msg}
contacts={props.contacts}
renderSigil={renderSigil}
paddingTop={paddingTop}
paddingBot={paddingBot}
pending={!!msg.pending}
/>
);
});
let group = Array.from(props.group.values());
const isinPopout = props.popout ? "popout/" : "";
let ownerContact = (window.ship in props.contacts)
? props.contacts[window.ship] : false;
let title = props.station.substr(1);
if (props.association && "metadata" in props.association) {
title =
props.association.metadata.title !== ""
? props.association.metadata.title
: props.station.substr(1);
}
return (
<div
key={props.station}
className="h-100 w-100 overflow-hidden flex flex-column">
<div
className="w-100 dn-m dn-l dn-xl inter pt4 pb6 pl3 f8"
style={{ height: "1rem" }}>
<Link to="/~chat/">{"⟵ All Chats"}</Link>
</div>
<div
className={"pl4 pt2 bb b--gray4 b--gray1-d bg-gray0-d flex relative" +
"overflow-x-scroll overflow-x-auto-l overflow-x-auto-xl flex-shrink-0"}
style={{ height: 48 }}>
<SidebarSwitcher
sidebarShown={this.props.sidebarShown}
popout={this.props.popout}
/>
<Link to={`/~chat/` + isinPopout + `room` + props.station}
className="pt2 white-d">
<h2
className={"dib f9 fw4 lh-solid v-top " +
((title === props.station.substr(1)) ? "mono" : "")}
style={{ width: "max-content" }}>
{title}
</h2>
</Link>
<ChatTabBar
{...props}
station={props.station}
numPeers={group.length}
isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout}
api={props.api}
/>
</div>
<div
className="overflow-y-scroll bg-white bg-gray0-d pt3 pb2 flex flex-column-reverse"
style={{ height: "100%", resize: "vertical" }}
onScroll={this.onScroll}>
<div
ref={el => {
this.scrollElement = el;
}}></div>
{ (
!(props.station in props.chatSynced) &&
(reversedMessages.length > 0)
) ? (
<ResubscribeElement
api={props.api}
host={props.match.params.ship}
station={props.station} />
) : (<div/>)
}
{reversedMessages}
</div>
<ChatInput
api={props.api}
numMsgs={lastMsgNum}
station={props.station}
owner={deSig(props.match.params.ship)}
ownerContact={ownerContact}
permissions={props.permissions}
envelopes={props.envelopes}
contacts={props.contacts}
placeholder="Message..."
/>
</div>
);
}
}

View File

@ -400,6 +400,7 @@ export class ChatInput extends Component {
}
})
if (message.length > 0) {
message = message.join(" ");
message = this.getLetterType(message);
@ -412,7 +413,8 @@ export class ChatInput extends Component {
message = [];
}
// perf: setTimeout(this.closure, 2000);
// perf:
//setTimeout(this.closure, 2000);
this.setState({
message: '',