mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-11-28 19:55:53 +03:00
interface: make store global
This commit is contained in:
parent
05b665f908
commit
60ff310422
@ -453,6 +453,7 @@
|
||||
[%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ frond:enjs:format 'initial-submissions'
|
||||
%- pairs:enjs:format
|
||||
%+ turn
|
||||
@ -514,6 +515,7 @@
|
||||
:_ [%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ frond:enjs:format 'submission'
|
||||
^- json
|
||||
=; sub=(unit submission)
|
||||
@ -538,6 +540,7 @@
|
||||
[%give %kick ~ ~]~
|
||||
=; =json
|
||||
[%give %fact ~ %json !>(json)]
|
||||
%+ frond:enjs:format 'link-update'
|
||||
%+ frond:enjs:format 'initial-discussions'
|
||||
%^ page-to-json p
|
||||
%+ get-paginated `p
|
||||
@ -552,6 +555,7 @@
|
||||
?+ -.update ~|([dap.bowl %unexpected-update -.update] !!)
|
||||
%submissions
|
||||
%+ give-json
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:en-json update)
|
||||
:~ /json/0/submissions
|
||||
(weld /json/0/submissions path.update)
|
||||
@ -559,6 +563,7 @@
|
||||
::
|
||||
%discussions
|
||||
%+ give-json
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:en-json update)
|
||||
:_ ~
|
||||
%+ weld /json/0/discussions
|
||||
@ -566,6 +571,7 @@
|
||||
::
|
||||
%observation
|
||||
%+ give-json
|
||||
%+ frond:enjs:format 'link-update'
|
||||
(update:en-json update)
|
||||
~[/json/seen]
|
||||
==
|
||||
|
@ -46,7 +46,7 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js?$/,
|
||||
test: /\.(j|t)sx?$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
@ -74,7 +74,7 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js']
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
// devServer: {
|
||||
|
@ -15,7 +15,7 @@ import PublishApp from './apps/publish/app';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import NotFound from './components/404';
|
||||
|
||||
import GlobalStore from './store/global';
|
||||
import GlobalStore from './store/store';
|
||||
import GlobalSubscription from './subscription/global';
|
||||
import GlobalApi from './api/global';
|
||||
|
||||
@ -55,11 +55,11 @@ export default class App extends React.Component {
|
||||
|
||||
this.appChannel = new window.channel();
|
||||
this.api = new GlobalApi(this.ship, this.appChannel, this.store);
|
||||
this.subscription =
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscription =
|
||||
new GlobalSubscription(this.store, this.api, this.appChannel);
|
||||
this.subscription.start();
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ export default class App extends React.Component {
|
||||
|
||||
const associations = this.state.associations ? this.state.associations : { contacts: {} };
|
||||
const selectedGroups = this.state.selectedGroups ? this.state.selectedGroups : [];
|
||||
const { state } = this;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={light}>
|
||||
|
@ -1,19 +1,24 @@
|
||||
import _ from 'lodash';
|
||||
import { uuid } from '../lib/util';
|
||||
import _ from "lodash";
|
||||
import { uuid } from "../lib/util";
|
||||
import { Patp, Path } from "../types/noun";
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class BaseApi<S extends object = {}> {
|
||||
bindPaths: Path[] = [];
|
||||
constructor(public ship: Patp, public channel: any, public store: BaseStore<S>) {}
|
||||
|
||||
unsubscribe(id: number) {
|
||||
this.channel.unsubscribe(id);
|
||||
|
||||
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) {
|
||||
subscribe(path: Path, method, ship = this.ship, app: string, success, fail, quit) {
|
||||
this.bindPaths = _.uniq([...this.bindPaths, path]);
|
||||
|
||||
window.subscriptionId = this.channel.subscribe(ship, app, path,
|
||||
return this.channel.subscribe(
|
||||
this.ship,
|
||||
app,
|
||||
path,
|
||||
(err) => {
|
||||
fail(err);
|
||||
},
|
||||
@ -22,25 +27,30 @@ export default class BaseApi {
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path
|
||||
}
|
||||
path,
|
||||
},
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
quit(qui);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
action(appl, mark, data) {
|
||||
action(appl: string, mark: string, data: any): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.channel.poke(window.ship, appl, mark, data,
|
||||
this.channel.poke(
|
||||
(window as any).ship,
|
||||
appl,
|
||||
mark,
|
||||
data,
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,85 +1,31 @@
|
||||
import BaseApi from './base';
|
||||
import { uuid } from '../lib/util';
|
||||
import { Letter, ChatAction, Envelope } from '../types/chat-update';
|
||||
import { Patp, Path, PatpNoSig } from '../types/noun';
|
||||
import { StoreState } from '../store/type';
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class ChatApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
export default class ChatApi extends BaseApi<StoreState> {
|
||||
|
||||
this.groups = {
|
||||
add: helper.groupAdd.bind(helper),
|
||||
remove: helper.groupRemove.bind(helper)
|
||||
};
|
||||
|
||||
this.chat = {
|
||||
message: helper.chatMessage.bind(helper),
|
||||
read: helper.chatRead.bind(helper)
|
||||
};
|
||||
|
||||
this.chatView = {
|
||||
create: helper.chatViewCreate.bind(helper),
|
||||
delete: helper.chatViewDelete.bind(helper),
|
||||
join: helper.chatViewJoin.bind(helper),
|
||||
groupify: helper.chatViewGroupify.bind(helper)
|
||||
};
|
||||
|
||||
this.chatHook = {
|
||||
addSynced: helper.chatHookAddSynced.bind(helper)
|
||||
};
|
||||
|
||||
this.invite = {
|
||||
accept: helper.inviteAccept.bind(helper),
|
||||
decline: helper.inviteDecline.bind(helper)
|
||||
};
|
||||
|
||||
this.metadata = {
|
||||
add: helper.metadataAdd.bind(helper)
|
||||
};
|
||||
this.sidebarToggle = helper.sidebarToggle.bind(helper);
|
||||
}
|
||||
}
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
groupsAction(data) {
|
||||
return this.action('group-store', 'group-action', data);
|
||||
/**
|
||||
* Fetch backlog
|
||||
*/
|
||||
fetchMessages(start: number, end: number, path: Path) {
|
||||
fetch(`/chat-view/paginate/${start}/${end}${path}`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.store.handleEvent({
|
||||
data: json
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
groupAdd(members, path) {
|
||||
return this.groupsAction({
|
||||
add: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
groupRemove(members, path) {
|
||||
this.groupsAction({
|
||||
remove: {
|
||||
members, path
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatAction(data) {
|
||||
this.action('chat-store', 'json', data);
|
||||
}
|
||||
|
||||
addPendingMessage(msg) {
|
||||
if (this.store.state.pendingMessages.has(msg.path)) {
|
||||
this.store.state.pendingMessages.get(msg.path).unshift(msg.envelope);
|
||||
} else {
|
||||
this.store.state.pendingMessages.set(msg.path, [msg.envelope]);
|
||||
}
|
||||
|
||||
this.store.setState({
|
||||
pendingMessages: this.store.state.pendingMessages
|
||||
});
|
||||
}
|
||||
|
||||
chatMessage(path, author, when, letter) {
|
||||
const data = {
|
||||
/**
|
||||
* Send a message to the chat at path
|
||||
*/
|
||||
message(path: Path, author: Patp, when: string, letter: Letter): Promise<void> {
|
||||
const data: ChatAction = {
|
||||
message: {
|
||||
path,
|
||||
envelope: {
|
||||
@ -92,36 +38,30 @@ class PrivateHelper extends BaseApi {
|
||||
}
|
||||
};
|
||||
|
||||
this.action('chat-hook', 'json', data).then(() => {
|
||||
this.chatRead(path);
|
||||
const promise = this.proxyHookAction(data).then(() => {
|
||||
this.read(path);
|
||||
});
|
||||
data.message.envelope.author = data.message.envelope.author.substr(1);
|
||||
this.addPendingMessage(data.message);
|
||||
this.addPendingMessage(data.message.path, data.message.envelope);
|
||||
return promise;
|
||||
}
|
||||
|
||||
chatRead(path, read) {
|
||||
this.chatAction({ read: { path } });
|
||||
/**
|
||||
* Mark chat as read
|
||||
*/
|
||||
read(path: Path): Promise<any> {
|
||||
return this.storeAction({ read: { path } });
|
||||
}
|
||||
|
||||
chatHookAddSynced(ship, path, askHistory) {
|
||||
return this.action('chat-hook', 'chat-hook-action', {
|
||||
'add-synced': {
|
||||
ship,
|
||||
path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
chatViewAction(data) {
|
||||
return this.action('chat-view', 'json', data);
|
||||
}
|
||||
|
||||
chatViewCreate(
|
||||
title, description, appPath, groupPath,
|
||||
security, members, allowHistory
|
||||
) {
|
||||
return this.chatViewAction({
|
||||
/**
|
||||
* Create a chat and setup metadata
|
||||
*/
|
||||
create(
|
||||
title: string, description: string, appPath: string, groupPath: string,
|
||||
security: any, members: PatpNoSig[], allowHistory: boolean
|
||||
): Promise<any> {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
title,
|
||||
description,
|
||||
@ -134,12 +74,20 @@ class PrivateHelper extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
chatViewDelete(path) {
|
||||
this.chatViewAction({ delete: { 'app-path': path } });
|
||||
/**
|
||||
* Deletes a chat
|
||||
*
|
||||
* If we don't host the chat, then it just leaves
|
||||
*/
|
||||
delete(path: Path) {
|
||||
this.viewAction({ delete: { 'app-path': path } });
|
||||
}
|
||||
|
||||
chatViewJoin(ship, path, askHistory) {
|
||||
this.chatViewAction({
|
||||
/**
|
||||
* Join a chat
|
||||
*/
|
||||
join(ship: Patp, path: Path, askHistory: boolean): Promise<any> {
|
||||
return this.viewAction({
|
||||
join: {
|
||||
ship,
|
||||
'app-path': path,
|
||||
@ -148,75 +96,64 @@ class PrivateHelper extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
chatViewGroupify(path, group = null, inclusive = false) {
|
||||
const action = { groupify: { 'app-path': path, existing: null } };
|
||||
/**
|
||||
* Groupify a chat that we host
|
||||
*
|
||||
* Will delete the old chat, recreate it based on a proper group,
|
||||
* and invite the current whitelist to that group.
|
||||
* existing messages get moved over.
|
||||
*
|
||||
* :existing is provided, associates chat with that group instead
|
||||
* creating a new one. :inclusive indicates whether or not to add
|
||||
* chat members to the group, if they aren't there already.
|
||||
*/
|
||||
groupify(path: Path, group: Path | null = null, inclusive = false) {
|
||||
let action: any = { groupify: { 'app-path': path, existing: null } };
|
||||
if (group) {
|
||||
action.groupify.existing = {
|
||||
'group-path': group,
|
||||
inclusive: inclusive
|
||||
};
|
||||
}
|
||||
return this.chatViewAction(action);
|
||||
return this.viewAction(action);
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
this.action('invite-store', 'json', data);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
this.inviteAction({
|
||||
accept: {
|
||||
path: '/chat',
|
||||
uid
|
||||
/**
|
||||
* Begin syncing a chat from the host
|
||||
*/
|
||||
addSynced(ship: Patp, path: Path, askHistory: boolean): Promise<any> {
|
||||
return this.action('chat-hook', 'chat-hook-action', {
|
||||
'add-synced': {
|
||||
ship,
|
||||
path,
|
||||
'ask-history': askHistory
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
this.inviteAction({
|
||||
decline: {
|
||||
path: '/chat',
|
||||
uid
|
||||
}
|
||||
});
|
||||
|
||||
private storeAction(action: ChatAction): Promise<any> {
|
||||
return this.action('chat-store', 'json', action)
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
private proxyHookAction(action: ChatAction): Promise<any> {
|
||||
return this.action('chat-hook', 'json', action);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
const creator = `~${window.ship}`;
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'chat'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
});
|
||||
private viewAction(action: unknown): Promise<any> {
|
||||
return this.action('chat-view', 'json', action);
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
private addPendingMessage(path: Path, envelope: Envelope) {
|
||||
const pending = this.store.state.pendingMessages.get(path);
|
||||
if (pending) {
|
||||
pending.unshift(envelope);
|
||||
} else {
|
||||
this.store.state.pendingMessages.set(path, [envelope]);
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
|
||||
this.store.setState({
|
||||
pendingMessages: this.store.state.pendingMessages
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
62
pkg/interface/src/api/contacts.ts
Normal file
62
pkg/interface/src/api/contacts.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path } from '../types/noun';
|
||||
import { Contact, ContactEdit } from '../types/contact-update';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
|
||||
create(path: Path, ships: Patp[] = [], title: string, description: string) {
|
||||
return this.viewAction({
|
||||
create: {
|
||||
path,
|
||||
ships,
|
||||
title,
|
||||
description
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
share(recipient: Patp, path: Patp, ship: Patp, contact: Contact) {
|
||||
return this.viewAction({
|
||||
share: {
|
||||
recipient, path, ship, contact
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contactDelete(path: Path) {
|
||||
return this.viewAction({ delete: { path } });
|
||||
}
|
||||
|
||||
contactRemove(path: Path, ship: Patp) {
|
||||
return this.viewAction({ remove: { path, ship } });
|
||||
}
|
||||
|
||||
|
||||
|
||||
contactEdit(path: Path, ship: Patp, editField: ContactEdit) {
|
||||
/* editField can be...
|
||||
{nickname: ''}
|
||||
{email: ''}
|
||||
{phone: ''}
|
||||
{website: ''}
|
||||
{notes: ''}
|
||||
{color: 'fff'} // with no 0x prefix
|
||||
{avatar: null}
|
||||
{avatar: {url: ''}}
|
||||
*/
|
||||
return this.hookAction({
|
||||
edit: {
|
||||
path, ship, 'edit-field': editField
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private hookAction(data) {
|
||||
return this.action('contact-hook', 'contact-action', data);
|
||||
}
|
||||
|
||||
private viewAction(data) {
|
||||
return this.action('contact-view', 'json', data);
|
||||
}
|
||||
}
|
@ -1,26 +1,29 @@
|
||||
import { Patp } from '../types/noun';
|
||||
import BaseApi from './base';
|
||||
import ChatApi from './chat';
|
||||
import { StoreState } from '../store/type';
|
||||
import GlobalStore from '../store/store';
|
||||
import LocalApi from './local';
|
||||
import InviteApi from './invite';
|
||||
import MetadataApi from './metadata';
|
||||
import ContactsApi from './contacts';
|
||||
import GroupsApi from './groups';
|
||||
import LaunchApi from './launch';
|
||||
import LinksApi from './links';
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
setSelected(selected) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected: selected
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
export default class GlobalApi extends BaseApi<StoreState> {
|
||||
chat = new ChatApi(this.ship, this.channel, this.store);
|
||||
local = new LocalApi(this.ship, this.channel, this.store);
|
||||
invite = new InviteApi(this.ship, this.channel, this.store);
|
||||
metadata = new MetadataApi(this.ship, this.channel, this.store);
|
||||
contacts = new ContactsApi(this.ship, this.channel, this.store);
|
||||
groups = new GroupsApi(this.ship, this.channel, this.store);
|
||||
launch = new LaunchApi(this.ship, this.channel, this.store);
|
||||
links = new LinksApi(this.ship, this.channel, this.store);
|
||||
|
||||
}
|
||||
|
||||
export default class GlobalApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.setSelected = helper.setSelected.bind(helper);
|
||||
constructor(public ship: Patp, public channel: any, public store: GlobalStore) {
|
||||
super(ship,channel,store);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,37 +1,19 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
|
||||
|
||||
export default class GroupsApi {
|
||||
constructor(ship, channel, store) {
|
||||
const helper = new PrivateHelper(ship, channel, store);
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
add(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
add: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
this.ship = ship;
|
||||
this.subscribe = helper.subscribe.bind(helper);
|
||||
|
||||
this.contactHook = {
|
||||
edit: helper.contactEdit.bind(helper)
|
||||
};
|
||||
|
||||
this.contactView = {
|
||||
create: helper.contactCreate.bind(helper),
|
||||
delete: helper.contactDelete.bind(helper),
|
||||
remove: helper.contactRemove.bind(helper),
|
||||
share: helper.contactShare.bind(helper)
|
||||
};
|
||||
|
||||
this.group = {
|
||||
add: helper.groupAdd.bind(helper),
|
||||
remove: helper.groupRemove.bind(helper)
|
||||
};
|
||||
|
||||
this.metadata = {
|
||||
add: helper.metadataAdd.bind(helper)
|
||||
};
|
||||
|
||||
this.invite = {
|
||||
accept: helper.inviteAccept.bind(helper),
|
||||
decline: helper.inviteDecline.bind(helper)
|
||||
};
|
||||
remove(path: Path, ships: Patp[] = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
remove: { members: ships, path }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,17 +33,9 @@ class PrivateHelper extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
groupAdd(path, ships = []) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
add: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
groupRemove(path, ships) {
|
||||
return this.action('group-store', 'group-action', {
|
||||
remove: { members: ships, path }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
contactShare(recipient, path, ship, contact) {
|
||||
return this.contactViewAction({
|
||||
|
27
pkg/interface/src/api/invite.ts
Normal file
27
pkg/interface/src/api/invite.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { Serial, Path } from "../types/noun";
|
||||
|
||||
export default class InviteApi extends BaseApi<StoreState> {
|
||||
accept(app: Path, uid: Serial) {
|
||||
return this.inviteAction({
|
||||
accept: {
|
||||
path: app,
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
decline(app: Path, uid: Serial) {
|
||||
return this.inviteAction({
|
||||
decline: {
|
||||
path: app,
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inviteAction(action) {
|
||||
return this.action('invite-store', 'json', action);
|
||||
}
|
||||
}
|
@ -1,51 +1,36 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
class PrivateHelper extends BaseApi {
|
||||
launchAction(data) {
|
||||
this.action('launch', 'launch-action', data);
|
||||
}
|
||||
|
||||
launchAdd(name, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
export default class LaunchApi extends BaseApi<StoreState> {
|
||||
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
this.launchAction({ add: { name, tile } });
|
||||
}
|
||||
|
||||
launchRemove(name) {
|
||||
remove(name: string) {
|
||||
this.launchAction({ remove: name });
|
||||
}
|
||||
|
||||
launchChangeOrder(orderedTiles = []) {
|
||||
changeOrder(orderedTiles = []) {
|
||||
this.launchAction({ 'change-order': orderedTiles });
|
||||
}
|
||||
|
||||
launchChangeFirstTime(firstTime = true) {
|
||||
changeFirstTime(firstTime = true) {
|
||||
this.launchAction({ 'change-first-time': firstTime });
|
||||
}
|
||||
|
||||
launchChangeIsShown(name, isShown = true) {
|
||||
changeIsShown(name: string, isShown = true) {
|
||||
this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||
}
|
||||
|
||||
weatherAction(latlng) {
|
||||
weather(latlng: any) {
|
||||
this.action('weather', 'json', latlng);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
this.weather = helper.weatherAction.bind(helper);
|
||||
private launchAction(data) {
|
||||
this.action('launch', 'launch-action', data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,82 +1,23 @@
|
||||
import { stringToTa } from '../lib/util';
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '../types/noun';
|
||||
|
||||
export default class LinksApi extends BaseApi {
|
||||
constructor(ship, channel, store) {
|
||||
super(ship, channel, store);
|
||||
this.ship = ship;
|
||||
export default class LinksApi extends BaseApi<StoreState> {
|
||||
|
||||
this.invite = {
|
||||
accept: this.inviteAccept.bind(this),
|
||||
decline: this.inviteDecline.bind(this)
|
||||
};
|
||||
|
||||
this.groups = {
|
||||
remove: this.groupRemove.bind(this)
|
||||
};
|
||||
|
||||
this.fetchLink = this.fetchLink.bind(this);
|
||||
}
|
||||
|
||||
fetchLink(path, result, fail, quit) {
|
||||
this.subscribe.bind(this)(
|
||||
path,
|
||||
'PUT',
|
||||
this.ship,
|
||||
'link-view',
|
||||
result,
|
||||
fail,
|
||||
quit
|
||||
);
|
||||
}
|
||||
|
||||
groupsAction(data) {
|
||||
this.action('group-store', 'group-action', data);
|
||||
}
|
||||
|
||||
groupRemove(path, members) {
|
||||
this.groupsAction({
|
||||
remove: {
|
||||
path,
|
||||
members
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteAction(data) {
|
||||
this.action('invite-store', 'json', data);
|
||||
}
|
||||
|
||||
inviteAccept(uid) {
|
||||
this.inviteAction({
|
||||
accept: {
|
||||
path: '/link',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inviteDecline(uid) {
|
||||
this.inviteAction({
|
||||
decline: {
|
||||
path: '/link',
|
||||
uid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCommentsPage(path, url, page) {
|
||||
getCommentsPage(path: Path, url: string, page: number) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/' + page + '/discussions/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
(res) => {
|
||||
if (res.data['initial-discussions']) {
|
||||
if (res.data['link-update']['initial-discussions']) {
|
||||
// these aren't returned with the response,
|
||||
// so this ensures the reducers know them.
|
||||
res.data['initial-discussions'].path = path;
|
||||
res.data['initial-discussions'].url = url;
|
||||
res.data['link-update']['initial-discussions'].path = path;
|
||||
res.data['link-update']['initial-discussions'].url = url;
|
||||
}
|
||||
this.store.handleEvent(res);
|
||||
},
|
||||
@ -85,7 +26,7 @@ export default class LinksApi extends BaseApi {
|
||||
);
|
||||
}
|
||||
|
||||
getPage(path, page) {
|
||||
getPage(path: Path, page: number) {
|
||||
const endpoint = '/json/' + page + '/submissions' + path;
|
||||
this.fetchLink(
|
||||
endpoint,
|
||||
@ -97,7 +38,7 @@ export default class LinksApi extends BaseApi {
|
||||
);
|
||||
}
|
||||
|
||||
getSubmission(path, url, callback) {
|
||||
getSubmission(path: Path, url: string, callback) {
|
||||
const strictUrl = stringToTa(url);
|
||||
const endpoint = '/json/0/submission/' + strictUrl + path;
|
||||
this.fetchLink(
|
||||
@ -114,34 +55,28 @@ export default class LinksApi extends BaseApi {
|
||||
);
|
||||
}
|
||||
|
||||
linkViewAction(data) {
|
||||
return this.action('link-view', 'link-view-action', data);
|
||||
}
|
||||
|
||||
|
||||
createCollection(path, title, description, members, realGroup) {
|
||||
// members is either {group:'/group-path'} or {'ships':[~zod]},
|
||||
// with realGroup signifying if ships should become a managed group or not.
|
||||
return this.linkViewAction({
|
||||
return this.viewAction({
|
||||
create: { path, title, description, members, realGroup }
|
||||
});
|
||||
}
|
||||
|
||||
deleteCollection(path) {
|
||||
return this.linkViewAction({
|
||||
return this.viewAction({
|
||||
delete: { path }
|
||||
});
|
||||
}
|
||||
|
||||
inviteToCollection(path, ships) {
|
||||
return this.linkViewAction({
|
||||
return this.viewAction({
|
||||
invite: { path, ships }
|
||||
});
|
||||
}
|
||||
|
||||
linkListenAction(data) {
|
||||
return this.action('link-listen-hook', 'link-listen-action', data);
|
||||
}
|
||||
|
||||
joinCollection(path) {
|
||||
return this.linkListenAction({ watch: path });
|
||||
}
|
||||
@ -150,73 +85,47 @@ export default class LinksApi extends BaseApi {
|
||||
return this.linkListenAction({ leave: path });
|
||||
}
|
||||
|
||||
linkAction(data) {
|
||||
return this.action('link-store', 'link-action', data);
|
||||
}
|
||||
|
||||
postLink(path, url, title) {
|
||||
postLink(path: Path, url: string, title: string) {
|
||||
return this.linkAction({
|
||||
save: { path, url, title }
|
||||
});
|
||||
}
|
||||
|
||||
postComment(path, url, comment) {
|
||||
postComment(path: Path, url: string, comment: string) {
|
||||
return this.linkAction({
|
||||
note: { path, url, udon: comment }
|
||||
});
|
||||
}
|
||||
|
||||
// leave url as null to mark all under path as read
|
||||
seenLink(path, url = null) {
|
||||
seenLink(path: Path, url?: string) {
|
||||
return this.linkAction({
|
||||
seen: { path, url }
|
||||
seen: { path, url: url || null }
|
||||
});
|
||||
}
|
||||
|
||||
metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
private linkAction(data) {
|
||||
return this.action('link-store', 'link-action', data);
|
||||
}
|
||||
|
||||
metadataAdd(appPath, groupPath, title, description, dateCreated, color) {
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'link'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator: `~${window.ship}`
|
||||
}
|
||||
}
|
||||
});
|
||||
private viewAction(data) {
|
||||
return this.action('link-view', 'link-view-action', data);
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
private linkListenAction(data) {
|
||||
return this.action('link-listen-hook', 'link-listen-action', data);
|
||||
}
|
||||
|
||||
setSelected(selected) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected: selected
|
||||
}
|
||||
}
|
||||
});
|
||||
private fetchLink(path: Path, result, fail, quit) {
|
||||
this.subscribe.bind(this)(
|
||||
path,
|
||||
'PUT',
|
||||
this.ship,
|
||||
'link-view',
|
||||
result,
|
||||
fail,
|
||||
quit
|
||||
);
|
||||
}
|
||||
}
|
||||
|
28
pkg/interface/src/api/local.ts
Normal file
28
pkg/interface/src/api/local.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { SelectedGroup } from "../types/local-update";
|
||||
|
||||
|
||||
|
||||
export default class LocalApi extends BaseApi<StoreState> {
|
||||
setSelected(selected: SelectedGroup[]) {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
selected
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
31
pkg/interface/src/api/metadata.ts
Normal file
31
pkg/interface/src/api/metadata.ts
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp } from '../types/noun';
|
||||
|
||||
export default class MetadataApi extends BaseApi<StoreState> {
|
||||
|
||||
metadataAdd(appPath: Path, groupPath: Path, title: string, description: string, dateCreated: string, color: string) {
|
||||
const creator = `~${this.ship}`;
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
'group-path': groupPath,
|
||||
resource: {
|
||||
'app-path': appPath,
|
||||
'app-name': 'contacts'
|
||||
},
|
||||
metadata: {
|
||||
title,
|
||||
description,
|
||||
color,
|
||||
'date-created': dateCreated,
|
||||
creator
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private metadataAction(data) {
|
||||
return this.action('metadata-hook', 'metadata-action', data);
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
import BaseApi from './base';
|
||||
import { PublishResponse } from '../types/publish-response';
|
||||
import { PatpNoSig } from '../types/noun';
|
||||
import { BookId, NoteId } from '../types/publish-update';
|
||||
|
||||
export default class PublishApi extends BaseApi {
|
||||
handleEvent(data) {
|
||||
handleEvent(data: PublishResponse) {
|
||||
this.store.handleEvent({ data: { 'publish-response' : data } });
|
||||
}
|
||||
|
||||
@ -16,7 +19,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotebook(host, book) {
|
||||
fetchNotebook(host: PatpNoSig, book: BookId) {
|
||||
fetch(`/publish-view/${host}/${book}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -29,7 +32,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNote(host, book, note) {
|
||||
fetchNote(host: PatpNoSig, book: BookId, note: NoteId) {
|
||||
fetch(`/publish-view/${host}/${book}/${note}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -43,7 +46,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchNotesPage(host, book, start, length) {
|
||||
fetchNotesPage(host: PatpNoSig, book: BookId, start: number, length: number) {
|
||||
fetch(`/publish-view/notes/${host}/${book}/${start}/${length}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -58,7 +61,7 @@ export default class PublishApi extends BaseApi {
|
||||
});
|
||||
}
|
||||
|
||||
fetchCommentsPage(host, book, note, start, length) {
|
||||
fetchCommentsPage(host: PatpNoSig, book: BookId, note: NoteId, start: number, length: number) {
|
||||
fetch(`/publish-view/comments/${host}/${book}/${note}/${start}/${length}.json`)
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
@ -74,30 +77,8 @@ 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) {
|
||||
publishAction(act: any) {
|
||||
return this.action('publish', 'publish-action', act);
|
||||
}
|
||||
|
||||
sidebarToggle() {
|
||||
let sidebarBoolean = true;
|
||||
if (this.store.state.sidebarShown === true) {
|
||||
sidebarBoolean = false;
|
||||
}
|
||||
this.store.handleEvent({
|
||||
data: {
|
||||
local: {
|
||||
sidebarToggle: sidebarBoolean
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ export default class GroupFilter extends Component {
|
||||
const selected = localStorage.getItem('urbit-selectedGroups');
|
||||
if (selected) {
|
||||
this.setState({ selected: JSON.parse(selected) }, (() => {
|
||||
this.props.api.setSelected(this.state.selected);
|
||||
this.props.api.local.setSelected(this.state.selected);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export class SidebarSwitcher extends Component {
|
||||
<a
|
||||
className='pointer flex-shrink-0'
|
||||
onClick={() => {
|
||||
this.props.api.sidebarToggle();
|
||||
this.props.api.local.sidebarToggle();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
|
@ -1,8 +1,14 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ChatUpdate } from '../types/chat-update';
|
||||
import { ChatHookUpdate } from '../types/chat-hook-update';
|
||||
|
||||
export default class ChatReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'chat-update', false);
|
||||
type ChatState = Pick<StoreState, 'chatInitialized' | 'chatSynced' | 'inbox' | 'pendingMessages'>;
|
||||
|
||||
export default class ChatReducer<S extends ChatState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['chat-update'];
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
this.pending(data, state);
|
||||
@ -13,13 +19,13 @@ export default class ChatReducer {
|
||||
this.delete(data, state);
|
||||
}
|
||||
|
||||
data = _.get(json, 'chat-hook-update', false);
|
||||
if (data) {
|
||||
this.hook(data, state);
|
||||
const hookUpdate = json['chat-hook-update'];
|
||||
if (hookUpdate) {
|
||||
this.hook(hookUpdate, state);
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.inbox = data;
|
||||
@ -27,11 +33,11 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
hook(json, state) {
|
||||
hook(json: ChatHookUpdate, state: S) {
|
||||
state.chatSynced = json;
|
||||
}
|
||||
|
||||
message(json, state) {
|
||||
message(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'message', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].envelopes.unshift(data.envelope);
|
||||
@ -40,7 +46,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
messages(json, state) {
|
||||
messages(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'messages', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].envelopes =
|
||||
@ -48,7 +54,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
read(json, state) {
|
||||
read(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'read', false);
|
||||
if (data) {
|
||||
state.inbox[data.path].config.read =
|
||||
@ -56,7 +62,7 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.inbox[data.path] = {
|
||||
@ -69,25 +75,25 @@ export default class ChatReducer {
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: ChatUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.inbox[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
pending(json, state) {
|
||||
pending(json: ChatUpdate, state: S) {
|
||||
const msg = _.get(json, 'message', false);
|
||||
if (!msg || !state.pendingMessages.has(msg.path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mailbox = state.pendingMessages.get(msg.path);
|
||||
const mailbox = state.pendingMessages.get(msg.path) || [];
|
||||
|
||||
for (const pendingMsg of mailbox) {
|
||||
if (msg.envelope.uid === pendingMsg.uid) {
|
||||
const index = mailbox.indexOf(pendingMsg);
|
||||
state.pendingMessages.get(msg.path).splice(index, 1);
|
||||
mailbox.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { ContactUpdate } from '../types/contact-update';
|
||||
|
||||
export default class ContactReducer {
|
||||
reduce(json, state) {
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
export default class ContactReducer<S extends ContactState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'contact-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -13,28 +18,28 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.contacts = data;
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.contacts[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.contacts[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (
|
||||
data &&
|
||||
@ -44,7 +49,7 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (
|
||||
data &&
|
||||
@ -55,7 +60,7 @@ export default class ContactReducer {
|
||||
}
|
||||
}
|
||||
|
||||
edit(json, state) {
|
||||
edit(json: ContactUpdate, state: S) {
|
||||
const data = _.get(json, 'edit', false);
|
||||
if (
|
||||
data &&
|
||||
|
@ -1,8 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { GroupUpdate } from '../types/group-update';
|
||||
|
||||
export default class GroupReducer {
|
||||
type GroupState = Pick<StoreState, 'groups' | 'groupKeys'>;
|
||||
|
||||
reduce(json, state) {
|
||||
export default class GroupReducer<S extends GroupState> {
|
||||
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, "group-update", false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -15,7 +20,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
for (let group in data) {
|
||||
@ -24,7 +29,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (const member of data.members) {
|
||||
@ -33,7 +38,7 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (const member of data.members) {
|
||||
@ -42,21 +47,21 @@ export default class GroupReducer {
|
||||
}
|
||||
}
|
||||
|
||||
bundle(json, state) {
|
||||
bundle(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'bundle', false);
|
||||
if (data) {
|
||||
state.groups[data.path] = new Set();
|
||||
}
|
||||
}
|
||||
|
||||
unbundle(json, state) {
|
||||
unbundle(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'unbundle', false);
|
||||
if (data) {
|
||||
delete state.groups[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
keys(json, state) {
|
||||
keys(json: GroupUpdate, state: S) {
|
||||
const data = _.get(json, 'keys', false);
|
||||
if (data) {
|
||||
state.groupKeys = new Set(data.keys);
|
||||
|
@ -1,10 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { InviteUpdate } from '../types/invite-update';
|
||||
|
||||
export default class InviteReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'invite-update', false);
|
||||
type InviteState = Pick<StoreState, "invites">;
|
||||
|
||||
|
||||
export default class InviteReducer<S extends InviteState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['invite-update'];
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.initial(data, state);
|
||||
this.create(data, state);
|
||||
this.delete(data, state);
|
||||
@ -14,35 +19,35 @@ export default class InviteReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.invites = data;
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.invites[data.path] = {};
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
invite(json, state) {
|
||||
invite(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'invite', false);
|
||||
if (data) {
|
||||
state.invites[data.path][data.uid] = data.invite;
|
||||
}
|
||||
}
|
||||
|
||||
accepted(json, state) {
|
||||
accepted(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'accepted', false);
|
||||
if (data) {
|
||||
console.log(data);
|
||||
@ -50,7 +55,7 @@ export default class InviteReducer {
|
||||
}
|
||||
}
|
||||
|
||||
decline(json, state) {
|
||||
decline(json: InviteUpdate, state: S) {
|
||||
const data = _.get(json, 'decline', false);
|
||||
if (data) {
|
||||
delete state.invites[data.path][data.uid];
|
||||
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { LaunchUpdate } from '../types/launch-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LaunchReducer {
|
||||
reduce(json, state) {
|
||||
type LaunchState = Pick<StoreState, 'launch' | 'weather' | 'location'>;
|
||||
|
||||
export default class LaunchReducer<S extends LaunchState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'launch-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -22,28 +27,28 @@ export default class LaunchReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.launch = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeFirstTime(json, state) {
|
||||
changeFirstTime(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeFirstTime', false);
|
||||
if (data) {
|
||||
state.launch.firstTime = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeOrder(json, state) {
|
||||
changeOrder(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeOrder', false);
|
||||
if (data) {
|
||||
state.launch.tileOrdering = data;
|
||||
}
|
||||
}
|
||||
|
||||
changeIsShown(json, state) {
|
||||
changeIsShown(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
console.log(json, data);
|
||||
if (data) {
|
||||
|
@ -1,20 +1,28 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { LinkUpdate, Pagination } from '../types/link-update';
|
||||
|
||||
// page size as expected from link-view.
|
||||
// must change in parallel with the +page-size in /app/link-view to
|
||||
// ensure sane behavior.
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
export default class LinkUpdateReducer {
|
||||
reduce(json, state) {
|
||||
this.submissionsPage(json, state);
|
||||
this.submissionsUpdate(json, state);
|
||||
this.discussionsPage(json, state);
|
||||
this.discussionsUpdate(json, state);
|
||||
this.observationUpdate(json, state);
|
||||
type LinkState = Pick<StoreState, 'linksSeen' | 'links' | 'linkListening' | 'linkComments'>;
|
||||
|
||||
export default class LinkUpdateReducer<S extends LinkState> {
|
||||
reduce(json: any, state: S) {
|
||||
const data = _.get(json, 'link-update', false);
|
||||
if(data) {
|
||||
this.submissionsPage(data, state);
|
||||
this.submissionsUpdate(data, state);
|
||||
this.discussionsPage(data, state);
|
||||
this.discussionsUpdate(data, state);
|
||||
this.observationUpdate(data, state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
submissionsPage(json, state) {
|
||||
submissionsPage(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-submissions', false);
|
||||
if (data) {
|
||||
// { "initial-submissions": {
|
||||
@ -32,7 +40,12 @@ export default class LinkUpdateReducer {
|
||||
|
||||
// if we didn't have any state for this path yet, initialize.
|
||||
if (!state.links[path]) {
|
||||
state.links[path] = { local: {} };
|
||||
state.links[path] = {
|
||||
local: {},
|
||||
totalItems: here.totalItems,
|
||||
totalPages: here.totalPages,
|
||||
unseenCount: here.unseenCount
|
||||
};
|
||||
}
|
||||
|
||||
// since data contains an up-to-date full version of the page,
|
||||
@ -47,17 +60,17 @@ export default class LinkUpdateReducer {
|
||||
|
||||
// write seen status to a separate structure,
|
||||
// for easier modification later.
|
||||
if (!state.seen[path]) {
|
||||
state.seen[path] = {};
|
||||
if (!state.linksSeen[path]) {
|
||||
state.linksSeen[path] = {};
|
||||
}
|
||||
(here.page || []).map((submission) => {
|
||||
state.seen[path][submission.url] = submission.seen;
|
||||
state.linksSeen[path][submission.url] = submission.seen;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
submissionsUpdate(json, state) {
|
||||
submissionsUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'submissions', false);
|
||||
if (data) {
|
||||
// { "submissions": {
|
||||
@ -70,7 +83,7 @@ export default class LinkUpdateReducer {
|
||||
// stub in a comment count, which is more or less guaranteed to be 0
|
||||
data.pages = data.pages.map((submission) => {
|
||||
submission.commentCount = 0;
|
||||
state.seen[path][submission.url] = false;
|
||||
state.linksSeen[path][submission.url] = false;
|
||||
return submission;
|
||||
});
|
||||
|
||||
@ -83,7 +96,7 @@ export default class LinkUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
discussionsPage(json, state) {
|
||||
discussionsPage(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-discussions', false);
|
||||
if (data) {
|
||||
// { "initial-discussions": {
|
||||
@ -100,13 +113,17 @@ export default class LinkUpdateReducer {
|
||||
const page = data.pageNumber;
|
||||
|
||||
// if we didn't have any state for this path yet, initialize.
|
||||
if (!state.comments[path]) {
|
||||
state.comments[path] = {};
|
||||
if (!state.linkComments[path]) {
|
||||
state.linkComments[path] = {};
|
||||
}
|
||||
if (!state.comments[path][url]) {
|
||||
state.comments[path][url] = { local: {} };
|
||||
}
|
||||
const here = state.comments[path][url];
|
||||
let comments = {...{
|
||||
local: {},
|
||||
totalPages: data.totalPages,
|
||||
totalItems: data.totalItems
|
||||
}, ...state.linkComments[path][url] };
|
||||
|
||||
state.linkComments[path][url] = comments;
|
||||
const here = state.linkComments[path][url];
|
||||
|
||||
// since data contains an up-to-date full version of the page,
|
||||
// we can safely overwrite the one in state.
|
||||
@ -117,7 +134,7 @@ export default class LinkUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
discussionsUpdate(json, state) {
|
||||
discussionsUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'discussions', false);
|
||||
if (data) {
|
||||
// { "discussions": {
|
||||
@ -130,13 +147,13 @@ export default class LinkUpdateReducer {
|
||||
const url = data.url;
|
||||
|
||||
// add new comments to state, update totals
|
||||
state.comments[path][url] = this._addNewItems(
|
||||
data.comments, state.comments[path][url]
|
||||
state.linkComments[path][url] = this._addNewItems(
|
||||
data.comments || [], state.linkComments[path][url]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
observationUpdate(json, state) {
|
||||
observationUpdate(json: LinkUpdate, state: S) {
|
||||
const data = _.get(json, 'observation', false);
|
||||
if (data) {
|
||||
// { "observation": {
|
||||
@ -145,10 +162,10 @@ export default class LinkUpdateReducer {
|
||||
// } }
|
||||
|
||||
const path = data.path;
|
||||
if (!state.seen[path]) {
|
||||
state.seen[path] = {};
|
||||
if (!state.linksSeen[path]) {
|
||||
state.linksSeen[path] = {};
|
||||
}
|
||||
const seen = state.seen[path];
|
||||
const seen = state.linksSeen[path];
|
||||
|
||||
// mark urls as seen
|
||||
data.urls.map((url) => {
|
||||
@ -163,7 +180,7 @@ export default class LinkUpdateReducer {
|
||||
|
||||
//
|
||||
|
||||
_addNewItems(items, pages, page = 0) {
|
||||
_addNewItems<S extends { time: number }>(items: S[], pages: Pagination<S>, page = 0) {
|
||||
if (!pages) {
|
||||
pages = {
|
||||
local: {},
|
||||
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LinkListenUpdate } from '../types/link-listen-update';
|
||||
|
||||
export default class ListenUpdateReducer {
|
||||
reduce(json, state) {
|
||||
type LinkListenState = Pick<StoreState, 'linkListening'>;
|
||||
|
||||
export default class LinkListenReducer<S extends LinkListenState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'link-listen-update', false);
|
||||
if (data) {
|
||||
this.listening(data, state);
|
||||
@ -10,24 +15,24 @@ export default class ListenUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
listening(json, state) {
|
||||
listening(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'listening', false);
|
||||
if (data) {
|
||||
state.listening = new Set(data);
|
||||
state.linkListening = new Set(data);
|
||||
}
|
||||
}
|
||||
|
||||
watch(json, state) {
|
||||
watch(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'watch', false);
|
||||
if (data) {
|
||||
state.listening.add(data);
|
||||
state.linkListening.add(data);
|
||||
}
|
||||
}
|
||||
|
||||
leave(json, state) {
|
||||
leave(json: LinkListenUpdate, state: S) {
|
||||
const data = _.get(json, 'leave', false);
|
||||
if (data) {
|
||||
state.listening.delete(data);
|
||||
state.linkListening.delete(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { LocalUpdate } from '../types/local-update';
|
||||
|
||||
export default class LocalReducer {
|
||||
reduce(json, state) {
|
||||
const data = _.get(json, 'local', false);
|
||||
type LocalState = Pick<StoreState, 'sidebarShown' | 'selectedGroups'>;
|
||||
|
||||
export default class LocalReducer<S extends LocalState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = json['local'];
|
||||
if (data) {
|
||||
this.sidebarToggle(data, state);
|
||||
this.setSelected(data, state);
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggle(obj, state) {
|
||||
const data = _.has(obj, 'sidebarToggle', false);
|
||||
if (data) {
|
||||
state.sidebarShown = obj.sidebarToggle;
|
||||
}
|
||||
sidebarToggle(obj: LocalUpdate, state: S) {
|
||||
if ('sidebarToggle' in obj) {
|
||||
state.sidebarShown = !state.sidebarShown;
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(obj, state) {
|
||||
const data = _.has(obj, 'selected', false);
|
||||
if (data) {
|
||||
setSelected(obj: LocalUpdate, state: S) {
|
||||
if ('selected' in obj) {
|
||||
state.selectedGroups = obj.selected;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class MetadataReducer {
|
||||
reduce(json, state) {
|
||||
let data = _.get(json, 'metadata-update', false);
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
import { MetadataUpdate } from '../types/metadata-update';
|
||||
import { Cage } from '../types/cage';
|
||||
|
||||
type MetadataState = Pick<StoreState, 'associations'>;
|
||||
|
||||
export default class MetadataReducer<S extends MetadataState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
let data = json['metadata-update']
|
||||
if (data) {
|
||||
console.log('data: ', data);
|
||||
this.associations(data, state);
|
||||
@ -13,7 +20,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
associations(json, state) {
|
||||
associations(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -34,7 +41,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -53,7 +60,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
update(json, state) {
|
||||
update(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
@ -72,7 +79,7 @@ export default class MetadataReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { PermissionUpdate } from '../types/permission-update';
|
||||
|
||||
export default class PermissionReducer {
|
||||
reduce(json, state) {
|
||||
type PermissionState = Pick<StoreState, "permissions">;
|
||||
|
||||
export default class PermissionReducer<S extends PermissionState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'permission-update', false);
|
||||
if (data) {
|
||||
this.initial(data, state);
|
||||
@ -12,7 +17,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
initial(json, state) {
|
||||
initial(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
for (const perm in data) {
|
||||
@ -24,7 +29,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
create(json, state) {
|
||||
create(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'create', false);
|
||||
if (data) {
|
||||
state.permissions[data.path] = {
|
||||
@ -34,14 +39,14 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
delete(json, state) {
|
||||
delete(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'delete', false);
|
||||
if (data) {
|
||||
delete state.permissions[data.path];
|
||||
}
|
||||
}
|
||||
|
||||
add(json, state) {
|
||||
add(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
for (const member of data.who) {
|
||||
@ -50,7 +55,7 @@ export default class PermissionReducer {
|
||||
}
|
||||
}
|
||||
|
||||
remove(json, state) {
|
||||
remove(json: PermissionUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
for (const member of data.who) {
|
||||
|
@ -1,7 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
|
||||
export default class PublishResponseReducer {
|
||||
reduce(json, state) {
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
||||
export default class PublishResponseReducer<S extends PublishState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 'publish-response', false);
|
||||
if (!data) { return; }
|
||||
switch(data.type) {
|
||||
@ -194,12 +198,4 @@ export default class PublishResponseReducer {
|
||||
throw Error("tried to fetch paginated comments, but we don't have the note");
|
||||
}
|
||||
}
|
||||
|
||||
sidebarToggle(json, state) {
|
||||
let data = _.has(json.data, 'sidebarToggle', false);
|
||||
if (data) {
|
||||
state.sidebarShown = json.data.sidebarToggle;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export default class PublishUpdateReducer {
|
||||
reduce(preJson, state){
|
||||
let json = _.get(preJson, "publish-update", false);
|
||||
switch(Object.keys(json)[0]){
|
||||
import { PublishUpdate } from '../types/publish-update';
|
||||
import { Cage } from '../types/cage';
|
||||
import { StoreState } from '../store/type';
|
||||
import { getTagFromFrond } from '../types/noun';
|
||||
|
||||
type PublishState = Pick<StoreState, 'notebooks'>;
|
||||
|
||||
|
||||
export default class PublishUpdateReducer<S extends PublishState> {
|
||||
reduce(data: Cage, state: S){
|
||||
let json = data["publish-update"];
|
||||
if(!json) {
|
||||
return;
|
||||
}
|
||||
const tag = getTagFromFrond(json);
|
||||
switch(tag){
|
||||
case "add-book":
|
||||
this.addBook(json["add-book"], state);
|
||||
break;
|
||||
@ -39,7 +51,7 @@ export default class PublishUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
addBook(json, state) {
|
||||
addBook(json, state: S) {
|
||||
let host = Object.keys(json)[0];
|
||||
let book = Object.keys(json[host])[0];
|
||||
if (state.notebooks[host]) {
|
||||
@ -49,7 +61,7 @@ export default class PublishUpdateReducer {
|
||||
}
|
||||
}
|
||||
|
||||
addNote(json, state) {
|
||||
addNote(json, state: S) {
|
||||
let host = Object.keys(json)[0];
|
||||
let book = Object.keys(json[host])[0];
|
||||
let noteId = json[host][book]["note-id"];
|
||||
@ -77,13 +89,13 @@ export default class PublishUpdateReducer {
|
||||
let prevNoteId = state.notebooks[host][book]["notes-by-date"][1] || null;
|
||||
state.notebooks[host][book].notes[noteId]["prev-note"] = prevNoteId
|
||||
state.notebooks[host][book].notes[noteId]["next-note"] = null;
|
||||
if (state.notebooks[host][book].notes[prevNoteId]) {
|
||||
if (prevNoteId && state.notebooks[host][book].notes[prevNoteId]) {
|
||||
state.notebooks[host][book].notes[prevNoteId]["next-note"] = noteId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addComment(json, state) {
|
||||
addComment(json, state: S) {
|
||||
let host = json.host
|
||||
let book = json.book
|
||||
let note = json.note
|
||||
@ -97,7 +109,7 @@ export default class PublishUpdateReducer {
|
||||
if (state.notebooks[host][book].notes[note].comments) {
|
||||
let limboCommentIdx =
|
||||
_.findIndex(state.notebooks[host][book].notes[note].comments, (o) => {
|
||||
let oldVal = o[Object.keys(o)[0]];
|
||||
let oldVal = o[getTagFromFrond(o)];
|
||||
let newVal = comment[Object.keys(comment)[0]];
|
||||
return (oldVal.pending &&
|
||||
(oldVal.author === newVal.author) &&
|
||||
|
@ -1,7 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Cage } from '../types/cage';
|
||||
import { S3Update } from '../types/s3-update';
|
||||
|
||||
export default class S3Reducer{
|
||||
reduce(json, state) {
|
||||
type S3State = Pick<StoreState, 's3'>;
|
||||
|
||||
export default class S3Reducer<S extends S3State> {
|
||||
reduce(json: Cage, state: S) {
|
||||
const data = _.get(json, 's3-update', false);
|
||||
if (data) {
|
||||
this.credentials(data, state);
|
||||
@ -15,14 +20,14 @@ export default class S3Reducer{
|
||||
}
|
||||
}
|
||||
|
||||
credentials(json, state) {
|
||||
credentials(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'credentials', false);
|
||||
if (data) {
|
||||
state.s3.credentials = data;
|
||||
}
|
||||
}
|
||||
|
||||
configuration(json, state) {
|
||||
configuration(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'configuration', false);
|
||||
if (data) {
|
||||
state.s3.configuration = {
|
||||
@ -32,14 +37,14 @@ export default class S3Reducer{
|
||||
}
|
||||
}
|
||||
|
||||
currentBucket(json, state) {
|
||||
currentBucket(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'setCurrentBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.currentBucket = data;
|
||||
if (data && state.s3) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
addBucket(json, state) {
|
||||
addBucket(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'addBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
@ -47,31 +52,30 @@ export default class S3Reducer{
|
||||
}
|
||||
}
|
||||
|
||||
removeBucket(json, state) {
|
||||
removeBucket(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'removeBucket', false);
|
||||
if (data) {
|
||||
state.s3.configuration.buckets =
|
||||
state.s3.configuration.buckets.delete(data);
|
||||
state.s3.configuration.buckets.delete(data);
|
||||
}
|
||||
}
|
||||
|
||||
endpoint(json, state) {
|
||||
endpoint(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'setEndpoint', false);
|
||||
if (data) {
|
||||
if (data && state.s3.credentials) {
|
||||
state.s3.credentials.endpoint = data;
|
||||
}
|
||||
}
|
||||
|
||||
accessKeyId(json, state) {
|
||||
accessKeyId(json: S3Update , state: S) {
|
||||
const data = _.get(json, 'setAccessKeyId', false);
|
||||
if (data) {
|
||||
if (data && state.s3.credentials) {
|
||||
state.s3.credentials.accessKeyId = data;
|
||||
}
|
||||
}
|
||||
|
||||
secretAccessKey(json, state) {
|
||||
secretAccessKey(json: S3Update, state: S) {
|
||||
const data = _.get(json, 'setSecretAccessKey', false);
|
||||
if (data) {
|
||||
if (data && state.s3.credentials) {
|
||||
state.s3.credentials.secretAccessKey = data;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
export default class BaseStore {
|
||||
export default class BaseStore<S extends object> {
|
||||
state: S;
|
||||
setState: (s: Partial<S>) => void = (s) => {};
|
||||
constructor() {
|
||||
this.state = this.initialState();
|
||||
this.setState = () => {};
|
||||
}
|
||||
|
||||
initialState() {
|
||||
return {};
|
||||
return {} as S;
|
||||
}
|
||||
|
||||
setStateHandler(setState) {
|
||||
setStateHandler(setState: (s: Partial<S>) => void) {
|
||||
this.setState = setState;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.handleEvent({
|
||||
data: { clear: true }
|
||||
data: { clear: true },
|
||||
});
|
||||
}
|
||||
|
||||
@ -25,7 +26,7 @@ export default class BaseStore {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('clear' in json && json.clear) {
|
||||
if ("clear" in json && json.clear) {
|
||||
this.setState(this.initialState());
|
||||
return;
|
||||
}
|
||||
@ -38,4 +39,3 @@ export default class BaseStore {
|
||||
// extend me!
|
||||
}
|
||||
}
|
||||
|
92
pkg/interface/src/store/store.ts
Normal file
92
pkg/interface/src/store/store.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import BaseStore from './base';
|
||||
import InviteReducer from '../reducers/invite-update';
|
||||
import MetadataReducer from '../reducers/metadata-update';
|
||||
import LocalReducer from '../reducers/local';
|
||||
import ChatReducer from '../reducers/chat-update';
|
||||
|
||||
import { StoreState } from './type';
|
||||
import { Cage } from '../types/cage';
|
||||
import ContactReducer from '../reducers/contact-update';
|
||||
import LinkUpdateReducer from '../reducers/link-update';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
import GroupReducer from '../reducers/group-update';
|
||||
import PermissionReducer from '../reducers/permission-update';
|
||||
import PublishUpdateReducer from '../reducers/publish-update';
|
||||
import PublishResponseReducer from '../reducers/publish-response';
|
||||
import LaunchReducer from '../reducers/launch-update';
|
||||
import LinkListenReducer from '../reducers/listen-update';
|
||||
|
||||
|
||||
export default class GlobalStore extends BaseStore<StoreState> {
|
||||
inviteReducer = new InviteReducer();
|
||||
metadataReducer = new MetadataReducer();
|
||||
localReducer = new LocalReducer();
|
||||
chatReducer = new ChatReducer();
|
||||
contactReducer = new ContactReducer();
|
||||
linkReducer = new LinkUpdateReducer();
|
||||
linkListenReducer = new LinkListenReducer();
|
||||
s3Reducer = new S3Reducer();
|
||||
groupReducer = new GroupReducer();
|
||||
permissionReducer = new PermissionReducer();
|
||||
publishUpdateReducer = new PublishUpdateReducer();
|
||||
publishResponseReducer = new PublishResponseReducer();
|
||||
launchReducer = new LaunchReducer();
|
||||
|
||||
|
||||
initialState(): StoreState {
|
||||
return {
|
||||
pendingMessages: new Map(),
|
||||
chatInitialized: false,
|
||||
sidebarShown: true,
|
||||
invites: {},
|
||||
associations: {
|
||||
chat: {},
|
||||
contacts: {},
|
||||
link: {},
|
||||
publish: {}
|
||||
},
|
||||
groups: {},
|
||||
groupKeys: new Set(),
|
||||
launch: {
|
||||
firstTime: false,
|
||||
tileOrdering: [],
|
||||
tiles: {},
|
||||
},
|
||||
weather: {},
|
||||
location: '',
|
||||
permissions: {},
|
||||
s3: {
|
||||
configuration: {
|
||||
buckets: new Set(),
|
||||
currentBucket: ''
|
||||
},
|
||||
credentials: null
|
||||
},
|
||||
links: {},
|
||||
linksSeen: {},
|
||||
linkListening: new Set(),
|
||||
linkComments: {},
|
||||
notebooks: {},
|
||||
contacts: {},
|
||||
selectedGroups: [],
|
||||
inbox: {},
|
||||
chatSynced: null,
|
||||
};
|
||||
}
|
||||
|
||||
reduce(data: Cage, state: StoreState) {
|
||||
this.inviteReducer.reduce(data, this.state);
|
||||
this.metadataReducer.reduce(data, this.state);
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.chatReducer.reduce(data, this.state);
|
||||
this.contactReducer.reduce(data, this.state);
|
||||
this.linkReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
this.groupReducer.reduce(data, this.state);
|
||||
this.permissionReducer.reduce(data, this.state);
|
||||
this.publishUpdateReducer.reduce(data, this.state);
|
||||
this.publishResponseReducer.reduce(data, this.state);
|
||||
this.launchReducer.reduce(data, this.state);
|
||||
this.linkListenReducer.reduce(data, this.state);
|
||||
}
|
||||
}
|
53
pkg/interface/src/store/type.ts
Normal file
53
pkg/interface/src/store/type.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { Inbox, Envelope } from '../types/chat-update';
|
||||
import { ChatHookUpdate } from '../types/chat-hook-update';
|
||||
import { Path } from '../types/noun';
|
||||
import { Invites } from '../types/invite-update';
|
||||
import { SelectedGroup } from '../types/local-update';
|
||||
import { Associations } from '../types/metadata-update';
|
||||
import { Rolodex } from '../types/contact-update';
|
||||
import { Notebooks } from '../types/publish-update';
|
||||
import { Groups } from '../types/group-update';
|
||||
import { S3State } from '../types/s3-update';
|
||||
import { Permissions } from '../types/permission-update';
|
||||
import { LaunchState } from '../types/launch-update';
|
||||
import { LinkComments, LinkCollections, LinkSeen } from '../types/link-update';
|
||||
|
||||
export interface StoreState {
|
||||
// local state
|
||||
sidebarShown: boolean;
|
||||
selectedGroups: SelectedGroup[];
|
||||
// invite state
|
||||
invites: Invites;
|
||||
// metadata state
|
||||
associations: Associations;
|
||||
// contact state
|
||||
contacts: Rolodex;
|
||||
// groups state
|
||||
groups: Groups;
|
||||
groupKeys: Set<Path>;
|
||||
permissions: Permissions;
|
||||
s3: S3State;
|
||||
|
||||
|
||||
// App specific states
|
||||
//
|
||||
// launch state
|
||||
launch: LaunchState;
|
||||
weather: any;
|
||||
location: any;
|
||||
|
||||
// links state
|
||||
linksSeen: LinkSeen;
|
||||
linkListening: Set<Path>;
|
||||
links: LinkCollections;
|
||||
linkComments: LinkComments;
|
||||
|
||||
// publish state
|
||||
notebooks: Notebooks;
|
||||
|
||||
// Chat state
|
||||
chatInitialized: boolean;
|
||||
chatSynced: ChatHookUpdate | null;
|
||||
inbox: Inbox;
|
||||
pendingMessages: Map<Path, Envelope[]>;
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
export default class BaseSubscription {
|
||||
constructor(store, api, channel) {
|
||||
this.store = store;
|
||||
this.api = api;
|
||||
this.channel = channel;
|
||||
import BaseStore from "../store/base";
|
||||
import BaseApi from "../api/base";
|
||||
import { Path } from "../types/noun";
|
||||
|
||||
export default class BaseSubscription<S extends object> {
|
||||
constructor(public store: BaseStore<S>, public api: BaseApi<S>, public channel: any) {
|
||||
this.channel.setOnChannelError(this.onChannelError.bind(this));
|
||||
}
|
||||
|
||||
@ -12,14 +13,14 @@ export default class BaseSubscription {
|
||||
|
||||
onChannelError(err) {
|
||||
console.error('event source error: ', err);
|
||||
setTimeout(2000, () => {
|
||||
setTimeout(() => {
|
||||
this.store.clear();
|
||||
this.start();
|
||||
});
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
subscribe(path, app) {
|
||||
this.api.subscribe(path, 'PUT', this.api.ship, app,
|
||||
subscribe(path: Path, app: string) {
|
||||
return this.api.subscribe(path, 'PUT', this.api.ship, app,
|
||||
this.handleEvent.bind(this),
|
||||
(err) => {
|
||||
console.log(err);
|
||||
@ -30,6 +31,10 @@ export default class BaseSubscription {
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribe(id: number) {
|
||||
this.api.unsubscribe(id);
|
||||
}
|
||||
|
||||
start() {
|
||||
// extend
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import BaseSubscription from './base';
|
||||
|
||||
export default class GlobalSubscription extends BaseSubscription {
|
||||
start() {
|
||||
this.subscribe('/all', 'invite-store');
|
||||
this.subscribe('/app-name/contacts', 'metadata-store');
|
||||
}
|
||||
}
|
||||
|
69
pkg/interface/src/subscription/global.ts
Normal file
69
pkg/interface/src/subscription/global.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import BaseSubscription from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '../types/noun';
|
||||
|
||||
|
||||
/**
|
||||
* Path to subscribe on and app to subscribe to
|
||||
*/
|
||||
type AppSubscription = [Path, string];
|
||||
|
||||
const chatSubscriptions: AppSubscription[] = [
|
||||
['/primary', 'chat-view'],
|
||||
['/synced', 'chat-hook']
|
||||
];
|
||||
|
||||
const publishSubscriptions: AppSubscription[] = [
|
||||
['/primary', 'publish']
|
||||
];
|
||||
|
||||
const linkSubscriptions: AppSubscription[] = [
|
||||
['/json/seen', 'link-view'],
|
||||
['/listening', 'link-listen-hook']
|
||||
]
|
||||
|
||||
const groupSubscriptions: AppSubscription[] = [
|
||||
['/all', 'group-store'],
|
||||
['/synced', 'contact-hook']
|
||||
];
|
||||
|
||||
type AppName = 'publish' | 'chat' | 'link' | 'groups';
|
||||
const appSubscriptions: Record<AppName, AppSubscription[]> = {
|
||||
chat: chatSubscriptions,
|
||||
publish: publishSubscriptions,
|
||||
link: linkSubscriptions,
|
||||
groups: groupSubscriptions
|
||||
};
|
||||
|
||||
export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
||||
openSubscriptions: Record<AppName, number[]> = {
|
||||
chat: [],
|
||||
publish: [],
|
||||
link: [],
|
||||
groups: []
|
||||
};
|
||||
start() {
|
||||
this.subscribe('/all', 'invite-store');
|
||||
this.subscribe('/app-name/contacts', 'metadata-store');
|
||||
this.subscribe('/all', 'invite-store');
|
||||
this.subscribe('/all', 'permission-store');
|
||||
this.subscribe('/primary', 'contact-view');
|
||||
this.subscribe('/all', 'metadata-store');
|
||||
this.subscribe('/all', 's3-store');
|
||||
this.subscribe('/all', 'launch');
|
||||
this.subscribe('/all', 'weather');
|
||||
}
|
||||
|
||||
startApp(app: AppName) {
|
||||
if(this.openSubscriptions[app].length > 0) {
|
||||
console.log(`${app} already started`);
|
||||
return;
|
||||
}
|
||||
this.openSubscriptions[app] = appSubscriptions[app].map(([path, agent]) => this.subscribe(path, agent));
|
||||
}
|
||||
|
||||
stopApp(app: AppName) {
|
||||
this.openSubscriptions[app].map(id => this.unsubscribe(id))
|
||||
this.openSubscriptions[app] = [];
|
||||
}
|
||||
}
|
35
pkg/interface/src/types/cage.ts
Normal file
35
pkg/interface/src/types/cage.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ChatUpdate } from "./chat-update";
|
||||
import { ChatHookUpdate } from "./chat-hook-update";
|
||||
import { ContactUpdate } from "./contact-update";
|
||||
import { InviteUpdate } from "./invite-update";
|
||||
import { LocalUpdate } from "./local-update";
|
||||
import { MetadataUpdate } from "./metadata-update";
|
||||
import { PublishUpdate } from './publish-update';
|
||||
import { PublishResponse } from "./publish-response";
|
||||
import { GroupUpdate } from "./group-update";
|
||||
import { PermissionUpdate } from "./permission-update";
|
||||
import { LaunchUpdate } from "./launch-update";
|
||||
import { LinkListenUpdate } from './link-listen-update';
|
||||
|
||||
interface MarksToTypes {
|
||||
readonly json: any;
|
||||
readonly "chat-update": ChatUpdate;
|
||||
readonly "chat-hook-update": ChatHookUpdate;
|
||||
readonly "contact-update": ContactUpdate;
|
||||
readonly "invite-update": InviteUpdate;
|
||||
readonly "metadata-update": MetadataUpdate;
|
||||
readonly 'publish-update': PublishUpdate;
|
||||
readonly "publish-response": PublishResponse;
|
||||
readonly "group-update": GroupUpdate;
|
||||
readonly "permission-update": PermissionUpdate;
|
||||
readonly "launch-update": LaunchUpdate;
|
||||
readonly "link-listen-update": LinkListenUpdate;
|
||||
// not really marks but w/e
|
||||
readonly 'local': LocalUpdate;
|
||||
readonly 'weather': any;
|
||||
readonly 'location': any;
|
||||
}
|
||||
|
||||
export type Cage = Partial<MarksToTypes>;
|
||||
|
||||
export type Mark = keyof MarksToTypes;
|
5
pkg/interface/src/types/chat-hook-update.ts
Normal file
5
pkg/interface/src/types/chat-hook-update.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Patp } from './noun';
|
||||
|
||||
export interface ChatHookUpdate {
|
||||
[p: string]: Patp;
|
||||
}
|
95
pkg/interface/src/types/chat-update.ts
Normal file
95
pkg/interface/src/types/chat-update.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { Path, Patp } from './noun';
|
||||
|
||||
export type ChatUpdate =
|
||||
ChatUpdateInitial
|
||||
| ChatUpdateCreate
|
||||
| ChatUpdateDelete
|
||||
| ChatUpdateMessage
|
||||
| ChatUpdateMessages
|
||||
| ChatUpdateRead;
|
||||
|
||||
export type ChatAction =
|
||||
ChatUpdateCreate
|
||||
| ChatUpdateDelete
|
||||
| ChatUpdateMessage
|
||||
| ChatUpdateRead;
|
||||
|
||||
interface ChatUpdateInitial {
|
||||
initial: Inbox;
|
||||
}
|
||||
|
||||
interface ChatUpdateCreate {
|
||||
create: Path;
|
||||
}
|
||||
|
||||
interface ChatUpdateDelete {
|
||||
delete: Path;
|
||||
}
|
||||
|
||||
interface ChatUpdateMessage {
|
||||
message: {
|
||||
path: Path;
|
||||
envelope: Envelope;
|
||||
}
|
||||
}
|
||||
|
||||
interface ChatUpdateMessages {
|
||||
messages: {
|
||||
path: Path;
|
||||
envelopes: Envelope[];
|
||||
}
|
||||
}
|
||||
|
||||
interface ChatUpdateRead {
|
||||
read: {
|
||||
path: Path;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Data structures
|
||||
// TODO: move to seperate file?
|
||||
|
||||
export interface Inbox {
|
||||
[chatName: string]: Mailbox;
|
||||
}
|
||||
|
||||
interface Mailbox {
|
||||
config: MailboxConfig;
|
||||
envelopes: Envelope[];
|
||||
}
|
||||
|
||||
interface MailboxConfig {
|
||||
length: number;
|
||||
read: number;
|
||||
}
|
||||
|
||||
export interface Envelope {
|
||||
uid: string;
|
||||
number: number;
|
||||
author: Patp;
|
||||
when: string;
|
||||
letter: Letter;
|
||||
}
|
||||
|
||||
interface LetterText {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface LetterUrl {
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface LetterCode {
|
||||
code: {
|
||||
expression: string;
|
||||
output: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface LetterMe {
|
||||
narrative: string;
|
||||
}
|
||||
|
||||
export type Letter = LetterText | LetterUrl | LetterCode | LetterMe;
|
85
pkg/interface/src/types/contact-update.ts
Normal file
85
pkg/interface/src/types/contact-update.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { Path, Patp } from "./noun";
|
||||
|
||||
export type ContactUpdate =
|
||||
| ContactUpdateCreate
|
||||
| ContactUpdateDelete
|
||||
| ContactUpdateAdd
|
||||
| ContactUpdateRemove
|
||||
| ContactUpdateEdit
|
||||
| ContactUpdateInitial
|
||||
| ContactUpdateContacts;
|
||||
|
||||
interface ContactUpdateCreate {
|
||||
create: Path;
|
||||
}
|
||||
|
||||
interface ContactUpdateDelete {
|
||||
delete: Path;
|
||||
}
|
||||
|
||||
interface ContactUpdateAdd {
|
||||
add: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
contact: Contact;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateRemove {
|
||||
remove: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateEdit {
|
||||
edit: {
|
||||
path: Path;
|
||||
ship: Patp;
|
||||
"edit-field": ContactEdit;
|
||||
};
|
||||
}
|
||||
|
||||
interface ContactUpdateInitial {
|
||||
initial: Rolodex;
|
||||
}
|
||||
|
||||
interface ContactUpdateContacts {
|
||||
contacts: {
|
||||
path: Path;
|
||||
contacts: Contacts;
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type ContactAvatar = ContactAvatarUrl | ContactAvatarOcts;
|
||||
|
||||
export type Rolodex = {
|
||||
[p in Path]: Contacts;
|
||||
};
|
||||
|
||||
export type Contacts = {
|
||||
[p in Patp]: Contact;
|
||||
};
|
||||
|
||||
interface ContactAvatarUrl {
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface ContactAvatarOcts {
|
||||
octs: string;
|
||||
}
|
||||
export interface Contact {
|
||||
nickname: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
website: string;
|
||||
notes: string;
|
||||
color: string;
|
||||
avatar: ContactAvatar | null;
|
||||
}
|
||||
|
||||
export type ContactEdit = {
|
||||
[k in keyof Contact]: Contact[k];
|
||||
};
|
7
pkg/interface/src/types/global.ts
Normal file
7
pkg/interface/src/types/global.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { PatpNoSig } from "./noun";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ship: PatpNoSig;
|
||||
}
|
||||
}
|
59
pkg/interface/src/types/group-update.ts
Normal file
59
pkg/interface/src/types/group-update.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { PatpNoSig, Path } from './noun';
|
||||
|
||||
export type Group = Set<PatpNoSig>
|
||||
|
||||
export type Groups = {
|
||||
[p in Path]: Group;
|
||||
}
|
||||
|
||||
interface GroupUpdateInitial {
|
||||
initial: Groups;
|
||||
}
|
||||
|
||||
interface GroupUpdateAdd {
|
||||
add: {
|
||||
members: PatpNoSig[];
|
||||
path: Path;
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupUpdateRemove {
|
||||
remove: {
|
||||
members: PatpNoSig[];
|
||||
path: Path;
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupUpdateBundle {
|
||||
bundle: {
|
||||
path: Path;
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupUpdateUnbundle {
|
||||
unbundle: {
|
||||
path: Path;
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupUpdateKeys {
|
||||
keys: {
|
||||
keys: Path[];
|
||||
}
|
||||
}
|
||||
|
||||
interface GroupUpdatePath {
|
||||
path: {
|
||||
path: Path;
|
||||
members: PatpNoSig[];
|
||||
}
|
||||
}
|
||||
|
||||
export type GroupUpdate =
|
||||
GroupUpdateInitial
|
||||
| GroupUpdateAdd
|
||||
| GroupUpdateRemove
|
||||
| GroupUpdateBundle
|
||||
| GroupUpdateUnbundle
|
||||
| GroupUpdateKeys
|
||||
| GroupUpdatePath;
|
67
pkg/interface/src/types/invite-update.ts
Normal file
67
pkg/interface/src/types/invite-update.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { Serial, PatpNoSig, Path } from './noun';
|
||||
|
||||
export type InviteUpdate =
|
||||
InviteUpdateInitial
|
||||
| InviteUpdateCreate
|
||||
| InviteUpdateDelete
|
||||
| InviteUpdateInvite
|
||||
| InviteUpdateAccepted
|
||||
| InviteUpdateDecline;
|
||||
|
||||
|
||||
interface InviteUpdateInitial {
|
||||
initial: Invites;
|
||||
}
|
||||
|
||||
interface InviteUpdateCreate {
|
||||
create: {
|
||||
path: Path;
|
||||
};
|
||||
}
|
||||
|
||||
interface InviteUpdateDelete {
|
||||
delete: {
|
||||
path: Path;
|
||||
};
|
||||
}
|
||||
|
||||
interface InviteUpdateInvite {
|
||||
invite: {
|
||||
path: Path;
|
||||
uid: Serial;
|
||||
invite: Invite;
|
||||
};
|
||||
}
|
||||
|
||||
interface InviteUpdateAccepted {
|
||||
accepted: {
|
||||
path: Path;
|
||||
uid: Serial;
|
||||
};
|
||||
}
|
||||
|
||||
interface InviteUpdateDecline {
|
||||
decline: {
|
||||
path: Path;
|
||||
uid: Serial;
|
||||
};
|
||||
}
|
||||
|
||||
// actual datastructures
|
||||
|
||||
|
||||
export type Invites = {
|
||||
[p in Path]: AppInvites;
|
||||
};
|
||||
|
||||
export type AppInvites = {
|
||||
[s in Serial]: Invite;
|
||||
};
|
||||
|
||||
export interface Invite {
|
||||
app: string;
|
||||
path: Path;
|
||||
recipeint: PatpNoSig;
|
||||
ship: PatpNoSig;
|
||||
text: string;
|
||||
}
|
53
pkg/interface/src/types/launch-update.ts
Normal file
53
pkg/interface/src/types/launch-update.ts
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
export type LaunchUpdate =
|
||||
LaunchUpdateInitial
|
||||
| LaunchUpdateFirstTime
|
||||
| LaunchUpdateOrder
|
||||
| LaunchUpdateIsShown;
|
||||
|
||||
|
||||
interface LaunchUpdateInitial {
|
||||
initial: LaunchState;
|
||||
}
|
||||
|
||||
interface LaunchUpdateFirstTime {
|
||||
changeFirstTime: boolean;
|
||||
}
|
||||
|
||||
interface LaunchUpdateOrder {
|
||||
changeOrder: string[];
|
||||
}
|
||||
|
||||
interface LaunchUpdateIsShown {
|
||||
changeIsShown: {
|
||||
name: string;
|
||||
isShown: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchState {
|
||||
firstTime: boolean;
|
||||
tileOrdering: string[];
|
||||
tiles: {
|
||||
[app: string]: Tile;
|
||||
}
|
||||
}
|
||||
|
||||
interface Tile {
|
||||
isShown: boolean;
|
||||
type: TileType;
|
||||
}
|
||||
|
||||
type TileType = TileTypeBasic | TileTypeCustom;
|
||||
|
||||
interface TileTypeBasic {
|
||||
basic: {
|
||||
iconUrl: string;
|
||||
linkedUrl: string;
|
||||
title: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface TileTypeCustom {
|
||||
custom: null;
|
||||
}
|
18
pkg/interface/src/types/link-listen-update.ts
Normal file
18
pkg/interface/src/types/link-listen-update.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Path } from './noun';
|
||||
|
||||
interface LinkListenUpdateListening {
|
||||
listening: Path[];
|
||||
}
|
||||
|
||||
interface LinkListenUpdateWatch {
|
||||
watch: Path;
|
||||
}
|
||||
|
||||
interface LinkListenUpdateLeave {
|
||||
leave: Path;
|
||||
}
|
||||
|
||||
export type LinkListenUpdate =
|
||||
LinkListenUpdateListening
|
||||
| LinkListenUpdateWatch
|
||||
| LinkListenUpdateLeave;
|
84
pkg/interface/src/types/link-update.ts
Normal file
84
pkg/interface/src/types/link-update.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { PatpNoSig, Path } from "./noun";
|
||||
|
||||
export type LinkCollections = {
|
||||
[p in Path]: Collection;
|
||||
};
|
||||
|
||||
export type LinkSeen = {
|
||||
[p in Path]: {
|
||||
[url: string]: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type Pagination<S> = {
|
||||
local: LocalPages;
|
||||
[p: number]: S[];
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export type LinkComments = {
|
||||
[p in Path]: {
|
||||
[url: string]: Pagination<LinkComment> & {
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface LinkComment {
|
||||
ship: PatpNoSig;
|
||||
time: number;
|
||||
udon: string;
|
||||
}
|
||||
|
||||
interface CollectionStats {
|
||||
unseenCount: number;
|
||||
}
|
||||
|
||||
type LocalPages = {
|
||||
[p: number]: boolean;
|
||||
}
|
||||
|
||||
type Collection = CollectionStats & Pagination<Link>;
|
||||
|
||||
interface Link {
|
||||
commentCount: number;
|
||||
seen: boolean;
|
||||
ship: PatpNoSig;
|
||||
time: number;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface LinkInitialSubmissions {
|
||||
'initial-submissions': {
|
||||
[p in Path]: CollectionStats & {
|
||||
pageNumber?: number;
|
||||
pages?: Link[];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
interface LinkUpdateSubmission {
|
||||
'submissions': {
|
||||
path: Path;
|
||||
pages: Link[];
|
||||
}
|
||||
}
|
||||
|
||||
interface LinkInitialDiscussion {
|
||||
'intitial-discussion': {
|
||||
path: Path;
|
||||
url: string;
|
||||
page: Comment[];
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
pageNumber: number;
|
||||
}
|
||||
}
|
||||
|
||||
export type LinkUpdate =
|
||||
LinkInitialSubmissions
|
||||
| LinkUpdateSubmission
|
||||
| LinkInitialDiscussion;
|
15
pkg/interface/src/types/local-update.ts
Normal file
15
pkg/interface/src/types/local-update.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Path } from './noun';
|
||||
|
||||
export type LocalUpdate =
|
||||
LocalUpdateSidebarToggle
|
||||
| LocalUpdateSelectedGroups;
|
||||
|
||||
interface LocalUpdateSidebarToggle {
|
||||
sidebarToggle: boolean;
|
||||
}
|
||||
|
||||
interface LocalUpdateSelectedGroups {
|
||||
selected: SelectedGroup[];
|
||||
}
|
||||
|
||||
export type SelectedGroup = [Path, string];
|
54
pkg/interface/src/types/metadata-update.ts
Normal file
54
pkg/interface/src/types/metadata-update.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { AppName, Path, Patp } from './noun';
|
||||
|
||||
|
||||
export type MetadataUpdate =
|
||||
MetadataUpdateInitial
|
||||
| MetadataUpdateAdd
|
||||
| MetadataUpdateUpdate
|
||||
| MetadataUpdateRemove;
|
||||
|
||||
interface MetadataUpdateInitial {
|
||||
associations: ResourceAssociations;
|
||||
}
|
||||
|
||||
type ResourceAssociations = {
|
||||
[p in Path]: Association;
|
||||
}
|
||||
|
||||
type MetadataUpdateAdd = {
|
||||
add: Association;
|
||||
}
|
||||
|
||||
type MetadataUpdateUpdate = {
|
||||
update: Association;
|
||||
}
|
||||
|
||||
type MetadataUpdateRemove = {
|
||||
remove: Resource & {
|
||||
'group-path': Path;
|
||||
}
|
||||
}
|
||||
|
||||
export type Associations = Record<AppName, AppAssociations>;
|
||||
|
||||
type AppAssociations = {
|
||||
[p in Path]: Association;
|
||||
}
|
||||
|
||||
interface Resource {
|
||||
'app-path': Path;
|
||||
'app-name': AppName;
|
||||
}
|
||||
|
||||
export type Association = Resource & {
|
||||
'group-path': Path;
|
||||
metadata: Metadata;
|
||||
};
|
||||
|
||||
interface Metadata {
|
||||
color: string;
|
||||
creator: Patp;
|
||||
'date-created': string;
|
||||
description: string;
|
||||
title: string;
|
||||
}
|
25
pkg/interface/src/types/noun.ts
Normal file
25
pkg/interface/src/types/noun.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// an urbit style path render as string
|
||||
export type Path = string;
|
||||
|
||||
// patp including leading sig
|
||||
export type Patp = string;
|
||||
|
||||
// patp excluding leading sig
|
||||
export type PatpNoSig = string;
|
||||
|
||||
// @uvH encoded string
|
||||
export type Serial = string;
|
||||
|
||||
// name of app
|
||||
export type AppName = 'chat' | 'link' | 'contacts' | 'publish';
|
||||
|
||||
export function getTagFromFrond<O>(frond: O): keyof O {
|
||||
const tags = Object.keys(frond) as Array<keyof O>;
|
||||
const tag = tags[0];
|
||||
if(!tag) {
|
||||
throw new Error("bad frond");
|
||||
}
|
||||
return tag;
|
||||
|
||||
|
||||
}
|
55
pkg/interface/src/types/permission-update.ts
Normal file
55
pkg/interface/src/types/permission-update.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Path, PatpNoSig } from './noun';
|
||||
|
||||
export type PermissionUpdate =
|
||||
PermissionUpdateInitial
|
||||
| PermissionUpdateCreate
|
||||
| PermissionUpdateDelete
|
||||
| PermissionUpdateRemove
|
||||
| PermissionUpdateAdd;
|
||||
|
||||
interface PermissionUpdateInitial {
|
||||
initial: {
|
||||
[p in Path]: {
|
||||
who: PatpNoSig[];
|
||||
kind: PermissionKind;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface PermissionUpdateCreate {
|
||||
create: {
|
||||
path: Path;
|
||||
kind: PermissionKind;
|
||||
who: PatpNoSig[];
|
||||
}
|
||||
}
|
||||
|
||||
interface PermissionUpdateDelete {
|
||||
delete: {
|
||||
path: Path;
|
||||
}
|
||||
}
|
||||
|
||||
interface PermissionUpdateAdd {
|
||||
add: {
|
||||
path: Path;
|
||||
who: PatpNoSig[];
|
||||
}
|
||||
}
|
||||
|
||||
interface PermissionUpdateRemove {
|
||||
remove: {
|
||||
path: Path;
|
||||
who: PatpNoSig[];
|
||||
}
|
||||
}
|
||||
|
||||
export type Permissions = {
|
||||
[p in Path]: Permission;
|
||||
};
|
||||
export interface Permission {
|
||||
who: Set<PatpNoSig>;
|
||||
kind: PermissionKind;
|
||||
}
|
||||
|
||||
export type PermissionKind = 'white' | 'black';
|
48
pkg/interface/src/types/publish-response.ts
Normal file
48
pkg/interface/src/types/publish-response.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Notebooks, Notebook, Note, BookId, NoteId } from './publish-update';
|
||||
import { Patp } from './noun';
|
||||
|
||||
export type PublishResponse =
|
||||
NotebooksResponse
|
||||
| NotebookResponse
|
||||
| NoteResponse
|
||||
| NotesPageResponse
|
||||
| CommentsPageResponse;
|
||||
|
||||
interface NotebooksResponse {
|
||||
type: 'notebooks';
|
||||
data: Notebooks;
|
||||
}
|
||||
|
||||
interface NotebookResponse {
|
||||
type: 'notebook';
|
||||
data: Notebook;
|
||||
host: Patp;
|
||||
notebook: BookId;
|
||||
}
|
||||
|
||||
interface NoteResponse {
|
||||
type: 'note';
|
||||
data: Note;
|
||||
host: Patp;
|
||||
notebook: BookId;
|
||||
note: NoteId;
|
||||
}
|
||||
|
||||
interface NotesPageResponse {
|
||||
type: 'notes-page';
|
||||
data: Note[];
|
||||
host: Patp;
|
||||
notebook: BookId;
|
||||
startIndex: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
interface CommentsPageResponse {
|
||||
type: 'comments-page';
|
||||
data: Comment[];
|
||||
host: Patp;
|
||||
notebook: BookId;
|
||||
note: NoteId;
|
||||
startIndex: number;
|
||||
length: number;
|
||||
}
|
158
pkg/interface/src/types/publish-update.ts
Normal file
158
pkg/interface/src/types/publish-update.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import { Patp, PatpNoSig, Path } from './noun';
|
||||
|
||||
|
||||
export type NoteId = string;
|
||||
export type BookId = string;
|
||||
|
||||
|
||||
export type PublishUpdate =
|
||||
PublishUpdateAddBook
|
||||
| PublishUpdateAddNote
|
||||
| PublishUpdateAddComment
|
||||
| PublishUpdateEditBook
|
||||
| PublishUpdateEditNote
|
||||
| PublishUpdateEditComment
|
||||
| PublishUpdateDelBook
|
||||
| PublishUpdateDelNote
|
||||
| PublishUpdateDelComment;
|
||||
|
||||
|
||||
type PublishUpdateBook = {
|
||||
[s in Patp]: {
|
||||
[b in BookId]: {
|
||||
title: string;
|
||||
'date-created': number;
|
||||
about: string;
|
||||
'num-notes': number;
|
||||
'num-unread': number;
|
||||
comments: boolean;
|
||||
'writers-group-path': Path;
|
||||
'subscribers-group-path': Path;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
type PublishUpdateNote = {
|
||||
[s in Patp]: {
|
||||
[b in BookId]: {
|
||||
'note-id': NoteId;
|
||||
author: Patp;
|
||||
title: string;
|
||||
'date-created': string;
|
||||
snippet: string;
|
||||
file: string;
|
||||
'num-comments': number;
|
||||
comments: Comment[];
|
||||
read: boolean;
|
||||
pending: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
interface PublishUpdateAddBook {
|
||||
'add-book': PublishUpdateBook;
|
||||
}
|
||||
|
||||
interface PublishUpdateEditBook {
|
||||
'edit-book': PublishUpdateBook;
|
||||
}
|
||||
|
||||
interface PublishUpdateDelBook {
|
||||
'del-book': {
|
||||
host: Patp;
|
||||
book: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface PublishUpdateAddNote {
|
||||
'add-note': PublishUpdateNote;
|
||||
}
|
||||
|
||||
interface PublishUpdateEditNote {
|
||||
'edit-note': PublishUpdateNote;
|
||||
}
|
||||
|
||||
interface PublishUpdateDelNote {
|
||||
'del-note': {
|
||||
host: Patp;
|
||||
book: BookId;
|
||||
note: NoteId;
|
||||
}
|
||||
}
|
||||
|
||||
interface PublishUpdateAddComment {
|
||||
'add-comment': {
|
||||
who: Patp;
|
||||
host: BookId;
|
||||
note: NoteId;
|
||||
body: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface PublishUpdateEditComment {
|
||||
'edit-comment': {
|
||||
host: Patp;
|
||||
book: BookId;
|
||||
note: NoteId;
|
||||
body: string;
|
||||
comment: Comment;
|
||||
}
|
||||
}
|
||||
|
||||
interface PublishUpdateDelComment {
|
||||
'del-comment': {
|
||||
host: Patp;
|
||||
book: BookId;
|
||||
note: NoteId;
|
||||
comment: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type Notebooks = {
|
||||
[host in Patp]: {
|
||||
[book in BookId]: Notebook;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface Notebook {
|
||||
about: string;
|
||||
comments: boolean;
|
||||
'date-created': number;
|
||||
notes: Notes;
|
||||
'notes-by-date': NoteId[];
|
||||
'num-notes': number;
|
||||
'num-unread': number;
|
||||
subscribers: PatpNoSig[];
|
||||
'subscribers-group-path': Path;
|
||||
title: string;
|
||||
'writers-group-path': Path;
|
||||
}
|
||||
|
||||
type Notes = {
|
||||
[id in NoteId]: Note;
|
||||
};
|
||||
|
||||
export interface Note {
|
||||
author: Patp;
|
||||
comments: Comment[];
|
||||
'date-created': number;
|
||||
file: string;
|
||||
'next-note': NoteId | null;
|
||||
'note-id': NoteId;
|
||||
'num-comments': number;
|
||||
pending: boolean;
|
||||
'prev-note': NoteId | null;
|
||||
read: boolean;
|
||||
snippet: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
[date: string]: {
|
||||
author: Patp;
|
||||
content: string;
|
||||
'date-created': number;
|
||||
pending: boolean;
|
||||
};
|
||||
}
|
63
pkg/interface/src/types/s3-update.ts
Normal file
63
pkg/interface/src/types/s3-update.ts
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
|
||||
export interface S3Credentials {
|
||||
endpoint: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
}
|
||||
|
||||
export interface S3Configuration {
|
||||
buckets: Set<string>;
|
||||
currentBucket: string;
|
||||
}
|
||||
|
||||
export interface S3State {
|
||||
configuration: S3Configuration;
|
||||
credentials: S3Credentials | null;
|
||||
}
|
||||
|
||||
interface S3UpdateCredentials {
|
||||
credentials: S3Credentials;
|
||||
}
|
||||
|
||||
interface S3UpdateConfiguration {
|
||||
configuration: {
|
||||
buckets: string[];
|
||||
currentBucket: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface S3UpdateCurrentBucket {
|
||||
setCurrentBucket: string;
|
||||
}
|
||||
|
||||
interface S3UpdateAddBucket {
|
||||
addBucket: string;
|
||||
}
|
||||
|
||||
interface S3UpdateRemoveBucket {
|
||||
removeBucket: string;
|
||||
}
|
||||
|
||||
interface S3UpdateEndpoint {
|
||||
setEndpoint: string;
|
||||
}
|
||||
|
||||
interface S3UpdateAccessKeyId {
|
||||
setAccessKeyId: string;
|
||||
}
|
||||
|
||||
interface S3UpdateSecretAccessKey {
|
||||
setSecretAccessKey: string;
|
||||
}
|
||||
|
||||
|
||||
export type S3Update =
|
||||
S3UpdateCredentials
|
||||
| S3UpdateConfiguration
|
||||
| S3UpdateCurrentBucket
|
||||
| S3UpdateAddBucket
|
||||
| S3UpdateRemoveBucket
|
||||
| S3UpdateEndpoint
|
||||
| S3UpdateAccessKeyId
|
||||
| S3UpdateSecretAccessKey;
|
Loading…
Reference in New Issue
Block a user