Merge branch 'feat/spa' into lf/groups-refactor

This commit is contained in:
Liam Fitzgerald 2020-06-10 14:41:17 +10:00
commit ef20a4d08a
63 changed files with 747 additions and 704 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:58deed8e9b8cd2c84d092cb8f638b9881cb0d12b97f7c719339e3604c9e9d1d2 oid sha256:575484aaf6c8bc03ab3b962ca52d48a90113bcb38a29a1ac84f2d49d1363b4ba
size 13826033 size 7319532

View File

@ -1,74 +0,0 @@
:: invite-view: provide a json interface to invite-store
::
:: accepts subscriptions at the /primary path.
:: passes through all invites and their updates.
:: only accepts subcriptions from the host's team.
::
::TODO could maybe use /lib/proxy-hook, be renamed invite-proxy-hook
::
/+ *invite-json, default-agent, dbug
::
|%
+$ card card:agent:gall
--
::
=>
|%
++ watch-updates
|= our=ship
^- card
[%pass /store %agent [our %invite-store] %watch /updates]
--
::
%- agent:dbug
^- agent:gall
|_ =bowl:gall
+* this .
def ~(. (default-agent this %|) bowl)
::
++ on-init
^- (quip card _this)
[[(watch-updates our.bowl)]~ this]
::
++ on-save on-save:def
++ on-load
|= old=vase
^- (quip card _this)
[~ this]
::
++ on-watch
|= =path
^- (quip card _this)
?> (team:title our.bowl src.bowl)
?. =(/primary path)
(on-watch:def path)
:_ this
=/ =invites
.^(invites %gx /=invite-store/(scot %da now.bowl)/all/noun)
[%give %fact ~ %json !>((invites-to-json invites))]~
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card _this)
:_ this
?- -.sign
%poke-ack ~|([dap.bowl %unexpected-poke-ack] !!)
%watch-ack ~
%kick [(watch-updates our.bowl)]~
::
%fact
~| [dap.bowl %unexpected-fact-mark p.cage.sign]
?> ?=(%invite-update p.cage.sign)
:~ :*
%give %fact
~[/primary] %json
!>((update-to-json !<(invite-update q.cage.sign)))
== ==
==
::
++ on-poke on-poke:def
++ on-peek on-peek:def
++ on-leave on-leave:def
++ on-arvo on-arvo:def
++ on-fail on-fail:def
--

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -2161,7 +2161,6 @@
[[[~ %json] [%'publish-view' %notebooks ~]] ~] [[[~ %json] [%'publish-view' %notebooks ~]] ~]
%- json-response:gen %- json-response:gen
%- json-to-octs %- json-to-octs
%+ frond:enjs:format %publish-response
(notebooks-map:enjs our.bol books) (notebooks-map:enjs our.bol books)
:: ::
:: notes pagination :: notes pagination
@ -2181,7 +2180,6 @@
not-found:gen not-found:gen
%- json-response:gen %- json-response:gen
%- json-to-octs %- json-to-octs
%+ frond:enjs:format %publish-response
:- %o :- %o
(notes-page:enjs notes.u.book u.start u.length) (notes-page:enjs notes.u.book u.start u.length)
:: ::
@ -2206,7 +2204,6 @@
not-found:gen not-found:gen
%- json-response:gen %- json-response:gen
%- json-to-octs %- json-to-octs
%+ frond:enjs:format %publish-response
(comments-page:enjs comments.u.note u.start u.length) (comments-page:enjs comments.u.note u.start u.length)
:: ::
:: single notebook with initial 50 notes in short form, as json :: single notebook with initial 50 notes in short form, as json
@ -2225,9 +2222,7 @@
(~(put by p.notebook-json) %subscribers (get-subscribers-json book-name)) (~(put by p.notebook-json) %subscribers (get-subscribers-json book-name))
=. p.notebook-json =. p.notebook-json
(~(put by p.notebook-json) %writers (get-writers-json u.host book-name)) (~(put by p.notebook-json) %writers (get-writers-json u.host book-name))
=/ jon=json (json-response:gen (json-to-octs (pairs notebook+notebook-json ~)))
(frond:enjs:format %publish-response (pairs notebook+notebook-json ~))
(json-response:gen (json-to-octs jon))
:: ::
:: single note, with initial 50 comments, as json :: single note, with initial 50 comments, as json
[[[~ %json] [%'publish-view' @ @ @ ~]] ~] [[[~ %json] [%'publish-view' @ @ @ ~]] ~]
@ -2241,7 +2236,6 @@
=/ note=(unit note) (~(get by notes.u.book) note-name) =/ note=(unit note) (~(get by notes.u.book) note-name)
?~ note not-found:gen ?~ note not-found:gen
=/ jon=json =/ jon=json
%+ frond %publish-response
o+(note-presentation:enjs u.book note-name u.note) o+(note-presentation:enjs u.book note-name u.note)
(json-response:gen (json-to-octs jon)) (json-response:gen (json-to-octs jon))
== ==

View File

@ -56,26 +56,23 @@
[%read (numb read.config)] [%read (numb read.config)]
== ==
:: ::
++ inbox
|= box=^inbox
^- json
%+ frond %chat-initial
%- pairs
%+ turn ~(tap by box)
|= [pax=^path =mailbox]
^- [cord json]
:- (spat pax)
%- pairs
:~ [%envelopes [%a (turn envelopes.mailbox envelope)]]
[%config (config config.mailbox)]
==
::
++ update ++ update
|= upd=^update |= upd=^update
^- json ^- json
%+ frond %chat-update %+ frond %chat-update
%- pairs %- pairs
:~ :~
?: ?=(%initial -.upd)
:- %initial
%- pairs
%+ turn ~(tap by inbox.upd)
|= [pax=^path =mailbox]
^- [cord json]
:- (spat pax)
%- pairs
:~ [%envelopes [%a (turn envelopes.mailbox envelope)]]
[%config (config config.mailbox)]
==
?: ?=(%message -.upd) ?: ?=(%message -.upd)
:- %message :- %message
%- pairs %- pairs

View File

@ -104,7 +104,6 @@
%permission-group-hook %permission-group-hook
%invite-store %invite-store
%invite-hook %invite-hook
%invite-view
%chat-store %chat-store
%chat-hook %chat-hook
%chat-view %chat-view

View File

@ -979,7 +979,7 @@
=/ actual-redirect ?:(=(u.redirect '') '/' u.redirect) =/ actual-redirect ?:(=(u.redirect '') '/' u.redirect)
%- handle-response %- handle-response
:* %start :* %start
:- status-code=307 :- status-code=303
^= headers ^= headers
:~ ['location' actual-redirect] :~ ['location' actual-redirect]
['set-cookie' cookie-line] ['set-cookie' cookie-line]

View File

@ -613,12 +613,12 @@
^- (hypo sign:http-server-gate) :- *type ^- (hypo sign:http-server-gate) :- *type
:* %g %unto %fact :* %g %unto %fact
%http-response-header %http-response-header
!>([307 ['location' '/~/login?redirect=/~landscape/inner-path']~]) !>([303 ['location' '/~/login?redirect=/~landscape/inner-path']~])
== ==
== ==
^= expected-move ^= expected-move
:~ :* duct=~[/http-blah] %give %response :~ :* duct=~[/http-blah] %give %response
[%start [307 ['location' '/~/login?redirect=/~landscape/inner-path']~] ~ %.n] [%start [303 ['location' '/~/login?redirect=/~landscape/inner-path']~] ~ %.n]
== == == == == ==
:: the browser then fetches the login page :: the browser then fetches the login page
:: ::
@ -2152,7 +2152,7 @@
%give %give
%response %response
%start %start
:- 307 :- 303
:~ ['location' '/~landscape'] :~ ['location' '/~landscape']
:- 'set-cookie' :- 'set-cookie'
'urbauth-~nul=0v3.q0p7t.mlkkq.cqtto.p0nvi.2ieea; Path=/; Max-Age=604800' 'urbauth-~nul=0v3.q0p7t.mlkkq.cqtto.p0nvi.2ieea; Path=/; Max-Age=604800'

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
URBIT_PIERS: [ URBIT_PIERS: [
"/Users/user/ships/zod/home", "/Users/user/ships/zod/home",
] ],
herb: false
}; };

View File

