mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-02 07:06:41 +03:00
interface: cleaning up imports
This commit is contained in:
parent
cf2380bb6c
commit
1b19a95fea
@ -1,6 +1,5 @@
|
||||
import _ from "lodash";
|
||||
import { uuid } from "../lib/util";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
import _ from 'lodash';
|
||||
import { Patp, Path } from '@urbit/api';
|
||||
import BaseStore from '../store/base';
|
||||
|
||||
export default class BaseApi<S extends object = {}> {
|
||||
@ -26,8 +25,8 @@ export default class BaseApi<S extends object = {}> {
|
||||
data: event,
|
||||
from: {
|
||||
ship,
|
||||
path,
|
||||
},
|
||||
path
|
||||
}
|
||||
});
|
||||
},
|
||||
(qui) => {
|
||||
@ -50,8 +49,12 @@ export default class BaseApi<S extends object = {}> {
|
||||
appl,
|
||||
mark,
|
||||
data,
|
||||
(json) => { resolve(json); },
|
||||
(err) => { reject(err); }
|
||||
(json) => {
|
||||
resolve(json);
|
||||
},
|
||||
(err) => {
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -69,5 +72,4 @@ export default class BaseApi<S extends object = {}> {
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path, Enc } from '~/types/noun';
|
||||
import { Contact, ContactEdit } from '~/types/contact-update';
|
||||
import { GroupPolicy, Resource } from '~/types/group-update';
|
||||
import { Patp } from '@urbit/api';
|
||||
import { ContactEdit } from '@urbit/api/contacts';
|
||||
|
||||
export default class ContactsApi extends BaseApi<StoreState> {
|
||||
add(ship: Patp, contact: any) {
|
||||
@ -31,7 +30,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
ship,
|
||||
'edit-field': editField,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -62,7 +61,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
return this.action(
|
||||
'contact-push-hook',
|
||||
'contact-share',
|
||||
{ share: recipient },
|
||||
{ share: recipient }
|
||||
);
|
||||
}
|
||||
|
||||
@ -85,7 +84,7 @@ export default class ContactsApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('contact-store', 'contact-update', action)
|
||||
return this.action('contact-store', 'contact-update', action);
|
||||
}
|
||||
|
||||
private viewAction(threadName: string, action: any) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Patp } from '~/types/noun';
|
||||
import { Patp } from '@urbit/api';
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import GlobalStore from '../store/store';
|
||||
@ -10,7 +10,7 @@ import GroupsApi from './groups';
|
||||
import LaunchApi from './launch';
|
||||
import GraphApi from './graph';
|
||||
import S3Api from './s3';
|
||||
import {HarkApi} from './hark';
|
||||
import { HarkApi } from './hark';
|
||||
import SettingsApi from './settings';
|
||||
|
||||
export default class GlobalApi extends BaseApi<StoreState> {
|
||||
|
@ -1,14 +1,14 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Patp, Path, PatpNoSig } from '~/types/noun';
|
||||
import { Patp, Path } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
import {makeResource, resourceFromPath} from '../lib/group';
|
||||
import {GroupPolicy, Enc, Post, NodeMap, Content, Resource} from '~/types';
|
||||
import { makeResource, resourceFromPath } from '../lib/group';
|
||||
import { GroupPolicy, Enc, Post, Content } from '@urbit/api';
|
||||
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
export const createBlankNodeWithChildPost = (
|
||||
parentIndex: string = '',
|
||||
childIndex: string = '',
|
||||
parentIndex = '',
|
||||
childIndex = '',
|
||||
contents: Content[]
|
||||
) => {
|
||||
const date = unixToDa(Date.now()).toString();
|
||||
@ -37,11 +37,11 @@ export const createBlankNodeWithChildPost = (
|
||||
signatures: []
|
||||
},
|
||||
children: childGraph
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function markPending(nodes: any) {
|
||||
_.forEach(nodes, node => {
|
||||
_.forEach(nodes, (node) => {
|
||||
node.post.author = deSig(node.post.author);
|
||||
node.post.pending = true;
|
||||
markPending(node.children || {});
|
||||
@ -50,8 +50,8 @@ function markPending(nodes: any) {
|
||||
|
||||
export const createPost = (
|
||||
contents: Content[],
|
||||
parentIndex: string = '',
|
||||
childIndex:string = 'DATE_PLACEHOLDER'
|
||||
parentIndex = '',
|
||||
childIndex = 'DATE_PLACEHOLDER'
|
||||
) => {
|
||||
if (childIndex === 'DATE_PLACEHOLDER') {
|
||||
childIndex = unixToDa(Date.now()).toString();
|
||||
@ -80,11 +80,10 @@ function moduleToMark(mod: string): string | undefined {
|
||||
}
|
||||
|
||||
export default class GraphApi extends BaseApi<StoreState> {
|
||||
|
||||
joiningGraphs = new Set<string>();
|
||||
|
||||
private storeAction(action: any): Promise<any> {
|
||||
return this.action('graph-store', 'graph-update', action)
|
||||
return this.action('graph-store', 'graph-update', action);
|
||||
}
|
||||
|
||||
private viewAction(threadName: string, action: any) {
|
||||
@ -106,12 +105,12 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
|
||||
return this.viewAction('graph-create', {
|
||||
"create": {
|
||||
'create': {
|
||||
resource,
|
||||
title,
|
||||
description,
|
||||
associated,
|
||||
"module": mod,
|
||||
'module': mod,
|
||||
mark: moduleToMark(mod)
|
||||
}
|
||||
});
|
||||
@ -127,12 +126,12 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
|
||||
return this.viewAction('graph-create', {
|
||||
"create": {
|
||||
'create': {
|
||||
resource,
|
||||
title,
|
||||
description,
|
||||
associated: { policy },
|
||||
"module": mod,
|
||||
'module': mod,
|
||||
mark: moduleToMark(mod)
|
||||
}
|
||||
});
|
||||
@ -148,9 +147,9 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
return this.viewAction('graph-join', {
|
||||
join: {
|
||||
resource,
|
||||
ship,
|
||||
ship
|
||||
}
|
||||
}).then(res => {
|
||||
}).then((res) => {
|
||||
this.joiningGraphs.delete(rid);
|
||||
return res;
|
||||
});
|
||||
@ -159,7 +158,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
deleteGraph(name: string) {
|
||||
const resource = makeResource(`~${window.ship}`, name);
|
||||
return this.viewAction('graph-delete', {
|
||||
"delete": {
|
||||
'delete': {
|
||||
resource
|
||||
}
|
||||
});
|
||||
@ -168,7 +167,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
leaveGraph(ship: Patp, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
return this.viewAction('graph-leave', {
|
||||
"leave": {
|
||||
'leave': {
|
||||
resource
|
||||
}
|
||||
});
|
||||
@ -203,7 +202,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
addPost(ship: Patp, name: string, post: Post) {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
nodes[post.index] = {
|
||||
post,
|
||||
children: null
|
||||
@ -212,7 +211,7 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
addNode(ship: Patp, name: string, node: Object) {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
nodes[node.post.index] = node;
|
||||
|
||||
return this.addNodes(ship, name, nodes);
|
||||
@ -300,7 +299,6 @@ export default class GraphApi extends BaseApi<StoreState> {
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
|
||||
getGraphSubset(ship: string, resource: string, start: string, end: string) {
|
||||
return this.scry<any>(
|
||||
'graph-store',
|
||||
|
@ -1,14 +1,14 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp, Enc } from '~/types/noun';
|
||||
import { Path, Patp, Enc } from '@urbit/api';
|
||||
import {
|
||||
GroupAction,
|
||||
GroupPolicy,
|
||||
Resource,
|
||||
Tag,
|
||||
GroupPolicyDiff,
|
||||
} from '~/types/group-update';
|
||||
import {makeResource} from '../lib/group';
|
||||
GroupPolicyDiff
|
||||
} from '@urbit/api/groups';
|
||||
import { makeResource } from '../lib/group';
|
||||
|
||||
export default class GroupsApi extends BaseApi<StoreState> {
|
||||
remove(resource: Resource, ships: Patp[]) {
|
||||
@ -38,7 +38,7 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
join(ship: string, name: string) {
|
||||
const resource = makeResource(ship, name);
|
||||
|
||||
return this.viewAction({ join: { resource, ship }});
|
||||
return this.viewAction({ join: { resource, ship } });
|
||||
}
|
||||
|
||||
create(name: string, policy: Enc<GroupPolicy>, title: string, description: string) {
|
||||
@ -76,7 +76,6 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
description
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private proxyAction(action: GroupAction) {
|
||||
@ -93,6 +92,5 @@ export default class GroupsApi extends BaseApi<StoreState> {
|
||||
|
||||
private viewAction(action: any) {
|
||||
return this.action('group-view', 'group-view-action', action);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,23 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { dateToDa, decToUd } from "../lib/util";
|
||||
import {NotifIndex, IndexedNotification, Association, GraphNotifDescription} from "~/types";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { dateToDa, decToUd } from '../lib/util';
|
||||
import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import {getParentIndex} from "../lib/notification";
|
||||
import { getParentIndex } from '../lib/notification';
|
||||
|
||||
export class HarkApi extends BaseApi<StoreState> {
|
||||
private harkAction(action: any): Promise<any> {
|
||||
return this.action("hark-store", "hark-action", action);
|
||||
return this.action('hark-store', 'hark-action', action);
|
||||
}
|
||||
|
||||
private graphHookAction(action: any) {
|
||||
return this.action("hark-graph-hook", "hark-graph-hook-action", action);
|
||||
return this.action('hark-graph-hook', 'hark-graph-hook-action', action);
|
||||
}
|
||||
|
||||
private groupHookAction(action: any) {
|
||||
return this.action("hark-group-hook", "hark-group-hook-action", action);
|
||||
return this.action('hark-group-hook', 'hark-group-hook-action', action);
|
||||
}
|
||||
|
||||
|
||||
private actOnNotification(frond: string, intTime: BigInteger, index: NotifIndex) {
|
||||
const time = decToUd(intTime.toString());
|
||||
return this.harkAction({
|
||||
@ -74,12 +73,10 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
module: association.metadata.module,
|
||||
description,
|
||||
index: parent
|
||||
} },
|
||||
} }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
markEachAsRead(association: Association, parent: string, child: string, description: GraphNotifDescription, mod: string) {
|
||||
return this.harkAction({
|
||||
'read-each': {
|
||||
@ -116,7 +113,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
mute(notif: IndexedNotification) {
|
||||
if('graph' in notif.index && 'graph' in notif.notification.contents) {
|
||||
const { index } = notif;
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph)
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
|
||||
if(!parentIndex) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -132,7 +129,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
unmute(notif: IndexedNotification) {
|
||||
if('graph' in notif.index && 'graph' in notif.notification.contents) {
|
||||
const { index } = notif;
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph)
|
||||
const parentIndex = getParentIndex(index.graph, notif.notification.contents.graph);
|
||||
if(!parentIndex) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@ -147,7 +144,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
ignoreGroup(group: string) {
|
||||
return this.groupHookAction({
|
||||
ignore: group
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
ignoreGraph(graph: string, index: string) {
|
||||
@ -156,13 +153,13 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
graph,
|
||||
index
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
listenGroup(group: string) {
|
||||
return this.groupHookAction({
|
||||
listen: group
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
listenGraph(graph: string, index: string) {
|
||||
@ -171,7 +168,7 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
graph,
|
||||
index
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
async getMore(): Promise<boolean> {
|
||||
@ -183,16 +180,16 @@ export class HarkApi extends BaseApi<StoreState> {
|
||||
|
||||
async getSubset(offset:number, count:number, isArchive: boolean) {
|
||||
const where = isArchive ? 'archive' : 'inbox';
|
||||
const data = await this.scry("hark-store", `/recent/${where}/${offset}/${count}`);
|
||||
const data = await this.scry('hark-store', `/recent/${where}/${offset}/${count}`);
|
||||
this.store.handleEvent({ data });
|
||||
}
|
||||
|
||||
async getTimeSubset(start?: Date, end?: Date) {
|
||||
const s = start ? dateToDa(start) : "-";
|
||||
const e = end ? dateToDa(end) : "-";
|
||||
const result = await this.scry("hark-hook", `/recent/${s}/${e}`);
|
||||
const s = start ? dateToDa(start) : '-';
|
||||
const e = end ? dateToDa(end) : '-';
|
||||
const result = await this.scry('hark-hook', `/recent/${s}/${e}`);
|
||||
this.store.handleEvent({
|
||||
data: result,
|
||||
data: result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import { Serial, Path } from "~/types/noun";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Serial, Path } from '@urbit/api';
|
||||
|
||||
export default class InviteApi extends BaseApi<StoreState> {
|
||||
accept(app: string, uid: Serial) {
|
||||
|
@ -2,7 +2,7 @@ import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LaunchApi extends BaseApi<StoreState> {
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' }}) {
|
||||
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) {
|
||||
return this.launchAction({ add: { name, tile } });
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ export default class LaunchApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
changeIsShown(name: string, isShown = true) {
|
||||
return this.launchAction({ 'change-is-shown': { name, isShown }});
|
||||
return this.launchAction({ 'change-is-shown': { name, isShown } });
|
||||
}
|
||||
|
||||
weather(location: string) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import BaseApi from "./base";
|
||||
import { StoreState } from "../store/type";
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
|
||||
export default class LocalApi extends BaseApi<StoreState> {
|
||||
getBaseHash() {
|
||||
this.scry<string>('file-server', '/clay/base/hash').then(baseHash => {
|
||||
this.scry<string>('file-server', '/clay/base/hash').then((baseHash) => {
|
||||
this.store.handleEvent({ data: { local: { baseHash } } });
|
||||
});
|
||||
}
|
||||
@ -11,5 +11,4 @@ export default class LocalApi extends BaseApi<StoreState> {
|
||||
dehydrate() {
|
||||
this.store.dehydrate();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '~/types';
|
||||
import {uxToHex} from '../lib/util';
|
||||
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '@urbit/api';
|
||||
import { uxToHex } from '../lib/util';
|
||||
|
||||
export default class MetadataApi extends BaseApi<StoreState> {
|
||||
|
||||
|
||||
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {
|
||||
const creator = `~${this.ship}`;
|
||||
return this.metadataAction({
|
||||
@ -44,9 +42,9 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
}
|
||||
|
||||
update(association: Association, newMetadata: Partial<Metadata>) {
|
||||
const metadata = {...association.metadata, ...newMetadata };
|
||||
const metadata = { ...association.metadata, ...newMetadata };
|
||||
metadata.color = uxToHex(metadata.color);
|
||||
return this.metadataAction({
|
||||
return this.metadataAction({
|
||||
add: {
|
||||
group: association.group,
|
||||
resource: {
|
||||
@ -69,10 +67,10 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
}
|
||||
done = true;
|
||||
tempChannel.delete();
|
||||
reject(new Error("offline"))
|
||||
reject(new Error('offline'));
|
||||
}, 15000);
|
||||
|
||||
tempChannel.subscribe(window.ship, "metadata-pull-hook", `/preview${group}`,
|
||||
tempChannel.subscribe(window.ship, 'metadata-pull-hook', `/preview${group}`,
|
||||
(err) => {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
@ -88,24 +86,22 @@ export default class MetadataApi extends BaseApi<StoreState> {
|
||||
} else {
|
||||
done = true;
|
||||
tempChannel.delete();
|
||||
reject(new Error("no-permissions"));
|
||||
reject(new Error('no-permissions'));
|
||||
}
|
||||
},
|
||||
(quit) => {
|
||||
tempChannel.delete();
|
||||
if(!done) {
|
||||
reject(new Error("offline"))
|
||||
reject(new Error('offline'));
|
||||
}
|
||||
},
|
||||
(a) => {
|
||||
console.log(a);
|
||||
}
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private metadataAction(data) {
|
||||
return this.action('metadata-push-hook', 'metadata-update', data);
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import {S3Update} from '../../types/s3-update';
|
||||
|
||||
import { S3Update } from '../../types/s3-update';
|
||||
|
||||
export default class S3Api extends BaseApi<StoreState> {
|
||||
|
||||
setCurrentBucket(bucket: string) {
|
||||
return this.s3Action({ 'set-current-bucket': bucket });
|
||||
}
|
||||
@ -32,6 +30,5 @@ export default class S3Api extends BaseApi<StoreState> {
|
||||
private s3Action(data: any) {
|
||||
return this.action('s3-store', 's3-action', data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
import BaseApi from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import {
|
||||
SettingsUpdate,
|
||||
SettingsData,
|
||||
Key,
|
||||
import { Key,
|
||||
Value,
|
||||
Bucket,
|
||||
} from '~/types/settings';
|
||||
|
||||
Bucket
|
||||
} from '@urbit/api/settings';
|
||||
|
||||
export default class SettingsApi extends BaseApi<StoreState> {
|
||||
private storeAction(action: SettingsEvent): Promise<any> {
|
||||
@ -16,59 +12,59 @@ export default class SettingsApi extends BaseApi<StoreState> {
|
||||
|
||||
putBucket(key: Key, bucket: Bucket) {
|
||||
this.storeAction({
|
||||
"put-bucket": {
|
||||
"bucket-key": key,
|
||||
"bucket": bucket,
|
||||
'put-bucket': {
|
||||
'bucket-key': key,
|
||||
'bucket': bucket
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delBucket(key: Key) {
|
||||
this.storeAction({
|
||||
"del-bucket": {
|
||||
"bucket-key": key,
|
||||
'del-bucket': {
|
||||
'bucket-key': key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
putEntry(buc: Key, key: Key, val: Value) {
|
||||
return this.storeAction({
|
||||
"put-entry": {
|
||||
"bucket-key": buc,
|
||||
"entry-key": key,
|
||||
"value": val,
|
||||
'put-entry': {
|
||||
'bucket-key': buc,
|
||||
'entry-key': key,
|
||||
'value': val
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delEntry(buc: Key, key: Key) {
|
||||
this.storeAction({
|
||||
"put-entry": {
|
||||
"bucket-key": buc,
|
||||
"entry-key": key,
|
||||
'put-entry': {
|
||||
'bucket-key': buc,
|
||||
'entry-key': key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
const data = await this.scry("settings-store", "/all");
|
||||
this.store.handleEvent({data: {"settings-data": data.all}});
|
||||
const data = await this.scry('settings-store', '/all');
|
||||
this.store.handleEvent({ data: { 'settings-data': data.all } });
|
||||
}
|
||||
|
||||
async getBucket(bucket: Key) {
|
||||
const data = await this.scry('settings-store', `/bucket/${bucket}`);
|
||||
this.store.handleEvent({data: {"settings-data": {
|
||||
"bucket-key": bucket,
|
||||
"bucket": data.bucket,
|
||||
}}});
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'bucket': data.bucket
|
||||
} } });
|
||||
}
|
||||
|
||||
async getEntry(bucket: Key, entry: Key) {
|
||||
const data = await this.scry('settings-store', `/entry/${bucket}/${entry}`);
|
||||
this.store.handleEvent({data: {"settings-data": {
|
||||
"bucket-key": bucket,
|
||||
"entry-key": entry,
|
||||
"entry": data.entry,
|
||||
}}});
|
||||
this.store.handleEvent({ data: { 'settings-data': {
|
||||
'bucket-key': bucket,
|
||||
'entry-key': entry,
|
||||
'entry': data.entry
|
||||
} } });
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
interface NonemptyNode<V> {
|
||||
n: [BigInteger, V];
|
||||
@ -14,7 +14,7 @@ type MapNode<V> = NonemptyNode<V> | null;
|
||||
*/
|
||||
export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
private root: MapNode<V> = null;
|
||||
size: number = 0;
|
||||
size = 0;
|
||||
|
||||
constructor(initial: [BigInteger, V][] = []) {
|
||||
initial.forEach(([key, val]) => {
|
||||
@ -48,13 +48,12 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
* Put an item by a key
|
||||
*/
|
||||
set(key: BigInteger, value: V): void {
|
||||
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if (!node) {
|
||||
return {
|
||||
n: [key, value],
|
||||
l: null,
|
||||
r: null,
|
||||
r: null
|
||||
};
|
||||
}
|
||||
const [k] = node.n;
|
||||
@ -62,22 +61,22 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
this.size--;
|
||||
return {
|
||||
...node,
|
||||
n: [k, value],
|
||||
n: [k, value]
|
||||
};
|
||||
}
|
||||
if (key.gt(k)) {
|
||||
const l = inner(node.l);
|
||||
if (!l) {
|
||||
throw new Error("invariant violation");
|
||||
throw new Error('invariant violation');
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
l,
|
||||
l
|
||||
};
|
||||
}
|
||||
const r = inner(node.r);
|
||||
if (!r) {
|
||||
throw new Error("invariant violation");
|
||||
throw new Error('invariant violation');
|
||||
}
|
||||
|
||||
return { ...node, r };
|
||||
@ -133,8 +132,8 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
bool,
|
||||
{
|
||||
...node,
|
||||
l,
|
||||
},
|
||||
l
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@ -143,8 +142,8 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
bool,
|
||||
{
|
||||
...node,
|
||||
r,
|
||||
},
|
||||
r
|
||||
}
|
||||
];
|
||||
};
|
||||
const [ret, newRoot] = inner(this.root);
|
||||
@ -165,12 +164,12 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
}
|
||||
return {
|
||||
...node.l,
|
||||
r: inner(node.r),
|
||||
r: inner(node.r)
|
||||
};
|
||||
};
|
||||
return inner(nod);
|
||||
}
|
||||
|
||||
|
||||
peekLargest(): [BigInteger, V] | undefined {
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if(!node) {
|
||||
@ -180,7 +179,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return inner(node.l);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
};
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
@ -193,7 +192,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return inner(node.r);
|
||||
}
|
||||
return node.n;
|
||||
}
|
||||
};
|
||||
return inner(this.root);
|
||||
}
|
||||
|
||||
@ -208,7 +207,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[BigInteger, V]> {
|
||||
let result: [BigInteger, V][] = [];
|
||||
const result: [BigInteger, V][] = [];
|
||||
const inner = (node: MapNode<V>) => {
|
||||
if (!node) {
|
||||
return;
|
||||
@ -227,7 +226,7 @@ export class BigIntOrderedMap<V> implements Iterable<[BigInteger, V]> {
|
||||
return { value: result[idx++], done: false };
|
||||
}
|
||||
return { done: true, value: null };
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
export class OrderedMap<V> extends Map<number, V>
|
||||
implements Iterable<[number, V]> {
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[number, V]> {
|
||||
const sorted = Array.from(super[Symbol.iterator]()).sort(
|
||||
([a], [b]) => b - a
|
||||
@ -15,7 +14,7 @@ export class OrderedMap<V> extends Map<number, V>
|
||||
} else {
|
||||
return { done: true, value: null };
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
export function max(a: BigInteger, b: BigInteger) {
|
||||
return a.gt(b) ? a : b;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import _ from "lodash";
|
||||
import { roleTags, RoleTags, Group, Resource } from "~/types/group-update";
|
||||
import { PatpNoSig, Path } from "~/types/noun";
|
||||
import {deSig} from "./util";
|
||||
import _ from 'lodash';
|
||||
import { roleTags, RoleTags, Group, Resource } from '@urbit/api/groups';
|
||||
import { PatpNoSig, Path } from '@urbit/api';
|
||||
import { deSig } from './util';
|
||||
|
||||
export function roleForShip(
|
||||
group: Group,
|
||||
@ -14,7 +14,7 @@ export function roleForShip(
|
||||
}
|
||||
|
||||
export function resourceFromPath(path: Path): Resource {
|
||||
const [, , ship, name] = path.split("/");
|
||||
const [, , ship, name] = path.split('/');
|
||||
return { ship, name };
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export function makeResource(ship: string, name: string) {
|
||||
export function isWriter(group: Group, resource: string) {
|
||||
const writers: Set<string> | undefined = _.get(
|
||||
group,
|
||||
["tags", "graph", resource, "writers"],
|
||||
['tags', 'graph', resource, 'writers'],
|
||||
undefined
|
||||
);
|
||||
const admins = group?.tags?.role?.admin ?? new Set();
|
||||
@ -36,18 +36,18 @@ export function isWriter(group: Group, resource: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function isChannelAdmin(group: Group, resource: string, ship: string = `~${window.ship}`) {
|
||||
export function isChannelAdmin(group: Group, resource: string, ship = `~${window.ship}`) {
|
||||
const role = roleForShip(group, ship.slice(1));
|
||||
|
||||
return (
|
||||
isHost(resource, ship) ||
|
||||
role === "admin" ||
|
||||
role === "moderator"
|
||||
role === 'admin' ||
|
||||
role === 'moderator'
|
||||
);
|
||||
}
|
||||
|
||||
export function isHost(resource: string, ship: string = `~${window.ship}`) {
|
||||
const [, , host] = resource.split("/");
|
||||
export function isHost(resource: string, ship = `~${window.ship}`) {
|
||||
const [, , host] = resource.split('/');
|
||||
|
||||
return ship === host;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import f from "lodash/fp";
|
||||
import { Unreads } from "~/types";
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import f from 'lodash/fp';
|
||||
import { Unreads } from '@urbit/api';
|
||||
|
||||
export function getLastSeen(
|
||||
unreads: Unreads,
|
||||
@ -8,10 +8,10 @@ export function getLastSeen(
|
||||
index: string
|
||||
): BigInteger | undefined {
|
||||
const lastSeenIdx = unreads.graph?.[path]?.[index]?.unreads;
|
||||
if (!(typeof lastSeenIdx === "string")) {
|
||||
if (!(typeof lastSeenIdx === 'string')) {
|
||||
return bigInt.zero;
|
||||
}
|
||||
return f.flow(f.split("/"), f.last, (x) => (!!x ? bigInt(x) : undefined))(
|
||||
return f.flow(f.split('/'), f.last, x => (x ? bigInt(x) : undefined))(
|
||||
lastSeenIdx
|
||||
);
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { GraphNotifIndex, GraphNotificationContents } from "~/types";
|
||||
import { GraphNotifIndex, GraphNotificationContents } from '@urbit/api';
|
||||
|
||||
export function getParentIndex(
|
||||
idx: GraphNotifIndex,
|
||||
contents: GraphNotificationContents
|
||||
) {
|
||||
const origIndex = contents[0].index.slice(1).split("/");
|
||||
const ret = (i: string[]) => `/${i.join("/")}`;
|
||||
const origIndex = contents[0].index.slice(1).split('/');
|
||||
const ret = (i: string[]) => `/${i.join('/')}`;
|
||||
switch (idx.description) {
|
||||
case "link":
|
||||
return "/";
|
||||
case "comment":
|
||||
case 'link':
|
||||
return '/';
|
||||
case 'comment':
|
||||
return ret(origIndex.slice(0, 1));
|
||||
case "note":
|
||||
return "/";
|
||||
case "mention":
|
||||
case 'note':
|
||||
return '/';
|
||||
case 'mention':
|
||||
return undefined;
|
||||
default:
|
||||
return undefined;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Post, GraphNode } from "~/types";
|
||||
import { Post, GraphNode } from '@urbit/api';
|
||||
|
||||
export const buntPost = (): Post => ({
|
||||
author: '',
|
||||
@ -10,7 +10,7 @@ export const buntPost = (): Post => ({
|
||||
});
|
||||
|
||||
export function makeNodeMap(posts: Post[]): Record<string, GraphNode> {
|
||||
let nodes = {};
|
||||
const nodes = {};
|
||||
posts.forEach((p) => {
|
||||
nodes[p.index] = { children: { empty: null }, post: p };
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Post, GraphNode, TextContent, Graph, NodeMap } from "~/types";
|
||||
import { Post, GraphNode, TextContent, Graph, NodeMap } from '@urbit/api';
|
||||
import { buntPost } from '~/logic/lib/post';
|
||||
import { unixToDa } from "~/logic/lib/util";
|
||||
import {BigIntOrderedMap} from "./BigIntOrderedMap";
|
||||
import bigInt, {BigInteger} from 'big-integer';
|
||||
import { unixToDa } from '~/logic/lib/util';
|
||||
import { BigIntOrderedMap } from './BigIntOrderedMap';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
export function newPost(
|
||||
title: string,
|
||||
@ -12,20 +12,20 @@ export function newPost(
|
||||
const nowDa = unixToDa(now);
|
||||
const root: Post = {
|
||||
author: `~${window.ship}`,
|
||||
index: "/" + nowDa.toString(),
|
||||
"time-sent": now,
|
||||
index: '/' + nowDa.toString(),
|
||||
'time-sent': now,
|
||||
contents: [],
|
||||
hash: null,
|
||||
signatures: [],
|
||||
signatures: []
|
||||
};
|
||||
|
||||
const revContainer: Post = { ...root, index: root.index + "/1" };
|
||||
const commentsContainer = { ...root, index: root.index + "/2" };
|
||||
const revContainer: Post = { ...root, index: root.index + '/1' };
|
||||
const commentsContainer = { ...root, index: root.index + '/2' };
|
||||
|
||||
const firstRevision: Post = {
|
||||
...revContainer,
|
||||
index: revContainer.index + "/1",
|
||||
contents: [{ text: title }, { text: body }],
|
||||
index: revContainer.index + '/1',
|
||||
contents: [{ text: title }, { text: body }]
|
||||
};
|
||||
|
||||
const nodes = {
|
||||
@ -37,16 +37,16 @@ export function newPost(
|
||||
children: {
|
||||
1: {
|
||||
post: firstRevision,
|
||||
children: null,
|
||||
},
|
||||
},
|
||||
children: null
|
||||
}
|
||||
}
|
||||
},
|
||||
2: {
|
||||
post: commentsContainer,
|
||||
children: null
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return [nowDa, nodes];
|
||||
@ -57,15 +57,15 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s
|
||||
const newRev: Post = {
|
||||
author: `~${window.ship}`,
|
||||
index: `/${noteId.toString()}/1/${rev}`,
|
||||
"time-sent": now,
|
||||
'time-sent': now,
|
||||
contents: [{ text: title }, { text: body }],
|
||||
hash: null,
|
||||
signatures: [],
|
||||
signatures: []
|
||||
};
|
||||
const nodes = {
|
||||
[newRev.index]: {
|
||||
post: newRev,
|
||||
children: null
|
||||
children: null
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,7 +74,7 @@ export function editPost(rev: number, noteId: BigInteger, title: string, body: s
|
||||
|
||||
export function getLatestRevision(node: GraphNode): [number, string, string, Post] {
|
||||
const revs = node.children.get(bigInt(1));
|
||||
const empty = [1, "", "", buntPost()] as [number, string, string, Post];
|
||||
const empty = [1, '', '', buntPost()] as [number, string, string, Post];
|
||||
if(!revs) {
|
||||
return empty;
|
||||
}
|
||||
@ -98,17 +98,16 @@ export function getLatestCommentRevision(node: GraphNode): [number, Post] {
|
||||
return [revNum.toJSNumber(), rev.post];
|
||||
}
|
||||
|
||||
|
||||
export function getComments(node: GraphNode): GraphNode {
|
||||
const comments = node.children.get(bigInt(2));
|
||||
if(!comments) {
|
||||
return { post: buntPost(), children: new BigIntOrderedMap() }
|
||||
return { post: buntPost(), children: new BigIntOrderedMap() };
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
|
||||
export function getSnippet(body: string) {
|
||||
const start = body.slice(0, body.indexOf('\n', 2));
|
||||
return (start === body || start.startsWith("![")) ? start : `${start}...`;
|
||||
return (start === body || start.startsWith('![')) ? start : `${start}...`;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import _ from "lodash";
|
||||
import _ from 'lodash';
|
||||
|
||||
export const alignY = ["top", "bottom"] as const;
|
||||
export const alignY = ['top', 'bottom'] as const;
|
||||
export type AlignY = typeof alignY[number];
|
||||
export const alignX = ["left", "right"] as const;
|
||||
export const alignX = ['left', 'right'] as const;
|
||||
export type AlignX = typeof alignX[number];
|
||||
|
||||
export function getRelativePosition(
|
||||
relativeTo: HTMLElement | null,
|
||||
alignX: AlignX | AlignX[],
|
||||
alignY: AlignY | AlignY[],
|
||||
offsetX: number = 0,
|
||||
offsetY: number = 0
|
||||
offsetX = 0,
|
||||
offsetY = 0
|
||||
) {
|
||||
const rect = relativeTo?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
@ -20,7 +20,7 @@ export function getRelativePosition(
|
||||
top: rect.top - offsetY,
|
||||
left: rect.left - offsetX,
|
||||
bottom: document.documentElement.clientHeight - rect.bottom - offsetY,
|
||||
right: document.documentElement.clientWidth - rect.right - offsetX,
|
||||
right: document.documentElement.clientWidth - rect.right - offsetX
|
||||
};
|
||||
const alignXArr = _.isArray(alignX) ? alignX : [alignX];
|
||||
const alignYArr = _.isArray(alignY) ? alignY : [alignY];
|
||||
@ -34,7 +34,7 @@ export function getRelativePosition(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
)
|
||||
}),
|
||||
{}
|
||||
),
|
||||
@ -46,10 +46,10 @@ export function getRelativePosition(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
)
|
||||
}),
|
||||
{}
|
||||
),
|
||||
)
|
||||
} as Record<AlignY | AlignX, string[]>;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TutorialProgress, Associations } from "~/types";
|
||||
import { AlignX, AlignY } from "~/logic/lib/relativePosition";
|
||||
import { Direction } from "~/views/components/Triangle";
|
||||
import { TutorialProgress, Associations } from '@urbit/api';
|
||||
import { AlignX, AlignY } from '~/logic/lib/relativePosition';
|
||||
import { Direction } from '~/views/components/Triangle';
|
||||
|
||||
export const MODAL_WIDTH = 256;
|
||||
export const MODAL_HEIGHT = 256;
|
||||
@ -43,7 +43,7 @@ export const getTrianglePosition = (dir: Direction) => {
|
||||
return {
|
||||
top: midY,
|
||||
left: '-32px'
|
||||
}
|
||||
};
|
||||
case 'North':
|
||||
return {
|
||||
top: '-32px',
|
||||
@ -55,117 +55,117 @@ export const getTrianglePosition = (dir: Direction) => {
|
||||
left: midX
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const progressDetails: Record<TutorialProgress, StepDetail> = {
|
||||
hidden: {} as any,
|
||||
exit: {} as any,
|
||||
done: {
|
||||
title: "End",
|
||||
title: 'End',
|
||||
description:
|
||||
"This tutorial is finished. Would you like to leave Beginner Island?",
|
||||
url: "/",
|
||||
alignX: "right",
|
||||
alignY: "top",
|
||||
'This tutorial is finished. Would you like to leave Beginner Island?',
|
||||
url: '/',
|
||||
alignX: 'right',
|
||||
alignY: 'top',
|
||||
offsetX: MODAL_WIDTH + 8,
|
||||
offsetY: 0,
|
||||
offsetY: 0
|
||||
},
|
||||
start: {
|
||||
title: "New Group added",
|
||||
title: 'New Group added',
|
||||
description:
|
||||
"We just added you to the Beginner island group to show you around. This group is public, but other groups can be private",
|
||||
url: "/",
|
||||
alignX: "right",
|
||||
alignY: "top",
|
||||
arrow: "West",
|
||||
'We just added you to the Beginner island group to show you around. This group is public, but other groups can be private',
|
||||
url: '/',
|
||||
alignX: 'right',
|
||||
alignY: 'top',
|
||||
arrow: 'West',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: 64,
|
||||
offsetY: 64
|
||||
},
|
||||
"group-desc": {
|
||||
title: "What's a group",
|
||||
'group-desc': {
|
||||
title: 'What\'s a group',
|
||||
description:
|
||||
"A group contains members and tends to be centered around a topic or multiple topics.",
|
||||
'A group contains members and tends to be centered around a topic or multiple topics.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
|
||||
alignX: "left",
|
||||
alignY: "top",
|
||||
arrow: "East",
|
||||
alignX: 'left',
|
||||
alignY: 'top',
|
||||
arrow: 'East',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: MODAL_HEIGHT / 2 - 8,
|
||||
offsetY: MODAL_HEIGHT / 2 - 8
|
||||
},
|
||||
channels: {
|
||||
title: "Channels",
|
||||
title: 'Channels',
|
||||
description:
|
||||
"Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!",
|
||||
'Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "West",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'West',
|
||||
offsetX: MODAL_WIDTH + 24,
|
||||
offsetY: -8,
|
||||
offsetY: -8
|
||||
},
|
||||
chat: {
|
||||
title: "Chat",
|
||||
title: 'Chat',
|
||||
description:
|
||||
"Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen",
|
||||
'Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/chat/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}`,
|
||||
alignY: "top",
|
||||
arrow: "North",
|
||||
alignX: "right",
|
||||
alignY: 'top',
|
||||
arrow: 'North',
|
||||
alignX: 'right',
|
||||
offsetY: -56,
|
||||
offsetX: -8,
|
||||
offsetX: -8
|
||||
},
|
||||
link: {
|
||||
title: "Collection",
|
||||
title: 'Collection',
|
||||
description:
|
||||
"A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.",
|
||||
'A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have it’s own comment thread.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/link/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56,
|
||||
offsetY: -56
|
||||
},
|
||||
publish: {
|
||||
title: "Notebook",
|
||||
title: 'Notebook',
|
||||
description:
|
||||
"Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.",
|
||||
'Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.',
|
||||
url: `/~landscape/ship/${TUTORIAL_HOST}/${TUTORIAL_GROUP}/resource/publish/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'North',
|
||||
offsetX: -8,
|
||||
offsetY: -56,
|
||||
offsetY: -56
|
||||
},
|
||||
notifications: {
|
||||
title: "Notifications",
|
||||
description: "You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.",
|
||||
title: 'Notifications',
|
||||
description: 'You will get updates from subscribed channels and mentions here. You can access Notifications through Leap.',
|
||||
url: '/~notifications',
|
||||
alignY: "top",
|
||||
alignX: "left",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: (MODAL_WIDTH / 2) - 16,
|
||||
offsetY: -48,
|
||||
offsetY: -48
|
||||
},
|
||||
profile: {
|
||||
title: "Profile",
|
||||
title: 'Profile',
|
||||
description:
|
||||
"Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.",
|
||||
'Your profile is customizable and can be shared with other ships. Enter as much or as little information as you’d like.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: "top",
|
||||
alignX: "right",
|
||||
arrow: "South",
|
||||
alignY: 'top',
|
||||
alignX: 'right',
|
||||
arrow: 'South',
|
||||
offsetX: -300 + MODAL_WIDTH / 2,
|
||||
offsetY: -120 + MODAL_HEIGHT / 2,
|
||||
offsetY: -120 + MODAL_HEIGHT / 2
|
||||
},
|
||||
leap: {
|
||||
title: "Leap",
|
||||
title: 'Leap',
|
||||
description:
|
||||
"Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.",
|
||||
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
|
||||
url: `/~profile/~${window.ship}`,
|
||||
alignY: "top",
|
||||
alignX: "left",
|
||||
arrow: "North",
|
||||
alignY: 'top',
|
||||
alignX: 'left',
|
||||
arrow: 'North',
|
||||
offsetX: 0.3 *MODAL_HEIGHT,
|
||||
offsetY: -48,
|
||||
},
|
||||
offsetY: -48
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useMemo, useEffect } from "react";
|
||||
import { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
|
||||
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
|
||||
const files: File[] = [];
|
||||
@ -8,8 +8,8 @@ function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
|
||||
}
|
||||
if (e.dataTransfer?.items) {
|
||||
Array.from(e.dataTransfer.items || [])
|
||||
.filter((i) => i.kind === 'file')
|
||||
.forEach(f => {
|
||||
.filter(i => i.kind === 'file')
|
||||
.forEach((f) => {
|
||||
valid = true; // Valid if file exists, but on DragOver, won't reveal its contents for security
|
||||
const data = f.getAsFile();
|
||||
if (data) {
|
||||
@ -89,14 +89,14 @@ export function useFileDrag(dragged: (f: FileList | File[], e: DragEvent) => voi
|
||||
document.body.addEventListener('mouseout', mouseleave);
|
||||
return () => {
|
||||
document.body.removeEventListener('mouseout', mouseleave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const bind = {
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
onDragEnter,
|
||||
onDragEnter
|
||||
};
|
||||
|
||||
return { bind, dragging };
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
|
||||
export function useDropdown<C>(
|
||||
candidates: C[],
|
||||
@ -12,10 +12,10 @@ export function useDropdown<C>(
|
||||
(s: string) => {
|
||||
const exactMatch = isExact(s);
|
||||
const exact = exactMatch ? [exactMatch] : [];
|
||||
const opts = [...new Set([...exact, ...candidates.filter((c) => searchPred(s, c))])];
|
||||
const opts = [...new Set([...exact, ...candidates.filter(c => searchPred(s, c))])];
|
||||
setOptions(opts);
|
||||
if (selected) {
|
||||
const idx = opts.findIndex((c) => key(c) === key(selected));
|
||||
const idx = opts.findIndex(c => key(c) === key(selected));
|
||||
if (idx < 0) {
|
||||
setSelected(undefined);
|
||||
}
|
||||
@ -29,9 +29,11 @@ export function useDropdown<C>(
|
||||
const select = (idx: number) => {
|
||||
setSelected(options[idx]);
|
||||
};
|
||||
if(!selected) { select(0); return false; }
|
||||
if(!selected) {
|
||||
select(0); return false;
|
||||
}
|
||||
|
||||
const idx = options.findIndex((c) => key(c) === key(selected));
|
||||
const idx = options.findIndex(c => key(c) === key(selected));
|
||||
if (
|
||||
idx === -1 ||
|
||||
(options.length - 1 <= idx && !backward)
|
||||
@ -55,6 +57,6 @@ export function useDropdown<C>(
|
||||
back,
|
||||
search,
|
||||
selected,
|
||||
options,
|
||||
options
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import {useLocation} from "react-router-dom";
|
||||
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export function useHashLink() {
|
||||
const location = useLocation();
|
||||
@ -10,8 +9,5 @@ export function useHashLink() {
|
||||
return;
|
||||
}
|
||||
document.querySelector(location.hash)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
}, [location.hash]);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, RefObject, useRef, useState } from "react";
|
||||
import _ from "lodash";
|
||||
import usePreviousValue from "./usePreviousValue";
|
||||
import { useEffect, RefObject, useRef, useState } from 'react';
|
||||
import _ from 'lodash';
|
||||
import usePreviousValue from './usePreviousValue';
|
||||
|
||||
export function distanceToBottom(el: HTMLElement) {
|
||||
const { scrollTop, scrollHeight, clientHeight } = el;
|
||||
@ -40,7 +40,6 @@ export function useLazyScroll(
|
||||
}
|
||||
}, [count]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
@ -54,13 +53,12 @@ export function useLazyScroll(
|
||||
loadUntil(el);
|
||||
};
|
||||
|
||||
ref.current.addEventListener("scroll", onScroll, { passive: true });
|
||||
ref.current.addEventListener('scroll', onScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
ref.current?.removeEventListener("scroll", onScroll);
|
||||
ref.current?.removeEventListener('scroll', onScroll);
|
||||
};
|
||||
}, [ref?.current, count]);
|
||||
|
||||
|
||||
return { isDone, isLoading };
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
function retrieve<T>(key: string, initial: T): T {
|
||||
const s = localStorage.getItem(key);
|
||||
@ -25,7 +25,7 @@ export function useLocalStorageState<T>(key: string, initial: T) {
|
||||
|
||||
const setState = useCallback(
|
||||
(s: SetState<T>) => {
|
||||
const updated = typeof s === "function" ? s(state) : s;
|
||||
const updated = typeof s === 'function' ? s(state) : s;
|
||||
_setState(updated);
|
||||
localStorage.setItem(key, JSON.stringify(updated));
|
||||
},
|
||||
|
@ -5,15 +5,15 @@ import React, {
|
||||
SyntheticEvent,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
useRef
|
||||
} from 'react';
|
||||
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { useOutsideClick } from "./useOutsideClick";
|
||||
import { ModalOverlay } from "~/views/components/ModalOverlay";
|
||||
import {Portal} from "~/views/components/Portal";
|
||||
import {ModalPortal} from "~/views/components/ModalPortal";
|
||||
import {PropFunc} from "~/types";
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { useOutsideClick } from './useOutsideClick';
|
||||
import { ModalOverlay } from '~/views/components/ModalOverlay';
|
||||
import { Portal } from '~/views/components/Portal';
|
||||
import { ModalPortal } from '~/views/components/ModalPortal';
|
||||
import { PropFunc } from '@urbit/api';
|
||||
|
||||
type ModalFunc = (dismiss: () => void) => JSX.Element;
|
||||
interface UseModalProps {
|
||||
@ -42,7 +42,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
() =>
|
||||
!modalShown
|
||||
? null
|
||||
: typeof modal === "function"
|
||||
: typeof modal === 'function'
|
||||
? modal(dismiss)
|
||||
: modal,
|
||||
[modalShown, modal, dismiss]
|
||||
@ -59,7 +59,7 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
bg="white"
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
borderColor={['washedGray', 'washedGray']}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
@ -76,6 +76,6 @@ export function useModal(props: UseModalProps & PropFunc<typeof Box>): UseModalR
|
||||
|
||||
return {
|
||||
showModal,
|
||||
modal: modalComponent,
|
||||
modal: modalComponent
|
||||
};
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useEffect, RefObject } from "react";
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export function useOutsideClick(
|
||||
ref: RefObject<HTMLElement | null | undefined>,
|
||||
onClick: () => void,
|
||||
onClick: () => void
|
||||
) {
|
||||
useEffect(() => {
|
||||
function handleClick(event: MouseEvent) {
|
||||
@ -16,17 +16,16 @@ export function useOutsideClick(
|
||||
}
|
||||
|
||||
function handleKeyDown(ev) {
|
||||
if(ev.key === "Escape") {
|
||||
if(ev.key === 'Escape') {
|
||||
onClick();
|
||||
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
document.addEventListener('mousedown', handleClick);
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClick);
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
document.removeEventListener('mousedown', handleClick);
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [ref.current, onClick]);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { useRef } from "react";
|
||||
import { Primitive } from "~/types";
|
||||
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { Primitive } from '@urbit/api';
|
||||
|
||||
export default function usePreviousValue<T extends Primitive>(value: T): T {
|
||||
const prev = useRef<T | null>(null);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useMemo, useCallback } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function useQuery() {
|
||||
@ -25,6 +25,6 @@ export function useQuery() {
|
||||
|
||||
return {
|
||||
query,
|
||||
appendQuery,
|
||||
appendQuery
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useCallback, useMemo, useEffect, useRef, useState } from "react";
|
||||
import { S3State } from "../../types/s3-update";
|
||||
import S3 from "aws-sdk/clients/s3";
|
||||
import { dateToDa, deSig } from "./util";
|
||||
import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
|
||||
import { S3State } from '../../types/s3-update';
|
||||
import S3 from 'aws-sdk/clients/s3';
|
||||
import { dateToDa, deSig } from './util';
|
||||
|
||||
export interface IuseS3 {
|
||||
canUpload: boolean;
|
||||
@ -28,14 +28,14 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
|
||||
|
||||
const canUpload = useMemo(
|
||||
() =>
|
||||
(client && s3.credentials && s3.configuration.currentBucket !== "") || false,
|
||||
(client && s3.credentials && s3.configuration.currentBucket !== '') || false,
|
||||
[s3.credentials, s3.configuration.currentBucket, client]
|
||||
);
|
||||
|
||||
const upload = useCallback(
|
||||
async (file: File, bucket: string) => {
|
||||
if (!client.current) {
|
||||
throw new Error("S3 not ready");
|
||||
throw new Error('S3 not ready');
|
||||
}
|
||||
|
||||
const fileParts = file.name.split('.');
|
||||
@ -47,8 +47,8 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
|
||||
Bucket: bucket,
|
||||
Key: `${window.ship}/${timestamp}-${fileName}.${fileExtension}`,
|
||||
Body: file,
|
||||
ACL: "public-read",
|
||||
ContentType: file.type,
|
||||
ACL: 'public-read',
|
||||
ContentType: file.type
|
||||
};
|
||||
|
||||
setUploading(true);
|
||||
@ -63,8 +63,8 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
|
||||
);
|
||||
|
||||
const uploadDefault = useCallback(async (file: File) => {
|
||||
if (s3.configuration.currentBucket === "") {
|
||||
throw new Error("current bucket not set");
|
||||
if (s3.configuration.currentBucket === '') {
|
||||
throw new Error('current bucket not set');
|
||||
}
|
||||
return upload(file, s3.configuration.currentBucket);
|
||||
}, [s3]);
|
||||
@ -84,11 +84,10 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
|
||||
}
|
||||
uploadDefault(files[0]).then(resolve);
|
||||
document.body.removeChild(fileSelector);
|
||||
})
|
||||
});
|
||||
document.body.appendChild(fileSelector);
|
||||
fileSelector.click();
|
||||
})
|
||||
|
||||
});
|
||||
},
|
||||
[uploadDefault]
|
||||
);
|
||||
@ -96,4 +95,4 @@ const useS3 = (s3: S3State, { accept = '*' } = { accept: '*' }): IuseS3 => {
|
||||
return { canUpload, upload, uploadDefault, uploading, promptUpload };
|
||||
};
|
||||
|
||||
export default useS3;
|
||||
export default useS3;
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { MouseEvent, useCallback, useState, useEffect } from "react";
|
||||
export type AsyncClickableState = "waiting" | "error" | "loading" | "success";
|
||||
import { MouseEvent, useCallback, useState, useEffect } from 'react';
|
||||
export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success';
|
||||
|
||||
export function useStatelessAsyncClickable(
|
||||
onClick: (e: MouseEvent) => Promise<void>,
|
||||
name: string
|
||||
) {
|
||||
const [state, setState] = useState<ButtonState>("waiting");
|
||||
const [state, setState] = useState<ButtonState>('waiting');
|
||||
const handleClick = useCallback(
|
||||
async (e: MouseEvent) => {
|
||||
try {
|
||||
setState("loading");
|
||||
setState('loading');
|
||||
await onClick(e);
|
||||
setState("success");
|
||||
setState('success');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setState("error");
|
||||
setState('error');
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setState("waiting");
|
||||
setState('waiting');
|
||||
}, 3000);
|
||||
}
|
||||
},
|
||||
@ -26,7 +26,7 @@ export function useStatelessAsyncClickable(
|
||||
|
||||
// When name changes, reset button
|
||||
useEffect(() => {
|
||||
setState("waiting");
|
||||
setState('waiting');
|
||||
}, [name]);
|
||||
|
||||
return { buttonState: state, onClick: handleClick };
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
|
||||
export function useWaitForProps<P>(props: P, timeout: number = 0) {
|
||||
export function useWaitForProps<P>(props: P, timeout = 0) {
|
||||
const [resolve, setResolve] = useState<() => void>(() => () => {});
|
||||
const [ready, setReady] = useState<(p: P) => boolean | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof ready === "function" && ready(props)) {
|
||||
if (typeof ready === 'function' && ready(props)) {
|
||||
resolve();
|
||||
}
|
||||
}, [props, ready, resolve]);
|
||||
@ -26,7 +25,7 @@ export function useWaitForProps<P>(props: P, timeout: number = 0) {
|
||||
setResolve(() => resolve);
|
||||
if(timeout > 0) {
|
||||
setTimeout(() => {
|
||||
reject(new Error("Timed out"));
|
||||
reject(new Error('Timed out'));
|
||||
}, timeout);
|
||||
}
|
||||
});
|
||||
|
@ -1,27 +1,27 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import _ from "lodash";
|
||||
import f, { memoize } from "lodash/fp";
|
||||
import bigInt, { BigInteger } from "big-integer";
|
||||
import { Contact } from '~/types';
|
||||
import _ from 'lodash';
|
||||
import f, { memoize } from 'lodash/fp';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
import { Contact } from '@urbit/api';
|
||||
import useLocalState from '../state/local';
|
||||
|
||||
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
|
||||
|
||||
export const MOMENT_CALENDAR_DATE = {
|
||||
sameDay: "[Today]",
|
||||
nextDay: "[Tomorrow]",
|
||||
nextWeek: "dddd",
|
||||
lastDay: "[Yesterday]",
|
||||
lastWeek: "[Last] dddd",
|
||||
sameElse: "~YYYY.M.D",
|
||||
sameDay: '[Today]',
|
||||
nextDay: '[Tomorrow]',
|
||||
nextWeek: 'dddd',
|
||||
lastDay: '[Yesterday]',
|
||||
lastWeek: '[Last] dddd',
|
||||
sameElse: '~YYYY.M.D'
|
||||
};
|
||||
|
||||
export const getModuleIcon = (mod: string) => {
|
||||
if (mod === "link") {
|
||||
return "Collection";
|
||||
if (mod === 'link') {
|
||||
return 'Collection';
|
||||
}
|
||||
return _.capitalize(mod);
|
||||
}
|
||||
};
|
||||
|
||||
export function wait(ms: number) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -37,8 +37,8 @@ export function parentPath(path: string) {
|
||||
return _.dropRight(path.split('/'), 1).join('/');
|
||||
}
|
||||
|
||||
const DA_UNIX_EPOCH = bigInt("170141184475152167957503069145530368000"); // `@ud` ~1970.1.1
|
||||
const DA_SECOND = bigInt("18446744073709551616"); // `@ud` ~s1
|
||||
const DA_UNIX_EPOCH = bigInt('170141184475152167957503069145530368000'); // `@ud` ~1970.1.1
|
||||
const DA_SECOND = bigInt('18446744073709551616'); // `@ud` ~s1
|
||||
export function daToUnix(da: BigInteger) {
|
||||
// ported from +time:enjs:format in hoon.hoon
|
||||
const offset = DA_SECOND.divide(bigInt(2000));
|
||||
@ -59,20 +59,20 @@ export function makePatDa(patda: string) {
|
||||
}
|
||||
|
||||
export function udToDec(ud: string): string {
|
||||
return ud.replace(/\./g, "");
|
||||
return ud.replace(/\./g, '');
|
||||
}
|
||||
|
||||
export function decToUd(str: string): string {
|
||||
return _.trimStart(
|
||||
f.flow(
|
||||
f.split(""),
|
||||
f.split(''),
|
||||
f.reverse,
|
||||
f.chunk(3),
|
||||
f.map(f.flow(f.reverse, f.join(""))),
|
||||
f.map(f.flow(f.reverse, f.join(''))),
|
||||
f.reverse,
|
||||
f.join(".")
|
||||
f.join('.')
|
||||
)(str),
|
||||
"0."
|
||||
'0.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,12 +86,12 @@ export function clamp(x: number, min: number, max: number) {
|
||||
// color is a #000000 color
|
||||
export function adjustHex(color: string, amount: number): string {
|
||||
return f.flow(
|
||||
f.split(""),
|
||||
f.split(''),
|
||||
f.chunk(2), // get RGB channels
|
||||
f.map((c) => parseInt(c.join(""), 16)), // as hex
|
||||
f.map((c) => clamp(c + amount, 0, 255).toString(16)), // adjust
|
||||
f.join(""),
|
||||
(res) => `#${res}` //format
|
||||
f.map(c => parseInt(c.join(''), 16)), // as hex
|
||||
f.map(c => clamp(c + amount, 0, 255).toString(16)), // adjust
|
||||
f.join(''),
|
||||
res => `#${res}` // format
|
||||
)(color.slice(1));
|
||||
}
|
||||
|
||||
@ -101,12 +101,12 @@ export function resourceAsPath(resource: any) {
|
||||
}
|
||||
|
||||
export function uuid() {
|
||||
let str = "0v";
|
||||
str += Math.ceil(Math.random() * 8) + ".";
|
||||
let str = '0v';
|
||||
str += Math.ceil(Math.random() * 8) + '.';
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let _str = Math.ceil(Math.random() * 10000000).toString(32);
|
||||
_str = ("00000" + _str).substr(-5, 5);
|
||||
str += _str + ".";
|
||||
_str = ('00000' + _str).substr(-5, 5);
|
||||
str += _str + '.';
|
||||
}
|
||||
|
||||
return str.slice(0, -1);
|
||||
@ -120,11 +120,11 @@ export function uuid() {
|
||||
*/
|
||||
export function daToDate(st: string) {
|
||||
const dub = function (n: string) {
|
||||
return parseInt(n) < 10 ? "0" + parseInt(n) : n.toString();
|
||||
return parseInt(n) < 10 ? '0' + parseInt(n) : n.toString();
|
||||
};
|
||||
const da = st.split("..");
|
||||
const bigEnd = da[0].split(".");
|
||||
const lilEnd = da[1].split(".");
|
||||
const da = st.split('..');
|
||||
const bigEnd = da[0].split('.');
|
||||
const lilEnd = da[1].split('.');
|
||||
const ds = `${bigEnd[0].slice(1)}-${dub(bigEnd[1])}-${dub(bigEnd[2])}T${dub(
|
||||
lilEnd[0]
|
||||
)}:${dub(lilEnd[1])}:${dub(lilEnd[2])}Z`;
|
||||
@ -138,9 +138,9 @@ export function daToDate(st: string) {
|
||||
~2018.7.17..23.15.09..5be5 // urbit @da
|
||||
*/
|
||||
|
||||
export function dateToDa(d: Date, mil: boolean = false) {
|
||||
export function dateToDa(d: Date, mil = false) {
|
||||
const fil = function (n: number) {
|
||||
return n >= 10 ? n : "0" + n;
|
||||
return n >= 10 ? n : '0' + n;
|
||||
};
|
||||
return (
|
||||
`~${d.getUTCFullYear()}.` +
|
||||
@ -149,7 +149,7 @@ export function dateToDa(d: Date, mil: boolean = false) {
|
||||
`${fil(d.getUTCHours())}.` +
|
||||
`${fil(d.getUTCMinutes())}.` +
|
||||
`${fil(d.getUTCSeconds())}` +
|
||||
`${mil ? "..0000" : ""}`
|
||||
`${mil ? '..0000' : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,16 +157,16 @@ export function deSig(ship: string) {
|
||||
if (!ship) {
|
||||
return null;
|
||||
}
|
||||
return ship.replace("~", "");
|
||||
return ship.replace('~', '');
|
||||
}
|
||||
|
||||
export function uxToHex(ux: string) {
|
||||
if (ux.length > 2 && ux.substr(0, 2) === "0x") {
|
||||
const value = ux.substr(2).replace(".", "").padStart(6, "0");
|
||||
if (ux.length > 2 && ux.substr(0, 2) === '0x') {
|
||||
const value = ux.substr(2).replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
const value = ux.replace(".", "").padStart(6, "0");
|
||||
const value = ux.replace('.', '').padStart(6, '0');
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -187,13 +187,13 @@ export function writeText(str: string) {
|
||||
|
||||
let success = false;
|
||||
function listener(e) {
|
||||
e.clipboardData.setData("text/plain", str);
|
||||
e.clipboardData.setData('text/plain', str);
|
||||
e.preventDefault();
|
||||
success = true;
|
||||
}
|
||||
document.addEventListener("copy", listener);
|
||||
document.execCommand("copy");
|
||||
document.removeEventListener("copy", listener);
|
||||
document.addEventListener('copy', listener);
|
||||
document.execCommand('copy');
|
||||
document.removeEventListener('copy', listener);
|
||||
|
||||
document?.getSelection()?.removeAllRanges();
|
||||
|
||||
@ -206,21 +206,21 @@ export function writeText(str: string) {
|
||||
// trim patps to match dojo, chat-cli
|
||||
export function cite(ship: string) {
|
||||
let patp = ship,
|
||||
shortened = "";
|
||||
if (patp === null || patp === "") {
|
||||
shortened = '';
|
||||
if (patp === null || patp === '') {
|
||||
return null;
|
||||
}
|
||||
if (patp.startsWith("~")) {
|
||||
if (patp.startsWith('~')) {
|
||||
patp = patp.substr(1);
|
||||
}
|
||||
// comet
|
||||
if (patp.length === 56) {
|
||||
shortened = "~" + patp.slice(0, 6) + "_" + patp.slice(50, 56);
|
||||
shortened = '~' + patp.slice(0, 6) + '_' + patp.slice(50, 56);
|
||||
return shortened;
|
||||
}
|
||||
// moon
|
||||
if (patp.length === 27) {
|
||||
shortened = "~" + patp.slice(14, 20) + "^" + patp.slice(21, 27);
|
||||
shortened = '~' + patp.slice(14, 20) + '^' + patp.slice(21, 27);
|
||||
return shortened;
|
||||
}
|
||||
return `~${patp}`;
|
||||
@ -232,7 +232,6 @@ export function alphabeticalOrder(a: string, b: string) {
|
||||
|
||||
export function lengthOrder(a: string, b: string) {
|
||||
return b.length - a.length;
|
||||
|
||||
}
|
||||
|
||||
// TODO: deprecated
|
||||
@ -244,13 +243,13 @@ export function alphabetiseAssociations(associations: any) {
|
||||
let bName = b.substr(1);
|
||||
if (associations[a].metadata && associations[a].metadata.title) {
|
||||
aName =
|
||||
associations[a].metadata.title !== ""
|
||||
associations[a].metadata.title !== ''
|
||||
? associations[a].metadata.title
|
||||
: a.substr(1);
|
||||
}
|
||||
if (associations[b].metadata && associations[b].metadata.title) {
|
||||
bName =
|
||||
associations[b].metadata.title !== ""
|
||||
associations[b].metadata.title !== ''
|
||||
? associations[b].metadata.title
|
||||
: b.substr(1);
|
||||
}
|
||||
@ -266,41 +265,42 @@ export function alphabetiseAssociations(associations: any) {
|
||||
// for example, 'some Chars!' becomes '~.some.~43.hars~21.'
|
||||
//
|
||||
export function stringToTa(str: string) {
|
||||
let out = "";
|
||||
let out = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str[i];
|
||||
let add = "";
|
||||
let add = '';
|
||||
switch (char) {
|
||||
case " ":
|
||||
add = ".";
|
||||
case ' ':
|
||||
add = '.';
|
||||
break;
|
||||
case ".":
|
||||
add = "~.";
|
||||
case '.':
|
||||
add = '~.';
|
||||
break;
|
||||
case "~":
|
||||
add = "~~";
|
||||
case '~':
|
||||
add = '~~';
|
||||
break;
|
||||
default:
|
||||
const charCode = str.charCodeAt(i);
|
||||
if (
|
||||
(charCode >= 97 && charCode <= 122) || // a-z
|
||||
(charCode >= 48 && charCode <= 57) || // 0-9
|
||||
char === "-"
|
||||
char === '-'
|
||||
) {
|
||||
add = char;
|
||||
} else {
|
||||
// TODO behavior for unicode doesn't match +wood's,
|
||||
// but we can probably get away with that for now.
|
||||
add = "~" + charCode.toString(16) + ".";
|
||||
add = '~' + charCode.toString(16) + '.';
|
||||
}
|
||||
}
|
||||
out = out + add;
|
||||
}
|
||||
return "~." + out;
|
||||
return '~.' + out;
|
||||
}
|
||||
|
||||
export function amOwnerOfGroup(groupPath: string) {
|
||||
if (!groupPath) return false;
|
||||
if (!groupPath)
|
||||
return false;
|
||||
const groupOwner = /(\/~)?\/~([a-z-]{3,})\/.*/.exec(groupPath)?.[2];
|
||||
return window.ship === groupOwner;
|
||||
}
|
||||
@ -308,18 +308,18 @@ export function amOwnerOfGroup(groupPath: string) {
|
||||
export function getContactDetails(contact: any) {
|
||||
const member = !contact;
|
||||
contact = contact || {
|
||||
nickname: "",
|
||||
nickname: '',
|
||||
avatar: null,
|
||||
color: "0x0",
|
||||
color: '0x0'
|
||||
};
|
||||
const nickname = contact.nickname || "";
|
||||
const color = uxToHex(contact.color || "0x0");
|
||||
const nickname = contact.nickname || '';
|
||||
const color = uxToHex(contact.color || '0x0');
|
||||
const avatar = contact.avatar || null;
|
||||
return { nickname, color, member, avatar };
|
||||
}
|
||||
|
||||
export function stringToSymbol(str: string) {
|
||||
let result = "";
|
||||
let result = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const n = str.charCodeAt(i);
|
||||
if ((n >= 97 && n <= 122) || (n >= 48 && n <= 57)) {
|
||||
@ -327,19 +327,17 @@ export function stringToSymbol(str: string) {
|
||||
} else if (n >= 65 && n <= 90) {
|
||||
result += String.fromCharCode(n + 32);
|
||||
} else {
|
||||
result += "-";
|
||||
result += '-';
|
||||
}
|
||||
}
|
||||
result = result.replace(/^[\-\d]+|\-+/g, "-");
|
||||
result = result.replace(/^\-+|\-+$/g, "");
|
||||
if (result === "") {
|
||||
result = result.replace(/^[\-\d]+|\-+/g, '-');
|
||||
result = result.replace(/^\-+|\-+$/g, '');
|
||||
if (result === '') {
|
||||
return dateToDa(new Date());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formats a numbers as a `@ud` inserting dot where needed
|
||||
*/
|
||||
@ -351,23 +349,24 @@ export function numToUd(num: number) {
|
||||
f.reverse,
|
||||
f.map(s => s.join('')),
|
||||
f.join('.')
|
||||
)(num.toString())
|
||||
)(num.toString());
|
||||
}
|
||||
|
||||
export function usePreventWindowUnload(shouldPreventDefault: boolean, message = "You have unsaved changes. Are you sure you want to exit?") {
|
||||
export function usePreventWindowUnload(shouldPreventDefault: boolean, message = 'You have unsaved changes. Are you sure you want to exit?') {
|
||||
useEffect(() => {
|
||||
if (!shouldPreventDefault) return;
|
||||
const handleBeforeUnload = event => {
|
||||
if (!shouldPreventDefault)
|
||||
return;
|
||||
const handleBeforeUnload = (event) => {
|
||||
event.preventDefault();
|
||||
return message;
|
||||
}
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
};
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
window.onbeforeunload = handleBeforeUnload;
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
// @ts-ignore
|
||||
window.onbeforeunload = undefined;
|
||||
}
|
||||
};
|
||||
}, [shouldPreventDefault]);
|
||||
}
|
||||
|
||||
@ -378,7 +377,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
|
||||
// Hide is an optional second parameter for when this function is used in class components
|
||||
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
|
||||
const hideNicknames = typeof hide !== 'undefined' ? hide : useLocalState(state => state.hideNicknames);
|
||||
return !!(contact && contact.nickname && !hideNicknames);
|
||||
return Boolean(contact && contact.nickname && !hideNicknames);
|
||||
}
|
||||
|
||||
interface useHoveringInterface {
|
||||
@ -406,7 +405,6 @@ export function getItemTitle(association: Association) {
|
||||
return cite(`~${name.slice(4)}`);
|
||||
}
|
||||
return cite(ship);
|
||||
|
||||
}
|
||||
return association.metadata.title || association.resource
|
||||
};
|
||||
return association.metadata.title || association.resource;
|
||||
}
|
||||
|
@ -1,24 +1,24 @@
|
||||
import { Associations, Workspace } from "~/types";
|
||||
import { Associations, Workspace } from '@urbit/api';
|
||||
|
||||
export function getTitleFromWorkspace(
|
||||
associations: Associations,
|
||||
workspace: Workspace
|
||||
) {
|
||||
switch (workspace.type) {
|
||||
case "home":
|
||||
return "My Channels";
|
||||
case "messages":
|
||||
return "Messages";
|
||||
case "group":
|
||||
case 'home':
|
||||
return 'My Channels';
|
||||
case 'messages':
|
||||
return 'Messages';
|
||||
case 'group':
|
||||
const association = associations.groups[workspace.group];
|
||||
return association?.metadata?.title || "";
|
||||
return association?.metadata?.title || '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getGroupFromWorkspace(
|
||||
workspace: Workspace
|
||||
): string | undefined {
|
||||
if (workspace.type === "group") {
|
||||
if (workspace.type === 'group') {
|
||||
return workspace.group;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { ContactUpdate } from '~/types/contact-update';
|
||||
import {resourceAsPath} from '../lib/util';
|
||||
import { ContactUpdate } from '@urbit/api/contacts';
|
||||
import { resourceAsPath } from '../lib/util';
|
||||
|
||||
type ContactState = Pick<StoreState, 'contacts'>;
|
||||
|
||||
@ -62,9 +62,9 @@ const edit = (json: ContactUpdate, state: S) => {
|
||||
const contact = state.contacts?.[ship];
|
||||
const value = data['edit-field'][field];
|
||||
if(!contact) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(field === 'add-group') {
|
||||
contact.groups.push(value);
|
||||
} else if (field === 'remove-group') {
|
||||
@ -80,4 +80,3 @@ const setPublic = (json: ContactUpdate, state: S) => {
|
||||
state.isContactPublic = data;
|
||||
};
|
||||
|
||||
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
OpenPolicyDiff,
|
||||
OpenPolicy,
|
||||
InvitePolicyDiff,
|
||||
InvitePolicy,
|
||||
} from '~/types/group-update';
|
||||
import { Enc, PatpNoSig } from '~/types/noun';
|
||||
InvitePolicy
|
||||
} from '@urbit/api/groups';
|
||||
import { Enc, PatpNoSig } from '@urbit/api';
|
||||
import { resourceAsPath } from '../lib/util';
|
||||
|
||||
type GroupState = Pick<StoreState, 'groups' | 'groupKeys'>;
|
||||
@ -23,7 +23,7 @@ function decodeGroup(group: Enc<Group>): Group {
|
||||
...group,
|
||||
members,
|
||||
tags: decodeTags(group.tags),
|
||||
policy: decodePolicy(group.policy),
|
||||
policy: decodePolicy(group.policy)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
@ -35,7 +35,7 @@ function decodePolicy(policy: Enc<GroupPolicy>): GroupPolicy {
|
||||
} else {
|
||||
const { open } = policy;
|
||||
return {
|
||||
open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) },
|
||||
open: { banned: new Set(open.banned), banRanks: new Set(open.banRanks) }
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -98,7 +98,7 @@ export default class GroupReducer<S extends GroupState> {
|
||||
members: new Set(),
|
||||
tags: { role: { admin: new Set([window.ship]) } },
|
||||
policy: decodePolicy(policy),
|
||||
hidden,
|
||||
hidden
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -189,7 +189,6 @@ export default class GroupReducer<S extends GroupState> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private inviteChangePolicy(diff: InvitePolicyDiff, policy: InvitePolicy) {
|
||||
if ('addInvites' in diff) {
|
||||
const { addInvites } = diff;
|
||||
|
@ -1,25 +1,24 @@
|
||||
import { resourceAsPath } from "~/logic/lib/util";
|
||||
|
||||
import { resourceAsPath } from '~/logic/lib/util';
|
||||
|
||||
const initial = (json: any, state: any) => {
|
||||
const data = json.initial;
|
||||
if(data) {
|
||||
state.pendingJoin = data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const progress = (json: any, state: any) => {
|
||||
const data = json.progress;
|
||||
if(data) {
|
||||
const { progress, resource } = data;
|
||||
state.pendingJoin = {...state.pendingJoin, [resource]: progress };
|
||||
state.pendingJoin = { ...state.pendingJoin, [resource]: progress };
|
||||
if(progress === 'done') {
|
||||
setTimeout(() => {
|
||||
delete state.pendingJoin[resource];
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const GroupViewReducer = (json: any, state: any) => {
|
||||
const data = json['group-view-update'];
|
||||
@ -27,4 +26,4 @@ export const GroupViewReducer = (json: any, state: any) => {
|
||||
progress(data, state);
|
||||
initial(data, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -3,22 +3,21 @@ import {
|
||||
NotifIndex,
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
UnreadStats,
|
||||
} from "~/types";
|
||||
import { makePatDa } from "~/logic/lib/util";
|
||||
import _ from "lodash";
|
||||
import {StoreState} from "../store/type";
|
||||
UnreadStats
|
||||
} from '@urbit/api';
|
||||
import { makePatDa } from '~/logic/lib/util';
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../store/type';
|
||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||
|
||||
type HarkState = Pick<StoreState, "notifications" | "notificationsGraphConfig" | "notificationsGroupConfig" | "unreads" >;
|
||||
|
||||
type HarkState = Pick<StoreState, 'notifications' | 'notificationsGraphConfig' | 'notificationsGroupConfig' | 'unreads' >;
|
||||
|
||||
export const HarkReducer = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "harkUpdate", false);
|
||||
const data = _.get(json, 'harkUpdate', false);
|
||||
if (data) {
|
||||
reduce(data, state);
|
||||
}
|
||||
const graphHookData = _.get(json, "hark-graph-hook-update", false);
|
||||
const graphHookData = _.get(json, 'hark-graph-hook-update', false);
|
||||
if (graphHookData) {
|
||||
graphInitial(graphHookData, state);
|
||||
graphIgnore(graphHookData, state);
|
||||
@ -26,7 +25,7 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
||||
graphWatchSelf(graphHookData, state);
|
||||
graphMentions(graphHookData, state);
|
||||
}
|
||||
const groupHookData = _.get(json, "hark-group-hook-update", false);
|
||||
const groupHookData = _.get(json, 'hark-group-hook-update', false);
|
||||
if (groupHookData) {
|
||||
groupInitial(groupHookData, state);
|
||||
groupListen(groupHookData, state);
|
||||
@ -35,31 +34,31 @@ export const HarkReducer = (json: any, state: HarkState) => {
|
||||
};
|
||||
|
||||
function groupInitial(json: any, state: HarkState) {
|
||||
const data = _.get(json, "initial", false);
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphInitial(json: any, state: HarkState) {
|
||||
const data = _.get(json, "initial", false);
|
||||
const data = _.get(json, 'initial', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphListen(json: any, state: HarkState) {
|
||||
const data = _.get(json, "listen", false);
|
||||
const data = _.get(json, 'listen', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig.watching = [
|
||||
...state.notificationsGraphConfig.watching,
|
||||
data,
|
||||
data
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function graphIgnore(json: any, state: HarkState) {
|
||||
const data = _.get(json, "ignore", false);
|
||||
const data = _.get(json, 'ignore', false);
|
||||
if (data) {
|
||||
state.notificationsGraphConfig.watching = state.notificationsGraphConfig.watching.filter(
|
||||
({ graph, index }) => !(graph === data.graph && index === data.index)
|
||||
@ -68,30 +67,30 @@ function graphIgnore(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function groupListen(json: any, state: HarkState) {
|
||||
const data = _.get(json, "listen", false);
|
||||
const data = _.get(json, 'listen', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = [...state.notificationsGroupConfig, data];
|
||||
}
|
||||
}
|
||||
|
||||
function groupIgnore(json: any, state: HarkState) {
|
||||
const data = _.get(json, "ignore", false);
|
||||
const data = _.get(json, 'ignore', false);
|
||||
if (data) {
|
||||
state.notificationsGroupConfig = state.notificationsGroupConfig.filter(
|
||||
(n) => n !== data
|
||||
n => n !== data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function graphMentions(json: any, state: HarkState) {
|
||||
const data = _.get(json, "set-mentions", undefined);
|
||||
const data = _.get(json, 'set-mentions', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.notificationsGraphConfig.mentions = data;
|
||||
}
|
||||
}
|
||||
|
||||
function graphWatchSelf(json: any, state: HarkState) {
|
||||
const data = _.get(json, "set-watch-on-self", undefined);
|
||||
const data = _.get(json, 'set-watch-on-self', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.notificationsGraphConfig.watchOnSelf = data;
|
||||
}
|
||||
@ -131,14 +130,14 @@ function seenIndex(json: any, state: HarkState) {
|
||||
function readEach(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'read-each');
|
||||
if(data) {
|
||||
updateUnreads(state, data.index, u => u.delete(data.target))
|
||||
updateUnreads(state, data.index, u => u.delete(data.target));
|
||||
}
|
||||
}
|
||||
|
||||
function readSince(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'read-count');
|
||||
if(data) {
|
||||
updateUnreadCount(state, data, () => 0)
|
||||
updateUnreadCount(state, data, () => 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +151,7 @@ function unreadSince(json: any, state: HarkState) {
|
||||
function unreadEach(json: any, state: HarkState) {
|
||||
const data = _.get(json, 'unread-each');
|
||||
if(data) {
|
||||
updateUnreads(state, data.index, us => us.add(data.target))
|
||||
updateUnreads(state, data.index, us => us.add(data.target));
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,15 +174,15 @@ function unreads(json: any, state: HarkState) {
|
||||
}
|
||||
}
|
||||
|
||||
function clearState(state){
|
||||
let initialState = {
|
||||
function clearState(state) {
|
||||
const initialState = {
|
||||
notifications: new BigIntOrderedMap<Timebox>(),
|
||||
archivedNotifications: new BigIntOrderedMap<Timebox>(),
|
||||
notificationsGroupConfig: [],
|
||||
notificationsGraphConfig: {
|
||||
watchOnSelf: false,
|
||||
mentions: false,
|
||||
watching: [],
|
||||
watching: []
|
||||
},
|
||||
unreads: {
|
||||
graph: {},
|
||||
@ -192,7 +191,7 @@ function clearState(state){
|
||||
notificationsCount: 0
|
||||
};
|
||||
|
||||
Object.keys(initialState).forEach(key => {
|
||||
Object.keys(initialState).forEach((key) => {
|
||||
state[key] = initialState[key];
|
||||
});
|
||||
}
|
||||
@ -203,7 +202,7 @@ function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: numbe
|
||||
}
|
||||
const property = [index.graph.graph, index.graph.index, 'unreads'];
|
||||
const curr = _.get(state.unreads.graph, property, 0);
|
||||
const newCount = count(curr)
|
||||
const newCount = count(curr);
|
||||
_.set(state.unreads.graph, property, newCount);
|
||||
}
|
||||
|
||||
@ -218,7 +217,6 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>)
|
||||
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
|
||||
}
|
||||
|
||||
|
||||
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number) {
|
||||
if(statField === 'notifications') {
|
||||
state.notificationsCount = f(state.notificationsCount);
|
||||
@ -233,13 +231,13 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField:
|
||||
}
|
||||
|
||||
function added(json: any, state: HarkState) {
|
||||
const data = _.get(json, "added", false);
|
||||
const data = _.get(json, 'added', false);
|
||||
if (data) {
|
||||
const { index, notification } = data;
|
||||
const time = makePatDa(data.time);
|
||||
const timebox = state.notifications.get(time) || [];
|
||||
|
||||
const arrIdx = timebox.findIndex((idxNotif) =>
|
||||
const arrIdx = timebox.findIndex(idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if (arrIdx !== -1) {
|
||||
@ -256,14 +254,14 @@ function added(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
const dnd = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "set-dnd", undefined);
|
||||
const data = _.get(json, 'set-dnd', undefined);
|
||||
if (!_.isUndefined(data)) {
|
||||
state.doNotDisturb = data;
|
||||
}
|
||||
};
|
||||
|
||||
const timebox = (json: any, state: HarkState) => {
|
||||
const data = _.get(json, "timebox", false);
|
||||
const data = _.get(json, 'timebox', false);
|
||||
if (data) {
|
||||
const time = makePatDa(data.time);
|
||||
if (!data.archive) {
|
||||
@ -273,21 +271,21 @@ const timebox = (json: any, state: HarkState) => {
|
||||
};
|
||||
|
||||
function more(json: any, state: HarkState) {
|
||||
const data = _.get(json, "more", false);
|
||||
const data = _.get(json, 'more', false);
|
||||
if (data) {
|
||||
_.forEach(data, (d) => reduce(d, state));
|
||||
_.forEach(data, d => reduce(d, state));
|
||||
}
|
||||
}
|
||||
|
||||
function notifIdxEqual(a: NotifIndex, b: NotifIndex) {
|
||||
if ("graph" in a && "graph" in b) {
|
||||
if ('graph' in a && 'graph' in b) {
|
||||
return (
|
||||
a.graph.graph === b.graph.graph &&
|
||||
a.graph.group === b.graph.group &&
|
||||
a.graph.module === b.graph.module &&
|
||||
a.graph.description === b.graph.description
|
||||
);
|
||||
} else if ("group" in a && "group" in b) {
|
||||
} else if ('group' in a && 'group' in b) {
|
||||
return (
|
||||
a.group.group === b.group.group &&
|
||||
a.group.description === b.group.description
|
||||
@ -305,14 +303,14 @@ function setRead(
|
||||
const patDa = makePatDa(time);
|
||||
const timebox = state.notifications.get(patDa);
|
||||
if (_.isNull(timebox)) {
|
||||
console.warn("Modifying nonexistent timebox");
|
||||
console.warn('Modifying nonexistent timebox');
|
||||
return;
|
||||
}
|
||||
const arrIdx = timebox.findIndex((idxNotif) =>
|
||||
const arrIdx = timebox.findIndex(idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if (arrIdx === -1) {
|
||||
console.warn("Modifying nonexistent index");
|
||||
console.warn('Modifying nonexistent index');
|
||||
return;
|
||||
}
|
||||
timebox[arrIdx].notification.read = read;
|
||||
@ -320,7 +318,7 @@ function setRead(
|
||||
}
|
||||
|
||||
function read(json: any, state: HarkState) {
|
||||
const data = _.get(json, "read-note", false);
|
||||
const data = _.get(json, 'read-note', false);
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
updateNotificationStats(state, index, 'notifications', x => x-1);
|
||||
@ -329,7 +327,7 @@ function read(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function unread(json: any, state: HarkState) {
|
||||
const data = _.get(json, "unread-note", false);
|
||||
const data = _.get(json, 'unread-note', false);
|
||||
if (data) {
|
||||
const { time, index } = data;
|
||||
updateNotificationStats(state, index, 'notifications', x => x+1);
|
||||
@ -338,16 +336,16 @@ function unread(json: any, state: HarkState) {
|
||||
}
|
||||
|
||||
function archive(json: any, state: HarkState) {
|
||||
const data = _.get(json, "archive", false);
|
||||
const data = _.get(json, 'archive', false);
|
||||
if (data) {
|
||||
const { index } = data;
|
||||
const time = makePatDa(data.time);
|
||||
const timebox = state.notifications.get(time);
|
||||
if (!timebox) {
|
||||
console.warn("Modifying nonexistent timebox");
|
||||
console.warn('Modifying nonexistent timebox');
|
||||
return;
|
||||
}
|
||||
const [archived, unarchived] = _.partition(timebox, (idxNotif) =>
|
||||
const [archived, unarchived] = _.partition(timebox, idxNotif =>
|
||||
notifIdxEqual(index, idxNotif.index)
|
||||
);
|
||||
if(unarchived.length === 0) {
|
||||
@ -357,6 +355,6 @@ function archive(json: any, state: HarkState) {
|
||||
state.notifications.set(time, unarchived);
|
||||
}
|
||||
const newlyRead = archived.filter(x => !x.notification.read).length;
|
||||
updateNotificationStats(state, index, 'notifications', (x) => x - newlyRead);
|
||||
updateNotificationStats(state, index, 'notifications', x => x - newlyRead);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import { Cage } from '~/types/cage';
|
||||
import { InviteUpdate } from '~/types/invite-update';
|
||||
|
||||
type InviteState = Pick<StoreState, "invites">;
|
||||
import { InviteUpdate } from '@urbit/api/invite';
|
||||
|
||||
type InviteState = Pick<StoreState, 'invites'>;
|
||||
|
||||
export default class InviteReducer<S extends InviteState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
|
@ -51,12 +51,11 @@ export default class LaunchReducer<S extends LaunchState> {
|
||||
changeIsShown(json: LaunchUpdate, state: S) {
|
||||
const data = _.get(json, 'changeIsShown', false);
|
||||
if (data) {
|
||||
let tile = state.launch.tiles[data.name];
|
||||
const tile = state.launch.tiles[data.name];
|
||||
console.log(tile);
|
||||
if (tile) {
|
||||
tile.isShown = data.isShown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,14 +2,14 @@ import _ from 'lodash';
|
||||
|
||||
import { StoreState } from '../../store/type';
|
||||
|
||||
import { MetadataUpdate } from '~/types/metadata-update';
|
||||
import { MetadataUpdate } from '@urbit/api/metadata';
|
||||
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']
|
||||
const data = json['metadata-update'];
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.associations(data, state);
|
||||
@ -29,13 +29,13 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
associations(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'associations', false);
|
||||
const data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
const metadata = state.associations;
|
||||
Object.keys(data).forEach((key) => {
|
||||
let val = data[key];
|
||||
let appName = val['app-name'];
|
||||
let rid = val.resource;
|
||||
const val = data[key];
|
||||
const appName = val['app-name'];
|
||||
const rid = val.resource;
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
@ -50,11 +50,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
add(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'add', false);
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let appPath = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const appPath = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
@ -69,11 +69,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
update(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'update-metadata', false);
|
||||
const data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let rid = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
@ -88,11 +88,11 @@ export default class MetadataReducer<S extends MetadataState> {
|
||||
}
|
||||
|
||||
remove(json: MetadataUpdate, state: S) {
|
||||
let data = _.get(json, 'remove', false);
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
let metadata = state.associations;
|
||||
let appName = data['app-name'];
|
||||
let rid = data.resource;
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (appName in metadata && rid in metadata[appName]) {
|
||||
delete metadata[appName][rid];
|
||||
|
@ -1,21 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
import { StoreState } from '../../store/type';
|
||||
import {
|
||||
SettingsUpdate,
|
||||
} from '~/types/settings';
|
||||
SettingsUpdate
|
||||
} from '@urbit/api/settings';
|
||||
|
||||
type SettingsState = Pick<StoreState, 'settings'>;
|
||||
|
||||
export default class SettingsReducer<S extends SettingsState>{
|
||||
export default class SettingsReducer<S extends SettingsState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
let data = json["settings-event"];
|
||||
let data = json['settings-event'];
|
||||
if (data) {
|
||||
this.putBucket(data, state);
|
||||
this.delBucket(data, state);
|
||||
this.putEntry(data, state);
|
||||
this.delEntry(data, state);
|
||||
}
|
||||
data = json["settings-data"];
|
||||
data = json['settings-data'];
|
||||
if (data) {
|
||||
this.getAll(data, state);
|
||||
this.getBucket(data, state);
|
||||
@ -26,31 +26,31 @@ export default class SettingsReducer<S extends SettingsState>{
|
||||
putBucket(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'put-bucket', false);
|
||||
if (data) {
|
||||
state.settings[data["bucket-key"]] = data.bucket;
|
||||
state.settings[data['bucket-key']] = data.bucket;
|
||||
}
|
||||
}
|
||||
|
||||
delBucket(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'del-bucket', false);
|
||||
if (data) {
|
||||
delete state.settings[data["bucket-key"]];
|
||||
delete state.settings[data['bucket-key']];
|
||||
}
|
||||
}
|
||||
|
||||
putEntry(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'put-entry', false);
|
||||
if (data) {
|
||||
if (!state.settings[data["bucket-key"]]) {
|
||||
state.settings[data["bucket-key"]] = {};
|
||||
if (!state.settings[data['bucket-key']]) {
|
||||
state.settings[data['bucket-key']] = {};
|
||||
}
|
||||
state.settings[data["bucket-key"]][data["entry-key"]] = data.value;
|
||||
state.settings[data['bucket-key']][data['entry-key']] = data.value;
|
||||
}
|
||||
}
|
||||
|
||||
delEntry(json: SettingsUpdate, state: S) {
|
||||
const data = _.get(json, 'del-entry', false);
|
||||
if (data) {
|
||||
delete state.settings[data["bucket-key"]][data["entry-key"]];
|
||||
delete state.settings[data['bucket-key']][data['entry-key']];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { ReactNode } from 'react';
|
||||
import f from 'lodash/fp';
|
||||
import create, { State } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import produce from 'immer';
|
||||
import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update";
|
||||
|
||||
import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
|
||||
|
||||
export interface LocalState extends State {
|
||||
hideAvatars: boolean;
|
||||
@ -22,8 +21,8 @@ export interface LocalState extends State {
|
||||
suspendedFocus?: HTMLElement;
|
||||
toggleOmnibox: () => void;
|
||||
set: (fn: (state: LocalState) => void) => void
|
||||
};
|
||||
export const selectLocalState =
|
||||
}
|
||||
export const selectLocalState =
|
||||
<K extends keyof LocalState>(keys: K[]) => f.pick<LocalState, K>(keys);
|
||||
|
||||
const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
@ -33,21 +32,21 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
hideNicknames: false,
|
||||
tutorialProgress: 'hidden',
|
||||
tutorialRef: null,
|
||||
setTutorialRef: (el: HTMLElement | null) => set(produce(state => {
|
||||
setTutorialRef: (el: HTMLElement | null) => set(produce((state) => {
|
||||
state.tutorialRef = el;
|
||||
})),
|
||||
hideTutorial: () => set(produce(state => {
|
||||
hideTutorial: () => set(produce((state) => {
|
||||
state.tutorialProgress = 'hidden';
|
||||
state.tutorialRef = null;
|
||||
})),
|
||||
nextTutStep: () => set(produce(state => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
|
||||
nextTutStep: () => set(produce((state) => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
|
||||
if(currIdx < tutorialProgress.length) {
|
||||
state.tutorialProgress = tutorialProgress[currIdx + 1];
|
||||
}
|
||||
})),
|
||||
prevTutStep: () => set(produce(state => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
|
||||
prevTutStep: () => set(produce((state) => {
|
||||
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress);
|
||||
if(currIdx > 0) {
|
||||
state.tutorialProgress = tutorialProgress[currIdx - 1];
|
||||
}
|
||||
@ -56,11 +55,11 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
|
||||
imageShown: true,
|
||||
audioShown: true,
|
||||
videoShown: true,
|
||||
oembedShown: true,
|
||||
oembedShown: true
|
||||
},
|
||||
omniboxShown: false,
|
||||
suspendedFocus: undefined,
|
||||
toggleOmnibox: () => set(produce(state => {
|
||||
toggleOmnibox: () => set(produce((state) => {
|
||||
state.omniboxShown = !state.omniboxShown;
|
||||
if (typeof state.suspendedFocus?.focus === 'function') {
|
||||
state.suspendedFocus.focus();
|
||||
@ -86,7 +85,7 @@ function withLocalState<P, S extends keyof LocalState>(Component: any, stateMemb
|
||||
(object, key) => ({ ...object, [key]: state[key] }), {}
|
||||
)
|
||||
): useLocalState();
|
||||
return <Component ref={ref} {...localState} {...props} />
|
||||
return <Component ref={ref} {...localState} {...props} />;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ export default class BaseStore<S extends object> {
|
||||
|
||||
clear() {
|
||||
this.handleEvent({
|
||||
data: { clear: true },
|
||||
data: { clear: true }
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export default class BaseStore<S extends object> {
|
||||
return;
|
||||
}
|
||||
|
||||
if ("clear" in json && json.clear) {
|
||||
if ('clear' in json && json.clear) {
|
||||
this.setState(this.initialState());
|
||||
return;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import MetadataReducer from '../reducers/metadata-update';
|
||||
import LocalReducer from '../reducers/local';
|
||||
|
||||
import { StoreState } from './type';
|
||||
import { Timebox } from '~/types';
|
||||
import { Timebox } from '@urbit/api';
|
||||
import { Cage } from '~/types/cage';
|
||||
import S3Reducer from '../reducers/s3-update';
|
||||
import { GraphReducer } from '../reducers/graph-update';
|
||||
@ -16,10 +16,9 @@ import GroupReducer from '../reducers/group-update';
|
||||
import LaunchReducer from '../reducers/launch-update';
|
||||
import ConnectionReducer from '../reducers/connection';
|
||||
import SettingsReducer from '../reducers/settings-update';
|
||||
import {OrderedMap} from '../lib/OrderedMap';
|
||||
import { OrderedMap } from '../lib/OrderedMap';
|
||||
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
|
||||
import {GroupViewReducer} from '../reducers/group-view';
|
||||
|
||||
import { GroupViewReducer } from '../reducers/group-view';
|
||||
|
||||
export default class GlobalStore extends BaseStore<StoreState> {
|
||||
inviteReducer = new InviteReducer();
|
||||
@ -58,7 +57,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
invites: {},
|
||||
associations: {
|
||||
groups: {},
|
||||
graph: {},
|
||||
graph: {}
|
||||
},
|
||||
groups: {},
|
||||
groupKeys: new Set(),
|
||||
@ -67,7 +66,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
launch: {
|
||||
firstTime: false,
|
||||
tileOrdering: [],
|
||||
tiles: {},
|
||||
tiles: {}
|
||||
},
|
||||
weather: {},
|
||||
userLocation: null,
|
||||
@ -87,7 +86,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
notificationsGraphConfig: {
|
||||
watchOnSelf: false,
|
||||
mentions: false,
|
||||
watching: [],
|
||||
watching: []
|
||||
},
|
||||
unreads: {
|
||||
graph: {},
|
||||
@ -95,7 +94,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
},
|
||||
notificationsCount: 0,
|
||||
settings: {},
|
||||
pendingJoin: {},
|
||||
pendingJoin: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { Path } from '~/types/noun';
|
||||
import { Invites } from '~/types/invite-update';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
import { Rolodex } from '~/types/contact-update';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { Path } from '@urbit/api';
|
||||
import { Invites } from '@urbit/api/invite';
|
||||
import { Associations } from '@urbit/api/metadata';
|
||||
import { Rolodex } from '@urbit/api/contacts';
|
||||
import { Groups } from '@urbit/api/groups';
|
||||
import { S3State } from '~/types/s3-update';
|
||||
import { LaunchState, WeatherState } from '~/types/launch-update';
|
||||
import { ConnectionStatus } from '~/types/connection';
|
||||
import {Graphs} from '~/types/graph-update';
|
||||
import { Graphs } from '@urbit/api/graph';
|
||||
import {
|
||||
Notifications,
|
||||
NotificationGraphConfig,
|
||||
NotificationGraphConfig,
|
||||
GroupNotificationsConfig,
|
||||
Unreads,
|
||||
JoinRequests,
|
||||
Patp
|
||||
} from "~/types";
|
||||
} from '@urbit/api';
|
||||
|
||||
export interface StoreState {
|
||||
// local state
|
||||
@ -35,7 +35,6 @@ export interface StoreState {
|
||||
graphs: Graphs;
|
||||
graphKeys: Set<string>;
|
||||
|
||||
|
||||
// App specific states
|
||||
// launch state
|
||||
launch: LaunchState;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BaseStore from "../store/base";
|
||||
import BaseApi from "../api/base";
|
||||
import { Path } from "~/types/noun";
|
||||
import BaseStore from '../store/base';
|
||||
import BaseApi from '../api/base';
|
||||
import { Path } from '@urbit/api';
|
||||
|
||||
export default class BaseSubscription<S extends object> {
|
||||
private errorCount = 0;
|
||||
@ -19,24 +19,24 @@ export default class BaseSubscription<S extends object> {
|
||||
|
||||
// Exists to allow subclasses to hook
|
||||
restart() {
|
||||
this.handleEvent({ data: { connection: 'reconnecting' }});
|
||||
this.handleEvent({ data: { connection: 'reconnecting' } });
|
||||
this.start();
|
||||
}
|
||||
|
||||
onChannelOpen(e: any) {
|
||||
this.errorCount = 0;
|
||||
this.handleEvent({ data: { connection: 'connected' }});
|
||||
this.handleEvent({ data: { connection: 'connected' } });
|
||||
}
|
||||
|
||||
onChannelError(err) {
|
||||
console.error('event source error: ', err);
|
||||
this.errorCount++;
|
||||
if(this.errorCount >= 5) {
|
||||
console.error("bailing out, too many retries");
|
||||
this.handleEvent({ data: { connection: 'disconnected' }});
|
||||
console.error('bailing out, too many retries');
|
||||
this.handleEvent({ data: { connection: 'disconnected' } });
|
||||
return;
|
||||
}
|
||||
this.handleEvent({ data: { connection: 'reconnecting' }});
|
||||
this.handleEvent({ data: { connection: 'reconnecting' } });
|
||||
setTimeout(() => {
|
||||
this.restart();
|
||||
}, Math.pow(2,this.errorCount - 1) * 750);
|
||||
|
@ -1,9 +1,8 @@
|
||||
import BaseSubscription from './base';
|
||||
import { StoreState } from '../store/type';
|
||||
import { Path } from '~/types/noun';
|
||||
import { Path } from '@urbit/api';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
/**
|
||||
* Path to subscribe on and app to subscribe to
|
||||
*/
|
||||
@ -68,7 +67,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
|
||||
}
|
||||
|
||||
stopApp(app: AppName) {
|
||||
this.openSubscriptions[app].map(id => this.unsubscribe(id))
|
||||
this.openSubscriptions[app].map(id => this.unsubscribe(id));
|
||||
this.openSubscriptions[app] = [];
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,17 @@
|
||||
import { ContactUpdate } from "./contact-update";
|
||||
import { InviteUpdate } from "./invite-update";
|
||||
import { LocalUpdate } from "./local-update";
|
||||
import { MetadataUpdate } from "./metadata-update";
|
||||
import { GroupUpdate } from "./group-update";
|
||||
import { LaunchUpdate, WeatherState } from "./launch-update";
|
||||
import { ConnectionStatus } from "./connection";
|
||||
import { SettingsUpdate } from "./settings";
|
||||
import { LocalUpdate } from './local-update';
|
||||
import { LaunchUpdate, WeatherState } from './launch-update';
|
||||
import { ConnectionStatus } from './connection';
|
||||
import { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate } from '@urbit/api';
|
||||
import { SettingsUpdate } from '@urbit/api/settings';
|
||||
|
||||
interface MarksToTypes {
|
||||
readonly json: any;
|
||||
readonly "contact-update": ContactUpdate;
|
||||
readonly "invite-update": InviteUpdate;
|
||||
readonly "metadata-update": MetadataUpdate;
|
||||
readonly 'contact-update': ContactUpdate;
|
||||
readonly 'invite-update': InviteUpdate;
|
||||
readonly 'metadata-update': MetadataUpdate;
|
||||
readonly groupUpdate: GroupUpdate;
|
||||
readonly "launch-update": LaunchUpdate;
|
||||
readonly "link-listen-update": LinkListenUpdate;
|
||||
readonly "settings-event": SettingsUpdate;
|
||||
readonly 'launch-update': LaunchUpdate;
|
||||
readonly 'settings-event': SettingsUpdate;
|
||||
// not really marks but w/e
|
||||
readonly 'local': LocalUpdate;
|
||||
readonly 'weather': WeatherState | {};
|
||||
|
@ -1,2 +1 @@
|
||||
|
||||
export type ConnectionStatus = 'reconnecting' | 'disconnected' | 'connected';
|
||||
|
@ -1,85 +0,0 @@
|
||||
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: string | null;
|
||||
}
|
||||
|
||||
export type ContactEdit = {
|
||||
[k in keyof Contact]: Contact[k];
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { PatpNoSig } from "./noun";
|
||||
import { PatpNoSig } from '@urbit/api';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { Patp } from "./noun";
|
||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||
|
||||
export interface TextContent {
|
||||
text: string;
|
||||
}
|
||||
export interface UrlContent {
|
||||
url: string;
|
||||
}
|
||||
export interface CodeContent {
|
||||
code: {
|
||||
expresssion: string;
|
||||
output: string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ReferenceContent {
|
||||
uid: string;
|
||||
}
|
||||
export interface MentionContent {
|
||||
mention: string;
|
||||
}
|
||||
export type Content =
|
||||
| TextContent
|
||||
| UrlContent
|
||||
| CodeContent
|
||||
| ReferenceContent
|
||||
| MentionContent;
|
||||
|
||||
export interface Post {
|
||||
author: Patp;
|
||||
contents: Content[];
|
||||
hash: string | null;
|
||||
index: string;
|
||||
pending?: boolean;
|
||||
signatures: string[];
|
||||
"time-sent": number;
|
||||
}
|
||||
|
||||
export interface GraphNode {
|
||||
children: Graph;
|
||||
post: Post;
|
||||
}
|
||||
|
||||
export type Graph = BigIntOrderedMap<GraphNode>;
|
||||
|
||||
export type Graphs = { [rid: string]: Graph };
|
@ -1,180 +0,0 @@
|
||||
import { PatpNoSig, Path, Jug, ShipRank, Enc } from './noun';
|
||||
|
||||
export const roleTags = ['janitor', 'moderator', 'admin'] as const;
|
||||
export type RoleTags = typeof roleTags[number];
|
||||
interface RoleTag {
|
||||
tag: 'admin' | 'moderator' | 'janitor';
|
||||
}
|
||||
|
||||
interface AppTag {
|
||||
app: string;
|
||||
resource: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export type Tag = AppTag | RoleTag;
|
||||
|
||||
export interface InvitePolicy {
|
||||
invite: {
|
||||
pending: Set<PatpNoSig>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface OpenPolicy {
|
||||
open: {
|
||||
banned: Set<PatpNoSig>;
|
||||
banRanks: Set<ShipRank>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Resource {
|
||||
name: string;
|
||||
ship: PatpNoSig;
|
||||
}
|
||||
|
||||
export type OpenPolicyDiff =
|
||||
| AllowRanksDiff
|
||||
| BanRanksDiff
|
||||
| AllowShipsDiff
|
||||
| BanShipsDiff;
|
||||
|
||||
interface AllowRanksDiff {
|
||||
allowRanks: ShipRank[];
|
||||
}
|
||||
|
||||
interface BanRanksDiff {
|
||||
banRanks: ShipRank[];
|
||||
}
|
||||
|
||||
interface AllowShipsDiff {
|
||||
allowShips: PatpNoSig[];
|
||||
}
|
||||
|
||||
interface BanShipsDiff {
|
||||
banShips: PatpNoSig[];
|
||||
}
|
||||
|
||||
export type InvitePolicyDiff = AddInvitesDiff | RemoveInvitesDiff;
|
||||
|
||||
interface AddInvitesDiff {
|
||||
addInvites: PatpNoSig[];
|
||||
}
|
||||
|
||||
interface RemoveInvitesDiff {
|
||||
removeInvites: PatpNoSig[];
|
||||
}
|
||||
|
||||
interface ReplacePolicyDiff {
|
||||
replace: GroupPolicy;
|
||||
}
|
||||
|
||||
export type GroupPolicyDiff =
|
||||
| { open: OpenPolicyDiff }
|
||||
| { invite: InvitePolicyDiff }
|
||||
| ReplacePolicyDiff;
|
||||
|
||||
export type GroupPolicy = OpenPolicy | InvitePolicy;
|
||||
|
||||
interface TaggedShips {
|
||||
[tag: string]: Set<PatpNoSig>;
|
||||
}
|
||||
|
||||
export interface Tags {
|
||||
role: TaggedShips;
|
||||
[app: string]: TaggedShips;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
members: Set<PatpNoSig>;
|
||||
tags: Tags;
|
||||
policy: GroupPolicy;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
export type Groups = {
|
||||
[p in Path]: Group;
|
||||
};
|
||||
|
||||
interface GroupUpdateInitial {
|
||||
initial: Enc<Groups>;
|
||||
}
|
||||
|
||||
interface GroupUpdateAddGroup {
|
||||
addGroup: {
|
||||
resource: Resource;
|
||||
policy: Enc<GroupPolicy>;
|
||||
hidden: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateAddMembers {
|
||||
addMembers: {
|
||||
ships: PatpNoSig[];
|
||||
resource: Resource;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateRemoveMembers {
|
||||
removeMembers: {
|
||||
ships: PatpNoSig[];
|
||||
resource: Resource;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateAddTag {
|
||||
addTag: {
|
||||
tag: Tag;
|
||||
resource: Resource;
|
||||
ships: PatpNoSig[];
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateRemoveTag {
|
||||
removeTag: {
|
||||
tag: Tag;
|
||||
resource: Resource;
|
||||
ships: PatpNoSig;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateChangePolicy {
|
||||
changePolicy: { resource: Resource; diff: GroupPolicyDiff };
|
||||
}
|
||||
|
||||
interface GroupUpdateRemoveGroup {
|
||||
removeGroup: {
|
||||
resource: Resource;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateExpose {
|
||||
expose: {
|
||||
resource: Resource;
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupUpdateInitialGroup {
|
||||
initialGroup: {
|
||||
resource: Resource;
|
||||
group: Enc<Group>;
|
||||
};
|
||||
}
|
||||
|
||||
export type GroupUpdate =
|
||||
| GroupUpdateInitial
|
||||
| GroupUpdateAddGroup
|
||||
| GroupUpdateAddMembers
|
||||
| GroupUpdateRemoveMembers
|
||||
| GroupUpdateAddTag
|
||||
| GroupUpdateRemoveTag
|
||||
| GroupUpdateChangePolicy
|
||||
| GroupUpdateRemoveGroup
|
||||
| GroupUpdateExpose
|
||||
| GroupUpdateInitialGroup;
|
||||
|
||||
export type GroupAction = Omit<GroupUpdate, 'initialGroup' | 'initial'>;
|
||||
|
||||
export const groupBunts = {
|
||||
group: (): Group => ({ members: new Set(), tags: { role: {} }, hidden: false, policy: groupBunts.policy() }),
|
||||
policy: (): GroupPolicy => ({ open: { banned: new Set(), banRanks: new Set() } })
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
export const joinError = ['no-perms', 'strange'] as const;
|
||||
export type JoinError = typeof joinError[number];
|
||||
export const joinResult = ['done', ...joinError] as const;
|
||||
export type JoinResult = typeof joinResult[number];
|
||||
|
||||
export const joinProgress = ['start', 'added', ...joinResult] as const;
|
||||
export type JoinProgress = typeof joinProgress[number];
|
||||
|
||||
export interface JoinRequests {
|
||||
[rid: string]: JoinProgress;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import _ from "lodash";
|
||||
import { Post } from "./graph-update";
|
||||
import { GroupUpdate } from "./group-update";
|
||||
import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
|
||||
|
||||
export type GraphNotifDescription = "link" | "comment" | "note" | "mention";
|
||||
|
||||
export interface UnreadStats {
|
||||
unreads: Set<string> | number;
|
||||
notifications: number;
|
||||
last: number;
|
||||
}
|
||||
|
||||
export interface GraphNotifIndex {
|
||||
graph: string;
|
||||
group: string;
|
||||
description: GraphNotifDescription;
|
||||
module: string;
|
||||
index: string;
|
||||
}
|
||||
|
||||
export interface GroupNotifIndex {
|
||||
group: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type NotifIndex =
|
||||
| { graph: GraphNotifIndex }
|
||||
| { group: GroupNotifIndex };
|
||||
|
||||
export type GraphNotificationContents = Post[];
|
||||
|
||||
export type GroupNotificationContents = GroupUpdate[];
|
||||
|
||||
export type NotificationContents =
|
||||
| { graph: GraphNotificationContents }
|
||||
| { group: GroupNotificationContents };
|
||||
export interface Notification {
|
||||
read: boolean;
|
||||
time: number;
|
||||
contents: NotificationContents;
|
||||
}
|
||||
|
||||
export interface IndexedNotification {
|
||||
index: NotifIndex;
|
||||
notification: Notification;
|
||||
}
|
||||
|
||||
export type Timebox = IndexedNotification[];
|
||||
|
||||
export type Notifications = BigIntOrderedMap<Timebox>;
|
||||
|
||||
export interface NotificationGraphConfig {
|
||||
watchOnSelf: boolean;
|
||||
mentions: boolean;
|
||||
watching: WatchedIndex[]
|
||||
}
|
||||
|
||||
export interface Unreads {
|
||||
graph: Record<string, Record<string, UnreadStats>>;
|
||||
group: Record<string, UnreadStats>;
|
||||
}
|
||||
|
||||
interface WatchedIndex {
|
||||
graph: string;
|
||||
index: string;
|
||||
}
|
||||
export type GroupNotificationsConfig = string[];
|
@ -1,16 +1,8 @@
|
||||
export * from './cage';
|
||||
export * from './connection';
|
||||
export * from './contact-update';
|
||||
export * from './global';
|
||||
export * from './group-update';
|
||||
export * from './group-view';
|
||||
export * from './graph-update';
|
||||
export * from './hark-update';
|
||||
export * from './invite-update';
|
||||
export * from './launch-update';
|
||||
export * from './local-update';
|
||||
export * from './metadata-update';
|
||||
export * from './noun';
|
||||
export * from './s3-update';
|
||||
export * from './workspace';
|
||||
export * from './util';
|
||||
|
@ -1,68 +0,0 @@
|
||||
import { Serial, PatpNoSig, Path } from './noun';
|
||||
import {Resource} from './group-update';
|
||||
|
||||
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;
|
||||
recipient: PatpNoSig;
|
||||
resource: Resource;
|
||||
ship: PatpNoSig;
|
||||
text: string;
|
||||
}
|
@ -1,11 +1,9 @@
|
||||
|
||||
export type LaunchUpdate =
|
||||
LaunchUpdateInitial
|
||||
| LaunchUpdateFirstTime
|
||||
| LaunchUpdateOrder
|
||||
| LaunchUpdateIsShown;
|
||||
|
||||
|
||||
interface LaunchUpdateInitial {
|
||||
initial: LaunchState;
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MetadataUpdatePreview {
|
||||
group: string;
|
||||
channels: Associations;
|
||||
"channel-count": number;
|
||||
members: number;
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
export type Associations = Record<AppName, AppAssociations>;
|
||||
|
||||
export type AppAssociations = {
|
||||
[p in Path]: Association;
|
||||
}
|
||||
|
||||
interface Resource {
|
||||
resource: Path;
|
||||
'app-name': AppName;
|
||||
}
|
||||
|
||||
export type Association = Resource & {
|
||||
group: Path;
|
||||
metadata: Metadata;
|
||||
};
|
||||
|
||||
export interface Metadata {
|
||||
color: string;
|
||||
creator: Patp;
|
||||
'date-created': string;
|
||||
description: string;
|
||||
title: string;
|
||||
module: string;
|
||||
picture: string;
|
||||
preview: boolean;
|
||||
vip: PermVariation;
|
||||
}
|
||||
|
||||
export type PermVariation = '' | 'reader-comments' | 'member-metadata';
|
@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Martian embassy
|
||||
*/
|
||||
|
||||
// an urbit style path rendered 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;
|
||||
|
||||
// jug from hoon
|
||||
export type Jug<K,V> = Map<K,Set<V>>;
|
||||
|
||||
// name of app
|
||||
export type AppName = 'contacts' | 'groups' | 'graph';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export type ShipRank = 'czar' | 'king' | 'duke' | 'earl' | 'pawn';
|
||||
|
||||
|
||||
export type SetElement<S> = S extends Set<(infer T)> ? T : never;
|
||||
export type MapKey<M> = M extends Map<(infer K), any> ? K : never;
|
||||
export type MapValue<M> = M extends Map<any, (infer V)> ? V : never;
|
||||
|
||||
/**
|
||||
* Turns sets into arrays and maps into objects so we can send them over the wire
|
||||
*/
|
||||
export type Enc<S> =
|
||||
S extends Set<any> ?
|
||||
Enc<SetElement<S>>[] :
|
||||
S extends Map<string, any> ?
|
||||
{ [s: string]: Enc<MapValue<S>> } :
|
||||
S extends object ?
|
||||
{ [K in keyof S]: Enc<S[K]> } :
|
||||
S;
|
@ -1,6 +1,4 @@
|
||||
|
||||
|
||||
export interface S3Credentials {
|
||||
export interface S3Credentials {
|
||||
endpoint: string;
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
@ -51,7 +49,6 @@ interface S3UpdateSecretAccessKey {
|
||||
setSecretAccessKey: string;
|
||||
}
|
||||
|
||||
|
||||
export type S3Update =
|
||||
S3UpdateCredentials
|
||||
| S3UpdateConfiguration
|
||||
|
@ -1,55 +0,0 @@
|
||||
export type Key = string;
|
||||
export type Value = string | boolean | number;
|
||||
export type Bucket = Map<string, Value>;
|
||||
export type Settings = Map<string, Bucket>;
|
||||
|
||||
interface PutBucket {
|
||||
"put-bucket": {
|
||||
"bucket-key": Key;
|
||||
"bucket": Bucket;
|
||||
};
|
||||
}
|
||||
|
||||
interface DelBucket {
|
||||
"del-bucket": {
|
||||
"bucket-key": Key;
|
||||
};
|
||||
}
|
||||
|
||||
interface PutEntry {
|
||||
"put-entry": {
|
||||
"bucket-key": Key;
|
||||
"entry-key": Key;
|
||||
"value": Value;
|
||||
};
|
||||
}
|
||||
|
||||
interface DelEntry {
|
||||
"del-entry": {
|
||||
"bucket-key": Key;
|
||||
"entry-key": Key;
|
||||
};
|
||||
}
|
||||
|
||||
interface AllData {
|
||||
"all": Settings;
|
||||
}
|
||||
|
||||
interface BucketData {
|
||||
"bucket": Bucket;
|
||||
}
|
||||
|
||||
interface EntryData {
|
||||
"entry": Value;
|
||||
}
|
||||
|
||||
export type SettingsUpdate =
|
||||
| PutBucket
|
||||
| DelBucket
|
||||
| PutEntry
|
||||
| DelEntry;
|
||||
|
||||
export type SettingsData =
|
||||
| AllData
|
||||
| BucketData
|
||||
| EntryData;
|
@ -1,4 +1,4 @@
|
||||
import { Icon } from "@tlon/indigo-react";
|
||||
import { Icon } from '@tlon/indigo-react';
|
||||
export type PropFunc<T extends (...args: any[]) => any> = Parameters<T>[0];
|
||||
|
||||
export type Primitive = string | number | undefined | symbol | null | boolean;
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
interface GroupWorkspace {
|
||||
type: 'group';
|
||||
group: string;
|
||||
|
@ -3,7 +3,7 @@ import { RouteComponentProps } from 'react-router-dom';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Association } from '~/types/metadata-update';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||
import ChatWindow from './components/ChatWindow';
|
||||
@ -86,12 +86,16 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!res) { return; }
|
||||
if (!group) { return; }
|
||||
if (!res) {
|
||||
return;
|
||||
}
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
if (group.hidden) {
|
||||
const members = _.compact(await Promise.all(
|
||||
Array.from(group.members)
|
||||
.map(s => {
|
||||
.map((s) => {
|
||||
const ship = `~${s}`;
|
||||
if(s === window.ship) {
|
||||
return Promise.resolve(null);
|
||||
@ -101,7 +105,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
'personal',
|
||||
ship,
|
||||
true
|
||||
).then(isAllowed => {
|
||||
).then((isAllowed) => {
|
||||
return isAllowed ? null : ship;
|
||||
});
|
||||
})
|
||||
@ -113,7 +117,6 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
} else {
|
||||
setShowBanner(false);
|
||||
}
|
||||
|
||||
} else {
|
||||
const groupShared = await props.api.contacts.fetchIsAllowed(
|
||||
`~${window.ship}`,
|
||||
@ -124,14 +127,13 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
setShowBanner(!groupShared);
|
||||
}
|
||||
})();
|
||||
|
||||
}, [groupPath]);
|
||||
|
||||
if(!graph) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
var modifiedContacts = { ...contacts };
|
||||
const modifiedContacts = { ...contacts };
|
||||
delete modifiedContacts[`~${window.ship}`];
|
||||
|
||||
return (
|
||||
@ -145,7 +147,7 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
setShowBanner={setShowBanner}
|
||||
group={group}
|
||||
groupPath={groupPath}
|
||||
/>
|
||||
/>
|
||||
{dragging && <SubmitDragger />}
|
||||
<ChatWindow
|
||||
history={props.history}
|
||||
|
@ -7,7 +7,7 @@ import { createPost } from '~/logic/api/graph';
|
||||
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Envelope } from '~/types/chat-update';
|
||||
import { Contacts, Content } from '~/types';
|
||||
import { Contacts, Content } from '@urbit/api';
|
||||
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
|
||||
import withS3 from '~/views/components/withS3';
|
||||
import { withLocalState } from '~/logic/state/local';
|
||||
@ -15,12 +15,12 @@ import { withLocalState } from '~/logic/state/local';
|
||||
type ChatInputProps = IuseS3 & {
|
||||
api: GlobalApi;
|
||||
numMsgs: number;
|
||||
station: any;
|
||||
ourContact: any;
|
||||
station: unknown;
|
||||
ourContact: unknown;
|
||||
envelopes: Envelope[];
|
||||
contacts: Contacts;
|
||||
onUnmount(msg: string): void;
|
||||
s3: any;
|
||||
s3: unknown;
|
||||
placeholder: string;
|
||||
message: string;
|
||||
deleteMessage(): void;
|
||||
@ -67,14 +67,14 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
inCodeMode: false
|
||||
}, async () => {
|
||||
const output = await props.api.graph.eval(text);
|
||||
const contents: Content[] = [{ code: { output, expression: text }}];
|
||||
const contents: Content[] = [{ code: { output, expression: text } }];
|
||||
const post = createPost(contents);
|
||||
props.api.graph.addPost(ship, name, post);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const post = createPost(tokenizeMessage((text)))
|
||||
const post = createPost(tokenizeMessage((text)));
|
||||
|
||||
props.deleteMessage();
|
||||
|
||||
@ -110,7 +110,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
if (!this.props.canUpload) {
|
||||
return;
|
||||
}
|
||||
Array.from(files).forEach(file => {
|
||||
Array.from(files).forEach((file) => {
|
||||
this.props.uploadDefault(file)
|
||||
.then(this.uploadSuccess)
|
||||
.catch(this.uploadError);
|
||||
@ -178,7 +178,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
width="16"
|
||||
height="16"
|
||||
onClick={() => this.props.promptUpload().then(this.uploadSuccess)}
|
||||
/>
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</Box>
|
||||
@ -200,4 +200,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
}
|
||||
}
|
||||
|
||||
export default withLocalState(withS3(ChatInput, {accept: 'image/*'}), ['hideAvatars']);
|
||||
export default withLocalState(withS3(ChatInput, { accept: 'image/*' }), ['hideAvatars']);
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
useShowNickname,
|
||||
useHovering
|
||||
} from '~/logic/lib/util';
|
||||
import { Group, Association, Contacts, Post, Groups, Associations } from '~/types';
|
||||
import { Group, Association, Contacts, Post, Groups, Associations } from '@urbit/api';
|
||||
import TextContent from './content/text';
|
||||
import CodeContent from './content/code';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
@ -66,12 +66,12 @@ interface ChatMessageProps {
|
||||
contacts: Contacts;
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
style?: any;
|
||||
style?: unknown;
|
||||
scrollWindow: HTMLDivElement;
|
||||
isLastMessage?: boolean;
|
||||
unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
history: any;
|
||||
api: any;
|
||||
history: unknown;
|
||||
api: GlobalApi;
|
||||
highlighted?: boolean;
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
highlighted,
|
||||
fontSize,
|
||||
associations,
|
||||
groups,
|
||||
groups
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
@ -230,7 +230,7 @@ export const MessageWithSigil = (props) => {
|
||||
fontSize
|
||||
} = props;
|
||||
|
||||
const dark = useLocalState((state) => state.dark);
|
||||
const dark = useLocalState(state => state.dark);
|
||||
|
||||
const datestamp = moment
|
||||
.unix(msg['time-sent'] / 1000)
|
||||
@ -255,7 +255,7 @@ export const MessageWithSigil = (props) => {
|
||||
const [showOverlay, setShowOverlay] = useState(false);
|
||||
|
||||
const toggleOverlay = () => {
|
||||
setShowOverlay((value) => !value);
|
||||
setShowOverlay(value => !value);
|
||||
};
|
||||
|
||||
const showCopyNotice = () => {
|
||||
|
@ -1,22 +1,17 @@
|
||||
import React, { Component } from "react";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
import React, { Component } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import _ from 'lodash';
|
||||
import bigInt, { BigInteger } from 'big-integer';
|
||||
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { Patp, Contacts, Association, Associations, Group, Groups, Graph } from '@urbit/api';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Patp, Path } from "~/types/noun";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { Association, Associations } from "~/types/metadata-update";
|
||||
import { Group, Groups } from "~/types/group-update";
|
||||
import { Envelope, IMessage } from "~/types/chat-update";
|
||||
import { Graph } from "~/types";
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||
import VirtualScroller from '~/views/components/VirtualScroller';
|
||||
|
||||
import ChatMessage, { MessagePlaceholder } from './ChatMessage';
|
||||
import { UnreadNotice } from "./unread-notice";
|
||||
import { UnreadNotice } from './unread-notice';
|
||||
|
||||
const INITIAL_LOAD = 20;
|
||||
const DEFAULT_BACKLOG_SIZE = 100;
|
||||
@ -66,8 +61,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
unreadIndex: bigInt.zero
|
||||
};
|
||||
|
||||
|
||||
|
||||
this.dismissUnread = this.dismissUnread.bind(this);
|
||||
this.scrollToUnread = this.scrollToUnread.bind(this);
|
||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||
@ -110,7 +103,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
this.setState({
|
||||
unreadIndex
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleWindowBlur() {
|
||||
@ -135,7 +128,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.calculateUnreadIndex();
|
||||
}
|
||||
|
||||
|
||||
if(this.prevSize !== graph.size) {
|
||||
if(this.state.unreadIndex.eq(bigInt.zero)) {
|
||||
this.calculateUnreadIndex();
|
||||
@ -170,8 +162,10 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
dismissUnread() {
|
||||
const { association } = this.props;
|
||||
if (this.state.fetchPending) return;
|
||||
if (this.props.unreadCount === 0) return;
|
||||
if (this.state.fetchPending)
|
||||
return;
|
||||
if (this.props.unreadCount === 0)
|
||||
return;
|
||||
this.props.api.hark.markCountAsRead(association, '/', 'message');
|
||||
this.props.api.hark.markCountAsRead(association, '/', 'mention');
|
||||
}
|
||||
@ -189,14 +183,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
const currSize = graph.size;
|
||||
if(newer && !this.loadedNewest) {
|
||||
const [index] = graph.peekLargest()!;
|
||||
await api.graph.getYoungerSiblings(ship,name, 20, `/${index.toString()}`)
|
||||
await api.graph.getYoungerSiblings(ship,name, 20, `/${index.toString()}`);
|
||||
if(currSize === graph.size) {
|
||||
console.log('loaded all newest');
|
||||
this.loadedNewest = true;
|
||||
}
|
||||
} else if(!newer && !this.loadedOldest) {
|
||||
const [index] = graph.peekSmallest()!;
|
||||
await api.graph.getOlderSiblings(ship,name, 20, `/${index.toString()}`)
|
||||
await api.graph.getOlderSiblings(ship,name, 20, `/${index.toString()}`);
|
||||
this.calculateUnreadIndex();
|
||||
if(currSize === graph.size) {
|
||||
console.log('loaded all oldest');
|
||||
@ -204,7 +198,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
}
|
||||
this.setState({ fetchPending: false });
|
||||
|
||||
}
|
||||
|
||||
onScroll({ scrollTop, scrollHeight, windowHeight }) {
|
||||
@ -216,10 +209,13 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
|
||||
dismissIfLineVisible() {
|
||||
if (this.props.unreadCount === 0) return;
|
||||
if (!this.unreadMarkerRef.current || !this.virtualList?.window) return;
|
||||
if (this.props.unreadCount === 0)
|
||||
return;
|
||||
if (!this.unreadMarkerRef.current || !this.virtualList?.window)
|
||||
return;
|
||||
const parent = this.unreadMarkerRef.current.parentElement?.parentElement;
|
||||
if (!parent) return;
|
||||
if (!parent)
|
||||
return;
|
||||
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
||||
if (
|
||||
(scrollHeight - parent.offsetTop > scrollTop)
|
||||
@ -246,7 +242,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
|
||||
|
||||
const messageProps = { association, group, contacts, unreadMarkerRef, history, api, groups, associations };
|
||||
|
||||
const keys = graph.keys().reverse();
|
||||
@ -262,7 +257,9 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
onClick={this.scrollToUnread}
|
||||
/>
|
||||
<VirtualScroller
|
||||
ref={list => {this.virtualList = list}}
|
||||
ref={(list) => {
|
||||
this.virtualList = list;
|
||||
}}
|
||||
origin="bottom"
|
||||
style={{ height: '100%' }}
|
||||
onStartReached={() => {
|
||||
@ -274,7 +271,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
size={graph.size}
|
||||
renderer={({ index, measure, scrollWindow }) => {
|
||||
const msg = graph.get(index)?.post;
|
||||
if (!msg) return null;
|
||||
if (!msg)
|
||||
return null;
|
||||
if (!this.state.initialized) {
|
||||
return <MessagePlaceholder key={index.toString()} height="64px" index={index} />;
|
||||
}
|
||||
@ -285,7 +283,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
const prevIdx = keys[graphIdx+1];
|
||||
const nextIdx = keys[graphIdx-1];
|
||||
|
||||
|
||||
const isLastRead: boolean = this.state.unreadIndex.eq(index);
|
||||
const props = { measure, highlighted, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||
return (
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, {useRef} from "react";
|
||||
import { Box, Text, Col } from "@tlon/indigo-react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import React, { useRef } from 'react';
|
||||
import { Box, Text, Col } from '@tlon/indigo-react';
|
||||
import f from 'lodash/fp';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Associations, Association, Unreads, UnreadStats } from "~/types";
|
||||
import { alphabeticalOrder } from "~/logic/lib/util";
|
||||
import { getUnreadCount, getNotificationCount } from "~/logic/lib/hark";
|
||||
import Tile from "../components/tiles/tile";
|
||||
import { useTutorialModal } from "~/views/components/useTutorialModal";
|
||||
import {TUTORIAL_HOST, TUTORIAL_GROUP} from "~/logic/lib/tutorialModal";
|
||||
import { Associations, Association, Unreads, UnreadStats } from '@urbit/api';
|
||||
import { alphabeticalOrder } from '~/logic/lib/util';
|
||||
import { getUnreadCount, getNotificationCount } from '~/logic/lib/hark';
|
||||
import Tile from '../components/tiles/tile';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
|
||||
|
||||
interface GroupsProps {
|
||||
associations: Associations;
|
||||
@ -17,7 +17,6 @@ interface GroupsProps {
|
||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||
|
||||
|
||||
const getGraphUnreads = (associations: Associations, unreads: Unreads) => (path: string) =>
|
||||
f.flow(
|
||||
f.pickBy((a: Association) => a.group === path),
|
||||
@ -34,12 +33,11 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
||||
f.reduce(f.add, 0)
|
||||
)(associations.graph);
|
||||
|
||||
|
||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const { associations, unreads, inbox, ...boxProps } = props;
|
||||
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter((e) => e?.group in props.groups)
|
||||
.filter(e => e?.group in props.groups)
|
||||
.sort(sortGroupsAlph);
|
||||
const graphUnreads = getGraphUnreads(associations || {}, unreads);
|
||||
const graphNotifications = getGraphNotifications(associations || {}, unreads);
|
||||
@ -48,7 +46,7 @@ export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
<>
|
||||
{groups.map((group, index) => {
|
||||
const path = group?.group;
|
||||
const unreadCount = graphUnreads(path)
|
||||
const unreadCount = graphUnreads(path);
|
||||
const notCount = graphNotifications(path);
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
import { Box, Button, Icon, Text } from "@tlon/indigo-react"
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import React from 'react';
|
||||
import { Box, Button, Icon, Text } from '@tlon/indigo-react';
|
||||
import { useModal } from '~/logic/lib/useModal';
|
||||
|
||||
const ModalButton = (props) => {
|
||||
const {
|
||||
@ -13,7 +13,6 @@ const ModalButton = (props) => {
|
||||
} = props;
|
||||
const { modal, showModal } = useModal({ modal: props.children });
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal}
|
||||
@ -33,6 +32,6 @@ const ModalButton = (props) => {
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ModalButton;
|
||||
|
@ -1,19 +1,18 @@
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import { Box, Row, Col, Center, LoadingSpinner, Text } from "@tlon/indigo-react";
|
||||
import { Switch, Route, Link } from "react-router-dom";
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Col, Center, LoadingSpinner, Text } from '@tlon/indigo-react';
|
||||
import { Switch, Route, Link } from 'react-router-dom';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { StoreState } from "~/logic/store/type";
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { LinkItem } from "./components/LinkItem";
|
||||
import { LinkPreview } from "./components/link-preview";
|
||||
import { LinkWindow } from "./LinkWindow";
|
||||
import { Comments } from "~/views/components/Comments";
|
||||
import { LinkItem } from './components/LinkItem';
|
||||
import { LinkWindow } from './LinkWindow';
|
||||
import { Comments } from '~/views/components/Comments';
|
||||
|
||||
import "./css/custom.css";
|
||||
import './css/custom.css';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
|
||||
const emptyMeasure = () => {};
|
||||
|
||||
@ -38,11 +37,11 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
history
|
||||
} = props;
|
||||
|
||||
const rid = association.resource;
|
||||
const rid = association.resource;
|
||||
|
||||
const relativePath = (p: string) => `${baseUrl}/resource/link${rid}${p}`;
|
||||
|
||||
const [, , ship, name] = rid.split("/");
|
||||
const [, , ship, name] = rid.split('/');
|
||||
const resourcePath = `${ship.slice(1)}/${name}`;
|
||||
const resource = associations.graph[rid]
|
||||
? associations.graph[rid]
|
||||
@ -58,7 +57,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
|
||||
const resourceUrl = `${baseUrl}/resource/link${rid}`;
|
||||
if (!graph) {
|
||||
return <Center width='100%' height='100%'><LoadingSpinner/></Center>;
|
||||
return <Center width='100%' height='100%'><LoadingSpinner /></Center>;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -66,7 +65,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={relativePath("")}
|
||||
path={relativePath('')}
|
||||
render={(props) => {
|
||||
return (
|
||||
<LinkWindow
|
||||
@ -86,7 +85,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={relativePath("/:index(\\d+)/:commentId?")}
|
||||
path={relativePath('/:index(\\d+)/:commentId?')}
|
||||
render={(props) => {
|
||||
const index = bigInt(props.match.params.index);
|
||||
const editCommentId = props.match.params.commentId || null;
|
||||
@ -95,7 +94,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
return <div>Malformed URL</div>;
|
||||
}
|
||||
|
||||
const node = !!graph ? graph.get(index) : null;
|
||||
const node = graph ? graph.get(index) : null;
|
||||
|
||||
if (!node) {
|
||||
return <Box>Not found</Box>;
|
||||
@ -106,7 +105,7 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
return (
|
||||
<Col alignItems="center" overflowY="auto" width="100%">
|
||||
<Col width="100%" p={3} maxWidth="768px">
|
||||
<Link to={resourceUrl}><Text px={3} bold>{"<- Back"}</Text></Link>
|
||||
<Link to={resourceUrl}><Text px={3} bold>{'<- Back'}</Text></Link>
|
||||
<LinkItem
|
||||
contacts={contacts}
|
||||
key={node.post.index}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from "react";
|
||||
import { Col } from "@tlon/indigo-react";
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from 'react';
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import bigInt from 'big-integer';
|
||||
import {
|
||||
Association,
|
||||
@ -9,13 +9,13 @@ import {
|
||||
LocalUpdateRemoteContentPolicy,
|
||||
Group,
|
||||
Rolodex,
|
||||
S3State,
|
||||
} from "~/types";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import VirtualScroller from "~/views/components/VirtualScroller";
|
||||
import { LinkItem } from "./components/LinkItem";
|
||||
import LinkSubmit from "./components/LinkSubmit";
|
||||
import {isWriter} from "~/logic/lib/group";
|
||||
S3State
|
||||
} from '@urbit/api';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import VirtualScroller from '~/views/components/VirtualScroller';
|
||||
import { LinkItem } from './components/LinkItem';
|
||||
import LinkSubmit from './components/LinkSubmit';
|
||||
import { isWriter } from '~/logic/lib/group';
|
||||
|
||||
interface LinkWindowProps {
|
||||
association: Association;
|
||||
@ -44,18 +44,19 @@ export function LinkWindow(props: LinkWindowProps) {
|
||||
|
||||
useEffect(() => {
|
||||
const list = virtualList?.current;
|
||||
if(!list) return;
|
||||
if(!list)
|
||||
return;
|
||||
list.calculateVisibleItems();
|
||||
}, [graph.size]);
|
||||
|
||||
const first = graph.peekLargest()?.[0];
|
||||
const [,,ship, name] = association.resource.split('/');
|
||||
const canWrite = isWriter(props.group, association.resource)
|
||||
const canWrite = isWriter(props.group, association.resource);
|
||||
|
||||
const style = useMemo(() =>
|
||||
({
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
@ -76,7 +77,7 @@ export function LinkWindow(props: LinkWindowProps) {
|
||||
|
||||
return (
|
||||
<VirtualScroller
|
||||
ref={(l) => (virtualList.current = l ?? undefined)}
|
||||
ref={l => (virtualList.current = l ?? undefined)}
|
||||
origin="top"
|
||||
style={style}
|
||||
onStartReached={() => {}}
|
||||
@ -86,11 +87,12 @@ export function LinkWindow(props: LinkWindowProps) {
|
||||
renderer={({ index, measure, scrollWindow }) => {
|
||||
const node = graph.get(index);
|
||||
const post = node?.post;
|
||||
if (!node || !post) return null;
|
||||
if (!node || !post)
|
||||
return null;
|
||||
const linkProps = {
|
||||
...props,
|
||||
node,
|
||||
measure,
|
||||
measure
|
||||
};
|
||||
if(canWrite && index.eq(first ?? bigInt.zero)) {
|
||||
return (
|
||||
@ -100,7 +102,7 @@ export function LinkWindow(props: LinkWindowProps) {
|
||||
</Col>
|
||||
<LinkItem {...linkProps} />
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
}
|
||||
return <LinkItem key={index.toString()} {...linkProps} />;
|
||||
}}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useRef, useCallback, ReactElement } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Row, Col, Anchor, Box, Text, Icon, Action } from '@tlon/indigo-react';
|
||||
import { GraphNode, Group, Rolodex, Unreads } from '@urbit/api';
|
||||
|
||||
import { writeText } from '~/logic/lib/util';
|
||||
import Author from '~/views/components/Author';
|
||||
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { Contacts, GraphNode, Group, Rolodex, Unreads } from '~/types';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import RemoteContent from '~/views/components/RemoteContent';
|
||||
@ -22,7 +22,7 @@ interface LinkItemProps {
|
||||
measure: (el: any) => void;
|
||||
}
|
||||
|
||||
export const LinkItem = (props: LinkItemProps) => {
|
||||
export const LinkItem = (props: LinkItemProps): ReactElement => {
|
||||
const {
|
||||
node,
|
||||
resource,
|
||||
@ -46,7 +46,7 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
// FF will only update on next tick
|
||||
setTimeout(() => {
|
||||
console.log(remoteRef.current);
|
||||
if(document.activeElement instanceof HTMLIFrameElement
|
||||
if(document.activeElement instanceof HTMLIFrameElement
|
||||
&& remoteRef?.current?.containerRef?.contains(document.activeElement)) {
|
||||
markRead();
|
||||
}
|
||||
@ -55,8 +55,7 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
window.addEventListener('blur', onBlur);
|
||||
return () => {
|
||||
window.removeEventListener('blur', onBlur);
|
||||
}
|
||||
|
||||
};
|
||||
}, [markRead]);
|
||||
|
||||
const URLparser = new RegExp(
|
||||
@ -94,11 +93,9 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
const commColor = (props.unreads.graph?.[appPath]?.[`/${index}`]?.unreads ?? 0) > 0 ? 'blue' : 'gray';
|
||||
const isUnread = props.unreads.graph?.[appPath]?.['/']?.unreads?.has(node.post.index);
|
||||
|
||||
|
||||
|
||||
const onMeasure = useCallback(() => {
|
||||
ref.current && measure(ref.current);
|
||||
}, [ref.current, measure])
|
||||
}, [ref.current, measure]);
|
||||
|
||||
useEffect(() => {
|
||||
onMeasure();
|
||||
@ -120,7 +117,9 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
onClick={markRead}
|
||||
>
|
||||
<RemoteContent
|
||||
ref={r => { remoteRef.current = r}}
|
||||
ref={(r) => {
|
||||
remoteRef.current = r;
|
||||
}}
|
||||
url={contents[1].url}
|
||||
text={contents[0].text}
|
||||
unfold={true}
|
||||
@ -143,7 +142,8 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
alignSelf: 'center',
|
||||
style: { textOverflow: 'ellipsis', whiteSpace: 'pre', width: '100%' },
|
||||
p: 2
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
<Text color="gray" p={2} flexShrink={0}>
|
||||
<Anchor target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }} href={contents[1].url}>
|
||||
<Box display='flex'>
|
||||
@ -189,7 +189,7 @@ export const LinkItem = (props: LinkItemProps) => {
|
||||
}
|
||||
</Col>
|
||||
}
|
||||
>
|
||||
>
|
||||
<Icon ml="2" display="block" icon="Ellipsis" color="gray" />
|
||||
</Dropdown>
|
||||
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { BaseInput, Box, Button, LoadingSpinner, Text } from "@tlon/indigo-react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { useFileDrag } from "~/logic/lib/useDrag";
|
||||
import useS3 from "~/logic/lib/useS3";
|
||||
import { S3State } from "~/types";
|
||||
import SubmitDragger from "~/views/components/SubmitDragger";
|
||||
import { createPost } from "~/logic/api/graph";
|
||||
import { hasProvider } from "oembed-parser";
|
||||
import { BaseInput, Box, Button, LoadingSpinner, Text } from '@tlon/indigo-react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||
import useS3 from '~/logic/lib/useS3';
|
||||
import { S3State } from '@urbit/api';
|
||||
import SubmitDragger from '~/views/components/SubmitDragger';
|
||||
import { createPost } from '~/logic/api/graph';
|
||||
import { hasProvider } from 'oembed-parser';
|
||||
|
||||
interface LinkSubmitProps {
|
||||
api: GlobalApi;
|
||||
s3: S3State;
|
||||
name: string;
|
||||
ship: string;
|
||||
};
|
||||
}
|
||||
|
||||
const LinkSubmit = (props: LinkSubmitProps) => {
|
||||
let { canUpload, uploadDefault, uploading, promptUpload } = useS3(props.s3);
|
||||
const { canUpload, uploadDefault, uploading, promptUpload } = useS3(props.s3);
|
||||
|
||||
const [submitFocused, setSubmitFocused] = useState(false);
|
||||
const [urlFocused, setUrlFocused] = useState(false);
|
||||
@ -100,7 +100,7 @@ const LinkSubmit = (props: LinkSubmitProps) => {
|
||||
|
||||
const onLinkChange = (linkValue: string) => {
|
||||
setLinkValueHook(linkValue);
|
||||
const link = validateLink(linkValue)
|
||||
const link = validateLink(linkValue);
|
||||
setLinkValid(link);
|
||||
};
|
||||
|
||||
@ -133,7 +133,7 @@ const LinkSubmit = (props: LinkSubmitProps) => {
|
||||
px={2}
|
||||
pt={2}
|
||||
style={{ pointerEvents: 'none' }}
|
||||
>{canUpload
|
||||
>{canUpload
|
||||
? <>
|
||||
Drop or{' '}
|
||||
<Text
|
||||
@ -169,7 +169,7 @@ const LinkSubmit = (props: LinkSubmitProps) => {
|
||||
zIndex={9}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
>
|
||||
<LoadingSpinner />
|
||||
</Box>}
|
||||
{dragging && <SubmitDragger />}
|
||||
@ -223,4 +223,4 @@ const LinkSubmit = (props: LinkSubmitProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LinkSubmit;
|
||||
export default LinkSubmit;
|
||||
|
@ -1,31 +1,29 @@
|
||||
import React, { ReactNode, useCallback } from "react";
|
||||
import moment from "moment";
|
||||
import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import _ from "lodash";
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Row, Box, Col, Text, Anchor, Icon, Action } from '@tlon/indigo-react';
|
||||
import {
|
||||
Post,
|
||||
GraphNotifIndex,
|
||||
GraphNotificationContents,
|
||||
Associations,
|
||||
Content,
|
||||
Rolodex,
|
||||
Groups,
|
||||
} from "~/types";
|
||||
import { Header } from "./header";
|
||||
import { cite, deSig, pluralize } from "~/logic/lib/util";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { getSnippet } from "~/logic/lib/publish";
|
||||
import styled from "styled-components";
|
||||
import {MentionText} from "~/views/components/MentionText";
|
||||
import ChatMessage, {MessageWithoutSigil} from "../chat/components/ChatMessage";
|
||||
Groups
|
||||
} from '@urbit/api';
|
||||
|
||||
function getGraphModuleIcon(module: string) {
|
||||
if (module === "link") {
|
||||
return "Collection";
|
||||
import { Header } from './header';
|
||||
import { cite, deSig, pluralize } from '~/logic/lib/util';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { getSnippet } from '~/logic/lib/publish';
|
||||
import { MentionText } from '~/views/components/MentionText';
|
||||
import { MessageWithoutSigil } from '../chat/components/ChatMessage';
|
||||
|
||||
function getGraphModuleIcon(module: string): string {
|
||||
if (module === 'link') {
|
||||
return 'Collection';
|
||||
}
|
||||
return _.capitalize(module);
|
||||
}
|
||||
@ -34,32 +32,32 @@ const FilterBox = styled(Box)`
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
${(p) => p.theme.colors.white}
|
||||
${p => p.theme.colors.white}
|
||||
);
|
||||
`;
|
||||
|
||||
function describeNotification(description: string, plural: boolean) {
|
||||
function describeNotification(description: string, plural: boolean): string {
|
||||
switch (description) {
|
||||
case "link":
|
||||
return `added ${pluralize("new link", plural)} to`;
|
||||
case "comment":
|
||||
return `left ${pluralize("comment", plural)} on`;
|
||||
case "edit-comment":
|
||||
return `updated ${pluralize("comment", plural)} on`;
|
||||
case "note":
|
||||
return `posted ${pluralize("note", plural)} to`;
|
||||
case "edit-note":
|
||||
return `updated ${pluralize("note", plural)} in`;
|
||||
case "mention":
|
||||
return "mentioned you on";
|
||||
case "message":
|
||||
return `sent ${pluralize("message", plural)} to`;
|
||||
case 'link':
|
||||
return `added ${pluralize('new link', plural)} to`;
|
||||
case 'comment':
|
||||
return `left ${pluralize('comment', plural)} on`;
|
||||
case 'edit-comment':
|
||||
return `updated ${pluralize('comment', plural)} on`;
|
||||
case 'note':
|
||||
return `posted ${pluralize('note', plural)} to`;
|
||||
case 'edit-note':
|
||||
return `updated ${pluralize('note', plural)} in`;
|
||||
case 'mention':
|
||||
return 'mentioned you on';
|
||||
case 'message':
|
||||
return `sent ${pluralize('message', plural)} to`;
|
||||
default:
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
const GraphUrl = ({ url, title }) => (
|
||||
const GraphUrl = ({ url, title }): ReactElement => (
|
||||
<Box borderRadius="2" p="2" bg="scales.black05">
|
||||
<Anchor underline={false} target="_blank" color="black" href={url}>
|
||||
<Icon verticalAlign="bottom" mr="2" icon="ArrowExternal" />
|
||||
@ -68,10 +66,10 @@ const GraphUrl = ({ url, title }) => (
|
||||
</Box>
|
||||
);
|
||||
|
||||
const GraphNodeContent = ({ group, post, contacts, mod, description, index, remoteContentPolicy }) => {
|
||||
const GraphNodeContent = ({ group, post, contacts, mod, index }): ReactElement => {
|
||||
const { contents } = post;
|
||||
const idx = index.slice(1).split("/");
|
||||
if (mod === "link") {
|
||||
const idx = index.slice(1).split('/');
|
||||
if (mod === 'link') {
|
||||
if (idx.length === 1) {
|
||||
const [{ text }, { url }] = contents;
|
||||
return <GraphUrl title={text} url={url} />;
|
||||
@ -80,20 +78,20 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
|
||||
content={contents}
|
||||
contacts={contacts}
|
||||
group={group}
|
||||
/>
|
||||
/>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (mod === "publish") {
|
||||
if (idx[1] === "2") {
|
||||
if (mod === 'publish') {
|
||||
if (idx[1] === '2') {
|
||||
return <MentionText
|
||||
content={contents}
|
||||
group={group}
|
||||
contacts={contacts}
|
||||
fontSize='14px'
|
||||
lineHeight="tall"
|
||||
/>
|
||||
} else if (idx[1] === "1") {
|
||||
/>;
|
||||
} else if (idx[1] === '1') {
|
||||
const [{ text: header }, { text: body }] = contents;
|
||||
const snippet = getSnippet(body);
|
||||
return (
|
||||
@ -123,42 +121,41 @@ const GraphNodeContent = ({ group, post, contacts, mod, description, index, remo
|
||||
flexShrink={0}
|
||||
flexGrow={1}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
>
|
||||
<MessageWithoutSigil
|
||||
containerClass="items-top cf hide-child"
|
||||
measure={() => {}}
|
||||
group={group}
|
||||
contacts={contacts}
|
||||
groups={{}}
|
||||
associations={{ graph: {}, groups: {}}}
|
||||
associations={{ graph: {}, groups: {} }}
|
||||
msg={post}
|
||||
fontSize='0'
|
||||
pt='2'
|
||||
/>
|
||||
</Row>);
|
||||
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
function getNodeUrl(mod: string, hidden: boolean, groupPath: string, graph: string, index: string) {
|
||||
function getNodeUrl(mod: string, hidden: boolean, groupPath: string, graph: string, index: string): string {
|
||||
if (hidden && mod === 'chat') {
|
||||
groupPath = '/messages';
|
||||
} else if (hidden) {
|
||||
groupPath = '/home';
|
||||
}
|
||||
const graphUrl = `/~landscape${groupPath}/resource/${mod}${graph}`;
|
||||
const idx = index.slice(1).split("/");
|
||||
if (mod === "publish") {
|
||||
const idx = index.slice(1).split('/');
|
||||
if (mod === 'publish') {
|
||||
const [noteId] = idx;
|
||||
return `${graphUrl}/note/${noteId}`;
|
||||
} else if (mod === "link") {
|
||||
} else if (mod === 'link') {
|
||||
const [linkId] = idx;
|
||||
return `${graphUrl}/${linkId}`;
|
||||
} else if (mod === 'chat') {
|
||||
return graphUrl;
|
||||
}
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
const GraphNode = ({
|
||||
post,
|
||||
@ -174,9 +171,7 @@ const GraphNode = ({
|
||||
read,
|
||||
onRead,
|
||||
showContact = false,
|
||||
remoteContentPolicy
|
||||
}) => {
|
||||
const { contents } = post;
|
||||
}): ReactElement => {
|
||||
author = deSig(author);
|
||||
const history = useHistory();
|
||||
|
||||
@ -185,7 +180,7 @@ const GraphNode = ({
|
||||
ship={`~${author}`}
|
||||
size={16}
|
||||
icon
|
||||
color={`#000000`}
|
||||
color={'#000000'}
|
||||
classes="mix-blend-diff"
|
||||
padding={2}
|
||||
/>
|
||||
@ -212,12 +207,12 @@ const GraphNode = ({
|
||||
alignItems="center"
|
||||
p="1"
|
||||
backgroundColor="white"
|
||||
>
|
||||
>
|
||||
<Text mono title={author}>
|
||||
{cite(author)}
|
||||
</Text>
|
||||
<Text ml="2" gray>
|
||||
{moment(time).format("HH:mm")}
|
||||
{moment(time).format('HH:mm')}
|
||||
</Text>
|
||||
</Row>}
|
||||
<Row width="100%" p="1" flexDirection="column">
|
||||
@ -249,7 +244,7 @@ export function GraphNotification(props: {
|
||||
}) {
|
||||
const { contents, index, read, time, api, timebox, groups } = props;
|
||||
|
||||
const authors = _.map(contents, "author");
|
||||
const authors = _.map(contents, 'author');
|
||||
const { graph, group } = index;
|
||||
const icon = getGraphModuleIcon(index.module);
|
||||
const desc = describeNotification(index.description, contents.length !== 1);
|
||||
@ -259,7 +254,7 @@ export function GraphNotification(props: {
|
||||
return;
|
||||
}
|
||||
|
||||
return api.hark["read"](timebox, { graph: index });
|
||||
return api.hark['read'](timebox, { graph: index });
|
||||
}, [api, timebox, index, read]);
|
||||
|
||||
return (
|
||||
@ -284,7 +279,7 @@ return (
|
||||
author={content.author}
|
||||
contacts={props.contacts}
|
||||
mod={index.module}
|
||||
time={content?.["time-sent"]}
|
||||
time={content?.['time-sent']}
|
||||
description={index.description}
|
||||
index={content.index}
|
||||
graph={graph}
|
||||
|
@ -1,44 +1,34 @@
|
||||
import React, { ReactNode, useCallback } from "react";
|
||||
import moment from "moment";
|
||||
import { Row, Box, Col, Text, Anchor, Icon, Action } from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import { NotificationProps } from "./types";
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import {
|
||||
Post,
|
||||
GraphNotifIndex,
|
||||
GraphNotificationContents,
|
||||
Associations,
|
||||
Content,
|
||||
IndexedNotification,
|
||||
GroupNotificationContents,
|
||||
GroupNotifIndex,
|
||||
GroupUpdate,
|
||||
Rolodex,
|
||||
} from "~/types";
|
||||
import { Header } from "./header";
|
||||
import { cite, deSig } from "~/logic/lib/util";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
|
||||
Rolodex
|
||||
} from '@urbit/api';
|
||||
|
||||
import { Header } from './header';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
function describeNotification(description: string, plural: boolean) {
|
||||
switch (description) {
|
||||
case "add-members":
|
||||
return "joined";
|
||||
case "remove-members":
|
||||
return "left";
|
||||
case 'add-members':
|
||||
return 'joined';
|
||||
case 'remove-members':
|
||||
return 'left';
|
||||
default:
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
function getGroupUpdateParticipants(update: GroupUpdate) {
|
||||
if ("addMembers" in update) {
|
||||
function getGroupUpdateParticipants(update: GroupUpdate): string[] {
|
||||
if ('addMembers' in update) {
|
||||
return update.addMembers.ships;
|
||||
}
|
||||
if ("removeMembers" in update) {
|
||||
if ('removeMembers' in update) {
|
||||
return update.removeMembers.ships;
|
||||
}
|
||||
return [];
|
||||
@ -56,7 +46,7 @@ interface GroupNotificationProps {
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
export function GroupNotification(props: GroupNotificationProps) {
|
||||
export function GroupNotification(props: GroupNotificationProps): ReactElement {
|
||||
const { contents, index, read, time, api, timebox, associations } = props;
|
||||
|
||||
const authors = _.flatten(_.map(contents, getGroupUpdateParticipants));
|
||||
@ -68,7 +58,7 @@ export function GroupNotification(props: GroupNotificationProps) {
|
||||
if (props.archived) {
|
||||
return;
|
||||
}
|
||||
const func = read ? "unread" : "read";
|
||||
const func = read ? 'unread' : 'read';
|
||||
return api.hark[func](timebox, { group: index });
|
||||
}, [api, timebox, index, read]);
|
||||
|
||||
|
@ -1,17 +1,19 @@
|
||||
import React from "react";
|
||||
import { Text as NormalText, Row, Icon, Rule, Box } from "@tlon/indigo-react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import { PropFunc } from "~/types/util";
|
||||
import { getContactDetails, useShowNickname } from "~/logic/lib/util";
|
||||
import { Associations, Contact, Contacts, Rolodex } from "~/types";
|
||||
import React, { ReactElement } from 'react';
|
||||
import f from 'lodash/fp';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import { Text as NormalText, Row, Icon, Rule } from '@tlon/indigo-react';
|
||||
import { Associations, Contact, Contacts, Rolodex } from '@urbit/api';
|
||||
|
||||
import { PropFunc } from '~/types/util';
|
||||
import { useShowNickname } from '~/logic/lib/util';
|
||||
|
||||
const Text = (props: PropFunc<typeof Text>) => (
|
||||
<NormalText fontWeight="500" {...props} />
|
||||
);
|
||||
|
||||
function Author(props: { patp: string; contacts: Contacts; last?: boolean }) {
|
||||
function Author(props: { patp: string; contacts: Contacts; last?: boolean }): ReactElement {
|
||||
const contact: Contact | undefined = props.contacts?.[props.patp];
|
||||
|
||||
const showNickname = useShowNickname(contact);
|
||||
@ -20,7 +22,7 @@ function Author(props: { patp: string; contacts: Contacts; last?: boolean }) {
|
||||
return (
|
||||
<Text mono={!showNickname}>
|
||||
{name}
|
||||
{!props.last && ", "}
|
||||
{!props.last && ', '}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@ -36,7 +38,7 @@ export function Header(props: {
|
||||
time: number;
|
||||
read: boolean;
|
||||
associations: Associations;
|
||||
} & PropFunc<typeof Row> ) {
|
||||
} & PropFunc<typeof Row> ): ReactElement {
|
||||
const { description, channel, group, moduleIcon, read } = props;
|
||||
const contacts = props.contacts[group] || {};
|
||||
|
||||
@ -50,17 +52,17 @@ export function Header(props: {
|
||||
const last = lent - 1 === parseInt(idx, 10);
|
||||
return <Author key={idx} contacts={contacts} patp={p} last={last} />;
|
||||
}),
|
||||
(auths) => (
|
||||
auths => (
|
||||
<React.Fragment>
|
||||
{auths}
|
||||
|
||||
{authors.length > 3 &&
|
||||
` and ${authors.length - 3} other${authors.length === 4 ? "" : "s"}`}
|
||||
` and ${authors.length - 3} other${authors.length === 4 ? '' : 's'}`}
|
||||
</React.Fragment>
|
||||
)
|
||||
)(authors);
|
||||
|
||||
const time = moment(props.time).format("HH:mm");
|
||||
const time = moment(props.time).format('HH:mm');
|
||||
const groupTitle =
|
||||
props.associations.groups?.[props.group]?.metadata?.title;
|
||||
|
||||
@ -84,8 +86,8 @@ export function Header(props: {
|
||||
{authorDesc}
|
||||
</Text>
|
||||
<Text mr="1">{description}</Text>
|
||||
{!!moduleIcon && <Icon icon={moduleIcon as any} mr={1} />}
|
||||
{!!channel && <Text fontWeight="500" mr={1}>{channelTitle}</Text>}
|
||||
{Boolean(moduleIcon) && <Icon icon={moduleIcon as any} mr={1} />}
|
||||
{Boolean(channel) && <Text fontWeight="500" mr={1}>{channelTitle}</Text>}
|
||||
<Rule vertical height="12px" mr={1} />
|
||||
{groupTitle &&
|
||||
<>
|
||||
|
@ -1,22 +1,28 @@
|
||||
import React, { useEffect, useCallback, useRef, useState } from "react";
|
||||
import f from "lodash/fp";
|
||||
import _ from "lodash";
|
||||
import { Icon, Col, Center, Row, Box, Text, Anchor, Rule, LoadingSpinner } from "@tlon/indigo-react";
|
||||
import moment from "moment";
|
||||
import { Notifications, Rolodex, Timebox, IndexedNotification, Groups, joinProgress, JoinRequests, GroupNotificationsConfig, NotificationGraphConfig } from "~/types";
|
||||
import { MOMENT_CALENDAR_DATE, daToUnix, resourceAsPath } from "~/logic/lib/util";
|
||||
import { BigInteger } from "big-integer";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notification } from "./notification";
|
||||
import { Associations } from "~/types";
|
||||
import { InviteItem } from '~/views/components/Invite';
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||
import {JoiningStatus} from "./joining";
|
||||
import {Invites} from "./invites";
|
||||
import {useLazyScroll} from "~/logic/lib/useLazyScroll";
|
||||
import React, { useEffect, useCallback, useRef } from 'react';
|
||||
import f from 'lodash/fp';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { BigInteger } from 'big-integer';
|
||||
|
||||
import { Col, Center, Box, Text, LoadingSpinner } from '@tlon/indigo-react';
|
||||
import {
|
||||
Associations,
|
||||
Notifications,
|
||||
Rolodex,
|
||||
Timebox,
|
||||
IndexedNotification,
|
||||
Groups,
|
||||
JoinRequests,
|
||||
GroupNotificationsConfig,
|
||||
NotificationGraphConfig,
|
||||
Invites as InviteType
|
||||
} from '@urbit/api';
|
||||
|
||||
import { MOMENT_CALENDAR_DATE, daToUnix } from '~/logic/lib/util';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { Notification } from './notification';
|
||||
import { Invites } from './invites';
|
||||
import { useLazyScroll } from '~/logic/lib/useLazyScroll';
|
||||
|
||||
type DatedTimebox = [BigInteger, Timebox];
|
||||
|
||||
@ -25,12 +31,12 @@ function filterNotification(associations: Associations, groups: string[]) {
|
||||
return () => true;
|
||||
}
|
||||
return (n: IndexedNotification) => {
|
||||
if ("graph" in n.index) {
|
||||
if ('graph' in n.index) {
|
||||
const { group } = n.index.graph;
|
||||
return groups.findIndex((g) => group === g) !== -1;
|
||||
} else if ("group" in n.index) {
|
||||
return groups.findIndex(g => group === g) !== -1;
|
||||
} else if ('group' in n.index) {
|
||||
const { group } = n.index.group;
|
||||
return groups.findIndex((g) => group === g) !== -1;
|
||||
return groups.findIndex(g => group === g) !== -1;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@ -46,7 +52,7 @@ export default function Inbox(props: {
|
||||
associations: Associations;
|
||||
contacts: Rolodex;
|
||||
filter: string[];
|
||||
invites: any;
|
||||
invites: InviteType;
|
||||
pendingJoin: JoinRequests;
|
||||
notificationsGroupConfig: GroupNotificationsConfig;
|
||||
notificationsGraphConfig: NotificationGraphConfig;
|
||||
@ -70,30 +76,30 @@ export default function Inbox(props: {
|
||||
const calendar = {
|
||||
...MOMENT_CALENDAR_DATE, sameDay: function (now) {
|
||||
if (this.subtract(6, 'hours').isBefore(now)) {
|
||||
return "[Earlier Today]";
|
||||
return '[Earlier Today]';
|
||||
} else {
|
||||
return MOMENT_CALENDAR_DATE.sameDay;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let notificationsByDay = f.flow(
|
||||
const notificationsByDay = f.flow(
|
||||
f.map<DatedTimebox, DatedTimebox>(([date, nots]) => [
|
||||
date,
|
||||
nots.filter(filterNotification(associations, props.filter)),
|
||||
nots.filter(filterNotification(associations, props.filter))
|
||||
]),
|
||||
f.groupBy<DatedTimebox>(([d]) => {
|
||||
const date = moment(daToUnix(d));
|
||||
if (moment().subtract(6, 'hours').isBefore(date)) {
|
||||
return 'latest';
|
||||
} else {
|
||||
return date.format("YYYYMMDD");
|
||||
return date.format('YYYYMMDD');
|
||||
}
|
||||
}),
|
||||
})
|
||||
)(notifications);
|
||||
|
||||
const notificationsByDayMap = new Map<string, DatedTimebox[]>(
|
||||
Object.keys(notificationsByDay).map(timebox => {
|
||||
Object.keys(notificationsByDay).map((timebox) => {
|
||||
return [timebox, notificationsByDay[timebox]];
|
||||
})
|
||||
);
|
||||
@ -105,13 +111,12 @@ export default function Inbox(props: {
|
||||
}, [api]);
|
||||
|
||||
const { isDone, isLoading } = useLazyScroll(
|
||||
scrollRef,
|
||||
scrollRef,
|
||||
0.2,
|
||||
_.flatten(notifications).length,
|
||||
loadMore
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<Col ref={scrollRef} position="relative" height="100%" overflowY="auto">
|
||||
<Invites groups={props.groups} pendingJoin={props.pendingJoin} invites={invites} api={api} associations={associations} />
|
||||
@ -123,7 +128,7 @@ export default function Inbox(props: {
|
||||
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
|
||||
timeboxes={timeboxes}
|
||||
contacts={props.contacts}
|
||||
archive={!!props.showArchive}
|
||||
archive={Boolean(props.showArchive)}
|
||||
associations={props.associations}
|
||||
api={api}
|
||||
groups={props.groups}
|
||||
@ -142,7 +147,7 @@ export default function Inbox(props: {
|
||||
<LoadingSpinner />
|
||||
</Center>
|
||||
)}
|
||||
|
||||
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
@ -167,9 +172,8 @@ function DaySection({
|
||||
associations,
|
||||
api,
|
||||
groupConfig,
|
||||
graphConfig,
|
||||
graphConfig
|
||||
}) {
|
||||
|
||||
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
|
||||
if (lent === 0 || timeboxes.length === 0) {
|
||||
return null;
|
||||
|
@ -1,15 +1,12 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import React, { ReactElement } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Box, Row, Col } from "@tlon/indigo-react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Invites as IInvites, Associations, Invite, JoinRequests, Groups, Contacts, AppInvites, JoinProgress } from "~/types";
|
||||
import { resourceAsPath, alphabeticalOrder } from "~/logic/lib/util";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import InviteItem from "~/views/components/Invite";
|
||||
import {JoiningStatus} from "./joining";
|
||||
import {useModal} from "~/logic/lib/useModal";
|
||||
import {JoinGroup} from "~/views/landscape/components/JoinGroup";
|
||||
|
||||
import { Col } from '@tlon/indigo-react';
|
||||
import { Invites as IInvites, Associations, Invite, JoinRequests, Groups, Contacts, AppInvites, JoinProgress } from '@urbit/api';
|
||||
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { resourceAsPath, alphabeticalOrder } from '~/logic/lib/util';
|
||||
import InviteItem from '~/views/components/Invite';
|
||||
|
||||
interface InvitesProps {
|
||||
api: GlobalApi;
|
||||
@ -26,50 +23,18 @@ interface InviteRef {
|
||||
invite: Invite;
|
||||
}
|
||||
|
||||
export function Invites(props: InvitesProps) {
|
||||
export function Invites(props: InvitesProps): ReactElement {
|
||||
const { api, invites, pendingJoin } = props;
|
||||
const [selected, setSelected] = useState<[string, string, Invite] | undefined>()
|
||||
|
||||
const acceptInvite = (
|
||||
app: string,
|
||||
uid: string,
|
||||
invite: Invite
|
||||
) => async () => {
|
||||
setSelected([app, uid, invite]);
|
||||
showModal();
|
||||
};
|
||||
|
||||
const declineInvite = useCallback(
|
||||
(app: string, uid: string) => () => api.invite.decline(app, uid),
|
||||
[api]
|
||||
);
|
||||
|
||||
const { modal, showModal } = useModal({ modal: () => {
|
||||
const [app, uid, invite] = selected!;
|
||||
const autojoin = `~${invite.resource.ship}/${invite.resource.name}`;
|
||||
return (
|
||||
<JoinGroup
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
api={api}
|
||||
autojoin={autojoin}
|
||||
inviteUid={uid}
|
||||
inviteApp={app}
|
||||
/>
|
||||
)}});
|
||||
|
||||
const inviteArr: InviteRef[] = _.reduce(invites, (acc: InviteRef[], val: AppInvites, app: string) => {
|
||||
const appInvites = _.reduce(val, (invs: InviteRef[], invite: Invite, uid: string) => {
|
||||
return [...invs, { invite, uid, app }];
|
||||
|
||||
}, []);
|
||||
return [...acc, ...appInvites];
|
||||
}, []);
|
||||
|
||||
const invitesAndStatus: { [rid: string]: JoinProgress | InviteRef } =
|
||||
{..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)), ...props.pendingJoin };
|
||||
|
||||
|
||||
const invitesAndStatus: { [rid: string]: JoinProgress | InviteRef } =
|
||||
{ ..._.keyBy(inviteArr, ({ invite }) => resourceAsPath(invite.resource)), ...props.pendingJoin };
|
||||
|
||||
return (
|
||||
<Col
|
||||
@ -83,7 +48,7 @@ export function Invites(props: InvitesProps) {
|
||||
{ Object
|
||||
.keys(invitesAndStatus)
|
||||
.sort(alphabeticalOrder)
|
||||
.map(resource => {
|
||||
.map((resource) => {
|
||||
const inviteOrStatus = invitesAndStatus[resource];
|
||||
if(typeof inviteOrStatus === 'string') {
|
||||
return (
|
||||
@ -93,10 +58,10 @@ export function Invites(props: InvitesProps) {
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
resource={resource}
|
||||
pendingJoin={pendingJoin}
|
||||
api={api} />
|
||||
)
|
||||
|
||||
pendingJoin={pendingJoin}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const { app, uid, invite } = inviteOrStatus;
|
||||
console.log(inviteOrStatus);
|
||||
@ -107,13 +72,13 @@ export function Invites(props: InvitesProps) {
|
||||
invite={invite}
|
||||
app={app}
|
||||
uid={uid}
|
||||
pendingJoin={pendingJoin}
|
||||
pendingJoin={pendingJoin}
|
||||
resource={resource}
|
||||
contacts={props.contacts}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Col>
|
||||
|
@ -1,35 +1,32 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Col, Row, Text, SegmentedProgressBar, Box } from "@tlon/indigo-react";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import React from 'react';
|
||||
import { Row, Text, SegmentedProgressBar, Box } from '@tlon/indigo-react';
|
||||
import {
|
||||
JoinProgress,
|
||||
joinProgress,
|
||||
MetadataUpdatePreview,
|
||||
joinError,
|
||||
} from "~/types";
|
||||
import { clamp } from "~/logic/lib/util";
|
||||
joinError
|
||||
} from '@urbit/api';
|
||||
|
||||
interface JoiningStatusProps {
|
||||
status: JoinProgress;
|
||||
}
|
||||
|
||||
const description: string[] = [
|
||||
"Attempting to contact host",
|
||||
"Retrieving data",
|
||||
"Finished join",
|
||||
"Unable to join, you do not have the correct permissions",
|
||||
"Internal error, please file an issue",
|
||||
'Attempting to contact host',
|
||||
'Retrieving data',
|
||||
'Finished join',
|
||||
'Unable to join, you do not have the correct permissions',
|
||||
'Internal error, please file an issue'
|
||||
];
|
||||
|
||||
export function JoiningStatus(props: JoiningStatusProps) {
|
||||
const { status } = props;
|
||||
|
||||
const current = joinProgress.indexOf(status);
|
||||
const desc = description?.[current] || "";
|
||||
const desc = description?.[current] || '';
|
||||
const isError = joinError.indexOf(status as any) !== -1;
|
||||
return (
|
||||
<Row
|
||||
display={["flex-column", "flex"]}
|
||||
display={['flex-column', 'flex']}
|
||||
alignItems="center"
|
||||
px="4"
|
||||
gapX="4"
|
||||
@ -37,7 +34,7 @@ export function JoiningStatus(props: JoiningStatusProps) {
|
||||
<Box flexGrow={1} maxWidth="400px">
|
||||
<SegmentedProgressBar current={current + 1} segments={3} />
|
||||
</Box>
|
||||
<Text display="block" flexShrink={0} color={isError ? "red" : "gray"}>
|
||||
<Text display="block" flexShrink={0} color={isError ? 'red' : 'gray'}>
|
||||
{desc}
|
||||
</Text>
|
||||
</Row>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import React from 'react';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import { MetadataBody, NotificationProps } from "./types";
|
||||
import { Header } from "./header";
|
||||
import { MetadataBody, NotificationProps } from './types';
|
||||
import { Header } from './header';
|
||||
|
||||
function getInvolvedUsers(body: MetadataBody) {
|
||||
return [];
|
||||
@ -10,22 +10,22 @@ function getInvolvedUsers(body: MetadataBody) {
|
||||
|
||||
function getDescription(body: MetadataBody) {
|
||||
const b = body.metadata;
|
||||
if ("new" in b) {
|
||||
return "created";
|
||||
} else if ("changedTitle" in b) {
|
||||
return "changed the title to";
|
||||
} else if ("changedDescription" in b) {
|
||||
return "changed the description to";
|
||||
} else if ("changedColor" in b) {
|
||||
return "changed the color to";
|
||||
} else if ("deleted" in b) {
|
||||
return "deleted";
|
||||
if ('new' in b) {
|
||||
return 'created';
|
||||
} else if ('changedTitle' in b) {
|
||||
return 'changed the title to';
|
||||
} else if ('changedDescription' in b) {
|
||||
return 'changed the description to';
|
||||
} else if ('changedColor' in b) {
|
||||
return 'changed the color to';
|
||||
} else if ('deleted' in b) {
|
||||
return 'deleted';
|
||||
} else {
|
||||
throw new Error("bad metadata frond");
|
||||
throw new Error('bad metadata frond');
|
||||
}
|
||||
}
|
||||
|
||||
export function MetadataNotification(props: NotificationProps<"metadata">) {
|
||||
export function MetadataNotification(props: NotificationProps<'metadata'>) {
|
||||
const { unread } = props;
|
||||
const description = getDescription(unread.unreads[0].body);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useState } from "react";
|
||||
import { Row, Box } from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import React, { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
import { Row, Box } from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
GraphNotificationContents,
|
||||
IndexedNotification,
|
||||
@ -9,15 +9,15 @@ import {
|
||||
GroupNotificationsConfig,
|
||||
Groups,
|
||||
Associations,
|
||||
Contacts,
|
||||
} from "~/types";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { getParentIndex } from "~/logic/lib/notification";
|
||||
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
|
||||
import { GroupNotification } from "./group";
|
||||
import { GraphNotification } from "./graph";
|
||||
import { BigInteger } from "big-integer";
|
||||
import { useHovering } from "~/logic/lib/util";
|
||||
Contacts
|
||||
} from '@urbit/api';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { getParentIndex } from '~/logic/lib/notification';
|
||||
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
||||
import { GroupNotification } from './group';
|
||||
import { GraphNotification } from './graph';
|
||||
import { BigInteger } from 'big-integer';
|
||||
import { useHovering } from '~/logic/lib/util';
|
||||
|
||||
interface NotificationProps {
|
||||
notification: IndexedNotification;
|
||||
@ -37,7 +37,7 @@ function getMuted(
|
||||
graphs: NotificationGraphConfig
|
||||
) {
|
||||
const { index, notification } = idxNotif;
|
||||
if ("graph" in idxNotif.index) {
|
||||
if ('graph' in idxNotif.index) {
|
||||
const { graph } = idxNotif.index.graph;
|
||||
if(!('graph' in notification.contents)) {
|
||||
throw new Error();
|
||||
@ -46,11 +46,11 @@ function getMuted(
|
||||
|
||||
return _.findIndex(
|
||||
graphs?.watching || [],
|
||||
(g) => g.graph === graph && g.index === parent
|
||||
g => g.graph === graph && g.index === parent
|
||||
) === -1;
|
||||
}
|
||||
if ("group" in index) {
|
||||
return _.findIndex(groups || [], (g) => g === index.group.group) === -1;
|
||||
if ('group' in index) {
|
||||
return _.findIndex(groups || [], g => g === index.group.group) === -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -77,13 +77,13 @@ function NotificationWrapper(props: {
|
||||
);
|
||||
|
||||
const onChangeMute = useCallback(async () => {
|
||||
const func = isMuted ? "unmute" : "mute";
|
||||
const func = isMuted ? 'unmute' : 'mute';
|
||||
return api.hark[func](notif);
|
||||
}, [notif, api, isMuted]);
|
||||
|
||||
const { hovering, bind } = useHovering();
|
||||
|
||||
const changeMuteDesc = isMuted ? "Unmute" : "Mute";
|
||||
const changeMuteDesc = isMuted ? 'Unmute' : 'Mute';
|
||||
return (
|
||||
<Box
|
||||
width="100%"
|
||||
@ -126,7 +126,7 @@ export function Notification(props: NotificationProps) {
|
||||
</NotificationWrapper>
|
||||
);
|
||||
|
||||
if ("graph" in notification.index) {
|
||||
if ('graph' in notification.index) {
|
||||
const index = notification.index.graph;
|
||||
const c: GraphNotificationContents = (contents as any).graph;
|
||||
|
||||
@ -147,7 +147,7 @@ export function Notification(props: NotificationProps) {
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
if ("group" in notification.index) {
|
||||
if ('group' in notification.index) {
|
||||
const index = notification.index.group;
|
||||
const c: GroupNotificationContents = (contents as any).group;
|
||||
return (
|
||||
|
@ -1,25 +1,25 @@
|
||||
import React, { useCallback, useState, useRef } from "react";
|
||||
import React, { useCallback, useState, useRef, ReactElement } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Box, Col, Text, Row } from "@tlon/indigo-react";
|
||||
import { Link, Switch, Route } from "react-router-dom";
|
||||
import Helmet from "react-helmet";
|
||||
import { Link, Switch, Route } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { Body } from "~/views/components/Body";
|
||||
import { PropFunc } from "~/types/util";
|
||||
import Inbox from "./inbox";
|
||||
import NotificationPreferences from "./preferences";
|
||||
import { Dropdown } from "~/views/components/Dropdown";
|
||||
import { Formik } from "formik";
|
||||
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
import {useTutorialModal} from "~/views/components/useTutorialModal";
|
||||
import { Box, Col, Text, Row } from '@tlon/indigo-react';
|
||||
|
||||
const baseUrl = "/~notifications";
|
||||
import { Body } from '~/views/components/Body';
|
||||
import { PropFunc } from '~/types/util';
|
||||
import Inbox from './inbox';
|
||||
import NotificationPreferences from './preferences';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
|
||||
const baseUrl = '/~notifications';
|
||||
|
||||
const HeaderLink = React.forwardRef((
|
||||
props: PropFunc<typeof Text> & { view?: string; current: string },
|
||||
ref
|
||||
) => {
|
||||
): ReactElement => {
|
||||
const { current, view, ...textProps } = props;
|
||||
const to = view ? `${baseUrl}/${view}` : baseUrl;
|
||||
const active = view ? current === view : !current;
|
||||
@ -35,7 +35,7 @@ interface NotificationFilter {
|
||||
groups: string[];
|
||||
}
|
||||
|
||||
export default function NotificationsScreen(props: any) {
|
||||
export default function NotificationsScreen(props: any): ReactElement {
|
||||
const relativePath = (p: string) => baseUrl + p;
|
||||
|
||||
const [filter, setFilter] = useState<NotificationFilter>({ groups: [] });
|
||||
@ -43,20 +43,20 @@ export default function NotificationsScreen(props: any) {
|
||||
setFilter({ groups });
|
||||
};
|
||||
const onReadAll = useCallback(() => {
|
||||
props.api.hark.readAll()
|
||||
props.api.hark.readAll();
|
||||
}, []);
|
||||
const groupFilterDesc =
|
||||
filter.groups.length === 0
|
||||
? "All"
|
||||
? 'All'
|
||||
: filter.groups
|
||||
.map((g) => props.associations?.groups?.[g]?.metadata?.title)
|
||||
.join(", ");
|
||||
.map(g => props.associations?.groups?.[g]?.metadata?.title)
|
||||
.join(', ');
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
useTutorialModal('notifications', true, anchorRef.current);
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
path={[relativePath("/:view"), relativePath("")]}
|
||||
path={[relativePath('/:view'), relativePath('')]}
|
||||
render={(routeProps) => {
|
||||
const { view } = routeProps.match.params;
|
||||
return (
|
||||
@ -89,7 +89,8 @@ export default function NotificationsScreen(props: any) {
|
||||
</Box>
|
||||
</Row>
|
||||
<Row
|
||||
justifyContent="space-between">
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box
|
||||
mr="1"
|
||||
overflow="hidden"
|
||||
@ -136,7 +137,7 @@ export default function NotificationsScreen(props: any) {
|
||||
</Dropdown>
|
||||
</Row>
|
||||
</Row>
|
||||
{view === "preferences" && (
|
||||
{view === 'preferences' && (
|
||||
<NotificationPreferences
|
||||
graphConfig={props.notificationsGraphConfig}
|
||||
api={props.api}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { ReactElement, useCallback } from 'react';
|
||||
import { Form, FormikHelpers } from 'formik';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { Box, Col, ManagedCheckboxField as Checkbox } from "@tlon/indigo-react";
|
||||
import { Formik, Form, FormikHelpers } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import _ from "lodash";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { FormikOnBlur } from "~/views/components/FormikOnBlur";
|
||||
import { NotificationGraphConfig } from "~/types";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Col, ManagedCheckboxField as Checkbox } from '@tlon/indigo-react';
|
||||
import { NotificationGraphConfig } from '@urbit/api';
|
||||
|
||||
import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
|
||||
interface FormSchema {
|
||||
mentions: boolean;
|
||||
@ -24,21 +23,21 @@ interface NotificationPreferencesProps {
|
||||
|
||||
export default function NotificationPreferences(
|
||||
props: NotificationPreferencesProps
|
||||
) {
|
||||
): ReactElement {
|
||||
const { graphConfig, api, dnd } = props;
|
||||
|
||||
const initialValues: FormSchema = {
|
||||
mentions: graphConfig.mentions,
|
||||
watchOnSelf: graphConfig.watchOnSelf,
|
||||
dnd,
|
||||
watching: graphConfig.watching,
|
||||
watching: graphConfig.watching
|
||||
};
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||
console.log(values);
|
||||
try {
|
||||
let promises: Promise<any>[] = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
if (values.mentions !== graphConfig.mentions) {
|
||||
promises.push(api.hark.setMentions(values.mentions));
|
||||
}
|
||||
@ -46,7 +45,7 @@ export default function NotificationPreferences(
|
||||
promises.push(api.hark.setWatchOnSelf(values.watchOnSelf));
|
||||
}
|
||||
if (values.dnd !== dnd && !_.isUndefined(values.dnd)) {
|
||||
promises.push(api.hark.setDoNotDisturb(values.dnd))
|
||||
promises.push(api.hark.setDoNotDisturb(values.dnd));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
@ -1,30 +1,25 @@
|
||||
import React from "react";
|
||||
import * as Yup from "yup";
|
||||
import React, { ReactElement } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import _ from 'lodash';
|
||||
import { Formik } from 'formik';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
ManagedForm as Form,
|
||||
ManagedTextInputField as Input,
|
||||
ManagedCheckboxField as Checkbox,
|
||||
Center,
|
||||
Col,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
Button,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, FormikHelpers } from "formik";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { ColorInput } from "~/views/components/ColorInput";
|
||||
import { ImageInput } from "~/views/components/ImageInput";
|
||||
import { MarkdownField } from "~/views/apps/publish/components/MarkdownField";
|
||||
import { resourceFromPath } from "~/logic/lib/group";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import { ColorInput } from '~/views/components/ColorInput';
|
||||
import { ImageInput } from '~/views/components/ImageInput';
|
||||
import { MarkdownField } from '~/views/apps/publish/components/MarkdownField';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
nickname: Yup.string(),
|
||||
@ -45,8 +40,7 @@ const emptyContact = {
|
||||
isPublic: false
|
||||
};
|
||||
|
||||
|
||||
export function EditProfile(props: any) {
|
||||
export function EditProfile(props: any): ReactElement {
|
||||
const { contact, ship, api, isPublic } = props;
|
||||
const history = useHistory();
|
||||
if (contact) {
|
||||
@ -58,10 +52,10 @@ export function EditProfile(props: any) {
|
||||
try {
|
||||
await Object.keys(values).reduce((acc, key) => {
|
||||
console.log(key);
|
||||
const newValue = key !== "color" ? values[key] : uxToHex(values[key]);
|
||||
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
|
||||
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === "isPublic") {
|
||||
if (key === 'isPublic') {
|
||||
return acc.then(() =>
|
||||
api.contacts.setPublic(newValue)
|
||||
);
|
||||
@ -70,23 +64,22 @@ export function EditProfile(props: any) {
|
||||
console.log(toRemove);
|
||||
const toAdd: string[] = _.difference(newValue, contact?.groups || []);
|
||||
console.log(toAdd);
|
||||
let promises: Promise<any>[] = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
promises.concat(
|
||||
toRemove.map(e =>
|
||||
api.contacts.edit(ship, {'remove-group': resourceFromPath(e) })
|
||||
api.contacts.edit(ship, { 'remove-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
promises.concat(
|
||||
toAdd.map(e =>
|
||||
api.contacts.edit(ship, {'add-group': resourceFromPath(e) })
|
||||
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
return acc.then(() => Promise.all(promises));
|
||||
|
||||
} else if (
|
||||
key !== "last-updated" &&
|
||||
key !== "isPublic"
|
||||
key !== 'last-updated' &&
|
||||
key !== 'isPublic'
|
||||
) {
|
||||
return acc.then(() =>
|
||||
api.contacts.edit(ship, { [key]: newValue })
|
||||
@ -95,7 +88,7 @@ export function EditProfile(props: any) {
|
||||
}
|
||||
return acc;
|
||||
}, Promise.resolve());
|
||||
//actions.setStatus({ success: null });
|
||||
// actions.setStatus({ success: null });
|
||||
history.push(`/~profile/${ship}`);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -1,25 +1,23 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { ViewProfile } from './ViewProfile';
|
||||
import { EditProfile } from './EditProfile';
|
||||
import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
|
||||
import React, { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Row,
|
||||
BaseImage,
|
||||
StatelessTextInput as Input,
|
||||
Button,
|
||||
Text
|
||||
} from "@tlon/indigo-react";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {useTutorialModal} from "~/views/components/useTutorialModal";
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { ViewProfile } from './ViewProfile';
|
||||
import { EditProfile } from './EditProfile';
|
||||
import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
|
||||
export function Profile(props: any) {
|
||||
export function Profile(props: any): ReactElement {
|
||||
const { hideAvatars } = useLocalState(({ hideAvatars }) => ({
|
||||
hideAvatars
|
||||
}));
|
||||
@ -31,17 +29,13 @@ export function Profile(props: any) {
|
||||
const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const nacked = nackedContacts.has(ship);
|
||||
|
||||
const [statusModal, showStatusModal] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(hasLoaded && !contact && !nacked) {
|
||||
props.api.contacts.retrieve(ship);
|
||||
}
|
||||
}, [hasLoaded, contact])
|
||||
}, [hasLoaded, contact]);
|
||||
|
||||
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : "#000000";
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
const cover = (contact?.cover)
|
||||
? <BaseImage src={contact.cover} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Box display="block" width='100%' height='100%' backgroundColor='washedGray' />;
|
||||
@ -58,19 +52,24 @@ export function Profile(props: any) {
|
||||
<Center
|
||||
p={[0,4]}
|
||||
height="100%"
|
||||
width="100%">
|
||||
width="100%"
|
||||
>
|
||||
|
||||
<Box
|
||||
ref={anchorRef}
|
||||
maxWidth="600px"
|
||||
width="100%">
|
||||
width="100%"
|
||||
>
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
{ship === `~${window.ship}` ? (
|
||||
<Row>
|
||||
<Text
|
||||
py='2'
|
||||
cursor='pointer'
|
||||
onClick={() => { history.push(`/~profile/${ship}/edit`) }}>
|
||||
onClick={() => {
|
||||
history.push(`/~profile/${ship}/edit`);
|
||||
}}
|
||||
>
|
||||
Edit Profile
|
||||
</Text>
|
||||
<SetStatusBarModal
|
||||
@ -82,9 +81,12 @@ export function Profile(props: any) {
|
||||
/>
|
||||
</Row>
|
||||
) : null}
|
||||
<Text maxWidth='18rem' overflowX='hidden' textOverflow="ellipsis"
|
||||
<Text maxWidth='18rem' overflowX='hidden'
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden" display="inline-block" verticalAlign="middle">{contact?.status ?? ""}</Text>
|
||||
overflow="hidden" display="inline-block"
|
||||
verticalAlign="middle"
|
||||
>{contact?.status ?? ''}</Text>
|
||||
</Row>
|
||||
<Row width="100%" height="300px">
|
||||
{cover}
|
||||
@ -108,7 +110,8 @@ export function Profile(props: any) {
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}/>
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
) : (
|
||||
<ViewProfile
|
||||
api={props.api}
|
||||
|
@ -3,14 +3,13 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
ChangeEvent
|
||||
} from "react";
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Button,
|
||||
StatelessTextInput as Input,
|
||||
} from "@tlon/indigo-react";
|
||||
|
||||
StatelessTextInput as Input
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
export function SetStatus(props: any) {
|
||||
const { contact, ship, api, callback } = props;
|
||||
@ -23,11 +22,11 @@ export function SetStatus(props: any) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setStatus(!!contact ? contact.status : '');
|
||||
setStatus(contact ? contact.status : '');
|
||||
}, [contact]);
|
||||
|
||||
const editStatus = () => {
|
||||
api.contacts.edit(ship, {status: _status});
|
||||
api.contacts.edit(ship, { status: _status });
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
@ -53,7 +52,8 @@ export function SetStatus(props: any) {
|
||||
color="white"
|
||||
ml={2}
|
||||
width="25%"
|
||||
onClick={editStatus}>
|
||||
onClick={editStatus}
|
||||
>
|
||||
Set Status
|
||||
</Button>
|
||||
</Row>
|
||||
|
@ -1,28 +1,21 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
Button,
|
||||
Col,
|
||||
LoadingSpinner
|
||||
} from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {GroupSummary} from "~/views/landscape/components/GroupSummary";
|
||||
import {MetadataUpdatePreview} from "~/types";
|
||||
import {GroupLink} from "~/views/components/GroupLink";
|
||||
import {lengthOrder} from "~/logic/lib/util";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import RichText from '~/views/components/RichText';
|
||||
import { GroupLink } from '~/views/components/GroupLink';
|
||||
import { lengthOrder } from '~/logic/lib/util';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
|
||||
export function ViewProfile(props: any) {
|
||||
const history = useHistory();
|
||||
export function ViewProfile(props: any): ReactElement {
|
||||
const { hideNicknames } = useLocalState(({ hideNicknames }) => ({
|
||||
hideNicknames
|
||||
}));
|
||||
@ -33,17 +26,19 @@ export function ViewProfile(props: any) {
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%">
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%">
|
||||
<Text>
|
||||
{((!hideNicknames && contact?.nickname) ? contact.nickname : "")}
|
||||
{((!hideNicknames && contact?.nickname) ? contact.nickname : '')}
|
||||
</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%">
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%">
|
||||
<Text mono color="darkGray">{ship}</Text>
|
||||
</Center>
|
||||
@ -52,10 +47,11 @@ export function ViewProfile(props: any) {
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
width="100%">
|
||||
width="100%"
|
||||
>
|
||||
<Center flexDirection="column" maxWidth='32rem'>
|
||||
<RichText width='100%' disableRemoteContent>
|
||||
{(contact?.bio ? contact.bio : "")}
|
||||
{(contact?.bio ? contact.bio : '')}
|
||||
</RichText>
|
||||
</Center>
|
||||
</Col>
|
||||
@ -82,7 +78,8 @@ export function ViewProfile(props: any) {
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
border={1}
|
||||
borderColor="washedGray">
|
||||
borderColor="washedGray"
|
||||
>
|
||||
<Center height="100%">
|
||||
<Text mono pr={1} color="gray">{ship}</Text>
|
||||
<Text color="gray">remains private</Text>
|
||||
|
@ -1,34 +1,24 @@
|
||||
import React from "react";
|
||||
import { Route, Link } from "react-router-dom";
|
||||
import React from 'react';
|
||||
import { Route, Link } from 'react-router-dom';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { Box, Text, Row, Col, Icon, BaseImage } from "@tlon/indigo-react";
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
|
||||
import { Profile } from "./components/Profile";
|
||||
import useLocalState from "~/logic/state/local";
|
||||
import { Profile } from './components/Profile';
|
||||
|
||||
export default function ProfileScreen(props: any) {
|
||||
const { dark } = props;
|
||||
const hideAvatars = useLocalState(state => state.hideAvatars);
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile</title>
|
||||
</Helmet>
|
||||
<Route
|
||||
path={"/~profile/:ship/:edit?"}
|
||||
render={({ match, history }) => {
|
||||
path={'/~profile/:ship/:edit?'}
|
||||
render={({ match }) => {
|
||||
const ship = match.params.ship;
|
||||
const isEdit = match.url.includes('edit');
|
||||
const isPublic = props.isContactPublic;
|
||||
const contact = props.contacts?.[ship];
|
||||
const sigilColor = contact?.color
|
||||
? `#${uxToHex(contact.color)}`
|
||||
: dark
|
||||
? "#FFFFFF"
|
||||
: "#000000";
|
||||
|
||||
return (
|
||||
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import React from 'react';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { StoreState } from "~/logic/store/type";
|
||||
import { Association } from "~/types";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { NotebookRoutes } from "./components/NotebookRoutes";
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import { Association } from '@urbit/api';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { NotebookRoutes } from './components/NotebookRoutes';
|
||||
|
||||
type PublishResourceProps = StoreState & {
|
||||
association: Association;
|
||||
@ -16,7 +16,7 @@ type PublishResourceProps = StoreState & {
|
||||
export function PublishResource(props: PublishResourceProps) {
|
||||
const { association, api, baseUrl, notebooks } = props;
|
||||
const rid = association.resource;
|
||||
const [, , ship, book] = rid.split("/");
|
||||
const [, , ship, book] = rid.split('/');
|
||||
const notebookContacts = props.contacts[association.group];
|
||||
|
||||
return (
|
||||
|
@ -1,12 +1,16 @@
|
||||
import React from "react";
|
||||
import React, { ReactElement } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { PostFormSchema, PostForm } from "./NoteForm";
|
||||
import { FormikHelpers } from "formik";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { RouteComponentProps, useLocation } from "react-router-dom";
|
||||
import { GraphNode, TextContent, Association, S3State } from "~/types";
|
||||
import { getLatestRevision, editPost } from "~/logic/lib/publish";
|
||||
import {useWaitForProps} from "~/logic/lib/useWaitForProps";
|
||||
import { FormikHelpers } from 'formik';
|
||||
import { RouteComponentProps, useLocation } from 'react-router-dom';
|
||||
|
||||
import { GraphNode } from '@urbit/api';
|
||||
|
||||
import { PostFormSchema, PostForm } from './NoteForm';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { getLatestRevision, editPost } from '~/logic/lib/publish';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { S3State } from '~/types';
|
||||
|
||||
interface EditPostProps {
|
||||
ship: string;
|
||||
noteId: number;
|
||||
@ -16,7 +20,7 @@ interface EditPostProps {
|
||||
s3: S3State;
|
||||
}
|
||||
|
||||
export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
export function EditPost(props: EditPostProps & RouteComponentProps): ReactElement {
|
||||
const { note, book, noteId, api, ship, history, s3 } = props;
|
||||
const [revNum, title, body] = getLatestRevision(note);
|
||||
const location = useLocation();
|
||||
@ -24,19 +28,19 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
const waiter = useWaitForProps(props);
|
||||
const initial: PostFormSchema = {
|
||||
title,
|
||||
body,
|
||||
body
|
||||
};
|
||||
|
||||
const onSubmit = async (
|
||||
values: PostFormSchema,
|
||||
actions: FormikHelpers<PostFormSchema>
|
||||
) => {
|
||||
): Promise<void> => {
|
||||
const { title, body } = values;
|
||||
try {
|
||||
const newRev = revNum + 1;
|
||||
const nodes = editPost(newRev, noteId, title, body);
|
||||
await api.graph.addNodes(ship, book, nodes);
|
||||
await waiter(p => {
|
||||
await waiter((p) => {
|
||||
const [rev] = getLatestRevision(p.note);
|
||||
return rev === newRev;
|
||||
});
|
||||
@ -44,7 +48,7 @@ export function EditPost(props: EditPostProps & RouteComponentProps) {
|
||||
history.push(noteUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: "Failed to edit notebook" });
|
||||
actions.setStatus({ error: 'Failed to edit notebook' });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,26 +1,26 @@
|
||||
import React, { createRef, useCallback, useRef } from "react";
|
||||
import { IUnControlledCodeMirror, UnControlled as CodeEditor } from "react-codemirror2";
|
||||
import React, { createRef, useCallback, useRef } from 'react';
|
||||
import { IUnControlledCodeMirror, UnControlled as CodeEditor } from 'react-codemirror2';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Prompt } from 'react-router-dom';
|
||||
import { Editor } from 'codemirror';
|
||||
|
||||
import { MOBILE_BROWSER_REGEX, usePreventWindowUnload } from "~/logic/lib/util";
|
||||
import { PropFunc } from "~/types/util";
|
||||
import CodeMirror from "codemirror";
|
||||
import { MOBILE_BROWSER_REGEX, usePreventWindowUnload } from '~/logic/lib/util';
|
||||
import { PropFunc } from '~/types/util';
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import "codemirror/mode/markdown/markdown";
|
||||
import "codemirror/addon/display/placeholder";
|
||||
import "codemirror/addon/edit/continuelist";
|
||||
import 'codemirror/mode/markdown/markdown';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/addon/edit/continuelist';
|
||||
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { useFileDrag } from "~/logic/lib/useDrag";
|
||||
import SubmitDragger from "~/views/components/SubmitDragger";
|
||||
import useS3 from "~/logic/lib/useS3";
|
||||
import { S3State } from "~/types";
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { useFileDrag } from '~/logic/lib/useDrag';
|
||||
import SubmitDragger from '~/views/components/SubmitDragger';
|
||||
import useS3 from '~/logic/lib/useS3';
|
||||
import { S3State } from '@urbit/api';
|
||||
|
||||
const MARKDOWN_CONFIG = {
|
||||
name: "markdown",
|
||||
name: 'markdown'
|
||||
};
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
@ -49,12 +49,12 @@ export function MarkdownEditor(
|
||||
|
||||
const options = {
|
||||
mode: MARKDOWN_CONFIG,
|
||||
theme: "tlon",
|
||||
theme: 'tlon',
|
||||
lineNumbers: false,
|
||||
lineWrapping: true,
|
||||
scrollbarStyle: "native",
|
||||
scrollbarStyle: 'native',
|
||||
// cursorHeight: 0.85,
|
||||
placeholder: placeholder || "",
|
||||
placeholder: placeholder || '',
|
||||
extraKeys: { 'Enter': 'newlineAndIndentContinueMarkdownList' }
|
||||
};
|
||||
|
||||
@ -84,7 +84,7 @@ export function MarkdownEditor(
|
||||
const codeMirror: Editor = editor.current.editor;
|
||||
const doc = codeMirror.getDoc();
|
||||
|
||||
Array.from(files).forEach(async file => {
|
||||
Array.from(files).forEach(async (file) => {
|
||||
const placeholder = `![Uploading ${file.name}](...)`;
|
||||
doc.setValue(doc.getValue() + placeholder);
|
||||
const url = await uploadDefault(file);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React, { useCallback } from "react";
|
||||
import _ from "lodash";
|
||||
import { Box, ErrorLabel } from "@tlon/indigo-react";
|
||||
import { useField } from "formik";
|
||||
import { MarkdownEditor } from "./MarkdownEditor";
|
||||
import React, { useCallback } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { Box, ErrorLabel } from '@tlon/indigo-react';
|
||||
import { useField } from 'formik';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
|
||||
export const MarkdownField = ({
|
||||
id,
|
||||
@ -13,13 +13,13 @@ export const MarkdownField = ({
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e: any) => {
|
||||
_.set(e, "target.id", id);
|
||||
_.set(e, 'target.id', id);
|
||||
onBlur && onBlur(e);
|
||||
},
|
||||
[onBlur, id]
|
||||
);
|
||||
|
||||
const hasError = !!(error && touched);
|
||||
const hasError = Boolean(error && touched);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -31,13 +31,13 @@ export const MarkdownField = ({
|
||||
{...rest}
|
||||
>
|
||||
<MarkdownEditor
|
||||
borderColor={hasError ? "red" : "lightGray"}
|
||||
borderColor={hasError ? 'red' : 'lightGray'}
|
||||
onBlur={handleBlur}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
s3={s3}
|
||||
/>
|
||||
<ErrorLabel mt="2" hasError={!!(error && touched)}>
|
||||
<ErrorLabel mt="2" hasError={Boolean(error && touched)}>
|
||||
{error}
|
||||
</ErrorLabel>
|
||||
</Box>
|
||||
|
@ -12,10 +12,10 @@ import {
|
||||
import { Formik, Form, useFormikContext, FormikHelpers } from "formik";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Notebook } from "~/types/publish-update";
|
||||
import { Contacts } from "~/types/contact-update";
|
||||
import { Contacts } from "@urbit/api/contacts";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { RouteComponentProps, useHistory } from "react-router-dom";
|
||||
import {Association} from "~/types";
|
||||
import {Association} from "@urbit/api";
|
||||
import { uxToHex } from "~/logic/lib/util";
|
||||
|
||||
interface MetadataFormProps {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Box, Text, Col } from "@tlon/indigo-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Text, Col } from '@tlon/indigo-react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import bigInt from 'big-integer';
|
||||
|
||||
import { Link, RouteComponentProps } from "react-router-dom";
|
||||
import { Spinner } from "~/views/components/Spinner";
|
||||
import { Comments } from "~/views/components/Comments";
|
||||
import { NoteNavigation } from "./NoteNavigation";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { Link, RouteComponentProps } from 'react-router-dom';
|
||||
import { Spinner } from '~/views/components/Spinner';
|
||||
import { Comments } from '~/views/components/Comments';
|
||||
import { NoteNavigation } from './NoteNavigation';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { getLatestRevision, getComments } from '~/logic/lib/publish';
|
||||
import Author from "~/views/components/Author";
|
||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from "~/types";
|
||||
import Author from '~/views/components/Author';
|
||||
import { Contacts, GraphNode, Graph, Association, Unreads, Group } from '@urbit/api';
|
||||
|
||||
interface NoteProps {
|
||||
ship: string;
|
||||
@ -34,12 +34,11 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
|
||||
const deletePost = async () => {
|
||||
setDeleting(true);
|
||||
const indices = [note.post.index]
|
||||
const indices = [note.post.index];
|
||||
await api.graph.removeNodes(ship, book, indices);
|
||||
props.history.push(rootUrl);
|
||||
};
|
||||
|
||||
|
||||
const comments = getComments(note);
|
||||
const [revNum, title, body, post] = getLatestRevision(note);
|
||||
const index = note.post.index.split('/');
|
||||
@ -49,8 +48,6 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
api.hark.markEachAsRead(props.association, '/',`/${index[1]}/1/1`, 'note', 'publish');
|
||||
}, [props.association, props.note]);
|
||||
|
||||
|
||||
|
||||
let adminLinks: JSX.Element | null = null;
|
||||
if (window.ship === note?.post?.author) {
|
||||
adminLinks = (
|
||||
@ -67,7 +64,7 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
color="red"
|
||||
ml={2}
|
||||
onClick={deletePost}
|
||||
style={{ cursor: "pointer" }}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
Delete
|
||||
</Text>
|
||||
@ -96,21 +93,21 @@ export function Note(props: NoteProps & RouteComponentProps) {
|
||||
ref={windowRef}
|
||||
>
|
||||
<Link to={rootUrl}>
|
||||
<Text>{"<- Notebook Index"}</Text>
|
||||
<Text>{'<- Notebook Index'}</Text>
|
||||
</Link>
|
||||
<Col>
|
||||
<Text display="block" mb={2}>{title || ""}</Text>
|
||||
<Text display="block" mb={2}>{title || ''}</Text>
|
||||
<Box display="flex">
|
||||
<Author
|
||||
ship={post?.author}
|
||||
contacts={contacts}
|
||||
date={post?.["time-sent"]}
|
||||
date={post?.['time-sent']}
|
||||
/>
|
||||
<Text ml={2}>{adminLinks}</Text>
|
||||
</Box>
|
||||
</Col>
|
||||
<Box color="black" className="md" style={{ overflowWrap: "break-word", overflow: "hidden" }}>
|
||||
<ReactMarkdown source={body} linkTarget={"_blank"} />
|
||||
<Box color="black" className="md" style={{ overflowWrap: 'break-word', overflow: 'hidden' }}>
|
||||
<ReactMarkdown source={body} linkTarget={'_blank'} />
|
||||
</Box>
|
||||
<NoteNavigation
|
||||
notebook={notebook}
|
||||
|
@ -5,11 +5,11 @@ import {
|
||||
Row,
|
||||
Col,
|
||||
Button
|
||||
} from "@tlon/indigo-react";
|
||||
import { AsyncButton } from "../../../components/AsyncButton";
|
||||
import { Formik, Form, FormikHelpers } from "formik";
|
||||
import { MarkdownField } from "./MarkdownField";
|
||||
import { S3State } from "~/types";
|
||||
} from '@tlon/indigo-react';
|
||||
import { AsyncButton } from '../../../components/AsyncButton';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import { MarkdownField } from './MarkdownField';
|
||||
import { S3State } from '@urbit/api';
|
||||
|
||||
interface PostFormProps {
|
||||
initial: PostFormSchema;
|
||||
@ -63,7 +63,8 @@ export function PostForm(props: PostFormProps) {
|
||||
onClick={() => {
|
||||
history.goBack();
|
||||
}}
|
||||
type="button">Cancel</Button>}
|
||||
type="button"
|
||||
>Cancel</Button>}
|
||||
</Row>
|
||||
</Row>
|
||||
<MarkdownField flexGrow={1} id="body" s3={s3} />
|
||||
|
@ -1,29 +1,31 @@
|
||||
import React, { Component } from "react";
|
||||
import moment from "moment";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Graph, GraphNode } from "~/types";
|
||||
import { getLatestRevision } from "~/logic/lib/publish";
|
||||
import { BigInteger } from "big-integer";
|
||||
import React, { ReactElement } from 'react';
|
||||
import moment from 'moment';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { BigInteger } from 'big-integer';
|
||||
|
||||
import { Box } from '@tlon/indigo-react';
|
||||
import { Graph } from '@urbit/api';
|
||||
|
||||
import { getLatestRevision } from '~/logic/lib/publish';
|
||||
|
||||
function NavigationItem(props: {
|
||||
url: string;
|
||||
title: string;
|
||||
date: number;
|
||||
prev?: boolean;
|
||||
}) {
|
||||
}): ReactElement {
|
||||
const date = moment(props.date).fromNow();
|
||||
return (
|
||||
<Box
|
||||
justifySelf={props.prev ? "start" : "end"}
|
||||
justifySelf={props.prev ? 'start' : 'end'}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
justifyContent="flex-end"
|
||||
textAlign={props.prev ? "left" : "right"}
|
||||
textAlign={props.prev ? 'left' : 'right'}
|
||||
>
|
||||
<Link to={props.url}>
|
||||
<Box color="gray" mb={2}>
|
||||
{props.prev ? "Previous" : "Next"}
|
||||
{props.prev ? 'Previous' : 'Next'}
|
||||
</Box>
|
||||
<Box mb={1}>{props.title}</Box>
|
||||
<Box color="gray">{date}</Box>
|
||||
@ -53,7 +55,7 @@ interface NoteNavigationProps {
|
||||
baseUrl: string;
|
||||
}
|
||||
|
||||
export function NoteNavigation(props: NoteNavigationProps) {
|
||||
export function NoteNavigation(props: NoteNavigationProps): ReactElement {
|
||||
let nextComponent = <Box />;
|
||||
let prevComponent = <Box />;
|
||||
const { noteId, notebook } = props;
|
||||
@ -72,13 +74,13 @@ export function NoteNavigation(props: NoteNavigationProps) {
|
||||
if (next && nextId) {
|
||||
const nextUrl = makeNoteUrl(nextId);
|
||||
const [, title, , post] = getLatestRevision(next);
|
||||
const date = post["time-sent"];
|
||||
const date = post['time-sent'];
|
||||
nextComponent = <NavigationItem title={title} date={date} url={nextUrl} />;
|
||||
}
|
||||
if (prev && prevId) {
|
||||
const prevUrl = makeNoteUrl(prevId);
|
||||
const [, title, , post] = getLatestRevision(prev);
|
||||
const date = post["time-sent"];
|
||||
const date = post['time-sent'];
|
||||
prevComponent = (
|
||||
<NavigationItem title={title} date={date} url={prevUrl} prev />
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user