Merge pull request #2241 from urbit/mp/os1/publish-new-screen

OS1: publish new screen
This commit is contained in:
matildepark 2020-02-06 14:57:03 -05:00 committed by GitHub
commit 5b4cff7f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 880 additions and 111 deletions

View File

@ -173,7 +173,7 @@
=/ uid (sham %publish who book eny.bol)
=/ inv=invite
:* our.bol %publish /notebook/[book] who
'invite for notebook {<who>}/{<book>}'
(crip "invite for notebook {<who>}/{<book>}")
==
=/ act=invite-action [%invite /publish uid inv]
[%pass /invite %agent [who %invite-hook] %poke %invite-action !>(act)]
@ -191,12 +191,13 @@
[%web %publish @ %publish-info ~]
=/ book-name i.t.t.pax
=/ old=old-info .^(old-info %cx (welp our-beak:main pax))
=/ write-pax /~/publish/(scot %p our.bol)/[book-name]/write
=/ read-pax /~/publish/(scot %p our.bol)/[book-name]/read
=/ group-pax /~/publish/(scot %p our.bol)/[book-name]
=/ book=notebook-info
[title.old '' =(%open comments.old) write-pax read-pax]
=/ grp-car=(list card)
(make-groups ~ writers.book ~ subscribers.book %journal)
[title.old '' =(%open comments.old) / /]
=+ ^- [grp-car=(list card) write-pax=path read-pax=path]
(make-groups book-name group-pax ~ %.n %.n)
=. writers.book write-pax
=. subscribers.book read-pax
=/ inv-car (send-invites book-name (~(get ju old-subs) book-name))
:- :(weld car grp-car inv-car)
^- soba:clay
@ -522,12 +523,14 @@
now.bol
~ ~ ~
==
=? writers.new-book =(writers.new-book /)
/~/publish/(scot %p our.bol)/[book-name]/write
=? subscribers.new-book =(writers.new-book /)
/~/publish/(scot %p our.bol)/[book-name]/read
=/ grp-car=(list card)
(make-groups ~ writers.new-book ~ subscribers.new-book %journal)
=/ group-path=path
?: =(writers.new-book /)
/~/publish/(scot %p our.bol)/[book-name]
writers.new-book
=+ ^- [grp-car=(list card) write-pax=path read-pax=path]
(make-groups book-name group-path ~ %.n %.n)
=. writers.new-book write-pax
=. subscribers.new-book read-pax
=+ ^- [read-cards=(list card) notes=(map @tas note)]
(watch-notes /app/publish/notebooks/[book-name])
=. notes.new-book notes
@ -810,6 +813,11 @@
^- card
[%pass / %agent [our.bol %group-store] %poke %group-action !>(act)]
::
++ contact-view-create
|= act=[%create path (set ship)]
^- card
[%pass / %agent [our.bol %contact-view] %poke %contact-view-action !>(act)]
::
++ perm-hook-poke
|= act=permission-hook-action
^- card
@ -853,22 +861,80 @@
(perm-group-hook-poke [%associate write [[write write-type] ~ ~]])
==
::
++ make-groups
|= $: write-grp=(set ship) write-pax=path
read-grp=(set ship) read-pax=path
sec=rw-security
==
++ create-managed-group
|= [pax=path security=rw-security ships=(set ship)]
^- (list card)
;: weld
:~ (group-poke [%bundle write-pax])
(group-poke [%bundle read-pax])
(group-poke [%add write-grp write-pax])
(group-poke [%add read-grp read-pax])
=/ grp
.^((unit group) %gx ;:(weld /=group-store/(scot %da now.bol) pax /noun))
?^ grp
~
?> ?=(^ pax)
?: |(=('~' i.pax) !=(%village security))
[(group-poke [%bundle pax])]~
[(contact-view-create [%create pax ships])]~
::
++ generate-invites
|= [book=@tas invitees=(set ship)]
^- (list card)
%+ turn ~(tap in invitees)
|= who=ship
=/ uid (sham %publish who book eny.bol)
=/ inv=invite
:* our.bol %publish /notebook/[book] who
(crip "invite for notebook {<who>}/{<book>}")
==
(create-security read-pax write-pax sec)
:~ (perm-hook-poke [%add-owned write-pax write-pax])
(perm-hook-poke [%add-owned read-pax read-pax])
=/ act=invite-action [%invite /publish uid inv]
[%pass / %agent [our.bol %invite-hook] %poke %invite-action !>(act)]
::
++ make-groups
|= [book=@tas group=group-info]
^- [(list card) write=path read=path]
?> ?=(^ group-path.group)
?: use-preexisting.group
=/ scry-path
;:(weld /=group-store/(scot %da now.bol) group-path.group /noun)
=/ grp .^((unit ^group) %gx scry-path)
?~ grp !!
?> ?=(^ group-path.group)
?< =('~' i.group-path.group)
:_ [group-path.group group-path.group]
%- zing
:~ (create-security group-path.group group-path.group %channel)
[(perm-hook-poke [%add-owned group-path.group group-path.group])]~
(generate-invites book (~(del in u.grp) our.bol))
==
::
?: make-managed.group
=/ scry-path
;:(weld /=group-store/(scot %da now.bol) group-path.group /noun)
=/ grp .^((unit ^group) %gx scry-path)
?^ grp [~ group-path.group group-path.group]
?> ?=(^ group-path.group)
?< =('~' i.group-path.group)
:_ [group-path.group group-path.group]
%- zing
:~ [(contact-view-create [%create group-path.group invitees.group])]~
(create-security group-path.group group-path.group %channel)
[(perm-hook-poke [%add-owned group-path.group group-path.group])]~
(generate-invites book (~(del in invitees.group) our.bol))
==
:: make unmanaged group
?> ?=(^ group-path.group)
?> =('~' i.group-path.group)
=* write-path group-path.group
=/ read-path (weld write-path /read)
=/ scry-path=path
;:(weld /=group-store/(scot %da now.bol) group-path.group /noun)
=/ grp .^((unit ^group) %gx scry-path)
?^ grp [~ write-path read-path]
:_ [write-path read-path]
%- zing
:~ [(group-poke [%bundle write-path])]~
[(group-poke [%bundle read-path])]~
(create-security read-path write-path %journal)
[(perm-hook-poke [%add-owned write-path write-path])]~
[(perm-hook-poke [%add-owned read-path read-path])]~
(generate-invites book (~(del in invitees.group) our.bol))
==
::
++ poke-publish-action
@ -881,14 +947,7 @@
?: (~(has by books) book.act)
~|("notebook already exists: {<book.act>}" !!)
=+ ^- [cards=(list card) write-pax=path read-pax=path]
?. ?=(%new -.group.act)
[~ write-pax.group.act read-pax.group.act]
:_ [write-pax.group.act read-pax.group.act]
%- make-groups
:* write-grp.group.act write-pax.group.act
read-grp.group.act read-pax.group.act
sec.group.act
==
(make-groups book.act group.act)
=/ new-book=notebook-info
:* title.act
about.act
@ -959,14 +1018,7 @@
=+ ^- [cards=(list card) write-pax=path read-pax=path]
?~ group.act
[~ writers.u.book subscribers.u.book]
?. ?=(%new -.u.group.act)
[~ write-pax.u.group.act read-pax.u.group.act]
:_ [write-pax.u.group.act read-pax.u.group.act]
%- make-groups
:* write-grp.u.group.act write-pax.u.group.act
read-grp.u.group.act read-pax.u.group.act
sec.u.group.act
==
(make-groups book.act u.group.act)
=/ new-info=notebook-info
:* title.act
about.act

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -115,16 +115,13 @@
note+so
==
++ group-info
%- of
:~ old+(ot write-pax+pa read-pax+pa ~)
:- %new
%- ot
:~ write-grp+set-ship write-pax+pa
read-grp+set-ship read-pax+pa
sec+so
==
%- ot
:~ group-path+pa
invitees+set-ship
use-preexisting+bo
make-managed+bo
==
++ set-ship (ar (su fed:ag))
++ set-ship (as (su fed:ag))
--
--
--

