Merge remote-tracking branch 'origin/release/next-js' into james/transclusion-polish

This commit is contained in:
James Acklin 2021-05-06 18:16:21 -04:00
commit ff4406a362
292 changed files with 6306 additions and 17094 deletions

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2773b91958c18c7537cb568e01bcf83056447bfef981c5ed4b6688cc3ab69936
size 10739521
oid sha256:90dd5f5b2821f1a057c053b141e1143f019193c8dd7da41b39b0a3799e0fda5a
size 10999106

View File

@ -5,7 +5,7 @@
/- glob
/+ default-agent, verb, dbug
|%
++ hash 0v2.9br5i.hl1e5.cda79.75gdi.jj5hc :: DO NOT MOVE FROM LINE 8
++ hash 0v7.k043v.fjsi2.bpm4g.0ekbj.566c4 :: DO NOT MOVE FROM LINE 8
+$ state-0 [%0 hash=@uv glob=(unit (each glob:glob tid=@ta))]
+$ all-states
$% state-0

View File

@ -24,6 +24,6 @@
<div id="portal-root"></div>
<script src="/~landscape/js/channel.js"></script>
<script src="/~landscape/js/session.js"></script>
<script src="/~landscape/js/bundle/index.2eee573a41d00825e88a.js"></script>
<script src="/~landscape/js/bundle/index.0a121973708299f6b966.js"></script>
</body>
</html>

View File

@ -3,15 +3,14 @@
:::: /hoon/code/hood/gen
::
/? 310
::
::::
::
:- %say
/- *sole
/+ *generators
:- %ask
|= $: [now=@da eny=@uvJ bec=beak]
[arg=?(~ [%reset ~]) ~]
==
=* our p.bec
:- %helm-code
^- (sole-result [%helm-code ?(~ %reset)])
?~ arg
=/ code=tape
%+ slag 1
@ -20,11 +19,23 @@
=/ step=tape
%+ scow %ud
.^(@ud %j /(scot %p our)/step/(scot %da now)/(scot %p our))
%- %- slog
:~ [%leaf code]
[%leaf (weld "current step=" step)]
[%leaf "use |code %reset to invalidate this and generate a new code"]
==
~
::
%+ print 'use |code %reset to invalidate this and generate a new code'
%+ print leaf+(weld "current step=" step)
%+ print leaf+code
(produce [%helm-code ~])
::
?> =(%reset -.arg)
%reset
%+ print 'continue?'
%+ print 'warning: resetting your code closes all web sessions'
%+ prompt
[%& %project "y/n: "]
%+ parse
;~ pose
(cold %.y (mask "yY"))
(cold %.n (mask "nN"))
==
|= reset=?
?. reset
no-product
(produce [%helm-code %reset])

View File

@ -114,6 +114,15 @@
state-3
state-4
==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
:: - Fixing incorrectly held unversioned subscriptions
::
++ diplomatic
^- ?
%.n
::
++ default
|* [pull-hook=* =config]
@ -239,6 +248,7 @@
=/ kick=(list card)
?: ?& =(min-version.config prev-min-version.old)
=(version.config prev-version.old)
diplomatic
==
~
(poke-self:pass kick+!>(%kick))^~
@ -439,6 +449,7 @@
?~ tan tr-core
?. versioned
(tr-ap-og:tr-cleanup |.((on-pull-nack:og rid u.tan)))
%- (slog leaf+"versioned nack for {<rid>} in {<dap.bowl>}" u.tan)
=/ pax
(kick-mule:virt rid |.((on-pull-kick:og rid)))
?~ pax tr-failed-kick
@ -463,18 +474,18 @@
:: subscription
tr-core
(tr-suspend-pub-ver min-version.config)
=/ =vase
=/ =^cage
(convert-to:ver cage)
=/ =wire
(make-wire /store)
=+ resources=(~(gas in *(set resource)) (resource-for-update:og vase))
=+ resources=(~(gas in *(set resource)) (resource-for-update:og q.cage))
?> ?| no-validate.config
?& (check-src resources)
(~(has in resources) rid)
== ==
=/ =mark
(append-version:ver version.config)
(tr-emit (~(poke-our pass wire) store-name.config mark vase))
(tr-emit (~(poke-our pass wire) store-name.config cage))
--
::
++ tr-kick

View File

@ -73,6 +73,16 @@
state-1
state-2
==
:: +diplomatic: only renegotiate if versions changed
::
:: If %.n please leave note as to why renegotiation necessary
::
:: - Fixing incorrectly held unversioned subscriptions
::
++ diplomatic
^- ?
%.n
::
++ push-hook
|* =config
$_ ^|
@ -221,6 +231,7 @@
|= [prev-min-version=@ud prev-version=@ud]
?: ?& =(min-version.config prev-min-version)
=(prev-version version.config)
diplomatic
==
:: bail on kick if we didn't change versions
~
@ -291,23 +302,21 @@
?. (supported:ver mark)
:_ this
(fact-init-kick:io version+!>(min-version.config))
=/ =vase
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
:_ this
[%give %fact ~ mark vase]~
=- [%give %fact ~ -]~
(convert-to:ver mark (initial-watch:og t.t.t.t.t.t.path resource))
::
++ unversioned
?> ?=([%ship @ @ *] t.path)
?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
`this
=/ =resource
(de-path:resource t.path)
=/ =vase
%+ convert-to:ver update-mark.config
=/ =vase
(initial-watch:og t.t.t.t.path resource)
:_ this
[%give %fact ~ update-mark.config vase]~
?. =(min-version.config 0)
~& >>> "unversioned req from: {<src.bowl>}, nooping"
~
[%give %fact ~ (convert-to:ver update-mark.config vase)]~
--
::
++ on-agent
@ -461,10 +470,7 @@
|= [fact-ver=@ud paths=(set path)]
=/ =mark
(append-version:ver fact-ver)
=/ =^cage
:- mark
(convert-from:ver mark q.cage)
(fact:io cage ~(tap in paths))
(fact:io (convert-from:ver mark q.cage) ~(tap in paths))
:: TODO: deprecate
++ unversioned
?. =(min-version.config 0) ~
@ -474,18 +480,15 @@
%- ~(gas in *(set path))
(turn (incoming-subscriptions prefix) tail)
?: =(0 ~(wyt in unversioned)) ~
=/ =^cage
:- update-mark.config
(convert-from:ver update-mark.config q.cage)
(fact:io cage ~(tap in unversioned))^~
(fact:io (convert-from:ver update-mark.config q.cage) ~(tap in unversioned))^~
--
::
++ forward-update
|= =cage
^- (list card:agent:gall)
=- lis
=/ vas
(convert-to:ver cage)
=/ vas=vase
q:(convert-to:ver cage)
%+ roll (resource-for-update q.cage)
|= [rid=resource [lis=(list card:agent:gall) tf-vas=(unit vase)]]
^- [(list card:agent:gall) (unit vase)]

View File

@ -29,11 +29,12 @@
&((gte ver min) (lte ver version))
::
++ convert-to
|= =cage
^- vase
?: =(p.cage current-version)
q.cage
((tube-to p.cage) q.cage)
|= [=mark =vase]
^- cage
:- current-version
?: =(mark current-version)
vase
((tube-to mark) vase)
::
++ tube-to
|= =mark
@ -44,10 +45,11 @@
.^(tube:clay %cc (scry:io %home /[current-version]/[mark]))
::
++ convert-from
|= =cage
^- vase
?: =(p.cage current-version)
q.cage
((tube-from p.cage) q.cage)
|= [=mark =vase]
^- cage
:- mark
?: =(mark current-version)
vase
((tube-from mark) vase)
--

View File

@ -1,3 +1,3 @@
module.exports = {
extends: "@urbit"
};
extends: '@urbit'
};

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,6 @@
"formik": "^2.1.5",
"immer": "^8.0.1",
"lodash": "^4.17.20",
"markdown-to-jsx": "^6.11.4",
"moment": "^2.29.1",
"mousetrap": "^1.6.5",
"mousetrap-global-bind": "^1.1.0",
@ -42,13 +41,15 @@
"react-use-gesture": "^9.1.3",
"react-virtuoso": "^0.20.3",
"react-visibility-sensor": "^5.1.1",
"remark": "^12.0.0",
"remark-breaks": "^2.0.1",
"remark-disable-tokenizers": "^1.0.24",
"remark-disable-tokenizers": "1.1.0",
"stacktrace-js": "^2.0.2",
"style-loader": "^1.3.0",
"styled-components": "^5.1.1",
"styled-system": "^5.1.5",
"suncalc": "^1.8.0",
"unist-util-visit": "^3.0.0",
"urbit-ob": "^5.0.1",
"workbox-core": "^6.0.2",
"workbox-precaching": "^6.0.2",

View File

@ -1,9 +1,7 @@
import './wdyr';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import './register-sw';
import App from './views/App';
import './wdyr';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@ -1,5 +1,5 @@
import { Path, Patp } from '@urbit/api';
import _ from 'lodash';
import { Patp, Path } from '@urbit/api';
import BaseStore from '../store/base';
export default class BaseApi<S extends object = {}> {

View File

@ -1,8 +1,8 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Patp } from '@urbit/api';
import { ContactEditField } from '@urbit/api/contacts';
import _ from 'lodash';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class ContactsApi extends BaseApi<StoreState> {
add(ship: Patp, contact: any) {
@ -78,17 +78,17 @@ export default class ContactsApi extends BaseApi<StoreState> {
return _.compact(
await Promise.all(
ships.map(
async s => {
async (s) => {
const ship = `~${s}`;
if(s === window.ship) {
return null
return null;
}
const allowed = await this.fetchIsAllowed(
`~${window.ship}`,
'personal',
ship,
true
)
);
return allowed ? null : ship;
}
)

View File

@ -1,7 +1,5 @@
import type { StoreState } from '../store/type';
import BaseApi from './base';
import type {StoreState} from '../store/type';
import type {GcpToken} from '../../types/gcp-state';
export default class GcpApi extends BaseApi<StoreState> {
// Does not touch the store; use the value manually.
@ -18,4 +16,4 @@ export default class GcpApi extends BaseApi<StoreState> {
});
});
}
};
}

View File

@ -1,17 +1,17 @@
import { Patp } from '@urbit/api';
import BaseApi from './base';
import { StoreState } from '../store/type';
import GlobalStore from '../store/store';
import LocalApi from './local';
import InviteApi from './invite';
import MetadataApi from './metadata';
import { StoreState } from '../store/type';
import BaseApi from './base';
import ContactsApi from './contacts';
import GroupsApi from './groups';
import LaunchApi from './launch';
import GraphApi from './graph';
import S3Api from './s3';
import GcpApi from './gcp';
import GraphApi from './graph';
import GroupsApi from './groups';
import { HarkApi } from './hark';
import InviteApi from './invite';
import LaunchApi from './launch';
import LocalApi from './local';
import MetadataApi from './metadata';
import S3Api from './s3';
import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> {

View File

@ -1,10 +1,9 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Patp, Path, Resource } from '@urbit/api';
import { Content, Enc, GraphNode, GroupPolicy, Path, Patp, Post, Resource } from '@urbit/api';
import _ from 'lodash';
import { decToUd, deSig, resourceAsPath, unixToDa } from '~/logic/lib/util';
import { makeResource, resourceFromPath } from '../lib/group';
import { GroupPolicy, Enc, Post, Content, GraphNode } from '@urbit/api';
import { numToUd, unixToDa, decToUd, deSig, resourceAsPath } from '~/logic/lib/util';
import { StoreState } from '../store/type';
import BaseApi from './base';
export const createBlankNodeWithChildPost = (
parentIndex = '',
@ -185,13 +184,12 @@ export default class GraphApi extends BaseApi<StoreState> {
});
}
eval(cord: string) {
eval(cord: string): Promise<string[] | undefined> {
return this.spider('graph-view-action', 'tang', 'graph-eval', {
eval: cord
});
}
addGraph(ship: Patp, name: string, graph: any, mark: any) {
return this.storeAction({
'add-graph': {
@ -265,7 +263,7 @@ export default class GraphApi extends BaseApi<StoreState> {
'resource',
'graph-create-group-feed',
{
"create-group-feed": { resource: group, vip }
'create-group-feed': { resource: group, vip }
}
);
return resource;
@ -277,12 +275,11 @@ export default class GraphApi extends BaseApi<StoreState> {
'json',
'graph-disable-group-feed',
{
"disable-group-feed": { resource: group }
'disable-group-feed': { resource: group }
}
);
}
removePosts(ship: Patp, name: string, indices: string[]) {
return this.hookAction(ship, {
'remove-posts': {
@ -369,7 +366,7 @@ export default class GraphApi extends BaseApi<StoreState> {
const node = data['graph-update'];
this.store.handleEvent({
data: {
"graph-update-loose": node
'graph-update-loose': node
}
});
}

View File

@ -1,14 +1,14 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Path, Patp, Enc } from '@urbit/api';
import { Enc, Patp } from '@urbit/api';
import {
GroupAction,
GroupPolicy,
Resource,
Tag,
GroupPolicyDiff
GroupPolicyDiff, Resource,
Tag
} from '@urbit/api/groups';
import { makeResource } from '../lib/group';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class GroupsApi extends BaseApi<StoreState> {
remove(resource: Resource, ships: Patp[]) {

View File

@ -1,10 +1,10 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { dateToDa, decToUd } from '../lib/util';
import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api';
import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
import { BigInteger } from 'big-integer';
import { getParentIndex } from '../lib/notification';
import { dateToDa, decToUd } from '../lib/util';
import useHarkState from '../state/hark';
import { StoreState } from '../store/type';
import BaseApi from './base';
function getHarkSize() {
return useHarkState.getState().notifications.size ?? 0;
@ -75,7 +75,6 @@ export class HarkApi extends BaseApi<StoreState> {
graph: {
graph: association.resource,
group: association.group,
module: association.metadata.module,
description,
index: parent
} }

View File

@ -1,6 +1,6 @@
import BaseApi from './base';
import { Serial } from '@urbit/api';
import { StoreState } from '../store/type';
import { Serial, Path } from '@urbit/api';
import BaseApi from './base';
export default class InviteApi extends BaseApi<StoreState> {
accept(app: string, uid: Serial) {

View File

@ -1,5 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LaunchApi extends BaseApi<StoreState> {
add(name: string, tile = { basic : { title: '', linkedUrl: '', iconUrl: '' } }) {

View File

@ -1,5 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class LocalApi extends BaseApi<StoreState> {
getBaseHash() {

View File

@ -1,8 +1,8 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Path, Patp, Association, Metadata, MetadataUpdatePreview } from '@urbit/api';
import { Association, Metadata, MetadataUpdatePreview, Path } from '@urbit/api';
import { uxToHex } from '../lib/util';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class MetadataApi extends BaseApi<StoreState> {
metadataAdd(appName: string, resource: Path, group: Path, title: string, description: string, dateCreated: string, color: string, moduleName: string) {

View File

@ -1,6 +1,5 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { S3Update } from '../../types/s3-update';
import BaseApi from './base';
export default class S3Api extends BaseApi<StoreState> {
setCurrentBucket(bucket: string) {

View File

@ -1,10 +1,10 @@
import BaseApi from './base';
import { StoreState } from '../store/type';
import { Key,
Value,
Bucket,
SettingsUpdate
import {
Bucket, Key,
SettingsUpdate, Value
} from '@urbit/api/settings';
import { StoreState } from '../store/type';
import BaseApi from './base';
export default class SettingsApi extends BaseApi<StoreState> {
private storeAction(action: SettingsUpdate): Promise<any> {

View File

@ -7,14 +7,12 @@
//
import querystring from 'querystring';
import {
StorageAcl,
StorageClient,
StorageUpload,
UploadParams,
UploadResult
} from './StorageClient';
const ENDPOINT = 'storage.googleapis.com';
class GcpUpload implements StorageUpload {
@ -27,7 +25,7 @@ class GcpUpload implements StorageUpload {
}
async promise(): Promise<UploadResult> {
const {Bucket, Key, ContentType, Body} = this.#params;
const { Bucket, Key, ContentType, Body } = this.#params;
const urlParams = {
uploadType: 'media',
name: Key,
@ -50,7 +48,7 @@ class GcpUpload implements StorageUpload {
console.error('GcpClient server error', await response.json());
throw new Error(`GcpClient: response ${response.status}`);
}
return {Location: `https://${ENDPOINT}/${Bucket}/${Key}`};
return { Location: `https://${ENDPOINT}/${Bucket}/${Key}` };
}
}

