publish: sidebar redesign with grouped notebooks

This commit is contained in:
Matilde Park 2020-03-26 15:47:54 -04:00
parent afa38b1d2d
commit e7a29dc628
7 changed files with 152 additions and 173 deletions

View File

@ -69,8 +69,8 @@ a {
.db-ns {
display: block;
}
.flex-basis-300-ns {
flex-basis: 300px;
.flex-basis-250-ns {
flex-basis: 250px;
}
.h-100-m-40-ns {
height: calc(100% - 40px);

View File

@ -0,0 +1,44 @@
import React, { Component } from 'react';
import { NotebookItem } from './notebook-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"] : "Unmanaged Notebooks";
if (association.metadata && association.metadata.title) {
title = association.metadata.title !== ""
? association.metadata.title : title;
}
let groupedBooks = !!props.groupedBooks ? props.groupedBooks : [];
let first = (props.index === 0) ? "pt1" : "pt4";
let notebookItems = groupedBooks.map((each, i) => {
let unreads = props.notebooks[each]["num-unread"] || 0;
let title = each.substr(1);
if (props.notebooks[each].title) {
title = (props.notebooks[each].title !== "")
? props.notebooks[each].title : title;
}
return (
<NotebookItem
key={i}
unreadCount={unreads}
title={title}
path={each}
selected={(props.path === each)}
/>
)
})
return (
<div className={first}>
<p className="f9 ph4 fw6 gray3">{title}</p>
{notebookItems}
</div>
)
}
}
export default GroupItem;

View File

@ -1,51 +1,23 @@
import React, { Component } from 'react';
import { Route, Link } from 'react-router-dom';
import { cite } from '../../lib/util';
export class NotebookItem extends Component {
render() {
let { props } = this;
let selectedClass = (props.selected) ? "bg-gray5 bg-gray1-d b--gray4 b--gray2-d" : "b--gray4 b--gray2-d";
let postCount = (props.total === 1)
? `${props.total} post` : `${props.total} posts`;
let selectedClass = (props.selected) ? "bg-gray5 bg-gray1-d c-default" : "pointer hover-bg-gray5 hover-bg-gray1-d";
let unread = (props.unreadCount > 0)
? `${props.unreadCount} unread` : "";
let notebookContacts = (props.contactsPath in props.contacts)
? props.contacts[props.contactsPath] : {};
let contact = !!(props.author.substr(1) in notebookContacts)
? notebookContacts[props.author.substr(1)] : false;
let name = props.author;
if (contact) {
name = (contact.nickname.length > 0)
? contact.nickname : props.author;
}
if (name === props.author) {
name = cite(props.author);
}
? <p className="dib f9 fr"><span className="dib white bg-gray3 bg-gray2-d fw6 br1" style={{ padding: "1px 5px" }}>
{props.unreadCount}
</span></p> : <span/>;
return (
<Link
to={"/~publish/notebook/" + props.path}>
<div className={"w-100 v-mid f9 pl4 bb " + selectedClass}>
<p className="f9 pt1">{props.title}</p>
<p className="f9 gray2">by
<span className={"pl1 " + (contact.nickname ? null : "mono")}
title={props.author}>
{name}
</span>
</p>
<p className="f9 pb1">
{postCount}
<span className="green2 ml3">
{unread}
</span>
</p>
<div className={"w-100 v-mid f9 ph4 pv1 " + selectedClass}>
<p className="dib f9">{props.title}</p>
{unread}
</div>
</Link>
);

View File

@ -1,95 +1,11 @@
import React, { Component } from 'react'
import { Route, Link } from 'react-router-dom';
import { Dropdown } from './dropdown';
import { NotebookItem } from './notebook-item';
import { SidebarInvite } from './sidebar-invite';
import { Welcome } from './welcome';
import { GroupItem } from './group-item';
import { alphabetiseAssociations } from '../../lib/util';
export class Sidebar extends Component {
constructor(props) {
super(props);
this.state = {
sort: "oldest",
sortedBooks: new Map()
}
this.sort = this.sort.bind(this);
this.sortChange = this.sortChange.bind(this);
}
componentDidMount() {
this.sort();
}
componentDidUpdate(prevProps, prevState) {
if ((prevState.sort !== this.state.sort) || (prevProps !== this.props)) {
this.sort();
}
}
sort() {
let { props, state } = this;
let notebooks = new Map();
Object.keys(props.notebooks).map(host => {
Object.keys(props.notebooks[host]).map(notebook => {
let title = `${host}/${notebook}`;
notebooks.set(title, props.notebooks[host][notebook])
})
});
switch (state.sort) {
case "oldest":
notebooks = new Map(
[...notebooks.entries()].sort(
(a, b) => {
if ((a[1]) && (b[1])) {
return a[1]["date-created"] - b[1]["date-created"]
}
}
)
);
break;
case "newest":
notebooks = new Map(
[...notebooks.entries()].sort(
(a, b) => {
if ((a[1]) && (b[1])) {
return b[1]["date-created"] - a[1]["date-created"]
}
}
)
);
break;
case "alphabetical":
notebooks = new Map(
[...notebooks.entries()].sort((a, b) => {
if ((a[1]) && (b[1])) {
return a[1]["title"].toLowerCase().localeCompare(
b[1]["title"].toLowerCase()
);
}
})
);
break;
case "reverseAlphabetical":
notebooks = new Map(
[...notebooks.entries()].sort((a, b) => {
if ((a[1]) && (b[1])) {
return b[1]["title"].toLowerCase().localeCompare(
a[1]["title"].toLowerCase()
);
}
})
);
break;
default:
break;
}
this.setState({ sortedBooks: notebooks });
}
sortChange(event) {
this.setState({sort: event.target.value});
}
render() {
const { props, state } = this;
let activeClasses = (props.active === "sidebar") ? " " : "dn-s ";
@ -111,24 +27,70 @@ export class Sidebar extends Component {
key={i} />
)
});
let associations = !!props.associations ? alphabetiseAssociations(props.associations.contacts) : {};
let notebookItems = [...state.sortedBooks].map(([path, book]) => {
let selected = (props.path === path);
let author = path.split("/")[0];
return (
<NotebookItem
key={book.title}
title={book.title}
author={author}
contacts={props.contacts}
contactsPath={book["subscribers-group-path"]}
path={path}
total={book["num-notes"]}
unreadCount={book["num-unread"]}
selected={selected}
/>
);
})
let notebooks = {};
Object.keys(props.notebooks).map(host => {
Object.keys(props.notebooks[host]).map(notebook => {
let title = `${host}/${notebook}`;
notebooks[title] = props.notebooks[host][notebook];
})
});
let groupedNotebooks = {};
Object.keys(notebooks).map(book => {
if (notebooks[book]["subscribers-group-path"].startsWith("/~/")) {
if (groupedNotebooks["/~/"]) {
let array = groupedNotebooks["/~/"];
array.push(book);
groupedNotebooks["/~/"] = array;
} else {
groupedNotebooks["/~/"] = [book];
};
};
let path = !!notebooks[book]["subscribers-group-path"]
? notebooks[book]["subscribers-group-path"] : book;
if (path in associations) {
if (groupedNotebooks[path]) {
let array = groupedNotebooks[path];
array.push[book];
groupedNotebooks[path] = array;
} else {
groupedNotebooks[path] = [book];
}
}
});
let groupedItems = Object.keys(associations)
.map((each, i) => {
let books = groupedNotebooks[each];
if (books.length === 0) return;
if (groupedNotebooks["/~/"] && groupedNotebooks["/~/"].length !== 0) {
i = i + 1;
}
return(
<GroupItem
key={i}
index={i}
association={associations[each]}
groupedBooks={books}
notebooks={notebooks}
path={props.path}
/>
)
})
if (groupedNotebooks["/~/"] && groupedNotebooks["/~/"].length !== 0) {
groupedItems.unshift(
<GroupItem
key={"/~/"}
index={0}
association={"/~/"}
groupedBooks={groupedNotebooks["/~/"]}
notebooks={notebooks}
path={props.path}
/>
)
}
return (
<div
@ -136,53 +98,24 @@ export class Sidebar extends Component {
"bn br-m br-l br-xl b--gray4 b--gray2-d lh-copy h-100 " +
"flex-shrink-0 pt3 pt0-m pt0-l pt0-xl relative " +
"overflow-y-hidden " + activeClasses +
(hiddenClasses ? "flex-basis-100-s flex-basis-300-ns" : "dn")
(hiddenClasses ? "flex-basis-100-s flex-basis-250-ns" : "dn")
}>
<a className="db dn-m dn-l dn-xl f9 pb3 pl3" href="/">
Landscape
</a>
<div className="w-100 f9">
<Link to="/~publish/new" className="green2 mr4 f9 pl4 pt4 dib">
<Link to="/~publish/new" className="green2 pa4 f9 dib">
New Notebook
</Link>
<Link to="/~publish/join" className="f9 gray2">
Join Notebook
</Link>
<div className="pl2 pv2 bb b--gray4 b--gray2-d">
<Dropdown
width="16rem"
align="left"
options={[
{
cls: "white-d w-100 tl pointer db ph2 pv3 hover-bg-gray4 hover-bg-gray1-d bg-transparent",
txt: "Oldest",
action: () => {this.setState({sort: "oldest"})}
},
{
cls: "white-d w-100 tl pointer db ph2 pv3 hover-bg-gray4 hover-bg-gray1-d bg-transparent",
txt: "Newest",
action: () => {this.setState({sort: "newest"})}
},
{
cls: "white-d w-100 tl pointer db ph2 pv3 hover-bg-gray4 hover-bg-gray1-d bg-transparent",
txt: "A -> Z",
action: () => {this.setState({sort: "alphabetical"})}
},
{
cls: "white-d w-100 tl pointer db ph2 pv3 hover-bg-gray4 hover-bg-gray1-d bg-transparent",
txt: "Z -> A",
action: () => {this.setState({sort: "reverseAlphabetical"})}
}
]}
buttonText="Sort By"
/>
</div>
</div>
<div className="overflow-y-auto pb1"
style={{height: "calc(100% - 82px)"}}>
<Welcome notebooks={props.notebooks}/>
{sidebarInvites}
{notebookItems}
{groupedItems}
</div>
</div>
);

View File

@ -38,6 +38,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}>
<div className={`h-100 w-100 overflow-x-hidden flex flex-column
bg-white bg-gray0-d dn db-ns`}>
@ -62,6 +63,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}>
<NewScreen
associations={associations.contacts}
@ -87,6 +89,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}>
<JoinScreen
notebooks={state.notebooks}
@ -124,6 +127,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}
path={path}>
<NewPost
@ -147,6 +151,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}
path={path}>
<Notebook
@ -191,6 +196,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}
path={path}>
<EditPost
@ -214,6 +220,7 @@ export class Root extends Component {
spinner={state.spinner}
invites={state.invites}
notebooks={state.notebooks}
associations={associations}
contacts={contacts}
path={path}>
<Note
@ -236,4 +243,4 @@ export class Root extends Component {
}
}
export default Root
export default Root;

View File

@ -30,6 +30,7 @@ export class Skeleton extends Component {
contacts={props.contacts}
path={props.path}
invites={props.invites}
associations={props.associations}
/>
<div className={"h-100 w-100 relative white-d flex-auto " + rightPanelHide} style={{
flexGrow: 1,

View File

@ -90,4 +90,26 @@ export function cite(ship) {
return shortened;
}
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;
}