View File

@ -2,15 +2,12 @@
|%
::
+$ group-info
$% [%old write-pax=path read-pax=path]
$: %new
write-grp=(set ship) write-pax=path
read-grp=(set ship) read-pax=path
sec=rw-security
==
$: group-path=path
invitees=(set ship)
use-preexisting=?
make-managed=?
==
::
::
+$ action
$% [%new-book book=@tas title=@t about=@t coms=? group=group-info]
[%new-note who=@p book=@tas note=@tas title=@t body=@t]

View File

@ -80,16 +80,20 @@ a {
background: rgba(42, 167, 121, 0.1);
}
.focus-b--black:focus {
border-color: #000;
}
.mix-blend-diff {
mix-blend-mode: difference;
}
.NotebookButton {
padding: 8px 12px;
border-radius:2px;
cursor: pointer;
}
.NotebookTab {
padding: 16px 8px;
}
.NewPost {
width: 100%;
height: calc(100% - 103px);
@ -97,6 +101,33 @@ a {
padding-top: 8px;
}
.placeholder-inter::placeholder {
font-family: "Inter", sans-serif;
}
/* toggler checkbox */
.toggle::after {
content: "";
height: 12px;
width: 12px;
background: white;
position: absolute;
top: 2px;
left: 2px;
border-radius: 100%;
}
.toggle.checked::after {
content: "";
height: 12px;
width: 12px;
background: white;
position: absolute;
top: 2px;
left: 14px;
border-radius: 100%;
}
.react-codemirror2 {
width: 100%;
}
@ -109,9 +140,10 @@ a {
cursor: text;
font-size: 12px;
line-height: 20px;
* {
}
.CodeMirror * {
font-family: 'Source Code Pro';
}
}
.CodeMirror-selected { background:#BAE3FE !important; }
@ -175,6 +207,7 @@ a {
top: 16px;
color: #7f7f7f;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;

View File

@ -0,0 +1,299 @@
import React, { Component } from 'react';
import urbitOb from "urbit-ob";
import { Sigil } from "../lib/icons/sigil";
export class InviteSearch extends Component {
constructor(props) {
super(props);
this.state = {
groups: [],
peers: [],
searchValue: "",
searchResults: {
groups: [],
ships: []
},
inviteError: false
}
this.search = this.search.bind(this);
}
componentDidMount() {
this.peerUpdate();
}
componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
this.peerUpdate();
}
}
peerUpdate() {
let groups = Array.from(Object.keys(this.props.groups));
groups = groups.filter(e => !e.startsWith("/~/"));
let peers = [],
peerSet = new Set();
Object.keys(this.props.groups).map(group => {
if (this.props.groups[group].size > 0) {
peerSet.add(...this.props.groups[group]);
}
});
peers = Array.from(peerSet);
this.setState({ groups: groups, peers: peers });
}
search(event) {
let searchTerm = event.target.value.toLowerCase().replace("~", "");
this.setState({searchValue: event.target.value});
if (searchTerm.length < 2) {
this.setState({searchResults: { groups: [], ships: [] }})
}
if (searchTerm.length > 2) {
if (this.state.inviteError === true) {
this.setState({inviteError: false});
}
let groupMatches = this.state.groups.filter(e => {
return e.includes(searchTerm);
});
let shipMatches = this.state.peers.filter(e => {
return e.includes(searchTerm) && !this.props.invites.ships.includes(e);
});
this.setState({
searchResults: { groups: groupMatches, ships: shipMatches }
});
}
}
deleteGroup() {
let { ships } = this.props.invites;
this.setState(
{
searchValue: "",
searchResults: { groups: [], ships: [] }
}
);
this.props.setInvite({ groups: [], ships: ships });
}
deleteShip(ship) {
let { groups, ships } = this.props.invites;
this.setState(
{
searchValue: "",
searchResults: { groups: [], ships: [] }
}
);
ships = ships.filter(e => {
return e !== ship;
});
this.props.setInvite({ groups: groups, ships: ships });
}
addGroup(group) {
this.setState(
{
searchValue: "",
searchResults: { groups: [], ships: [] }
}
);
this.props.setInvite({ groups: [group], ships: [] });
}
addShip(ship) {
let { groups, ships } = this.props.invites;
this.setState(
{
searchValue: "",
searchResults: { groups: [], ships: [] }
}
);
ships.push(ship);
if (groups.length > 0) {
return false;
}
this.props.setInvite({ groups: groups, ships: ships });
}
submitShipToAdd(ship) {
let searchTerm = ship.toLowerCase().replace("~", "").trim();
let isValid = true;
if (!urbitOb.isValidPatp("~" + searchTerm)) {
isValid = false;
}
if (!isValid) {
this.setState({ inviteError: true, searchValue: "" });
} else if (isValid) {
this.addShip(searchTerm);
this.setState({ searchValue: "" });
}
}
render() {
const { props, state } = this;
let searchDisabled = false;
if (props.invites.groups) {
if (props.invites.groups.length > 0) {
searchDisabled = true;
}
}
let participants = <div/>
let searchResults = <div/>
let invErrElem = <span />;
if (state.inviteError) {
invErrElem = (
<span className="f9 inter red2 db pt2">
Invited ships must be validly formatted ship names.
</span>
);
}
if ((state.searchResults.groups.length > 0)
|| (state.searchResults.ships.length > 0)) {
let groupHeader = (state.searchResults.groups.length > 0)
? <p className="f9 gray2 ph3">Groups</p> : "";
let shipHeader = (state.searchResults.ships.length > 0)
? <p className="f9 gray2 pv2 ph3">Ships</p> : "";
let groupResults = state.searchResults.groups.map(group => {
return (
<li
key={group}
className={
"list mono white-d f8 pv2 ph3 pointer" +
" hover-bg-gray4 hover-black-d"}
onClick={e => this.addGroup(group)}>
{group}
</li>
);
})
let shipResults = state.searchResults.ships.map(ship => {
return (
<li
key={ship}
className={
"list mono white-d f8 pv1 ph3 pointer" +
" hover-bg-gray4 hover-black-d"
}
onClick={e => this.addShip(ship)}>
<Sigil
ship={"~" + ship}
size={24}
color="#000000"
classes="mix-blend-diff v-mid"
/>
<span className="v-mid ml2">{"~" + ship}</span>
</li>
);
})
searchResults =
<div className={"absolute bg-white bg-gray0-d white-d" +
" pv3 z-1 w-100 mt1 ba b--white-d overflow-y-scroll mh-16"}>
{groupHeader}
{groupResults}
{shipHeader}
{shipResults}
</div>
}
let groupInvites = props.invites.groups || [];
let shipInvites = props.invites.ships || [];
if (groupInvites.length > 0 || shipInvites.length > 0) {
let groups = groupInvites.map(group => {
return (
<span
key={group}
className={
"f9 mono black pa2 bg-gray5 bg-gray1-d" +
" ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default"}>
{group}
<span className="white-d ml3 mono pointer"
onClick={e => this.deleteGroup(group)}>
x
</span>
</span>
);
});
let ships = shipInvites.map(ship => {
return (
<span
key={ship}
className={"f9 mono black pa2 bg-gray5 bg-gray1-d" +
" ba b--gray4 b--gray2-d white-d dib mr2 mt2 c-default"}>
{"~" + ship}
<span className="white-d ml3 mono pointer"
onClick={e => this.deleteShip(ship)}>x</span>
</span>
);
});
participants = (
<div
className={
"f9 gray2 bb bl br b--gray3 b--gray2-d bg-gray0-d " +
"white-d pa3 db w-100 inter"
}>
<span className="db gray2">Participants</span>
{groups} {ships}
</div>
);
}
return (
<div className="relative">
<img
src="/~publish/search.png"
className="absolute invert-d"
style={{
height: 16,
width: 16,
top: 14,
left: 12
}}
/>
<textarea
ref={e => {
this.textarea = e;
}}
className={"f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 w-100"
+ " db focus-b--black focus-b--white-d mono placeholder-inter"}
placeholder="Search for ships or existing groups"
disabled={searchDisabled}
rows={1}
spellCheck={false}
style={{
resize: "none",
paddingLeft: 36
}}
onKeyPress={e => {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
this.submitShipToAdd(this.state.searchValue);
}}}
onChange={this.search}
value={state.searchValue}
/>
{searchResults}
{participants}
{invErrElem}
</div>
);
}
}
export default InviteSearch;