View File

@ -1,14 +1,13 @@
// Defines a StorageClient interface interoperable between S3 and GCP Storage.
//
// XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'.
// Rather than write a wrapper around S3, we offer this field here, which
// should always be passed, and will be replaced by 'publicRead' in the
// GCP client.
export enum StorageAcl {
PublicRead = 'public-read'
};
}
export interface UploadParams {
Bucket: string; // the bucket to upload the object to
@ -16,17 +15,17 @@ export interface UploadParams {
ContentType: string; // the object's mime-type
ACL: StorageAcl; // ACL, always 'public-read'
Body: File; // the object itself
};
}
export interface UploadResult {
Location: string;
};
}
// Extra layer of indirection used by S3 client.
export interface StorageUpload {
promise(): Promise<UploadResult>;
};
}
export interface StorageClient {
upload(params: UploadParams): StorageUpload;
};
}

View File

@ -1,4 +1,4 @@
import bigInt, { BigInteger } from 'big-integer';
import { BigInteger } from 'big-integer';
export function max(a: BigInteger, b: BigInteger) {
return a.gt(b) ? a : b;

View File

@ -1,4 +1,4 @@
import React from "react";
import React from 'react';
export type SubmitHandler = () => Promise<any>;
interface IFormGroupContext {
@ -12,7 +12,7 @@ const fallback: IFormGroupContext = {
addSubmit: () => {},
onDirty: () => {},
onErrors: () => {},
submitAll: () => Promise.resolve(),
submitAll: () => Promise.resolve()
};
export const FormGroupContext = React.createContext(fallback);

View File

@ -15,7 +15,6 @@
import GlobalApi from '../api/global';
import useStorageState from '../state/storage';
class GcpManager {
#api: GlobalApi | null = null;
@ -59,15 +58,15 @@ class GcpManager {
this.start();
}
#consecutiveFailures: number = 0;
#configured: boolean = false;
#consecutiveFailures = 0;
#configured = false;
private refreshLoop() {
if (!this.#configured) {
this.#api!.gcp.isConfigured()
.then((configured) => {
if (configured === undefined) {
throw new Error("can't check whether GCP is configured?");
throw new Error('can\'t check whether GCP is configured?');
}
this.#configured = configured;
if (this.#configured) {

View File

@ -1,7 +1,6 @@
import { Path, PatpNoSig } from '@urbit/api';
import { Group, Resource, roleTags, RoleTags } from '@urbit/api/groups';
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,

View File

@ -1,6 +1,6 @@
import { IndexedNotification, NotificationGraphConfig, Unreads } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer';
import f from 'lodash/fp';
import { Unreads, NotificationGraphConfig, IndexedNotification } from '@urbit/api';
export function getLastSeen(
unreads: Unreads,
@ -36,13 +36,13 @@ export function getNotificationCount(
}
export function isWatching(
config: NotificationGraphConfig,
config: NotificationGraphConfig,
graph: string,
index = "/"
index = '/'
) {
return !!config.watching.find(
return Boolean(config.watching.find(
watch => watch.graph === graph && watch.index === index
);
));
}
export function getNotificationKey(time: BigInteger, notification: IndexedNotification): string {

View File

@ -1,9 +1,9 @@
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';
export function useIdlingState() {
const [idling, setIdling] = useState(false);
useEffect(() => {
useEffect(() => {
function blur() {
setIdling(true);
}
@ -16,7 +16,7 @@ export function useIdlingState() {
return () => {
window.removeEventListener('blur', blur);
window.removeEventListener('focus', focus);
}
};
}, []);
return idling;

View File

@ -1,15 +1,15 @@
import useLocalState, { LocalState } from "~/logic/state/local";
import useSettingsState from "~/logic/state/settings";
import GlobalApi from "../api/global";
import { BackgroundConfig, RemoteContentPolicy } from "~/types";
import useLocalState from '~/logic/state/local';
import useSettingsState from '~/logic/state/settings';
import { BackgroundConfig, RemoteContentPolicy } from '~/types';
import GlobalApi from '../api/global';
const getBackgroundString = (bg: BackgroundConfig) => {
if (bg?.type === "url") {
if (bg?.type === 'url') {
return bg.url;
} else if (bg?.type === "color") {
} else if (bg?.type === 'color') {
return bg.color;
} else {
return "";
return '';
}
};
@ -18,17 +18,17 @@ export function useMigrateSettings(api: GlobalApi) {
const { display, remoteContentPolicy, calm } = useSettingsState();
return async () => {
let promises: Promise<any>[] = [];
const promises: Promise<any>[] = [];
if (local.hideAvatars !== calm.hideAvatars) {
promises.push(
api.settings.putEntry("calm", "hideAvatars", local.hideAvatars)
api.settings.putEntry('calm', 'hideAvatars', local.hideAvatars)
);
}
if (local.hideNicknames !== calm.hideNicknames) {
promises.push(
api.settings.putEntry("calm", "hideNicknames", local.hideNicknames)
api.settings.putEntry('calm', 'hideNicknames', local.hideNicknames)
);
}
@ -38,15 +38,15 @@ export function useMigrateSettings(api: GlobalApi) {
) {
promises.push(
api.settings.putEntry(
"display",
"background",
'display',
'background',
getBackgroundString(local.background)
)
);
promises.push(
api.settings.putEntry(
"display",
"backgroundType",
'display',
'backgroundType',
local.background?.type
)
);
@ -57,12 +57,12 @@ export function useMigrateSettings(api: GlobalApi) {
const localVal = local.remoteContentPolicy[key];
if (localVal !== remoteContentPolicy[key]) {
promises.push(
api.settings.putEntry("remoteContentPolicy", key, localVal)
api.settings.putEntry('remoteContentPolicy', key, localVal)
);
}
});
await Promise.all(promises);
localStorage.removeItem("localReducer");
localStorage.removeItem('localReducer');
};
}

View File

@ -1,4 +1,4 @@
import { GraphNotifIndex, GraphNotificationContents } from '@urbit/api';
import { GraphNotificationContents, GraphNotifIndex } from '@urbit/api';
export function getParentIndex(
idx: GraphNotifIndex,

View File

@ -1,5 +1,5 @@
import { cite } from '~/logic/lib/util';
import { isChannelAdmin } from '~/logic/lib/group';
import { cite } from '~/logic/lib/util';
const makeIndexes = () => new Map([
['ships', []],
@ -23,7 +23,7 @@ const result = function(title, link, app, host) {
const shipIndex = function(contacts) {
const ships = [];
Object.keys(contacts).map((e) => {
return ships.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status || ""));
return ships.push(result(e, `/~profile/${e}`, 'profile', contacts[e]?.status || ''));
});
return ships;
};
@ -38,11 +38,11 @@ const commandIndex = function (currentGroup, groups, associations) {
? (association.metadata.vip === 'member-metadata' || isChannelAdmin(group, currentGroup))
: !currentGroup; // home workspace or hasn't loaded
const workspace = currentGroup || '/home';
commands.push(result(`Groups: Create`, `/~landscape/new`, 'Groups', null));
commands.push(result('Groups: Create', '/~landscape/new', 'Groups', null));
if (canAdd) {
commands.push(result(`Channel: Create`, `/~landscape${workspace}/new`, 'Groups', null));
commands.push(result('Channel: Create', `/~landscape${workspace}/new`, 'Groups', null));
}
commands.push(result(`Groups: Join`, `/~landscape/join`, 'Groups', null));
commands.push(result('Groups: Join', '/~landscape/join', 'Groups', null));
return commands;
};
@ -80,7 +80,7 @@ const otherIndex = function(config) {
logout: result('Log Out', '/~/logout', 'logout', null)
};
other.push(result('Tutorial', '/?tutorial=true', 'tutorial', null));
for(let cat of config.categories) {
for(const cat of config.categories) {
if(idx[cat]) {
other.push(idx[cat]);
}
@ -102,7 +102,7 @@ export default function index(contacts, associations, apps, currentGroup, groups
}).map((e) => {
// iterate through each app's metadata object
Object.keys(associations[e])
.filter((association) => !associations?.[e]?.[association]?.metadata?.hidden)
.filter(association => !associations?.[e]?.[association]?.metadata?.hidden)
.map((association) => {
const each = associations[e][association];
let title = each.resource;

View File

@ -1,15 +1,12 @@
import {
Association,
resourceFromPath,
Group,
ReferenceContent,
} from "@urbit/api";
ReferenceContent, resourceFromPath
} from '@urbit/api';
export function getPermalinkForGraph(
group: string,
graph: string,
index = ""
index = ''
) {
const groupLink = getPermalinkForAssociatedGroup(group);
const { ship, name } = resourceFromPath(graph);
@ -21,16 +18,15 @@ function getPermalinkForAssociatedGroup(group: string) {
return `web+urbitgraph://group/${ship}/${name}`;
}
type Permalink = GraphPermalink | GroupPermalink;
interface GroupPermalink {
type: "group";
type: 'group';
group: string;
link: string;
}
interface GraphPermalink {
type: "graph";
type: 'graph';
link: string;
graph: string;
group: string;
@ -43,16 +39,16 @@ function parseGraphPermalink(
segments: string[]
): GraphPermalink | null {
const [kind, ship, name, ...index] = segments;
if (kind !== "graph") {
if (kind !== 'graph') {
return null;
}
const graph = `/ship/${ship}/${name}`;
return {
type: "graph",
type: 'graph',
link: link.slice(16),
graph,
group,
index: `/${index.join("/")}`,
index: `/${index.join('/')}`
};
}
@ -64,7 +60,7 @@ export function permalinkToReference(link: Permalink): ReferenceContent {
group: link.group,
index: link.index
}
}
};
return { reference };
} else {
const reference = {
@ -89,22 +85,22 @@ export function referenceToPermalink({ reference }: ReferenceContent): Permalink
type: 'group',
link,
...reference
}
};
}
}
export function parsePermalink(url: string): Permalink | null {
const [kind, ...rest] = url.slice(17).split("/");
if (kind === "group") {
const [kind, ...rest] = url.slice(17).split('/');
if (kind === 'group') {
const [ship, name, ...graph] = rest;
const group = `/ship/${ship}/${name}`;
if (graph.length > 0) {
return parseGraphPermalink(url, group, graph);
}
return {
type: "group",
type: 'group',
group,
link: url.slice(11),
link: url.slice(11)
};
}
return null;

View File

@ -1,4 +1,4 @@
import { Post, GraphNode } from '@urbit/api';
import { GraphNode, Post } from '@urbit/api';
export const buntPost = (): Post => ({
author: '',

View File

@ -1,8 +1,9 @@
import { Post, GraphNode, TextContent } from '@urbit/api';
import { GraphNode, Post, TextContent } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
import { buntPost } from '~/logic/lib/post';
import { unixToDa } from '~/logic/lib/util';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
import bigInt, { BigInteger } from 'big-integer';
import tokenizeMessage from './tokenizeMessage';
export function newPost(
title: string,
@ -19,13 +20,15 @@ export function newPost(
signatures: []
};
const tokenisedBody = tokenizeMessage(body);
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 }]
contents: [{ text: title }, ...tokenisedBody]
};
const nodes = {
@ -54,11 +57,12 @@ export function newPost(
export function editPost(rev: number, noteId: BigInteger, title: string, body: string) {
const now = Date.now();
const tokenisedBody = tokenizeMessage(body);
const newRev: Post = {
author: `~${window.ship}`,
index: `/${noteId.toString()}/1/${rev}`,
'time-sent': now,
contents: [{ text: title }, { text: body }],
contents: [{ text: title }, ...tokenisedBody],
hash: null,
signatures: []
};
@ -85,8 +89,9 @@ export function getLatestRevision(node: GraphNode): [number, string, string, Pos
if (!rev) {
return empty;
}
const [title, body] = rev.post.contents as TextContent[];
return [revNum.toJSNumber(), title.text, body.text, rev.post];
const title = rev.post.contents[0];
const body = rev.post.contents.slice(1);
return [revNum.toJSNumber(), title.text, body, rev.post];
}
export function getLatestCommentRevision(node: GraphNode): [number, Post] {
@ -113,10 +118,11 @@ export function getComments(node: GraphNode): GraphNode {
return comments;
}
export function getSnippet(body: string) {
const newlineIdx = body.indexOf('\n', 2);
const end = newlineIdx > -1 ? newlineIdx : body.length;
const start = body.substr(0, end);
export function getSnippet(body: any) {
const firstContent = Object.values(body[0])[0];
const newlineIdx = firstContent.indexOf('\n', 2);
const end = newlineIdx > -1 ? newlineIdx : firstContent.length;
const start = firstContent.substr(0, end);
return (start === body || start.startsWith('![')) ? start : `${start}...`;
return (start === firstContent || firstContent.startsWith('![')) ? start : `${start}...`;
}

View File

@ -1,6 +1,6 @@
import React, { memo } from 'react';
import { sigil, reactRenderer } from '@tlon/sigil-js';
import { Box } from '@tlon/indigo-react';
import { reactRenderer, sigil } from '@tlon/sigil-js';
import React, { memo } from 'react';
export const foregroundFromBackground = (background) => {
const rgb = {

View File

@ -1,31 +1,43 @@
import urbitOb from 'urbit-ob';
import { parsePermalink, permalinkToReference } from "~/logic/lib/permalinks";
import { parsePermalink, permalinkToReference } from '~/logic/lib/permalinks';
const URL_REGEX = new RegExp(String(/^(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
const GROUP_REGEX = new RegExp(String(/^~[-a-z_]+\/[-a-z]+/.source));
const isUrl = (string) => {
try {
return URL_REGEX.test(string);
} catch (e) {
return false;
}
}
};
const isRef = (str) => {
return isUrl(str) && str.startsWith("web+urbitgraph://");
return isUrl(str) && str.startsWith('web+urbitgraph://');
};
const isGroup = str => {
try {
return GROUP_REGEX.test(str);
} catch (e) {
return false;
}
}
const convertToGroupRef = (group) => `web+urbitgraph://group/${group}`;
const tokenizeMessage = (text) => {
let messages = [];
let message = [];
// by line
let currTextBlock = [];
let isInCodeBlock = false;
let endOfCodeBlock = false;
text.split(/\r?\n/).forEach((line, index) => {
if (index !== 0) {
message.push('\n');
}
// by space
let currTextLine = [];
// A line of backticks enters and exits a codeblock
if (line.startsWith('```')) {
if (line.trim().startsWith('```')) {
// But we need to check if we've ended a codeblock
endOfCodeBlock = isInCodeBlock;
isInCodeBlock = (!isInCodeBlock);
@ -34,9 +46,13 @@ const tokenizeMessage = (text) => {
}
if (isInCodeBlock || endOfCodeBlock) {
message.push(line);
currTextLine = [line];
} else {
line.split(/\s/).forEach((str) => {
const words = line.split(/\s/);
words.forEach((word, idx) => {
const str = isGroup(word) ? convertToGroupRef(word) : word;
const last = words.length - 1 === idx;
if (
(str.startsWith('`') && str !== '`')
|| (str === '`' && !isInCodeBlock)
@ -50,9 +66,12 @@ const tokenizeMessage = (text) => {
}
if(isRef(str) && !isInCodeBlock) {
if (message.length > 0) {
if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') });
currTextLine.push('');
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
}
const link = parsePermalink(str);
if(!link) {
@ -61,34 +80,39 @@ const tokenizeMessage = (text) => {
const reference = permalinkToReference(link);
messages.push(reference);
}
message = [];
currTextLine = [];
} else if (isUrl(str) && !isInCodeBlock) {
if (message.length > 0) {
if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') });
message = [];
currTextLine.push('');
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
}
messages.push({ url: str });
message = [];
currTextLine = [];
} else if(urbitOb.isValidPatp(str) && !isInCodeBlock) {
if (message.length > 0) {
if (currTextLine.length > 0 || currTextBlock.length > 0) {
// If we're in the middle of a message, add it to the stack and reset
messages.push({ text: message.join(' ') });
message = [];
currTextLine.push('');
messages.push({ text: currTextBlock.join('\n') + currTextLine.join(' ') });
currTextBlock = last ? [''] : [];
currTextLine = [];
}
messages.push({ mention: str });
message = [];
currTextLine = [];
} else {
message.push(str);
currTextLine.push(str);
}
});
}
currTextBlock.push(currTextLine.join(' '))
});
if (message.length) {
if (currTextBlock.length) {
// Add any remaining message
messages.push({ text: message.join(' ') });
messages.push({ text: currTextBlock.join('\n') });
}
return messages;
};

View File

@ -1,6 +1,6 @@
import { Associations } from '@urbit/api';
import { TutorialProgress } from '~/types';
import { AlignX, AlignY } from '~/logic/lib/relativePosition';
import { TutorialProgress } from '~/types';
import { Direction } from '~/views/components/Triangle';
export const MODAL_WIDTH = 256;
@ -92,7 +92,7 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignY: 'top',
arrow: 'East',
offsetX: MODAL_WIDTH + 24,
offsetY: 80,
offsetY: 80
},
channels: {
title: 'Channels',
@ -157,17 +157,17 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignX: 'right',
arrow: 'South',
offsetX: -300 + MODAL_WIDTH / 2,
offsetY: -4,
offsetY: -4
},
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.',
url: `/~profile/~${window.ship}`,
alignY: "top",
alignX: "left",
arrow: "North",
alignY: 'top',
alignX: 'left',
arrow: 'North',
offsetX: 76,
offsetY: -48,
},
offsetY: -48
}
};

View File

@ -1,7 +1,7 @@
import { writeText } from "./util";
import { useCallback, useState, useMemo } from "react";
import { useCallback, useMemo, useState } from 'react';
import { writeText } from './util';
export function useCopy(copied: string, display: string) {
export function useCopy(copied: string, display?: string) {
const [didCopy, setDidCopy] = useState(false);
const doCopy = useCallback(() => {
writeText(copied);
@ -11,9 +11,9 @@ export function useCopy(copied: string, display: string) {
}, 2000);
}, [copied]);
const copyDisplay = useMemo(() => (didCopy ? "Copied" : display), [
const copyDisplay = useMemo(() => (didCopy ? 'Copied' : display), [
didCopy,
display,
display
]);
return { copyDisplay, doCopy, didCopy };

View File

@ -1,4 +1,4 @@
import { useState, useCallback, useMemo, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = [];

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, useCallback } from 'react';
import { useCallback, useState } from 'react';
export function useDropdown<C>(
candidates: C[],

View File

@ -1,5 +1,5 @@
import { useEffect, RefObject, useRef, useState } from 'react';
import _ from 'lodash';
import { RefObject, useEffect, useState } from 'react';
import usePreviousValue from './usePreviousValue';
export function distanceToBottom(el: HTMLElement) {

View File

@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
function retrieve<T>(key: string, initial: T): T {
const s = localStorage.getItem(key);

View File

@ -1,15 +1,13 @@
import { Box } from '@tlon/indigo-react';
import React, {
useState,
ReactNode,
useCallback,
useMemo,
useRef
useRef, useState
} from 'react';
import { Box } from '@tlon/indigo-react';
import { PropFunc } from '~/types';
import { ModalOverlay } from '~/views/components/ModalOverlay';
import { Portal } from '~/views/components/Portal';
import { PropFunc } from '~/types';
type ModalFunc = (dismiss: () => void) => JSX.Element;
interface UseModalProps {

View File

@ -1,4 +1,4 @@
import { useEffect, RefObject } from 'react';
import { RefObject, useEffect } from 'react';
export function useOutsideClick(
ref: RefObject<HTMLElement | null | undefined>,

View File

@ -1,6 +1,6 @@
import { useMemo, useCallback } from "react";
import { useLocation } from "react-router-dom";
import _ from "lodash";
import _ from 'lodash';
import { useCallback, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
function mergeQuery(search: URLSearchParams, added: Record<string, string>) {
_.forIn(added, (v, k) => {
@ -32,7 +32,7 @@ export function useQuery() {
mergeQuery(q, params);
return {
pathname: path,
search: q.toString(),
search: q.toString()
};
},
[search, pathname]
@ -41,6 +41,6 @@ export function useQuery() {
return {
query,
appendQuery,
toQuery,
toQuery
};
}

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import {unstable_batchedUpdates} from "react-dom";
import { useEffect, useState } from 'react';
import { unstable_batchedUpdates } from 'react-dom';
export type IOInstance<I, P, O> = (
input: I
@ -29,7 +29,7 @@ export function useRunIO<I, O>(
});
useEffect(() => {
reject(new Error("useRunIO: key changed"));
reject(new Error('useRunIO: key changed'));
setDone(false);
setOutput(null);
}, [key]);

View File

@ -1,4 +1,4 @@
import { MouseEvent, useCallback, useState, useEffect } from 'react';
import { MouseEvent, useCallback, useEffect, useState } from 'react';
export type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success';
export function useStatelessAsyncClickable(

View File

@ -1,22 +1,16 @@
import { useCallback, useMemo, useEffect, useRef, useState } from 'react';
import {
GcpState,
S3State,
StorageState
} from '../../types';
import S3 from 'aws-sdk/clients/s3';
import GcpClient from './GcpClient';
import { StorageClient, StorageAcl } from './StorageClient';
import { dateToDa, deSig } from './util';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useStorageState from '../state/storage';
import GcpClient from './GcpClient';
import { StorageAcl, StorageClient } from './StorageClient';
import { dateToDa, deSig } from './util';
export interface IuseStorage {
canUpload: boolean;
upload: (file: File, bucket: string) => Promise<string>;
uploadDefault: (file: File) => Promise<string>;
uploading: boolean;
promptUpload: () => Promise<unknown>;
promptUpload: () => Promise<string>;
}
const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
@ -54,7 +48,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
);
const upload = useCallback(
async (file: File, bucket: string) => {
async (file: File, bucket: string): Promise<string> => {
if (client.current === null) {
throw new Error('Storage not ready');
}
@ -83,7 +77,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
[client, setUploading]
);
const uploadDefault = useCallback(async (file: File) => {
const uploadDefault = useCallback(async (file: File): Promise<string> => {
if (s3.configuration.currentBucket === '') {
throw new Error('current bucket not set');
}
@ -91,7 +85,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
}, [s3, upload]);
const promptUpload = useCallback(
() => {
(): Promise<string> => {
return new Promise((resolve, reject) => {
const fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file');
@ -101,10 +95,10 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
const files = fileSelector.files;
if (!files || files.length <= 0) {
reject();
return;
} else {
uploadDefault(files[0]).then(resolve);
document.body.removeChild(fileSelector);
}
uploadDefault(files[0]).then(resolve);
document.body.removeChild(fileSelector);
});
document.body.appendChild(fileSelector);
fileSelector.click();
@ -113,7 +107,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
[uploadDefault]
);
return {canUpload, upload, uploadDefault, uploading, promptUpload};
return { canUpload, upload, uploadDefault, uploading, promptUpload };
};
export default useStorage;

View File

@ -1,10 +1,10 @@
import { useState, useCallback } from "react";
import { useCallback, useState } from 'react';
export function useToggleState(initial: boolean) {
const [state, setState] = useState(initial);
const toggle = useCallback(() => {
setState((s) => !s);
setState(s => !s);
}, [setState]);
return [state, toggle] as const;

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
export function useWaitForProps<P>(props: P, timeout = 0) {
const [resolve, setResolve] = useState<() => void>(() => () => {});

View File

@ -1,14 +1,13 @@
/* eslint-disable max-lines */
import { useEffect, useState, useCallback, useMemo } from 'react';
import _ from 'lodash';
import { IconRef } from '~/types';
import f, { compose, memoize } from 'lodash/fp';
import bigInt, { BigInteger } from 'big-integer';
import { Association, Contact } from '@urbit/api';
import { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
import anyAscii from 'any-ascii';
import bigInt, { BigInteger } from 'big-integer';
import { enableMapSet } from 'immer';
import _ from 'lodash';
import f from 'lodash/fp';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { IconRef } from '~/types';
import useSettingsState from '../state/settings';
enableMapSet();
@ -23,7 +22,7 @@ export const MOMENT_CALENDAR_DATE = {
sameElse: '~YYYY.M.D'
};
type GraphModule = 'link' | 'post' | 'chat' | 'publish';
export type GraphModule = 'link' | 'post' | 'chat' | 'publish';
export const getModuleIcon = (mod: GraphModule): IconRef => {
if (mod === 'link') {
@ -202,7 +201,7 @@ export const hexToUx = (hex) => {
return `0x${ux}`;
};
export function writeText(str: string) {
export function writeText(str: string | null): Promise<void> {
return new Promise<void>((resolve, reject) => {
const range = document.createRange();
range.selectNodeContents(document.body);
@ -401,7 +400,7 @@ export function pluralize(text: string, isPlural = false, vowel = false) {
export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideState = useSettingsState(state => state.calm.hideNicknames);
const hideNicknames = typeof hide !== 'undefined' ? hide : hideState;
return !!(contact && contact.nickname && !hideNicknames);
return Boolean(contact && contact.nickname && !hideNicknames);
}
interface useHoveringInterface {
@ -414,14 +413,13 @@ interface useHoveringInterface {
export const useHovering = (): useHoveringInterface => {
const [hovering, setHovering] = useState(false);
const onMouseOver = useCallback(() => setHovering(true), [])
const onMouseLeave = useCallback(() => setHovering(false), [])
const onMouseOver = useCallback(() => setHovering(true), []);
const onMouseLeave = useCallback(() => setHovering(false), []);
const bind = useMemo(() => ({
onMouseOver,
onMouseLeave,
onMouseLeave
}), [onMouseLeave, onMouseOver]);
return useMemo(() => ({ hovering, bind }), [hovering, bind]);
};

View File

@ -1,13 +1,10 @@
import React, {
useContext,
useState,
useCallback,
useLayoutEffect,
useRef,
useEffect,
} from "react";
import usePreviousValue from "./usePreviousValue";
import {Primitive} from "~/types";
useCallback, useContext,
useEffect, useState
} from 'react';
import { Primitive } from '~/types';
import usePreviousValue from './usePreviousValue';
export interface VirtualContextProps {
save: () => void;
@ -15,7 +12,7 @@ export interface VirtualContextProps {
}
const fallback: VirtualContextProps = {
save: () => {},
restore: () => {},
restore: () => {}
};
export const VirtualContext = React.createContext(fallback);
@ -27,7 +24,7 @@ export function useVirtual() {
export const withVirtual = <P extends {}>(Component: React.ComponentType<P>) =>
React.forwardRef((props: P, ref) => (
<VirtualContext.Consumer>
{(context) => <Component ref={ref} {...props} {...context} />}
{context => <Component ref={ref} {...props} {...context} />}
</VirtualContext.Consumer>
));
@ -52,7 +49,7 @@ export function useVirtualResizeState(s: boolean) {
export function useVirtualResizeProp(prop: Primitive) {
const { save, restore } = useVirtual();
const oldProp = usePreviousValue(prop)
const oldProp = usePreviousValue(prop);
if(prop !== oldProp) {
save();
@ -61,6 +58,4 @@ export function useVirtualResizeProp(prop: Primitive) {
useEffect(() => {
requestAnimationFrame(restore);
}, [prop]);
}

View File

@ -1,6 +1,6 @@
import React from "react";
import { UseStore } from "zustand";
import { BaseState } from "../state/base";
import React from 'react';
import { UseStore } from 'zustand';
import { BaseState } from '../state/base';
const withStateo = <
StateType extends BaseState<any>
@ -15,8 +15,8 @@ const withStateo = <
(object, key) => ({ ...object, [key]: state[key] }), {}
)
) : useState();
return <Component ref={ref} {...state} {...props} />
})
return <Component ref={ref} {...state} {...props} />;
});
};
const withState = <
@ -24,10 +24,10 @@ const withState = <
stateKey extends keyof StateType
>(
Component: any,
stores: ([UseStore<StateType>, stateKey[]])[],
stores: ([UseStore<StateType>, stateKey[]])[]
) => {
return React.forwardRef((props, ref) => {
let stateProps: unknown = {};
const stateProps: unknown = {};
stores.forEach(([store, keys]) => {
const storeProps = Array.isArray(keys)
? store(state => keys.reduce(
@ -36,8 +36,8 @@ const withState = <
: store();
Object.assign(stateProps, storeProps);
});
return <Component ref={ref} {...stateProps} {...props} />
return <Component ref={ref} {...stateProps} {...props} />;
});
}
};
export default withState;
export default withState;

View File

@ -1,5 +1,5 @@
import { StoreState } from '../store/type';
import { Cage } from '~/types/cage';
import { StoreState } from '../store/type';
type LocalState = Pick<StoreState, 'connection'>;

View File

@ -1,11 +1,7 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { ContactUpdate } from '@urbit/api';
import useContactState, { ContactState } from '../state/contact';
import _ from 'lodash';
import { reduceState } from '../state/base';
import useContactState, { ContactState } from '../state/contact';
export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false);
@ -69,7 +65,7 @@ const edit = (json: ContactUpdate, state: ContactState): ContactState => {
}
const value = data['edit-field'][field];
if(field === 'add-group') {
state.contacts[ship].groups.push(value);
} else if (field === 'remove-group') {

View File

@ -1,8 +1,7 @@
import {StoreState} from '../store/type';
import type {GcpToken} from '../../types/gcp-state';
import type {Cage} from '~/types/cage';
import useStorageState, { StorageState } from '../state/storage';
import type { Cage } from '~/types/cage';
import type { GcpToken } from '../../types/gcp-state';
import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage';
export default class GcpReducer {
reduce(json: Cage) {
@ -13,21 +12,21 @@ export default class GcpReducer {
}
const reduceToken = (json: Cage, state: StorageState): StorageState => {
let data = json['gcp-token'];
const data = json['gcp-token'];
if (data) {
setToken(data, state);
}
return state;
}
};
const setToken = (data: any, state: StorageState): StorageState => {
if (isToken(data)) {
state.gcp.token = data;
}
return state;
}
};
const isToken = (token: any): token is GcpToken => {
return (typeof(token.accessKey) === 'string' &&
typeof(token.expiresIn) === 'number');
}
};

View File

@ -1,13 +1,13 @@
import _ from 'lodash';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import bigInt, { BigInteger } from 'big-integer';
import produce from 'immer';
import bigInt, { BigInteger } from "big-integer";
import useGraphState, { GraphState } from '../state/graph';
import _ from 'lodash';
import { reduceState } from '../state/base';
import useGraphState, { GraphState } from '../state/graph';
export const GraphReducer = (json) => {
const data = _.get(json, 'graph-update', false);
if (data) {
reduceState<GraphState, any>(useGraphState, data, [
keys,
@ -36,13 +36,13 @@ const addNodesLoose = (json: any, state: GraphState): GraphState => {
_.set(state.looseNodes, [resource], indices);
}
return state;
}
};
const keys = (json, state: GraphState): GraphState => {
const data = _.get(json, 'keys', false);
if (data) {
state.graphKeys = new Set(data.map((res) => {
let resource = res.ship + '/' + res.name;
const resource = res.ship + '/' + res.name;
return resource;
}));
}
@ -52,36 +52,32 @@ const keys = (json, state: GraphState): GraphState => {
const processNode = (node) => {
// is empty
if (!node.children) {
return produce(node, draft => {
return produce(node, (draft) => {
draft.children = new BigIntOrderedMap();
});
}
// is graph
return produce(node, draft => {
return produce(node, (draft) => {
draft.children = new BigIntOrderedMap()
.gas(_.map(draft.children, (item, idx) =>
.gas(_.map(draft.children, (item, idx) =>
[bigInt(idx), processNode(item)] as [BigInteger, any]
));
});
};
const addGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'add-graph', false);
if (data) {
if (!('graphs' in state)) {
state.graphs = {};
}
let resource = data.resource.ship + '/' + data.resource.name;
const resource = data.resource.ship + '/' + data.resource.name;
state.graphs[resource] = new BigIntOrderedMap();
state.graphTimesentMap[resource] = {};
state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map(idx => {
state.graphs[resource] = state.graphs[resource].gas(Object.keys(data.graph).map((idx) => {
return [bigInt(idx), processNode(data.graph[idx])];
}));
@ -93,11 +89,10 @@ const addGraph = (json, state: GraphState): GraphState => {
const removeGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'remove-graph', false);
if (data) {
if (!('graphs' in state)) {
state.graphs = {};
}
let resource = data.ship + '/' + data.name;
const resource = data.ship + '/' + data.name;
state.graphKeys.delete(resource);
delete state.graphs[resource];
}
@ -108,7 +103,7 @@ const mapifyChildren = (children) => {
return new BigIntOrderedMap().gas(
_.map(children, (node, idx) => {
idx = idx && idx.startsWith('/') ? idx.slice(1) : idx;
const nd = {...node, children: mapifyChildren(node.children || {}) };
const nd = { ...node, children: mapifyChildren(node.children || {}) };
return [bigInt(idx), nd];
}));
};
@ -121,12 +116,12 @@ const addNodes = (json, state) => {
}
// set parent of graph
let parNode = graph.get(index[0]);
const parNode = graph.get(index[0]);
if (!parNode) {
console.error('parent node does not exist, cannot add child');
return graph;
}
return graph.set(index[0], produce(parNode, draft => {
return graph.set(index[0], produce(parNode, (draft) => {
draft.children = _addNode(draft.children, index.slice(1), node);
}));
};
@ -137,7 +132,7 @@ const addNodes = (json, state) => {
} else {
const child = graph.get(index[0]);
if (child) {
return graph.set(index[0], produce(child, draft => {
return graph.set(index[0], produce(child, (draft) => {
draft.children = _remove(draft.children, index.slice(1));
}));
}
@ -148,10 +143,12 @@ const addNodes = (json, state) => {
const _killByFuzzyTimestamp = (graph, resource, timestamp) => {
if (state.graphTimesentMap[resource][timestamp]) {
let index = state.graphTimesentMap[resource][timestamp];
const index = state.graphTimesentMap[resource][timestamp];
if (index.split('/').length === 0) { return graph; }
let indexArr = index.split('/').slice(1).map((ind) => {
if (index.split('/').length === 0) {
return graph;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind);
});
@ -174,10 +171,12 @@ const addNodes = (json, state) => {
const data = _.get(json, 'add-nodes', false);
if (data) {
if (!('graphs' in state)) { return state; }
if (!('graphs' in state)) {
return state;
}
let resource = data.resource.ship + '/' + data.resource.name;
if (!(resource in state.graphs)) {
const resource = data.resource.ship + '/' + data.resource.name;
if (!(resource in state.graphs)) {
state.graphs[resource] = new BigIntOrderedMap();
}
@ -186,48 +185,47 @@ const addNodes = (json, state) => {
}
state.graphKeys.add(resource);
let indices = Array.from(Object.keys(data.nodes));
const indices = Array.from(Object.keys(data.nodes));
indices.sort((a, b) => {
let aArr = a.split('/');
let bArr = b.split('/');
const aArr = a.split('/');
const bArr = b.split('/');
return aArr.length - bArr.length;
});
indices.forEach((index) => {
let node = data.nodes[index];
const node = data.nodes[index];
const old = state.graphs[resource].size;
state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource);
const newSize = state.graphs[resource].size;
if (index.split('/').length === 0) { return; }
let indexArr = index.split('/').slice(1).map((ind) => {
if (index.split('/').length === 0) {
return;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind);
});
if (indexArr.length === 0) { return state; }
if (indexArr.length === 0) {
return state;
}
if (node.post.pending) {
state.graphTimesentMap[resource][node.post['time-sent']] = index;
}
state.graphs[resource] = _addNode(
state.graphs[resource],
indexArr,
produce(node, draft => {
produce(node, (draft) => {
draft.children = mapifyChildren(draft?.children || {});
})
);
if(newSize !== old) {
console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`);
}
});
}
return state;
};
@ -239,7 +237,7 @@ const removePosts = (json, state: GraphState): GraphState => {
if (child) {
return graph.set(index[0], {
post: child.post.hash || '',
children: child.children
children: child.children
});
}
} else {
@ -249,8 +247,8 @@ const removePosts = (json, state: GraphState): GraphState => {
} else {
const child = graph.get(index[0]);
if (child) {
return graph.set(index[0], produce(draft => {
draft.children = _remove(draft.children, index.slice(1))
return graph.set(index[0], produce((draft) => {
draft.children = _remove(draft.children, index.slice(1));
}));
}
return graph;
@ -262,11 +260,15 @@ const removePosts = (json, state: GraphState): GraphState => {
if (data) {
const { ship, name } = data.resource;
const res = `${ship}/${name}`;
if (!(res in state.graphs)) { return state; }
if (!(res in state.graphs)) {
return state;
}
data.indices.forEach((index) => {
if (index.split('/').length === 0) { return; }
let indexArr = index.split('/').slice(1).map((ind) => {
if (index.split('/').length === 0) {
return;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind);
});
state.graphs[res] = _remove(state.graphs[res], indexArr);

View File

@ -1,19 +1,16 @@
import { Enc } from '@urbit/api';
import {
Group,
GroupPolicy, GroupUpdate,
InvitePolicy, InvitePolicyDiff, OpenPolicy, OpenPolicyDiff, Tags
} from '@urbit/api/groups';
import _ from 'lodash';
import { Cage } from '~/types/cage';
import {
GroupUpdate,
Group,
Tags,
GroupPolicy,
OpenPolicyDiff,
OpenPolicy,
InvitePolicyDiff,
InvitePolicy
} from '@urbit/api/groups';
import { Enc } from '@urbit/api';
import { resourceAsPath } from '../lib/util';
import useGroupState, { GroupState } from '../state/group';
import { reduceState } from '../state/base';
import useGroupState, { GroupState } from '../state/group';
function decodeGroup(group: Enc<Group>): Group {
const members = new Set(group.members);
@ -41,7 +38,7 @@ function decodePolicy(policy: Enc<GroupPolicy>): GroupPolicy {
function decodeTags(tags: Enc<Tags>): Tags {
return _.reduce(
tags,
(acc, ships, key): Tags => {
(acc, ships: any, key): Tags => {
if (key.search(/\\/) === -1) {
acc.role[key] = new Set(ships);
return acc;
@ -69,11 +66,10 @@ export default class GroupReducer {
addGroup,
removeGroup,
changePolicy,
expose,
expose
]);
}
}
}
const initial = (json: GroupUpdate, state: GroupState): GroupState => {
const data = json['initial'];
@ -81,7 +77,7 @@ const initial = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups = _.mapValues(data, decodeGroup);
}
return state;
}
};
const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('initialGroup' in json) {
@ -90,7 +86,7 @@ const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[path] = decodeGroup(group);
}
return state;
}
};
const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addGroup' in json) {
@ -104,7 +100,7 @@ const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
};
}
return state;
}
};
const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if('removeGroup' in json) {
@ -113,7 +109,7 @@ const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
delete state.groups[resourcePath];
}
return state;
}
};
const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addMembers' in json) {
@ -130,7 +126,7 @@ const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
}
}
return state;
}
};
const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('removeMembers' in json) {
@ -141,7 +137,7 @@ const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
}
}
return state;
}
};
const addTag = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addTag' in json) {
@ -177,7 +173,7 @@ const removeTag = (json: GroupUpdate, state: GroupState): GroupState => {
_.set(tags, tagAccessors, tagged);
}
return state;
}
};
const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
if ('changePolicy' in json && state) {
@ -195,7 +191,7 @@ const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
}
}
return state;
}
};
const expose = (json: GroupUpdate, state: GroupState): GroupState => {
if( 'expose' in json && state) {
@ -204,7 +200,7 @@ const expose = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[resourcePath].hidden = false;
}
return state;
}
};
const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
if ('addInvites' in diff) {
@ -220,7 +216,7 @@ const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
} else {
console.log('bad policy change');
}
}
};
const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
if ('allowRanks' in diff) {
@ -246,4 +242,4 @@ const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
} else {
console.log('bad policy change');
}
}
};

View File

@ -1,5 +1,4 @@
import { GroupUpdate } from '@urbit/api/groups';
import { resourceAsPath } from '~/logic/lib/util';
import { reduceState } from '../state/base';
import useGroupState, { GroupState } from '../state/group';
@ -18,7 +17,7 @@ const started = (json: any, state: GroupState): GroupState => {
state.pendingJoin[resource] = request;
}
return state;
}
};
const progress = (json: any, state: GroupState): GroupState => {
const data = json.progress;
@ -26,7 +25,6 @@ const progress = (json: any, state: GroupState): GroupState => {
const { progress, resource } = data;
state.pendingJoin[resource].progress = progress;
if(progress === 'done') {
setTimeout(() => {
delete state.pendingJoin[resource];
}, 10000);
@ -41,8 +39,7 @@ const hide = (json: any, state: GroupState) => {
state.pendingJoin[data].hidden = true;
}
return state;
}
};
export const GroupViewReducer = (json: any) => {
const data = json['group-view-update'];

View File

@ -2,13 +2,13 @@ import {
NotifIndex,
Timebox
} from '@urbit/api';
import { makePatDa } from '~/logic/lib/util';
import _ from 'lodash';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
import useHarkState, { HarkState } from '../state/hark';
import { BigInteger } from 'big-integer';
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { makePatDa } from '~/logic/lib/util';
import { reduceState } from '../state/base';
import {BigInteger} from 'big-integer';
import useHarkState, { HarkState } from '../state/hark';
export const HarkReducer = (json: any) => {
const data = _.get(json, 'harkUpdate', false);
@ -22,7 +22,7 @@ export const HarkReducer = (json: any) => {
graphIgnore,
graphListen,
graphWatchSelf,
graphMentions,
graphMentions
]);
}
const groupHookData = _.get(json, 'hark-group-hook-update', false);
@ -30,7 +30,7 @@ export const HarkReducer = (json: any) => {
reduceState<HarkState, any>(useHarkState, groupHookData, [
groupInitial,
groupListen,
groupIgnore,
groupIgnore
]);
}
};
@ -52,9 +52,9 @@ function reduce(data, state) {
unreadEach,
seenIndex,
removeGraph,
readAll,
readAll
];
const reducer = compose(reducers.map(r => s => {
const reducer = compose(reducers.map(r => (s) => {
return r(data, s);
}));
return reducer(state);
@ -63,13 +63,13 @@ function reduce(data, state) {
function calculateCount(json: any, state: HarkState) {
let count = 0;
_.forEach(state.unreads.graph, (graphs) => {
_.forEach(graphs, graph => {
_.forEach(graphs, (graph) => {
count += (graph?.notifications || []).length;
});
});
_.forEach(state.unreads.group, group => {
_.forEach(state.unreads.group, (group) => {
count += (group?.notifications || []).length;
})
});
state.notificationsCount = count;
return state;
}
@ -260,7 +260,7 @@ function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>)
if(!('graph' in index)) {
return state;
}
let unreads: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
const unreads: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
f(unreads);
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
@ -294,19 +294,18 @@ function removeNotificationFromUnread(state: HarkState, index: NotifIndex, time:
const path = [index.graph.graph, index.graph.index, 'notifications'];
const curr = _.get(state.unreads.graph, path, []);
_.set(state.unreads.graph, path,
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index))),
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index)))
);
} else if ('group' in index) {
const path = [index.group.group, 'notifications'];
const curr = _.get(state.unreads.group, path, []);
_.set(state.unreads.group, path,
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index))),
curr.filter(c => !(c.time.eq(time) && notifIdxEqual(c.index, index)))
);
}
}
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'unreads' | 'last', f: (x: number) => number) {
if('graph' in index) {
const curr: any = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, statField], 0);
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, statField], f(curr));
@ -359,7 +358,7 @@ const timebox = (json: any, state: HarkState): HarkState => {
function more(json: any, state: HarkState): HarkState {
const data = _.get(json, 'more', false);
if (data) {
_.forEach(data, d => {
_.forEach(data, (d) => {
reduce(d, state);
});
}
@ -431,7 +430,7 @@ function archive(json: any, state: HarkState): HarkState {
const data = _.get(json, 'archive', false);
if (data) {
const { index } = data;
removeNotificationFromUnread(state, index, makePatDa(data.time))
removeNotificationFromUnread(state, index, makePatDa(data.time));
const time = makePatDa(data.time);
const timebox = state.notifications.get(time);
if (!timebox) {

View File

@ -1,11 +1,8 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { InviteUpdate } from '@urbit/api/invite';
import _ from 'lodash';
import { Cage } from '~/types/cage';
import useInviteState, { InviteState } from '../state/invite';
import { reduceState } from '../state/base';
import useInviteState, { InviteState } from '../state/invite';
export default class InviteReducer {
reduce(json: Cage) {
@ -17,7 +14,7 @@ export default class InviteReducer {
deleteInvite,
invite,
accepted,
decline,
decline
]);
}
}
@ -29,7 +26,7 @@ const initial = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites = data;
}
return state;
}
};
const create = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'create', false);
@ -37,7 +34,7 @@ const create = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites[data] = {};
}
return state;
}
};
const deleteInvite = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'delete', false);
@ -45,7 +42,7 @@ const deleteInvite = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data];
}
return state;
}
};
const invite = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'invite', false);
@ -53,7 +50,7 @@ const invite = (json: InviteUpdate, state: InviteState): InviteState => {
state.invites[data.term][data.uid] = data.invite;
}
return state;
}
};
const accepted = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'accepted', false);
@ -61,7 +58,7 @@ const accepted = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data.term][data.uid];
}
return state;
}
};
const decline = (json: InviteUpdate, state: InviteState): InviteState => {
const data = _.get(json, 'decline', false);
@ -69,4 +66,4 @@ const decline = (json: InviteUpdate, state: InviteState): InviteState => {
delete state.invites[data.term][data.uid];
}
return state;
}
};

View File

@ -1,9 +1,8 @@
import _ from 'lodash';
import { LaunchUpdate, WeatherState } from '~/types/launch-update';
import { Cage } from '~/types/cage';
import useLaunchState, { LaunchState } from '../state/launch';
import { compose } from 'lodash/fp';
import { LaunchUpdate, WeatherState } from '~/types/launch-update';
import { reduceState } from '../state/base';
import useLaunchState, { LaunchState } from '../state/launch';
export default class LaunchReducer {
reduce(json: Cage) {
@ -14,29 +13,29 @@ export default class LaunchReducer {
changeFirstTime,
changeOrder,
changeFirstTime,
changeIsShown,
changeIsShown
]);
}
const weatherData: WeatherState | boolean | Record<string, never> = _.get(json, 'weather', false);
if (weatherData) {
useLaunchState.getState().set(state => {
useLaunchState.getState().set((state) => {
state.weather = weatherData;
});
}
const locationData = _.get(json, 'location', false);
if (locationData) {
useLaunchState.getState().set(state => {
useLaunchState.getState().set((state) => {
state.userLocation = locationData;
});
}
const baseHash = _.get(json, 'baseHash', false);
if (baseHash) {
useLaunchState.getState().set(state => {
useLaunchState.getState().set((state) => {
state.baseHash = baseHash;
})
});
}
}
}
@ -44,12 +43,12 @@ export default class LaunchReducer {
export const initial = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'initial', false);
if (data) {
Object.keys(data).forEach(key => {
Object.keys(data).forEach((key) => {
state[key] = data[key];
});
}
return state;
}
};
export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeFirstTime', false);
@ -57,7 +56,7 @@ export const changeFirstTime = (json: LaunchUpdate, state: LaunchState): LaunchS
state.firstTime = data;
}
return state;
}
};
export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeOrder', false);
@ -65,7 +64,7 @@ export const changeOrder = (json: LaunchUpdate, state: LaunchState): LaunchState
state.tileOrdering = data;
}
return state;
}
};
export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchState => {
const data = _.get(json, 'changeIsShown', false);
@ -76,4 +75,4 @@ export const changeIsShown = (json: LaunchUpdate, state: LaunchState): LaunchSta
}
}
return state;
}
};

View File

@ -1,11 +1,8 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { MetadataUpdate } from '@urbit/api/metadata';
import _ from 'lodash';
import { Cage } from '~/types/cage';
import useMetadataState, { MetadataState } from '../state/metadata';
import { reduceState } from '../state/base';
import useMetadataState, { MetadataState } from '../state/metadata';
export default class MetadataReducer {
reduce(json: Cage) {
@ -16,7 +13,7 @@ export default class MetadataReducer {
add,
update,
remove,
groupInitial,
groupInitial
]);
}
}
@ -28,7 +25,7 @@ const groupInitial = (json: MetadataUpdate, state: MetadataState): MetadataState
associations(data, state);
}
return state;
}
};
const associations = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'associations', false);
@ -50,7 +47,7 @@ const associations = (json: MetadataUpdate, state: MetadataState): MetadataState
state.associations = metadata;
}
return state;
}
};
const add = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'add', false);
@ -70,7 +67,7 @@ const add = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata;
}
return state;
}
};
const update = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'update-metadata', false);
@ -90,7 +87,7 @@ const update = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata;
}
return state;
}
};
const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'remove', false);
@ -105,4 +102,4 @@ const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => {
state.associations = metadata;
}
return state;
}
};

View File

@ -1,11 +1,9 @@
import _ from 'lodash';
import { compose } from 'lodash/fp';
import { Cage } from '~/types/cage';
import { S3Update } from '~/types/s3-update';
import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage';
export default class S3Reducer {
reduce(json: Cage) {
const data = _.get(json, 's3-update', false);
@ -18,7 +16,7 @@ export default class S3Reducer {
removeBucket,
endpoint,
accessKeyId,
secretAccessKey,
secretAccessKey
]);
}
}
@ -30,7 +28,7 @@ const credentials = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials = data;
}
return state;
}
};
const configuration = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'configuration', false);
@ -41,7 +39,7 @@ const configuration = (json: S3Update, state: StorageState): StorageState => {
};
}
return state;
}
};
const currentBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setCurrentBucket', false);
@ -49,7 +47,7 @@ const currentBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.currentBucket = data;
}
return state;
}
};
const addBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'addBucket', false);
@ -58,7 +56,7 @@ const addBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.buckets.add(data);
}
return state;
}
};
const removeBucket = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'removeBucket', false);
@ -66,7 +64,7 @@ const removeBucket = (json: S3Update, state: StorageState): StorageState => {
state.s3.configuration.buckets.delete(data);
}
return state;
}
};
const endpoint = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setEndpoint', false);
@ -74,7 +72,7 @@ const endpoint = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials.endpoint = data;
}
return state;
}
};
const accessKeyId = (json: S3Update , state: StorageState): StorageState => {
const data = _.get(json, 'setAccessKeyId', false);
@ -82,7 +80,7 @@ const accessKeyId = (json: S3Update , state: StorageState): StorageState => {
state.s3.credentials.accessKeyId = data;
}
return state;
}
};
const secretAccessKey = (json: S3Update, state: StorageState): StorageState => {
const data = _.get(json, 'setSecretAccessKey', false);
@ -90,4 +88,4 @@ const secretAccessKey = (json: S3Update, state: StorageState): StorageState => {
state.s3.credentials.secretAccessKey = data;
}
return state;
}
};

View File

@ -1,26 +1,25 @@
import { SettingsUpdate } from '@urbit/api/settings';
import _ from 'lodash';
import useSettingsState, { SettingsState } from '~/logic/state/settings';
import { SettingsUpdate } from '@urbit/api/settings';
import { reduceState } from '../state/base';
import { string } from 'prop-types';
export default class SettingsReducer {
reduce(json: any) {
let data = json["settings-event"];
let data = json['settings-event'];
if (data) {
reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [
this.putBucket,
this.delBucket,
this.putEntry,
this.delEntry,
this.delEntry
]);
}
data = json["settings-data"];
data = json['settings-data'];
if (data) {
reduceState<SettingsState, SettingsUpdate>(useSettingsState, data, [
this.getAll,
this.getBucket,
this.getEntry,
this.getEntry
]);
}
}
@ -28,7 +27,7 @@ export default class SettingsReducer {
putBucket(json: SettingsUpdate, state: SettingsState): SettingsState {
const data = _.get(json, 'put-bucket', false);
if (data) {
state[data["bucket-key"]] = data.bucket;
state[data['bucket-key']] = data.bucket;
}
return state;
}
@ -63,7 +62,7 @@ export default class SettingsReducer {
getAll(json: any, state: SettingsState): SettingsState {
const data = _.get(json, 'all');
if(data) {
_.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined)
_.mergeWith(state, data, (obj, src) => _.isArray(src) ? src : undefined);
}
return state;
}

View File

@ -1,11 +1,10 @@
import produce, { setAutoFreeze } from "immer";
import { compose } from "lodash/fp";
import create, { State, UseStore } from "zustand";
import { persist, devtools } from "zustand/middleware";
import produce, { setAutoFreeze } from 'immer';
import { compose } from 'lodash/fp';
import create, { State, UseStore } from 'zustand';
import { persist } from 'zustand/middleware';
setAutoFreeze(false);
export const stateSetter = <StateType>(
fn: (state: StateType) => void,
set
@ -22,7 +21,7 @@ export const reduceState = <
reducers: ((data: UpdateType, state: StateType) => StateType)[]
): void => {
const reducer = compose(reducers.map(r => sta => r(data, sta)));
state.getState().set(state => {
state.getState().set((state) => {
reducer(state);
});
};
@ -36,10 +35,10 @@ export const stateStorageKey = (stateName: string) => {
};
(window as any).clearStates = () => {
stateStorageKeys.forEach(key => {
stateStorageKeys.forEach((key) => {
localStorage.removeItem(key);
});
}
};
export interface BaseState<StateType> extends State {
set: (fn: (state: StateType) => void) => void;

View File

@ -1,7 +1,6 @@
import { Patp, Rolodex, Scry, Contact } from "@urbit/api";
import { BaseState, createState } from "./base";
import {useCallback} from "react";
import { Contact, Patp, Rolodex } from '@urbit/api';
import { useCallback } from 'react';
import { BaseState, createState } from './base';
export interface ContactState extends BaseState<ContactState> {
contacts: Rolodex;
@ -13,7 +12,7 @@ export interface ContactState extends BaseState<ContactState> {
const useContactState = createState<ContactState>('Contact', {
contacts: {},
nackedContacts: new Set(),
isContactPublic: false,
isContactPublic: false
// fetchIsAllowed: async (
// entity,
// name,
@ -36,7 +35,7 @@ export function useContact(ship: string) {
}
export function useOurContact() {
return useContact(`~${window.ship}`)
return useContact(`~${window.ship}`);
}
export default useContactState;

View File

@ -1,7 +1,6 @@
import { Graphs, decToUd, numToUd, GraphNode, deSig, Association, resourceFromPath } from "@urbit/api";
import {useCallback} from "react";
import { BaseState, createState } from "./base";
import { Association, deSig, GraphNode, Graphs, resourceFromPath } from '@urbit/api';
import { useCallback } from 'react';
import { BaseState, createState } from './base';
export interface GraphState extends BaseState<GraphState> {
graphs: Graphs;
@ -22,14 +21,14 @@ export interface GraphState extends BaseState<GraphState> {
// getYoungerSiblings: (ship: string, resource: string, count: number, index?: string) => Promise<void>;
// getGraphSubset: (ship: string, resource: string, start: string, end: string) => Promise<void>;
// getNode: (ship: string, resource: string, index: string) => Promise<void>;
};
}
const useGraphState = createState<GraphState>('Graph', {
graphs: {},
graphKeys: new Set(),
looseNodes: {},
pendingIndices: {},
graphTimesentMap: {},
graphTimesentMap: {}
// getKeys: async () => {
// const api = useApi();
// const keys = await api.scry({

View File

@ -1,18 +1,17 @@
import { Path, JoinRequests, Association, Group } from "@urbit/api";
import { BaseState, createState } from "./base";
import {useCallback} from "react";
import { Association, Group, JoinRequests } from '@urbit/api';
import { useCallback } from 'react';
import { BaseState, createState } from './base';
export interface GroupState extends BaseState<GroupState> {
groups: {
[group: string]: Group;
}
pendingJoin: JoinRequests;
};
}
const useGroupState = createState<GroupState>('Group', {
groups: {},
pendingJoin: {},
pendingJoin: {}
}, ['groups']);
export function useGroup(group: string) {

View File

@ -1,8 +1,7 @@
import { NotificationGraphConfig, Timebox, Unreads, dateToDa } from "@urbit/api";
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
import { NotificationGraphConfig, Timebox, Unreads } from '@urbit/api';
import BigIntOrderedMap from '@urbit/api/lib/BigIntOrderedMap';
// import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark";
import { BaseState, createState } from "./base";
import { BaseState, createState } from './base';
export const HARK_FETCH_MORE_COUNT = 3;
@ -15,9 +14,9 @@ export interface HarkState extends BaseState<HarkState> {
notifications: BigIntOrderedMap<Timebox>;
notificationsCount: number;
notificationsGraphConfig: NotificationGraphConfig; // TODO unthread this everywhere
notificationsGroupConfig: string[];
notificationsGroupConfig: string[];
unreads: Unreads;
};
}
const useHarkState = createState<HarkState>('Hark', {
archivedNotifications: new BigIntOrderedMap<Timebox>(),
@ -62,8 +61,7 @@ const useHarkState = createState<HarkState>('Hark', {
unreads: {
graph: {},
group: {}
},
}
}, ['notifications', 'archivedNotifications', 'unreads', 'notificationsCount']);
export default useHarkState;

View File

@ -3,10 +3,10 @@ import { BaseState, createState } from './base';
export interface InviteState extends BaseState<InviteState> {
invites: Invites;
};
}
const useInviteState = createState<InviteState>('Invite', {
invites: {},
invites: {}
});
export default useInviteState;
export default useInviteState;

View File

@ -1,7 +1,5 @@
import { Tile, WeatherState } from "~/types/launch-update";
import { BaseState, createState } from "./base";
import { Tile, WeatherState } from '~/types/launch-update';
import { BaseState, createState } from './base';
export interface LaunchState extends BaseState<LaunchState> {
firstTime: boolean;
@ -12,7 +10,7 @@ export interface LaunchState extends BaseState<LaunchState> {
weather: WeatherState | null | Record<string, never> | boolean,
userLocation: string | null;
baseHash: string | null;
};
}
const useLaunchState = createState<LaunchState>('Launch', {
firstTime: true,
@ -23,5 +21,4 @@ const useLaunchState = createState<LaunchState>('Launch', {
baseHash: null
});
export default useLaunchState;
export default useLaunchState;

View File

@ -1,10 +1,9 @@
import React 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, LeapCategories } from "~/types/local-update";
import f from 'lodash/fp';
import React from 'react';
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { BackgroundConfig, LeapCategories, RemoteContentPolicy, TutorialProgress, tutorialProgress } from '~/types/local-update';
export interface LocalState {
theme: 'light' | 'dark' | 'auto';
@ -27,7 +26,7 @@ export interface LocalState {
suspendedFocus?: HTMLElement;
toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void
};
}
type LocalStateZus = LocalState & State;

View File

@ -1,15 +1,14 @@
import { useCallback } from 'react';
import { Association, Associations } from '@urbit/api';
import _ from 'lodash';
import { MetadataUpdatePreview, Association, Associations } from "@urbit/api";
import { BaseState, createState } from "./base";
import { useCallback } from 'react';
import { BaseState, createState } from './base';
export const METADATA_MAX_PREVIEW_WAIT = 150000;
export interface MetadataState extends BaseState<MetadataState> {
associations: Associations;
// preview: (group: string) => Promise<MetadataUpdatePreview>;
};
}
export function useAssocForGraph(graph: string) {
return useMetadataState(useCallback(s => s.associations.graph[graph] as Association | undefined, [graph]));
@ -25,12 +24,12 @@ export function useGraphsForGroup(group: string) {
}
const useMetadataState = createState<MetadataState>('Metadata', {
associations: { groups: {}, graph: {}, contacts: {}, chat: {}, link: {}, publish: {} },
associations: { groups: {}, graph: {}, contacts: {}, chat: {}, link: {}, publish: {} }
// preview: async (group): Promise<MetadataUpdatePreview> => {
// return new Promise<MetadataUpdatePreview>((resolve, reject) => {
// const api = useApi();
// let done = false;
// setTimeout(() => {
// if (done) {
// return;
@ -38,7 +37,7 @@ const useMetadataState = createState<MetadataState>('Metadata', {
// done = true;
// reject(new Error('offline'));
// }, METADATA_MAX_PREVIEW_WAIT);
// api.subscribe({
// app: 'metadata-pull-hook',
// path: `/preview${group}`,
@ -68,5 +67,4 @@ const useMetadataState = createState<MetadataState>('Metadata', {
// },
});
export default useMetadataState;

View File

@ -1,14 +1,13 @@
import f from 'lodash/fp';
import { RemoteContentPolicy, LeapCategories, leapCategories } from "~/types/local-update";
import { BaseState, createState } from '~/logic/state/base';
import { LeapCategories, leapCategories, RemoteContentPolicy } from '~/types/local-update';
export interface SettingsState extends BaseState<SettingsState> {
display: {
backgroundType: 'none' | 'url' | 'color';
background?: string;
dark: boolean;
theme: "light" | "dark" | "auto";
theme: 'light' | 'dark' | 'auto';
};
calm: {
hideNicknames: boolean;
@ -25,7 +24,7 @@ export interface SettingsState extends BaseState<SettingsState> {
seen: boolean;
joined?: number;
};
};
}
export const selectSettingsState =
<K extends keyof SettingsState>(keys: K[]) => f.pick<SettingsState, K>(keys);
@ -39,7 +38,7 @@ const useSettingsState = createState<SettingsState>('Settings', {
backgroundType: 'none',
background: undefined,
dark: false,
theme: "auto"
theme: 'auto'
},
calm: {
hideNicknames: false,
@ -55,7 +54,7 @@ const useSettingsState = createState<SettingsState>('Settings', {
videoShown: true
},
leap: {
categories: leapCategories,
categories: leapCategories
},
tutorial: {
seen: true,

View File

@ -1,4 +1,4 @@
import { BaseState, createState } from "./base";
import { BaseState, createState } from './base';
export interface GcpToken {
accessKey: string;
@ -17,7 +17,7 @@ export interface StorageState extends BaseState<StorageState> {
};
credentials: any | null; // TODO better type
}
};
}
const useStorageState = createState<StorageState>('Storage', {
gcp: {},
@ -26,8 +26,8 @@ const useStorageState = createState<StorageState>('Storage', {
buckets: new Set(),
currentBucket: ''
},
credentials: null,
credentials: null
}
}, ['s3']);
export default useStorageState;
export default useStorageState;

View File

@ -1,22 +1,20 @@
import _ from 'lodash';
import BaseStore from './base';
import InviteReducer from '../reducers/invite-update';
import MetadataReducer from '../reducers/metadata-update';
import { StoreState } from './type';
import { Cage } from '~/types/cage';
import S3Reducer from '../reducers/s3-update';
import { GraphReducer } from '../reducers/graph-update';
import { HarkReducer } from '../reducers/hark-update';
import { ContactReducer } from '../reducers/contact-update';
import GroupReducer from '../reducers/group-update';
import LaunchReducer from '../reducers/launch-update';
import ConnectionReducer from '../reducers/connection';
import SettingsReducer from '../reducers/settings-update';
import GcpReducer from '../reducers/gcp-reducer';
import { GroupViewReducer } from '../reducers/group-view';
import { unstable_batchedUpdates } from 'react-dom';
import { Cage } from '~/types/cage';
import ConnectionReducer from '../reducers/connection';
import { ContactReducer } from '../reducers/contact-update';
import GcpReducer from '../reducers/gcp-reducer';
import { GraphReducer } from '../reducers/graph-update';
import GroupReducer from '../reducers/group-update';
import { GroupViewReducer } from '../reducers/group-view';
import { HarkReducer } from '../reducers/hark-update';
import InviteReducer from '../reducers/invite-update';
import LaunchReducer from '../reducers/launch-update';
import MetadataReducer from '../reducers/metadata-update';
import S3Reducer from '../reducers/s3-update';
import SettingsReducer from '../reducers/settings-update';
import BaseStore from './base';
import { StoreState } from './type';
export default class GlobalStore extends BaseStore<StoreState> {
inviteReducer = new InviteReducer();
@ -42,7 +40,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
initialState(): StoreState {
return {
connection: 'connected',
connection: 'connected'
};
}

View File

@ -1,6 +1,6 @@
import BaseStore from '../store/base';
import BaseApi from '../api/base';
import { Path } from '@urbit/api';
import BaseApi from '../api/base';
import BaseStore from '../store/base';
export default class BaseSubscription<S extends object> {
private errorCount = 0;

View File

@ -1,8 +1,6 @@
import BaseSubscription from './base';
import { StoreState } from '../store/type';
import { Path } from '@urbit/api';
import _ from 'lodash';
import { StoreState } from '../store/type';
import BaseSubscription from './base';
export default class GlobalSubscription extends BaseSubscription<StoreState> {
openSubscriptions: any = {};
@ -25,7 +23,7 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
this.subscribe('/all', 'group-view');
this.subscribe('/nacks', 'contact-pull-hook');
this.clearQueue();
this.subscribe('/updates', 'graph-store');
}
@ -39,8 +37,8 @@ export default class GlobalSubscription extends BaseSubscription<StoreState> {
}
unsubscribe(id) {
for (let key in Object.keys(this.openSubscriptions)) {
let val = this.openSubscriptions[key];
for (const key in Object.keys(this.openSubscriptions)) {
const val = this.openSubscriptions[key];
if (id === val.id) {
delete this.openSubscriptions[`${val.app}${val.path}`];
super.unsubscribe(id);

View File

@ -1,8 +1,8 @@
if ("serviceWorker" in navigator && process.env.NODE_ENV !== 'development') {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/~landscape/js/bundle/serviceworker.js", {
scope: "/",
}).then(reg => {
if ('serviceWorker' in navigator && process.env.NODE_ENV !== 'development') {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/~landscape/js/bundle/serviceworker.js', {
scope: '/'
}).then((reg) => {
});
});
}

View File

@ -1,24 +1,21 @@
import { registerRoute } from 'workbox-routing';
import {
NetworkFirst,
StaleWhileRevalidate,
CacheFirst,
} from 'workbox-strategies';
// Used for filtering matches based on status code, header, or both
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Used to limit entries in cache, remove entries after a certain period of time
import { ExpirationPlugin } from 'workbox-expiration';
import { registerRoute } from 'workbox-routing';
import {
CacheFirst, NetworkFirst,
StaleWhileRevalidate
} from 'workbox-strategies';
// generate a different sw for every build, to bust cache properly
const hash = process.env.LANDSCAPE_SHORTHASH;
self.addEventListener("install", ev => {
self.addEventListener('install', (ev) => {
self.skipWaiting();
});
self.addEventListener('activate', ev => {
self.addEventListener('activate', (ev) => {
ev.waitUntil(clients.claim());
});
@ -33,10 +30,10 @@ registerRoute(
plugins: [
// Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({
statuses: [200],
}),
],
}),
statuses: [200]
})
]
})
);
// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
@ -53,12 +50,12 @@ registerRoute(
plugins: [
// Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({
statuses: [200],
}),
],
}),
statuses: [200]
})
]
})
);
// Cache images with a Cache First strategy
registerRoute(
// Check to see if the request's destination is style for an image
@ -70,13 +67,13 @@ registerRoute(
plugins: [
// Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({
statuses: [200],
statuses: [200]
}),
// Don't cache more than 50 items, and expire them after 30 days
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 Days
}),
],
}),
maxAgeSeconds: 60 * 60 * 24 * 30 // 30 Days
})
]
})
);

View File

@ -1,8 +1,8 @@
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';
import { ConnectionStatus } from './connection';
import { LaunchUpdate, WeatherState } from './launch-update';
import { LocalUpdate } from './local-update';
interface MarksToTypes {
readonly json: any;

View File

@ -1,8 +1,8 @@
export interface GcpToken {
accessKey: string;
expiresIn: number;
};
}
export interface GcpState {
token?: GcpToken
};
}

View File

@ -1,10 +1,11 @@
export * from './cage';
export * from './connection';
export * from './gcp-state';
export * from './global';
export * from './launch-update';
export * from './local-update';
export * from './storage-state';
export * from './gcp-state';
export * from './s3-update';
export * from './workspace';
export * from './storage-state';
export * from './util';
export * from './workspace';

View File

@ -1,6 +1,6 @@
export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'profile', 'leap', 'notifications', 'done', 'exit'] as const;
export const leapCategories = ["mychannel", "messages", "updates", "profile", "logout"];
export const leapCategories = ['mychannel', 'messages', 'updates', 'profile', 'logout'];
export type LeapCategories = typeof leapCategories[number];

View File

@ -1,8 +1,7 @@
import {GcpState} from './gcp-state';
import {S3State} from './s3-update';
import { GcpState } from './gcp-state';
import { S3State } from './s3-update';
export interface StorageState {
gcp: GcpState;
s3: S3State;
};
}

View File

@ -1,42 +1,34 @@
import { hot } from 'react-hot-loader/root';
import 'react-hot-loader';
import * as React from 'react';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import dark from '@tlon/indigo-dark';
import light from '@tlon/indigo-light';
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import Helmet from 'react-helmet';
import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind';
import './css/indigo-static.css';
import './css/fonts.css';
import './apps/chat/css/custom.css';
import './landscape/css/custom.css';
import light from '@tlon/indigo-light';
import dark from '@tlon/indigo-dark';
import { Text, Anchor, Row } from '@tlon/indigo-react';
import { Content } from './landscape/components/Content';
import StatusBar from './components/StatusBar';
import Omnibox from './components/leap/Omnibox';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import { TutorialModal } from '~/views/landscape/components/TutorialModal';
import GlobalStore from '~/logic/store/store';
import GlobalSubscription from '~/logic/subscription/global';
import * as React from 'react';
import Helmet from 'react-helmet';
import 'react-hot-loader';
import { hot } from 'react-hot-loader/root';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider } from 'styled-components';
import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import gcpManager from '~/logic/lib/gcpManager';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
import withState from '~/logic/lib/withState';
import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import useLocalState from '~/logic/state/local';
import useSettingsState from '~/logic/state/settings';
import gcpManager from '~/logic/lib/gcpManager';
import GlobalStore from '~/logic/store/store';
import GlobalSubscription from '~/logic/subscription/global';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import { TutorialModal } from '~/views/landscape/components/TutorialModal';
import './apps/chat/css/custom.css';
import Omnibox from './components/leap/Omnibox';
import StatusBar from './components/StatusBar';
import './css/fonts.css';
import './css/indigo-static.css';
import { Content } from './landscape/components/Content';
import './landscape/css/custom.css';
const Root = withState(styled.div`
font-family: ${p => p.theme.fonts.sans};
@ -126,13 +118,13 @@ class App extends React.Component {
}
updateTheme(e) {
this.props.set(state => {
this.props.set((state) => {
state.dark = e.matches;
});
}
updateMobile(e) {
this.props.set(state => {
this.props.set((state) => {
state.mobile = e.matches;
});
}
@ -155,8 +147,8 @@ class App extends React.Component {
getTheme() {
const { props } = this;
return ((props.dark && props?.display?.theme == "auto") ||
props?.display?.theme == "dark"
return ((props.dark && props?.display?.theme == 'auto') ||
props?.display?.theme == 'dark'
) ? dark : light;
}

View File

@ -1,33 +1,20 @@
import React, {
useRef,
useCallback,
useEffect,
useState,
useMemo,
} from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { Col } from '@tlon/indigo-react';
import _ from 'lodash';
import bigInt, { BigInteger } from 'big-integer';
import { Association } from '@urbit/api/metadata';
import { StoreState } from '~/logic/store/type';
import { useFileDrag } from '~/logic/lib/useDrag';
import ChatWindow from './components/ChatWindow';
import ChatInput from './components/ChatInput';
import GlobalApi from '~/logic/api/global';
import { ShareProfile } from '~/views/apps/chat/components/ShareProfile';
import SubmitDragger from '~/views/components/SubmitDragger';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { Loading } from '~/views/components/Loading';
import { isWriter, resourceFromPath } from '~/logic/lib/group';
import useContactState from '~/logic/state/contact';
import useGraphState, { useGraphForAssoc } from '~/logic/state/graph';
import useGroupState, { useGroupForAssoc } from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import { Content, createPost, Post } from '@urbit/api';
import { Association } from '@urbit/api/metadata';
import { BigInteger } from 'big-integer';
import React, {
ReactElement, useCallback,
useEffect,
useMemo, useState
} from 'react';
import GlobalApi from '~/logic/api/global';
import { isWriter, resourceFromPath } from '~/logic/lib/group';
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
import useGraphState, { useGraphForAssoc } from '~/logic/state/graph';
import { useGroupForAssoc } from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import { StoreState } from '~/logic/store/type';
import { Loading } from '~/views/components/Loading';
import { ChatPane } from './components/ChatPane';
const getCurrGraphSize = (ship: string, name: string) => {
@ -36,20 +23,19 @@ const getCurrGraphSize = (ship: string, name: string) => {
return graph?.size ?? 0;
};
type ChatResourceProps = StoreState & {
association: Association;
api: GlobalApi;
baseUrl: string;
};
function ChatResource(props: ChatResourceProps) {
const ChatResource = (props: ChatResourceProps): ReactElement => {
const { association, api } = props;
const { resource } = association;
const [toShare, setToShare] = useState<string[] | string | undefined>();
const group = useGroupForAssoc(association)!;
const graph = useGraphForAssoc(association);
const unreads = useHarkState((state) => state.unreads);
const unreads = useHarkState(state => state.unreads);
const unreadCount =
(unreads.graph?.[resource]?.['/']?.unreads as number) || 0;
const canWrite = group ? isWriter(group, resource) : false;
@ -59,12 +45,12 @@ function ChatResource(props: ChatResourceProps) {
const { ship, name } = resourceFromPath(resource);
props.api.graph.getNewest(ship, name, count);
setToShare(undefined);
(async function() {
if(group.hidden) {
(async function () {
if (group.hidden) {
const members = await props.api.contacts.disallowedShipsForOurContact(
Array.from(group.members)
);
if(members.length > 0) {
if (members.length > 0) {
setToShare(members);
}
} else {
@ -75,7 +61,7 @@ function ChatResource(props: ChatResourceProps) {
groupHost,
true
);
if(!shared) {
if (!shared) {
setToShare(association.group);
}
}
@ -108,7 +94,7 @@ function ChatResource(props: ChatResourceProps) {
const expectedSize = graphSize + pageSize;
if (newer) {
const index = graph.peekLargest()?.[0];
if(!index) {
if (!index) {
return true;
}
await api.graph.getYoungerSiblings(
@ -120,7 +106,7 @@ function ChatResource(props: ChatResourceProps) {
return expectedSize !== getCurrGraphSize(ship.slice(1), name);
} else {
const index = graph.peekSmallest()?.[0];
if(!index) {
if (!index) {
return true;
}
await api.graph.getOlderSiblings(ship, name, pageSize, `/${index.toString()}`);
@ -131,7 +117,12 @@ function ChatResource(props: ChatResourceProps) {
const onSubmit = useCallback((contents: Content[]) => {
const { ship, name } = resourceFromPath(resource);
api.graph.addPost(ship, name, createPost(window.ship, contents))
api.graph.addPost(ship, name, createPost(window.ship, contents));
}, [resource]);
const onDelete = useCallback((msg: Post) => {
const { ship, name } = resourceFromPath(resource);
api.graph.removePosts(ship, name, [msg.index]);
}, [resource]);
const dismissUnread = useCallback(() => {
@ -156,6 +147,7 @@ function ChatResource(props: ChatResourceProps) {
api={api}
canWrite={canWrite}
onReply={onReply}
onDelete={onDelete}
fetchMessages={fetchMessages}
dismissUnread={dismissUnread}
getPermalink={getPermalink}
@ -164,6 +156,6 @@ function ChatResource(props: ChatResourceProps) {
promptShare={toShare}
/>
);
}
};
export { ChatResource };

View File

@ -1,23 +1,15 @@
import React, { Component } from 'react';
import { UnControlled as CodeEditor } from 'react-codemirror2';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import CodeMirror from 'codemirror';
import styled from "styled-components";
import { Row, BaseTextArea, Box } from '@tlon/indigo-react';
import 'codemirror/mode/markdown/markdown';
/* eslint-disable max-lines-per-function */
import { BaseTextArea, Box, Row } from '@tlon/indigo-react';
import 'codemirror/addon/display/placeholder';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/markdown/markdown';
import React, { Component } from 'react';
import { UnControlled as CodeEditor } from 'react-codemirror2';
import styled from 'styled-components';
import { MOBILE_BROWSER_REGEX } from '~/logic/lib/util';
import '../css/custom.css';
const BROWSER_REGEX =
new RegExp(String(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i));
const MARKDOWN_CONFIG = {
name: 'markdown',
tokenTypeOverrides: {
@ -40,12 +32,12 @@ const MARKDOWN_CONFIG = {
// Until CodeMirror supports options.inputStyle = 'textarea' on mobile,
// we need to hack this into a regular input that has some funny behaviors
const inputProxy = (input) => new Proxy(input, {
const inputProxy = input => new Proxy(input, {
get(target, property) {
if(property === 'focus') {
return () => {
target.focus();
}
};
}
if (property in target) {
return target[property];
@ -55,7 +47,7 @@ const inputProxy = (input) => new Proxy(input, {
target.setSelectionRange(target.value.length, target.value.length);
input.blur();
input.focus();
}
};
}
if (property === 'setOption') {
return () => {};
@ -64,7 +56,9 @@ const inputProxy = (input) => new Proxy(input, {
return () => target.value;
}
if (property === 'setValue') {
return (val) => target.value = val;
return (val) => {
target.value = val;
};
}
if (property === 'element') {
return input;
@ -100,24 +94,40 @@ const MobileBox = styled(Box)`
}
`;
export default class ChatEditor extends Component {
constructor(props) {
interface ChatEditorProps {
message: string;
inCodeMode: boolean;
submit: (message: string) => void;
onUnmount: (message: string) => void;
onPaste: () => void;
changeEvent: (message: string) => void;
placeholder: string;
}
interface ChatEditorState {
message: string;
}
export default class ChatEditor extends Component<ChatEditorProps, ChatEditorState> {
editor: ProxyHandler<unknown> | null;
constructor(props: ChatEditorProps) {
super(props);
this.state = {
message: props.message
};
this.editor = null;
}
componentWillUnmount() {
componentWillUnmount(): void {
this.props.onUnmount(this.state.message);
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: ChatEditorProps): void {
const { props } = this;
if (prevProps.message !== props.message) {
if (prevProps.message !== props.message && this.editor) {
this.editor.setValue(props.message);
this.editor.setOption('mode', MARKDOWN_CONFIG);
this.editor?.focus();
@ -147,7 +157,7 @@ export default class ChatEditor extends Component {
return;
}
let editorMessage = this.editor.getValue();
const editorMessage = this.editor.getValue();
if (editorMessage === '') {
return;
}
@ -203,9 +213,9 @@ export default class ChatEditor extends Component {
}
},
// The below will ony work once codemirror's bug is fixed
spellcheck: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent),
autocorrect: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent),
autocapitalize: !!MOBILE_BROWSER_REGEX.test(navigator.userAgent)
spellcheck: Boolean(MOBILE_BROWSER_REGEX.test(navigator.userAgent)),
autocorrect: Boolean(MOBILE_BROWSER_REGEX.test(navigator.userAgent)),
autocapitalize: Boolean(MOBILE_BROWSER_REGEX.test(navigator.userAgent))
};
return (
@ -227,7 +237,7 @@ export default class ChatEditor extends Component {
data-value={this.state.message}
fontSize="1"
lineHeight="tall"
onClick={event => {
onClick={(event) => {
if (this.editor) {
this.editor.element.focus();
}
@ -237,17 +247,18 @@ export default class ChatEditor extends Component {
fontFamily={inCodeMode ? 'Source Code Pro' : 'Inter'}
fontSize="1"
lineHeight="tall"
rows="1"
rows={1}
style={{ width: '100%', background: 'transparent', color: 'currentColor' }}
placeholder={inCodeMode ? "Code..." : "Message..."}
placeholder={inCodeMode ? 'Code...' : 'Message...'}
onChange={event =>
this.messageChange(null, null, event.target.value)
}
onKeyDown={event =>
this.messageChange(null, null, event.target.value)
}
ref={input => {
if (!input) return;
ref={(input) => {
if (!input)
return;
this.editor = inputProxy(input);
}}
{...props}
@ -263,7 +274,7 @@ export default class ChatEditor extends Component {
editor.focus();
}}
{...props}
/>
/>
}
</Row>

View File

@ -1,16 +1,14 @@
import { BaseImage, Box, Icon, LoadingSpinner, Row } from '@tlon/indigo-react';
import { Contact, Content } from '@urbit/api';
import React, { Component, ReactNode } from 'react';
import ChatEditor from './chat-editor';
import { IuseStorage } from '~/logic/lib/useStorage';
import { uxToHex } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil';
import { createPost } from '~/logic/api/graph';
import tokenizeMessage, { isUrl } from '~/logic/lib/tokenizeMessage';
import GlobalApi from '~/logic/api/global';
import { Contact, Contacts, Content, Post } from '@urbit/api';
import { Row, BaseImage, Box, Icon, LoadingSpinner } from '@tlon/indigo-react';
import withStorage from '~/views/components/withStorage';
import { Sigil } from '~/logic/lib/sigil';
import tokenizeMessage from '~/logic/lib/tokenizeMessage';
import { IuseStorage } from '~/logic/lib/useStorage';
import { MOBILE_BROWSER_REGEX, uxToHex } from '~/logic/lib/util';
import { withLocalState } from '~/logic/state/local';
import { MOBILE_BROWSER_REGEX } from "~/logic/lib/util";
import withStorage from '~/views/components/withStorage';
import ChatEditor from './ChatEditor';
type ChatInputProps = IuseStorage & {
api: GlobalApi;
@ -82,7 +80,7 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
this.chatEditor.current.editor.setValue(url);
this.setState({ uploadingPaste: false });
} else {
props.onSubmit([{ url }])
props.onSubmit([{ url }]);
}
}
@ -231,4 +229,4 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
export default withLocalState<Omit<ChatInputProps, keyof IuseStorage>, 'hideAvatars', ChatInput>(
withStorage<ChatInputProps, ChatInput>(ChatInput, { accept: 'image/*' }),
['hideAvatars']
)
);

View File

@ -1,32 +1,26 @@
/* eslint-disable max-lines-per-function */
import { BaseImage, Box, Col, Icon, Row, Rule, Text } from '@tlon/indigo-react';
import { Contact, Post } from '@urbit/api';
import bigInt from 'big-integer';
import React, {
useState,
useEffect,
useMemo
} from 'react';
import moment from 'moment';
import React, {
useEffect,
useMemo, useState
} from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import { Box, Row, Text, Rule, BaseImage, Icon, Col } from '@tlon/indigo-react';
import GlobalApi from '~/logic/api/global';
import { useIdlingState } from '~/logic/lib/idling';
import { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import {
uxToHex,
cite,
useShowNickname,
useHovering,
daToUnix
cite, daToUnix, useHovering, useShowNickname, uxToHex
} from '~/logic/lib/util';
import { Post } from '@urbit/api';
import { Dropdown } from '~/views/components/Dropdown';
import { useContact } from '~/logic/state/contact';
import useLocalState from '~/logic/state/local';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useContactState, {useContact} from '~/logic/state/contact';
import { useIdlingState } from '~/logic/lib/idling';
import { Dropdown } from '~/views/components/Dropdown';
import ProfileOverlay from '~/views/components/ProfileOverlay';
import {useCopy} from '~/logic/lib/useCopy';
import {GraphContentWide} from '~/views/landscape/components/Graph/GraphContentWide';
import {Contact} from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import { GraphContent} from '~/views/landscape/components/Graph/GraphContent';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
@ -36,7 +30,6 @@ interface DayBreakProps {
shimTop?: boolean;
}
export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
<Row
px={2}
@ -120,7 +113,7 @@ const MessageActionItem = (props) => {
);
};
const MessageActions = ({ api, onReply, association, msg, isAdmin, permalink }) => {
const MessageActions = ({ api, onReply, onDelete, association, msg, isAdmin, permalink }) => {
const isOwn = () => msg.author === window.ship;
const { doCopy, copyDisplay } = useCopy(permalink, 'Copy Message Link');
@ -167,13 +160,13 @@ const MessageActions = ({ api, onReply, association, msg, isAdmin, permalink })
<MessageActionItem onClick={doCopy}>
{copyDisplay}
</MessageActionItem>
{false && (isAdmin() || isOwn()) ? (
<MessageActionItem onClick={(e) => console.log(e)} color='red'>
{(isAdmin || isOwn()) ? (
<MessageActionItem onClick={(e) => onDelete(msg)} color='red'>
Delete Message
</MessageActionItem>
) : null}
{false && (
<MessageActionItem onClick={(e) => console.log(e)}>
<MessageActionItem onClick={e => console.log(e)}>
View Signature
</MessageActionItem>
)}
@ -250,7 +243,14 @@ function ChatMessage(props: ChatMessageProps) {
permalink
} = props;
if (typeof msg === 'string' || !msg) {
return (
<Text gray>This message has been deleted.</Text>
);
}
let onReply = props?.onReply ?? (() => {});
let onDelete = props?.onDelete ?? (() => {});
const transcluded = props?.transcluded ?? 0;
const renderSigil = props.renderSigil ?? (Boolean(nextMsg && msg.author !== nextMsg.author) ||
!nextMsg ||
@ -268,7 +268,7 @@ function ChatMessage(props: ChatMessageProps) {
}
const date = useMemo(() => daToUnix(bigInt(msg.index.split('/')[1])), [msg.index]);
const nextDate = useMemo(() => nextMsg ? (
const nextDate = useMemo(() => nextMsg && typeof nextMsg !== 'string' ? (
daToUnix(bigInt(nextMsg.index.split('/')[1]))
) : null,
[nextMsg]
@ -278,7 +278,7 @@ function ChatMessage(props: ChatMessageProps) {
nextDate &&
new Date(date).getDate() !==
new Date(nextDate).getDate()
, [nextDate, date])
, [nextDate, date]);
const containerClass = `${isPending ? 'o-40' : ''} ${className}`;
@ -299,7 +299,8 @@ function ChatMessage(props: ChatMessageProps) {
fontSize,
hideHover,
transcluded,
onReply
onReply,
onDelete
};
const message = useMemo(() => (
@ -353,9 +354,9 @@ export const MessageAuthor = ({
showOurContact,
...props
}) => {
const osDark = useLocalState((state) => state.dark);
const osDark = useLocalState(state => state.dark);
const theme = useSettingsState((s) => s.display.theme);
const theme = useSettingsState(s => s.display.theme);
const dark = theme === 'dark' || (theme === 'auto' && osDark);
let contact: Contact | null = useContact(`~${msg.author}`);
@ -474,7 +475,7 @@ export const MessageAuthor = ({
};
type MessageProps = { timestamp: string; timestampHover: boolean; }
& Pick<ChatMessageProps, "msg" | "api" | "transcluded" | "showOurContact">
& Pick<ChatMessageProps, 'msg' | 'api' | 'transcluded' | 'showOurContact'>
export const Message = React.memo(({
timestamp,
@ -503,10 +504,10 @@ export const Message = React.memo(({
) : (
<></>
)}
<GraphContentWide
<GraphContent
{...bind}
width="100%"
post={msg}
contents={msg.contents}
transcluded={transcluded}
api={api}
showOurContact={showOurContact}
@ -567,7 +568,7 @@ export const MessagePlaceholder = ({
display='inline-block'
verticalAlign='middle'
fontSize='0'
washedGray
color='washedGray'
cursor='default'
>
<Text maxWidth='32rem' display='block'>

View File

@ -1,27 +1,18 @@
import React, { useRef, useCallback, useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { Col } from '@tlon/indigo-react';
import _ from 'lodash';
import { Content, Graph, Post } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer';
import { Association } from '@urbit/api/metadata';
import { StoreState } from '~/logic/store/type';
import { useFileDrag } from '~/logic/lib/useDrag';
import ChatWindow from './ChatWindow';
import ChatInput from './ChatInput';
import _ from 'lodash';
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
import GlobalApi from '~/logic/api/global';
import { ShareProfile } from '~/views/apps/chat/components/ShareProfile';
import SubmitDragger from '~/views/components/SubmitDragger';
import { useFileDrag } from '~/logic/lib/useDrag';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { Loading } from '~/views/components/Loading';
import { isWriter, resourceFromPath } from '~/logic/lib/group';
import useContactState, { useOurContact } from '~/logic/state/contact';
import { useOurContact } from '~/logic/state/contact';
import useGraphState from '~/logic/state/graph';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import { Post, Graph, Content } from '@urbit/api';
import { getPermalinkForGraph } from '~/logic/lib/permalinks';
import ShareProfile from '~/views/apps/chat/components/ShareProfile';
import { Loading } from '~/views/components/Loading';
import SubmitDragger from '~/views/components/SubmitDragger';
import ChatInput from './ChatInput';
import ChatWindow from './ChatWindow';
interface ChatPaneProps {
/**
@ -43,6 +34,7 @@ interface ChatPaneProps {
* Get contents of reply message
*/
onReply: (msg: Post) => string;
onDelete: (msg: Post) => void;
/**
* Fetch more messages
*
@ -73,7 +65,7 @@ interface ChatPaneProps {
promptShare?: string[] | string;
}
export function ChatPane(props: ChatPaneProps) {
export function ChatPane(props: ChatPaneProps): ReactElement {
const {
api,
graph,
@ -84,10 +76,11 @@ export function ChatPane(props: ChatPaneProps) {
isAdmin,
dismissUnread,
onSubmit,
onDelete,
promptShare = [],
fetchMessages
} = props;
const graphTimesentMap = useGraphState((state) => state.graphTimesentMap);
const graphTimesentMap = useGraphState(state => state.graphTimesentMap);
const ourContact = useOurContact();
const chatInput = useRef<ChatInput>();
@ -109,7 +102,7 @@ export function ChatPane(props: ChatPaneProps) {
);
const appendUnsent = useCallback(
(u: string) => setUnsent((s) => ({ ...s, [id]: u })),
(u: string) => setUnsent(s => ({ ...s, [id]: u })),
[id]
);
@ -133,7 +126,7 @@ export function ChatPane(props: ChatPaneProps) {
const onReply = useCallback(
(msg: Post) => {
const message = props.onReply(msg);
setUnsent((s) => ({ ...s, [id]: message }));
setUnsent(s => ({ ...s, [id]: message }));
},
[id, props.onReply]
);
@ -159,6 +152,7 @@ export function ChatPane(props: ChatPaneProps) {
showOurContact={promptShare.length === 0 && !showBanner}
pendingSize={Object.keys(graphTimesentMap[id] || {}).length}
onReply={onReply}
onDelete={onDelete}
dismissUnread={dismissUnread}
fetchMessages={fetchMessages}
isAdmin={isAdmin}

View File

@ -1,47 +1,27 @@
import React, { useEffect, Component, useRef, useState, useCallback } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import _ from 'lodash';
import bigInt, { BigInteger } from 'big-integer';
import { Col } from '@tlon/indigo-react';
import { Col, Text } from '@tlon/indigo-react';
import {
Patp,
Contacts,
Association,
Associations,
Group,
Groups,
Graph,
Post,
GraphNode
GraphNode, Post
} from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer';
import React, { Component } from 'react';
import GlobalApi from '~/logic/api/global';
import VirtualScroller from '~/views/components/VirtualScroller';
import ChatMessage, { MessagePlaceholder } from './ChatMessage';
import { UnreadNotice } from './unread-notice';
import withState from '~/logic/lib/withState';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
import useGraphState from '~/logic/state/graph';
import UnreadNotice from './UnreadNotice';
const INITIAL_LOAD = 20;
const DEFAULT_BACKLOG_SIZE = 100;
const IDLE_THRESHOLD = 64;
const MAX_BACKLOG_SIZE = 1000;
type ChatWindowProps = {
unreadCount: number;
graph: Graph;
graphSize: number;
station: any;
station: unknown;
fetchMessages: (newer: boolean) => Promise<boolean>;
api: GlobalApi;
scrollTo?: BigInteger;
onReply: (msg: Post) => void;
onDelete: (msg: Post) => void;
dismissUnread: () => void;
pendingSize?: number;
showOurContact: boolean;
@ -58,7 +38,6 @@ interface ChatWindowState {
const virtScrollerStyle = { height: '100%' };
class ChatWindow extends Component<
ChatWindowProps,
ChatWindowState
@ -97,7 +76,6 @@ class ChatWindow extends Component<
this.virtualList!.scrollToIndex(this.props.scrollTo);
}
});
}, this.INITIALIZATION_MAX_TIME);
}
@ -121,26 +99,26 @@ class ChatWindow extends Component<
});
}
dismissedInitialUnread() {
dismissedInitialUnread(): void {
const { unreadCount, graph } = this.props;
return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size :
this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero);
return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size :
this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero);
}
handleWindowBlur() {
handleWindowBlur(): void {
this.setState({ idle: true });
}
handleWindowFocus() {
handleWindowFocus(): void {
this.setState({ idle: false });
if (this.virtualList?.window?.scrollTop === 0) {
this.props.dismissUnread();
}
}
componentDidUpdate(prevProps: ChatWindowProps, prevState) {
const { graph, unreadCount, graphSize, station } = this.props;
componentDidUpdate(prevProps: ChatWindowProps): void {
const { unreadCount, graphSize, station } = this.props;
if(unreadCount === 0 && prevProps.unreadCount !== unreadCount) {
this.unreadSet = true;
}
@ -155,7 +133,6 @@ class ChatWindow extends Component<
this.virtualList!.startOffset() < 5) {
this.props.dismissUnread();
}
}
if (unreadCount > prevProps.unreadCount) {
@ -168,7 +145,7 @@ class ChatWindow extends Component<
}
}
stayLockedIfActive() {
stayLockedIfActive(): void {
if (this.virtualList && !this.state.idle) {
this.virtualList.resetScroll();
this.props.dismissUnread();
@ -181,7 +158,7 @@ class ChatWindow extends Component<
}
}
scrollToUnread() {
scrollToUnread(): void {
const { unreadIndex } = this.state;
if (unreadIndex.eq(bigInt.zero)) {
return;
@ -190,35 +167,42 @@ class ChatWindow extends Component<
this.virtualList?.scrollToIndex(this.state.unreadIndex);
}
onScroll = ({ scrollTop, scrollHeight, windowHeight }) => {
onScroll = ({ scrollTop }) => {
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
this.setState({ idle: true });
}
}
renderer = React.forwardRef(({ index, scrollWindow }, ref) => {
const {
api,
showOurContact,
graph,
onReply,
onDelete,
getPermalink,
dismissUnread,
isAdmin,
isAdmin
} = this.props;
const permalink = getPermalink(index);
const messageProps = {
showOurContact,
api,
onReply,
onDelete,
permalink,
dismissUnread,
isAdmin
};
const msg = graph.get(index)?.post;
if (!msg) return null;
if (!msg || typeof msg === 'string') {
return (
<Text pl="44px" pt="1" pb="1" gray display="block">
This message has been deleted.
</Text>
);
};
if (!this.state.initialized) {
return (
<MessagePlaceholder
@ -234,7 +218,7 @@ class ChatWindow extends Component<
);
const highlighted = index.eq(this.props.scrollTo ?? bigInt.zero);
const keys = graph.keys();
const graphIdx = keys.findIndex((idx) => idx.eq(index));
const graphIdx = keys.findIndex(idx => idx.eq(index));
const prevIdx = keys[graphIdx - 1];
const nextIdx = keys[graphIdx + 1];
const isLastRead: boolean = this.state.unreadIndex.eq(index);
@ -262,17 +246,15 @@ class ChatWindow extends Component<
render() {
const {
unreadCount,
api,
graph,
showOurContact,
pendingSize = 0,
pendingSize = 0
} = this.props;
const unreadMsg = graph.get(this.state.unreadIndex);
return (
<Col height='100%' overflow='hidden' position='relative'>
{ this.dismissedInitialUnread() &&
{ this.dismissedInitialUnread() &&
(<UnreadNotice
unreadCount={unreadCount}
unreadMsg={
@ -284,7 +266,7 @@ class ChatWindow extends Component<
}
dismissUnread={this.props.dismissUnread}
onClick={this.scrollToUnread}
/>)}
/>)}
<VirtualScroller<GraphNode>
ref={(list) => {
this.virtualList = list;
@ -306,5 +288,4 @@ class ChatWindow extends Component<
}
}
export default ChatWindow
export default ChatWindow;

View File

@ -1,19 +1,20 @@
import React, {
useState,
useEffect
} from 'react';
import _ from 'lodash';
import { Box, Row, Text, BaseImage } from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
import { BaseImage, Box, Row, Text } from '@tlon/indigo-react';
import { Contact } from '@urbit/api';
import React, { ReactElement } from 'react';
import GlobalApi from '~/logic/api/global';
import { Sigil } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
export const ShareProfile = (props) => {
interface ShareProfileProps {
our?: Contact;
api: GlobalApi;
recipients: string | string[];
onShare: () => void;
}
const ShareProfile = (props: ShareProfileProps): ReactElement | null => {
const {
api,
showBanner,
setShowBanner,
group,
groupPath,
recipients
} = props;
@ -24,18 +25,21 @@ export const ShareProfile = (props) => {
width='24px'
height='24px'
borderRadius={2}
style={{ objectFit: 'cover' }} />
style={{ objectFit: 'cover' }}
/>
) : (
<Row
p={1}
alignItems="center"
borderRadius={2}
backgroundColor={!!props.our ? `#${uxToHex(props.our.color)}` : "#000000"}>
backgroundColor={props.our ? `#${uxToHex(props.our.color)}` : '#000000'}
>
<Sigil
ship={window.ship}
size={16}
color={!!props.our ? `#${uxToHex(props.our.color)}` : "#000000"}
icon />
color={props.our ? `#${uxToHex(props.our.color)}` : '#000000'}
icon
/>
</Row>
);
@ -48,8 +52,8 @@ export const ShareProfile = (props) => {
}
} else if(recipients.length > 0) {
await api.contacts.allowShips(recipients);
await Promise.all(recipients.map(r => api.contacts.share(r)))
}
await Promise.all(recipients.map(r => api.contacts.share(r)));
}
props.onShare();
};
@ -72,3 +76,5 @@ export const ShareProfile = (props) => {
</Row>
) : null;
};
export default ShareProfile;

View File

@ -1,11 +1,9 @@
import React, { useEffect, useState } from 'react';
import { Box, Center, Icon, Text } from '@tlon/indigo-react';
import moment from 'moment';
import { Box, Text, Center, Icon } from '@tlon/indigo-react';
import VisibilitySensor from 'react-visibility-sensor';
import React, { ReactElement } from 'react';
import Timestamp from '~/views/components/Timestamp';
export const UnreadNotice = (props) => {
const UnreadNotice = (props): ReactElement | null => {
const { unreadCount, unreadMsg, dismissUnread, onClick } = props;
if (unreadCount === 0) {
@ -20,7 +18,7 @@ export const UnreadNotice = (props) => {
p='12px'
width='100%'
position='absolute'
zIndex='1'
zIndex={1}
className='unread-notice'
>
<Center>
@ -66,3 +64,5 @@ export const UnreadNotice = (props) => {
</Box>
);
};
export default UnreadNotice;

View File

@ -1,15 +1,18 @@
import React, { PureComponent } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom';
import { Center, Text } from "@tlon/indigo-react";
import { Center, Text } from '@tlon/indigo-react';
import React, { ReactElement } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom';
import GlobalApi from '~/logic/api/global';
import { deSig } from '~/logic/lib/util';
import useGraphState from '~/logic/state/graph';
import useMetadataState from '~/logic/state/metadata';
import useGroupState from '~/logic/state/group';
const GraphApp = (props) => {
interface GraphAppProps {
api: GlobalApi;
}
const GraphApp = (props: GraphAppProps): ReactElement => {
const associations= useMetadataState(state => state.associations);
const graphKeys = useGraphState(state => state.graphKeys);
const groups = useGroupState(state => state.groups);
const history = useHistory();
const { api } = props;
@ -24,15 +27,12 @@ const GraphApp = (props) => {
const path = `/ship/~${deSig(ship)}/${name}`;
const association = associations.graph[path];
const autoJoin = () => {
try {
api.graph.joinGraph(
`~${deSig(props.match.params.ship)}`,
props.match.params.name
);
} catch(err) {
setTimeout(autoJoin, 2000);
}
@ -40,7 +40,7 @@ const GraphApp = (props) => {
if(!graphKeys.has(resource)) {
autoJoin();
} else if(!!association && 'graph' in association.config) {
} else if(Boolean(association) && 'graph' in association.config) {
history.push(`/~landscape/home/resource/${association.metadata.config.graph}${path}`);
}
return (
@ -52,6 +52,6 @@ const GraphApp = (props) => {
/>
</Switch>
);
}
};
export default GraphApp;

Some files were not shown because too many files have changed in this diff Show More