launch-js: added launch files and made a new pattern for store/api/subscription

This commit is contained in:
Logan Allen 2020-05-20 15:05:40 -04:00
parent 8890fe5b03
commit 8d48b78cba
12 changed files with 405 additions and 31 deletions

View File

@ -5,6 +5,7 @@ import './css/indigo-static.css';
import './css/fonts.css';
import { light } from '@tlon/indigo-react';
import LaunchApp from './apps/launch/LaunchApp';
import ChatApp from './apps/chat/ChatApp';
import DojoApp from './apps/dojo/DojoApp';
import StatusBar from './components/StatusBar';
@ -80,27 +81,48 @@ export default class App extends React.Component {
api={this.api}
/>
<div>
<Route exact path="/" component={Home} />
<Route path="/~chat" render={
p => <ChatApp ship={this.ship} channel={channel} {...p} selectedGroups={selectedGroups} />
}
/>
<Route path="/~dojo" render={
p => <DojoApp ship={this.ship} channel={channel} {...p} selectedGroups={selectedGroups} />
}
/>
<Route path="/~groups" render={
p => <GroupsApp ship={this.ship} channel={channel} {...p} selectedGroups={selectedGroups} />
}
/>
<Route path="/~link" render={
p => <LinksApp ship={this.ship} channel={channel} {...p} selectedGroups={selectedGroups} />
}
/>
<Route path="/~publish" render={
p => <PublishApp ship={this.ship} channel={channel} {...p} selectedGroups={selectedGroups} />
}
/>
<Route exact path="/" render={ p => (
<LaunchApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
<Route path="/~chat" render={ p => (
<ChatApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
<Route path="/~dojo" render={ p => (
<DojoApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
<Route path="/~groups" render={ p => (
<GroupsApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
<Route path="/~link" render={ p => (
<LinksApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
<Route path="/~publish" render={ p => (
<PublishApp
ship={this.ship}
channel={channel}
selectedGroups={selectedGroups}
{...p} />
)} />
</div>
</Router>
</Root>

View File

@ -0,0 +1,46 @@
import _ from 'lodash';
import { uuid } from '../lib/util';
export default class BaseApi {
constructor(ship, channel, store) {
this.ship = ship;
this.channel = channel;
this.store = store;
this.bindPaths = [];
}
subscribe(path, method, ship = this.ship, app, success, fail, quit) {
this.bindPaths = _.uniq([...this.bindPaths, path]);
window.subscriptionId = this.channel.subscribe(ship, app, path,
(err) => {
fail(err);
},
(event) => {
success({
data: event,
from: {
ship,
path
}
});
},
(qui) => {
quit(qui);
});
}
action(appl, mark, data) {
return new Promise((resolve, reject) => {
this.channel.poke(window.ship, appl, mark, data,
(json) => {
resolve(json);
},
(err) => {
reject(err);
});
});
}
}

View File

@ -0,0 +1,45 @@
import BaseApi from './base';
class PrivateHelper extends BaseApi {
launchAction(data) {
this.action('launch', 'launch-action', data);
}
launchAdd(name, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
this.launchAction({ add: { name, tile } });
}
launchRemove(name) {
this.launchAction({ remove: name });
}
launchChangeOrder(orderedTiles = []) {
this.launchAction({ changeOrder: orderedTiles });
}
launchChangeFirstTime(firstTime = true) {
this.launchAction({ changeFirstTime: firstTime });
}
launchChangeIsShown(isShown = true) {
this.launchAction({ isShown });
}
}
export default class LaunchApi {
constructor(ship, channel, store) {
const helper = new PrivateHelper(ship, channel, store);
this.ship = ship;
this.subscribe = helper.subscribe.bind(helper);
this.launch = {
add: helper.launchAdd.bind(helper),
remove: helper.launchRemove.bind(helper),
changeOrder: helper.launchChangeOrder.bind(helper),
changeFirstTime: helper.launchChangeFirstTime.bind(helper),
changeIsShown: helper.launchChangeIsShown.bind(helper)
};
}
}

View File

@ -0,0 +1,50 @@
import React from 'react';
import LaunchApi from '../../api/launch';
import LaunchStore from '../../store/launch';
import LaunchSubscription from '../../subscription/launch';
import './css/custom.css';
export default class LaunchApp extends React.Component {
constructor(props) {
super(props);
this.store = new LaunchStore();
this.store.setStateHandler(this.setState.bind(this));
this.state = this.store.state;
this.resetControllers();
}
resetControllers() {
this.api = null;
this.subscription = null;
}
componentDidMount() {
window.title = 'OS1 - Home';
// preload spinner asset
new Image().src = '/~chat/img/Spinner.png';
this.store.clear();
const channel = new this.props.channel();
this.api = new LaunchApi(this.props.ship, channel, this.store);
this.subscription = new LaunchSubscription(this.store, this.api, channel);
this.subscription.start();
}
componentWillUnmount() {
this.subscription.delete();
this.store.clear();
this.resetControllers();
}
render() {
return (
<div></div>
);
}
}

View File

@ -0,0 +1,40 @@
import React, { Component } from 'react'
export class Welcome extends Component {
constructor() {
super();
this.state = {
show: true
}
this.disableWelcome = this.disableWelcome.bind(this);
}
disableWelcome() {
window.api.action("launch", "json", "disable welcome message");
this.setState({ show: false });
}
render() {
let firstTime = window.startupMessage;
return (firstTime && this.state.show)
? (
<div className={"fl ma2 bg-white bg-gray0-d white-d overflow-hidden " +
"ba b--black b--gray1-d pa2 w-100 lh-copy"}>
<p className="f9">Welcome. This virtual computer belongs to you completely. The Urbit ID you used to boot it is yours as well.</p>
<p className="f9 pt2">Since your ID and OS belong to you, its up to you to keep them safe. Be sure your ID is somewhere you wont lose it and you keep your OS on a machine you trust.</p>
<p className="f9 pt2">Urbit OS is designed to keep your data secure and hard to lose. But the system is still young so dont put anything critical in here just yet.</p>
<p className="f9 pt2">To begin exploring, you should probably pop into a chat and verify there are signs of life in this new place. If you were invited by a friend, you probably already have access to a few groups.</p>
<p className="f9 pt2">If you don't know where to go, feel free to <a className="no-underline bb b--black b--gray1-d dib" href="/~chat/join/~/~dopzod/urbit-help">join our lobby.</a>
</p>
<p className="f9 pt2">Have fun!</p>
<p className="dib f9 pt2 bb b--black b--gray1-d pointer"
onClick={(() => {this.disableWelcome()})}>
Close this note
</p>
</div>
)
: ( null)
}
}
export default Welcome

View File

@ -0,0 +1,64 @@
body, html {
width: 100%;
height: 100%;
font-family: "Inter", sans-serif;
-webkit-font-smoothing: antialiased;
}
p, h1, h2, h3, h4, h5, h6, a, input, textarea, button {
margin-block-end: unset;
margin-block-start: unset;
-webkit-margin-before: unset;
-webkit-margin-after: unset;
font-family: Inter, sans-serif;
}
a:any-link {
color: unset;
}
a:-webkit-any-link {
color: unset;
}
textarea, select, input, button { outline: none; }
.c-default {
cursor: default;
}
.mono {
font-family: "Source Code Pro", monospace;
}
.mix-blend-diff {
mix-blend-mode: difference;
}
/* dark */
@media all and (prefers-color-scheme: dark) {
body {
background-color: #333;
}
.bg-gray0-d {
background-color: #333;
}
.bg-gray1-d {
background-color: #4d4d4d;
}
.bg-gray2-d {
background-color: #7f7f7f;
}
.b--gray1-d {
border-color: #4d4d4d;
}
.white-d {
color: #fff;
}
.invert-d {
filter: invert(1);
}
.hover-bg-gray1-d:hover {
background-color: #4d4d4d;
}
}

View File

@ -0,0 +1,14 @@
import _ from 'lodash';
export default class LaunchReducer {
reduce(json, state) {
const data = _.get(json, 'launch-update', false);
if (data) {
this.log(data, state);
}
}
log(json, state) {
console.log(json);
}
}

View File

@ -0,0 +1,37 @@
export default class BaseStore {
constructor() {
this.state = this.initialState();
this.setState = () => {};
}
initialState() {
return {};
}
setStateHandler(setState) {
this.setState = setState;
}
clear() {
this.handleEvent({
data: { clear: true }
});
}
handleEvent(data) {
let json = data.data;
if ('clear' in json && json.clear) {
this.setState(this.initialState());
return;
}
this.reduce(json, this.state);
this.setState(this.state);
}
reduce(data, state) {
// extend me!
}
}

View File

@ -0,0 +1,13 @@
import BaseStore from './base';
import LaunchReducer from '../reducers/launch-update';
export default class LaunchStore extends BaseStore {
constructor() {
super();
this.launchReducer = new LaunchReducer();
}
reduce(data, state) {
this.launchReducer.reduce(data, state);
}
}

View File

@ -8,11 +8,8 @@ export default class Subscription {
}
start() {
if (this.api.ship) {
this.initialize();
} else {
console.error('~~~ ERROR: Must set api.ship before operation ~~~');
}
this.subscribe('/primary', 'invite-view');
this.subscribe('/app-name/contacts', 'metadata-store');
}
onChannelError(err) {
@ -34,11 +31,6 @@ export default class Subscription {
});
}
initialize() {
this.subscribe('/primary', 'invite-view');
this.subscribe('/app-name/contacts', 'metadata-store');
}
handleEvent(diff) {
this.store.handleEvent(diff);
}

View File

@ -0,0 +1,43 @@
export default class BaseSubscription {
constructor(store, api, channel) {
this.store = store;
this.api = api;
this.channel = channel;
this.channel.setOnChannelError(this.onChannelError.bind(this));
}
delete() {
this.channel.delete();
}
onChannelError(err) {
console.error('event source error: ', err);
console.log('initiating new channel');
setTimeout(2000, () => {
this.store.clear();
this.start();
});
}
subscribe(path, app) {
this.api.subscribe(path, 'PUT', this.api.ship, app,
this.handleEvent.bind(this),
(err) => {
console.log(err);
this.subscribe(path, app);
},
() => {
this.subscribe(path, app);
});
}
start() {
// extend
}
handleEvent(diff) {
// extend
this.store.handleEvent(diff);
}
}

View File

@ -0,0 +1,8 @@
import BaseSubscription from './base';
export default class LaunchSubscription extends BaseSubscription {
start() {
this.subscribe('/all', 'launch');
}
}