mirror of
https://github.com/urbit/shrub.git
synced 2025-01-01 17:16:47 +03:00
launch-js: added launch files and made a new pattern for store/api/subscription
This commit is contained in:
parent
8890fe5b03
commit
8d48b78cba
@ -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>
|
||||
|
46
pkg/interface/src/api/base.js
Normal file
46
pkg/interface/src/api/base.js
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
45
pkg/interface/src/api/launch.js
Normal file
45
pkg/interface/src/api/launch.js
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
50
pkg/interface/src/apps/launch/LaunchApp.js
Normal file
50
pkg/interface/src/apps/launch/LaunchApp.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
40
pkg/interface/src/apps/launch/components/welcome.js
Normal file
40
pkg/interface/src/apps/launch/components/welcome.js
Normal 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, it’s up to you to keep them safe. Be sure your ID is somewhere you won’t 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 don’t 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
|
64
pkg/interface/src/apps/launch/css/custom.css
Normal file
64
pkg/interface/src/apps/launch/css/custom.css
Normal 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;
|
||||
}
|
||||
}
|
14
pkg/interface/src/reducers/launch-update.js
Normal file
14
pkg/interface/src/reducers/launch-update.js
Normal 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);
|
||||
}
|
||||
}
|
37
pkg/interface/src/store/base.js
Normal file
37
pkg/interface/src/store/base.js
Normal 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!
|
||||
}
|
||||
}
|
||||
|
13
pkg/interface/src/store/launch.js
Normal file
13
pkg/interface/src/store/launch.js
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
43
pkg/interface/src/subscription/base.js
Normal file
43
pkg/interface/src/subscription/base.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
8
pkg/interface/src/subscription/launch.js
Normal file
8
pkg/interface/src/subscription/launch.js
Normal file
@ -0,0 +1,8 @@
|
||||
import BaseSubscription from './base';
|
||||
|
||||
export default class LaunchSubscription extends BaseSubscription {
|
||||
start() {
|
||||
this.subscribe('/all', 'launch');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user