View File

@ -64,8 +64,6 @@ export class JoinScreen extends Component {
}
}
console.log('actionData', actionData);
// TODO: askHistory setting
window.api.action("publish","publish-action", actionData);

View File

@ -1,16 +1,191 @@
import React, { Component } from 'react'
import React, { Component } from 'react';
import { InviteSearch } from './invite-search';
import { Route, Link } from 'react-router-dom';
import { uuid, isPatTa, deSig, stringToSymbol } from "/lib/util";
import urbitOb from 'urbit-ob';
//TODO textarea fields for title/description
//TODO add component for ship / group search using props.groups
// (integrate props.contacts as well once contact-view is bound)
export class NewScreen extends Component {
render() {
return (
<div>
</div>
)
}
}
constructor(props) {
super(props);
export default NewScreen
this.state = {
idName: '',
description: '',
invites: {
groups: [],
ships: []
},
createGroup: false
};
this.idChange = this.idChange.bind(this);
this.descriptionChange = this.descriptionChange.bind(this);
this.setInvite = this.setInvite.bind(this);
this.createGroupChange = this.createGroupChange.bind(this);
}
componentDidUpdate(prevProps) {
const { props, state } = this;
if (props.notebooks && (("~" + window.ship) in props.notebooks)) {
let notebookId = stringToSymbol(state.idName)
if (notebookId in props.notebooks["~" + window.ship]) {
let notebook = `/~${window.ship}/${notebookId}`;
props.history.push("/~publish/notebook" + notebook);
}
}
}
idChange(event) {
this.setState({
idName: event.target.value
});
}
descriptionChange(event) {
this.setState({
description: event.target.value
});
}
createGroupChange(event) {
this.setState({createGroup: !!event.target.checked});
}
setInvite(value) {
this.setState({invites: value})
}
onClickCreate() {
const { props, state } = this;
let bookId = stringToSymbol(state.idName);
let groupInfo = (state.invites.groups.length > 0)
? {
"group-path": state.invites.groups[0],
"invitees": [],
"use-preexisting": true,
"make-managed": false,
}
: {
// TODO remove /~ and set make-managed on toggle
"group-path": `/~/publish/~${window.ship}/${bookId}`,
"invitees": state.invites.ships,
"use-preexisting": false,
"make-managed": false,
};
let action = {
"new-book": {
book: bookId,
title: state.idName,
about: state.description,
coms: true,
group: groupInfo
}
}
props.api.action("publish", "publish-action", action);
}
render() {
// let createGroupClasses = this.state.createGroup
// ? "relative checked bg-green2 br3 h1 toggle v-mid z-0"
// : "relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0";
let createClasses = "pointer db f9 green2 bg-gray0-d ba pv3 ph4 mv7 b--green2";
if (!this.state.idName) {
createClasses = "pointer db f9 gray2 ba bg-gray0-d pa2 pv3 ph4 mv7 b--gray3";
}
// let createGroupToggle = <div/>
// if ((this.state.invites.ships.length > 0) && (this.state.invites.groups.length === 0)) {
// createGroupToggle = (
// <div className="mv7">
// <input
// type="checkbox"
// style={{ WebkitAppearance: "none", width: 28 }}
// className={createGroupClasses}
// onChange={this.createGroupChange}
// />
// <span className="dib f9 white-d inter ml3">Create Group</span>
// <p className="f9 gray2 pt1" style={{ paddingLeft: 40 }}>
// Participants will share this group across applications
// </p>
// </div>
// );
// }
let idErrElem = <span />;
if (this.state.idError) {
idErrElem = (
<span className="f9 inter red2 db pt2">
Notebook must have a valid name.
</span>
);
}
return (
<div
className={
"h-100 w-100 mw6 pa3 pt4 overflow-x-hidden flex flex-column"
}>
<div className="w-100 dn-m dn-l dn-xl inter pt1 pb6 f8">
<Link to="/~publish/">{"⟵ All Notebooks"}</Link>
</div>
<h2 className="mb3 f8">New Notebook</h2>
<div className="w-100">
<p className="f8 mt3 lh-copy db">Name</p>
<p className="f9 gray2 db mb2 pt1">
Provide a name for your notebook
</p>
<textarea
className="f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100"
placeholder="eg. My Journal"
rows={1}
style={{
resize: "none"
}}
onChange={this.idChange}
value={this.state.idName}
/>
{idErrElem}
<p className="f8 mt4 lh-copy db">
Description
<span className="gray3 ml1">(Optional)</span>
</p>
<p className="f9 gray2 db mb2 pt1">What's your notebook about?</p>
<textarea
className="f7 ba b--gray3 b--gray2-d bg-gray0-d white-d pa3 db w-100"
placeholder="Notebook description"
rows={1}
style={{
resize: "none"
}}
onChange={this.descriptionChange}
value={this.state.description}
/>
<p className="f8 mt4 lh-copy db">
Invite
<span className="gray3 ml1">
(Optional)
</span>
</p>
<p className="f9 gray2 db mb2 pt1">Select an initial read-only audience for your notebook</p>
<InviteSearch
groups={this.props.groups}
invites={this.state.invites}
setInvite={this.setInvite}
/>
{/* {createGroupToggle} */}
<button
onClick={this.onClickCreate.bind(this)}
className={createClasses}>
Create Notebook
</button>
</div>
</div>
);
}
}
export default NewScreen;

