mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-25 07:52:59 +03:00
Merge branch 'feat/spa' into lf/groups-refactor
This commit is contained in:
commit
ef20a4d08a
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58deed8e9b8cd2c84d092cb8f638b9881cb0d12b97f7c719339e3604c9e9d1d2
|
||||
size 13826033
|
||||
oid sha256:575484aaf6c8bc03ab3b962ca52d48a90113bcb38a29a1ac84f2d49d1363b4ba
|
||||
size 7319532
|
||||
|
@ -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
|
||||
--
|
BIN
pkg/arvo/app/landscape/img/Link.png
Normal file
BIN
pkg/arvo/app/landscape/img/Link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
@ -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))
|
||||
==
|
||||
|
@ -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
|
||||
|
@ -104,7 +104,6 @@
|
||||
%permission-group-hook
|
||||
%invite-store
|
||||
%invite-hook
|
||||
%invite-view
|
||||
%chat-store
|
||||
%chat-hook
|
||||
%chat-view
|
||||
|
@ -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]
|
||||
|
@ -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'
|
||||
|
@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
URBIT_PIERS: [
|
||||
"/Users/user/ships/zod/home",
|
||||
]
|
||||
],
|
||||
herb: false
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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}
|
||||
/>
|
||||
<div>
|
||||
<Route exact path="/" render={ p => (
|
||||
<Switch>
|
||||
<Route exact path="/"
|
||||
render={ p => (
|
||||
<LaunchApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route path="/~chat" render={ p => (
|
||||
<ChatApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route path="/~dojo" render={ p => (
|
||||
<DojoApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route path="/~groups" render={ p => (
|
||||
<GroupsApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route path="/~link" render={ p => (
|
||||
<LinksApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route path="/~publish" render={ p => (
|
||||
<PublishApp
|
||||
ship={this.ship}
|
||||
channel={channel}
|
||||
selectedGroups={selectedGroups}
|
||||
{...p} />
|
||||
)} />
|
||||
{...p}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
</Router>
|
||||
</Root>
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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 {
|
||||
>
|
||||
<MemberScreen
|
||||
{...props}
|
||||
api={api}
|
||||
api={this.api}
|
||||
station={station}
|
||||
association={association}
|
||||
permission={permission}
|
||||
@ -361,7 +361,7 @@ export default class ChatApp extends React.Component {
|
||||
permissions={state.permissions || {}}
|
||||
contacts={state.contacts || {}}
|
||||
associations={associations.contacts}
|
||||
api={api}
|
||||
api={this.api}
|
||||
inbox={state.inbox}
|
||||
popout={popout}
|
||||
sidebarShown={state.sidebarShown}
|
||||
|
@ -493,9 +493,9 @@ ref={(e) => {
|
||||
style={{ height: 48 }}
|
||||
>
|
||||
<SidebarSwitcher
|
||||
sidebarShown={this.props.sidebarShown}
|
||||
popout={this.props.popout}
|
||||
api={this.props.api}
|
||||
sidebarShown={props.sidebarShown}
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
<Link to={'/~chat/' + isinPopout + 'room' + props.station}
|
||||
className="pt2 white-d"
|
||||
@ -513,7 +513,7 @@ ref={(e) => {
|
||||
station={props.station}
|
||||
numPeers={group.length}
|
||||
isOwner={deSig(props.match.params.ship) === window.ship}
|
||||
popout={this.props.popout}
|
||||
popout={props.popout}
|
||||
api={props.api}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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 {
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={this.state.title}
|
||||
value={state.title}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeTitle}
|
||||
onBlur={() => {
|
||||
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 {
|
||||
<input
|
||||
className={'f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={this.state.description}
|
||||
value={state.description}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeDescription}
|
||||
onBlur={() => {
|
||||
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 {
|
||||
<input
|
||||
className={'pl7 f8 ba b--gray3 b--gray2-d bg-gray0-d white-d ' +
|
||||
'focus-b--black focus-b--white-d pa3 db w-100 flex-auto mr3'}
|
||||
value={this.state.color}
|
||||
value={state.color}
|
||||
disabled={!chatOwner}
|
||||
onChange={this.changeColor}
|
||||
onBlur={this.submitColor}
|
||||
@ -400,7 +400,9 @@ export class SettingsScreen extends Component {
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
@ -477,7 +479,9 @@ export class SettingsScreen extends Component {
|
||||
{this.renderGroupify()}
|
||||
{this.renderDelete()}
|
||||
{this.renderMetadataSettings()}
|
||||
<Spinner awaiting={this.state.awaiting} classes="absolute right-2 bottom-2 ba pa2 b--gray1-d" text={this.state.type} />
|
||||
<Spinner awaiting={state.awaiting}
|
||||
classes="absolute right-2 bottom-2 ba pa2 b--gray1-d"
|
||||
text={state.type} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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 {
|
||||
<GroupItem
|
||||
key={i}
|
||||
index={i}
|
||||
association={associations[each]}
|
||||
chatMetadata={props.associations['chat']}
|
||||
association={contactAssoc[each]}
|
||||
chatMetadata={chatAssoc}
|
||||
channels={channels}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
@ -108,7 +115,7 @@ export class Sidebar extends Component {
|
||||
groupedItems.push(
|
||||
<GroupItem
|
||||
association={'/~/'}
|
||||
chatMetadata={props.associations['chat']}
|
||||
chatMetadata={chatAssoc}
|
||||
channels={groupedChannels['/~/']}
|
||||
inbox={props.inbox}
|
||||
station={props.station}
|
||||
|
@ -51,7 +51,7 @@ h2 {
|
||||
}
|
||||
|
||||
.clamp-attachment {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
max-height: 10em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export default class DojoApp extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.title = 'OS1 - Dojo';
|
||||
document.title = 'OS1 - Dojo';
|
||||
|
||||
const channel = new this.props.channel();
|
||||
this.api = new Api(this.props.ship, channel);
|
||||
@ -47,7 +47,7 @@ export default class DojoApp extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="bg-white bg-gray1-d"
|
||||
className="bg-white bg-gray0-d"
|
||||
style={{ height: 'calc(100vh - 45px)' }}
|
||||
>
|
||||
<Route
|
||||
@ -72,7 +72,7 @@ export default class DojoApp extends Component {
|
||||
<div
|
||||
className={
|
||||
'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
|
||||
}
|
||||
style={{
|
||||
|
@ -8,8 +8,8 @@ export class History extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="relative flex flex-column-reverse overflow-container flex-auto"
|
||||
style={{ height: 'calc(100% - 1rem)', resize: 'none' }}
|
||||
className="h-100 relative flex flex-column-reverse overflow-container flex-auto"
|
||||
style={{ resize: 'none' }}
|
||||
>
|
||||
<div style={{ marginTop: 'auto' }}>
|
||||
{this.props.commandLog.map((text, index) => {
|
||||
|
@ -7,7 +7,7 @@ export class Popout extends Component {
|
||||
: 'dib-m dib-l dib-xl';
|
||||
return (
|
||||
<div
|
||||
className="dib absolute z-2"
|
||||
className="db tr z-2"
|
||||
style={{
|
||||
right: 16,
|
||||
top: 16
|
||||
|
@ -23,7 +23,7 @@ export default class GroupsApp extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.title = 'OS1 - Groups';
|
||||
document.title = 'OS1 - Groups';
|
||||
// preload spinner asset
|
||||
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 settings = Boolean(props.match.url.includes('/settings'));
|
||||
|
||||
const association = (associations[groupPath])
|
||||
? associations[groupPath]
|
||||
const association = (associations.contacts?.[groupPath])
|
||||
? associations.contacts[groupPath]
|
||||
: {};
|
||||
|
||||
return (
|
||||
@ -152,6 +152,7 @@ export default class GroupsApp extends Component {
|
||||
group={group}
|
||||
activeDrawer={(detail || settings) ? 'detail' : 'contacts'}
|
||||
settings={settings}
|
||||
associations={associations}
|
||||
api={this.api}
|
||||
{...props}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
<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' }}
|
||||
>
|
||||
<div className="tl dib">
|
||||
<p className="f9 gray2 lh-copy">Sigil Color</p>
|
||||
<textarea
|
||||
className={
|
||||
@ -543,6 +544,7 @@ export class ContactCard extends Component {
|
||||
width: 114
|
||||
}}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-100 pt8 pb8 lh-copy tl">
|
||||
<p className="f9 gray2">Ship Name</p>
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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 = (<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 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 <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 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 = <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;
|
||||
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,
|
||||
|
@ -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 (
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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 <div style={{position:'relative'}}>
|
||||
return <div style={{ position:'relative' }}>
|
||||
<canvas
|
||||
style={{position:'absolute'}}
|
||||
style={{ position:'absolute' }}
|
||||
ref={ canvasRef => this.canvasRef = canvasRef }
|
||||
id="clock-canvas"/>
|
||||
</div>
|
||||
id="clock-canvas"
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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((
|
||||
<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 { 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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 (
|
||||
<Tile>
|
||||
<div
|
||||
className={"relative " + weatherStyle.text}
|
||||
className={'relative ' + weatherStyle.text}
|
||||
style={{
|
||||
width: 126,
|
||||
height: 126,
|
||||
background: `linear-gradient(135deg, ${weatherStyle.gradient1} 0%,` +
|
||||
`${weatherStyle.gradient2} 45%, ${weatherStyle.gradient2} 65%,` +
|
||||
`${weatherStyle.gradient1} 100%)`
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
</Tile>
|
||||
@ -139,56 +140,65 @@ export default class WeatherTile extends React.Component {
|
||||
let error;
|
||||
if (this.state.error === true) {
|
||||
error = <p
|
||||
className="f9 red2 pt1">Please try again.
|
||||
</p>
|
||||
className="f9 red2 pt1"
|
||||
>Please try again.
|
||||
</p>;
|
||||
}
|
||||
if (location.protocol === "https:") {
|
||||
if (location.protocol === 'https:') {
|
||||
secureCheck = <a
|
||||
className="black white-d f9 absolute pointer"
|
||||
style={{right: 8, top: 8}}
|
||||
onClick={() => this.locationSubmit()}>Detect -></a>
|
||||
style={{ right: 8, top: 8 }}
|
||||
onClick={() => this.locationSubmit()}
|
||||
>Detect -></a>;
|
||||
}
|
||||
return this.renderWrapper(
|
||||
<div className={"pa2 w-100 h-100 bg-white bg-gray0-d black white-d " +
|
||||
"b--black b--gray1-d ba"}>
|
||||
<div className={'pa2 w-100 h-100 bg-white bg-gray0-d black white-d ' +
|
||||
'b--black b--gray1-d ba'}
|
||||
>
|
||||
<a
|
||||
className="f9 black white-d pointer"
|
||||
className="f9 black white-d pointer absolute"
|
||||
style={{ top: 8 }}
|
||||
onClick={() =>
|
||||
this.setState({ manualEntry: !this.state.manualEntry })
|
||||
}>
|
||||
}
|
||||
>
|
||||
<-
|
||||
</a>
|
||||
{secureCheck}
|
||||
<p className="f9 pt2">
|
||||
Please enter your{" "}
|
||||
<p className="f9 pt5">
|
||||
Please enter your{' '}
|
||||
<a
|
||||
className="black white-d"
|
||||
className="black bb white-d"
|
||||
href="https://latitudeandlongitude.org/"
|
||||
target="_blank">
|
||||
target="_blank"
|
||||
>
|
||||
latitude and longitude
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
{error}
|
||||
<div className="absolute" style={{left: 8, bottom: 8}}>
|
||||
<form className="flex" style={{marginBlockEnd: 0 }}>
|
||||
<div className="absolute" style={{ left: 8, bottom: 8 }}>
|
||||
<form className="flex" style={{ marginBlockEnd: 0 }}>
|
||||
<input
|
||||
id="gps"
|
||||
className="w-100 black white-d bg-transparent bn f9"
|
||||
type="text"
|
||||
placeholder="29.558107, -95.089023"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.manualLocationSubmit(e.target.value);
|
||||
}}
|
||||
}/>
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
<input
|
||||
className={"bg-transparent black white-d bn pointer " +
|
||||
"f9 flex-shrink-0"}
|
||||
className={'bg-transparent black white-d bn pointer ' +
|
||||
'f9 flex-shrink-0 pr1'}
|
||||
type="submit"
|
||||
onClick={() => this.manualLocationSubmit()}
|
||||
value="->"/>
|
||||
value="->"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,15 +208,18 @@ export default class WeatherTile extends React.Component {
|
||||
renderNoData() {
|
||||
return this.renderWrapper((
|
||||
<div
|
||||
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})}>
|
||||
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 })}
|
||||
>
|
||||
<p className="f9 absolute"
|
||||
style={{left: 8, top: 8}}>
|
||||
style={{ left: 8, top: 8 }}
|
||||
>
|
||||
Weather
|
||||
</p>
|
||||
<p className="absolute w-100 flex-col f9"
|
||||
style={{bottom: 8, left: 8, cursor: "pointer"}}>
|
||||
style={{ bottom: 8, left: 8, cursor: 'pointer' }}
|
||||
>
|
||||
-> Set location
|
||||
</p>
|
||||
</div>
|
||||
@ -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(
|
||||
<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 }}>
|
||||
Weather
|
||||
</p>
|
||||
@ -230,7 +244,8 @@ export default class WeatherTile extends React.Component {
|
||||
style={{ right: 8, top: 8 }}
|
||||
onClick={() =>
|
||||
this.setState({ manualEntry: !this.state.manualEntry })
|
||||
}>
|
||||
}
|
||||
>
|
||||
->
|
||||
</a>
|
||||
<div className="w-100 absolute" style={{ left: 8, bottom: 8 }}>
|
||||
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ export class LinkItem extends Component {
|
||||
classes={(member ? 'mix-blend-diff' : '')}
|
||||
/>;
|
||||
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}
|
||||
<div className="flex flex-column ml2 flex-auto">
|
||||
<a href={props.url}
|
||||
@ -80,7 +80,7 @@ export class LinkItem extends Component {
|
||||
</p>
|
||||
<span className="gray2 dib v-btm ml2 f8 flex-shrink-0">{hostname} ↗</span>
|
||||
</a>
|
||||
<div className="w-100 pt1">
|
||||
<div className="w-100">
|
||||
<span className={'f9 pr2 dib ' + mono}
|
||||
title={props.ship}
|
||||
>
|
||||
|
@ -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 {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
>
|
||||
<NewScreen
|
||||
associations={associations.contacts}
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
groups={state.groups}
|
||||
contacts={contacts}
|
||||
api={this.api}
|
||||
@ -140,14 +142,14 @@ 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}
|
||||
api={this.api}
|
||||
>
|
||||
<JoinScreen
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
ship={ship}
|
||||
notebook={notebook}
|
||||
api={this.api}
|
||||
@ -171,7 +173,7 @@ export default class PublishApp extends React.Component {
|
||||
const path = `${ship}/${notebook}`;
|
||||
|
||||
const bookGroupPath =
|
||||
state.notebooks[ship][notebook]['subscribers-group-path'];
|
||||
notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
|
||||
|
||||
const notebookContacts = (bookGroupPath in contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
@ -184,7 +186,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}
|
||||
@ -192,7 +194,7 @@ export default class PublishApp extends React.Component {
|
||||
api={this.api}
|
||||
>
|
||||
<NewPost
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
sidebarShown={state.sidebarShown}
|
||||
@ -210,7 +212,7 @@ export default class PublishApp extends React.Component {
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
selectedGroups={selectedGroups}
|
||||
@ -218,7 +220,7 @@ export default class PublishApp extends React.Component {
|
||||
api={this.api}
|
||||
>
|
||||
<Notebook
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
view={view}
|
||||
ship={ship}
|
||||
book={notebook}
|
||||
@ -247,7 +249,7 @@ export default class PublishApp extends React.Component {
|
||||
const popout = Boolean(props.match.params.popout) || false;
|
||||
|
||||
const bookGroupPath =
|
||||
state.notebooks[ship][notebook]['subscribers-group-path'];
|
||||
notebooks?.[ship]?.[notebook]?.['subscribers-group-path'];
|
||||
const notebookContacts = (bookGroupPath in state.contacts)
|
||||
? contacts[bookGroupPath] : {};
|
||||
|
||||
@ -261,7 +263,7 @@ export default class PublishApp extends React.Component {
|
||||
rightPanelHide={false}
|
||||
sidebarShown={state.sidebarShown}
|
||||
invites={state.invites}
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
selectedGroups={selectedGroups}
|
||||
associations={associations}
|
||||
contacts={contacts}
|
||||
@ -269,7 +271,7 @@ export default class PublishApp extends React.Component {
|
||||
api={this.api}
|
||||
>
|
||||
<EditPost
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
book={notebook}
|
||||
note={note}
|
||||
ship={ship}
|
||||
@ -288,7 +290,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}
|
||||
@ -296,7 +298,7 @@ export default class PublishApp extends React.Component {
|
||||
api={this.api}
|
||||
>
|
||||
<Note
|
||||
notebooks={state.notebooks}
|
||||
notebooks={notebooks}
|
||||
book={notebook}
|
||||
groups={state.groups}
|
||||
contacts={notebookContacts}
|
||||
|
@ -8,7 +8,7 @@ const CommentInput = React.forwardRef((props, ref) => (
|
||||
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"
|
||||
|
@ -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 });
|
||||
});
|
||||
|
@ -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"
|
||||
>
|
||||
<img src="/~publish/popout.png"
|
||||
<img src="/~landscape/img/popout.png"
|
||||
height={16}
|
||||
width={16}
|
||||
/>
|
||||
|
@ -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 });
|
||||
|
@ -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'
|
||||
>
|
||||
<img src='/~publish/popout.png' height={16} width={16} />
|
||||
<img src='/~landscape/img/popout.png' height={16} width={16} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='mw6 center'>
|
||||
|
@ -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(() => {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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'
|
||||
>
|
||||
<img src='/~publish/popout.png' height={16} width={16} />
|
||||
<img src='/~landscape/img/popout.png' height={16} width={16} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='w-100 mw6'>
|
||||
|
@ -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 = <NotebookPosts notes={notes}
|
||||
popout={props.popout}
|
||||
list={notesList}
|
||||
@ -103,7 +103,7 @@ export class Notebook extends Component {
|
||||
break;
|
||||
}
|
||||
case 'about':
|
||||
inner = <p className="f8 lh-solid">{notebook.about}</p>;
|
||||
inner = <p className="f8 lh-solid">{notebook?.about}</p>;
|
||||
break;
|
||||
case 'subscribers':
|
||||
inner = <Subscribers
|
||||
@ -112,6 +112,7 @@ export class Notebook extends Component {
|
||||
notebook={notebook}
|
||||
permissions={this.props.permissions}
|
||||
groups={this.props.groups}
|
||||
api={this.props.api}
|
||||
/>;
|
||||
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 = (
|
||||
<Link
|
||||
to={newUrl}
|
||||
@ -186,7 +188,7 @@ export class Notebook extends Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='overflow-y-scroll'
|
||||
className='overflow-y-scroll h-100'
|
||||
style={{ paddingLeft: 16, paddingRight: 16 }}
|
||||
onScroll={this.onScroll}
|
||||
ref={(el) => {
|
||||
@ -213,12 +215,12 @@ export class Notebook extends Component {
|
||||
to={popoutHref}
|
||||
target='_blank'
|
||||
>
|
||||
<img src='/~publish/popout.png' height={16} width={16} />
|
||||
<img src='/~landscape/img/popout.png' height={16} width={16} />
|
||||
</Link>
|
||||
<div className='h-100 pt0 pt8-m pt8-l pt8-xl no-scrollbar'>
|
||||
<div className='flex justify-between' style={{ marginBottom: 32 }}>
|
||||
<div className='flex-col'>
|
||||
<div className='mb1'>{notebook.title}</div>
|
||||
<div className='mb1'>{notebook?.title}</div>
|
||||
<span>
|
||||
<span className='gray3 mr1'>by</span>
|
||||
<span
|
||||
|
@ -63,7 +63,7 @@ export class Settings extends Component {
|
||||
|
||||
changeComments() {
|
||||
this.setState({ comments: !this.state.comments, disabled: true }, (() => {
|
||||
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 (
|
||||
<div>
|
||||
<div className={'w-100 fl mt3 mb3'} style={{ maxWidth: '29rem' }}>
|
||||
<p className="f8 mt3 lh-copy db">Convert Notebook</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Convert this notebook into a group with associated chat, or select a
|
||||
group to add this notebook to.
|
||||
</p>
|
||||
{this.renderHeader(
|
||||
'Convert Notebook',
|
||||
'Convert this notebook into a group with associated chat, or select a group to add this notebook to.')}
|
||||
<InviteSearch
|
||||
groups={props.groups}
|
||||
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'}
|
||||
disabled={this.state.disabled}
|
||||
>
|
||||
Convert to group
|
||||
{state.targetGroup ? 'Add to group' : 'Convert to group'}
|
||||
</button>
|
||||
</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() {
|
||||
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 = <div>
|
||||
<p className="f9 mt3 lh-copy">Share</p>
|
||||
<p className="f9 gray2 mb4">Share a shortcode to join this notebook</p>
|
||||
{this.renderHeader('Share', 'Share a shortcode to join this notebook')}
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
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">
|
||||
{copyShortcode}
|
||||
{this.renderGroupify()}
|
||||
<p className="f9 mt6 lh-copy db">Delete Notebook</p>
|
||||
<p className="f9 gray2 db mb4">
|
||||
Permanently delete this notebook. (All current members will no
|
||||
longer see this notebook)
|
||||
</p>
|
||||
{this.renderHeader(
|
||||
'Delete Notebook',
|
||||
'Permanently delete this notebook. (All current members will no longer see this notebook)')}
|
||||
<button
|
||||
className="bg-transparent b--red2 red2 pointer dib f9 ba pa2"
|
||||
onClick={this.deleteNotebook}
|
||||
>
|
||||
Delete this notebook
|
||||
</button>
|
||||
<p className="f9 mt6 lh-copy">Rename</p>
|
||||
<p className="f9 gray2 db mb4">Change the name of this notebook</p>
|
||||
{this.renderHeader('Rename', 'Change the name of this notebook')}
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={
|
||||
@ -251,7 +254,7 @@ export class Settings extends Component {
|
||||
onBlur={() => {
|
||||
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 {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="f9 mt6 lh-copy">Change description</p>
|
||||
<p className="f9 gray2 db mb4">Change the description of this notebook</p>
|
||||
{this.renderHeader("Change description", "Change the description of this notebook")}
|
||||
<div className="relative w-100 flex" style={{ maxWidth: '29rem' }}>
|
||||
<input
|
||||
className={
|
||||
@ -279,7 +281,7 @@ export class Settings extends Component {
|
||||
onBlur={() => {
|
||||
this.setState({ disabled: true });
|
||||
this.props.api
|
||||
.action('publish', 'publish-action', {
|
||||
.publishAction({
|
||||
'edit-book': {
|
||||
book: this.props.book,
|
||||
title: this.props.notebook.title,
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
15
pkg/interface/src/components/404.js
Normal file
15
pkg/interface/src/components/404.js
Normal 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;
|
@ -10,7 +10,7 @@ export class Spinner extends Component {
|
||||
return (
|
||||
<div className={classes + ' z-2 bg-white bg-gray0-d white-d'}>
|
||||
<img className="invert-d spin-active v-mid"
|
||||
src="/~chat/img/Spinner.png"
|
||||
src="/~landscape/img/Spinner.png"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
|
@ -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 (
|
||||
<div
|
||||
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 }}
|
||||
>
|
||||
@ -54,7 +57,7 @@ const StatusBar = (props) => {
|
||||
location.pathname === '/'
|
||||
? null
|
||||
: <Link
|
||||
className="dib f9 v-mid inter ml2 no-underline"
|
||||
className="dib f9 v-mid inter ml2 no-underline white-d"
|
||||
to="/"
|
||||
style={{ top: 14 }}
|
||||
>
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -12,7 +12,9 @@ export default class LaunchStore extends BaseStore {
|
||||
launch: {
|
||||
firstTime: false,
|
||||
tileOrdering: [],
|
||||
tiles: {}
|
||||
tiles: {},
|
||||
weather: {},
|
||||
clock: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import BaseSubscription from './base';
|
||||
export default class LaunchSubscription extends BaseSubscription {
|
||||
start() {
|
||||
this.subscribe('/all', 'launch');
|
||||
this.subscribe('/weathertile', 'weather');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user