diff --git a/bin/solid.pill b/bin/solid.pill index 766a71c0a..cdd7cd826 100644 --- a/bin/solid.pill +++ b/bin/solid.pill @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58deed8e9b8cd2c84d092cb8f638b9881cb0d12b97f7c719339e3604c9e9d1d2 -size 13826033 +oid sha256:575484aaf6c8bc03ab3b962ca52d48a90113bcb38a29a1ac84f2d49d1363b4ba +size 7319532 diff --git a/pkg/arvo/app/invite-view.hoon b/pkg/arvo/app/invite-view.hoon deleted file mode 100644 index 82cdf18df..000000000 --- a/pkg/arvo/app/invite-view.hoon +++ /dev/null @@ -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 --- diff --git a/pkg/arvo/app/landscape/img/Link.png b/pkg/arvo/app/landscape/img/Link.png new file mode 100644 index 000000000..d83bb877c Binary files /dev/null and b/pkg/arvo/app/landscape/img/Link.png differ diff --git a/pkg/arvo/app/publish.hoon b/pkg/arvo/app/publish.hoon index c04180d0d..5e0125a75 100644 --- a/pkg/arvo/app/publish.hoon +++ b/pkg/arvo/app/publish.hoon @@ -2161,7 +2161,6 @@ [[[~ %json] [%'publish-view' %notebooks ~]] ~] %- json-response:gen %- json-to-octs - %+ frond:enjs:format %publish-response (notebooks-map:enjs our.bol books) :: :: notes pagination @@ -2181,7 +2180,6 @@ not-found:gen %- json-response:gen %- json-to-octs - %+ frond:enjs:format %publish-response :- %o (notes-page:enjs notes.u.book u.start u.length) :: @@ -2206,7 +2204,6 @@ not-found:gen %- json-response:gen %- json-to-octs - %+ frond:enjs:format %publish-response (comments-page:enjs comments.u.note u.start u.length) :: :: 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)) =. p.notebook-json (~(put by p.notebook-json) %writers (get-writers-json u.host book-name)) - =/ jon=json - (frond:enjs:format %publish-response (pairs notebook+notebook-json ~)) - (json-response:gen (json-to-octs jon)) + (json-response:gen (json-to-octs (pairs notebook+notebook-json ~))) :: :: single note, with initial 50 comments, as json [[[~ %json] [%'publish-view' @ @ @ ~]] ~] @@ -2241,7 +2236,6 @@ =/ note=(unit note) (~(get by notes.u.book) note-name) ?~ note not-found:gen =/ jon=json - %+ frond %publish-response o+(note-presentation:enjs u.book note-name u.note) (json-response:gen (json-to-octs jon)) == diff --git a/pkg/arvo/lib/chat-store.hoon b/pkg/arvo/lib/chat-store.hoon index d589168d6..1ef713879 100644 --- a/pkg/arvo/lib/chat-store.hoon +++ b/pkg/arvo/lib/chat-store.hoon @@ -56,26 +56,23 @@ [%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 |= upd=^update ^- json %+ frond %chat-update %- 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 %- pairs diff --git a/pkg/arvo/lib/hood/drum.hoon b/pkg/arvo/lib/hood/drum.hoon index 36bc3cfbf..dec82994f 100644 --- a/pkg/arvo/lib/hood/drum.hoon +++ b/pkg/arvo/lib/hood/drum.hoon @@ -104,7 +104,6 @@ %permission-group-hook %invite-store %invite-hook - %invite-view %chat-store %chat-hook %chat-view diff --git a/pkg/arvo/sys/vane/eyre.hoon b/pkg/arvo/sys/vane/eyre.hoon index 58ac07e72..01218dc38 100644 --- a/pkg/arvo/sys/vane/eyre.hoon +++ b/pkg/arvo/sys/vane/eyre.hoon @@ -979,7 +979,7 @@ =/ actual-redirect ?:(=(u.redirect '') '/' u.redirect) %- handle-response :* %start - :- status-code=307 + :- status-code=303 ^= headers :~ ['location' actual-redirect] ['set-cookie' cookie-line] diff --git a/pkg/arvo/tests/sys/vane/eyre.hoon b/pkg/arvo/tests/sys/vane/eyre.hoon index 9d6d67a38..8ce38a774 100644 --- a/pkg/arvo/tests/sys/vane/eyre.hoon +++ b/pkg/arvo/tests/sys/vane/eyre.hoon @@ -613,12 +613,12 @@ ^- (hypo sign:http-server-gate) :- *type :* %g %unto %fact %http-response-header - !>([307 ['location' '/~/login?redirect=/~landscape/inner-path']~]) + !>([303 ['location' '/~/login?redirect=/~landscape/inner-path']~]) == == ^= expected-move :~ :* 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 :: @@ -2152,7 +2152,7 @@ %give %response %start - :- 307 + :- 303 :~ ['location' '/~landscape'] :- 'set-cookie' 'urbauth-~nul=0v3.q0p7t.mlkkq.cqtto.p0nvi.2ieea; Path=/; Max-Age=604800' diff --git a/pkg/interface/config/urbitrc-sample b/pkg/interface/config/urbitrc-sample index 88053638e..1315a1d06 100644 --- a/pkg/interface/config/urbitrc-sample +++ b/pkg/interface/config/urbitrc-sample @@ -1,5 +1,6 @@ module.exports = { URBIT_PIERS: [ "/Users/user/ships/zod/home", - ] + ], + herb: false }; diff --git a/pkg/interface/config/webpack.dev.js b/pkg/interface/config/webpack.dev.js index 7d972ee62..caea66830 100644 --- a/pkg/interface/config/webpack.dev.js +++ b/pkg/interface/config/webpack.dev.js @@ -2,6 +2,41 @@ const path = require('path'); // const HtmlWebpackPlugin = require('html-webpack-plugin'); // const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 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 = { mode: 'development', @@ -49,16 +84,18 @@ module.exports = { // historyApiFallback: true // }, plugins: [ + new UrbitShipPlugin(urbitrc) // new CleanWebpackPlugin(), // new HtmlWebpackPlugin({ // title: 'Hot Module Replacement', // template: './public/index.html', // }), ], + watch: true, output: { filename: 'index.js', chunkFilename: 'index.js', - path: path.resolve(urbitrc.URBIT_PIERS[0] + '/app/landscape/', 'js'), + path: path.resolve(__dirname, '../dist'), publicPath: '/' }, optimization: { diff --git a/pkg/interface/src/App.js b/pkg/interface/src/App.js index f0c08ebb3..34c8d2919 100644 --- a/pkg/interface/src/App.js +++ b/pkg/interface/src/App.js @@ -1,5 +1,5 @@ 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 './css/indigo-static.css'; import './css/fonts.css'; @@ -8,11 +8,13 @@ import { light } from '@tlon/indigo-react'; import LaunchApp from './apps/launch/app'; import ChatApp from './apps/chat/app'; import DojoApp from './apps/dojo/app'; -import StatusBar from './components/StatusBar'; import GroupsApp from './apps/groups/app'; import LinksApp from './apps/links/app'; import PublishApp from './apps/publish/app'; +import StatusBar from './components/StatusBar'; +import NotFound from './components/404'; + import GlobalStore from './store/global'; import GlobalSubscription from './subscription/global'; import GlobalApi from './api/global'; @@ -71,48 +73,64 @@ export default class App extends React.Component { api={this.api} />
- ( + + ( - )} /> + {...p} + /> + )} + /> ( - )} /> + {...p} + /> + )} + /> ( - )} /> + {...p} + /> + )} + /> ( - )} /> + {...p} + /> + )} + /> ( - )} /> + {...p} + /> + )} + /> ( - )} /> + {...p} + /> + )} + /> + +
diff --git a/pkg/interface/src/api/chat.js b/pkg/interface/src/api/chat.js index 3a9014eb9..aeb7d23e9 100644 --- a/pkg/interface/src/api/chat.js +++ b/pkg/interface/src/api/chat.js @@ -1,4 +1,5 @@ import BaseApi from './base'; +import { uuid } from '../lib/util'; export default class ChatApi { constructor(ship, channel, store) { @@ -36,6 +37,7 @@ export default class ChatApi { this.metadata = { add: helper.metadataAdd.bind(helper) }; + this.sidebarToggle = helper.sidebarToggle.bind(helper); } } @@ -66,7 +68,7 @@ class PrivateHelper extends BaseApi { addPendingMessage(msg) { 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 { 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 + } + } + }); + } } diff --git a/pkg/interface/src/api/groups.js b/pkg/interface/src/api/groups.js index 58b18d6b2..a735201c8 100644 --- a/pkg/interface/src/api/groups.js +++ b/pkg/interface/src/api/groups.js @@ -21,7 +21,11 @@ export default class GroupsApi { this.group = { add: helper.groupAdd.bind(helper), - delete: helper.groupRemove.bind(helper) + remove: helper.groupRemove.bind(helper) + }; + + this.metadata = { + add: helper.metadataAdd.bind(helper) }; this.invite = { diff --git a/pkg/interface/src/api/launch.js b/pkg/interface/src/api/launch.js index 5dcedd75e..1958a8a4c 100644 --- a/pkg/interface/src/api/launch.js +++ b/pkg/interface/src/api/launch.js @@ -24,6 +24,14 @@ class PrivateHelper extends BaseApi { launchChangeIsShown(name, isShown = true) { 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 { @@ -40,6 +48,10 @@ export default class LaunchApi { changeFirstTime: helper.launchChangeFirstTime.bind(helper), changeIsShown: helper.launchChangeIsShown.bind(helper) }; + + this.clock = { action: helper.clockAction.bind(helper) }; + + this.weather ={ action: helper.weatherAction.bind(helper) }; } } diff --git a/pkg/interface/src/api/links.js b/pkg/interface/src/api/links.js index 4f7172718..683a3d069 100644 --- a/pkg/interface/src/api/links.js +++ b/pkg/interface/src/api/links.js @@ -1,9 +1,7 @@ -import _ from 'lodash'; import { stringToTa } from '../lib/util'; import BaseApi from './base'; - export default class LinksApi extends BaseApi { constructor(ship, channel, store) { super(ship, channel, store); diff --git a/pkg/interface/src/api/publish.js b/pkg/interface/src/api/publish.js index 453fa874e..68480118b 100644 --- a/pkg/interface/src/api/publish.js +++ b/pkg/interface/src/api/publish.js @@ -1,12 +1,15 @@ import BaseApi from './base'; - export default class PublishApi extends BaseApi { + handleEvent(data) { + this.store.handleEvent({ data: { 'publish-response' : data } }); + } + fetchNotebooks() { fetch('/publish-view/notebooks.json') .then(response => response.json()) .then((json) => { - this.store.handleEvent({ + this.handleEvent({ type: 'notebooks', data: json }); @@ -17,7 +20,7 @@ export default class PublishApi extends BaseApi { fetch(`/publish-view/${host}/${book}.json`) .then(response => response.json()) .then((json) => { - this.store.handleEvent({ + this.handleEvent({ type: 'notebook', data: json, host: host, @@ -30,7 +33,7 @@ export default class PublishApi extends BaseApi { fetch(`/publish-view/${host}/${book}/${note}.json`) .then(response => response.json()) .then((json) => { - this.store.handleEvent({ + this.handleEvent({ type: 'note', data: json, host: host, @@ -44,7 +47,7 @@ export default class PublishApi extends BaseApi { fetch(`/publish-view/notes/${host}/${book}/${start}/${length}.json`) .then(response => response.json()) .then((json) => { - this.store.handleEvent({ + this.handleEvent({ type: 'notes-page', data: json, host: host, @@ -59,7 +62,7 @@ export default class PublishApi extends BaseApi { fetch(`/publish-view/comments/${host}/${book}/${note}/${start}/${length}.json`) .then(response => response.json()) .then((json) => { - this.store.handleEvent({ + this.handleEvent({ type: 'comments-page', data: json, 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() { let sidebarBoolean = true; if (this.store.state.sidebarShown === true) { sidebarBoolean = false; } this.store.handleEvent({ - type: 'local', data: { - 'sidebarToggle': sidebarBoolean + local: { + sidebarToggle: sidebarBoolean + } } }); } diff --git a/pkg/interface/src/apps/chat/app.js b/pkg/interface/src/apps/chat/app.js index a4d9c2713..32a97abf1 100644 --- a/pkg/interface/src/apps/chat/app.js +++ b/pkg/interface/src/apps/chat/app.js @@ -32,7 +32,7 @@ export default class ChatApp extends React.Component { } componentDidMount() { - window.title = 'OS1 - Chat'; + document.title = 'OS1 - Chat'; // preload spinner asset new Image().src = '/~landscape/img/Spinner.png'; @@ -74,7 +74,7 @@ export default class ChatApp extends React.Component { }); if (totalUnreads !== this.totalUnreads) { - document.title = totalUnreads > 0 ? `Chat - (${totalUnreads})` : 'Chat'; + document.title = totalUnreads > 0 ? `OS1 - Chat (${totalUnreads})` : 'OS1 - Chat'; this.totalUnreads = totalUnreads; } @@ -310,7 +310,7 @@ export default class ChatApp extends React.Component { > { style={{ height: 48 }} > { station={props.station} numPeers={group.length} isOwner={deSig(props.match.params.ship) === window.ship} - popout={this.props.popout} + popout={props.popout} api={props.api} /> diff --git a/pkg/interface/src/apps/chat/components/settings.js b/pkg/interface/src/apps/chat/components/settings.js index b4fdb1525..804ceebcc 100644 --- a/pkg/interface/src/apps/chat/components/settings.js +++ b/pkg/interface/src/apps/chat/components/settings.js @@ -108,7 +108,7 @@ export class SettingsScreen extends Component { if (chatOwner) { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { - props.api.metadataAdd( + props.api.metadata.add( association['app-path'], association['group-path'], association.metadata.title, @@ -272,16 +272,16 @@ export class SettingsScreen extends Component { { if (chatOwner) { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { - props.api.metadataAdd( + props.api.metadata.add( association['app-path'], association['group-path'], - this.state.title, + state.title, association.metadata.description, association.metadata['date-created'], uxToHex(association.metadata.color) @@ -301,17 +301,17 @@ export class SettingsScreen extends Component { { if (chatOwner) { this.setState({ awaiting: true, type: 'Editing chat...' }, (() => { - props.api.metadataAdd( + props.api.metadata.add( association['app-path'], association['group-path'], association.metadata.title, - this.state.description, + state.description, association.metadata['date-created'], uxToHex(association.metadata.color) ).then(() => { @@ -339,7 +339,7 @@ export class SettingsScreen extends Component {
- +
); @@ -477,7 +479,9 @@ export class SettingsScreen extends Component { {this.renderGroupify()} {this.renderDelete()} {this.renderMetadataSettings()} - + ); diff --git a/pkg/interface/src/apps/chat/components/sidebar.js b/pkg/interface/src/apps/chat/components/sidebar.js index 6f74633fd..dc44f8e0f 100644 --- a/pkg/interface/src/apps/chat/components/sidebar.js +++ b/pkg/interface/src/apps/chat/components/sidebar.js @@ -14,6 +14,7 @@ export class Sidebar extends Component { dmOverlay: false }; } + onClickNew() { this.props.history.push('/~chat/new'); } @@ -37,10 +38,14 @@ export class Sidebar extends Component { const selectedGroups = props.selectedGroups ? props.selectedGroups : []; - const associations = + const contactAssoc = (props.associations && 'contacts' in props.associations) ? alphabetiseAssociations(props.associations.contacts) : {}; + const chatAssoc = + (props.associations && 'chat' in props.associations) + ? alphabetiseAssociations(props.associations.chat) : {}; + const groupedChannels = {}; Object.keys(props.inbox).map((box) => { if (box.startsWith('/~/')) { @@ -51,16 +56,18 @@ export class Sidebar extends Component { } else { groupedChannels['/~/'] = [box]; } - } - const path = props.associations.chat[box] - ? props.associations.chat[box]['group-path'] : box; - if (path in associations) { - if (groupedChannels[path]) { - const array = groupedChannels[path]; - array.push(box); - groupedChannels[path] = array; - } else { - groupedChannels[path] = [box]; + } else { + const path = chatAssoc[box] + ? chatAssoc[box]['group-path'] : box; + + if (path in contactAssoc) { + if (groupedChannels[path]) { + const array = groupedChannels[path]; + array.push(box); + groupedChannels[path] = array; + } 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) => { if (selectedGroups.length === 0) { @@ -94,8 +101,8 @@ export class Sidebar extends Component {
{this.props.commandLog.map((text, index) => { diff --git a/pkg/interface/src/apps/dojo/components/lib/icons/popout.js b/pkg/interface/src/apps/dojo/components/lib/icons/popout.js index ca59bced7..fe4dd684c 100644 --- a/pkg/interface/src/apps/dojo/components/lib/icons/popout.js +++ b/pkg/interface/src/apps/dojo/components/lib/icons/popout.js @@ -7,7 +7,7 @@ export class Popout extends Component { : 'dib-m dib-l dib-xl'; return (
diff --git a/pkg/interface/src/apps/groups/components/lib/contact-card.js b/pkg/interface/src/apps/groups/components/lib/contact-card.js index 937090ee2..4e84fe104 100644 --- a/pkg/interface/src/apps/groups/components/lib/contact-card.js +++ b/pkg/interface/src/apps/groups/components/lib/contact-card.js @@ -141,12 +141,10 @@ export class ContactCard extends Component { type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { - avatar: { - url: state.avatarToSet - } - }) + props.api.contactHook.edit(props.path, ship, { + avatar: { + url: state.avatarToSet + }}) .then(() => { this.setState({ awaiting: false }); }); @@ -163,8 +161,8 @@ export class ContactCard extends Component { if (hexTest && hexTest[1] !== currentColor && !props.share) { this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, `~${props.ship}`, { color: hexTest[1] }) + props.api.contactHook.edit( + props.path, `~${props.ship}`, { color: hexTest[1] }) .then(() => { this.setState({ awaiting: false }); }); @@ -182,8 +180,8 @@ export class ContactCard extends Component { const emailTestResult = emailTest.exec(state.emailToSet); if (emailTestResult) { this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { email: state.emailToSet }) + props.api.contactHook.edit( + props.path, ship, { email: state.emailToSet }) .then(() => { this.setState({ awaiting: false }); }); @@ -199,8 +197,8 @@ export class ContactCard extends Component { return false; } this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { nickname: state.nickNameToSet }) + props.api.contactHook.edit( + props.path, ship, { nickname: state.nickNameToSet }) .then(() => { this.setState({ awaiting: false }); }); @@ -216,8 +214,8 @@ export class ContactCard extends Component { return false; } this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { notes: state.notesToSet }) + props.api.contactHook.edit( + props.path, ship, { notes: state.notesToSet }) .then(() => { this.setState({ awaiting: false }); }); @@ -234,8 +232,8 @@ export class ContactCard extends Component { const phoneTestResult = phoneTest.exec(state.phoneToSet); if (phoneTestResult) { this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { phone: state.phoneToSet }) + props.api.contactHook.edit( + props.path, ship, { phone: state.phoneToSet }) .then(() => { this.setState({ awaiting: false }); }); @@ -253,8 +251,8 @@ export class ContactCard extends Component { const websiteTestResult = websiteTest.exec(state.websiteToSet); if (websiteTestResult) { this.setState({ awaiting: true, type: 'Saving to group' }, () => { - props.api - .contactEdit(props.path, ship, { website: state.websiteToSet }) + props.api.contactHook.edit( + props.path, ship, { website: state.websiteToSet }) .then(() => { this.setState({ awaiting: false }); }); @@ -266,9 +264,10 @@ export class ContactCard extends Component { this.setState( { emailToSet: '', awaiting: true, type: 'Removing from group' }, () => { - props.api.contactEdit(props.path, ship, { email: '' }).then(() => { - this.setState({ awaiting: false }); - }); + props.api.contactHook.edit(props.path, ship, { email: '' }) + .then(() => { + this.setState({ awaiting: false }); + }); } ); break; @@ -277,9 +276,10 @@ export class ContactCard extends Component { this.setState( { nicknameToSet: '', awaiting: true, type: 'Removing from group' }, () => { - props.api.contactEdit(props.path, ship, { nickname: '' }).then(() => { - this.setState({ awaiting: false }); - }); + props.api.contactHook.edit(props.path, ship, { nickname: '' }) + .then(() => { + this.setState({ awaiting: false }); + }); } ); break; @@ -288,7 +288,7 @@ export class ContactCard extends Component { this.setState( { 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 }); }); } @@ -299,7 +299,7 @@ export class ContactCard extends Component { this.setState( { 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 }); }); } @@ -314,7 +314,7 @@ export class ContactCard extends Component { 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 }); }); } @@ -325,7 +325,7 @@ export class ContactCard extends Component { this.setState( { 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 }); }); } @@ -519,9 +519,10 @@ export class ContactCard extends Component { key={'avatar' + currentColor} />
+

Sigil Color

+

Ship Name

diff --git a/pkg/interface/src/apps/groups/components/lib/contact-sidebar.js b/pkg/interface/src/apps/groups/components/lib/contact-sidebar.js index b2552ddeb..80838ac35 100644 --- a/pkg/interface/src/apps/groups/components/lib/contact-sidebar.js +++ b/pkg/interface/src/apps/groups/components/lib/contact-sidebar.js @@ -99,7 +99,7 @@ export class ContactSidebar extends Component { style={{ paddingTop: 6 }} onClick={() => { this.setState({ awaiting: true }, (() => { - props.api.groupRemove(props.path, [`~${member}`]) + props.api.group.remove(props.path, [`~${member}`]) .then(() => { this.setState({ awaiting: false }); }); diff --git a/pkg/interface/src/apps/groups/components/lib/group-detail.js b/pkg/interface/src/apps/groups/components/lib/group-detail.js index 7fdfb6a1c..9d71acb0e 100644 --- a/pkg/interface/src/apps/groups/components/lib/group-detail.js +++ b/pkg/interface/src/apps/groups/components/lib/group-detail.js @@ -18,11 +18,10 @@ export class GroupDetail extends Component { componentDidMount() { const { props } = this; - const channelPath = `${props.path}/contacts${props.path}`; - if ((props.association) && (props.association[channelPath])) { + if (props.association.metadata) { this.setState({ - title: props.association[channelPath].metadata.title, - description: props.association[channelPath].metadata.description + title: props.association.metadata.title, + description: props.association.metadata.description }); } } @@ -30,11 +29,10 @@ export class GroupDetail extends Component { componentDidUpdate(prevProps) { const { props } = this; if (prevProps !== this.props) { - const channelPath = `${props.path}/contacts${props.path}`; - if ((props.association) && (props.association[channelPath])) { + if (props.association.metadata) { this.setState({ - title: props.association[channelPath].metadata.title, - description: props.association[channelPath].metadata.description + title: props.association.metadata.title, + description: props.association.metadata.description }); } } @@ -54,78 +52,74 @@ export class GroupDetail extends Component { const responsiveClass = props.activeDrawer === 'detail' ? 'db ' : 'dn db-ns '; - const isEmpty = (Object.keys(props.association).length === 0) || - ((Object.keys(props.association).length === 1) && - (Object.keys(props.association)[0].includes('contacts'))); + let channelList = []; - let channelList = (
); + 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 aChannel = props.association[a]; - const bChannel = props.association[b]; - - const aTitle = aChannel.metadata.title || a; - const bTitle = bChannel.metadata.title || b; - - return aTitle.toLowerCase().localeCompare(bTitle.toLowerCase()); - }).map((key) => { - const channel = props.association[key]; - if (!('metadata' in channel)) { - return
; - } - - if (channel['app-name'] === 'contacts') { - return
; - } - - 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') - ?
- :
- -
; - - return ( -
  • - {tile} -
    -

    {title}

    -

    - {app} - - Open - -

    -
    -
  • - ); + const color = uxToHex(channelObj.metadata?.color) || '000000'; + const channelPath = channelObj['app-path']; + const link = `/~${app}/join${channelPath}`; + return( + channelList.push({ + title: title, + color: color, + app: app.charAt(0).toUpperCase() + app.slice(1), + link: link + }) + ); + }); }); + const isEmpty = (Boolean(channelList.length === 0)); + + if (channelList.length === 0) { + channelList =
    ; + } 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 ( +
  • +
    + +
    +
    +

    {each.title}

    +

    + {each.app} + + Open + +

    +
    +
  • + ); + }); + } + let backLink = props.location.pathname; 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 description = ''; - const channel = `${props.path}/contacts${props.path}`; - if ((props.association) && (props.association[channel])) { - title = (props.association[channel].metadata.title !== '') - ? props.association[channel].metadata.title + if (props.association?.metadata) { + title = (props.association.metadata.title !== '') + ? props.association.metadata.title : props.path.substr(1); - description = (props.association[channel].metadata.description !== '') - ? props.association[channel].metadata.description + description = (props.association.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 channelPath = `${props.path}/contacts${props.path}`; - - const association = ((props.association) && (props.association[channelPath])) - ? props.association[channelPath] : {}; + const association = props.association; 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={() => { if (groupOwner) { this.setState({ awaiting: true }, (() => { - props.api.metadataAdd( + props.api.metadata.add( association['app-path'], association['group-path'], this.state.title, @@ -237,7 +227,7 @@ export class GroupDetail extends Component { onBlur={() => { if (groupOwner) { this.setState({ awaiting: true }, (() => { - props.api.metadataAdd( + props.api.metadata.add( association['app-path'], association['group-path'], association.metadata.title, diff --git a/pkg/interface/src/apps/groups/components/lib/group-sidebar.js b/pkg/interface/src/apps/groups/components/lib/group-sidebar.js index a4171b408..814a2f4d8 100644 --- a/pkg/interface/src/apps/groups/components/lib/group-sidebar.js +++ b/pkg/interface/src/apps/groups/components/lib/group-sidebar.js @@ -69,33 +69,23 @@ export class GroupSidebar extends Component { return true; } const selectedPaths = selectedGroups.map(((e) => { - return e[0]; -})); + return e[0]; + })); return (selectedPaths.includes(path)); }) .sort((a, b) => { let aName = a.substr(1); let bName = b.substr(1); - const aChannel = `${a}/contacts${a}`; - const bChannel = `${b}/contacts${b}`; - if ( - props.associations[a] && - props.associations[a][aChannel] && - props.associations[a][aChannel].metadata - ) { + if (props.associations.contacts?.[a]?.metadata) { aName = - props.associations[a][aChannel].metadata.title !== '' - ? props.associations[a][aChannel].metadata.title + props.associations.contacts[a].metadata.title !== '' + ? props.associations.contacts[a].metadata.title : a.substr(1); } - if ( - props.associations[b] && - props.associations[b][bChannel] && - props.associations[b][bChannel].metadata - ) { + if (props.associations.contacts?.[b]?.metadata) { bName = - props.associations[b][bChannel].metadata.title !== '' - ? props.associations[b][bChannel].metadata.title + props.associations.contacts[b].metadata.title !== '' + ? props.associations.contacts[b].metadata.title : b.substr(1); } @@ -104,15 +94,10 @@ export class GroupSidebar extends Component { .map((path) => { let name = path.substr(1); const selected = props.selected === path; - const groupChannel = `${path}/contacts${path}`; - if ( - props.associations[path] && - props.associations[path][groupChannel] && - props.associations[path][groupChannel].metadata - ) { + if (props.associations.contacts?.[path]?.metadata) { name = - props.associations[path][groupChannel].metadata.title !== '' - ? props.associations[path][groupChannel].metadata.title + props.associations.contacts[path].metadata.title !== '' + ? props.associations.contacts[path].metadata.title : path.substr(1); } return ( diff --git a/pkg/interface/src/apps/launch/app.js b/pkg/interface/src/apps/launch/app.js index 5c5c79d30..6aa903c40 100644 --- a/pkg/interface/src/apps/launch/app.js +++ b/pkg/interface/src/apps/launch/app.js @@ -23,7 +23,7 @@ export default class LaunchApp extends React.Component { } componentDidMount() { - window.title = 'OS1 - Home'; + document.title = 'OS1 - Home'; // preload spinner asset new Image().src = '/~landscape/img/Spinner.png'; diff --git a/pkg/interface/src/apps/launch/components/tiles/clock.js b/pkg/interface/src/apps/launch/components/tiles/clock.js index 5c6927ea0..67c3bcd62 100644 --- a/pkg/interface/src/apps/launch/components/tiles/clock.js +++ b/pkg/interface/src/apps/launch/components/tiles/clock.js @@ -1,14 +1,12 @@ import React from 'react'; -import classnames from 'classnames'; import moment from 'moment'; import SunCalc from 'suncalc'; 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) { // return { // 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) { - text = "#7f7f7f"; - background = "#333"; + text = '#7f7f7f'; + background = '#333'; } function darkColors(dark) { if (dark.matches) { - text = "#7f7f7f"; - background = "#333"; + text = '#7f7f7f'; + background = '#333'; } else { - text = "#000000"; - background = "#ffffff" + text = '#000000'; + background = '#ffffff'; } } dark.addListener(darkColors); - const toRelativeTime = (date, referenceTime, unit) => moment(date) - .diff(referenceTime, unit) + .diff(referenceTime, unit); const minsToDegs = (mins) => { // 1440 = total minutes in an earth day - return (mins / 1440) * 360 -} + return (mins / 1440) * 360; +}; -const clockwise = (deg, delta) => deg + delta - -const anticlockwise = (deg, delta) => deg - delta - -const splitArc = (start, end) => end + ((start - end) * 0.5) +const splitArc = (start, end) => end + ((start - end) * 0.5); 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) => { - return minsToDegs(toRelativeTime(date, referenceTime, 'minutes')) -} + return minsToDegs(toRelativeTime(date, referenceTime, 'minutes')); +}; const circle = (ctx, x, y, r, from, to, fill) => { ctx.beginPath(); - ctx.arc( x, y, r, from, to, ); + ctx.arc( x, y, r, from, to ); ctx.strokeStyle = 'rgba(0,0,0,0)'; ctx.fillStyle = fill || 'rgba(0,0,0,0)'; ctx.fill(); -} +}; const circleOutline = (ctx, x, y, r, from, to, stroke, lineWidth) => { ctx.beginPath(); - ctx.arc( x, y, r, from, to, ); + ctx.arc( x, y, r, from, to ); ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.lineWidth = lineWidth; ctx.strokeStyle = stroke || 'rgba(0,0,0,0)'; ctx.stroke(); -} +}; const arc = (ctx, x, y, r, from, to, fill) => { ctx.beginPath(); - ctx.arc( x, y, r, from, to, ); + ctx.arc( x, y, r, from, to ); ctx.fillStyle = 'rgba(0,0,0,0)'; ctx.lineWidth = r * 2; ctx.strokeStyle = fill || 'rgba(0,0,0,0)'; ctx.stroke(); -} +}; const degArc = (ctx, x, y, r, from, to, fill) => { ctx.beginPath(); @@ -95,17 +88,16 @@ const degArc = (ctx, x, y, r, from, to, fill) => { ctx.lineWidth = r * 2; ctx.strokeStyle = fill || 'rgba(0,0,0,0)'; ctx.stroke(); -} +}; class Clock extends React.Component { - constructor(props) { super(props); this.animate = this.animate.bind(this); this.canvasRef = React.createRef(); this.canvas = null; this.angle = 0; - this.referenceTime = moment().startOf('day').subtract(6, 'hours') + this.referenceTime = moment().startOf('day').subtract(6, 'hours'); this.state = { lat: 0, lon: 0, @@ -119,23 +111,22 @@ class Clock extends React.Component { night: 0, nightEnd: 0, nauticalDawn: 0, - nauticalDusk: 0, + nauticalDusk: 0 // sunriseStartTime = 1509967519, // sunriseEndTime = 2500 + 1509967519, // sunsetStartTime = 1510003982, // sunsetEndTime // moonPhase = 0.59, - } - + }; } initGeolocation() { if (typeof this.props.data === 'string') { - const latlon = this.props.data.split(',') - const lat = latlon[0] - const lon = latlon[1] + const latlon = this.props.data.split(','); + const lat = latlon[0]; + const lon = latlon[1]; - const suncalc = SunCalc.getTimes(new Date(), lat, lon) + const suncalc = SunCalc.getTimes(new Date(), lat, lon); const convertedSunCalc = { sunset: convert(suncalc.sunset, this.referenceTime), @@ -147,25 +138,24 @@ class Clock extends React.Component { night: convert(suncalc.night, this.referenceTime), nightEnd: convert(suncalc.nightEnd, this.referenceTime), nauticalDawn: convert(suncalc.nauticalDawn, this.referenceTime), - nauticalDusk: convert(suncalc.nauticalDusk, this.referenceTime), - } + nauticalDusk: convert(suncalc.nauticalDusk, this.referenceTime) + }; this.setState({ lat, lon, ...convertedSunCalc, - geolocationSuccess: true, - }) + geolocationSuccess: true + }); } } componentDidUpdate(prevProps) { if (prevProps !== this.props) { - this.initGeolocation() + this.initGeolocation(); } } - componentDidMount() { this.canvas = initCanvas( this.canvasRef, @@ -173,29 +163,27 @@ class Clock extends React.Component { 4 ); - this.initGeolocation() - this.animate() + this.initGeolocation(); + this.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 ctx = this.canvas.getContext("2d"); + const ctx = this.canvas.getContext('2d'); ctx.clearRect(0, 0, ctx.width, ctx.height); ctx.save(); - const ctr = innerSize / 2 + const ctr = innerSize / 2; // Sun+moon calculations - const dd = 4 - var cx = ctr - var cy = ctr - this.angle = degToRad(convert(time, this.referenceTime)) - var newX = cx + (ctr - 15) * Math.cos(this.angle); - var newY = cy + (ctr - 15) * Math.sin(this.angle); + const cx = ctr; + const cy = ctr; + this.angle = degToRad(convert(time, this.referenceTime)); + const newX = cx + (ctr - 15) * Math.cos(this.angle); + const newY = cy + (ctr - 15) * Math.sin(this.angle); // Initial background circle( @@ -206,7 +194,7 @@ class Clock extends React.Component { -1, 2 * Math.PI, background - ) + ); // Day degArc( @@ -276,7 +264,7 @@ class Clock extends React.Component { 0, 2 * Math.PI, '#FCC440' - ) + ); // Sun circle border circleOutline( @@ -299,7 +287,7 @@ class Clock extends React.Component { 0, 2 * Math.PI, '#FFFFFF' - ) + ); // Moon circle outline circleOutline( ctx, @@ -357,7 +345,7 @@ class Clock extends React.Component { -1, 2 * Math.PI, background - ) + ); // Center white circle border circleOutline( @@ -374,26 +362,27 @@ class Clock extends React.Component { // Text for time and date const timeText = isOdd(time.getSeconds()) ? moment().format('h mm A') - : moment().format('h:mm A') - const dateText = moment().format('MMM Do') - ctx.textAlign = 'center' - ctx.fillStyle = text - ctx.font = '12px Inter' - ctx.fillText(timeText, ctr, ctr + 6 - 7) - ctx.fillStyle = text - ctx.font = '12px Inter' - ctx.fillText(dateText, ctr, ctr + 6 + 7) + : moment().format('h:mm A'); + const dateText = moment().format('MMM Do'); + ctx.textAlign = 'center'; + ctx.fillStyle = text; + ctx.font = '12px Inter'; + ctx.fillText(timeText, ctr, ctr + 6 - 7); + ctx.fillStyle = text; + ctx.font = '12px Inter'; + ctx.fillText(dateText, ctr, ctr + 6 + 7); ctx.restore(); } render() { - return
    + return
    this.canvasRef = canvasRef } - id="clock-canvas"/> -
    + id="clock-canvas" + /> +
    ; } } @@ -411,27 +400,15 @@ export default class ClockTile extends React.Component { } render() { - let data = !!this.props.data ? this.props.data : {}; + const data = this.props.data ? this.props.data : {}; return this.renderWrapper(( - + )); - } - } -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 { x, y } = size; - let ctx = canvas.getContext('2d'); - // let ratio = ctx.webkitBackingStorePixelRatio < 2 // ? window.devicePixelRatio // : 1; @@ -439,7 +416,6 @@ const initCanvas = (canvas, size, ratio) => { // default for high print resolution. // ratio = ratio * resMult; - canvas.width = x * ratio; canvas.height = y * ratio; canvas.style.width = x + 'px'; @@ -448,5 +424,5 @@ const initCanvas = (canvas, size, ratio) => { canvas.getContext('2d').scale(ratio, ratio); return canvas; -} +}; diff --git a/pkg/interface/src/apps/launch/components/tiles/weather.js b/pkg/interface/src/apps/launch/components/tiles/weather.js index 530ad2193..997703262 100644 --- a/pkg/interface/src/apps/launch/components/tiles/weather.js +++ b/pkg/interface/src/apps/launch/components/tiles/weather.js @@ -1,5 +1,4 @@ import React from 'react'; -import classnames from 'classnames'; import moment from 'moment'; import Tile from './tile'; @@ -13,121 +12,123 @@ export default class WeatherTile extends React.Component { error: false }; - let api = props.api; + this.api = props.api; } // geolocation and manual input functions locationSubmit() { navigator.geolocation.getCurrentPosition((res) => { - let latlng = `${res.coords.latitude},${res.coords.longitude}`; + const latlng = `${res.coords.latitude},${res.coords.longitude}`; this.setState({ latlng }, (err) => { console.log(err); }, { maximumAge: Infinity, timeout: 10000 }); - api.action("clock", "json", latlng); - api.action('weather', 'json', latlng); + // this.api.clock.action(latlng); + this.api.weather.action(latlng); this.setState({ manualEntry: !this.state.manualEntry }); }); } manualLocationSubmit() { event.preventDefault(); - let gpsInput = document.getElementById('gps'); - let latlngNoSpace = gpsInput.value.replace(/\s+/g, ''); - let latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g; + const gpsInput = document.getElementById('gps'); + const latlngNoSpace = gpsInput.value.replace(/\s+/g, ''); + const latlngParse = /-?[0-9]+(?:\.[0-9]*)?,-?[0-9]+(?:\.[0-9]*)?/g; if (latlngParse.test(latlngNoSpace)) { - let latlng = latlngNoSpace; - this.setState({latlng}, (err) => {console.log(err)}, {maximumAge: Infinity, timeout: 10000}); - api.action("clock", "json", latlng); - api.action('weather', 'json', latlng); - this.setState({manualEntry: !this.state.manualEntry}); - } - else { - this.setState({error: true}); + const latlng = latlngNoSpace; + this.setState({ latlng }, (err) => { + console.log(err); + }, { maximumAge: Infinity, timeout: 10000 }); + // this.api.clock.action(latlng); + this.api.weather.action(latlng); + this.setState({ manualEntry: !this.state.manualEntry }); + } else { + this.setState({ error: true }); return false; } } // set appearance based on weather setColors(data) { let weatherStyle = { - gradient1: "", - gradient2: "", - text: "" + gradient1: '', + gradient2: '', + text: '' }; switch (data.daily.icon) { - case "clear-day": + case 'clear-day': weatherStyle = { - 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" + gradient1: '#A5CEF0', gradient2: '#FEF4E0', text: 'black' }; break; - case "snow": + case 'clear-night': weatherStyle = { - gradient1: "#eee", gradient2: "#f9f9f9", text: "black" + gradient1: '#56668e', gradient2: '#000080', text: 'white' }; break; - case "sleet": + case 'rain': weatherStyle = { - gradient1: "#eee", gradient2: "#f9f9f9", text: "black" + gradient1: '#b1b2b3', gradient2: '#b0c7ff', text: 'black' }; break; - case "wind": + case 'snow': weatherStyle = { - gradient1: "#eee", gradient2: "#fff", text: "black" + gradient1: '#eee', gradient2: '#f9f9f9', text: 'black' }; break; - case "fog": + case 'sleet': weatherStyle = { - gradient1: "#eee", gradient2: "#fff", text: "black" + gradient1: '#eee', gradient2: '#f9f9f9', text: 'black' }; break; - case "cloudy": + case 'wind': weatherStyle = { - gradient1: "#eee", gradient2: "#b1b2b3", text: "black" + gradient1: '#eee', gradient2: '#fff', text: 'black' }; break; - case "partly-cloudy-day": + case 'fog': weatherStyle = { - gradient1: "#fcc440", gradient2: "#b1b2b3", text: "black" + gradient1: '#eee', gradient2: '#fff', text: 'black' }; break; - case "partly-cloudy-night": + case 'cloudy': 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; default: weatherStyle = { - gradient1: "white", gradient2: "white", text: "black" + gradient1: 'white', gradient2: 'white', text: 'black' }; } return weatherStyle; } // all tile views renderWrapper(child, - weatherStyle = { gradient1: "white", gradient2: "white", text: "black" } + weatherStyle = { gradient1: 'white', gradient2: 'white', text: 'black' } ) { return (
    + }} + > {child}
    @@ -139,56 +140,65 @@ export default class WeatherTile extends React.Component { let error; if (this.state.error === true) { error =

    Please try again. -

    + className="f9 red2 pt1" + >Please try again. +

    ; } - if (location.protocol === "https:") { + if (location.protocol === 'https:') { secureCheck = this.locationSubmit()}>Detect -> + style={{ right: 8, top: 8 }} + onClick={() => this.locationSubmit()} + >Detect ->; } return this.renderWrapper( -
    +
    this.setState({ manualEntry: !this.state.manualEntry }) - }> + } + > <- {secureCheck} -

    - Please enter your{" "} +

    + Please enter your{' '} + target="_blank" + > latitude and longitude .

    {error} -
    -
    +
    + { - if (e.key === "Enter") { + if (e.key === 'Enter') { e.preventDefault(); this.manualLocationSubmit(e.target.value); - }} - }/> + } +} + } + /> this.manualLocationSubmit()} - value="->"/> + value="->" + />
    @@ -198,15 +208,18 @@ export default class WeatherTile extends React.Component { renderNoData() { return this.renderWrapper((
    this.setState({manualEntry: !this.state.manualEntry})}> + className={'pa2 w-100 h-100 b--black b--gray1-d ba ' + + 'bg-white bg-gray0-d black white-d'} + onClick={() => this.setState({ manualEntry: !this.state.manualEntry })} + >

    + style={{ left: 8, top: 8 }} + > Weather

    + style={{ bottom: 8, left: 8, cursor: 'pointer' }} + > -> Set location

    @@ -214,14 +227,15 @@ export default class WeatherTile extends React.Component { } renderWithData(data, weatherStyle) { - let c = data.currently; - let d = data.daily.data[0]; + const c = data.currently; + 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(
    + style={{ backdropFilter: 'blur(80px)' }} + >

    Weather

    @@ -230,7 +244,8 @@ export default class WeatherTile extends React.Component { style={{ right: 8, top: 8 }} onClick={() => this.setState({ manualEntry: !this.state.manualEntry }) - }> + } + > ->
    @@ -243,20 +258,19 @@ export default class WeatherTile extends React.Component { } render() { - let data = !!this.props.data ? this.props.data : {}; + const data = this.props.data ? this.props.data : {}; if (this.state.manualEntry === true) { return this.renderManualEntry(); } if ('currently' in data && 'daily' in data) { - let weatherStyle = this.setColors(data); + const weatherStyle = this.setColors(data); return this.renderWithData(data, weatherStyle); } return this.renderNoData(); } - } window.weatherTile = WeatherTile; diff --git a/pkg/interface/src/apps/launch/css/custom.css b/pkg/interface/src/apps/launch/css/custom.css index b9795fd4b..51ad58b90 100644 --- a/pkg/interface/src/apps/launch/css/custom.css +++ b/pkg/interface/src/apps/launch/css/custom.css @@ -13,14 +13,6 @@ p, h1, h2, h3, h4, h5, h6, a, input, textarea, button { font-family: Inter, sans-serif; } -a:any-link { - color: unset; -} - -a:-webkit-any-link { - color: unset; -} - textarea, select, input, button { outline: none; } .c-default { diff --git a/pkg/interface/src/apps/links/app.js b/pkg/interface/src/apps/links/app.js index 15d3a9f08..3ca8e2c15 100644 --- a/pkg/interface/src/apps/links/app.js +++ b/pkg/interface/src/apps/links/app.js @@ -29,7 +29,7 @@ export class LinksApp extends Component { } componentDidMount() { - window.title = 'OS1 - Groups'; + document.title = 'OS1 - Links'; // preload spinner asset new Image().src = '/~landscape/img/Spinner.png'; @@ -73,7 +73,7 @@ export class LinksApp extends Component { ); if(totalUnseen !== this.totalUnseen) { - document.title = totalUnseen !== 0 ? `Links - (${totalUnseen})` : 'Links'; + document.title = totalUnseen !== 0 ? `OS1 - Links (${totalUnseen})` : 'OS1 - Links'; this.totalUnseen = totalUnseen; } diff --git a/pkg/interface/src/apps/links/components/lib/link-item.js b/pkg/interface/src/apps/links/components/lib/link-item.js index b79df9259..10fcea921 100644 --- a/pkg/interface/src/apps/links/components/lib/link-item.js +++ b/pkg/interface/src/apps/links/components/lib/link-item.js @@ -67,7 +67,7 @@ export class LinkItem extends Component { classes={(member ? 'mix-blend-diff' : '')} />; return ( -
    +
    {img}
    {hostname} ↗ -
    +
    diff --git a/pkg/interface/src/apps/links/components/link.js b/pkg/interface/src/apps/links/components/link.js index fe3a9a58e..7c8cde9af 100644 --- a/pkg/interface/src/apps/links/components/link.js +++ b/pkg/interface/src/apps/links/components/link.js @@ -30,20 +30,22 @@ export class LinkDetail extends Component { } componentDidMount() { - // if we have no preloaded data, and we aren't expecting it, get it - if (!this.state.data.title) { - this.props.api.getSubmission( - this.props.resourcePath, this.props.url, this.updateData.bind(this) - ); - } + this.componentDidUpdate(); } componentDidUpdate(prevProps) { - if (this.props.url !== prevProps.url) { - this.updateData(this.props.data); + // if we have no preloaded data, and we aren't expecting it, get it + 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'] && - this.props.comments && this.props.comments['0']) { + if (prevProps) { + 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 thisFirstComment = this.props.comments['0'][0]; if ((prevFirstComment && prevFirstComment.udon) && @@ -56,6 +58,7 @@ export class LinkDetail extends Component { }); } } + } } } diff --git a/pkg/interface/src/apps/links/components/links-list.js b/pkg/interface/src/apps/links/components/links-list.js index 872c038a2..9636a76e4 100644 --- a/pkg/interface/src/apps/links/components/links-list.js +++ b/pkg/interface/src/apps/links/components/links-list.js @@ -26,14 +26,16 @@ export class Links extends Component { // and don't have links for it yet, // or the links we have might not be complete, // request the links for that page. - if ( (!prevProps || - linkPage !== prevProps.page || - this.props.resourcePath !== prevProps.resourcePath - ) && - !this.props.links[linkPage] || - this.props.links.local[linkPage] + if ( ((!prevProps || // first load? + linkPage !== prevProps.page || // already waiting on response? + this.props.resourcePath !== prevProps.resourcePath // new page? + ) || + (prevProps.api !== this.props.api)) // api prop instantiated? + && + !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); } } diff --git a/pkg/interface/src/apps/publish/app.js b/pkg/interface/src/apps/publish/app.js index ae720f8bc..659702df9 100644 --- a/pkg/interface/src/apps/publish/app.js +++ b/pkg/interface/src/apps/publish/app.js @@ -31,7 +31,7 @@ export default class PublishApp extends React.Component { } componentDidMount() { - window.title = 'OS1 - Groups'; + document.title = 'OS1 - Publish'; // preload spinner asset 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 selectedGroups = props.selectedGroups ? props.selectedGroups : []; - const unreadTotal = _.chain(state.notebooks) + const notebooks = state.notebooks ? state.notebooks : {}; + + const unreadTotal = _.chain(notebooks) .values() .map(_.values) .flatten() // flatten into array of notebooks @@ -68,7 +70,7 @@ export default class PublishApp extends React.Component { .value(); if (this.unreadTotal !== unreadTotal) { - window.title = unreadTotal > 0 ? `Publish - (${unreadTotal})` : 'Publish'; + document.title = unreadTotal > 0 ? `OS1 - Publish (${unreadTotal})` : 'OS1 - Publish'; this.unreadTotal = unreadTotal; } @@ -83,7 +85,7 @@ export default class PublishApp extends React.Component { rightPanelHide={true} sidebarShown={true} invites={state.invites} - notebooks={state.notebooks} + notebooks={notebooks} associations={associations} selectedGroups={selectedGroups} contacts={contacts} @@ -111,7 +113,7 @@ export default class PublishApp extends React.Component { rightPanelHide={false} sidebarShown={state.sidebarShown} invites={state.invites} - notebooks={state.notebooks} + notebooks={notebooks} associations={associations} selectedGroups={selectedGroups} contacts={contacts} @@ -119,7 +121,7 @@ export default class PublishApp extends React.Component { > ( name="comment" placeholder="Leave a comment here" 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' } aria-describedby="comment-desc" diff --git a/pkg/interface/src/apps/publish/components/lib/comments.js b/pkg/interface/src/apps/publish/components/lib/comments.js index c82d048fc..9e6ac6eb1 100644 --- a/pkg/interface/src/apps/publish/components/lib/comments.js +++ b/pkg/interface/src/apps/publish/components/lib/comments.js @@ -53,7 +53,7 @@ export class Comments extends Component { this.textArea.value = ''; this.setState({ commentBody: '', awaiting: 'new' }); - const submit = this.props.api.action('publish', 'publish-action', comment); + const submit = this.props.api.publishAction(comment); submit.then(() => { this.setState({ awaiting: null }); }); @@ -88,7 +88,7 @@ export class Comments extends Component { this.setState({ awaiting: 'edit' }); window.api - .action('publish', 'publish-action', comment) + .publishAction(comment) .then(() => { this.setState({ awaiting: null, editing: null }); }); @@ -107,7 +107,7 @@ export class Comments extends Component { this.setState({ awaiting: { kind: 'del', what: idx } }); window.api - .action('publish', 'publish-action', comment) + .publishAction(comment) .then(() => { this.setState({ awaiting: null }); }); diff --git a/pkg/interface/src/apps/publish/components/lib/edit-post.js b/pkg/interface/src/apps/publish/components/lib/edit-post.js index 1337cb94e..beee1727c 100644 --- a/pkg/interface/src/apps/publish/components/lib/edit-post.js +++ b/pkg/interface/src/apps/publish/components/lib/edit-post.js @@ -20,18 +20,21 @@ export class EditPost extends Component { } componentDidMount() { - const { props } = this; - if (!(props.notebooks[props.ship]) || - !(props.notebooks[props.ship][props.book]) || - !(props.notebooks[props.ship][props.book].notes[props.note]) || - !(props.notebooks[props.ship][props.book].notes[props.note].file)) { - this.props.api.fetchNote(props.ship, props.book, props.note); - } else { - const notebook = props.notebooks[props.ship][props.book]; - const note = notebook.notes[props.note]; - const file = note.file; - const body = file.slice(file.indexOf(';>') + 3); - this.setState({ body: body }); + this.componentDidUpdate(); + } + + componentDidUpdate(prevProps) { + const { props, state } = this; + if (prevProps && prevProps.api !== props.api) { + if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) { + props.api?.fetchNote(props.ship, props.book, props.note); + } else if (state.body === '') { + const notebook = props.notebooks[props.ship][props.book]; + const note = notebook.notes[props.note]; + 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.props.api.action('publish', 'publish-action', editNote).then(() => { + this.props.api.publishAction(editNote).then(() => { const editIndex = props.location.pathname.indexOf('/edit'); const noteHref = props.location.pathname.slice(0, editIndex); this.setState({ awaiting: false }); @@ -112,7 +115,7 @@ export class EditPost extends Component { to={popoutHref} target="_blank" > - diff --git a/pkg/interface/src/apps/publish/components/lib/join.js b/pkg/interface/src/apps/publish/components/lib/join.js index 83a5d48ef..2fd3d6c7d 100644 --- a/pkg/interface/src/apps/publish/components/lib/join.js +++ b/pkg/interface/src/apps/publish/components/lib/join.js @@ -93,7 +93,7 @@ export class JoinScreen extends Component { // TODO: askHistory setting this.setState({ disable: true }); - this.props.api.action('publish','publish-action', actionData).catch((err) => { + this.props.api.publishAction(actionData).catch((err) => { console.log(err); }).then(() => { this.setState({ awaiting: text }); diff --git a/pkg/interface/src/apps/publish/components/lib/new-post.js b/pkg/interface/src/apps/publish/components/lib/new-post.js index 3d7aadbda..ab8f74285 100644 --- a/pkg/interface/src/apps/publish/components/lib/new-post.js +++ b/pkg/interface/src/apps/publish/components/lib/new-post.js @@ -37,14 +37,14 @@ export class NewPost extends Component { }; 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 }); }).catch((err) => { if (err.includes('note already exists')) { const timestamp = Math.floor(Date.now() / 1000); newNote['new-note'].note += '-' + timestamp; this.setState({ awaiting: newNote['new-note'].note }); - this.props.api.action('publish', 'publish-action', newNote); + this.props.api.publishAction(newNote); } else { this.setState({ disabled: false, awaiting: null }); } @@ -52,11 +52,15 @@ export class NewPost extends Component { } } - componentWillMount() { - this.props.api.fetchNotebook(this.props.ship, this.props.book); + componentDidMount() { + 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]; if (notebook.notes[this.state.awaiting]) { this.setState({ disabled: false, awaiting: null }); @@ -131,7 +135,7 @@ export class NewPost extends Component { to={popoutHref} target='_blank' > - +
    diff --git a/pkg/interface/src/apps/publish/components/lib/new.js b/pkg/interface/src/apps/publish/components/lib/new.js index 71cb62096..7b89f333c 100644 --- a/pkg/interface/src/apps/publish/components/lib/new.js +++ b/pkg/interface/src/apps/publish/components/lib/new.js @@ -93,7 +93,7 @@ export class NewScreen extends Component { } }; this.setState({ awaiting: bookId, disabled: true }, () => { - props.api.action('publish', 'publish-action', action).then(() => { + props.api.publishAction(action).then(() => { }); }); } diff --git a/pkg/interface/src/apps/publish/components/lib/note.js b/pkg/interface/src/apps/publish/components/lib/note.js index 9b00c18a6..a230d10ce 100644 --- a/pkg/interface/src/apps/publish/components/lib/note.js +++ b/pkg/interface/src/apps/publish/components/lib/note.js @@ -40,54 +40,40 @@ export class Note extends Component { 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() { - if (!(this.props.notebooks[this.props.ship]) || - !(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.componentDidUpdate(); this.onScroll(); } componentDidUpdate(prevProps) { - if (!(this.props.notebooks[this.props.ship]) || - !(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); - } - if ((prevProps.book !== this.props.book) || - (prevProps.note !== this.props.note) || - (prevProps.ship !== this.props.ship)) { - const readAction = { - read: { - who: this.props.ship.slice(1), - book: this.props.book, - note: this.props.note + const { props } = this; + if ((prevProps && prevProps.api !== props.api) || props.api) { + if (!(props.notebooks[props.ship]?.[props.book]?.notes?.[props.note]?.file)) { + props.api.fetchNote(props.ship, props.book, props.note); + } + + if (prevProps) { + if ((prevProps.book !== props.book) || + (prevProps.note !== props.note) || + (prevProps.ship !== props.ship)) { + const readAction = { + read: { + who: props.ship.slice(1), + book: props.book, + note: props.note + } + }; + props.api.publishAction(readAction); } - }; - this.props.api.action('publish', 'publish-action', readAction); + } } } onScroll() { - const notebook = this.props.notebooks[this.props.ship][this.props.book]; - const note = notebook.notes[this.props.note]; + const notebook = this.props.notebooks?.[this.props.ship]?.[this.props.book]; + const note = notebook?.notes?.[this.props.note]; - if (!note.comments) { + if (!note?.comments) { return; } @@ -123,7 +109,7 @@ export class Note extends Component { const popout = (props.popout) ? 'popout/' : ''; const baseUrl = `/~publish/${popout}notebook/${props.ship}/${props.book}`; this.setState({ deleting: true }); - this.props.api.action('publish', 'publish-action', deleteAction) + this.props.api.publishAction(deleteAction) .then(() => { props.history.push(baseUrl); }); @@ -131,12 +117,12 @@ export class Note extends Component { render() { const { props } = this; - const notebook = props.notebooks[props.ship][props.book] || {}; - const comments = notebook.notes[props.note].comments || false; - const title = notebook.notes[props.note].title || ''; - const author = notebook.notes[props.note].author || ''; - const file = notebook.notes[props.note].file || ''; - const date = moment(notebook.notes[props.note]['date-created']).fromNow() || 0; + const notebook = props.notebooks?.[props.ship]?.[props.book] || {}; + const comments = notebook?.notes?.[props.note]?.comments || false; + const title = notebook?.notes?.[props.note]?.title || ''; + const author = notebook?.notes?.[props.note]?.author || ''; + const file = notebook?.notes?.[props.note]?.file || ''; + const date = moment(notebook.notes?.[props.note]?.['date-created']).fromNow() || 0; const contact = author.substr(1) in props.contacts ? props.contacts[author.substr(1)] : false; @@ -156,22 +142,24 @@ export class Note extends Component { } const newfile = file.slice(file.indexOf(';>')+2); - const prevId = notebook.notes[props.note]['prev-note'] || null; - const nextId = notebook.notes[props.note]['next-note'] || null; + const prevId = notebook?.notes?.[props.note]?.['prev-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) ? null : { id: prevId, - title: notebook.notes[prevId].title, - date: moment(notebook.notes[prevId]['date-created']).fromNow() + title: notebook?.notes?.[prevId]?.title, + date: prevDate }; const next = (nextId === null) ? null : { id: nextId, - title: notebook.notes[nextId].title, - date: moment(notebook.notes[nextId]['date-created']).fromNow() + title: notebook?.notes?.[nextId]?.title, + date: nextDate }; let editPost = null; @@ -218,7 +206,7 @@ export class Note extends Component { className={'dn absolute right-1 top-1 ' + hiddenOnPopout} target='_blank' > - +
    diff --git a/pkg/interface/src/apps/publish/components/lib/notebook.js b/pkg/interface/src/apps/publish/components/lib/notebook.js index 68a7949d9..45088586c 100644 --- a/pkg/interface/src/apps/publish/components/lib/notebook.js +++ b/pkg/interface/src/apps/publish/components/lib/notebook.js @@ -24,13 +24,13 @@ export class Notebook extends Component { if (scrollHeight - scrollTop - clientHeight < 40) { atBottom = true; } - if (!notebook.notes) { + if (!notebook.notes && this.props.api) { this.props.api.fetchNotebook(this.props.ship, this.props.book); return; } - const loadedNotes = Object.keys(notebook.notes).length; - const allNotes = notebook['notes-by-date'].length; + const loadedNotes = Object.keys(notebook?.notes).length || 0; + const allNotes = notebook?.['notes-by-date'].length || 0; 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) { - const notebook = this.props.notebooks[this.props.ship][this.props.book]; - if (!notebook.subscribers) { - this.props.api.fetchNotebook(this.props.ship, this.props.book); + const { props } = this; + if ((prevProps && (prevProps.api !== props.api)) || props.api) { + const notebook = props.notebooks?.[props.ship]?.[props.book]; + if (!notebook?.subscribers) { + props.api.fetchNotebook(props.ship, props.book); + } } } componentDidMount() { - const notebook = this.props.notebooks[this.props.ship][this.props.book]; - if (notebook.notes) { + this.componentDidUpdate(); + const notebook = this.props.notebooks?.[this.props.ship]?.[this.props.book]; + if (notebook?.notes) { this.onScroll(); } } @@ -64,7 +64,7 @@ export class Notebook extends Component { book: this.props.book } }; - this.props.api.action('publish', 'publish-action', action); + this.props.api.publishAction(action); 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 notebook = props.notebooks[props.ship][props.book]; + const notebook = props.notebooks?.[props.ship]?.[props.book]; const tabStyles = { posts: 'bb b--gray4 b--gray2-d gray2 pv4 ph2', @@ -91,8 +91,8 @@ export class Notebook extends Component { let inner = null; switch (props.view) { case 'posts': { - const notesList = notebook['notes-by-date'] || []; - const notes = notebook.notes || null; + const notesList = notebook?.['notes-by-date'] || []; + const notes = notebook?.notes || null; inner = {notebook.about}

    ; + inner =

    {notebook?.about}

    ; break; case 'subscribers': inner = ; break; case 'settings': @@ -123,6 +124,7 @@ export class Notebook extends Component { contacts={this.props.contacts} associations={this.props.associations} history={this.props.history} + api={this.props.api} />; break; default: @@ -150,9 +152,9 @@ export class Notebook extends Component { const newUrl = base + '/new'; let newPost = null; - if (notebook['writers-group-path'] in props.groups) { - const writers = notebook['writers-group-path']; - if (props.groups[writers].has(window.ship)) { + if (notebook?.['writers-group-path'] in props.groups) { + const writers = notebook?.['writers-group-path']; + if (props.groups?.[writers].has(window.ship)) { newPost = ( { @@ -213,12 +215,12 @@ export class Notebook extends Component { to={popoutHref} target='_blank' > - +
    -
    {notebook.title}
    +
    {notebook?.title}
    by { - this.props.api.action('publish', 'publish-action', { + this.props.api.publishAction({ 'edit-book': { book: this.props.book, title: this.props.notebook.title, @@ -84,7 +84,7 @@ export class Settings extends Component { } }; 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'); }); } @@ -108,7 +108,7 @@ export class Settings extends Component { disabled: true, type: 'Converting' }, (() => { - this.props.api.action('publish', 'publish-action', { + this.props.api.publishAction({ groupify: { book: props.book, target: state.targetGroup, @@ -125,7 +125,7 @@ export class Settings extends Component { const ownedUnmanaged = owner && - props.notebook['writers-group-path'].slice(0, 3) === '/~/'; + props.notebook?.['writers-group-path'].slice(0, 3) === '/~/'; if (!ownedUnmanaged) { return null; @@ -162,11 +162,9 @@ export class Settings extends Component { return (
    -

    Convert Notebook

    -

    - Convert this notebook into a group with associated chat, or select a - group to add this notebook to. -

    + {this.renderHeader( + 'Convert Notebook', + 'Convert this notebook into a group with associated chat, or select a group to add this notebook to.')} - Convert to group + {state.targetGroup ? 'Add to group' : 'Convert to group'}
    @@ -193,14 +191,22 @@ export class Settings extends Component { } } + renderHeader(title, subtitle) { + return ( + <> +

    {title}

    +

    {subtitle}

    + + ); + } + render() { const commentsSwitchClasses = (this.state.comments) ? 'relative checked bg-green2 br3 h1 toggle v-mid z-0' : 'relative bg-gray4 bg-gray1-d br3 h1 toggle v-mid z-0'; const copyShortcode =
    -

    Share

    -

    Share a shortcode to join this notebook

    + {this.renderHeader('Share', 'Share a shortcode to join this notebook')}
    {copyShortcode} {this.renderGroupify()} -

    Delete Notebook

    -

    - Permanently delete this notebook. (All current members will no - longer see this notebook) -

    + {this.renderHeader( + 'Delete Notebook', + 'Permanently delete this notebook. (All current members will no longer see this notebook)')} -

    Rename

    -

    Change the name of this notebook

    + {this.renderHeader('Rename', 'Change the name of this notebook')}
    { this.setState({ disabled: true }); this.props.api - .action('publish', 'publish-action', { + .publishAction({ 'edit-book': { book: this.props.book, title: this.state.title, @@ -266,8 +269,7 @@ export class Settings extends Component { }} />
    -

    Change description

    -

    Change the description of this notebook

    + {this.renderHeader("Change description", "Change the description of this notebook")}
    { this.setState({ disabled: true }); this.props.api - .action('publish', 'publish-action', { + .publishAction({ 'edit-book': { book: this.props.book, title: this.props.notebook.title, diff --git a/pkg/interface/src/apps/publish/components/lib/sidebar-invite.js b/pkg/interface/src/apps/publish/components/lib/sidebar-invite.js index aca48c2ac..13f96193d 100644 --- a/pkg/interface/src/apps/publish/components/lib/sidebar-invite.js +++ b/pkg/interface/src/apps/publish/components/lib/sidebar-invite.js @@ -8,7 +8,7 @@ export class SidebarInvite extends Component { uid: this.props.uid } }; - window.api.action('invite-store', 'invite-action', action); + this.props.api.inviteAction(action); } onDecline() { @@ -18,7 +18,7 @@ export class SidebarInvite extends Component { uid: this.props.uid } }; - this.props.api.action('invite-store', 'invite-action', action); + this.props.api.inviteAction(action); } render() { diff --git a/pkg/interface/src/apps/publish/components/lib/subscribers.js b/pkg/interface/src/apps/publish/components/lib/subscribers.js index 5650570cc..8d48153f5 100644 --- a/pkg/interface/src/apps/publish/components/lib/subscribers.js +++ b/pkg/interface/src/apps/publish/components/lib/subscribers.js @@ -17,7 +17,7 @@ export class Subscribers extends Component { path: path } }; - this.props.api.action('group-store', 'group-action', action); + this.props.api.groupAction(action); } removeUser(who, path) { @@ -27,7 +27,7 @@ export class Subscribers extends Component { path: path } }; - this.props.api.action('group-store', 'group-action', action); + this.props.api.groupAction(action); } redirect(url) { diff --git a/pkg/interface/src/components/404.js b/pkg/interface/src/components/404.js new file mode 100644 index 000000000..fbe74bce8 --- /dev/null +++ b/pkg/interface/src/components/404.js @@ -0,0 +1,15 @@ +import React from 'react'; + +const NotFound = () =>
    +

    404

    +

    Not found.

    + If this is unexpected, email support@tlon.io or submit an issue.

    +
    ; + +export default NotFound; diff --git a/pkg/interface/src/components/Spinner.js b/pkg/interface/src/components/Spinner.js index bf8156735..d818fb03f 100644 --- a/pkg/interface/src/components/Spinner.js +++ b/pkg/interface/src/components/Spinner.js @@ -10,7 +10,7 @@ export class Spinner extends Component { return (
    diff --git a/pkg/interface/src/components/StatusBar.js b/pkg/interface/src/components/StatusBar.js index dcbc34c9a..71a00505b 100644 --- a/pkg/interface/src/components/StatusBar.js +++ b/pkg/interface/src/components/StatusBar.js @@ -7,14 +7,16 @@ import { Sigil } from '../lib/sigil'; const getLocationName = (basePath) => { if (basePath === '~chat') return 'Chat'; - if (basePath === '~dojo') + else if (basePath === '~dojo') return 'Dojo'; - if (basePath === '~groups') + else if (basePath === '~groups') return 'Groups'; - if (basePath === '~link') + else if (basePath === '~link') return 'Links'; - if (basePath === '~publish') + else if (basePath === '~publish') return 'Publish'; + else + return 'Unknown'; }; const StatusBar = (props) => { @@ -24,8 +26,9 @@ const StatusBar = (props) => { ? 'Home' : getLocationName(basePath); - const popout = window.location.href.includes('popout/') - ? 'dn' : 'db'; + const display = (!window.location.href.includes('popout/') && + (locationName !== 'Unknown')) + ? 'db' : 'dn'; const invites = (props.invites && props.invites['/contacts']) ? props.invites['/contacts'] @@ -34,7 +37,7 @@ const StatusBar = (props) => { return (
    @@ -54,7 +57,7 @@ const StatusBar = (props) => { location.pathname === '/' ? null : diff --git a/pkg/interface/src/reducers/invite-update.js b/pkg/interface/src/reducers/invite-update.js index fb10d2b55..d9be947d0 100644 --- a/pkg/interface/src/reducers/invite-update.js +++ b/pkg/interface/src/reducers/invite-update.js @@ -4,6 +4,7 @@ export default class InviteReducer { reduce(json, state) { const data = _.get(json, 'invite-update', false); if (data) { + console.log(data); this.initial(data, state); this.create(data, state); this.delete(data, state); diff --git a/pkg/interface/src/reducers/metadata-update.js b/pkg/interface/src/reducers/metadata-update.js index a6bd20ce8..58fd1d257 100644 --- a/pkg/interface/src/reducers/metadata-update.js +++ b/pkg/interface/src/reducers/metadata-update.js @@ -4,25 +4,32 @@ export default class MetadataReducer { reduce(json, state) { let data = _.get(json, 'metadata-update', false); if (data) { + console.log('data: ', data); this.associations(data, state); this.add(data, state); this.update(data, state); this.remove(data, state); + console.log('state: ', state); } } associations(json, state) { let data = _.get(json, 'associations', false); if (data) { - let metadata = {}; + let metadata = state.associations; Object.keys(data).forEach((key) => { let val = data[key]; - let groupPath = val['group-path']; - if (!(groupPath in metadata)) { - metadata[groupPath] = {}; + let appName = val['app-name']; + let appPath = val['app-path']; + 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; } } @@ -31,11 +38,16 @@ export default class MetadataReducer { let data = _.get(json, 'add', false); if (data) { let metadata = state.associations; - if (!(data['group-path'] in metadata)) { - metadata[data['group-path']] = {}; + let appName = data['app-name']; + let appPath = data['app-path']; + + if (!(appName in metadata)) { + metadata[appName] = {}; } - metadata[data['group-path']] - [`${data["group-path"]}/${data["app-name"]}${data["app-path"]}`] = data; + if (!(appPath in metadata[appName])) { + metadata[appName][appPath] = {}; + } + metadata[appName][appPath] = data; state.associations = metadata; } @@ -45,12 +57,16 @@ export default class MetadataReducer { let data = _.get(json, 'update-metadata', false); if (data) { let metadata = state.associations; - if (!(data["group-path"] in metadata)) { - metadata[data["group-path"]] = {}; + let appName = data['app-name']; + let appPath = data['app-path']; + + if (!(appName in metadata)) { + metadata[appName] = {}; } - metadata[data["group-path"]][ - `${data["group-path"]}/${data["app-name"]}${data["app-path"]}` - ] = data; + if (!(appPath in metadata[appName])) { + metadata[appName][appPath] = {}; + } + metadata[appName][appPath] = data; state.associations = metadata; } @@ -60,12 +76,13 @@ export default class MetadataReducer { let data = _.get(json, 'remove', false); if (data) { let metadata = state.associations; - if (data['group-path'] in metadata) { - let path = - `${data['group-path']}/${data['app-name']}${data['app-path']}` - delete metadata[data["group-path"]][path]; - state.associations = metadata; + let appName = data['app-name']; + let appPath = data['app-path']; + + if (appName in metadata && appPath in metadata[appName]) { + delete metadata[appName][appPath]; } + state.associations = metadata; } } } diff --git a/pkg/interface/src/store/base.js b/pkg/interface/src/store/base.js index 6f42dc05a..d54a23bed 100644 --- a/pkg/interface/src/store/base.js +++ b/pkg/interface/src/store/base.js @@ -19,7 +19,11 @@ export default class BaseStore { } handleEvent(data) { - let json = data.data; + const json = data.data; + + if (json === null) { + return; + } if ('clear' in json && json.clear) { this.setState(this.initialState()); diff --git a/pkg/interface/src/store/launch.js b/pkg/interface/src/store/launch.js index 130b5669b..7318964a3 100644 --- a/pkg/interface/src/store/launch.js +++ b/pkg/interface/src/store/launch.js @@ -12,7 +12,9 @@ export default class LaunchStore extends BaseStore { launch: { firstTime: false, tileOrdering: [], - tiles: {} + tiles: {}, + weather: {}, + clock: {} } }; } diff --git a/pkg/interface/src/store/publish.js b/pkg/interface/src/store/publish.js index 5d996badf..fc70cbdde 100644 --- a/pkg/interface/src/store/publish.js +++ b/pkg/interface/src/store/publish.js @@ -1,6 +1,8 @@ import BaseStore from './base'; +import ContactReducer from '../reducers/contact-update'; import GroupReducer from '../reducers/group-update'; +import LocalReducer from '../reducers/local'; import PublishReducer from '../reducers/publish-update'; import InviteReducer from '../reducers/invite-update'; import PublishResponseReducer from '../reducers/publish-response'; @@ -11,7 +13,9 @@ export default class PublishStore extends BaseStore { constructor() { super(); + this.contactReducer = new ContactReducer(); this.groupReducer = new GroupReducer(); + this.localReducer = new LocalReducer(); this.publishReducer = new PublishReducer(); this.inviteReducer = new InviteReducer(); this.responseReducer = new PublishResponseReducer(); @@ -34,7 +38,9 @@ export default class PublishStore extends BaseStore { } reduce(data, state) { + this.contactReducer.reduce(data, this.state); this.groupReducer.reduce(data, this.state); + this.localReducer.reduce(data, this.state); this.publishReducer.reduce(data, this.state); this.permissionReducer.reduce(data, this.state); this.metadataReducer.reduce(data, this.state); diff --git a/pkg/interface/src/subscription/chat.js b/pkg/interface/src/subscription/chat.js index 97e024116..c29d9ce4d 100644 --- a/pkg/interface/src/subscription/chat.js +++ b/pkg/interface/src/subscription/chat.js @@ -5,7 +5,7 @@ export default class ChatSubscription extends BaseSubscription { this.subscribe('/primary', 'chat-view'); setTimeout(() => { this.subscribe('/synced', 'chat-hook'); - this.subscribe('/primary', 'invite-view'); + this.subscribe('/all', 'invite-store'); this.subscribe('/all', 'permission-store'); this.subscribe('/primary', 'contact-view'); this.subscribe('/app-name/chat', 'metadata-store'); diff --git a/pkg/interface/src/subscription/global.js b/pkg/interface/src/subscription/global.js index 11b42958f..993b885aa 100644 --- a/pkg/interface/src/subscription/global.js +++ b/pkg/interface/src/subscription/global.js @@ -2,7 +2,7 @@ import BaseSubscription from './base'; export default class GlobalSubscription extends BaseSubscription { start() { - this.subscribe('/primary', 'invite-view'); + this.subscribe('/all', 'invite-store'); this.subscribe('/app-name/contacts', 'metadata-store'); } } diff --git a/pkg/interface/src/subscription/groups.js b/pkg/interface/src/subscription/groups.js index f104c4eb7..df3d7f491 100644 --- a/pkg/interface/src/subscription/groups.js +++ b/pkg/interface/src/subscription/groups.js @@ -6,7 +6,7 @@ export default class GroupsSubscription extends BaseSubscription { this.subscribe('/all', 'group-store'); this.subscribe('/all', 'metadata-store'); this.subscribe('/synced', 'contact-hook'); - this.subscribe('/primary', 'invite-view'); + this.subscribe('/all', 'invite-store'); this.subscribe('/all', 's3-store'); } } diff --git a/pkg/interface/src/subscription/launch.js b/pkg/interface/src/subscription/launch.js index 4eba96388..f6c7c7ecd 100644 --- a/pkg/interface/src/subscription/launch.js +++ b/pkg/interface/src/subscription/launch.js @@ -3,6 +3,7 @@ import BaseSubscription from './base'; export default class LaunchSubscription extends BaseSubscription { start() { this.subscribe('/all', 'launch'); + this.subscribe('/weathertile', 'weather'); } } diff --git a/pkg/interface/src/subscription/links.js b/pkg/interface/src/subscription/links.js index 0112bbd77..30adddd76 100644 --- a/pkg/interface/src/subscription/links.js +++ b/pkg/interface/src/subscription/links.js @@ -4,7 +4,7 @@ export default class LinksSubscription extends BaseSubscription { start() { this.subscribe('/all', 'group-store'); 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/contacts', 'metadata-store'); this.subscribe('/listening', 'link-listen-hook'); diff --git a/pkg/interface/src/subscription/publish.js b/pkg/interface/src/subscription/publish.js index 31076e067..9a7c08599 100644 --- a/pkg/interface/src/subscription/publish.js +++ b/pkg/interface/src/subscription/publish.js @@ -5,7 +5,7 @@ export default class PublishSubscription extends BaseSubscription { this.subscribe('/primary', 'publish'); this.subscribe('/all', 'group-store'); this.subscribe('/primary', 'contact-view'); - this.subscribe('/primary', 'invite-view'); + this.subscribe('/all', 'invite-store'); this.subscribe('/all', 'permission-store'); this.subscribe('/app-name/contacts', 'metadata-store'); }