View File

@ -21,7 +21,7 @@ export class Notebook extends Component {
let scrollTop = this.scrollElement.scrollTop;
let clientHeight = this.scrollElement.clientHeight;
let scrollHeight = this.scrollElement.scrollHeight;
let atBottom = false;
if (scrollHeight - scrollTop - clientHeight < 40) {
atBottom = true;
@ -60,12 +60,12 @@ export class Notebook extends Component {
render() {
let notebook = this.props.notebooks[this.props.ship][this.props.book];
let tabStyles = {
posts: "bb b--gray4 gray2 NotebookTab",
about: "bb b--gray4 gray2 NotebookTab",
// subscribers: "bb b--gray4 gray2 NotebookTab",
// settings: "bb b--gray4 pr2 gray2 NotebookTab",
}
tabStyles[this.props.view] = "bb b--black black NotebookTab";
posts: "bb b--gray4 gray2 pv4 ph2",
about: "bb b--gray4 gray2 pv4 ph2"
// subscribers: "bb b--gray4 gray2 pv4 ph2",
// settings: "bb b--gray4 pr2 gray2 pv4 ph2",
};
tabStyles[this.props.view] = "bb b--black black pv4 ph2";
let inner = null;
switch (this.props.view) {
@ -111,19 +111,23 @@ export class Notebook extends Component {
</button>
return (
<div className="center mw6 f9 h-100"
style={{paddingLeft:16, paddingRight:16}}>
<div className="h-100 overflow-container no-scrollbar"
onScroll={this.onScroll}
ref={(el) => {this.scrollElement = el}}>
<div className="flex justify-between"
style={{marginTop: 56, marginBottom: 32}}>
<div
className="center mw6 f9 h-100"
style={{ paddingLeft: 16, paddingRight: 16 }}>
<div
className="h-100 overflow-container no-scrollbar"
onScroll={this.onScroll}
ref={el => {
this.scrollElement = el;
}}>
<div
className="flex justify-between"
style={{ marginTop: 56, marginBottom: 32 }}>
<div className="flex-col">
<div className="mb1">{notebook.title}</div>
<span><span className="gray3 mr1">by</span>
<span className="mono">
{this.props.ship}
</span>
<span>
<span className="gray3 mr1">by</span>
<span className="mono">{this.props.ship}</span>
</span>
</div>
<div className="flex">
@ -132,23 +136,24 @@ export class Notebook extends Component {
</div>
</div>
<div className="flex" style={{marginBottom:24}}>
<div className="flex" style={{ marginBottom: 24 }}>
<Link to={base} className={tabStyles.posts}>
All Posts
</Link>
<Link to={about} className={tabStyles.about}>
About
</Link>
<div className="bb b--gray4 gray2 NotebookTab" style={{flexGrow:1}}>
</div>
<div
className="bb b--gray4 gray2 pv4 ph2"
style={{ flexGrow: 1 }}></div>
</div>
<div style={{height:"calc(100% - 188px)"}} className="f9 lh-solid">
<div style={{ height: "calc(100% - 188px)" }} className="f9 lh-solid">
{inner}
</div>
</div>
</div>
)
);
}
}

View File

@ -1,4 +0,0 @@
{
key:{}
}

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import { BrowserRouter, Route, Link } from "react-router-dom";
import { store } from '/store';
import { api } from '/api';
import { Skeleton } from '/components/skeleton';
import { NewScreen } from '/components/lib/new';
import { JoinScreen } from '/components/lib/join';
@ -8,6 +9,7 @@ import { Notebook } from '/components/lib/notebook';
import { Note } from '/components/lib/note';
import { NewPost } from '/components/lib/new-post';
export class Root extends Component {
constructor(props) {
super(props);
@ -52,7 +54,11 @@ export class Root extends Component {
sidebarShown={true}
notebooks={state.notebooks}>
<NewScreen
groups={state.groups}/>
notebooks={state.notebooks}
groups={state.groups}
api={api}
{...props}
/>
</Skeleton>
)
}}/>

View File

@ -0,0 +1,68 @@
import _ from "lodash";
export class GroupReducer {
reduce(json, state) {
let data = _.get(json, "group-initial", false);
if (data) {
for (let group in data) {
state.groups[group] = new Set(data[group]);
}
}
data = _.get(json, "group-update", false);
if (data) {
this.add(data, state);
this.remove(data, state);
this.bundle(data, state);
this.unbundle(data, state);
this.keys(data, state);
this.path(data, state);
}
}
add(json, state) {
let data = _.get(json, "add", false);
if (data) {
for (let member of data.members) {
state.groups[data.path].add(member);
}
}
}
remove(json, state) {
let data = _.get(json, "remove", false);
if (data) {
for (let member of data.members) {
state.groups[data.path].delete(member);
}
}
}
bundle(json, state) {
let data = _.get(json, "bundle", false);
if (data) {
state.groups[data.path] = new Set();
}
}
unbundle(json, state) {
let data = _.get(json, "unbundle", false);
if (data) {
delete state.groups[data.path];
}
}
keys(json, state) {
let data = _.get(json, "keys", false);
if (data) {
state.groupKeys = new Set(data.keys);
}
}
path(json, state) {
let data = _.get(json, "path", false);
if (data) {
state.groups[data.path] = new Set([data.members]);
}
}
}

View File

@ -0,0 +1,58 @@
import _ from 'lodash';
export class InviteReducer {
reduce(json, state) {
let initial = _.get(json, 'invite-initial', false);
if (initial) {
this.initial(initial, state);
}
let update = _.get(json, 'invite-update', false);
if (update) {
this.create(update, state);
this.delete(update, state);
this.invite(update, state);
this.accepted(update, state);
this.decline(update, state);
}
}
initial(json, state) {
state.invites = json;;
}
create(json, state) {
let data = _.get(json, 'create', false);
if (data) {
state.invites[data.path] = {};
}
}
delete(json, state) {
let data = _.get(json, 'delete', false);
if (data) {
delete state.invites[data.path];
}
}
invite(json, state) {
let data = _.get(json, 'invite', false);
if (data) {
state.invites[data.path][data.uid] = data.invite;
}
}
accepted(json, state) {
let data = _.get(json, 'accepted', false);
if (data) {
delete state.invites[data.path][data.uid];
}
}
decline(json, state) {
let data = _.get(json, 'decline', false);
if (data) {
delete state.invites[data.path][data.uid];
}
}
}

View File

@ -3,6 +3,7 @@ import _ from 'lodash';
export class PrimaryReducer {
reduce(json, state){
switch(Object.keys(json)[0]){
//publish actions
case "add-book":
this.addBook(json["add-book"], state);
break;
@ -33,6 +34,12 @@ export class PrimaryReducer {
case "read":
this.read(json["read"], state);
break;
// contacts actions
case "contact-initial":
this.contactInitial(json["contact-initial"], state);
break;
case "contact-update":
this.contactUpdate(json["contact-update"], state);
default:
break;
}
@ -58,7 +65,7 @@ export class PrimaryReducer {
} else {
state.notebooks[host][book]["notes-by-date"] = [noteId];
}
if (state.notebooks[host][book].notes) {
state.notebooks[host][book].notes[noteId] = json[host][book];
} else {
@ -225,4 +232,64 @@ export class PrimaryReducer {
state.notebooks[host][book].notes[noteId]["read"] = true;
}
}
contactInitial(json, state) {
state.contacts = json;
}
contactUpdate(json, state) {
this.createContact(json, state);
this.deleteContact(json, state);
this.addContact(json, state);
this.removeContact(json, state);
this.editContact(json, state);
}
createContact(json, state) {
let data = _.get(json, "create", false);
if (data) {
state.contacts[data.path] = {};
}
}
deleteContact(json, state) {
let data = _.get(json, "delete", false);
if (data) {
delete state.contacts[data.path];
}
}
addContact(json, state) {
let data = _.get(json, "add", false);
if (data && data.path in state.contacts) {
state.contacts[data.path][data.ship] = data.contact;
}
}
removeContact(json, state) {
let data = _.get(json, "remove", false);
if (
data &&
data.path in state.contacts &&
data.ship in state.contacts[data.path]
) {
delete state.contacts[data.path][data.ship];
}
}
editContact(json, state) {
let data = _.get(json, "edit", false);
if (
data &&
data.path in state.contacts &&
data.ship in state.contacts[data.path]
) {
let edit = Object.keys(data["edit-field"]);
if (edit.length !== 1) {
return;
}
state.contacts[data.path][data.ship][edit[0]] =
data["edit-field"][edit[0]];
}
}
}

View File

@ -1,12 +1,15 @@
import { InitialReducer } from '/reducers/initial';
import { PrimaryReducer } from '/reducers/primary';
import { ResponseReducer } from '/reducers/response';
import { GroupReducer } from '/reducers/group';
import { InviteReducer } from '/reducers/invite';
class Store {
constructor() {
this.state = {
notebooks: {},
groups: {},
contacts: {},
permissions: {},
invites: {},
spinner: false,
@ -16,6 +19,8 @@ class Store {
this.initialReducer = new InitialReducer();
this.primaryReducer = new PrimaryReducer();
this.responseReducer = new ResponseReducer();
this.groupReducer = new GroupReducer();
this.inviteReducer = new InviteReducer();
this.setState = () => {};
this.initialReducer.reduce(window.injectedState, this.state);
@ -26,8 +31,12 @@ class Store {
}
handleEvent(evt) {
if (evt.from && evt.from.path === '/primary'){
if (evt.from && evt.from.path === '/all') {
this.groupReducer.reduce(evt.data, this.state);
}
else if (evt.from && evt.from.path === '/primary'){
this.primaryReducer.reduce(evt.data, this.state);
this.inviteReducer.reduce(evt.data, this.state);
} else if (evt.type) {
this.responseReducer.reduce(evt, this.state);
}

View File

@ -15,6 +15,15 @@ export class Subscription {
api.bind(`/primary`, "PUT", api.authTokens.ship, 'publish',
this.handleEvent.bind(this),
this.handleError.bind(this));
api.bind('/all', 'PUT', api.authTokens.ship, 'group-store',
this.handleEvent.bind(this),
this.handleError.bind(this));
api.bind('/primary', 'PUT', api.authTokens.ship, 'contact-view',
this.handleEvent.bind(this),
this.handleError.bind(this));
api.bind('/primary', 'PUT', api.authTokens.ship, 'invite-view',
this.handleEvent.bind(this),
this.handleError.bind(this));
}
handleEvent(diff) {