mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
Merge pull request #2825 from urbit/lf/chat-bottom-scroll
chat-js: fix scrolling and unread behaviour
This commit is contained in:
commit
06f44ab9f1
@ -22,6 +22,37 @@ function getNumPending(props) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ACTIVITY_TIMEOUT = 60000; // a minute
|
||||||
|
const DEFAULT_BACKLOG_SIZE = 300;
|
||||||
|
|
||||||
|
function scrollIsAtTop(container) {
|
||||||
|
if ((navigator.userAgent.includes("Safari") &&
|
||||||
|
navigator.userAgent.includes("Chrome")) ||
|
||||||
|
navigator.userAgent.includes("Firefox")
|
||||||
|
) {
|
||||||
|
return container.scrollTop === 0;
|
||||||
|
} else if (navigator.userAgent.includes("Safari")) {
|
||||||
|
return container.scrollHeight + Math.round(container.scrollTop) <=
|
||||||
|
container.clientHeight + 10;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollIsAtBottom(container) {
|
||||||
|
if ((navigator.userAgent.includes("Safari") &&
|
||||||
|
navigator.userAgent.includes("Chrome")) ||
|
||||||
|
navigator.userAgent.includes("Firefox")
|
||||||
|
) {
|
||||||
|
return container.scrollHeight - Math.round(container.scrollTop) <=
|
||||||
|
container.clientHeight + 10;
|
||||||
|
} else if (navigator.userAgent.includes("Safari")) {
|
||||||
|
return container.scrollTop === 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ChatScreen extends Component {
|
export class ChatScreen extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -29,9 +60,10 @@ export class ChatScreen extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
numPages: 1,
|
numPages: 1,
|
||||||
scrollLocked: false,
|
scrollLocked: false,
|
||||||
|
read: props.read,
|
||||||
|
active: true,
|
||||||
// only for FF
|
// only for FF
|
||||||
lastScrollHeight: null,
|
lastScrollHeight: null,
|
||||||
scrollBottom: true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.hasAskedForMessages = false;
|
this.hasAskedForMessages = false;
|
||||||
@ -41,6 +73,12 @@ export class ChatScreen extends Component {
|
|||||||
this.onScroll = this.onScroll.bind(this);
|
this.onScroll = this.onScroll.bind(this);
|
||||||
|
|
||||||
this.unreadMarker = null;
|
this.unreadMarker = null;
|
||||||
|
this.scrolledToMarker = false;
|
||||||
|
this.setUnreadMarker = this.setUnreadMarker.bind(this);
|
||||||
|
|
||||||
|
this.activityTimeout = true;
|
||||||
|
this.handleActivity = this.handleActivity.bind(this);
|
||||||
|
this.setInactive = this.setInactive.bind(this);
|
||||||
|
|
||||||
moment.updateLocale('en', {
|
moment.updateLocale('en', {
|
||||||
calendar: {
|
calendar: {
|
||||||
@ -56,10 +94,72 @@ export class ChatScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.askForMessages();
|
document.addEventListener("mousemove", this.handleActivity, false);
|
||||||
this.scrollToBottom();
|
document.addEventListener("mousedown", this.handleActivity, false);
|
||||||
|
document.addEventListener("keypress", this.handleActivity, false);
|
||||||
|
document.addEventListener("touchmove", this.handleActivity, false);
|
||||||
|
this.activityTimeout = setTimeout(this.setInactive, ACTIVITY_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("mousemove", this.handleActivity, false);
|
||||||
|
document.removeEventListener("mousedown", this.handleActivity, false);
|
||||||
|
document.removeEventListener("keypress", this.handleActivity, false);
|
||||||
|
document.removeEventListener("touchmove", this.handleActivity, false);
|
||||||
|
if(this.activityTimeout) {
|
||||||
|
clearTimeout(this.activityTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleActivity() {
|
||||||
|
if(!this.state.active) {
|
||||||
|
this.setState({ active: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.activityTimeout) {
|
||||||
|
clearTimeout(this.activityTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activityTimeout = setTimeout(this.setInactive, ACTIVITY_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInactive() {
|
||||||
|
this.activityTimeout = null;
|
||||||
|
this.setState({ active: false, scrollLocked: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedNewChat() {
|
||||||
|
const { props } = this;
|
||||||
|
this.hasAskedForMessages = false;
|
||||||
|
|
||||||
|
this.unreadMarker = null;
|
||||||
|
this.scrolledToMarker = false;
|
||||||
|
|
||||||
|
this.setState({ read: props.read });
|
||||||
|
|
||||||
|
const unread = props.length - props.read;
|
||||||
|
const unreadUnloaded = unread - props.envelopes.length;
|
||||||
|
|
||||||
|
if(unreadUnloaded + 20 > DEFAULT_BACKLOG_SIZE) {
|
||||||
|
this.askForMessages(unreadUnloaded + 20);
|
||||||
|
} else {
|
||||||
|
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(props.read === props.length){
|
||||||
|
this.scrolledToMarker = true;
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
scrollLocked: false,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.setState({ scrollLocked: true, numPages: Math.ceil(unread/100) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
@ -68,18 +168,7 @@ export class ChatScreen extends Component {
|
|||||||
prevProps.match.params.station !== props.match.params.station ||
|
prevProps.match.params.station !== props.match.params.station ||
|
||||||
prevProps.match.params.ship !== props.match.params.ship
|
prevProps.match.params.ship !== props.match.params.ship
|
||||||
) {
|
) {
|
||||||
this.hasAskedForMessages = false;
|
this.receivedNewChat();
|
||||||
|
|
||||||
if (props.envelopes.length < 100) {
|
|
||||||
this.askForMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
|
||||||
{ scrollLocked: false },
|
|
||||||
() => {
|
|
||||||
this.scrollToBottom();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (props.chatInitialized &&
|
} else if (props.chatInitialized &&
|
||||||
!(props.station in props.inbox) &&
|
!(props.station in props.inbox) &&
|
||||||
(!!props.chatSynced && !(props.station in props.chatSynced))) {
|
(!!props.chatSynced && !(props.station in props.chatSynced))) {
|
||||||
@ -89,21 +178,26 @@ export class ChatScreen extends Component {
|
|||||||
props.envelopes.length >= prevProps.envelopes.length + 10
|
props.envelopes.length >= prevProps.envelopes.length + 10
|
||||||
) {
|
) {
|
||||||
this.hasAskedForMessages = false;
|
this.hasAskedForMessages = false;
|
||||||
|
} else if(props.length !== prevProps.length &&
|
||||||
|
prevProps.length === prevState.read &&
|
||||||
|
state.active
|
||||||
|
) {
|
||||||
|
this.setState({ read: props.length });
|
||||||
|
this.props.api.chat.read(this.props.station);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!prevProps.chatInitialized && props.chatInitialized) {
|
||||||
|
this.receivedNewChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FF logic
|
|
||||||
if (
|
if (
|
||||||
navigator.userAgent.includes("Firefox") &&
|
|
||||||
(props.length !== prevProps.length ||
|
(props.length !== prevProps.length ||
|
||||||
props.envelopes.length !== prevProps.envelopes.length ||
|
props.envelopes.length !== prevProps.envelopes.length ||
|
||||||
getNumPending(props) !== this.lastNumPending ||
|
getNumPending(props) !== this.lastNumPending ||
|
||||||
state.numPages !== prevState.numPages)
|
state.numPages !== prevState.numPages)
|
||||||
) {
|
) {
|
||||||
if(state.scrollBottom) {
|
this.scrollToBottom();
|
||||||
setTimeout(() => {
|
if(navigator.userAgent.includes("Firefox")) {
|
||||||
this.scrollToBottom();
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.recalculateScrollTop();
|
this.recalculateScrollTop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,16 +205,9 @@ export class ChatScreen extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
askForMessages() {
|
askForMessages(size) {
|
||||||
const { props, state } = this;
|
const { props, state } = this;
|
||||||
|
|
||||||
if (props.envelopes.length === 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.askForMessages();
|
|
||||||
}, 500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
props.envelopes.length >= props.length ||
|
props.envelopes.length >= props.length ||
|
||||||
this.hasAskedForMessages ||
|
this.hasAskedForMessages ||
|
||||||
@ -132,7 +219,7 @@ export class ChatScreen extends Component {
|
|||||||
let start =
|
let start =
|
||||||
props.length - props.envelopes[props.envelopes.length - 1].number;
|
props.length - props.envelopes[props.envelopes.length - 1].number;
|
||||||
if (start > 0) {
|
if (start > 0) {
|
||||||
let end = start + 300 < props.length ? start + 300 : props.length;
|
const end = start + size < props.length ? start + size : props.length;
|
||||||
this.hasAskedForMessages = true;
|
this.hasAskedForMessages = true;
|
||||||
props.subscription.fetchMessages(start + 1, end, props.station);
|
props.subscription.fetchMessages(start + 1, end, props.station);
|
||||||
}
|
}
|
||||||
@ -161,80 +248,51 @@ export class ChatScreen extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onScroll(e) {
|
onScroll(e) {
|
||||||
if (
|
if(scrollIsAtTop(e.target)) {
|
||||||
(navigator.userAgent.includes("Safari") &&
|
// Save scroll position for FF
|
||||||
navigator.userAgent.includes("Chrome")) ||
|
if (navigator.userAgent.includes('Firefox')) {
|
||||||
navigator.userAgent.includes("Firefox")
|
this.setState({
|
||||||
) {
|
lastScrollHeight: e.target.scrollHeight
|
||||||
// Google Chrome and Firefox
|
});
|
||||||
if (e.target.scrollTop === 0) {
|
}
|
||||||
|
this.setState(
|
||||||
// Save scroll position for FF
|
{
|
||||||
if (navigator.userAgent.includes('Firefox')) {
|
numPages: this.state.numPages + 1,
|
||||||
|
scrollLocked: true
|
||||||
this.setState({
|
},
|
||||||
lastScrollHeight: e.target.scrollHeight
|
() => {
|
||||||
})
|
this.askForMessages(DEFAULT_BACKLOG_SIZE);
|
||||||
}
|
}
|
||||||
this.setState(
|
);
|
||||||
{
|
} else if (scrollIsAtBottom(e.target)) {
|
||||||
numPages: this.state.numPages + 1,
|
this.dismissUnread();
|
||||||
scrollLocked: true
|
this.setState({
|
||||||
},
|
numPages: 1,
|
||||||
() => {
|
scrollLocked: false
|
||||||
this.askForMessages();
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
} else if (
|
|
||||||
e.target.scrollHeight - Math.round(e.target.scrollTop) ===
|
|
||||||
e.target.clientHeight
|
|
||||||
) {
|
|
||||||
this.setState({
|
|
||||||
numPages: 1,
|
|
||||||
scrollLocked: false,
|
|
||||||
scrollBottom: true
|
|
||||||
});
|
|
||||||
} else if (navigator.userAgent.includes('Firefox')) {
|
|
||||||
this.setState({ scrollBottom: 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.");
|
|
||||||
}
|
}
|
||||||
if(!!this.unreadMarker) {
|
}
|
||||||
if(
|
|
||||||
!navigator.userAgent.includes('Firefox') &&
|
|
||||||
e.target.scrollHeight - e.target.scrollTop - (e.target.clientHeight * 1.5) + this.unreadMarker.offsetTop > 50
|
|
||||||
) {
|
|
||||||
this.props.api.chat.read(this.props.station);
|
|
||||||
} else if(navigator.userAgent.includes('Firefox') &&
|
|
||||||
this.unreadMarker.offsetTop - e.target.scrollTop - (e.target.clientHeight / 2) > 0
|
|
||||||
) {
|
|
||||||
this.props.api.chat.read(this.props.station);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
setUnreadMarker(ref) {
|
||||||
|
if(ref && !this.scrolledToMarker) {
|
||||||
|
this.setState({ scrollLocked: true }, () => {
|
||||||
|
ref.scrollIntoView({ block: 'center' });
|
||||||
|
if(ref.offsetParent &&
|
||||||
|
scrollIsAtBottom(ref.offsetParent)) {
|
||||||
|
this.dismissUnread();
|
||||||
|
this.setState({
|
||||||
|
numPages: 1,
|
||||||
|
scrollLocked: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.scrolledToMarker = true;
|
||||||
}
|
}
|
||||||
|
this.unreadMarker = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissUnread() {
|
||||||
|
this.props.api.chat.read(this.props.station);
|
||||||
}
|
}
|
||||||
|
|
||||||
chatWindow(unread) {
|
chatWindow(unread) {
|
||||||
@ -291,12 +349,12 @@ export class ChatScreen extends Component {
|
|||||||
group={props.association}
|
group={props.association}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if(unread > 0 && i === unread) {
|
if(unread > 0 && i === unread - 1) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{messageElem}
|
{messageElem}
|
||||||
<div key={'unreads'+ msg.uid} ref={ref => (this.unreadMarker = ref)} className="mv2 green2 flex items-center f9">
|
<div key={'unreads'+ msg.uid} ref={this.setUnreadMarker} className="mv2 green2 flex items-center f9">
|
||||||
<hr className="ma0 w2 b--green2 bt-0" />
|
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||||
<p className="mh4">
|
<p className="mh4">
|
||||||
New messages below
|
New messages below
|
||||||
</p>
|
</p>
|
||||||
@ -410,10 +468,13 @@ export class ChatScreen extends Component {
|
|||||||
: props.station.substr(1);
|
: props.station.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unread = props.length - props.read;
|
const unread = props.length - state.read;
|
||||||
|
|
||||||
const unreadMsg = unread > 0 && messages[unread - 1];
|
const unreadMsg = unread > 0 && messages[unread - 1];
|
||||||
|
|
||||||
|
|
||||||
|
const showUnreadNotice = props.length !== props.read && props.read === state.read;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={props.station}
|
key={props.station}
|
||||||
@ -449,11 +510,11 @@ export class ChatScreen extends Component {
|
|||||||
api={props.api}
|
api={props.api}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{ !!unreadMsg && (
|
{ !!unreadMsg && showUnreadNotice && (
|
||||||
<UnreadNotice
|
<UnreadNotice
|
||||||
unread={unread}
|
unread={unread}
|
||||||
unreadMsg={unreadMsg}
|
unreadMsg={unreadMsg}
|
||||||
onRead={() => props.api.chat.read(props.station)}
|
onRead={() => this.dismissUnread()}
|
||||||
/>
|
/>
|
||||||
) }
|
) }
|
||||||
{this.chatWindow(unread)}
|
{this.chatWindow(unread)}
|
||||||
@ -465,6 +526,7 @@ export class ChatScreen extends Component {
|
|||||||
ownerContact={ownerContact}
|
ownerContact={ownerContact}
|
||||||
envelopes={props.envelopes}
|
envelopes={props.envelopes}
|
||||||
contacts={props.contacts}
|
contacts={props.contacts}
|
||||||
|
onEnter={() => this.setState({ scrollLocked: false })}
|
||||||
s3={props.s3}
|
s3={props.s3}
|
||||||
placeholder="Message..."
|
placeholder="Message..."
|
||||||
/>
|
/>
|
||||||
|
@ -194,6 +194,8 @@ export class ChatInput extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
props.onEnter();
|
||||||
|
|
||||||
if(state.code) {
|
if(state.code) {
|
||||||
props.api.chat.message(props.station, `~${window.ship}`, Date.now(), {
|
props.api.chat.message(props.station, `~${window.ship}`, Date.now(), {
|
||||||
code: {
|
code: {
|
||||||
|
@ -32,7 +32,7 @@ export class Sigil extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={'dib ' + classes}
|
className={'dib ' + classes}
|
||||||
style={{ flexBasis: 32, backgroundColor: props.color }}
|
style={{ flexBasis: props.size, backgroundColor: props.color }}
|
||||||
>
|
>
|
||||||
{sigil({
|
{sigil({
|
||||||
patp: props.ship,
|
patp: props.ship,
|
||||||
|
@ -20,6 +20,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
|||||||
nameStyle.color = hexToRgba(hex, 0.7);
|
nameStyle.color = hexToRgba(hex, 0.7);
|
||||||
nameStyle.textShadow = '0px 0px 0px #000';
|
nameStyle.textShadow = '0px 0px 0px #000';
|
||||||
nameStyle.filter = 'contrast(1.3) saturate(1.5)';
|
nameStyle.filter = 'contrast(1.3) saturate(1.5)';
|
||||||
|
nameStyle.maxWidth = '200px';
|
||||||
sigilClass = 'v-mid';
|
sigilClass = 'v-mid';
|
||||||
nickname = contact.nickname;
|
nickname = contact.nickname;
|
||||||
}
|
}
|
||||||
@ -28,7 +29,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
|||||||
<div
|
<div
|
||||||
onClick={() => onSelect(ship)}
|
onClick={() => onSelect(ship)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'f8 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center',
|
'f9 pv1 ph3 pointer hover-bg-gray1-d hover-bg-gray4 relative flex items-center',
|
||||||
{
|
{
|
||||||
'white-d bg-gray0-d bg-white': !isSelected,
|
'white-d bg-gray0-d bg-white': !isSelected,
|
||||||
'black-d bg-gray1-d bg-gray4': isSelected
|
'black-d bg-gray1-d bg-gray4': isSelected
|
||||||
@ -38,7 +39,7 @@ function ShipSearchItem({ ship, contacts, selected, onSelect }) {
|
|||||||
>
|
>
|
||||||
<Sigil ship={'~' + ship} size={24} color={color} classes={sigilClass} />
|
<Sigil ship={'~' + ship} size={24} color={color} classes={sigilClass} />
|
||||||
{nickname && (
|
{nickname && (
|
||||||
<p style={nameStyle} className="dib ml4 b">
|
<p style={nameStyle} className="dib ml4 b truncate">
|
||||||
{nickname}
|
{nickname}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@ -232,7 +233,7 @@ export class ShipSearch extends Component {
|
|||||||
let idx = suggestions.findIndex(s => s === this.state.selected);
|
let idx = suggestions.findIndex(s => s === this.state.selected);
|
||||||
|
|
||||||
idx = backward ? idx - 1 : idx + 1;
|
idx = backward ? idx - 1 : idx + 1;
|
||||||
idx = idx % suggestions.length;
|
idx = idx % Math.min(suggestions.length, 5);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
idx = suggestions.length - 1;
|
idx = suggestions.length - 1;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ export class Sigil extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={'dib ' + classes}
|
className={'dib ' + classes}
|
||||||
style={{ flexBasis: 32, backgroundColor: props.color }}
|
style={{ flexBasis: props.size, backgroundColor: props.color }}
|
||||||
>
|
>
|
||||||
{sigil({
|
{sigil({
|
||||||
patp: props.ship,
|
patp: props.ship,
|
||||||
|
@ -17,7 +17,7 @@ export class Sigil extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={"dib " + classes}
|
className={"dib " + classes}
|
||||||
style={{ flexBasis: 16, backgroundColor: props.color }}>
|
style={{ flexBasis: props.size, backgroundColor: props.color }}>
|
||||||
{sigil({
|
{sigil({
|
||||||
patp: props.ship,
|
patp: props.ship,
|
||||||
renderer: reactRenderer,
|
renderer: reactRenderer,
|
||||||
|
@ -17,7 +17,7 @@ export class Sigil extends Component {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={"dib " + classes}
|
className={"dib " + classes}
|
||||||
style={{ flexBasis: 32, backgroundColor: props.color }}>
|
style={{ flexBasis: props.size, backgroundColor: props.color }}>
|
||||||
{sigil({
|
{sigil({
|
||||||
patp: props.ship,
|
patp: props.ship,
|
||||||
renderer: reactRenderer,
|
renderer: reactRenderer,
|
||||||
|
Loading…
Reference in New Issue
Block a user