From afec74cb2ed0ec04871a5e8545231993adb983d4 Mon Sep 17 00:00:00 2001 From: Matilde Park Date: Wed, 1 Apr 2020 17:28:24 -0400 Subject: [PATCH] groups: add componentised spinner and behaviours --- .../contacts/img/{Home.png => Spinner.png} | Bin pkg/interface/groups/src/js/api.js | 10 -- .../src/js/components/lib/add-contact.js | 19 +-- .../src/js/components/lib/contact-card.js | 120 ++++++++++++------ .../src/js/components/lib/contact-sidebar.js | 19 ++- .../src/js/components/lib/group-detail.js | 60 +++++---- .../src/js/components/lib/header-bar.js | 9 -- .../src/js/components/lib/icons/icon-home.js | 16 --- .../js/components/lib/icons/icon-spinner.js | 24 +++- pkg/interface/groups/src/js/components/new.js | 9 +- .../groups/src/js/components/root.js | 7 - .../groups/src/js/components/skeleton.js | 4 +- pkg/interface/groups/src/js/reducers/local.js | 7 - pkg/interface/groups/src/js/store.js | 3 +- 14 files changed, 162 insertions(+), 145 deletions(-) rename pkg/arvo/app/contacts/img/{Home.png => Spinner.png} (100%) delete mode 100644 pkg/interface/groups/src/js/components/lib/icons/icon-home.js diff --git a/pkg/arvo/app/contacts/img/Home.png b/pkg/arvo/app/contacts/img/Spinner.png similarity index 100% rename from pkg/arvo/app/contacts/img/Home.png rename to pkg/arvo/app/contacts/img/Spinner.png diff --git a/pkg/interface/groups/src/js/api.js b/pkg/interface/groups/src/js/api.js index 9c0016fb5..6e56ca9ac 100644 --- a/pkg/interface/groups/src/js/api.js +++ b/pkg/interface/groups/src/js/api.js @@ -181,16 +181,6 @@ class UrbitApi { }) } - setSpinner(boolean) { - store.handleEvent({ - data: { - local: { - spinner: boolean - } - } - }) - } - setSelected(selected) { store.handleEvent({ data: { diff --git a/pkg/interface/groups/src/js/components/lib/add-contact.js b/pkg/interface/groups/src/js/components/lib/add-contact.js index 18f2e5187..8cf0e3ea1 100644 --- a/pkg/interface/groups/src/js/components/lib/add-contact.js +++ b/pkg/interface/groups/src/js/components/lib/add-contact.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import { Route, Link } from 'react-router-dom'; import { InviteSearch } from './invite-search'; +import { Spinner } from './icons/icon-spinner'; export class AddScreen extends Component { @@ -11,7 +12,8 @@ export class AddScreen extends Component { invites: { groups: [], ships: [] - } + }, + awaiting: false }; this.invChange = this.invChange.bind(this); @@ -38,12 +40,12 @@ export class AddScreen extends Component { invites: { groups: [], ships: [] - } + }, + awaiting: true }, () => { - props.api.setSpinner(true); let submit = props.api.group.add(props.path, aud); submit.then(() => { - props.api.setSpinner(false); + this.setState({awaiting: false}); props.history.push("/~groups" + props.path); }) }); @@ -51,14 +53,6 @@ export class AddScreen extends Component { render() { const { props } = this; - let invErrElem = (); - if (this.state.inviteError) { - invErrElem = ( - - Invites must be validly formatted ship names. - - ); - } return (
@@ -86,6 +80,7 @@ export class AddScreen extends Component { +
) diff --git a/pkg/interface/groups/src/js/components/lib/contact-card.js b/pkg/interface/groups/src/js/components/lib/contact-card.js index 9a9531580..d02e5ddb4 100644 --- a/pkg/interface/groups/src/js/components/lib/contact-card.js +++ b/pkg/interface/groups/src/js/components/lib/contact-card.js @@ -4,6 +4,7 @@ import { Sigil } from './icons/sigil'; import { api } from '/api'; import { Route, Link } from 'react-router-dom'; import { EditElement } from '/components/lib/edit-element'; +import { Spinner } from './icons/icon-spinner'; import { uxToHex } from '/lib/util'; export class ContactCard extends Component { @@ -16,7 +17,9 @@ export class ContactCard extends Component { emailToSet: null, phoneToSet: null, websiteToSet: null, - notesToSet: null + notesToSet: null, + awaiting: false, + type: "Saving to group" }; this.editToggle = this.editToggle.bind(this); this.sigilColorSet = this.sigilColorSet.bind(this); @@ -44,16 +47,6 @@ export class ContactCard extends Component { }); return; } - // sigil color updates are done by keystroke parsing on update - // other field edits are exclusively handled by setField() - let currentColor = (props.contact.color) ? props.contact.color : "000000"; - currentColor = uxToHex(currentColor); - let hexExp = /([0-9A-Fa-f]{6})/ - let hexTest = hexExp.exec(this.state.colorToSet); - - if (hexTest && (hexTest[1] !== currentColor) && !props.share) { - api.contactEdit(props.path, `~${props.ship}`, {color: hexTest[1]}); - } } editToggle() { @@ -123,8 +116,11 @@ export class ContactCard extends Component { let hexTest = hexExp.exec(this.state.colorToSet); if (hexTest && (hexTest[1] !== currentColor) && !props.share) { + this.setState({ awaiting: true, type: "Saving to group" }, (() => { api.contactEdit(props.path, `~${props.ship}`, { color: hexTest[1] }).then(() => { + this.setState({ awaiting: false }); }); + })) } break; } @@ -137,7 +133,11 @@ export class ContactCard extends Component { } let emailTestResult = emailTest.exec(state.emailToSet); if (emailTestResult) { - api.contactEdit(props.path, ship, { email: state.emailToSet }); + this.setState({ awaiting: true, type: "Saving to group" }, (() => { + api.contactEdit(props.path, ship, { email: state.emailToSet }).then(() => { + this.setState({awaiting: false}); + }); + })) } break; } @@ -148,7 +148,12 @@ export class ContactCard extends Component { ) { return false; } - api.contactEdit(props.path, ship, { nickname: state.nickNameToSet }); + this.setState({ awaiting: true, type: "Saving to group" }, (() => { + api.contactEdit(props.path, ship, { nickname: state.nickNameToSet }).then(() => { + this.setState({ awaiting: false }); + }); + })) + break; } case "notes": { @@ -158,7 +163,11 @@ export class ContactCard extends Component { ) { return false; } - api.contactEdit(props.path, ship, { notes: state.notesToSet }); + this.setState({ awaiting: true, type: "Saving to group" }, (() => { + api.contactEdit(props.path, ship, { notes: state.notesToSet }).then(() => { + this.setState({ awaiting: false }); + }); + })) break; } case "phone": { @@ -170,7 +179,11 @@ export class ContactCard extends Component { } let phoneTestResult = phoneTest.exec(state.phoneToSet); if (phoneTestResult) { - api.contactEdit(props.path, ship, { phone: state.phoneToSet }); + this.setState({ awaiting: true, type: "Saving to group" }, (() => { + api.contactEdit(props.path, ship, { phone: state.phoneToSet }).then(() => { + this.setState({ awaiting: false }); + }); + })) } break; } @@ -183,37 +196,60 @@ export class ContactCard extends Component { } let websiteTestResult = websiteTest.exec(state.websiteToSet); if (websiteTestResult) { - api.contactEdit(props.path, ship, { website: state.websiteToSet }); + this.setState({ awaiting: true, type: "Saving to group" }, (() => { + api.contactEdit(props.path, ship, { website: state.websiteToSet }).then(() => { + this.setState({ awaiting: false }); + }); + })) } break; } case "removeAvatar": { - api.contactEdit(props.path, ship, { avatar: null }); + this.setState({ awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { avatar: null }).then(() => { + this.setState({ awaiting: false }); + }); + })) break; } case "removeEmail": { - this.setState({ emailToSet: "" }); - api.contactEdit(props.path, ship, { email: "" }); + this.setState({ emailToSet: "", awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { email: "" }).then(() => { + this.setState({awaiting: false}); + }); + })); break; } case "removeNickname": { - this.setState({ nicknameToSet: "" }); - api.contactEdit(props.path, ship, { nickname: "" }); + this.setState({ nicknameToSet: "", awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { nickname: "" }).then(() => { + this.setState({awaiting: false}); + }); + })); break; } case "removePhone": { - this.setState({ phoneToSet: "" }); - api.contactEdit(props.path, ship, { phone: "" }); + this.setState({ phoneToSet: "", awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { phone: "" }).then(() => { + this.setState({awaiting: false}); + }); + })); break; } case "removeWebsite": { - this.setState({ websiteToSet: "" }); - api.contactEdit(props.path, ship, { website: "" }); + this.setState({ websiteToSet: "", awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { website: "" }).then(() => { + this.setState({awaiting: false}); + }); + })); break; } case "removeNotes": { - this.setState({ notesToSet: "" }); - api.contactEdit(props.path, ship, { notes: "" }); + this.setState({ notesToSet: "", awaiting: true, type: "Removing from group" }, (() => { + api.contactEdit(props.path, ship, { notes: "" }).then(() => { + this.setState({awaiting: false}); + }); + })); break; } } @@ -253,13 +289,13 @@ export class ContactCard extends Component { color: this.pickFunction(state.colorToSet, defaultVal.color), avatar: null }; - api.setSpinner(true); - api.contactView.share( - `~${props.ship}`, props.path, `~${window.ship}`, contact - ).then(() => { - api.setSpinner(false); - props.history.push(`/~groups/view${props.path}/${window.ship}`) - }); + this.setState({awaiting: true, type: "Sharing with group"}, (() => { + api.contactView.share( + `~${props.ship}`, props.path, `~${window.ship}`, contact + ).then(() => { + props.history.push(`/~groups/view${props.path}/${window.ship}`) + }); + })) } removeFromGroup() { @@ -280,13 +316,14 @@ export class ContactCard extends Component { `~${props.ship}`, props.path, `~${window.ship}`, contact ); - api.setSpinner(true); - api.contactHook.remove(props.path, `~${props.ship}`).then(() => { - api.setSpinner(false); - let destination = (props.ship === window.ship) - ? "" : props.path; - props.history.push(`/~groups${destination}`); - }); + this.setState({awaiting: true, type: "Removing from group"}, (() => { + api.contactHook.remove(props.path, `~${props.ship}`).then(() => { + let destination = (props.ship === window.ship) + ? "" : props.path; + this.setState({awaiting: false}); + props.history.push(`/~groups${destination}`); + }); + })) } renderEditCard() { @@ -548,6 +585,7 @@ export class ContactCard extends Component {
{card}
+ ); } diff --git a/pkg/interface/groups/src/js/components/lib/contact-sidebar.js b/pkg/interface/groups/src/js/components/lib/contact-sidebar.js index 4823f842c..b5ccd849f 100644 --- a/pkg/interface/groups/src/js/components/lib/contact-sidebar.js +++ b/pkg/interface/groups/src/js/components/lib/contact-sidebar.js @@ -3,9 +3,16 @@ import { Route, Link } from 'react-router-dom'; import { ContactItem } from '/components/lib/contact-item'; import { ShareSheet } from '/components/lib/share-sheet'; import { Sigil } from '../lib/icons/sigil'; +import { Spinner } from '../lib/icons/icon-spinner'; import { cite } from '../../lib/util'; export class ContactSidebar extends Component { + constructor(props) { + super(props); + this.state = { + awaiting: false + } + } render() { const { props } = this; @@ -71,11 +78,12 @@ export class ContactSidebar extends Component {

{ - props.api.setSpinner(true); - props.api.groupRemove(props.path, [`~${member}`]) - .then(() => { - props.api.setSpinner(false); - }) + this.setState({awaiting: true}, (() => { + props.api.groupRemove(props.path, [`~${member}`]) + .then(() => { + this.setState({awaiting: false}) + }) + })) }}> Remove

@@ -107,6 +115,7 @@ export class ContactSidebar extends Component { {contactItems} {groupItems} + ); } diff --git a/pkg/interface/groups/src/js/components/lib/group-detail.js b/pkg/interface/groups/src/js/components/lib/group-detail.js index dae8f24a6..7958afa28 100644 --- a/pkg/interface/groups/src/js/components/lib/group-detail.js +++ b/pkg/interface/groups/src/js/components/lib/group-detail.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import { Route, Link } from 'react-router-dom'; +import { Spinner } from './icons/icon-spinner'; import { deSig, uxToHex } from '/lib/util.js'; export class GroupDetail extends Component { @@ -8,6 +9,8 @@ export class GroupDetail extends Component { this.state = { title: "", description: "", + awaiting: false, + type: "Editing" } this.changeTitle = this.changeTitle.bind(this); this.changeDescription = this.changeDescription.bind(this); @@ -199,17 +202,18 @@ export class GroupDetail extends Component { onChange={this.changeTitle} onBlur={() => { if (groupOwner) { - props.api.setSpinner(true); - props.api.metadataAdd( - association['app-path'], - association['group-path'], - this.state.title, - association.metadata.description, - association.metadata['date-created'], - uxToHex(association.metadata.color) - ).then(() => { - props.api.setSpinner(false); - }) + this.setState({awaiting: true}, (() => { + props.api.metadataAdd( + association['app-path'], + association['group-path'], + this.state.title, + association.metadata.description, + association.metadata['date-created'], + uxToHex(association.metadata.color) + ).then(() => { + this.setState({awaiting: false}) + }) + })) } }} /> @@ -226,17 +230,18 @@ export class GroupDetail extends Component { onChange={this.changeDescription} onBlur={() => { if (groupOwner) { - props.api.setSpinner(true); - props.api.metadataAdd( - association['app-path'], - association['group-path'], - association.metadata.title, - this.state.description, - association.metadata['date-created'], - uxToHex(association.metadata.color) - ).then(() => { - props.api.setSpinner(false); - }) + this.setState({awaiting: true}, (() => { + props.api.metadataAdd( + association['app-path'], + association['group-path'], + association.metadata.title, + this.state.description, + association.metadata['date-created'], + uxToHex(association.metadata.color) + ).then(() => { + this.setState({awaiting: false}) + }) + })) } }} /> @@ -248,14 +253,15 @@ export class GroupDetail extends Component { { if (groupOwner) { - props.api.setSpinner(true); - props.api.contactView.delete(props.path).then(() => { - props.api.setSpinner(false); - props.history.push("/~groups"); - }) + this.setState({awaiting: true, type: "Deleting"}, (() => { + props.api.contactView.delete(props.path).then(() => { + props.history.push("/~groups"); + }) + })) } }}>Delete this group + ) } diff --git a/pkg/interface/groups/src/js/components/lib/header-bar.js b/pkg/interface/groups/src/js/components/lib/header-bar.js index fcae264fb..544a30e37 100644 --- a/pkg/interface/groups/src/js/components/lib/header-bar.js +++ b/pkg/interface/groups/src/js/components/lib/header-bar.js @@ -7,15 +7,6 @@ export class HeaderBar extends Component { let popout = window.location.href.includes("popout/") ? "dn" : "dn db-m db-l db-xl"; - // let spinner = !!this.props.spinner - // ? this.props.spinner : false; - - // let spinnerClasses = ""; - - // if (spinner === true) { - // spinnerClasses = "spin-active"; - // } - let invites = (this.props.invites && this.props.invites.contacts) ? this.props.invites.contacts : {}; diff --git a/pkg/interface/groups/src/js/components/lib/icons/icon-home.js b/pkg/interface/groups/src/js/components/lib/icons/icon-home.js deleted file mode 100644 index b6aa8be8c..000000000 --- a/pkg/interface/groups/src/js/components/lib/icons/icon-home.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from "react"; - -export class IconHome extends Component { - render() { - - let classes = !!this.props.classes ? this.props.classes : ""; - return ( - - ); - } -} \ No newline at end of file diff --git a/pkg/interface/groups/src/js/components/lib/icons/icon-spinner.js b/pkg/interface/groups/src/js/components/lib/icons/icon-spinner.js index ee2730f6a..a933ba9aa 100644 --- a/pkg/interface/groups/src/js/components/lib/icons/icon-spinner.js +++ b/pkg/interface/groups/src/js/components/lib/icons/icon-spinner.js @@ -1,9 +1,25 @@ import React, { Component } from 'react'; -export class IconSpinner extends Component { +export class Spinner extends Component { render() { - return ( -
- ); + + let classes = !!this.props.classes ? this.props.classes : ""; + let text = !!this.props.text ? this.props.text : ""; + let awaiting = !!this.props.awaiting ? this.props.awaiting : false; + + if (awaiting) { + return ( +
+ +

{text}

+
+ ); + } + else { + return null; + } } } diff --git a/pkg/interface/groups/src/js/components/new.js b/pkg/interface/groups/src/js/components/new.js index 06bd980e4..5c0835493 100644 --- a/pkg/interface/groups/src/js/components/new.js +++ b/pkg/interface/groups/src/js/components/new.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import { Route, Link } from 'react-router-dom'; import { InviteSearch } from './lib/invite-search'; +import { Spinner } from './lib/icons/icon-spinner'; import { deSig } from '/lib/util'; import urbitOb from 'urbit-ob'; @@ -19,6 +20,7 @@ export class NewScreen extends Component { }, // color: '', groupNameError: false, + awaiting: false }; this.groupNameChange = this.groupNameChange.bind(this); @@ -64,16 +66,16 @@ export class NewScreen extends Component { this.setState({ error: false, success: true, - invites: '' + invites: '', + awaiting: true }, () => { - props.api.setSpinner(true); props.api.contactView.create( group, aud, this.state.title, this.state.description ).then(() => { - props.api.setSpinner(false); + this.setState({awaiting: false}); props.history.push(`/~groups${group}`); }) }); @@ -147,6 +149,7 @@ export class NewScreen extends Component { + ); diff --git a/pkg/interface/groups/src/js/components/root.js b/pkg/interface/groups/src/js/components/root.js index 5ea98087a..fcfd469df 100644 --- a/pkg/interface/groups/src/js/components/root.js +++ b/pkg/interface/groups/src/js/components/root.js @@ -45,7 +45,6 @@ export class Root extends Component { return ( { return ( - +
{props.children}
diff --git a/pkg/interface/groups/src/js/reducers/local.js b/pkg/interface/groups/src/js/reducers/local.js index 2e615bb41..f83c08fb6 100644 --- a/pkg/interface/groups/src/js/reducers/local.js +++ b/pkg/interface/groups/src/js/reducers/local.js @@ -4,17 +4,10 @@ export class LocalReducer { reduce(json, state) { let data = _.get(json, 'local', false); if (data) { - this.setSpinner(data, state); this.setSelected(data, state); } } - setSpinner(json, state) { - let data = _.has(json, 'spinner', false); - if (data) { - state.spinner = json.spinner; - } - } setSelected(json, state) { let data = _.has(json, 'selected', false); if (data) { diff --git a/pkg/interface/groups/src/js/store.js b/pkg/interface/groups/src/js/store.js index c712b9b82..f1ebad44c 100644 --- a/pkg/interface/groups/src/js/store.js +++ b/pkg/interface/groups/src/js/store.js @@ -15,8 +15,7 @@ class Store { associations: {}, permissions: {}, invites: {}, - selectedGroups: [], - spinner: false + selectedGroups: [] }; this.initialReducer = new InitialReducer();