mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-01 11:33:41 +03:00
chat: sidebar redesign, sorted by group
This commit is contained in:
parent
f5e9b2d171
commit
afa38b1d2d
@ -190,8 +190,8 @@ h2 {
|
||||
}
|
||||
|
||||
@media all and (min-width: 34.375em) and (max-width: 46.875em) {
|
||||
.flex-basis-300-m {
|
||||
flex-basis: 300px;
|
||||
.flex-basis-250-m {
|
||||
flex-basis: 250px;
|
||||
}
|
||||
.h-100-minus-40-m {
|
||||
height: calc(100% - 40px);
|
||||
@ -202,8 +202,8 @@ h2 {
|
||||
}
|
||||
|
||||
@media all and (min-width: 46.875em) and (max-width: 60em) {
|
||||
.flex-basis-300-l {
|
||||
flex-basis: 300px;
|
||||
.flex-basis-250-l {
|
||||
flex-basis: 250px;
|
||||
}
|
||||
.h-100-minus-40-l {
|
||||
height: calc(100% - 40px);
|
||||
@ -214,8 +214,8 @@ h2 {
|
||||
}
|
||||
|
||||
@media all and (min-width: 60em) {
|
||||
.flex-basis-300-xl {
|
||||
flex-basis: 300px;
|
||||
.flex-basis-250-xl {
|
||||
flex-basis: 250px;
|
||||
}
|
||||
.h-100-minus-40-xl {
|
||||
height: calc(100% - 40px);
|
||||
|
39
pkg/interface/chat/src/js/components/lib/channel-item.js
Normal file
39
pkg/interface/chat/src/js/components/lib/channel-item.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class ChannelItem extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
const { props } = this;
|
||||
props.history.push('/~chat/room' + props.box);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let unreadElem = !!props.unread
|
||||
? "fw6"
|
||||
: "";
|
||||
|
||||
let title = props.title;
|
||||
|
||||
let selectedCss = !!props.selected
|
||||
? 'bg-gray4 bg-gray1-d gray3-d c-default'
|
||||
: 'bg-white bg-gray0-d gray3-d hover-bg-gray5 hover-bg-gray1-d pointer';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"z1 ph4 pv1 " + selectedCss}
|
||||
onClick={this.onClick.bind(this)}>
|
||||
<div className="w-100 v-mid">
|
||||
<p className={"dib f9 " + unreadElem}>
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
53
pkg/interface/chat/src/js/components/lib/group-item.js
Normal file
53
pkg/interface/chat/src/js/components/lib/group-item.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react';
|
||||
import { ChannelItem } from './channel-item';
|
||||
|
||||
export class GroupItem extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
let association = !!props.association ? props.association : {};
|
||||
|
||||
let title = association["app-path"] ? association["app-path"] : "Direct Messages";
|
||||
if (association.metadata && association.metadata.title) {
|
||||
title = association.metadata.title !== ""
|
||||
? association.metadata.title
|
||||
: title;
|
||||
}
|
||||
|
||||
let channels = !!props.channels ? props.channels : [];
|
||||
let first = (props.index === 0) ? "pt1" : "pt4"
|
||||
|
||||
|
||||
let channelItems = channels.map((each, i) => {
|
||||
let unread = props.unreads[each];
|
||||
let title = each.substr(1);
|
||||
if (
|
||||
each in props.chatMetadata &&
|
||||
props.chatMetadata[each].metadata
|
||||
) {
|
||||
title = props.chatMetadata[each].metadata.title
|
||||
? props.chatMetadata[each].metadata.title
|
||||
: each.substr(1);
|
||||
}
|
||||
let selected = props.station === each;
|
||||
|
||||
return (
|
||||
<ChannelItem
|
||||
key={i}
|
||||
unread={unread}
|
||||
title={title}
|
||||
selected={selected}
|
||||
box={each}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className={first}>
|
||||
<p className="f9 ph4 fw6 gray3">{title}</p>
|
||||
{channelItems}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default GroupItem
|
@ -1,98 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { cite } from '../../lib/util';
|
||||
import classnames from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
export class SidebarItem extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
timeSinceNewestMessage: this.getTimeSinceNewestMessage()
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateTimeSinceNewestMessageInterval = setInterval( () => {
|
||||
this.setState({timeSinceNewestMessage: this.getTimeSinceNewestMessage()});
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.updateTimeSinceNewestMessageInterval) {
|
||||
clearInterval(this.updateTimeSinceNewestMessageInterval);
|
||||
this.updateTimeSinceNewestMessageInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
getTimeSinceNewestMessage() {
|
||||
return !!this.props.when ?
|
||||
moment.unix(this.props.when / 1000).from(moment.utc())
|
||||
: '';
|
||||
}
|
||||
|
||||
onClick() {
|
||||
const { props } = this;
|
||||
props.history.push('/~chat/room' + props.box);
|
||||
}
|
||||
|
||||
getLetter(lett) {
|
||||
if ('text' in lett) {
|
||||
return lett.text;
|
||||
} else if ('url' in lett) {
|
||||
return lett.url;
|
||||
} else if ('code' in lett) {
|
||||
return lett.code.expression;
|
||||
} else if ('me' in lett) {
|
||||
return lett.me;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let unreadElem = !!props.unread
|
||||
? "green2"
|
||||
: "";
|
||||
|
||||
let title = props.title;
|
||||
|
||||
let box = props.box.substr(1);
|
||||
|
||||
let latest = this.getLetter(props.latest);
|
||||
|
||||
let selectedCss = !!props.selected
|
||||
? 'bg-gray5 bg-gray1-d gray3-d c-default'
|
||||
: 'bg-white bg-gray0-d gray3-d pointer';
|
||||
|
||||
let authorCss = (props.nickname === props.ship)
|
||||
? "mono" : "";
|
||||
|
||||
let author = (props.nickname === props.ship)
|
||||
? cite(props.ship) : props.nickname;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={"z1 pa3 pt2 pb2 bb b--gray4 b--gray1-d " + selectedCss}
|
||||
onClick={this.onClick.bind(this)}>
|
||||
<div className="w-100 v-mid">
|
||||
<p className="dib f8">
|
||||
{title}
|
||||
</p>
|
||||
<p className="f8 db mono gray3 gray2-d pt1">{box}</p>
|
||||
</div>
|
||||
<div className="w-100 pt3">
|
||||
<p className={((unreadElem === "") ? "black white-d" : "") +
|
||||
unreadElem + " dib f9 mr3 mw4 truncate v-mid " + authorCss}
|
||||
title={props.ship || ""}>
|
||||
{(author === "~") ? "" : author}
|
||||
</p>
|
||||
<p className="dib mono f9 gray3 v-mid">{state.timeSinceNewestMessage}</p>
|
||||
</div>
|
||||
<p className="f8 clamp-3 pt1">{latest}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,15 +1,13 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from "react-router-dom";
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Welcome from '/components/lib/welcome.js';
|
||||
import { alphabetiseAssociations } from '../lib/util';
|
||||
import { SidebarInvite } from '/components/lib/sidebar-invite';
|
||||
import { SidebarItem } from '/components/lib/sidebar-item';
|
||||
import { GroupItem } from '/components/lib/group-item';
|
||||
|
||||
|
||||
export class Sidebar extends Component {
|
||||
|
||||
onClickNew() {
|
||||
this.props.history.push('/~chat/new');
|
||||
}
|
||||
@ -21,6 +19,32 @@ export class Sidebar extends Component {
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
let associations = alphabetiseAssociations(props.associations.contacts);
|
||||
|
||||
let groupedChannels = {};
|
||||
Object.keys(props.inbox).map((box) => {
|
||||
if (box.startsWith("/~/")) {
|
||||
if (groupedChannels["/~/"]) {
|
||||
let array = groupedChannels["/~/"];
|
||||
array.push(box);
|
||||
groupedChannels["/~/"] = array;
|
||||
} else {
|
||||
groupedChannels["/~/"] = [box]
|
||||
};
|
||||
};
|
||||
let path = !!props.associations.chat[box]
|
||||
? props.associations.chat[box]["group-path"] : box;
|
||||
if (path in associations) {
|
||||
if (groupedChannels[path]) {
|
||||
let array = groupedChannels[path];
|
||||
array.push(box);
|
||||
groupedChannels[path] = array;
|
||||
} else {
|
||||
groupedChannels[path] = [box];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let sidebarInvites = Object.keys(props.invites)
|
||||
.map((uid) => {
|
||||
return (
|
||||
@ -31,75 +55,48 @@ export class Sidebar extends Component {
|
||||
);
|
||||
});
|
||||
|
||||
let sidebarItems = Object.keys(props.inbox)
|
||||
.map((box) => {
|
||||
let msg = props.messagePreviews[box];
|
||||
let letter = _.has(msg, 'letter')
|
||||
? msg.letter
|
||||
: {text: 'No messages yet'};
|
||||
let author = !!msg ? msg.author : '';
|
||||
let when = !!msg ? msg.when : 0;
|
||||
|
||||
let title = box.substr(1);
|
||||
let associatedGroup = box;
|
||||
if (
|
||||
box in props.associations["chat"] &&
|
||||
props.associations.chat[box].metadata
|
||||
) {
|
||||
title = props.associations.chat[box].metadata.title
|
||||
? props.associations.chat[box].metadata.title
|
||||
: box.substr(1);
|
||||
associatedGroup = props.associations.chat[box]["group-path"]
|
||||
? props.associations.chat[box]["group-path"]
|
||||
: box;
|
||||
let groupedItems = Object.keys(associations)
|
||||
.map((each, i) => {
|
||||
let channels = groupedChannels[each];
|
||||
if (channels.length === 0) return;
|
||||
if (groupedChannels["/~/"] && groupedChannels["/~/"].length !== 0) {
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
let nickname = author;
|
||||
if (associatedGroup in props.contacts &&
|
||||
author in props.contacts[associatedGroup]) {
|
||||
nickname = props.contacts[associatedGroup][author].nickname
|
||||
? props.contacts[associatedGroup][author].nickname
|
||||
: author;
|
||||
}
|
||||
|
||||
return {
|
||||
msg,
|
||||
when,
|
||||
author,
|
||||
nickname,
|
||||
letter,
|
||||
box,
|
||||
title: title,
|
||||
selected: props.station === box
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return b.when - a.when;
|
||||
})
|
||||
.map((obj) => {
|
||||
let unread = props.unreads[obj.box];
|
||||
return (
|
||||
<SidebarItem
|
||||
key={obj.box + '/' + obj.when}
|
||||
title={obj.title}
|
||||
latest={obj.letter}
|
||||
box={obj.box}
|
||||
when={obj.when}
|
||||
ship={obj.author}
|
||||
nickname={obj.nickname}
|
||||
selected={obj.selected}
|
||||
unread={unread}
|
||||
history={props.history}
|
||||
return(
|
||||
<GroupItem
|
||||
key={i}
|
||||
index={i}
|
||||
association={associations[each]}
|
||||
chatMetadata={props.associations["chat"]}
|
||||
channels={channels}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
unreads={props.unreads}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
)
|
||||
});
|
||||
if (groupedChannels["/~/"] && groupedChannels["/~/"].length !== 0) {
|
||||
groupedItems.unshift(
|
||||
<GroupItem
|
||||
association={"/~/"}
|
||||
chatMetadata={props.associations["chat"]}
|
||||
channels={groupedChannels["/~/"]}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
unreads={props.unreads}
|
||||
index={0}
|
||||
key={"/~/"}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`h-100-minus-96-s h-100 w-100 overflow-x-hidden flex
|
||||
bg-gray0-d flex-column relative z1`}>
|
||||
<div className="w-100 bg-transparent pa4 bb b--gray4 b--gray1-d"
|
||||
style={{paddingBottom: 13}}>
|
||||
<div className="w-100 bg-transparent pa4">
|
||||
<a
|
||||
className="dib f9 pointer green2 gray4-d mr4"
|
||||
onClick={this.onClickNew.bind(this)}>
|
||||
@ -114,7 +111,7 @@ export class Sidebar extends Component {
|
||||
<div className="overflow-y-auto h-100">
|
||||
<Welcome inbox={props.inbox}/>
|
||||
{sidebarInvites}
|
||||
{sidebarItems}
|
||||
{groupedItems}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -45,8 +45,8 @@ export class Skeleton extends Component {
|
||||
<div
|
||||
className={
|
||||
`fl h-100 br b--gray4 b--gray1-d overflow-x-hidden
|
||||
flex-basis-full-s flex-basis-300-m flex-basis-300-l
|
||||
flex-basis-300-xl ` +
|
||||
flex-basis-full-s flex-basis-250-m flex-basis-250-l
|
||||
flex-basis-250-xl ` +
|
||||
sidebarHide +
|
||||
" " +
|
||||
sidebarHideOnMobile
|
||||
|
@ -132,3 +132,25 @@ export function cite(ship) {
|
||||
}
|
||||
return `~${patp}`;
|
||||
}
|
||||
|
||||
export function alphabetiseAssociations(associations) {
|
||||
let result = {};
|
||||
Object.keys(associations).sort((a, b) => {
|
||||
let aName = a.substr(1);
|
||||
let bName = b.substr(1);
|
||||
if (a.metadata && a.metadata.title) {
|
||||
aName = a.metadata.title !== ""
|
||||
? a.metadata.title
|
||||
: a.substr(1);
|
||||
}
|
||||
if (b.metadata && b.metadata.title) {
|
||||
bName = b.metadata.title !== ""
|
||||
? b.metadata.title
|
||||
: b.substr(1);
|
||||
}
|
||||
return aName.toLowerCase().localeCompare(bName.toLowerCase());
|
||||
}).map((each) => {
|
||||
result[each] = associations[each];
|
||||
})
|
||||
return result;
|
||||
}
|
Loading…
Reference in New Issue
Block a user