Merge pull request #2648 from urbit/mp/integrated-spinners
os1: reimplement spinner and behaviours as part of app layouts
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
@ -246,16 +246,6 @@ class UrbitApi {
|
||||
});
|
||||
}
|
||||
|
||||
setSpinner(boolean) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
spinner: boolean
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
|
@ -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
|
||||
: {};
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
return (
|
||||
<img
|
||||
className={"invert-d " + classes}
|
||||
src="/~chat/img/Home.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
|
||||
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 (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~chat/img/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid inter">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from './invite-search';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
|
||||
export class InviteElement extends Component {
|
||||
@ -9,7 +10,8 @@ export class InviteElement extends Component {
|
||||
this.state = {
|
||||
members: [],
|
||||
error: false,
|
||||
success: false
|
||||
success: false,
|
||||
awaiting: false
|
||||
};
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
}
|
||||
@ -27,15 +29,15 @@ export class InviteElement extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
props.api.setSpinner(true);
|
||||
|
||||
this.setState({
|
||||
error: false,
|
||||
success: true,
|
||||
members: []
|
||||
members: [],
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.groups.add(aud, props.path).then(() => {
|
||||
props.api.setSpinner(false);
|
||||
this.setState({awaiting: false});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -77,6 +79,7 @@ export class InviteElement extends Component {
|
||||
className={modifyButtonClasses}>
|
||||
{buttonText}
|
||||
</button>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4" text="Inviting to chat..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { InviteSearch } from './lib/invite-search';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { uuid, isPatTa, deSig } from '/lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
@ -19,7 +20,8 @@ export class NewScreen extends Component {
|
||||
idError: false,
|
||||
inviteError: false,
|
||||
allowHistory: true,
|
||||
createGroup: false
|
||||
createGroup: false,
|
||||
awaiting: false
|
||||
};
|
||||
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
@ -138,9 +140,9 @@ export class NewScreen extends Component {
|
||||
error: false,
|
||||
success: true,
|
||||
group: [],
|
||||
ships: []
|
||||
ships: [],
|
||||
awaiting: true
|
||||
}, () => {
|
||||
props.api.setSpinner(true);
|
||||
// if we want a "proper group" that can be managed from the contacts UI,
|
||||
// we make a path of the form /~zod/cool-group
|
||||
// if not, we make a path of the form /~/~zod/free-chat
|
||||
@ -162,7 +164,7 @@ export class NewScreen extends Component {
|
||||
state.allowHistory
|
||||
);
|
||||
submit.then(() => {
|
||||
props.api.setSpinner(false);
|
||||
this.setState({awaiting: false});
|
||||
props.history.push(`/~chat/room${appPath}`);
|
||||
})
|
||||
});
|
||||
@ -293,6 +295,7 @@ export class NewScreen extends Component {
|
||||
className={createClasses}>
|
||||
Start Chat
|
||||
</button>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4" text="Creating chat..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -23,6 +23,11 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//preload spinner asset
|
||||
new Image().src = "/~chat/img/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -97,7 +102,6 @@ export class Root extends Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
spinner={state.spinner}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
>
|
||||
@ -128,7 +132,6 @@ export class Root extends Component {
|
||||
<Skeleton
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
spinner={state.spinner}
|
||||
sidebarHideOnMobile={true}
|
||||
sidebar={renderChannelSidebar(props)}
|
||||
sidebarShown={state.sidebarShown}
|
||||
@ -186,7 +189,6 @@ export class Root extends Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
spinner={state.spinner}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
@ -237,7 +239,6 @@ export class Root extends Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
spinner={state.spinner}
|
||||
sidebarShown={state.sidebarShown}
|
||||
popout={popout}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
@ -283,7 +284,6 @@ export class Root extends Component {
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
sidebarHideOnMobile={true}
|
||||
spinner={state.spinner}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
sidebar={renderChannelSidebar(props, station)}
|
||||
|
@ -3,7 +3,7 @@ import classnames from 'classnames';
|
||||
import { deSig, uxToHex, writeText } from '/lib/util';
|
||||
import { Route, Link } from "react-router-dom";
|
||||
|
||||
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { ChatTabBar } from '/components/lib/chat-tabbar';
|
||||
import { InviteSearch } from '/components/lib/invite-search';
|
||||
import SidebarSwitcher from './lib/icons/icon-sidebar-switch';
|
||||
@ -20,7 +20,9 @@ export class SettingsScreen extends Component {
|
||||
color: "",
|
||||
// groupify settings
|
||||
targetGroup: null,
|
||||
inclusive: false
|
||||
inclusive: false,
|
||||
awaiting: false,
|
||||
type: "Editing chat..."
|
||||
};
|
||||
|
||||
this.renderDelete = this.renderDelete.bind(this);
|
||||
@ -49,7 +51,6 @@ export class SettingsScreen extends Component {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
}, () => {
|
||||
props.api.setSpinner(false);
|
||||
props.history.push('/~chat');
|
||||
});
|
||||
}
|
||||
@ -108,17 +109,19 @@ export class SettingsScreen extends Component {
|
||||
? props.association : {};
|
||||
|
||||
if (chatOwner) {
|
||||
props.api.setSpinner(true);
|
||||
props.api.metadataAdd(
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
color
|
||||
).then(() => {
|
||||
props.api.setSpinner(false);
|
||||
})
|
||||
this.setState({awaiting: true, type: "Editing chat..."}, (() => {
|
||||
props.api.metadataAdd(
|
||||
association['app-path'],
|
||||
association['group-path'],
|
||||
association.metadata.title,
|
||||
association.metadata.description,
|
||||
association.metadata['date-created'],
|
||||
color
|
||||
).then(() => {
|
||||
this.setState({awaiting: false});
|
||||
})
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,29 +129,29 @@ export class SettingsScreen extends Component {
|
||||
deleteChat() {
|
||||
const { props, state } = this;
|
||||
|
||||
props.api.chatView.delete(props.station);
|
||||
props.api.setSpinner(true);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
loadingText: (deSig(props.match.params.ship) === window.ship)
|
||||
? 'Deleting...'
|
||||
: 'Leaving...'
|
||||
});
|
||||
awaiting: true,
|
||||
type: (deSig(props.match.params.ship) === window.ship)
|
||||
? 'Deleting chat...'
|
||||
: 'Leaving chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.delete(props.station);
|
||||
}));
|
||||
}
|
||||
|
||||
groupifyChat() {
|
||||
const { props, state } = this;
|
||||
|
||||
props.api.chatView.groupify(
|
||||
props.station, state.targetGroup, state.inclusive
|
||||
);
|
||||
props.api.setSpinner(true);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
loadingText: 'Converting...'
|
||||
});
|
||||
awaiting: true,
|
||||
type: 'Converting chat...'
|
||||
}, (() => {
|
||||
props.api.chatView.groupify(
|
||||
props.station, state.targetGroup, state.inclusive
|
||||
).then(() => this.setState({awaiting: false}));
|
||||
}));
|
||||
}
|
||||
|
||||
renderDelete() {
|
||||
@ -273,17 +276,18 @@ export class SettingsScreen extends Component {
|
||||
onChange={this.changeTitle}
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
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, type: "Editing chat..."}, (() => {
|
||||
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});
|
||||
})
|
||||
}))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -300,17 +304,18 @@ export class SettingsScreen extends Component {
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
if (chatOwner) {
|
||||
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, type: "Editing chat..."}, (() => {
|
||||
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});
|
||||
})
|
||||
}))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@ -348,7 +353,6 @@ export class SettingsScreen extends Component {
|
||||
let permission = Array.from(props.permission.who.values());
|
||||
|
||||
if (!!state.isLoading) {
|
||||
let text = state.loadingText || 'Working...';
|
||||
|
||||
let title = props.station.substr(1);
|
||||
|
||||
@ -389,7 +393,7 @@ export class SettingsScreen extends Component {
|
||||
/>
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<h2 className="f8 pb2">{text}</h2>
|
||||
<Spinner awaiting={this.state.awaiting} classes="absolute right-2 bottom-2 ba pa2 b--gray1-d" text={this.state.type} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -459,6 +463,7 @@ export class SettingsScreen extends Component {
|
||||
{this.renderGroupify()}
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
<Spinner awaiting={this.state.awaiting} classes="absolute right-2 bottom-2 ba pa2 b--gray1-d" text={this.state.type}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -32,7 +32,7 @@ export class Skeleton extends Component {
|
||||
return (
|
||||
// app outer skeleton
|
||||
<div className={"absolute h-100 w-100 bg-gray0-d " + popoutWindow}>
|
||||
<HeaderBar spinner={this.props.spinner} associations={this.props.associations} invites={this.props.invites} />
|
||||
<HeaderBar associations={this.props.associations} invites={this.props.invites} />
|
||||
{/* app window borders */}
|
||||
<div className={
|
||||
`cf w-100 flex ` +
|
||||
|
@ -5,7 +5,6 @@ export class LocalReducer {
|
||||
let data = _.get(json, 'local', false);
|
||||
if (data) {
|
||||
this.sidebarToggle(data, state);
|
||||
this.setSpinner(data, state);
|
||||
this.setSelected(data, state);
|
||||
}
|
||||
}
|
||||
@ -17,13 +16,6 @@ export class LocalReducer {
|
||||
}
|
||||
}
|
||||
|
||||
setSpinner(obj, state) {
|
||||
let data = _.has(obj, 'spinner', false);
|
||||
if (data) {
|
||||
state.spinner = obj.spinner;
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(obj, state) {
|
||||
let data = _.has(obj, 'selected', false);
|
||||
if (data) {
|
||||
|
@ -19,7 +19,6 @@ class Store {
|
||||
chat: {},
|
||||
contacts: {}
|
||||
},
|
||||
spinner: false,
|
||||
selectedGroups: [],
|
||||
sidebarShown: true,
|
||||
pendingMessages: new Map([]),
|
||||
|
@ -181,16 +181,6 @@ class UrbitApi {
|
||||
})
|
||||
}
|
||||
|
||||
setSpinner(boolean) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
spinner: boolean
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
|
@ -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 = (<span />);
|
||||
if (this.state.inviteError) {
|
||||
invErrElem = (
|
||||
<span className="f9 inter red2 ml3 mb5 db">
|
||||
Invites must be validly formatted ship names.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 flex flex-column overflow-y-scroll white-d">
|
||||
@ -86,6 +80,7 @@ export class AddScreen extends Component {
|
||||
<Link to="/~groups">
|
||||
<button className="f8 ml4 ba pa2 b--black pointer bg-transparent b--white-d white-d">Cancel</button>
|
||||
</Link>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4 pl4" text="Inviting to group..." />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -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() {
|
||||
@ -116,6 +109,21 @@ export class ContactCard extends Component {
|
||||
);
|
||||
|
||||
switch (field) {
|
||||
case "color": {
|
||||
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) {
|
||||
this.setState({ awaiting: true, type: "Saving to group" }, (() => {
|
||||
api.contactEdit(props.path, `~${props.ship}`, { color: hexTest[1] }).then(() => {
|
||||
this.setState({ awaiting: false });
|
||||
});
|
||||
}))
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "email": {
|
||||
if (
|
||||
(state.emailToSet === "") ||
|
||||
@ -125,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;
|
||||
}
|
||||
@ -136,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": {
|
||||
@ -146,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": {
|
||||
@ -158,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;
|
||||
}
|
||||
@ -171,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;
|
||||
}
|
||||
}
|
||||
@ -241,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() {
|
||||
@ -268,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() {
|
||||
@ -318,6 +367,7 @@ export class ContactCard extends Component {
|
||||
onChange={this.sigilColorSet}
|
||||
defaultValue={defaultColor}
|
||||
key={"default" + defaultColor}
|
||||
onBlur={(() => this.setField("color"))}
|
||||
style={{
|
||||
resize: "none",
|
||||
height: 40,
|
||||
@ -535,6 +585,7 @@ export class ContactCard extends Component {
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-100 w-100 overflow-x-hidden pb8 white-d">{card}</div>
|
||||
<Spinner awaiting={this.state.awaiting} text={`${this.state.type}...`} classes="absolute right-1 bottom-1 ba pa2 b--gray1-d" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
<p className={"v-mid f9 mh3 red2 pointer " + adminOpt}
|
||||
style={{paddingTop: 6}}
|
||||
onClick={() => {
|
||||
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
|
||||
</p>
|
||||
@ -107,6 +115,7 @@ export class ContactSidebar extends Component {
|
||||
{contactItems}
|
||||
{groupItems}
|
||||
</div>
|
||||
<Spinner awaiting={this.state.awaiting} text="Removing from group..." classes="pa2 ba absolute right-1 bottom-1 b--gray1-d" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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 {
|
||||
<a className={"dib f9 ba pa2 " + deleteButtonClasses}
|
||||
onClick={() => {
|
||||
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</a>
|
||||
</div>
|
||||
<Spinner awaiting={this.state.awaiting} text={`${this.state.type} group...`} classes="pa2 ba absolute right-1 bottom-1 b--gray1-d"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
: {};
|
||||
|
@ -1,16 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
return (
|
||||
<img
|
||||
className={"invert-d " + classes}
|
||||
src="/~groups/img/Home.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconSpinner extends Component {
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="spinner-pending"></div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~groups/img/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid inter">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
<Link to="/~groups">
|
||||
<button className="f9 ml3 ba pa2 b--black pointer bg-transparent b--white-d white-d">Cancel</button>
|
||||
</Link>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt4" text="Creating group..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -22,6 +22,10 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
new Image().src = "/~groups/img/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -45,7 +49,6 @@ export class Root extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
activeDrawer="groups"
|
||||
spinner={state.spinner}
|
||||
selectedGroups={selectedGroups}
|
||||
history={props.history}
|
||||
api={api}
|
||||
@ -67,7 +70,6 @@ export class Root extends Component {
|
||||
render={ (props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={api}
|
||||
@ -100,7 +102,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={api}
|
||||
@ -141,7 +142,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
selectedGroups={selectedGroups}
|
||||
api={api}
|
||||
@ -185,7 +185,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
@ -235,7 +234,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
@ -271,7 +269,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
history={props.history}
|
||||
api={api}
|
||||
selectedGroups={selectedGroups}
|
||||
|
@ -12,7 +12,7 @@ export class Skeleton extends Component {
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 ph4-m ph4-l ph4-xl pb4-m pb4-l pb4-xl">
|
||||
<HeaderBar spinner={props.spinner} invites={props.invites} associations={props.associations} />
|
||||
<HeaderBar invites={props.invites} associations={props.associations} />
|
||||
<div className="cf w-100 h-100 h-100-m-40-ns flex ba-m ba-l ba-xl b--gray4 b--gray1-d br1">
|
||||
<GroupSidebar
|
||||
contacts={props.contacts}
|
||||
@ -26,7 +26,7 @@ export class Skeleton extends Component {
|
||||
associations={props.associations}
|
||||
/>
|
||||
<div
|
||||
className={"h-100 w-100 " + rightPanelClasses}
|
||||
className={"h-100 w-100 relative " + rightPanelClasses}
|
||||
style={{ flexGrow: 1 }}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
@ -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) {
|
||||
|
@ -15,8 +15,7 @@ class Store {
|
||||
associations: {},
|
||||
permissions: {},
|
||||
invites: {},
|
||||
selectedGroups: [],
|
||||
spinner: false
|
||||
selectedGroups: []
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
|
@ -237,16 +237,6 @@ class UrbitApi {
|
||||
});
|
||||
}
|
||||
|
||||
setSpinner(boolean) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
spinner: boolean
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
store.handleEvent({
|
||||
data: {
|
||||
|
@ -35,8 +35,10 @@ export class CommentItem extends Component {
|
||||
|
||||
let member = this.props.member || false;
|
||||
|
||||
let pending = !!this.props.pending ? "o-60" : "";
|
||||
|
||||
return (
|
||||
<div className="w-100 pv3">
|
||||
<div className={"w-100 pv3 " + pending}>
|
||||
<div className="flex bg-white bg-gray0-d">
|
||||
<Sigil
|
||||
ship={"~" + props.ship}
|
||||
|
@ -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
|
||||
: {};
|
||||
|
@ -1,16 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
|
||||
return (
|
||||
<img
|
||||
className={"invert-d " + classes}
|
||||
src="/~link/img/Home.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
|
||||
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 (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~link/img/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from './invite-search';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class InviteElement extends Component {
|
||||
|
||||
@ -8,7 +9,8 @@ export class InviteElement extends Component {
|
||||
this.state = {
|
||||
members: [],
|
||||
error: false,
|
||||
success: false
|
||||
success: false,
|
||||
awaiting: false
|
||||
};
|
||||
this.setInvite = this.setInvite.bind(this);
|
||||
}
|
||||
@ -26,7 +28,7 @@ export class InviteElement extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
api.setSpinner(true);
|
||||
this.setState({awaiting: true});
|
||||
|
||||
this.setState({
|
||||
error: false,
|
||||
@ -34,7 +36,7 @@ export class InviteElement extends Component {
|
||||
members: []
|
||||
}, () => {
|
||||
api.inviteToCollection(props.resourcePath, aud).then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({awaiting: false});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -69,6 +71,7 @@ export class InviteElement extends Component {
|
||||
className={modifyButtonClasses}>
|
||||
Invite
|
||||
</button>
|
||||
<Spinner awaiting={this.state.awaiting} text="Inviting to collection..." classes="mt3"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from "react";
|
||||
import { api } from "../../api";
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class LinkSubmit extends Component {
|
||||
constructor() {
|
||||
@ -20,10 +21,8 @@ export class LinkSubmit extends Component {
|
||||
let title = this.state.linkTitle
|
||||
? this.state.linkTitle
|
||||
: this.state.linkValue;
|
||||
api.setSpinner(true);
|
||||
this.setState({disabled: true})
|
||||
api.postLink(this.props.resourcePath, link, title).then(r => {
|
||||
api.setSpinner(false);
|
||||
this.setState({
|
||||
disabled: false,
|
||||
linkValue: "",
|
||||
@ -126,6 +125,7 @@ export class LinkSubmit extends Component {
|
||||
}}>
|
||||
Post
|
||||
</button>
|
||||
<Spinner awaiting={this.state.disabled} classes="mt3 absolute right-0" text="Posting to collection..." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
|
||||
import { api } from '../api';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Comments } from './lib/comments';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { LoadingScreen } from './loading';
|
||||
import { makeRoutePath, getContactDetails } from '../lib/util';
|
||||
import CommentItem from './lib/comment-item';
|
||||
|
||||
export class LinkDetail extends Component {
|
||||
constructor(props) {
|
||||
@ -14,7 +16,9 @@ export class LinkDetail extends Component {
|
||||
this.state = {
|
||||
comment: "",
|
||||
data: props.data,
|
||||
commentFocus: false
|
||||
commentFocus: false,
|
||||
pending: new Set(),
|
||||
disabled: false
|
||||
};
|
||||
|
||||
this.setComment = this.setComment.bind(this);
|
||||
@ -39,20 +43,36 @@ export class LinkDetail extends Component {
|
||||
if (this.props.url !== prevProps.url) {
|
||||
this.updateData(this.props.data);
|
||||
}
|
||||
if (prevProps.comments && prevProps.comments["0"] &&
|
||||
this.props.comments && this.props.comments["0"]) {
|
||||
let prevFirstComment = prevProps.comments["0"][0];
|
||||
let thisFirstComment = this.props.comments["0"][0];
|
||||
if ((prevFirstComment && prevFirstComment.udon) &&
|
||||
(thisFirstComment && thisFirstComment.udon)) {
|
||||
if (this.state.pending.has(thisFirstComment.udon)) {
|
||||
let pending = this.state.pending;
|
||||
pending.delete(thisFirstComment.udon);
|
||||
this.setState({
|
||||
pending: pending
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClickPost() {
|
||||
let url = this.props.url || "";
|
||||
|
||||
api.setSpinner(true);
|
||||
let pending = this.state.pending;
|
||||
pending.add(this.state.comment);
|
||||
this.setState({ pending: pending, disabled: true });
|
||||
|
||||
api.postComment(
|
||||
this.props.resourcePath,
|
||||
url,
|
||||
this.state.comment
|
||||
).then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({ comment: "" });
|
||||
this.setState({ comment: "", disabled: false });
|
||||
});
|
||||
|
||||
}
|
||||
@ -88,7 +108,24 @@ export class LinkDetail extends Component {
|
||||
|
||||
let focus = (this.state.commentFocus)
|
||||
? "b--black b--white-d"
|
||||
: "b--gray4 b--gray2-d"
|
||||
: "b--gray4 b--gray2-d";
|
||||
|
||||
let our = getContactDetails(props.contacts[window.ship]);
|
||||
|
||||
let pendingArray = Array.from(this.state.pending).map((com, i) => {
|
||||
return(
|
||||
<CommentItem
|
||||
key={i}
|
||||
color={our.color}
|
||||
nickname={our.nickname}
|
||||
ship={window.ship}
|
||||
pending={true}
|
||||
content={com}
|
||||
member={our.member}
|
||||
time={new Date().getTime()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 overflow-hidden flex flex-column">
|
||||
@ -121,37 +158,43 @@ export class LinkDetail extends Component {
|
||||
linkIndex={props.linkIndex}
|
||||
time={this.state.data.time}
|
||||
/>
|
||||
<div className={"relative ba br1 mt6 mb6 " + focus}>
|
||||
<textarea
|
||||
className="w-100 bg-gray0-d white-d f8 pa2 pr8"
|
||||
style={{
|
||||
resize: "none",
|
||||
height: 75
|
||||
}}
|
||||
placeholder="Leave a comment on this link"
|
||||
onChange={this.setComment}
|
||||
onKeyPress={(e) => {
|
||||
if ((e.getModifierState("Control") || event.getModifierState("Meta"))
|
||||
&& e.key === "Enter") {
|
||||
this.onClickPost();
|
||||
<div className="relative">
|
||||
<div className={"relative ba br1 mt6 mb6 " + focus}>
|
||||
<textarea
|
||||
className="w-100 bg-gray0-d white-d f8 pa2 pr8"
|
||||
style={{
|
||||
resize: "none",
|
||||
height: 75
|
||||
}}
|
||||
placeholder="Leave a comment on this link"
|
||||
onChange={this.setComment}
|
||||
onKeyDown={e => {
|
||||
if (
|
||||
(e.getModifierState("Control") || e.metaKey) &&
|
||||
e.key === "Enter"
|
||||
) {
|
||||
this.onClickPost();
|
||||
}
|
||||
}}
|
||||
onFocus={() => this.setState({ commentFocus: true })}
|
||||
onBlur={() => this.setState({ commentFocus: false })}
|
||||
value={this.state.comment}
|
||||
/>
|
||||
<button
|
||||
className={
|
||||
"f8 bg-gray0-d ml2 absolute " + activeClasses
|
||||
}
|
||||
}}
|
||||
onFocus={() => this.setState({commentFocus: true})}
|
||||
onBlur={() => this.setState({commentFocus: false})}
|
||||
value={this.state.comment}
|
||||
/>
|
||||
<button
|
||||
className={
|
||||
"f8 bg-gray0-d ml2 absolute " + activeClasses
|
||||
}
|
||||
disabled={!this.state.comment}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}>
|
||||
Post
|
||||
</button>
|
||||
disabled={!this.state.comment || this.state.disabled}
|
||||
onClick={this.onClickPost.bind(this)}
|
||||
style={{
|
||||
bottom: 12,
|
||||
right: 8
|
||||
}}>
|
||||
Post
|
||||
</button>
|
||||
</div>
|
||||
<Spinner awaiting={this.state.disabled} classes="absolute pt5 right-0" text="Posting comment..." />
|
||||
{pendingArray}
|
||||
</div>
|
||||
<Comments
|
||||
resourcePath={props.resourcePath}
|
||||
|
@ -13,7 +13,6 @@ import { makeRoutePath, getContactDetails } from '../lib/util';
|
||||
export class Links extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.markAllAsSeen = this.markAllAsSeen.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -30,10 +29,6 @@ export class Links extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
markAllAsSeen() {
|
||||
api.seenLink(this.props.resourcePath);
|
||||
}
|
||||
|
||||
render() {
|
||||
let props = this.props;
|
||||
|
||||
@ -121,11 +116,6 @@ export class Links extends Component {
|
||||
<LinkSubmit resourcePath={props.resourcePath}/>
|
||||
</div>
|
||||
<div className="pb4">
|
||||
<span
|
||||
className="f9 inter gray2 ba b--gray2 br2 dib pa1 pointer"
|
||||
onClick={this.markAllAsSeen}>
|
||||
mark all as seen
|
||||
</span>
|
||||
{LinkList}
|
||||
<Pagination
|
||||
{...props}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from './lib/invite-search';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { makeRoutePath, isPatTa, deSig } from '/lib/util';
|
||||
import urbitOb from 'urbit-ob';
|
||||
@ -16,7 +17,8 @@ export class NewScreen extends Component {
|
||||
ships: [],
|
||||
idError: false,
|
||||
inviteError: false,
|
||||
createGroup: false
|
||||
createGroup: false,
|
||||
disabled: false
|
||||
};
|
||||
|
||||
this.titleChange = this.titleChange.bind(this);
|
||||
@ -115,9 +117,9 @@ export class NewScreen extends Component {
|
||||
error: false,
|
||||
success: true,
|
||||
group: [],
|
||||
ships: []
|
||||
ships: [],
|
||||
disabled: true
|
||||
}, () => {
|
||||
api.setSpinner(true);
|
||||
let submit = api.createCollection(
|
||||
appPath,
|
||||
state.title,
|
||||
@ -126,7 +128,7 @@ export class NewScreen extends Component {
|
||||
state.createGroup
|
||||
);
|
||||
submit.then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({disabled: false})
|
||||
props.history.push(makeRoutePath(appPath));
|
||||
})
|
||||
});
|
||||
@ -231,9 +233,11 @@ export class NewScreen extends Component {
|
||||
{createGroupToggle}
|
||||
<button
|
||||
onClick={this.onClickCreate.bind(this)}
|
||||
className={createClasses}>
|
||||
className={createClasses}
|
||||
disabled={this.state.disabled}>
|
||||
Create Collection
|
||||
</button>
|
||||
<Spinner awaiting={this.state.disabled} classes="mt3" text="Creating collection..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -27,6 +27,11 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//preload spinner asset
|
||||
new Image().src = "/~link/img/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { state } = this;
|
||||
|
||||
@ -50,7 +55,6 @@ export class Root extends Component {
|
||||
return (
|
||||
<Skeleton
|
||||
active="collections"
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -73,7 +77,6 @@ export class Root extends Component {
|
||||
render={(props) => {
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -111,7 +114,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -149,7 +151,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -203,7 +204,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
@ -259,7 +259,6 @@ export class Root extends Component {
|
||||
|
||||
return (
|
||||
<Skeleton
|
||||
spinner={state.spinner}
|
||||
associations={associations}
|
||||
invites={invites}
|
||||
groups={groups}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { deSig, uxToHex } from '/lib/util';
|
||||
import { Route, Link } from "react-router-dom";
|
||||
|
||||
import { LoadingScreen } from './loading';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
import { LinksTabBar } from '/components/lib/links-tabbar';
|
||||
import SidebarSwitcher from './lib/icons/icon-sidebar-switch';
|
||||
import { makeRoutePath } from '../lib/util';
|
||||
@ -16,7 +16,9 @@ export class SettingsScreen extends Component {
|
||||
isLoading: false,
|
||||
title: "",
|
||||
description: "",
|
||||
color: ""
|
||||
color: "",
|
||||
disabled: false,
|
||||
type: "Editing"
|
||||
};
|
||||
|
||||
this.changeTitle = this.changeTitle.bind(this);
|
||||
@ -44,7 +46,6 @@ export class SettingsScreen extends Component {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
}, () => {
|
||||
api.setSpinner(false);
|
||||
props.history.push('/~link');
|
||||
});
|
||||
}
|
||||
@ -92,7 +93,7 @@ export class SettingsScreen extends Component {
|
||||
}
|
||||
if (hexTest && (hexTest[1] !== currentColor)) {
|
||||
if (props.amOwner) {
|
||||
api.setSpinner(true);
|
||||
this.setState({disabled: true});
|
||||
api.metadataAdd(
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
@ -101,7 +102,7 @@ export class SettingsScreen extends Component {
|
||||
resource.metadata['date-created'],
|
||||
color
|
||||
).then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({disabled: false});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -110,35 +111,39 @@ export class SettingsScreen extends Component {
|
||||
removeCollection() {
|
||||
const { props, state } = this;
|
||||
|
||||
api.setSpinner(true);
|
||||
this.setState({
|
||||
isLoading: true
|
||||
isLoading: true,
|
||||
disabled: true,
|
||||
type: "Removing"
|
||||
});
|
||||
api.removeCollection(props.resourcePath)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
});
|
||||
api.setSpinner(false);
|
||||
});
|
||||
}
|
||||
|
||||
deleteCollection() {
|
||||
const { props, state } = this;
|
||||
|
||||
api.setSpinner(true);
|
||||
this.setState({
|
||||
isLoading: true
|
||||
isLoading: true,
|
||||
disabled: true,
|
||||
type: "Deleting"
|
||||
});
|
||||
api.deleteCollection(props.resourcePath)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
isLoading: false
|
||||
});
|
||||
api.setSpinner(false);
|
||||
});
|
||||
}
|
||||
|
||||
markAllAsSeen() {
|
||||
api.seenLink(this.props.resourcePath);
|
||||
}
|
||||
|
||||
renderRemove() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -196,11 +201,11 @@ export class SettingsScreen extends Component {
|
||||
className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.title}
|
||||
disabled={!props.amOwner}
|
||||
disabled={!props.amOwner || this.state.disabled}
|
||||
onChange={this.changeTitle}
|
||||
onBlur={() => {
|
||||
if (props.amOwner) {
|
||||
api.setSpinner(true);
|
||||
this.setState({ disabled: true });
|
||||
api.metadataAdd(
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
@ -209,7 +214,7 @@ export class SettingsScreen extends Component {
|
||||
resource.metadata['date-created'],
|
||||
uxToHex(resource.metadata.color)
|
||||
).then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({ disabled: false });
|
||||
});
|
||||
}
|
||||
}}
|
||||
@ -225,20 +230,20 @@ export class SettingsScreen extends Component {
|
||||
className={"f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.description}
|
||||
disabled={!props.amOwner}
|
||||
disabled={!props.amOwner || this.state.disabled}
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
if (props.amOwner) {
|
||||
api.setSpinner(true);
|
||||
this.setState({ disabled: true });
|
||||
api.metadataAdd(
|
||||
props.resourcePath,
|
||||
props.groupPath,
|
||||
resource.metadata.title,
|
||||
state.description,
|
||||
resource['date-created'],
|
||||
uxToHex(resource.color)
|
||||
resource.metadata['date-created'],
|
||||
uxToHex(resource.metadata.color)
|
||||
).then(() => {
|
||||
api.setSpinner(false);
|
||||
this.setState({ disabled: false });
|
||||
});
|
||||
}
|
||||
}}
|
||||
@ -260,7 +265,7 @@ export class SettingsScreen extends Component {
|
||||
className={"pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d " +
|
||||
"focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3"}
|
||||
value={this.state.color}
|
||||
disabled={!props.amOwner}
|
||||
disabled={!props.amOwner || this.state.disabled}
|
||||
onChange={this.changeColor}
|
||||
onBlur={this.submitColor}
|
||||
/>
|
||||
@ -338,9 +343,20 @@ export class SettingsScreen extends Component {
|
||||
</div>
|
||||
<div className="w-100 pl3 mt4 cf">
|
||||
<h2 className="f8 pb2">Collection Settings</h2>
|
||||
<p className="f8 mt3 lh-copy db">Mark all links as read</p>
|
||||
<p className="f9 gray2 db mb4">Mark all links in this collection as read.</p>
|
||||
<a className="dib f9 black gray4-d bg-gray0-d ba pa2 b--black b--gray1-d pointer"
|
||||
onClick={() => this.markAllAsSeen}>
|
||||
Mark all as read
|
||||
</a>
|
||||
{this.renderRemove()}
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
<Spinner
|
||||
awaiting={this.state.disabled}
|
||||
classes="absolute right-1 bottom-1 pa2 ba b--black b--gray0-d white-d"
|
||||
text={`${this.state.type} collection...`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -25,7 +25,6 @@ export class Skeleton extends Component {
|
||||
return (
|
||||
<div className={"absolute h-100 w-100 " + popoutWindow}>
|
||||
<HeaderBar
|
||||
spinner={this.props.spinner}
|
||||
invites={this.props.invites}
|
||||
associations={this.props.associations}
|
||||
/>
|
||||
@ -41,7 +40,7 @@ export class Skeleton extends Component {
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
links={this.props.links}
|
||||
listening={this.props.listening}/>
|
||||
<div className={"h-100 w-100 flex-auto" + rightPanelHide} style={{
|
||||
<div className={"h-100 w-100 flex-auto relative " + rightPanelHide} style={{
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
{this.props.children}
|
||||
|
@ -5,7 +5,6 @@ export class LocalReducer {
|
||||
let data = _.get(json, 'local', false);
|
||||
if (data) {
|
||||
this.sidebarToggle(data, state);
|
||||
this.setSpinner(data, state);
|
||||
this.setSelected(data, state);
|
||||
}
|
||||
}
|
||||
@ -17,12 +16,6 @@ export class LocalReducer {
|
||||
}
|
||||
}
|
||||
|
||||
setSpinner(obj, state) {
|
||||
let data = _.has(obj, 'spinner', false);
|
||||
if (data) {
|
||||
state.spinner = obj.spinner;
|
||||
}
|
||||
}
|
||||
setSelected(obj, state) {
|
||||
let data = _.has(obj, 'selected', false);
|
||||
if (data) {
|
||||
|
@ -26,8 +26,7 @@ class Store {
|
||||
comments: {},
|
||||
seen: {},
|
||||
permissions: {},
|
||||
sidebarShown: true,
|
||||
spinner: false
|
||||
sidebarShown: true
|
||||
};
|
||||
|
||||
this.initialReducer = new InitialReducer();
|
||||
|
@ -130,15 +130,6 @@ class UrbitApi {
|
||||
});
|
||||
}
|
||||
|
||||
setSpinner(boolean) {
|
||||
store.handleEvent({
|
||||
type: "local",
|
||||
data: {
|
||||
'spinner': boolean
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
store.handleEvent({
|
||||
type: "local",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import { CommentItem } from './comment-item';
|
||||
import { dateToDa } from '/lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class Comments extends Component {
|
||||
constructor(props){
|
||||
@ -49,11 +50,9 @@ export class Comments extends Component {
|
||||
this.setState({pending: pendingState});
|
||||
|
||||
this.textArea.value = '';
|
||||
window.api.setSpinner(true);
|
||||
this.setState({commentBody: ""});
|
||||
this.setState({commentBody: "", disabled: true});
|
||||
let submit = window.api.action("publish", "publish-action", comment);
|
||||
submit.then(() => {
|
||||
window.api.setSpinner(false);
|
||||
this.setState({ disabled: false });
|
||||
})
|
||||
}
|
||||
@ -105,7 +104,7 @@ export class Comments extends Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mv8">
|
||||
<div className="mv8 relative">
|
||||
<div>
|
||||
<textarea style={{resize:'vertical'}}
|
||||
ref={(el) => {this.textArea = el}}
|
||||
@ -117,8 +116,8 @@ export class Comments extends Component {
|
||||
aria-describedby="comment-desc"
|
||||
style={{height: "4rem"}}
|
||||
onChange={this.commentChange}
|
||||
onKeyPress={(e) => {
|
||||
if ((e.getModifierState("Control") || event.getModifierState("Meta"))
|
||||
onKeyDown={(e) => {
|
||||
if ((e.getModifierState("Control") || event.metaKey)
|
||||
&& e.key === "Enter") {
|
||||
this.commentSubmit();
|
||||
}
|
||||
@ -130,6 +129,7 @@ export class Comments extends Component {
|
||||
className={commentClass}>
|
||||
Add comment
|
||||
</button>
|
||||
<Spinner text="Posting comment..." awaiting={this.state.disabled} classes="absolute bottom-0 right-0 pb2"/>
|
||||
</div>
|
||||
{pendingArray}
|
||||
{commentArray}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import { dateToDa } from '/lib/util';
|
||||
@ -11,7 +12,8 @@ export class EditPost extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
body: '',
|
||||
submit: false
|
||||
submit: false,
|
||||
awaiting: false
|
||||
}
|
||||
this.postSubmit = this.postSubmit.bind(this);
|
||||
this.bodyChange = this.bodyChange.bind(this);
|
||||
@ -48,11 +50,11 @@ export class EditPost extends Component {
|
||||
body: state.body
|
||||
}
|
||||
}
|
||||
window.api.setSpinner(true);
|
||||
this.setState({awaiting: true});
|
||||
window.api.action("publish", "publish-action", editNote).then(() => {
|
||||
let editIndex = props.location.pathname.indexOf("/edit");
|
||||
let noteHref = props.location.pathname.slice(0, editIndex);
|
||||
window.api.setSpinner(false);
|
||||
this.setState({awaiting: false});
|
||||
props.history.push(noteHref);
|
||||
});
|
||||
}
|
||||
@ -125,6 +127,7 @@ export class EditPost extends Component {
|
||||
onBeforeChange={(e, d, v) => this.bodyChange(e, d, v)}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
<Spinner text="Editing post..." awaiting={this.state.awaiting} classes="absolute bottom-1 right-1 ba b--gray1-d pa2"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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
|
||||
: {};
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
|
||||
return (
|
||||
<img className={"invert-d " + classes}
|
||||
src="/~publish/Home.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
|
||||
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 (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~publish/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import urbitOb from 'urbit-ob';
|
||||
|
||||
export class JoinScreen extends Component {
|
||||
@ -8,7 +9,7 @@ export class JoinScreen extends Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
book: '/',
|
||||
book: '',
|
||||
error: false,
|
||||
awaiting: null,
|
||||
disable: false
|
||||
@ -36,6 +37,7 @@ export class JoinScreen extends Component {
|
||||
let notebook = book[1];
|
||||
if ((ship in this.props.notebooks) &&
|
||||
(notebook in this.props.notebooks[ship])) {
|
||||
this.setState({disable: false, book: "/"});
|
||||
this.props.history.push(`/~publish/notebook/${ship}/${notebook}`)
|
||||
}
|
||||
}
|
||||
@ -91,13 +93,11 @@ export class JoinScreen extends Component {
|
||||
}
|
||||
|
||||
// TODO: askHistory setting
|
||||
window.api.setSpinner(true);
|
||||
this.setState({disable: true});
|
||||
window.api.action("publish","publish-action", actionData).catch((err) => {
|
||||
console.log(err)
|
||||
}).then(() => {
|
||||
this.setState({awaiting: text, disable: false, book: ""})
|
||||
window.api.setSpinner(false);
|
||||
this.setState({awaiting: text})
|
||||
});
|
||||
|
||||
}
|
||||
@ -152,7 +152,9 @@ export class JoinScreen extends Component {
|
||||
style={{
|
||||
resize: 'none',
|
||||
}}
|
||||
onChange={this.bookChange} />
|
||||
onChange={this.bookChange}
|
||||
value={this.state.book}
|
||||
/>
|
||||
{errElem}
|
||||
<br />
|
||||
<button
|
||||
@ -160,10 +162,11 @@ export class JoinScreen extends Component {
|
||||
onClick={this.onClickJoin.bind(this)}
|
||||
className={joinClasses}
|
||||
>Join Notebook</button>
|
||||
<Spinner awaiting={this.state.disable} classes="mt4" text="Joining notebook..." />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default JoinScreen
|
||||
export default JoinScreen;
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2'
|
||||
import { dateToDa, stringToSymbol } from '/lib/util';
|
||||
@ -35,7 +36,6 @@ export class NewPost extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
window.api.setSpinner(true);
|
||||
this.setState({ disabled: true });
|
||||
window.api.action("publish", "publish-action", newNote).then(() => {
|
||||
this.setState({ awaiting: newNote["new-note"].note, disabled: false });
|
||||
@ -57,7 +57,6 @@ export class NewPost extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
let notebook = this.props.notebooks[this.props.ship][this.props.book];
|
||||
if (notebook.notes[this.state.awaiting]) {
|
||||
window.api.setSpinner(false);
|
||||
let popout = (this.props.popout) ? "popout/" : "";
|
||||
let redirect =
|
||||
`/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.state.awaiting}`;
|
||||
@ -151,6 +150,7 @@ export class NewPost extends Component {
|
||||
onBeforeChange={(e, d, v) => this.bodyChange(e, d, v)}
|
||||
onChange={(editor, data, value) => {}}
|
||||
/>
|
||||
<Spinner text="Creating post..." awaiting={this.state.disabled} classes="absolute bottom-1 right-1 ba b--gray1-d pa2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { InviteSearch } from './invite-search';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { uuid, isPatTa, deSig, stringToSymbol } from "/lib/util";
|
||||
import urbitOb from 'urbit-ob';
|
||||
@ -30,7 +31,6 @@ export class NewScreen extends Component {
|
||||
const { props, state } = this;
|
||||
if (props.notebooks && (("~" + window.ship) in props.notebooks)) {
|
||||
if (state.awaiting in props.notebooks["~" + window.ship]) {
|
||||
props.api.setSpinner(false);
|
||||
let notebook = `/~${window.ship}/${state.awaiting}`;
|
||||
props.history.push("/~publish/notebook" + notebook);
|
||||
}
|
||||
@ -93,10 +93,8 @@ export class NewScreen extends Component {
|
||||
group: groupInfo
|
||||
}
|
||||
}
|
||||
props.api.setSpinner(true);
|
||||
this.setState({awaiting: bookId, disabled: true}, () => {
|
||||
props.api.action("publish", "publish-action", action).then(() => {
|
||||
props.api.setSpinner(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -135,7 +133,7 @@ export class NewScreen extends Component {
|
||||
Notebook must have a valid name.
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -202,6 +200,7 @@ export class NewScreen extends Component {
|
||||
className={createClasses}>
|
||||
Create Notebook
|
||||
</button>
|
||||
<Spinner awaiting={this.state.awaiting} classes="mt3" text="Creating notebook..."/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
import { Comments } from './comments';
|
||||
import { NoteNavigation } from './note-navigation';
|
||||
import moment from 'moment';
|
||||
@ -8,8 +9,11 @@ import ReactMarkdown from 'react-markdown';
|
||||
import { cite } from '../../lib/util';
|
||||
|
||||
export class Note extends Component {
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
deleting: false
|
||||
}
|
||||
moment.updateLocale('en', {
|
||||
relativeTime: {
|
||||
past: function(input) {
|
||||
@ -119,10 +123,9 @@ export class Note extends Component {
|
||||
}
|
||||
let popout = (props.popout) ? "popout/" : "";
|
||||
let baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
|
||||
window.api.setSpinner(true);
|
||||
this.setState({deleting: true});
|
||||
window.api.action("publish", "publish-action", deleteAction)
|
||||
.then(() => {
|
||||
window.api.setSpinner(false);
|
||||
props.history.push(baseUrl);
|
||||
});
|
||||
}
|
||||
@ -251,6 +254,7 @@ export class Note extends Component {
|
||||
comments={comments}
|
||||
contacts={props.contacts}
|
||||
/>
|
||||
<Spinner text="Deleting post..." awaiting={this.state.deleting} classes="absolute bottom-1 right-1 ba b--gray1-d pa2" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import { writeText } from '../../lib/util';
|
||||
import { Spinner } from './icons/icon-spinner';
|
||||
|
||||
export class Settings extends Component {
|
||||
constructor(props){
|
||||
@ -8,7 +9,8 @@ export class Settings extends Component {
|
||||
title: "",
|
||||
description: "",
|
||||
comments: false,
|
||||
disabled: false
|
||||
disabled: false,
|
||||
type: "Editing"
|
||||
}
|
||||
this.deleteNotebook = this.deleteNotebook.bind(this);
|
||||
this.changeTitle = this.changeTitle.bind(this);
|
||||
@ -29,13 +31,19 @@ export class Settings extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { props } = this;
|
||||
if (prevProps !== this.props) {
|
||||
if (prevProps !== props) {
|
||||
if (props.notebook) {
|
||||
this.setState({
|
||||
title: props.notebook.title,
|
||||
description: props.notebook.about,
|
||||
comments: props.notebook.comments
|
||||
})
|
||||
if (prevProps.notebook && prevProps.notebook !== props.notebook) {
|
||||
if (prevProps.notebook.title !== props.notebook.title) {
|
||||
this.setState({title: props.notebook.title});
|
||||
}
|
||||
if (prevProps.notebook.about !== props.notebook.about) {
|
||||
this.setState({description: props.notebook.about});
|
||||
}
|
||||
if (prevProps.notebook.comments !== props.notebook.comments) {
|
||||
this.setState({comments: props.notebook.comments})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,8 +57,7 @@ export class Settings extends Component {
|
||||
}
|
||||
|
||||
changeComments() {
|
||||
this.setState({comments: !this.state.comments}, (() => {
|
||||
window.api.setSpinner(true);
|
||||
this.setState({comments: !this.state.comments, disabled: true}, (() => {
|
||||
window.api.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
book: this.props.book,
|
||||
@ -60,7 +67,7 @@ export class Settings extends Component {
|
||||
group: null
|
||||
}
|
||||
}).then(() => {
|
||||
window.api.setSpinner(false);
|
||||
this.setState({disabled: false});
|
||||
})
|
||||
}));
|
||||
}
|
||||
@ -71,9 +78,8 @@ export class Settings extends Component {
|
||||
book: this.props.book
|
||||
}
|
||||
}
|
||||
window.api.setSpinner(true);
|
||||
this.setState({ disabled: true, type: "Deleting" });
|
||||
window.api.action("publish", "publish-action", action).then(() => {
|
||||
window.api.setSpinner(false);
|
||||
this.props.history.push("/~publish");
|
||||
});
|
||||
}
|
||||
@ -132,7 +138,6 @@ export class Settings extends Component {
|
||||
disabled={this.state.disabled}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
window.api.setSpinner(true);
|
||||
window.api
|
||||
.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
@ -145,7 +150,6 @@ export class Settings extends Component {
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({ disabled: false })
|
||||
window.api.setSpinner(false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -162,7 +166,6 @@ export class Settings extends Component {
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
window.api.setSpinner(true);
|
||||
window.api
|
||||
.action("publish", "publish-action", {
|
||||
"edit-book": {
|
||||
@ -175,7 +178,6 @@ export class Settings extends Component {
|
||||
})
|
||||
.then(() => {
|
||||
this.setState({ disabled: false });
|
||||
window.api.setSpinner(false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -192,6 +194,11 @@ export class Settings extends Component {
|
||||
Subscribers may comment when enabled
|
||||
</p>
|
||||
</div>
|
||||
<Spinner
|
||||
awaiting={this.state.disabled}
|
||||
classes="absolute right-1 bottom-1 pa2 ba b--black b--gray0-d white-d"
|
||||
text={`${this.state.type} notebook...`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -19,6 +19,11 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//preload spinner asset
|
||||
new Image().src = "/~publish/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
|
||||
@ -36,7 +41,6 @@ export class Root extends Component {
|
||||
active={"sidebar"}
|
||||
rightPanelHide={true}
|
||||
sidebarShown={true}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
@ -62,7 +66,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
@ -89,7 +92,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
@ -128,7 +130,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
@ -153,7 +154,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
@ -199,7 +199,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
selectedGroups={selectedGroups}
|
||||
@ -224,7 +223,6 @@ export class Root extends Component {
|
||||
active={"rightPanel"}
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
spinner={state.spinner}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
associations={associations}
|
||||
|
@ -21,7 +21,6 @@ export class Skeleton extends Component {
|
||||
return (
|
||||
<div className={"absolute h-100 w-100 " + popoutWindow}>
|
||||
<HeaderBar
|
||||
spinner={props.spinner}
|
||||
invites={props.invites}
|
||||
associations={props.associations} />
|
||||
<div className={`cf w-100 h-100 flex ` + popoutBorder}>
|
||||
|
@ -20,7 +20,6 @@ export class ResponseReducer {
|
||||
break;
|
||||
case "local":
|
||||
this.sidebarToggle(json, state);
|
||||
this.setSpinner(json, state);
|
||||
this.setSelected(json, state);
|
||||
break;
|
||||
default:
|
||||
@ -205,12 +204,6 @@ export class ResponseReducer {
|
||||
}
|
||||
}
|
||||
|
||||
setSpinner(json, state) {
|
||||
let data = _.has(json.data, 'spinner', false);
|
||||
if (data) {
|
||||
state.spinner = json.data.spinner;
|
||||
}
|
||||
}
|
||||
setSelected(json, state) {
|
||||
let data = _.has(json.data, 'selected', false);
|
||||
if (data) {
|
||||
|
@ -18,7 +18,6 @@ class Store {
|
||||
permissions: {},
|
||||
invites: {},
|
||||
selectedGroups: [],
|
||||
spinner: false,
|
||||
sidebarShown: true
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class UrbitApi {
|
||||
}
|
||||
|
||||
soto(data) {
|
||||
this.action("dojo", "sole-action",
|
||||
return this.action("dojo", "sole-action",
|
||||
{id: this.authTokens.dojoId, dat: data}
|
||||
);
|
||||
}
|
||||
|
@ -2,10 +2,15 @@ import React, { Component } from 'react';
|
||||
import { store } from '../store';
|
||||
import { api } from '../api';
|
||||
import { cite } from '../lib/util';
|
||||
import { Spinner } from './lib/icons/icon-spinner';
|
||||
|
||||
export class Input extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
awaiting: false,
|
||||
type: "Sending to Dojo"
|
||||
}
|
||||
this.keyPress = this.keyPress.bind(this);
|
||||
this.inputRef = React.createRef();
|
||||
}
|
||||
@ -32,8 +37,10 @@ export class Input extends Component {
|
||||
|
||||
// submit on enter
|
||||
if (e.key === "Enter") {
|
||||
store.setSpinner(true);
|
||||
api.soto("ret");
|
||||
this.setState({ awaiting: true, type: "Sending to Dojo"});
|
||||
api.soto("ret").then(() => {
|
||||
this.setState({awaiting: false});
|
||||
});
|
||||
}
|
||||
|
||||
else if ((e.key === "Backspace") && (this.props.cursor > 0)) {
|
||||
@ -61,8 +68,10 @@ export class Input extends Component {
|
||||
|
||||
// tab completion
|
||||
else if (e.key === "Tab") {
|
||||
store.setSpinner(true);
|
||||
api.soto({tab: this.props.cursor});
|
||||
this.setState({awaiting: true, type: "Getting suggestions"})
|
||||
api.soto({tab: this.props.cursor}).then(() => {
|
||||
this.setState({awaiting: false})
|
||||
});
|
||||
}
|
||||
|
||||
// capture and transmit most characters
|
||||
@ -74,7 +83,7 @@ export class Input extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="flex flex-row flex-grow-1">
|
||||
<div className="flex flex-row flex-grow-1 relative">
|
||||
<div className="flex-shrink-0">{cite(this.props.ship)}:dojo
|
||||
</div>
|
||||
<span id="prompt">
|
||||
@ -100,6 +109,7 @@ render() {
|
||||
ref={this.inputRef}
|
||||
defaultValue={this.props.input}
|
||||
/>
|
||||
<Spinner awaiting={this.state.awaiting} text={`${this.state.type}...`} classes="absolute right-0 bottom-0 inter pa ba pa2 b--gray1-d"/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -6,15 +6,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
|
||||
: {};
|
||||
@ -35,7 +26,7 @@ export class HeaderBar extends Component {
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
className="dib f9 v-mid inter ml2"
|
||||
className="dib f9 v-mid inter ml2 black white-d"
|
||||
href="/"
|
||||
style={{ top: 14 }}>
|
||||
⟵</a> <p className="dib f9 v-mid inter ml2 white-d">Dojo</p>
|
||||
|
@ -1,16 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export class IconHome extends Component {
|
||||
render() {
|
||||
|
||||
let classes = !!this.props.classes ? this.props.classes : "";
|
||||
return (
|
||||
<img
|
||||
className={"invert-d " + classes}
|
||||
src="/~link/img/Home.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export class Spinner extends Component {
|
||||
render() {
|
||||
|
||||
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 (
|
||||
<div className={classes + " z-2 bg-white bg-gray0-d white-d"}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~dojo/img/Spinner.png"
|
||||
width={16}
|
||||
height={16} />
|
||||
<p className="dib f9 ml2 v-mid inter">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -16,11 +16,16 @@ export class Root extends Component {
|
||||
store.setStateHandler(this.setState.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
//preload spinner asset
|
||||
new Image().src = "/~dojo/img/Spinner.png";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<div className="w-100 h-100 bg-white bg-gray1-d">
|
||||
<HeaderBar spinner={this.state.spinner}/>
|
||||
<HeaderBar/>
|
||||
<Route
|
||||
exact path="/~dojo/:popout?"
|
||||
render={(props) => {
|
||||
|
@ -9,8 +9,7 @@ export class Store {
|
||||
txt: [],
|
||||
prompt: '',
|
||||
cursor: 0,
|
||||
input: "",
|
||||
spinner: false
|
||||
input: ""
|
||||
}
|
||||
this.sync = this.sync.bind(this);
|
||||
this.print = this.print.bind(this);
|
||||
@ -21,18 +20,10 @@ export class Store {
|
||||
if (data.data) {
|
||||
var dojoReply = data.data;
|
||||
}
|
||||
else if (data.local) {
|
||||
if (data.local.spinner) {
|
||||
return this.setState({spinner: data.local.spinner})
|
||||
}
|
||||
}
|
||||
else {
|
||||
var dojoReply = data;
|
||||
}
|
||||
|
||||
// on response, disable spinner
|
||||
this.setState({spinner: false});
|
||||
|
||||
// %mor sole-effects are nested, so throw back to handler
|
||||
if (dojoReply.map) {
|
||||
return dojoReply.map(reply => this.handleEvent(reply));
|
||||
@ -86,14 +77,6 @@ export class Store {
|
||||
setStateHandler(setState) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
setSpinner(boolean) {
|
||||
store.handleEvent({
|
||||
local: {
|
||||
spinner: boolean
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export let store = new Store();
|
||||
|