@ -2,6 +2,41 @@ const path = require('path');
// const HtmlWebpackPlugin = require('html-webpack-plugin'); // const HtmlWebpackPlugin = require('html-webpack-plugin');
// const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const urbitrc = require('./urbitrc'); const urbitrc = require('./urbitrc');
const fs = require('fs');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
function copyFile(src,dest) {
return new Promise((res,rej) =>
fs.copyFile(src,dest, err => err ? rej(err) : res()));
}
class UrbitShipPlugin {
constructor(urbitrc) {
this.piers = urbitrc.URBIT_PIERS;
this.herb = urbitrc.herb || false;
}
apply(compiler) {
compiler.hooks.afterCompile.tapPromise(
'UrbitShipPlugin',
async (compilation) => {
const src = path.resolve(compiler.options.output.path, 'index.js');
return Promise.all(this.piers.map(pier => {
const dst = path.resolve(pier, 'app/landscape/js/index.js');
copyFile(src, dst).then(() => {
if(!this.herb) {
return;
}
pier = pier.split('/');
const desk = pier.pop();
return exec(`herb -p hood -d '+hood/commit %${desk}' ${pier.join('/')}`);
});
}));
}
)
}
}
module.exports = { module.exports = {
mode: 'development', mode: 'development',
@ -49,16 +84,18 @@ module.exports = {
// historyApiFallback: true // historyApiFallback: true
// }, // },
plugins: [ plugins: [
new UrbitShipPlugin(urbitrc)
// new CleanWebpackPlugin(), // new CleanWebpackPlugin(),
// new HtmlWebpackPlugin({ // new HtmlWebpackPlugin({
// title: 'Hot Module Replacement', // title: 'Hot Module Replacement',
// template: './public/index.html', // template: './public/index.html',
// }), // }),
], ],
watch: true,
output: { output: {
filename: 'index.js', filename: 'index.js',
chunkFilename: 'index.js', chunkFilename: 'index.js',
path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/landscape/', 'js'), path: path.resolve(__dirname, '../dist'),
publicPath: '/' publicPath: '/'
}, },
optimization: { optimization: {

View File

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { BrowserRouter as Router, Route, Link, withRouter } from 'react-router-dom'; import { BrowserRouter as Router, Route, withRouter, Switch } from 'react-router-dom';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import './css/indigo-static.css'; import './css/indigo-static.css';
import './css/fonts.css'; import './css/fonts.css';
@ -8,11 +8,13 @@ import { light } from '@tlon/indigo-react';
import LaunchApp from './apps/launch/app'; import LaunchApp from './apps/launch/app';
import ChatApp from './apps/chat/app'; import ChatApp from './apps/chat/app';
import DojoApp from './apps/dojo/app'; import DojoApp from './apps/dojo/app';
import StatusBar from './components/StatusBar';
import GroupsApp from './apps/groups/app'; import GroupsApp from './apps/groups/app';
import LinksApp from './apps/links/app'; import LinksApp from './apps/links/app';
import PublishApp from './apps/publish/app'; import PublishApp from './apps/publish/app';
import StatusBar from './components/StatusBar';
import NotFound from './components/404';
import GlobalStore from './store/global'; import GlobalStore from './store/global';
import GlobalSubscription from './subscription/global'; import GlobalSubscription from './subscription/global';
import GlobalApi from './api/global'; import GlobalApi from './api/global';
@ -71,48 +73,64 @@ export default class App extends React.Component {
api={this.api} api={this.api}
/> />
<div> <div>
<Route exact path="/" render={ p => ( <Switch>
<Route exact path="/"
render={ p => (
<LaunchApp <LaunchApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route path="/~chat" render={ p => ( <Route path="/~chat" render={ p => (
<ChatApp <ChatApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route path="/~dojo" render={ p => ( <Route path="/~dojo" render={ p => (
<DojoApp <DojoApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route path="/~groups" render={ p => ( <Route path="/~groups" render={ p => (
<GroupsApp <GroupsApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route path="/~link" render={ p => ( <Route path="/~link" render={ p => (
<LinksApp <LinksApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route path="/~publish" render={ p => ( <Route path="/~publish" render={ p => (
<PublishApp <PublishApp
ship={this.ship} ship={this.ship}
channel={channel} channel={channel}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
{...p} /> {...p}
)} /> />
)}
/>
<Route component={NotFound} />
</Switch>
</div> </div>
</Router> </Router>
</Root> </Root>

View File

@ -1,4 +1,5 @@
import BaseApi from './base'; import BaseApi from './base';
import { uuid } from '../lib/util';
export default class ChatApi { export default class ChatApi {
constructor(ship, channel, store) { constructor(ship, channel, store) {
@ -36,6 +37,7 @@ export default class ChatApi {
this.metadata = { this.metadata = {
add: helper.metadataAdd.bind(helper) add: helper.metadataAdd.bind(helper)
}; };
this.sidebarToggle = helper.sidebarToggle.bind(helper);
} }
} }
@ -66,7 +68,7 @@ class PrivateHelper extends BaseApi {
addPendingMessage(msg) { addPendingMessage(msg) {
if (this.store.state.pendingMessages.has(msg.path)) { if (this.store.state.pendingMessages.has(msg.path)) {
this.store.state.pendingMessages.get(msg.path).push(msg.envelope); this.store.state.pendingMessages.get(msg.path).unshift(msg.envelope);
} else { } else {
this.store.state.pendingMessages.set(msg.path, [msg.envelope]); this.store.state.pendingMessages.set(msg.path, [msg.envelope]);
} }
@ -203,5 +205,18 @@ class PrivateHelper extends BaseApi {
}); });
} }
sidebarToggle() {
let sidebarBoolean = true;
if (this.store.state.sidebarShown === true) {
sidebarBoolean = false;
}
this.store.handleEvent({
data: {
local: {
sidebarToggle: sidebarBoolean
}
}
});
}
} }

View File

@ -21,7 +21,11 @@ export default class GroupsApi {
this.group = { this.group = {
add: helper.groupAdd.bind(helper), add: helper.groupAdd.bind(helper),
delete: helper.groupRemove.bind(helper) remove: helper.groupRemove.bind(helper)
};
this.metadata = {
add: helper.metadataAdd.bind(helper)
}; };
this.invite = { this.invite = {

View File

@ -24,6 +24,14 @@ class PrivateHelper extends BaseApi {
launchChangeIsShown(name, isShown = true) { launchChangeIsShown(name, isShown = true) {
this.launchAction({ 'change-is-shown': { name, isShown }}); this.launchAction({ 'change-is-shown': { name, isShown }});
} }
clockAction(latlng) {
return this.action('clock', 'json', latlng);
}
weatherAction(latlng) {
return this.action('weather', 'json', latlng);
}
} }
export default class LaunchApi { export default class LaunchApi {
@ -40,6 +48,10 @@ export default class LaunchApi {
changeFirstTime: helper.launchChangeFirstTime.bind(helper), changeFirstTime: helper.launchChangeFirstTime.bind(helper),
changeIsShown: helper.launchChangeIsShown.bind(helper) changeIsShown: helper.launchChangeIsShown.bind(helper)
}; };
this.clock = { action: helper.clockAction.bind(helper) };
this.weather ={ action: helper.weatherAction.bind(helper) };
} }
} }

View File

@ -1,9 +1,7 @@
import _ from 'lodash';
import { stringToTa } from '../lib/util'; import { stringToTa } from '../lib/util';
import BaseApi from './base'; import BaseApi from './base';
export default class LinksApi extends BaseApi { export default class LinksApi extends BaseApi {
constructor(ship, channel, store) { constructor(ship, channel, store) {
super(ship, channel, store); super(ship, channel, store);

View File

@ -1,12 +1,15 @@
import BaseApi from './base'; import BaseApi from './base';
export default class PublishApi extends BaseApi { export default class PublishApi extends BaseApi {
handleEvent(data) {
this.store.handleEvent({ data: { 'publish-response' : data } });
}
fetchNotebooks() { fetchNotebooks() {
fetch('/publish-view/notebooks.json') fetch('/publish-view/notebooks.json')
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.handleEvent({ this.handleEvent({
type: 'notebooks', type: 'notebooks',
data: json data: json
}); });
@ -17,7 +20,7 @@ export default class PublishApi extends BaseApi {
fetch(`/publish-view/${host}/${book}.json`) fetch(`/publish-view/${host}/${book}.json`)
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.handleEvent({ this.handleEvent({
type: 'notebook', type: 'notebook',
data: json, data: json,
host: host, host: host,
@ -30,7 +33,7 @@ export default class PublishApi extends BaseApi {
fetch(`/publish-view/${host}/${book}/${note}.json`) fetch(`/publish-view/${host}/${book}/${note}.json`)
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.handleEvent({ this.handleEvent({
type: 'note', type: 'note',
data: json, data: json,
host: host, host: host,
@ -44,7 +47,7 @@ export default class PublishApi extends BaseApi {
fetch(`/publish-view/notes/${host}/${book}/${start}/${length}.json`) fetch(`/publish-view/notes/${host}/${book}/${start}/${length}.json`)
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.handleEvent({ this.handleEvent({
type: 'notes-page', type: 'notes-page',
data: json, data: json,
host: host, host: host,
@ -59,7 +62,7 @@ export default class PublishApi extends BaseApi {
fetch(`/publish-view/comments/${host}/${book}/${note}/${start}/${length}.json`) fetch(`/publish-view/comments/${host}/${book}/${note}/${start}/${length}.json`)
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.handleEvent({ this.handleEvent({
type: 'comments-page', type: 'comments-page',
data: json, data: json,
host: host, host: host,
@ -71,15 +74,28 @@ export default class PublishApi extends BaseApi {
}); });
} }
groupAction(act) {
return this.action('group-store', 'group-action', act);
}
inviteAction(act) {
return this.action('invite-store', 'invite-action', act);
}
publishAction(act) {
return this.action('publish', 'publish-action', act);
}
sidebarToggle() { sidebarToggle() {
let sidebarBoolean = true; let sidebarBoolean = true;
if (this.store.state.sidebarShown === true) { if (this.store.state.sidebarShown === true) {
sidebarBoolean = false; sidebarBoolean = false;
} }
this.store.handleEvent({ this.store.handleEvent({
type: 'local',
data: { data: {
'sidebarToggle': sidebarBoolean local: {
sidebarToggle: sidebarBoolean
}
} }
}); });
} }

View File

@ -32,7 +32,7 @@ export default class ChatApp extends React.Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Chat'; document.title = 'OS1 - Chat';
// preload spinner asset // preload spinner asset
new Image().src = '/~landscape/img/Spinner.png'; new Image().src = '/~landscape/img/Spinner.png';
@ -74,7 +74,7 @@ export default class ChatApp extends React.Component {
}); });
if (totalUnreads !== this.totalUnreads) { if (totalUnreads !== this.totalUnreads) {
document.title = totalUnreads > 0 ? `Chat - (${totalUnreads})` : 'Chat'; document.title = totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : 'OS1 - Chat';
this.totalUnreads = totalUnreads; this.totalUnreads = totalUnreads;
} }
@ -310,7 +310,7 @@ export default class ChatApp extends React.Component {
> >
<MemberScreen <MemberScreen
{...props} {...props}
api={api} api={this.api}
station={station} station={station}
association={association} association={association}
permission={permission} permission={permission}
@ -361,7 +361,7 @@ export default class ChatApp extends React.Component {
permissions={state.permissions || {}} permissions={state.permissions || {}}
contacts={state.contacts || {}} contacts={state.contacts || {}}
associations={associations.contacts} associations={associations.contacts}
api={api} api={this.api}
inbox={state.inbox} inbox={state.inbox}
popout={popout} popout={popout}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}

View File

@ -493,9 +493,9 @@ ref={(e) => {
style={{ height: 48 }} style={{ height: 48 }}
> >
<SidebarSwitcher <SidebarSwitcher
sidebarShown={this.props.sidebarShown} sidebarShown={props.sidebarShown}
popout={this.props.popout} popout={props.popout}
api={this.props.api} api={props.api}
/> />
<Link to={'/~chat/' + isinPopout + 'room' + props.station} <Link to={'/~chat/' + isinPopout + 'room' + props.station}
className="pt2 white-d" className="pt2 white-d"
@ -513,7 +513,7 @@ ref={(e) => {
station={props.station} station={props.station}
numPeers={group.length} numPeers={group.length}
isOwner={deSig(props.match.params.ship) === window.ship} isOwner={deSig(props.match.params.ship) === window.ship}
popout={this.props.popout} popout={props.popout}
api={props.api} api={props.api}
/> />
</div> </div>

View File

@ -108,7 +108,7 @@ export class SettingsScreen extends Component {
if (chatOwner) { if (chatOwner) {
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
props.api.metadataAdd( props.api.metadata.add(
association['app-path'], association['app-path'],
association['group-path'], association['group-path'],
association.metadata.title, association.metadata.title,
@ -272,16 +272,16 @@ export class SettingsScreen extends Component {
<input <input
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' + 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'} 'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
value={this.state.title} value={state.title}
disabled={!chatOwner} disabled={!chatOwner}
onChange={this.changeTitle} onChange={this.changeTitle}
onBlur={() => { onBlur={() => {
if (chatOwner) { if (chatOwner) {
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
props.api.metadataAdd( props.api.metadata.add(
association['app-path'], association['app-path'],
association['group-path'], association['group-path'],
this.state.title, state.title,
association.metadata.description, association.metadata.description,
association.metadata['date-created'], association.metadata['date-created'],
uxToHex(association.metadata.color) uxToHex(association.metadata.color)
@ -301,17 +301,17 @@ export class SettingsScreen extends Component {
<input <input
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' + 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'} 'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
value={this.state.description} value={state.description}
disabled={!chatOwner} disabled={!chatOwner}
onChange={this.changeDescription} onChange={this.changeDescription}
onBlur={() => { onBlur={() => {
if (chatOwner) { if (chatOwner) {
this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => {
props.api.metadataAdd( props.api.metadata.add(
association['app-path'], association['app-path'],
association['group-path'], association['group-path'],
association.metadata.title, association.metadata.title,
this.state.description, state.description,
association.metadata['date-created'], association.metadata['date-created'],
uxToHex(association.metadata.color) uxToHex(association.metadata.color)
).then(() => { ).then(() => {
@ -339,7 +339,7 @@ export class SettingsScreen extends Component {
<input <input
className={'pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' + 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'} 'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
value={this.state.color} value={state.color}
disabled={!chatOwner} disabled={!chatOwner}
onChange={this.changeColor} onChange={this.changeColor}
onBlur={this.submitColor} onBlur={this.submitColor}
@ -400,7 +400,9 @@ export class SettingsScreen extends Component {
/> />
</div> </div>
<div className="w-100 pl3 mt4 cf"> <div className="w-100 pl3 mt4 cf">
<Spinner awaiting={this.state.awaiting} classes="absolute right-2 bottom-2 ba pa2 b--gray1-d" text={this.state.type} /> <Spinner awaiting={state.awaiting}
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
text={state.type} />
</div> </div>
</div> </div>
); );
@ -477,7 +479,9 @@ export class SettingsScreen extends Component {
{this.renderGroupify()} {this.renderGroupify()}
{this.renderDelete()} {this.renderDelete()}
{this.renderMetadataSettings()} {this.renderMetadataSettings()}
<Spinner awaiting={this.state.awaiting} classes="absolute right-2 bottom-2 ba pa2 b--gray1-d" text={this.state.type} /> <Spinner awaiting={state.awaiting}
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
text={state.type} />
</div> </div>
</div> </div>
); );

View File

@ -14,6 +14,7 @@ export class Sidebar extends Component {
dmOverlay: false dmOverlay: false
}; };
} }
onClickNew() { onClickNew() {
this.props.history.push('/~chat/new'); this.props.history.push('/~chat/new');
} }
@ -37,10 +38,14 @@ export class Sidebar extends Component {
const selectedGroups = props.selectedGroups ? props.selectedGroups : []; const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const associations = const contactAssoc =
(props.associations && 'contacts' in props.associations) (props.associations && 'contacts' in props.associations)
? alphabetiseAssociations(props.associations.contacts) : {}; ? alphabetiseAssociations(props.associations.contacts) : {};
const chatAssoc =
(props.associations && 'chat' in props.associations)
? alphabetiseAssociations(props.associations.chat) : {};
const groupedChannels = {}; const groupedChannels = {};
Object.keys(props.inbox).map((box) => { Object.keys(props.inbox).map((box) => {
if (box.startsWith('/~/')) { if (box.startsWith('/~/')) {
@ -51,16 +56,18 @@ export class Sidebar extends Component {
} else { } else {
groupedChannels['/~/'] = [box]; groupedChannels['/~/'] = [box];
} }
} } else {
const path = props.associations.chat[box] const path = chatAssoc[box]
? props.associations.chat[box]['group-path'] : box; ? chatAssoc[box]['group-path'] : box;
if (path in associations) {
if (groupedChannels[path]) { if (path in contactAssoc) {
const array = groupedChannels[path]; if (groupedChannels[path]) {
array.push(box); const array = groupedChannels[path];
groupedChannels[path] = array; array.push(box);
} else { groupedChannels[path] = array;
groupedChannels[path] = [box]; } else {
groupedChannels[path] = [box];
}
} }
} }
}); });
@ -77,7 +84,7 @@ export class Sidebar extends Component {
); );
}); });
const groupedItems = Object.keys(associations) const groupedItems = Object.keys(contactAssoc)
.filter(each => (groupedChannels[each] || []).length !== 0) .filter(each => (groupedChannels[each] || []).length !== 0)
.filter((each) => { .filter((each) => {
if (selectedGroups.length === 0) { if (selectedGroups.length === 0) {
@ -94,8 +101,8 @@ export class Sidebar extends Component {
<GroupItem <GroupItem
key={i} key={i}
index={i} index={i}
association={associations[each]} association={contactAssoc[each]}
chatMetadata={props.associations['chat']} chatMetadata={chatAssoc}
channels={channels} channels={channels}
inbox={props.inbox} inbox={props.inbox}
station={props.station} station={props.station}
@ -108,7 +115,7 @@ export class Sidebar extends Component {
groupedItems.push( groupedItems.push(
<GroupItem <GroupItem
association={'/~/'} association={'/~/'}
chatMetadata={props.associations['chat']} chatMetadata={chatAssoc}
channels={groupedChannels['/~/']} channels={groupedChannels['/~/']}
inbox={props.inbox} inbox={props.inbox}
station={props.station} station={props.station}

View File

@ -51,7 +51,7 @@ h2 {
} }
.clamp-attachment { .clamp-attachment {
overflow: scroll; overflow: auto;
max-height: 10em; max-height: 10em;
max-width: 100%; max-width: 100%;
} }

View File

@ -28,7 +28,7 @@ export default class DojoApp extends Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Dojo'; document.title = 'OS1 - Dojo';
const channel = new this.props.channel(); const channel = new this.props.channel();
this.api = new Api(this.props.ship, channel); this.api = new Api(this.props.ship, channel);
@ -47,7 +47,7 @@ export default class DojoApp extends Component {
render() { render() {
return ( return (
<div <div
className="bg-white bg-gray1-d" className="bg-white bg-gray0-d"
style={{ height: 'calc(100vh - 45px)' }} style={{ height: 'calc(100vh - 45px)' }}
> >
<Route <Route
@ -72,7 +72,7 @@ export default class DojoApp extends Component {
<div <div
className={ className={
'pa3 bg-white bg-gray0-d black white-d mono w-100 f8 relative' + 'pa3 bg-white bg-gray0-d black white-d mono w-100 f8 relative' +
' h-100-m40-s b--gray2 br1 flex-auto ' + ' h-100-m40-s b--gray2 br1 flex-auto flex flex-column ' +
popoutClasses popoutClasses
} }
style={{ style={{

View File

@ -8,8 +8,8 @@ export class History extends Component {
render() { render() {
return ( return (
<div <div
className="relative flex flex-column-reverse overflow-container flex-auto" className="h-100 relative flex flex-column-reverse overflow-container flex-auto"
style={{ height: 'calc(100% - 1rem)', resize: 'none' }} style={{ resize: 'none' }}
> >
<div style={{ marginTop: 'auto' }}> <div style={{ marginTop: 'auto' }}>
{this.props.commandLog.map((text, index) => { {this.props.commandLog.map((text, index) => {

View File

@ -7,7 +7,7 @@ export class Popout extends Component {
: 'dib-m dib-l dib-xl'; : 'dib-m dib-l dib-xl';
return ( return (
<div <div
className="dib absolute z-2" className="db tr z-2"
style={{ style={{
right: 16, right: 16,
top: 16 top: 16

View File

@ -23,7 +23,7 @@ export default class GroupsApp extends Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Groups'; document.title = 'OS1 - Groups';
// preload spinner asset // preload spinner asset
new Image().src = '/~landscape/img/Spinner.png'; new Image().src = '/~landscape/img/Spinner.png';
@ -121,8 +121,8 @@ export default class GroupsApp extends Component {
const detail = Boolean(props.match.url.includes('/detail')); const detail = Boolean(props.match.url.includes('/detail'));
const settings = Boolean(props.match.url.includes('/settings')); const settings = Boolean(props.match.url.includes('/settings'));
const association = (associations[groupPath]) const association = (associations.contacts?.[groupPath])
? associations[groupPath] ? associations.contacts[groupPath]
: {}; : {};
return ( return (
@ -152,6 +152,7 @@ export default class GroupsApp extends Component {
group={group} group={group}
activeDrawer={(detail || settings) ? 'detail' : 'contacts'} activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
settings={settings} settings={settings}
associations={associations}
api={this.api} api={this.api}
{...props} {...props}
/> />

View File

@ -141,12 +141,10 @@ export class ContactCard extends Component {
type: 'Saving to group' type: 'Saving to group'
}, },
() => { () => {
props.api props.api.contactHook.edit(props.path, ship, {
.contactEdit(props.path, ship, { avatar: {
avatar: { url: state.avatarToSet
url: state.avatarToSet }})
}
})
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -163,8 +161,8 @@ export class ContactCard extends Component {
if (hexTest && hexTest[1] !== currentColor && !props.share) { if (hexTest && hexTest[1] !== currentColor && !props.share) {
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, `~${props.ship}`, { color: hexTest[1] }) props.path, `~${props.ship}`, { color: hexTest[1] })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -182,8 +180,8 @@ export class ContactCard extends Component {
const emailTestResult = emailTest.exec(state.emailToSet); const emailTestResult = emailTest.exec(state.emailToSet);
if (emailTestResult) { if (emailTestResult) {
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, ship, { email: state.emailToSet }) props.path, ship, { email: state.emailToSet })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -199,8 +197,8 @@ export class ContactCard extends Component {
return false; return false;
} }
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, ship, { nickname: state.nickNameToSet }) props.path, ship, { nickname: state.nickNameToSet })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -216,8 +214,8 @@ export class ContactCard extends Component {
return false; return false;
} }
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, ship, { notes: state.notesToSet }) props.path, ship, { notes: state.notesToSet })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -234,8 +232,8 @@ export class ContactCard extends Component {
const phoneTestResult = phoneTest.exec(state.phoneToSet); const phoneTestResult = phoneTest.exec(state.phoneToSet);
if (phoneTestResult) { if (phoneTestResult) {
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, ship, { phone: state.phoneToSet }) props.path, ship, { phone: state.phoneToSet })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -253,8 +251,8 @@ export class ContactCard extends Component {
const websiteTestResult = websiteTest.exec(state.websiteToSet); const websiteTestResult = websiteTest.exec(state.websiteToSet);
if (websiteTestResult) { if (websiteTestResult) {
this.setState({ awaiting: true, type: 'Saving to group' }, () => { this.setState({ awaiting: true, type: 'Saving to group' }, () => {
props.api props.api.contactHook.edit(
.contactEdit(props.path, ship, { website: state.websiteToSet }) props.path, ship, { website: state.websiteToSet })
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
@ -266,9 +264,10 @@ export class ContactCard extends Component {
this.setState( this.setState(
{ emailToSet: '', awaiting: true, type: 'Removing from group' }, { emailToSet: '', awaiting: true, type: 'Removing from group' },
() => { () => {
props.api.contactEdit(props.path, ship, { email: '' }).then(() => { props.api.contactHook.edit(props.path, ship, { email: '' })
this.setState({ awaiting: false }); .then(() => {
}); this.setState({ awaiting: false });
});
} }
); );
break; break;
@ -277,9 +276,10 @@ export class ContactCard extends Component {
this.setState( this.setState(
{ nicknameToSet: '', awaiting: true, type: 'Removing from group' }, { nicknameToSet: '', awaiting: true, type: 'Removing from group' },
() => { () => {
props.api.contactEdit(props.path, ship, { nickname: '' }).then(() => { props.api.contactHook.edit(props.path, ship, { nickname: '' })
this.setState({ awaiting: false }); .then(() => {
}); this.setState({ awaiting: false });
});
} }
); );
break; break;
@ -288,7 +288,7 @@ export class ContactCard extends Component {
this.setState( this.setState(
{ phoneToSet: '', awaiting: true, type: 'Removing from group' }, { phoneToSet: '', awaiting: true, type: 'Removing from group' },
() => { () => {
props.api.contactEdit(props.path, ship, { phone: '' }).then(() => { props.api.contactHook.edit(props.path, ship, { phone: '' }).then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
} }
@ -299,7 +299,7 @@ export class ContactCard extends Component {
this.setState( this.setState(
{ websiteToSet: '', awaiting: true, type: 'Removing from group' }, { websiteToSet: '', awaiting: true, type: 'Removing from group' },
() => { () => {
props.api.contactEdit(props.path, ship, { website: '' }).then(() => { props.api.contactHook.edit(props.path, ship, { website: '' }).then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
} }
@ -314,7 +314,7 @@ export class ContactCard extends Component {
type: 'Removing from group' type: 'Removing from group'
}, },
() => { () => {
props.api.contactEdit(props.path, ship, { avatar: null }).then(() => { props.api.contactHook.edit(props.path, ship, { avatar: null }).then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
} }
@ -325,7 +325,7 @@ export class ContactCard extends Component {
this.setState( this.setState(
{ notesToSet: '', awaiting: true, type: 'Removing from group' }, { notesToSet: '', awaiting: true, type: 'Removing from group' },
() => { () => {
props.api.contactEdit(props.path, ship, { notes: '' }).then(() => { props.api.contactHook.edit(props.path, ship, { notes: '' }).then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });
} }
@ -519,9 +519,10 @@ export class ContactCard extends Component {
key={'avatar' + currentColor} key={'avatar' + currentColor}
/> />
<div <div
className="tl mt4 mb4 w-auto ml-auto mr-auto" className="tc mt4 mb4 w-auto ml-auto mr-auto"
style={{ width: 'fit-content' }} style={{ width: 'fit-content' }}
> >
<div className="tl dib">
<p className="f9 gray2 lh-copy">Sigil Color</p> <p className="f9 gray2 lh-copy">Sigil Color</p>
<textarea <textarea
className={ className={
@ -543,6 +544,7 @@ export class ContactCard extends Component {
width: 114 width: 114
}} }}
></textarea> ></textarea>
</div>
</div> </div>
<div className="w-100 pt8 pb8 lh-copy tl"> <div className="w-100 pt8 pb8 lh-copy tl">
<p className="f9 gray2">Ship Name</p> <p className="f9 gray2">Ship Name</p>

View File

@ -99,7 +99,7 @@ export class ContactSidebar extends Component {
style={{ paddingTop: 6 }} style={{ paddingTop: 6 }}
onClick={() => { onClick={() => {
this.setState({ awaiting: true }, (() => { this.setState({ awaiting: true }, (() => {
props.api.groupRemove(props.path, [`~${member}`]) props.api.group.remove(props.path, [`~${member}`])
.then(() => { .then(() => {
this.setState({ awaiting: false }); this.setState({ awaiting: false });
}); });

View File

@ -18,11 +18,10 @@ export class GroupDetail extends Component {
componentDidMount() { componentDidMount() {
const { props } = this; const { props } = this;
const channelPath = `${props.path}/contacts${props.path}`; if (props.association.metadata) {
if ((props.association) && (props.association[channelPath])) {
this.setState({ this.setState({
title: props.association[channelPath].metadata.title, title: props.association.metadata.title,
description: props.association[channelPath].metadata.description description: props.association.metadata.description
}); });
} }
} }
@ -30,11 +29,10 @@ export class GroupDetail extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { props } = this; const { props } = this;
if (prevProps !== this.props) { if (prevProps !== this.props) {
const channelPath = `${props.path}/contacts${props.path}`; if (props.association.metadata) {
if ((props.association) && (props.association[channelPath])) {
this.setState({ this.setState({
title: props.association[channelPath].metadata.title, title: props.association.metadata.title,
description: props.association[channelPath].metadata.description description: props.association.metadata.description
}); });
} }
} }
@ -54,78 +52,74 @@ export class GroupDetail extends Component {
const responsiveClass = const responsiveClass =
props.activeDrawer === 'detail' ? 'db ' : 'dn db-ns '; props.activeDrawer === 'detail' ? 'db ' : 'dn db-ns ';
const isEmpty = (Object.keys(props.association).length === 0) || let channelList = [];
((Object.keys(props.association).length === 1) &&
(Object.keys(props.association)[0].includes('contacts')));
let channelList = (<div />); Object.keys(props.associations).filter((app) => {
return app !== 'contacts';
}).map((app) => {
Object.keys(props.associations[app]).filter((channel) => {
return props.associations[app][channel]['group-path'] === props.association['group-path'];
})
.map((channel) => {
const channelObj = props.associations[app][channel];
const title =
channelObj.metadata?.title || channelObj['app-path'] || '';
channelList = Object.keys(props.association).sort((a, b) => { const color = uxToHex(channelObj.metadata?.color) || '000000';
const aChannel = props.association[a]; const channelPath = channelObj['app-path'];
const bChannel = props.association[b]; const link = `/~${app}/join${channelPath}`;
return(
const aTitle = aChannel.metadata.title || a; channelList.push({
const bTitle = bChannel.metadata.title || b; title: title,
color: color,
return aTitle.toLowerCase().localeCompare(bTitle.toLowerCase()); app: app.charAt(0).toUpperCase() + app.slice(1),
}).map((key) => { link: link
const channel = props.association[key]; })
if (!('metadata' in channel)) { );
return <div key={channel} />; });
}
if (channel['app-name'] === 'contacts') {
return <div key={channel} />;
}
const title = channel.metadata.title || channel['app-path'] || '';
const color = uxToHex(channel.metadata.color) || '000000';
let app = channel['app-name'] || 'Unknown';
const channelPath = channel['app-path'];
const link = `/~${app}/join${channelPath}`;
app = app.charAt(0).toUpperCase() + app.slice(1);
const overlay = {
r: parseInt(color.slice(0, 2), 16),
g: parseInt(color.slice(2, 4), 16),
b: parseInt(color.slice(4, 6), 16)
};
const tile = (app === 'Unknown')
? <div className="dib ba pa1" style={{
backgroundColor: `#${color}`,
borderColor: `#${color}`,
height: 24,
width: 24 }}
/>
: <div className="ba" style={{
borderColor: `#${color}`,
backgroundColor: `rgba(${overlay.r}, ${overlay.g}, ${overlay.b}, 0.25)`
}}
>
<img
src={`/~groups/img/${app}.png`}
className="dib invert-d pa1 v-mid"
style={{ height: 26, width: 26 }}
/>
</div>;
return (
<li key={channelPath} className="f9 list flex pv1 w-100">
{tile}
<div className="flex flex-column flex-auto">
<p className="f9 inter ml2 w-100">{title}</p>
<p className="f9 inter ml2 w-100">
<span className="f9 di mr2 inter">{app}</span>
<Link className="f9 di green2" to={link}>
Open
</Link>
</p>
</div>
</li>
);
}); });
const isEmpty = (Boolean(channelList.length === 0));
if (channelList.length === 0) {
channelList = <div />;
} else {
channelList = channelList.sort((a, b) => {
return a.title.toLowerCase().localeCompare(b.title.toLowerCase());
}).map((each) => {
const overlay = {
r: parseInt(each.color.slice(0, 2), 16),
g: parseInt(each.color.slice(2, 4), 16),
b: parseInt(each.color.slice(4, 6), 16)
};
return (
<li key={each.link} className="f9 list flex pv1 w-100">
<div className="ba" style={{
borderColor: `#${each.color}`,
backgroundColor: `rgba(${overlay.r}, ${overlay.g}, ${overlay.b}, 0.25)`
}}
>
<img
src={`/~landscape/img/${each.app}.png`}
className="dib invert-d pa1 v-mid"
style={{ height: 26, width: 26 }}
/>
</div>
<div className="flex flex-column flex-auto">
<p className="f9 inter ml2 w-100">{each.title}</p>
<p className="f9 inter ml2 w-100">
<span className="f9 di mr2 inter">{each.app}</span>
<Link className="f9 di green2" to={each.link}>
Open
</Link>
</p>
</div>
</li>
);
});
}
let backLink = props.location.pathname; let backLink = props.location.pathname;
backLink = backLink.slice(0, props.location.pathname.indexOf('/detail')); backLink = backLink.slice(0, props.location.pathname.indexOf('/detail'));
@ -139,13 +133,12 @@ export class GroupDetail extends Component {
let title = props.path.substr(1); let title = props.path.substr(1);
let description = ''; let description = '';
const channel = `${props.path}/contacts${props.path}`; if (props.association?.metadata) {
if ((props.association) && (props.association[channel])) { title = (props.association.metadata.title !== '')
title = (props.association[channel].metadata.title !== '') ? props.association.metadata.title
? props.association[channel].metadata.title
: props.path.substr(1); : props.path.substr(1);
description = (props.association[channel].metadata.description !== '') description = (props.association.metadata.description !== '')
? props.association[channel].metadata.description ? props.association.metadata.description
: ''; : '';
} }
@ -181,10 +174,7 @@ export class GroupDetail extends Component {
const groupOwner = (deSig(props.match.params.ship) === window.ship); const groupOwner = (deSig(props.match.params.ship) === window.ship);
const channelPath = `${props.path}/contacts${props.path}`; const association = props.association;
const association = ((props.association) && (props.association[channelPath]))
? props.association[channelPath] : {};
const deleteButtonClasses = (groupOwner) ? 'b--red2 red2 pointer bg-gray0-d' : 'b--gray3 gray3 bg-gray0-d c-default'; const deleteButtonClasses = (groupOwner) ? 'b--red2 red2 pointer bg-gray0-d' : 'b--gray3 gray3 bg-gray0-d c-default';
@ -208,7 +198,7 @@ export class GroupDetail extends Component {
onBlur={() => { onBlur={() => {
if (groupOwner) { if (groupOwner) {
this.setState({ awaiting: true }, (() => { this.setState({ awaiting: true }, (() => {
props.api.metadataAdd( props.api.metadata.add(
association['app-path'], association['app-path'],
association['group-path'], association['group-path'],
this.state.title, this.state.title,
@ -237,7 +227,7 @@ export class GroupDetail extends Component {
onBlur={() => { onBlur={() => {
if (groupOwner) { if (groupOwner) {
this.setState({ awaiting: true }, (() => { this.setState({ awaiting: true }, (() => {
props.api.metadataAdd( props.api.metadata.add(
association['app-path'], association['app-path'],
association['group-path'], association['group-path'],
association.metadata.title, association.metadata.title,

View File

@ -69,33 +69,23 @@ export class GroupSidebar extends Component {
return true; return true;
} }
const selectedPaths = selectedGroups.map(((e) => { const selectedPaths = selectedGroups.map(((e) => {
return e[0]; return e[0];
})); }));
return (selectedPaths.includes(path)); return (selectedPaths.includes(path));
}) })
.sort((a, b) => { .sort((a, b) => {
let aName = a.substr(1); let aName = a.substr(1);
let bName = b.substr(1); let bName = b.substr(1);
const aChannel = `${a}/contacts${a}`; if (props.associations.contacts?.[a]?.metadata) {
const bChannel = `${b}/contacts${b}`;
if (
props.associations[a] &&
props.associations[a][aChannel] &&
props.associations[a][aChannel].metadata
) {
aName = aName =
props.associations[a][aChannel].metadata.title !== '' props.associations.contacts[a].metadata.title !== ''
? props.associations[a][aChannel].metadata.title ? props.associations.contacts[a].metadata.title
: a.substr(1); : a.substr(1);
} }
if ( if (props.associations.contacts?.[b]?.metadata) {
props.associations[b] &&
props.associations[b][bChannel] &&
props.associations[b][bChannel].metadata
) {
bName = bName =
props.associations[b][bChannel].metadata.title !== '' props.associations.contacts[b].metadata.title !== ''
? props.associations[b][bChannel].metadata.title ? props.associations.contacts[b].metadata.title
: b.substr(1); : b.substr(1);
} }
@ -104,15 +94,10 @@ export class GroupSidebar extends Component {
.map((path) => { .map((path) => {
let name = path.substr(1); let name = path.substr(1);
const selected = props.selected === path; const selected = props.selected === path;
const groupChannel = `${path}/contacts${path}`; if (props.associations.contacts?.[path]?.metadata) {
if (
props.associations[path] &&
props.associations[path][groupChannel] &&
props.associations[path][groupChannel].metadata
) {
name = name =
props.associations[path][groupChannel].metadata.title !== '' props.associations.contacts[path].metadata.title !== ''
? props.associations[path][groupChannel].metadata.title ? props.associations.contacts[path].metadata.title
: path.substr(1); : path.substr(1);
} }
return ( return (

View File

@ -23,7 +23,7 @@ export default class LaunchApp extends React.Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Home'; document.title = 'OS1 - Home';
// preload spinner asset // preload spinner asset
new Image().src = '/~landscape/img/Spinner.png'; new Image().src = '/~landscape/img/Spinner.png';

View File

@ -1,14 +1,12 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames';
import moment from 'moment'; import moment from 'moment';
import SunCalc from 'suncalc'; import SunCalc from 'suncalc';
import Tile from './tile'; import Tile from './tile';
const outerSize = 124; //tile size const innerSize = 124; // clock size
const innerSize = 124; //clock size
//polar to cartesian // polar to cartesian
// var ptc = function(r, theta) { // var ptc = function(r, theta) {
// return { // return {
// x: r * Math.cos(theta), // x: r * Math.cos(theta),
@ -16,77 +14,72 @@ const innerSize = 124; //clock size
// } // }
// } // }
let text = "#000000", background = "#ffffff"; let text = '#000000', background = '#ffffff';
let dark = window.matchMedia('(prefers-color-scheme: dark)'); const dark = window.matchMedia('(prefers-color-scheme: dark)');
if (dark.matches) { if (dark.matches) {
text = "#7f7f7f"; text = '#7f7f7f';
background = "#333"; background = '#333';
} }
function darkColors(dark) { function darkColors(dark) {
if (dark.matches) { if (dark.matches) {
text = "#7f7f7f"; text = '#7f7f7f';
background = "#333"; background = '#333';
} else { } else {
text = "#000000"; text = '#000000';
background = "#ffffff" background = '#ffffff';
} }
} }
dark.addListener(darkColors); dark.addListener(darkColors);
const toRelativeTime = (date, referenceTime, unit) => moment(date) const toRelativeTime = (date, referenceTime, unit) => moment(date)
.diff(referenceTime, unit) .diff(referenceTime, unit);
const minsToDegs = (mins) => { const minsToDegs = (mins) => {
// 1440 = total minutes in an earth day // 1440 = total minutes in an earth day
return (mins / 1440) * 360 return (mins / 1440) * 360;
} };
const clockwise = (deg, delta) => deg + delta const splitArc = (start, end) => end + ((start - end) * 0.5);
const anticlockwise = (deg, delta) => deg - delta
const splitArc = (start, end) => end + ((start - end) * 0.5)
const isOdd = n => Math.abs(n % 2) == 1; const isOdd = n => Math.abs(n % 2) == 1;
const radToDeg = (rad) => rad * (180 / Math.PI); const radToDeg = rad => rad * (180 / Math.PI);
const degToRad = (deg) => deg * (Math.PI / 180); const degToRad = deg => deg * (Math.PI / 180);
const convert = (date, referenceTime) => { const convert = (date, referenceTime) => {
return minsToDegs(toRelativeTime(date, referenceTime, 'minutes')) return minsToDegs(toRelativeTime(date, referenceTime, 'minutes'));
} };
const circle = (ctx, x, y, r, from, to, fill) => { const circle = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath(); ctx.beginPath();
ctx.arc( x, y, r, from, to, ); ctx.arc( x, y, r, from, to );
ctx.strokeStyle = 'rgba(0,0,0,0)'; ctx.strokeStyle = 'rgba(0,0,0,0)';
ctx.fillStyle = fill || 'rgba(0,0,0,0)'; ctx.fillStyle = fill || 'rgba(0,0,0,0)';
ctx.fill(); ctx.fill();
} };
const circleOutline = (ctx, x, y, r, from, to, stroke, lineWidth) => { const circleOutline = (ctx, x, y, r, from, to, stroke, lineWidth) => {
ctx.beginPath(); ctx.beginPath();
ctx.arc( x, y, r, from, to, ); ctx.arc( x, y, r, from, to );
ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = lineWidth; ctx.lineWidth = lineWidth;
ctx.strokeStyle = stroke || 'rgba(0,0,0,0)'; ctx.strokeStyle = stroke || 'rgba(0,0,0,0)';
ctx.stroke(); ctx.stroke();
} };
const arc = (ctx, x, y, r, from, to, fill) => { const arc = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath(); ctx.beginPath();
ctx.arc( x, y, r, from, to, ); ctx.arc( x, y, r, from, to );
ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.fillStyle = 'rgba(0,0,0,0)';
ctx.lineWidth = r * 2; ctx.lineWidth = r * 2;
ctx.strokeStyle = fill || 'rgba(0,0,0,0)'; ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
ctx.stroke(); ctx.stroke();
} };
const degArc = (ctx, x, y, r, from, to, fill) => { const degArc = (ctx, x, y, r, from, to, fill) => {
ctx.beginPath(); ctx.beginPath();
@ -95,17 +88,16 @@ const degArc = (ctx, x, y, r, from, to, fill) => {
ctx.lineWidth = r * 2; ctx.lineWidth = r * 2;
ctx.strokeStyle = fill || 'rgba(0,0,0,0)'; ctx.strokeStyle = fill || 'rgba(0,0,0,0)';
ctx.stroke(); ctx.stroke();
} };
class Clock extends React.Component { class Clock extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.animate = this.animate.bind(this); this.animate = this.animate.bind(this);
this.canvasRef = React.createRef(); this.canvasRef = React.createRef();
this.canvas = null; this.canvas = null;
this.angle = 0; this.angle = 0;
this.referenceTime = moment().startOf('day').subtract(6, 'hours') this.referenceTime = moment().startOf('day').subtract(6, 'hours');
this.state = { this.state = {
lat: 0, lat: 0,
lon: 0, lon: 0,
@ -119,23 +111,22 @@ class Clock extends React.Component {
night: 0, night: 0,
nightEnd: 0, nightEnd: 0,
nauticalDawn: 0, nauticalDawn: 0,
nauticalDusk: 0, nauticalDusk: 0
// sunriseStartTime = 1509967519, // sunriseStartTime = 1509967519,
// sunriseEndTime = 2500 + 1509967519, // sunriseEndTime = 2500 + 1509967519,
// sunsetStartTime = 1510003982, // sunsetStartTime = 1510003982,
// sunsetEndTime // sunsetEndTime
// moonPhase = 0.59, // moonPhase = 0.59,
} };
} }
initGeolocation() { initGeolocation() {
if (typeof this.props.data === 'string') { if (typeof this.props.data === 'string') {
const latlon = this.props.data.split(',') const latlon = this.props.data.split(',');
const lat = latlon[0] const lat = latlon[0];
const lon = latlon[1] const lon = latlon[1];
const suncalc = SunCalc.getTimes(new Date(), lat, lon) const suncalc = SunCalc.getTimes(new Date(), lat, lon);
const convertedSunCalc = { const convertedSunCalc = {
sunset: convert(suncalc.sunset, this.referenceTime), sunset: convert(suncalc.sunset, this.referenceTime),
@ -147,25 +138,24 @@ class Clock extends React.Component {
night: convert(suncalc.night, this.referenceTime), night: convert(suncalc.night, this.referenceTime),
nightEnd: convert(suncalc.nightEnd, this.referenceTime), nightEnd: convert(suncalc.nightEnd, this.referenceTime),
nauticalDawn: convert(suncalc.nauticalDawn, this.referenceTime), nauticalDawn: convert(suncalc.nauticalDawn, this.referenceTime),
nauticalDusk: convert(suncalc.nauticalDusk, this.referenceTime), nauticalDusk: convert(suncalc.nauticalDusk, this.referenceTime)
} };
this.setState({ this.setState({
lat, lat,
lon, lon,
...convertedSunCalc, ...convertedSunCalc,
geolocationSuccess: true, geolocationSuccess: true
}) });
} }
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (prevProps !== this.props) { if (prevProps !== this.props) {
this.initGeolocation() this.initGeolocation();
} }
} }
componentDidMount() { componentDidMount() {
this.canvas = initCanvas( this.canvas = initCanvas(
this.canvasRef, this.canvasRef,
@ -173,29 +163,27 @@ class Clock extends React.Component {
4 4
); );
this.initGeolocation() this.initGeolocation();
this.animate() this.animate();
} }
animate() { animate() {
window.setTimeout(() => window.requestAnimationFrame(this.animate), 1000) window.setTimeout(() => window.requestAnimationFrame(this.animate), 1000);
const { state } = this const { state } = this;
const time = new Date(); const time = new Date();
const ctx = this.canvas.getContext("2d"); const ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, ctx.width, ctx.height); ctx.clearRect(0, 0, ctx.width, ctx.height);
ctx.save(); ctx.save();
const ctr = innerSize / 2 const ctr = innerSize / 2;
// Sun+moon calculations // Sun+moon calculations
const dd = 4 const cx = ctr;
var cx = ctr const cy = ctr;
var cy = ctr this.angle = degToRad(convert(time, this.referenceTime));
this.angle = degToRad(convert(time, this.referenceTime)) const newX = cx + (ctr - 15) * Math.cos(this.angle);
var newX = cx + (ctr - 15) * Math.cos(this.angle); const newY = cy + (ctr - 15) * Math.sin(this.angle);
var newY = cy + (ctr - 15) * Math.sin(this.angle);
// Initial background // Initial background
circle( circle(
@ -206,7 +194,7 @@ class Clock extends React.Component {
-1, -1,
2 * Math.PI, 2 * Math.PI,
background background
) );
// Day // Day
degArc( degArc(
@ -276,7 +264,7 @@ class Clock extends React.Component {
0, 0,
2 * Math.PI, 2 * Math.PI,
'#FCC440' '#FCC440'
) );
// Sun circle border // Sun circle border
circleOutline( circleOutline(
@ -299,7 +287,7 @@ class Clock extends React.Component {
0, 0,
2 * Math.PI, 2 * Math.PI,
'#FFFFFF' '#FFFFFF'
) );
// Moon circle outline // Moon circle outline
circleOutline( circleOutline(
ctx, ctx,
@ -357,7 +345,7 @@ class Clock extends React.Component {
-1, -1,
2 * Math.PI, 2 * Math.PI,
background background
) );
// Center white circle border // Center white circle border
circleOutline( circleOutline(
@ -374,26 +362,27 @@ class Clock extends React.Component {
// Text for time and date // Text for time and date
const timeText = isOdd(time.getSeconds()) const timeText = isOdd(time.getSeconds())
? moment().format('h mm A') ? moment().format('h mm A')
: moment().format('h:mm A') : moment().format('h:mm A');
const dateText = moment().format('MMM Do') const dateText = moment().format('MMM Do');
ctx.textAlign = 'center' ctx.textAlign = 'center';
ctx.fillStyle = text ctx.fillStyle = text;
ctx.font = '12px Inter' ctx.font = '12px Inter';
ctx.fillText(timeText, ctr, ctr + 6 - 7) ctx.fillText(timeText, ctr, ctr + 6 - 7);
ctx.fillStyle = text ctx.fillStyle = text;
ctx.font = '12px Inter' ctx.font = '12px Inter';
ctx.fillText(dateText, ctr, ctr + 6 + 7) ctx.fillText(dateText, ctr, ctr + 6 + 7);
ctx.restore(); ctx.restore();
} }
render() { render() {
return <div style={{position:'relative'}}> return <div style={{ position:'relative' }}>
<canvas <canvas
style={{position:'absolute'}} style={{ position:'absolute' }}
ref={ canvasRef => this.canvasRef = canvasRef } ref={ canvasRef => this.canvasRef = canvasRef }
id="clock-canvas"/> id="clock-canvas"
</div> />
</div>;
} }
} }
@ -411,27 +400,15 @@ export default class ClockTile extends React.Component {
} }
render() { render() {
let data = !!this.props.data ? this.props.data : {}; const data = this.props.data ? this.props.data : {};
return this.renderWrapper(( return this.renderWrapper((
<Clock data={data}/> <Clock data={data} />
)); ));
} }
} }
const loadImg = (base64, cb) => new Promise(resolve => {
const img = new Image();
img.onload = () => resolve(cb(img));
img.onerror = () => reject('Error loading image');
img.src = base64;
});
const initCanvas = (canvas, size, ratio) => { const initCanvas = (canvas, size, ratio) => {
const { x, y } = size; const { x, y } = size;
let ctx = canvas.getContext('2d');
// let ratio = ctx.webkitBackingStorePixelRatio < 2 // let ratio = ctx.webkitBackingStorePixelRatio < 2
// ? window.devicePixelRatio // ? window.devicePixelRatio
// : 1; // : 1;
@ -439,7 +416,6 @@ const initCanvas = (canvas, size, ratio) => {
// default for high print resolution. // default for high print resolution.
// ratio = ratio * resMult; // ratio = ratio * resMult;
canvas.width = x * ratio; canvas.width = x * ratio;
canvas.height = y * ratio; canvas.height = y * ratio;
canvas.style.width = x + 'px'; canvas.style.width = x + 'px';
@ -448,5 +424,5 @@ const initCanvas = (canvas, size, ratio) => {
canvas.getContext('2d').scale(ratio, ratio); canvas.getContext('2d').scale(ratio, ratio);
return canvas; return canvas;
} };

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames';
import moment from 'moment'; import moment from 'moment';
import Tile from './tile'; import Tile from './tile';
@ -13,121 +12,123 @@ export default class WeatherTile extends React.Component {
error: false error: false
}; };
let api = props.api; this.api = props.api;
} }
// geolocation and manual input functions // geolocation and manual input functions
locationSubmit() { locationSubmit() {
navigator.geolocation.getCurrentPosition((res) => { navigator.geolocation.getCurrentPosition((res) => {
let latlng = `${res.coords.latitude},${res.coords.longitude}`; const latlng = `${res.coords.latitude},${res.coords.longitude}`;
this.setState({ this.setState({
latlng latlng
}, (err) => { }, (err) => {
console.log(err); console.log(err);
}, { maximumAge: Infinity, timeout: 10000 }); }, { maximumAge: Infinity, timeout: 10000 });
api.action("clock", "json", latlng); // this.api.clock.action(latlng);
api.action('weather', 'json', latlng); this.api.weather.action(latlng);
this.setState({ manualEntry: !this.state.manualEntry }); this.setState({ manualEntry: !this.state.manualEntry });
}); });
} }
manualLocationSubmit() { manualLocationSubmit() {
event.preventDefault(); event.preventDefault();
let gpsInput = document.getElementById('gps'); const gpsInput = document.getElementById('gps');
let latlngNoSpace = gpsInput.value.replace(/\s+/g, ''); const latlngNoSpace = gpsInput.value.replace(/\s+/g, '');
let latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g; const latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g;
if (latlngParse.test(latlngNoSpace)) { if (latlngParse.test(latlngNoSpace)) {
let latlng = latlngNoSpace; const latlng = latlngNoSpace;
this.setState({latlng}, (err) => {console.log(err)}, {maximumAge: Infinity, timeout: 10000}); this.setState({ latlng }, (err) => {
api.action("clock", "json", latlng); console.log(err);
api.action('weather', 'json', latlng); }, { maximumAge: Infinity, timeout: 10000 });
this.setState({manualEntry: !this.state.manualEntry}); // this.api.clock.action(latlng);
} this.api.weather.action(latlng);
else { this.setState({ manualEntry: !this.state.manualEntry });
this.setState({error: true}); } else {
this.setState({ error: true });
return false; return false;
} }
} }
// set appearance based on weather // set appearance based on weather
setColors(data) { setColors(data) {
let weatherStyle = { let weatherStyle = {
gradient1: "", gradient1: '',
gradient2: "", gradient2: '',
text: "" text: ''
}; };
switch (data.daily.icon) { switch (data.daily.icon) {
case "clear-day": case 'clear-day':
weatherStyle = { weatherStyle = {
gradient1: "#A5CEF0", gradient2: "#FEF4E0", text: "black" gradient1: '#A5CEF0', gradient2: '#FEF4E0', text: 'black'
}
break;
case "clear-night":
weatherStyle = {
gradient1: "#56668e", gradient2: "#000080", text: "white"
}
break;
case "rain":
weatherStyle = {
gradient1: "#b1b2b3", gradient2: "#b0c7ff", text: "black"
}; };
break; break;
case "snow": case 'clear-night':
weatherStyle = { weatherStyle = {
gradient1: "#eee", gradient2: "#f9f9f9", text: "black" gradient1: '#56668e', gradient2: '#000080', text: 'white'
}; };
break; break;
case "sleet": case 'rain':
weatherStyle = { weatherStyle = {
gradient1: "#eee", gradient2: "#f9f9f9", text: "black" gradient1: '#b1b2b3', gradient2: '#b0c7ff', text: 'black'
}; };
break; break;
case "wind": case 'snow':
weatherStyle = { weatherStyle = {
gradient1: "#eee", gradient2: "#fff", text: "black" gradient1: '#eee', gradient2: '#f9f9f9', text: 'black'
}; };
break; break;
case "fog": case 'sleet':
weatherStyle = { weatherStyle = {
gradient1: "#eee", gradient2: "#fff", text: "black" gradient1: '#eee', gradient2: '#f9f9f9', text: 'black'
}; };
break; break;
case "cloudy": case 'wind':
weatherStyle = { weatherStyle = {
gradient1: "#eee", gradient2: "#b1b2b3", text: "black" gradient1: '#eee', gradient2: '#fff', text: 'black'
}; };
break; break;
case "partly-cloudy-day": case 'fog':
weatherStyle = { weatherStyle = {
gradient1: "#fcc440", gradient2: "#b1b2b3", text: "black" gradient1: '#eee', gradient2: '#fff', text: 'black'
}; };
break; break;
case "partly-cloudy-night": case 'cloudy':
weatherStyle = { weatherStyle = {
gradient1: "#7f7f7f", gradient2: "#56668e", text: "white" gradient1: '#eee', gradient2: '#b1b2b3', text: 'black'
};
break;
case 'partly-cloudy-day':
weatherStyle = {
gradient1: '#fcc440', gradient2: '#b1b2b3', text: 'black'
};
break;
case 'partly-cloudy-night':
weatherStyle = {
gradient1: '#7f7f7f', gradient2: '#56668e', text: 'white'
}; };
break; break;
default: default:
weatherStyle = { weatherStyle = {
gradient1: "white", gradient2: "white", text: "black" gradient1: 'white', gradient2: 'white', text: 'black'
}; };
} }
return weatherStyle; return weatherStyle;
} }
// all tile views // all tile views
renderWrapper(child, renderWrapper(child,
weatherStyle = { gradient1: "white", gradient2: "white", text: "black" } weatherStyle = { gradient1: 'white', gradient2: 'white', text: 'black' }
) { ) {
return ( return (
<Tile> <Tile>
<div <div
className={"relative " + weatherStyle.text} className={'relative ' + weatherStyle.text}
style={{ style={{
width: 126, width: 126,
height: 126, height: 126,
background: `linear-gradient(135deg, ${weatherStyle.gradient1} 0%,` + background: `linear-gradient(135deg, ${weatherStyle.gradient1} 0%,` +
`${weatherStyle.gradient2} 45%, ${weatherStyle.gradient2} 65%,` + `${weatherStyle.gradient2} 45%, ${weatherStyle.gradient2} 65%,` +
`${weatherStyle.gradient1} 100%)` `${weatherStyle.gradient1} 100%)`
}}> }}
>
{child} {child}
</div> </div>
</Tile> </Tile>
@ -139,56 +140,65 @@ export default class WeatherTile extends React.Component {
let error; let error;
if (this.state.error === true) { if (this.state.error === true) {
error = <p error = <p
className="f9 red2 pt1">Please try again. className="f9 red2 pt1"
</p> >Please try again.
</p>;
} }
if (location.protocol === "https:") { if (location.protocol === 'https:') {
secureCheck = <a secureCheck = <a
className="black white-d f9 absolute pointer" className="black white-d f9 absolute pointer"
style={{right: 8, top: 8}} style={{ right: 8, top: 8 }}
onClick={() => this.locationSubmit()}>Detect -></a> onClick={() => this.locationSubmit()}
>Detect -></a>;
} }
return this.renderWrapper( return this.renderWrapper(
<div className={"pa2 w-100 h-100 bg-white bg-gray0-d black white-d " + <div className={'pa2 w-100 h-100 bg-white bg-gray0-d black white-d ' +
"b--black b--gray1-d ba"}> 'b--black b--gray1-d ba'}
>
<a <a
className="f9 black white-d pointer" className="f9 black white-d pointer absolute"
style={{ top: 8 }}
onClick={() => onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry }) this.setState({ manualEntry: !this.state.manualEntry })
}> }
>
&lt;&#45; &lt;&#45;
</a> </a>
{secureCheck} {secureCheck}
<p className="f9 pt2"> <p className="f9 pt5">
Please enter your{" "} Please enter your{' '}
<a <a
className="black white-d" className="black bb white-d"
href="https://latitudeandlongitude.org/" href="https://latitudeandlongitude.org/"
target="_blank"> target="_blank"
>
latitude and longitude latitude and longitude
</a> </a>
. .
</p> </p>
{error} {error}
<div className="absolute" style={{left: 8, bottom: 8}}> <div className="absolute" style={{ left: 8, bottom: 8 }}>
<form className="flex" style={{marginBlockEnd: 0 }}> <form className="flex" style={{ marginBlockEnd: 0 }}>
<input <input
id="gps" id="gps"
className="w-100 black white-d bg-transparent bn f9" className="w-100 black white-d bg-transparent bn f9"
type="text" type="text"
placeholder="29.558107, -95.089023" placeholder="29.558107, -95.089023"
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
this.manualLocationSubmit(e.target.value); this.manualLocationSubmit(e.target.value);
}} }
}/> }
}
/>
<input <input
className={"bg-transparent black white-d bn pointer " + className={'bg-transparent black white-d bn pointer ' +
"f9 flex-shrink-0"} 'f9 flex-shrink-0 pr1'}
type="submit" type="submit"
onClick={() => this.manualLocationSubmit()} onClick={() => this.manualLocationSubmit()}
value="->"/> value="->"
/>
</form> </form>
</div> </div>
</div> </div>
@ -198,15 +208,18 @@ export default class WeatherTile extends React.Component {
renderNoData() { renderNoData() {
return this.renderWrapper(( return this.renderWrapper((
<div <div
className={"pa2 w-100 h-100 b--black b--gray1-d ba " + className={'pa2 w-100 h-100 b--black b--gray1-d ba ' +
"bg-white bg-gray0-d black white-d"} 'bg-white bg-gray0-d black white-d'}
onClick={() => this.setState({manualEntry: !this.state.manualEntry})}> onClick={() => this.setState({ manualEntry: !this.state.manualEntry })}
>
<p className="f9 absolute" <p className="f9 absolute"
style={{left: 8, top: 8}}> style={{ left: 8, top: 8 }}
>
Weather Weather
</p> </p>
<p className="absolute w-100 flex-col f9" <p className="absolute w-100 flex-col f9"
style={{bottom: 8, left: 8, cursor: "pointer"}}> style={{ bottom: 8, left: 8, cursor: 'pointer' }}
>
-> Set location -> Set location
</p> </p>
</div> </div>
@ -214,14 +227,15 @@ export default class WeatherTile extends React.Component {
} }
renderWithData(data, weatherStyle) { renderWithData(data, weatherStyle) {
let c = data.currently; const c = data.currently;
let d = data.daily.data[0]; const d = data.daily.data[0];
let da = moment.unix(d.sunsetTime).format('h:mm a') || ''; const da = moment.unix(d.sunsetTime).format('h:mm a') || '';
return this.renderWrapper( return this.renderWrapper(
<div className="w-100 h-100 b--black b--gray1-d ba" <div className="w-100 h-100 b--black b--gray1-d ba"
style={{backdropFilter: "blur(80px)"}}> style={{ backdropFilter: 'blur(80px)' }}
>
<p className="f9 absolute" style={{ left: 8, top: 8 }}> <p className="f9 absolute" style={{ left: 8, top: 8 }}>
Weather Weather
</p> </p>
@ -230,7 +244,8 @@ export default class WeatherTile extends React.Component {
style={{ right: 8, top: 8 }} style={{ right: 8, top: 8 }}
onClick={() => onClick={() =>
this.setState({ manualEntry: !this.state.manualEntry }) this.setState({ manualEntry: !this.state.manualEntry })
}> }
>
-> ->
</a> </a>
<div className="w-100 absolute" style={{ left: 8, bottom: 8 }}> <div className="w-100 absolute" style={{ left: 8, bottom: 8 }}>
@ -243,20 +258,19 @@ export default class WeatherTile extends React.Component {
} }
render() { render() {
let data = !!this.props.data ? this.props.data : {}; const data = this.props.data ? this.props.data : {};
if (this.state.manualEntry === true) { if (this.state.manualEntry === true) {
return this.renderManualEntry(); return this.renderManualEntry();
} }
if ('currently' in data && 'daily' in data) { if ('currently' in data && 'daily' in data) {
let weatherStyle = this.setColors(data); const weatherStyle = this.setColors(data);
return this.renderWithData(data, weatherStyle); return this.renderWithData(data, weatherStyle);
} }
return this.renderNoData(); return this.renderNoData();
} }
} }
window.weatherTile = WeatherTile; window.weatherTile = WeatherTile;

View File

@ -13,14 +13,6 @@ p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
} }
a:any-link {
color: unset;
}
a:-webkit-any-link {
color: unset;
}
textarea, select, input, button { outline: none; } textarea, select, input, button { outline: none; }
.c-default { .c-default {

View File

@ -29,7 +29,7 @@ export class LinksApp extends Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Groups'; document.title = 'OS1 - Links';
// preload spinner asset // preload spinner asset
new Image().src = '/~landscape/img/Spinner.png'; new Image().src = '/~landscape/img/Spinner.png';
@ -73,7 +73,7 @@ export class LinksApp extends Component {
); );
if(totalUnseen !== this.totalUnseen) { if(totalUnseen !== this.totalUnseen) {
document.title = totalUnseen !== 0 ? `Links - (${totalUnseen})` : 'Links'; document.title = totalUnseen !== 0 ? `OS1 - Links (${totalUnseen})` : 'OS1 - Links';
this.totalUnseen = totalUnseen; this.totalUnseen = totalUnseen;
} }

View File

@ -67,7 +67,7 @@ export class LinkItem extends Component {
classes={(member ? 'mix-blend-diff' : '')} classes={(member ? 'mix-blend-diff' : '')}
/>; />;
return ( return (
<div className="w-100 pv3 flex bg-white bg-gray0-d"> <div className="w-100 pv3 flex bg-white bg-gray0-d lh-solid">
{img} {img}
<div className="flex flex-column ml2 flex-auto"> <div className="flex flex-column ml2 flex-auto">
<a href={props.url} <a href={props.url}
@ -80,7 +80,7 @@ export class LinkItem extends Component {
</p> </p>
<span className="gray2 dib v-btm ml2 f8 flex-shrink-0">{hostname} </span> <span className="gray2 dib v-btm ml2 f8 flex-shrink-0">{hostname} </span>
</a> </a>
<div className="w-100 pt1"> <div className="w-100">
<span className={'f9 pr2 dib ' + mono} <span className={'f9 pr2 dib ' + mono}
title={props.ship} title={props.ship}
> >

View File

@ -30,20 +30,22 @@ export class LinkDetail extends Component {
} }
componentDidMount() { componentDidMount() {
// if we have no preloaded data, and we aren't expecting it, get it this.componentDidUpdate();
if (!this.state.data.title) {
this.props.api.getSubmission(
this.props.resourcePath, this.props.url, this.updateData.bind(this)
);
}
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.url !== prevProps.url) { // if we have no preloaded data, and we aren't expecting it, get it
this.updateData(this.props.data); if ((!this.state.data.title) && (this.props.api)) {
this.props.api?.getSubmission(
this.props.resourcePath, this.props.url, this.updateData.bind(this)
);
} }
if (prevProps.comments && prevProps.comments['0'] && if (prevProps) {
this.props.comments && this.props.comments['0']) { if (this.props.url !== prevProps.url) {
this.updateData(this.props.data);
}
if (prevProps.comments && prevProps.comments['0'] &&
this.props.comments && this.props.comments['0']) {
const prevFirstComment = prevProps.comments['0'][0]; const prevFirstComment = prevProps.comments['0'][0];
const thisFirstComment = this.props.comments['0'][0]; const thisFirstComment = this.props.comments['0'][0];
if ((prevFirstComment && prevFirstComment.udon) && if ((prevFirstComment && prevFirstComment.udon) &&
@ -56,6 +58,7 @@ export class LinkDetail extends Component {
}); });
} }
} }
}
} }
} }

View File

@ -26,14 +26,16 @@ export class Links extends Component {
// and don't have links for it yet, // and don't have links for it yet,
// or the links we have might not be complete, // or the links we have might not be complete,
// request the links for that page. // request the links for that page.
if ( (!prevProps || if ( ((!prevProps || // first load?
linkPage !== prevProps.page || linkPage !== prevProps.page || // already waiting on response?
this.props.resourcePath !== prevProps.resourcePath this.props.resourcePath !== prevProps.resourcePath // new page?
) && ) ||
!this.props.links[linkPage] || (prevProps.api !== this.props.api)) // api prop instantiated?
this.props.links.local[linkPage] &&
!this.props.links[linkPage] || // don't have info?
this.props.links.local[linkPage] // waiting on post confirmation?
) { ) {
this.props.api.getPage(this.props.resourcePath, this.props.page); this.props.api?.getPage(this.props.resourcePath, this.props.page);
} }
} }

View File

@ -31,7 +31,7 @@ export default class PublishApp extends React.Component {
} }
componentDidMount() { componentDidMount() {
window.title = 'OS1 - Groups'; document.title = 'OS1 - Publish';
// preload spinner asset // preload spinner asset
new Image().src = '/~landscape/img/Spinner.png'; new Image().src = '/~landscape/img/Spinner.png';
@ -59,7 +59,9 @@ export default class PublishApp extends React.Component {
const associations = state.associations ? state.associations : { contacts: {} }; const associations = state.associations ? state.associations : { contacts: {} };
const selectedGroups = props.selectedGroups ? props.selectedGroups : []; const selectedGroups = props.selectedGroups ? props.selectedGroups : [];
const unreadTotal = _.chain(state.notebooks) const notebooks = state.notebooks ? state.notebooks : {};
const unreadTotal = _.chain(notebooks)
.values() .values()
.map(_.values) .map(_.values)
.flatten() // flatten into array of notebooks .flatten() // flatten into array of notebooks
@ -68,7 +70,7 @@ export default class PublishApp extends React.Component {
.value(); .value();
if (this.unreadTotal !== unreadTotal) { if (this.unreadTotal !== unreadTotal) {
window.title = unreadTotal > 0 ? `Publish - (${unreadTotal})` : 'Publish'; document.title = unreadTotal > 0 ? `OS1 - Publish (${unreadTotal})` : 'OS1 - Publish';
this.unreadTotal = unreadTotal; this.unreadTotal = unreadTotal;
} }
@ -83,7 +85,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={true} rightPanelHide={true}
sidebarShown={true} sidebarShown={true}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
contacts={contacts} contacts={contacts}
@ -111,7 +113,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
contacts={contacts} contacts={contacts}
@ -119,7 +121,7 @@ export default class PublishApp extends React.Component {
> >
<NewScreen <NewScreen
associations={associations.contacts} associations={associations.contacts}
notebooks={state.notebooks} notebooks={notebooks}
groups={state.groups} groups={state.groups}
contacts={contacts} contacts={contacts}
api={this.api} api={this.api}
@ -140,14 +142,14 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
contacts={contacts} contacts={contacts}
api={this.api} api={this.api}
> >
<JoinScreen <JoinScreen
notebooks={state.notebooks} notebooks={notebooks}
ship={ship} ship={ship}
notebook={notebook} notebook={notebook}
api={this.api} api={this.api}
@ -171,7 +173,7 @@ export default class PublishApp extends React.Component {
const path = `${ship}/${notebook}`; const path = `${ship}/${notebook}`;
const bookGroupPath = const bookGroupPath =
state.notebooks[ship][notebook]['subscribers-group-path']; notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
const notebookContacts = (bookGroupPath in contacts) const notebookContacts = (bookGroupPath in contacts)
? contacts[bookGroupPath] : {}; ? contacts[bookGroupPath] : {};
@ -184,7 +186,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
contacts={contacts} contacts={contacts}
@ -192,7 +194,7 @@ export default class PublishApp extends React.Component {
api={this.api} api={this.api}
> >
<NewPost <NewPost
notebooks={state.notebooks} notebooks={notebooks}
ship={ship} ship={ship}
book={notebook} book={notebook}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
@ -210,7 +212,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
contacts={contacts} contacts={contacts}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
@ -218,7 +220,7 @@ export default class PublishApp extends React.Component {
api={this.api} api={this.api}
> >
<Notebook <Notebook
notebooks={state.notebooks} notebooks={notebooks}
view={view} view={view}
ship={ship} ship={ship}
book={notebook} book={notebook}
@ -247,7 +249,7 @@ export default class PublishApp extends React.Component {
const popout = Boolean(props.match.params.popout) || false; const popout = Boolean(props.match.params.popout) || false;
const bookGroupPath = const bookGroupPath =
state.notebooks[ship][notebook]['subscribers-group-path']; notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
const notebookContacts = (bookGroupPath in state.contacts) const notebookContacts = (bookGroupPath in state.contacts)
? contacts[bookGroupPath] : {}; ? contacts[bookGroupPath] : {};
@ -261,7 +263,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
associations={associations} associations={associations}
contacts={contacts} contacts={contacts}
@ -269,7 +271,7 @@ export default class PublishApp extends React.Component {
api={this.api} api={this.api}
> >
<EditPost <EditPost
notebooks={state.notebooks} notebooks={notebooks}
book={notebook} book={notebook}
note={note} note={note}
ship={ship} ship={ship}
@ -288,7 +290,7 @@ export default class PublishApp extends React.Component {
rightPanelHide={false} rightPanelHide={false}
sidebarShown={state.sidebarShown} sidebarShown={state.sidebarShown}
invites={state.invites} invites={state.invites}
notebooks={state.notebooks} notebooks={notebooks}
associations={associations} associations={associations}
selectedGroups={selectedGroups} selectedGroups={selectedGroups}
contacts={contacts} contacts={contacts}
@ -296,7 +298,7 @@ export default class PublishApp extends React.Component {
api={this.api} api={this.api}
> >
<Note <Note
notebooks={state.notebooks} notebooks={notebooks}
book={notebook} book={notebook}
groups={state.groups} groups={state.groups}
contacts={notebookContacts} contacts={notebookContacts}

View File

@ -8,7 +8,7 @@ const CommentInput = React.forwardRef((props, ref) => (
name="comment" name="comment"
placeholder="Leave a comment here" placeholder="Leave a comment here"
className={ className={
'f9 db border-box w-100 ba b--gray3 pt3 ph3 br1 ' + 'f9 db border-box w-100 ba b--gray3 pt2 ph2 br1 ' +
'b--gray2-d mb2 focus-b--black focus-b--white-d white-d bg-gray0-d' 'b--gray2-d mb2 focus-b--black focus-b--white-d white-d bg-gray0-d'
} }
aria-describedby="comment-desc" aria-describedby="comment-desc"

View File

@ -53,7 +53,7 @@ export class Comments extends Component {
this.textArea.value = ''; this.textArea.value = '';
this.setState({ commentBody: '', awaiting: 'new' }); this.setState({ commentBody: '', awaiting: 'new' });
const submit = this.props.api.action('publish', 'publish-action', comment); const submit = this.props.api.publishAction(comment);
submit.then(() => { submit.then(() => {
this.setState({ awaiting: null }); this.setState({ awaiting: null });
}); });
@ -88,7 +88,7 @@ export class Comments extends Component {
this.setState({ awaiting: 'edit' }); this.setState({ awaiting: 'edit' });
window.api window.api
.action('publish', 'publish-action', comment) .publishAction(comment)
.then(() => { .then(() => {
this.setState({ awaiting: null, editing: null }); this.setState({ awaiting: null, editing: null });
}); });
@ -107,7 +107,7 @@ export class Comments extends Component {
this.setState({ awaiting: { kind: 'del', what: idx } }); this.setState({ awaiting: { kind: 'del', what: idx } });
window.api window.api
.action('publish', 'publish-action', comment) .publishAction(comment)
.then(() => { .then(() => {
this.setState({ awaiting: null }); this.setState({ awaiting: null });
}); });

View File

@ -20,18 +20,21 @@ export class EditPost extends Component {
} }
componentDidMount() { componentDidMount() {
const { props } = this; this.componentDidUpdate();
if (!(props.notebooks[props.ship]) || }
!(props.notebooks[props.ship][props.book]) ||
!(props.notebooks[props.ship][props.book].notes[props.note]) || componentDidUpdate(prevProps) {
!(props.notebooks[props.ship][props.book].notes[props.note].file)) { const { props, state } = this;
this.props.api.fetchNote(props.ship, props.book, props.note); if (prevProps && prevProps.api !== props.api) {
} else { if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) {
const notebook = props.notebooks[props.ship][props.book]; props.api?.fetchNote(props.ship, props.book, props.note);
const note = notebook.notes[props.note]; } else if (state.body === '') {
const file = note.file; const notebook = props.notebooks[props.ship][props.book];
const body = file.slice(file.indexOf(';>') + 3); const note = notebook.notes[props.note];
this.setState({ body: body }); const file = note.file;
const body = file.slice(file.indexOf(';>') + 3);
this.setState({ body: body });
}
} }
} }
@ -50,7 +53,7 @@ export class EditPost extends Component {
} }
}; };
this.setState({ awaiting: true }); this.setState({ awaiting: true });
this.props.api.action('publish', 'publish-action', editNote).then(() => { this.props.api.publishAction(editNote).then(() => {
const editIndex = props.location.pathname.indexOf('/edit'); const editIndex = props.location.pathname.indexOf('/edit');
const noteHref = props.location.pathname.slice(0, editIndex); const noteHref = props.location.pathname.slice(0, editIndex);
this.setState({ awaiting: false }); this.setState({ awaiting: false });
@ -112,7 +115,7 @@ export class EditPost extends Component {
to={popoutHref} to={popoutHref}
target="_blank" target="_blank"
> >
<img src="/~publish/popout.png" <img src="/~landscape/img/popout.png"
height={16} height={16}
width={16} width={16}
/> />

View File

@ -93,7 +93,7 @@ export class JoinScreen extends Component {
// TODO: askHistory setting // TODO: askHistory setting
this.setState({ disable: true }); this.setState({ disable: true });
this.props.api.action('publish','publish-action', actionData).catch((err) => { this.props.api.publishAction(actionData).catch((err) => {
console.log(err); console.log(err);
}).then(() => { }).then(() => {
this.setState({ awaiting: text }); this.setState({ awaiting: text });

View File

@ -37,14 +37,14 @@ export class NewPost extends Component {
}; };
this.setState({ disabled: true }); this.setState({ disabled: true });
this.props.api.action('publish', 'publish-action', newNote).then(() => { this.props.api.publishAction(newNote).then(() => {
this.setState({ awaiting: newNote['new-note'].note }); this.setState({ awaiting: newNote['new-note'].note });
}).catch((err) => { }).catch((err) => {
if (err.includes('note already exists')) { if (err.includes('note already exists')) {
const timestamp = Math.floor(Date.now() / 1000); const timestamp = Math.floor(Date.now() / 1000);
newNote['new-note'].note += '-' + timestamp; newNote['new-note'].note += '-' + timestamp;
this.setState({ awaiting: newNote['new-note'].note }); this.setState({ awaiting: newNote['new-note'].note });
this.props.api.action('publish', 'publish-action', newNote); this.props.api.publishAction(newNote);
} else { } else {
this.setState({ disabled: false, awaiting: null }); this.setState({ disabled: false, awaiting: null });
} }
@ -52,11 +52,15 @@ export class NewPost extends Component {
} }
} }
componentWillMount() { componentDidMount() {
this.props.api.fetchNotebook(this.props.ship, this.props.book); this.componentDidUpdate();
} }
componentDidUpdate() { componentDidUpdate(prevProps) {
if (prevProps && prevProps.api !== this.props.api) {
this.props.api.fetchNotebook(this.props.ship, this.props.book);
}
const notebook = this.props.notebooks[this.props.ship][this.props.book]; const notebook = this.props.notebooks[this.props.ship][this.props.book];
if (notebook.notes[this.state.awaiting]) { if (notebook.notes[this.state.awaiting]) {
this.setState({ disabled: false, awaiting: null }); this.setState({ disabled: false, awaiting: null });
@ -131,7 +135,7 @@ export class NewPost extends Component {
to={popoutHref} to={popoutHref}
target='_blank' target='_blank'
> >
<img src='/~publish/popout.png' height={16} width={16} /> <img src='/~landscape/img/popout.png' height={16} width={16} />
</Link> </Link>
</div> </div>
<div className='mw6 center'> <div className='mw6 center'>

View File

@ -93,7 +93,7 @@ export class NewScreen extends Component {
} }
}; };
this.setState({ awaiting: bookId, disabled: true }, () => { this.setState({ awaiting: bookId, disabled: true }, () => {
props.api.action('publish', 'publish-action', action).then(() => { props.api.publishAction(action).then(() => {
}); });
}); });
} }

View File

@ -40,54 +40,40 @@ export class Note extends Component {
this.deletePost = this.deletePost.bind(this); this.deletePost = this.deletePost.bind(this);
} }
componentWillMount() {
const readAction = {
read: {
who: this.props.ship.slice(1),
book: this.props.book,
note: this.props.note
}
};
this.props.api.action('publish', 'publish-action', readAction);
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note);
}
componentDidMount() { componentDidMount() {
if (!(this.props.notebooks[this.props.ship]) || this.componentDidUpdate();
!(this.props.notebooks[this.props.ship][this.props.book]) ||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note]) ||
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note].file)) {
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note);
}
this.onScroll(); this.onScroll();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (!(this.props.notebooks[this.props.ship]) || const { props } = this;
!(this.props.notebooks[this.props.ship][this.props.book]) || if ((prevProps && prevProps.api !== props.api) || props.api) {
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note]) || if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) {
!(this.props.notebooks[this.props.ship][this.props.book].notes[this.props.note].file)) { props.api.fetchNote(props.ship, props.book, props.note);
this.props.api.fetchNote(this.props.ship, this.props.book, this.props.note); }
}
if ((prevProps.book !== this.props.book) || if (prevProps) {
(prevProps.note !== this.props.note) || if ((prevProps.book !== props.book) ||
(prevProps.ship !== this.props.ship)) { (prevProps.note !== props.note) ||
const readAction = { (prevProps.ship !== props.ship)) {
read: { const readAction = {
who: this.props.ship.slice(1), read: {
book: this.props.book, who: props.ship.slice(1),
note: this.props.note book: props.book,
note: props.note
}
};
props.api.publishAction(readAction);
} }
}; }
this.props.api.action('publish', 'publish-action', readAction);
} }
} }
onScroll() { onScroll() {
const notebook = this.props.notebooks[this.props.ship][this.props.book]; const notebook = this.props.notebooks?.[this.props.ship]?.[this.props.book];
const note = notebook.notes[this.props.note]; const note = notebook?.notes?.[this.props.note];
if (!note.comments) { if (!note?.comments) {
return; return;
} }
@ -123,7 +109,7 @@ export class Note extends Component {
const popout = (props.popout) ? 'popout/' : ''; const popout = (props.popout) ? 'popout/' : '';
const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`; const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`;
this.setState({ deleting: true }); this.setState({ deleting: true });
this.props.api.action('publish', 'publish-action', deleteAction) this.props.api.publishAction(deleteAction)
.then(() => { .then(() => {
props.history.push(baseUrl); props.history.push(baseUrl);
}); });
@ -131,12 +117,12 @@ export class Note extends Component {
render() { render() {
const { props } = this; const { props } = this;
const notebook = props.notebooks[props.ship][props.book] || {}; const notebook = props.notebooks?.[props.ship]?.[props.book] || {};
const comments = notebook.notes[props.note].comments || false; const comments = notebook?.notes?.[props.note]?.comments || false;
const title = notebook.notes[props.note].title || ''; const title = notebook?.notes?.[props.note]?.title || '';
const author = notebook.notes[props.note].author || ''; const author = notebook?.notes?.[props.note]?.author || '';
const file = notebook.notes[props.note].file || ''; const file = notebook?.notes?.[props.note]?.file || '';
const date = moment(notebook.notes[props.note]['date-created']).fromNow() || 0; const date = moment(notebook.notes?.[props.note]?.['date-created']).fromNow() || 0;
const contact = author.substr(1) in props.contacts const contact = author.substr(1) in props.contacts
? props.contacts[author.substr(1)] : false; ? props.contacts[author.substr(1)] : false;
@ -156,22 +142,24 @@ export class Note extends Component {
} }
const newfile = file.slice(file.indexOf(';>')+2); const newfile = file.slice(file.indexOf(';>')+2);
const prevId = notebook.notes[props.note]['prev-note'] || null; const prevId = notebook?.notes?.[props.note]?.['prev-note'] || null;
const nextId = notebook.notes[props.note]['next-note'] || null; const nextId = notebook?.notes?.[props.note]?.['next-note'] || null;
const prevDate = moment(notebook?.notes?.[prevId]?.['date-created']).fromNow() || 0;
const nextDate = moment(notebook?.notes?.[nextId]?.['date-created']).fromNow() || 0;
const prev = (prevId === null) const prev = (prevId === null)
? null ? null
: { : {
id: prevId, id: prevId,
title: notebook.notes[prevId].title, title: notebook?.notes?.[prevId]?.title,
date: moment(notebook.notes[prevId]['date-created']).fromNow() date: prevDate
}; };
const next = (nextId === null) const next = (nextId === null)
? null ? null
: { : {
id: nextId, id: nextId,
title: notebook.notes[nextId].title, title: notebook?.notes?.[nextId]?.title,
date: moment(notebook.notes[nextId]['date-created']).fromNow() date: nextDate
}; };
let editPost = null; let editPost = null;
@ -218,7 +206,7 @@ export class Note extends Component {
className={'dn absolute right-1 top-1 ' + hiddenOnPopout} className={'dn absolute right-1 top-1 ' + hiddenOnPopout}
target='_blank' target='_blank'
> >
<img src='/~publish/popout.png' height={16} width={16} /> <img src='/~landscape/img/popout.png' height={16} width={16} />
</Link> </Link>
</div> </div>
<div className='w-100 mw6'> <div className='w-100 mw6'>

View File

@ -24,13 +24,13 @@ export class Notebook extends Component {
if (scrollHeight - scrollTop - clientHeight < 40) { if (scrollHeight - scrollTop - clientHeight < 40) {
atBottom = true; atBottom = true;
} }
if (!notebook.notes) { if (!notebook.notes && this.props.api) {
this.props.api.fetchNotebook(this.props.ship, this.props.book); this.props.api.fetchNotebook(this.props.ship, this.props.book);
return; return;
} }
const loadedNotes = Object.keys(notebook.notes).length; const loadedNotes = Object.keys(notebook?.notes).length || 0;
const allNotes = notebook['notes-by-date'].length; const allNotes = notebook?.['notes-by-date'].length || 0;
const fullyLoaded = (loadedNotes === allNotes); const fullyLoaded = (loadedNotes === allNotes);
@ -39,20 +39,20 @@ export class Notebook extends Component {
} }
} }
componentWillMount() {
this.props.api.fetchNotebook(this.props.ship, this.props.book);
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const notebook = this.props.notebooks[this.props.ship][this.props.book]; const { props } = this;
if (!notebook.subscribers) { if ((prevProps && (prevProps.api !== props.api)) || props.api) {
this.props.api.fetchNotebook(this.props.ship, this.props.book); const notebook = props.notebooks?.[props.ship]?.[props.book];
if (!notebook?.subscribers) {
props.api.fetchNotebook(props.ship, props.book);
}
} }
} }
componentDidMount() { componentDidMount() {
const notebook = this.props.notebooks[this.props.ship][this.props.book]; this.componentDidUpdate();
if (notebook.notes) { const notebook = this.props.notebooks?.[this.props.ship]?.[this.props.book];
if (notebook?.notes) {
this.onScroll(); this.onScroll();
} }
} }
@ -64,7 +64,7 @@ export class Notebook extends Component {
book: this.props.book book: this.props.book
} }
}; };
this.props.api.action('publish', 'publish-action', action); this.props.api.publishAction(action);
this.props.history.push('/~publish'); this.props.history.push('/~publish');
} }
@ -78,7 +78,7 @@ export class Notebook extends Component {
const hiddenOnPopout = props.popout ? '' : 'dib-m dib-l dib-xl'; const hiddenOnPopout = props.popout ? '' : 'dib-m dib-l dib-xl';
const notebook = props.notebooks[props.ship][props.book]; const notebook = props.notebooks?.[props.ship]?.[props.book];
const tabStyles = { const tabStyles = {
posts: 'bb b--gray4 b--gray2-d gray2 pv4 ph2', posts: 'bb b--gray4 b--gray2-d gray2 pv4 ph2',
@ -91,8 +91,8 @@ export class Notebook extends Component {
let inner = null; let inner = null;
switch (props.view) { switch (props.view) {
case 'posts': { case 'posts': {
const notesList = notebook['notes-by-date'] || []; const notesList = notebook?.['notes-by-date'] || [];
const notes = notebook.notes || null; const notes = notebook?.notes || null;
inner = <NotebookPosts notes={notes} inner = <NotebookPosts notes={notes}
popout={props.popout} popout={props.popout}
list={notesList} list={notesList}
@ -103,7 +103,7 @@ export class Notebook extends Component {
break; break;
} }
case 'about': case 'about':
inner = <p className="f8 lh-solid">{notebook.about}</p>; inner = <p className="f8 lh-solid">{notebook?.about}</p>;
break; break;
case 'subscribers': case 'subscribers':
inner = <Subscribers inner = <Subscribers
@ -112,6 +112,7 @@ export class Notebook extends Component {
notebook={notebook} notebook={notebook}
permissions={this.props.permissions} permissions={this.props.permissions}
groups={this.props.groups} groups={this.props.groups}
api={this.props.api}
/>; />;
break; break;
case 'settings': case 'settings':
@ -123,6 +124,7 @@ export class Notebook extends Component {
contacts={this.props.contacts} contacts={this.props.contacts}
associations={this.props.associations} associations={this.props.associations}
history={this.props.history} history={this.props.history}
api={this.props.api}
/>; />;
break; break;
default: default:
@ -150,9 +152,9 @@ export class Notebook extends Component {
const newUrl = base + '/new'; const newUrl = base + '/new';
let newPost = null; let newPost = null;
if (notebook['writers-group-path'] in props.groups) { if (notebook?.['writers-group-path'] in props.groups) {
const writers = notebook['writers-group-path']; const writers = notebook?.['writers-group-path'];
if (props.groups[writers].has(window.ship)) { if (props.groups?.[writers].has(window.ship)) {
newPost = ( newPost = (
<Link <Link
to={newUrl} to={newUrl}
@ -186,7 +188,7 @@ export class Notebook extends Component {
return ( return (
<div <div
className='overflow-y-scroll' className='overflow-y-scroll h-100'
style={{ paddingLeft: 16, paddingRight: 16 }} style={{ paddingLeft: 16, paddingRight: 16 }}
onScroll={this.onScroll} onScroll={this.onScroll}
ref={(el) => { ref={(el) => {
@ -213,12 +215,12 @@ export class Notebook extends Component {
to={popoutHref} to={popoutHref}
target='_blank' target='_blank'
> >
<img src='/~publish/popout.png' height={16} width={16} /> <img src='/~landscape/img/popout.png' height={16} width={16} />
</Link> </Link>
<div className='h-100 pt0 pt8-m pt8-l pt8-xl no-scrollbar'> <div className='h-100 pt0 pt8-m pt8-l pt8-xl no-scrollbar'>
<div className='flex justify-between' style={{ marginBottom: 32 }}> <div className='flex justify-between' style={{ marginBottom: 32 }}>
<div className='flex-col'> <div className='flex-col'>
<div className='mb1'>{notebook.title}</div> <div className='mb1'>{notebook?.title}</div>
<span> <span>
<span className='gray3 mr1'>by</span> <span className='gray3 mr1'>by</span>
<span <span

View File

@ -63,7 +63,7 @@ export class Settings extends Component {
changeComments() { changeComments() {
this.setState({ comments: !this.state.comments, disabled: true }, (() => { this.setState({ comments: !this.state.comments, disabled: true }, (() => {
this.props.api.action('publish', 'publish-action', { this.props.api.publishAction({
'edit-book': { 'edit-book': {
book: this.props.book, book: this.props.book,
title: this.props.notebook.title, title: this.props.notebook.title,
@ -84,7 +84,7 @@ export class Settings extends Component {
} }
}; };
this.setState({ disabled: true, type: 'Deleting' }); this.setState({ disabled: true, type: 'Deleting' });
this.props.api.action('publish', 'publish-action', action).then(() => { this.props.api.publishAction(action).then(() => {
this.props.history.push('/~publish'); this.props.history.push('/~publish');
}); });
} }
@ -108,7 +108,7 @@ export class Settings extends Component {
disabled: true, disabled: true,
type: 'Converting' type: 'Converting'
}, (() => { }, (() => {
this.props.api.action('publish', 'publish-action', { this.props.api.publishAction({
groupify: { groupify: {
book: props.book, book: props.book,
target: state.targetGroup, target: state.targetGroup,
@ -125,7 +125,7 @@ export class Settings extends Component {
const ownedUnmanaged = const ownedUnmanaged =
owner && owner &&
props.notebook['writers-group-path'].slice(0, 3) === '/~/'; props.notebook?.['writers-group-path'].slice(0, 3) === '/~/';
if (!ownedUnmanaged) { if (!ownedUnmanaged) {
return null; return null;
@ -162,11 +162,9 @@ export class Settings extends Component {
return ( return (
<div> <div>
<div className={'w-100 fl mt3 mb3'} style={{ maxWidth: '29rem' }}> <div className={'w-100 fl mt3 mb3'} style={{ maxWidth: '29rem' }}>
<p className="f8 mt3 lh-copy db">Convert Notebook</p> {this.renderHeader(
<p className="f9 gray2 db mb4"> 'Convert Notebook',
Convert this notebook into a group with associated chat, or select a 'Convert this notebook into a group with associated chat, or select a group to add this notebook to.')}
group to add this notebook to.
</p>
<InviteSearch <InviteSearch
groups={props.groups} groups={props.groups}
contacts={props.contacts} contacts={props.contacts}
@ -185,7 +183,7 @@ export class Settings extends Component {
className={'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black b--gray1-d pointer'} className={'dib f9 black gray4-d bg-gray0-d ba pa2 mt4 b--black b--gray1-d pointer'}
disabled={this.state.disabled} disabled={this.state.disabled}
> >
Convert to group {state.targetGroup ? 'Add to group' : 'Convert to group'}
</button> </button>
</div> </div>
</div> </div>
@ -193,14 +191,22 @@ export class Settings extends Component {
} }
} }
renderHeader(title, subtitle) {
return (
<>
<p className="f9 mt6 lh-copy">{title}</p>
<p className="f9 gray2 db mb4">{subtitle}</p>
</>
);
}
render() { render() {
const commentsSwitchClasses = (this.state.comments) const commentsSwitchClasses = (this.state.comments)
? 'relative checked bg-green2 br3 h1 toggle v-mid z-0' ? 'relative checked bg-green2 br3 h1 toggle v-mid z-0'
: 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0'; : 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0';
const copyShortcode = <div> const copyShortcode = <div>
<p className="f9 mt3 lh-copy">Share</p> {this.renderHeader('Share', 'Share a shortcode to join this notebook')}
<p className="f9 gray2 mb4">Share a shortcode to join this notebook</p>
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}> <div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
<input <input
className={'f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d ' + className={'f8 mono ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
@ -226,19 +232,16 @@ export class Settings extends Component {
<div className="flex-column"> <div className="flex-column">
{copyShortcode} {copyShortcode}
{this.renderGroupify()} {this.renderGroupify()}
<p className="f9 mt6 lh-copy db">Delete Notebook</p> {this.renderHeader(
<p className="f9 gray2 db mb4"> 'Delete Notebook',
Permanently delete this notebook. (All current members will no 'Permanently delete this notebook. (All current members will no longer see this notebook)')}
longer see this notebook)
</p>
<button <button
className="bg-transparent b--red2 red2 pointer dib f9 ba pa2" className="bg-transparent b--red2 red2 pointer dib f9 ba pa2"
onClick={this.deleteNotebook} onClick={this.deleteNotebook}
> >
Delete this notebook Delete this notebook
</button> </button>
<p className="f9 mt6 lh-copy">Rename</p> {this.renderHeader('Rename', 'Change the name of this notebook')}
<p className="f9 gray2 db mb4">Change the name of this notebook</p>
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}> <div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
<input <input
className={ className={
@ -251,7 +254,7 @@ export class Settings extends Component {
onBlur={() => { onBlur={() => {
this.setState({ disabled: true }); this.setState({ disabled: true });
this.props.api this.props.api
.action('publish', 'publish-action', { .publishAction({
'edit-book': { 'edit-book': {
book: this.props.book, book: this.props.book,
title: this.state.title, title: this.state.title,
@ -266,8 +269,7 @@ export class Settings extends Component {
}} }}
/> />
</div> </div>
<p className="f9 mt6 lh-copy">Change description</p> {this.renderHeader("Change description", "Change the description of this notebook")}
<p className="f9 gray2 db mb4">Change the description of this notebook</p>
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}> <div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
<input <input
className={ className={
@ -279,7 +281,7 @@ export class Settings extends Component {
onBlur={() => { onBlur={() => {
this.setState({ disabled: true }); this.setState({ disabled: true });
this.props.api this.props.api
.action('publish', 'publish-action', { .publishAction({
'edit-book': { 'edit-book': {
book: this.props.book, book: this.props.book,
title: this.props.notebook.title, title: this.props.notebook.title,

View File

@ -8,7 +8,7 @@ export class SidebarInvite extends Component {
uid: this.props.uid uid: this.props.uid
} }
}; };
window.api.action('invite-store', 'invite-action', action); this.props.api.inviteAction(action);
} }
onDecline() { onDecline() {
@ -18,7 +18,7 @@ export class SidebarInvite extends Component {
uid: this.props.uid uid: this.props.uid
} }
}; };
this.props.api.action('invite-store', 'invite-action', action); this.props.api.inviteAction(action);
} }
render() { render() {

View File

@ -17,7 +17,7 @@ export class Subscribers extends Component {
path: path path: path
} }
}; };
this.props.api.action('group-store', 'group-action', action); this.props.api.groupAction(action);
} }
removeUser(who, path) { removeUser(who, path) {
@ -27,7 +27,7 @@ export class Subscribers extends Component {
path: path path: path
} }
}; };
this.props.api.action('group-store', 'group-action', action); this.props.api.groupAction(action);
} }
redirect(url) { redirect(url) {

View File

@ -0,0 +1,15 @@
import React from 'react';
const NotFound = () => <div
className="fixed inter tc"
style={{
top: '50vh',
left: '50%',
transform: 'translate(-50%, -25vh)',
fontFeatureSettings: '\'zero\' 1' }}>
<h1 className="fw2 f2">404</h1>
<p className="tc">Not found.<br /><br />
If this is unexpected, email <code>support@tlon.io</code> or <a className="bb" href="https://github.com/urbit/urbit/issues/new/choose">submit an issue</a>.</p>
</div>;
export default NotFound;

View File

@ -10,7 +10,7 @@ export class Spinner extends Component {
return ( return (
<div className={classes + ' z-2 bg-white bg-gray0-d white-d'}> <div className={classes + ' z-2 bg-white bg-gray0-d white-d'}>
<img className="invert-d spin-active v-mid" <img className="invert-d spin-active v-mid"
src="/~chat/img/Spinner.png" src="/~landscape/img/Spinner.png"
width={16} width={16}
height={16} height={16}
/> />

View File

@ -7,14 +7,16 @@ import { Sigil } from '../lib/sigil';
const getLocationName = (basePath) => { const getLocationName = (basePath) => {
if (basePath === '~chat') if (basePath === '~chat')
return 'Chat'; return 'Chat';
if (basePath === '~dojo') else if (basePath === '~dojo')
return 'Dojo'; return 'Dojo';
if (basePath === '~groups') else if (basePath === '~groups')
return 'Groups'; return 'Groups';
if (basePath === '~link') else if (basePath === '~link')
return 'Links'; return 'Links';
if (basePath === '~publish') else if (basePath === '~publish')
return 'Publish'; return 'Publish';
else
return 'Unknown';
}; };
const StatusBar = (props) => { const StatusBar = (props) => {
@ -24,8 +26,9 @@ const StatusBar = (props) => {
? 'Home' ? 'Home'
: getLocationName(basePath); : getLocationName(basePath);
const popout = window.location.href.includes('popout/') const display = (!window.location.href.includes('popout/') &&
? 'dn' : 'db'; (locationName !== 'Unknown'))
? 'db' : 'dn';
const invites = (props.invites && props.invites['/contacts']) const invites = (props.invites && props.invites['/contacts'])
? props.invites['/contacts'] ? props.invites['/contacts']
@ -34,7 +37,7 @@ const StatusBar = (props) => {
return ( return (
<div <div
className={ className={
'bg-white bg-gray0-d w-100 justify-between relative tc pt3 ' + popout 'bg-white bg-gray0-d w-100 justify-between relative tc pt3 ' + display
} }
style={{ height: 45 }} style={{ height: 45 }}
> >
@ -54,7 +57,7 @@ const StatusBar = (props) => {
location.pathname === '/' location.pathname === '/'
? null ? null
: <Link : <Link
className="dib f9 v-mid inter ml2 no-underline" className="dib f9 v-mid inter ml2 no-underline white-d"
to="/" to="/"
style={{ top: 14 }} style={{ top: 14 }}
> >

View File

@ -4,6 +4,7 @@ export default class InviteReducer {
reduce(json, state) { reduce(json, state) {
const data = _.get(json, 'invite-update', false); const data = _.get(json, 'invite-update', false);
if (data) { if (data) {
console.log(data);
this.initial(data, state); this.initial(data, state);
this.create(data, state); this.create(data, state);
this.delete(data, state); this.delete(data, state);

View File

@ -4,25 +4,32 @@ export default class MetadataReducer {
reduce(json, state) { reduce(json, state) {
let data = _.get(json, 'metadata-update', false); let data = _.get(json, 'metadata-update', false);
if (data) { if (data) {
console.log('data: ', data);
this.associations(data, state); this.associations(data, state);
this.add(data, state); this.add(data, state);
this.update(data, state); this.update(data, state);
this.remove(data, state); this.remove(data, state);
console.log('state: ', state);
} }
} }
associations(json, state) { associations(json, state) {
let data = _.get(json, 'associations', false); let data = _.get(json, 'associations', false);
if (data) { if (data) {
let metadata = {}; let metadata = state.associations;
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
let val = data[key]; let val = data[key];
let groupPath = val['group-path']; let appName = val['app-name'];
if (!(groupPath in metadata)) { let appPath = val['app-path'];
metadata[groupPath] = {}; if (!(appName in metadata)) {
metadata[appName] = {};
} }
metadata[groupPath][key] = val; if (!(appPath in metadata[appName])) {
metadata[appName][appPath] = {};
}
metadata[appName][appPath] = val;
}); });
state.associations = metadata; state.associations = metadata;
} }
} }
@ -31,11 +38,16 @@ export default class MetadataReducer {
let data = _.get(json, 'add', false); let data = _.get(json, 'add', false);
if (data) { if (data) {
let metadata = state.associations; let metadata = state.associations;
if (!(data['group-path'] in metadata)) { let appName = data['app-name'];
metadata[data['group-path']] = {}; let appPath = data['app-path'];
if (!(appName in metadata)) {
metadata[appName] = {};
} }
metadata[data['group-path']] if (!(appPath in metadata[appName])) {
[`${data["group-path"]}/${data["app-name"]}${data["app-path"]}`] = data; metadata[appName][appPath] = {};
}
metadata[appName][appPath] = data;
state.associations = metadata; state.associations = metadata;
} }
@ -45,12 +57,16 @@ export default class MetadataReducer {
let data = _.get(json, 'update-metadata', false); let data = _.get(json, 'update-metadata', false);
if (data) { if (data) {
let metadata = state.associations; let metadata = state.associations;
if (!(data["group-path"] in metadata)) { let appName = data['app-name'];
metadata[data["group-path"]] = {}; let appPath = data['app-path'];
if (!(appName in metadata)) {
metadata[appName] = {};
} }
metadata[data["group-path"]][ if (!(appPath in metadata[appName])) {
`${data["group-path"]}/${data["app-name"]}${data["app-path"]}` metadata[appName][appPath] = {};
] = data; }
metadata[appName][appPath] = data;
state.associations = metadata; state.associations = metadata;
} }
@ -60,12 +76,13 @@ export default class MetadataReducer {
let data = _.get(json, 'remove', false); let data = _.get(json, 'remove', false);
if (data) { if (data) {
let metadata = state.associations; let metadata = state.associations;
if (data['group-path'] in metadata) { let appName = data['app-name'];
let path = let appPath = data['app-path'];
`${data['group-path']}/${data['app-name']}${data['app-path']}`
delete metadata[data["group-path"]][path]; if (appName in metadata && appPath in metadata[appName]) {
state.associations = metadata; delete metadata[appName][appPath];
} }
state.associations = metadata;
} }
} }
} }

View File

@ -19,7 +19,11 @@ export default class BaseStore {
} }
handleEvent(data) { handleEvent(data) {
let json = data.data; const json = data.data;
if (json === null) {
return;
}
if ('clear' in json && json.clear) { if ('clear' in json && json.clear) {
this.setState(this.initialState()); this.setState(this.initialState());

View File

@ -12,7 +12,9 @@ export default class LaunchStore extends BaseStore {
launch: { launch: {
firstTime: false, firstTime: false,
tileOrdering: [], tileOrdering: [],
tiles: {} tiles: {},
weather: {},
clock: {}
} }
}; };
} }

View File

@ -1,6 +1,8 @@
import BaseStore from './base'; import BaseStore from './base';
import ContactReducer from '../reducers/contact-update';
import GroupReducer from '../reducers/group-update'; import GroupReducer from '../reducers/group-update';
import LocalReducer from '../reducers/local';
import PublishReducer from '../reducers/publish-update'; import PublishReducer from '../reducers/publish-update';
import InviteReducer from '../reducers/invite-update'; import InviteReducer from '../reducers/invite-update';
import PublishResponseReducer from '../reducers/publish-response'; import PublishResponseReducer from '../reducers/publish-response';
@ -11,7 +13,9 @@ export default class PublishStore extends BaseStore {
constructor() { constructor() {
super(); super();
this.contactReducer = new ContactReducer();
this.groupReducer = new GroupReducer(); this.groupReducer = new GroupReducer();
this.localReducer = new LocalReducer();
this.publishReducer = new PublishReducer(); this.publishReducer = new PublishReducer();
this.inviteReducer = new InviteReducer(); this.inviteReducer = new InviteReducer();
this.responseReducer = new PublishResponseReducer(); this.responseReducer = new PublishResponseReducer();
@ -34,7 +38,9 @@ export default class PublishStore extends BaseStore {
} }
reduce(data, state) { reduce(data, state) {
this.contactReducer.reduce(data, this.state);
this.groupReducer.reduce(data, this.state); this.groupReducer.reduce(data, this.state);
this.localReducer.reduce(data, this.state);
this.publishReducer.reduce(data, this.state); this.publishReducer.reduce(data, this.state);
this.permissionReducer.reduce(data, this.state); this.permissionReducer.reduce(data, this.state);
this.metadataReducer.reduce(data, this.state); this.metadataReducer.reduce(data, this.state);

View File

@ -5,7 +5,7 @@ export default class ChatSubscription extends BaseSubscription {
this.subscribe('/primary', 'chat-view'); this.subscribe('/primary', 'chat-view');
setTimeout(() => { setTimeout(() => {
this.subscribe('/synced', 'chat-hook'); this.subscribe('/synced', 'chat-hook');
this.subscribe('/primary', 'invite-view'); this.subscribe('/all', 'invite-store');
this.subscribe('/all', 'permission-store'); this.subscribe('/all', 'permission-store');
this.subscribe('/primary', 'contact-view'); this.subscribe('/primary', 'contact-view');
this.subscribe('/app-name/chat', 'metadata-store'); this.subscribe('/app-name/chat', 'metadata-store');

View File

@ -2,7 +2,7 @@ import BaseSubscription from './base';
export default class GlobalSubscription extends BaseSubscription { export default class GlobalSubscription extends BaseSubscription {
start() { start() {
this.subscribe('/primary', 'invite-view'); this.subscribe('/all', 'invite-store');
this.subscribe('/app-name/contacts', 'metadata-store'); this.subscribe('/app-name/contacts', 'metadata-store');
} }
} }

View File

@ -6,7 +6,7 @@ export default class GroupsSubscription extends BaseSubscription {
this.subscribe('/all', 'group-store'); this.subscribe('/all', 'group-store');
this.subscribe('/all', 'metadata-store'); this.subscribe('/all', 'metadata-store');
this.subscribe('/synced', 'contact-hook'); this.subscribe('/synced', 'contact-hook');
this.subscribe('/primary', 'invite-view'); this.subscribe('/all', 'invite-store');
this.subscribe('/all', 's3-store'); this.subscribe('/all', 's3-store');
} }
} }

View File

@ -3,6 +3,7 @@ import BaseSubscription from './base';
export default class LaunchSubscription extends BaseSubscription { export default class LaunchSubscription extends BaseSubscription {
start() { start() {
this.subscribe('/all', 'launch'); this.subscribe('/all', 'launch');
this.subscribe('/weathertile', 'weather');
} }
} }

View File

@ -4,7 +4,7 @@ export default class LinksSubscription extends BaseSubscription {
start() { start() {
this.subscribe('/all', 'group-store'); this.subscribe('/all', 'group-store');
this.subscribe('/primary', 'contact-view'); this.subscribe('/primary', 'contact-view');
this.subscribe('/primary', 'invite-view'); this.subscribe('/all', 'invite-store');
this.subscribe('/app-name/link', 'metadata-store'); this.subscribe('/app-name/link', 'metadata-store');
this.subscribe('/app-name/contacts', 'metadata-store'); this.subscribe('/app-name/contacts', 'metadata-store');
this.subscribe('/listening', 'link-listen-hook'); this.subscribe('/listening', 'link-listen-hook');

View File

@ -5,7 +5,7 @@ export default class PublishSubscription extends BaseSubscription {
this.subscribe('/primary', 'publish'); this.subscribe('/primary', 'publish');
this.subscribe('/all', 'group-store'); this.subscribe('/all', 'group-store');
this.subscribe('/primary', 'contact-view'); this.subscribe('/primary', 'contact-view');
this.subscribe('/primary', 'invite-view'); this.subscribe('/all', 'invite-store');
this.subscribe('/all', 'permission-store'); this.subscribe('/all', 'permission-store');
this.subscribe('/app-name/contacts', 'metadata-store'); this.subscribe('/app-name/contacts', 'metadata-store');
} }