interface: Converted all to typescript, fixed auto-fixable problems

This commit is contained in:
Tyler Brown Cifu Shuster 2021-05-05 15:22:08 -07:00
parent f54410c3fd
commit 76bef0ba00
269 changed files with 2666 additions and 3025 deletions

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
import type { StoreState } from '../store/type';
import BaseApi from './base'; import BaseApi from './base';
import type {StoreState} from '../store/type';
import type {GcpToken} from '../../types/gcp-state';
export default class GcpApi extends BaseApi<StoreState> { export default class GcpApi extends BaseApi<StoreState> {
// Does not touch the store; use the value manually. // 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 { Patp } from '@urbit/api';
import BaseApi from './base';
import { StoreState } from '../store/type';
import GlobalStore from '../store/store'; import GlobalStore from '../store/store';
import LocalApi from './local'; import { StoreState } from '../store/type';
import InviteApi from './invite'; import BaseApi from './base';
import MetadataApi from './metadata';
import ContactsApi from './contacts'; 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 GcpApi from './gcp';
import GraphApi from './graph';
import GroupsApi from './groups';
import { HarkApi } from './hark'; 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'; import SettingsApi from './settings';
export default class GlobalApi extends BaseApi<StoreState> { export default class GlobalApi extends BaseApi<StoreState> {

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import BaseApi from './base'; import { Association, GraphNotifDescription, IndexedNotification, NotifIndex } from '@urbit/api';
import { StoreState } from '../store/type';
import { dateToDa, decToUd } from '../lib/util';
import { NotifIndex, IndexedNotification, Association, GraphNotifDescription } from '@urbit/api';
import { BigInteger } from 'big-integer'; import { BigInteger } from 'big-integer';
import { getParentIndex } from '../lib/notification'; import { getParentIndex } from '../lib/notification';
import { dateToDa, decToUd } from '../lib/util';
import useHarkState from '../state/hark'; import useHarkState from '../state/hark';
import { StoreState } from '../store/type';
import BaseApi from './base';
function getHarkSize() { function getHarkSize() {
return useHarkState.getState().notifications.size ?? 0; return useHarkState.getState().notifications.size ?? 0;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,14 +7,12 @@
// //
import querystring from 'querystring'; import querystring from 'querystring';
import { import {
StorageAcl,
StorageClient, StorageClient,
StorageUpload, StorageUpload,
UploadParams, UploadParams,
UploadResult UploadResult
} from './StorageClient'; } from './StorageClient';
const ENDPOINT = 'storage.googleapis.com'; const ENDPOINT = 'storage.googleapis.com';
class GcpUpload implements StorageUpload { class GcpUpload implements StorageUpload {
@ -27,7 +25,7 @@ class GcpUpload implements StorageUpload {
} }
async promise(): Promise<UploadResult> { async promise(): Promise<UploadResult> {
const {Bucket, Key, ContentType, Body} = this.#params; const { Bucket, Key, ContentType, Body } = this.#params;
const urlParams = { const urlParams = {
uploadType: 'media', uploadType: 'media',
name: Key, name: Key,
@ -50,7 +48,7 @@ class GcpUpload implements StorageUpload {
console.error('GcpClient server error', await response.json()); console.error('GcpClient server error', await response.json());
throw new Error(`GcpClient: response ${response.status}`); 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. // Defines a StorageClient interface interoperable between S3 and GCP Storage.
// //
// XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'. // XX kind of gross. S3 needs 'public-read', GCP needs 'publicRead'.
// Rather than write a wrapper around S3, we offer this field here, which // 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 // should always be passed, and will be replaced by 'publicRead' in the
// GCP client. // GCP client.
export enum StorageAcl { export enum StorageAcl {
PublicRead = 'public-read' PublicRead = 'public-read'
}; }
export interface UploadParams { export interface UploadParams {
Bucket: string; // the bucket to upload the object to Bucket: string; // the bucket to upload the object to
@ -16,17 +15,17 @@ export interface UploadParams {
ContentType: string; // the object's mime-type ContentType: string; // the object's mime-type
ACL: StorageAcl; // ACL, always 'public-read' ACL: StorageAcl; // ACL, always 'public-read'
Body: File; // the object itself Body: File; // the object itself
}; }
export interface UploadResult { export interface UploadResult {
Location: string; Location: string;
}; }
// Extra layer of indirection used by S3 client. // Extra layer of indirection used by S3 client.
export interface StorageUpload { export interface StorageUpload {
promise(): Promise<UploadResult>; promise(): Promise<UploadResult>;
}; }
export interface StorageClient { export interface StorageClient {
upload(params: UploadParams): StorageUpload; 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) { export function max(a: BigInteger, b: BigInteger) {
return a.gt(b) ? a : b; 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>; export type SubmitHandler = () => Promise<any>;
interface IFormGroupContext { interface IFormGroupContext {
@ -12,7 +12,7 @@ const fallback: IFormGroupContext = {
addSubmit: () => {}, addSubmit: () => {},
onDirty: () => {}, onDirty: () => {},
onErrors: () => {}, onErrors: () => {},
submitAll: () => Promise.resolve(), submitAll: () => Promise.resolve()
}; };
export const FormGroupContext = React.createContext(fallback); export const FormGroupContext = React.createContext(fallback);

View File

@ -15,7 +15,6 @@
import GlobalApi from '../api/global'; import GlobalApi from '../api/global';
import useStorageState from '../state/storage'; import useStorageState from '../state/storage';
class GcpManager { class GcpManager {
#api: GlobalApi | null = null; #api: GlobalApi | null = null;
@ -59,15 +58,15 @@ class GcpManager {
this.start(); this.start();
} }
#consecutiveFailures: number = 0; #consecutiveFailures = 0;
#configured: boolean = false; #configured = false;
private refreshLoop() { private refreshLoop() {
if (!this.#configured) { if (!this.#configured) {
this.#api!.gcp.isConfigured() this.#api!.gcp.isConfigured()
.then((configured) => { .then((configured) => {
if (configured === undefined) { 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; this.#configured = configured;
if (this.#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 _ from 'lodash';
import { roleTags, RoleTags, Group, Resource } from '@urbit/api/groups';
import { PatpNoSig, Path } from '@urbit/api';
import { deSig } from './util';
export function roleForShip( export function roleForShip(
group: Group, group: Group,

View File

@ -1,6 +1,6 @@
import { IndexedNotification, NotificationGraphConfig, Unreads } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import f from 'lodash/fp'; import f from 'lodash/fp';
import { Unreads, NotificationGraphConfig, IndexedNotification } from '@urbit/api';
export function getLastSeen( export function getLastSeen(
unreads: Unreads, unreads: Unreads,
@ -36,13 +36,13 @@ export function getNotificationCount(
} }
export function isWatching( export function isWatching(
config: NotificationGraphConfig, config: NotificationGraphConfig,
graph: string, graph: string,
index = "/" index = '/'
) { ) {
return !!config.watching.find( return Boolean(config.watching.find(
watch => watch.graph === graph && watch.index === index watch => watch.graph === graph && watch.index === index
); ));
} }
export function getNotificationKey(time: BigInteger, notification: IndexedNotification): string { 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() { export function useIdlingState() {
const [idling, setIdling] = useState(false); const [idling, setIdling] = useState(false);
useEffect(() => { useEffect(() => {
function blur() { function blur() {
setIdling(true); setIdling(true);
} }
@ -16,7 +16,7 @@ export function useIdlingState() {
return () => { return () => {
window.removeEventListener('blur', blur); window.removeEventListener('blur', blur);
window.removeEventListener('focus', focus); window.removeEventListener('focus', focus);
} };
}, []); }, []);
return idling; return idling;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
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 { buntPost } from '~/logic/lib/post';
import { unixToDa } from '~/logic/lib/util'; import { unixToDa } from '~/logic/lib/util';
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
import bigInt, { BigInteger } from 'big-integer';
export function newPost( export function newPost(
title: string, title: string,

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 { Box } from '@tlon/indigo-react';
import { reactRenderer, sigil } from '@tlon/sigil-js';
import React, { memo } from 'react';
export const foregroundFromBackground = (background) => { export const foregroundFromBackground = (background) => {
const rgb = { const rgb = {

View File

@ -1,5 +1,5 @@
import urbitOb from 'urbit-ob'; 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 URL_REGEX = new RegExp(String(/^(([\w\-\+]+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+\w)/.source));
@ -9,14 +9,14 @@ const isUrl = (string) => {
} catch (e) { } catch (e) {
return false; return false;
} }
} };
const isRef = (str) => { const isRef = (str) => {
return isUrl(str) && str.startsWith("web+urbitgraph://"); return isUrl(str) && str.startsWith('web+urbitgraph://');
} };
const tokenizeMessage = (text) => { const tokenizeMessage = (text) => {
let messages = []; const messages = [];
let message = []; let message = [];
let isInCodeBlock = false; let isInCodeBlock = false;
let endOfCodeBlock = false; let endOfCodeBlock = false;
@ -78,7 +78,6 @@ const tokenizeMessage = (text) => {
} }
messages.push({ mention: str }); messages.push({ mention: str });
message = []; message = [];
} else { } else {
message.push(str); message.push(str);
} }

View File

@ -1,6 +1,6 @@
import { Associations } from '@urbit/api'; import { Associations } from '@urbit/api';
import { TutorialProgress } from '~/types';
import { AlignX, AlignY } from '~/logic/lib/relativePosition'; import { AlignX, AlignY } from '~/logic/lib/relativePosition';
import { TutorialProgress } from '~/types';
import { Direction } from '~/views/components/Triangle'; import { Direction } from '~/views/components/Triangle';
export const MODAL_WIDTH = 256; export const MODAL_WIDTH = 256;
@ -92,7 +92,7 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignY: 'top', alignY: 'top',
arrow: 'East', arrow: 'East',
offsetX: MODAL_WIDTH + 24, offsetX: MODAL_WIDTH + 24,
offsetY: 80, offsetY: 80
}, },
channels: { channels: {
title: 'Channels', title: 'Channels',
@ -157,17 +157,17 @@ export const progressDetails: Record<TutorialProgress, StepDetail> = {
alignX: 'right', alignX: 'right',
arrow: 'South', arrow: 'South',
offsetX: -300 + MODAL_WIDTH / 2, offsetX: -300 + MODAL_WIDTH / 2,
offsetY: -4, offsetY: -4
}, },
leap: { leap: {
title: 'Leap', title: 'Leap',
description: description:
'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.', 'Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.',
url: `/~profile/~${window.ship}`, url: `/~profile/~${window.ship}`,
alignY: "top", alignY: 'top',
alignX: "left", alignX: 'left',
arrow: "North", arrow: 'North',
offsetX: 76, offsetX: 76,
offsetY: -48, offsetY: -48
}, }
}; };

View File

@ -1,5 +1,5 @@
import { writeText } from "./util"; import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState, useMemo } 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 [didCopy, setDidCopy] = useState(false);
@ -11,9 +11,9 @@ export function useCopy(copied: string, display: string) {
}, 2000); }, 2000);
}, [copied]); }, [copied]);
const copyDisplay = useMemo(() => (didCopy ? "Copied" : display), [ const copyDisplay = useMemo(() => (didCopy ? 'Copied' : display), [
didCopy, didCopy,
display, display
]); ]);
return { copyDisplay, doCopy, didCopy }; 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 { function validateDragEvent(e: DragEvent): FileList | File[] | true | null {
const files: File[] = []; 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>( export function useDropdown<C>(
candidates: C[], candidates: C[],

View File

@ -1,5 +1,5 @@
import { useEffect, RefObject, useRef, useState } from 'react';
import _ from 'lodash'; import _ from 'lodash';
import { RefObject, useEffect, useState } from 'react';
import usePreviousValue from './usePreviousValue'; import usePreviousValue from './usePreviousValue';
export function distanceToBottom(el: HTMLElement) { 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 { function retrieve<T>(key: string, initial: T): T {
const s = localStorage.getItem(key); const s = localStorage.getItem(key);

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useEffect, useState } from 'react';
import {unstable_batchedUpdates} from "react-dom"; import { unstable_batchedUpdates } from 'react-dom';
export type IOInstance<I, P, O> = ( export type IOInstance<I, P, O> = (
input: I input: I
@ -29,7 +29,7 @@ export function useRunIO<I, O>(
}); });
useEffect(() => { useEffect(() => {
reject(new Error("useRunIO: key changed")); reject(new Error('useRunIO: key changed'));
setDone(false); setDone(false);
setOutput(null); setOutput(null);
}, [key]); }, [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 type AsyncClickableState = 'waiting' | 'error' | 'loading' | 'success';
export function useStatelessAsyncClickable( 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 S3 from 'aws-sdk/clients/s3';
import GcpClient from './GcpClient'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { StorageClient, StorageAcl } from './StorageClient';
import { dateToDa, deSig } from './util';
import useStorageState from '../state/storage'; import useStorageState from '../state/storage';
import GcpClient from './GcpClient';
import { StorageAcl, StorageClient } from './StorageClient';
import { dateToDa, deSig } from './util';
export interface IuseStorage { export interface IuseStorage {
canUpload: boolean; canUpload: boolean;
upload: (file: File, bucket: string) => Promise<string>; upload: (file: File, bucket: string) => Promise<string>;
uploadDefault: (file: File) => Promise<string>; uploadDefault: (file: File) => Promise<string>;
uploading: boolean; uploading: boolean;
promptUpload: () => Promise<unknown>; promptUpload: () => Promise<string>;
} }
const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => { const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
@ -54,7 +48,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
); );
const upload = useCallback( const upload = useCallback(
async (file: File, bucket: string) => { async (file: File, bucket: string): Promise<string> => {
if (client.current === null) { if (client.current === null) {
throw new Error('Storage not ready'); throw new Error('Storage not ready');
} }
@ -83,7 +77,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
[client, setUploading] [client, setUploading]
); );
const uploadDefault = useCallback(async (file: File) => { const uploadDefault = useCallback(async (file: File): Promise<string> => {
if (s3.configuration.currentBucket === '') { if (s3.configuration.currentBucket === '') {
throw new Error('current bucket not set'); throw new Error('current bucket not set');
} }
@ -91,7 +85,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
}, [s3, upload]); }, [s3, upload]);
const promptUpload = useCallback( const promptUpload = useCallback(
() => { (): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const fileSelector = document.createElement('input'); const fileSelector = document.createElement('input');
fileSelector.setAttribute('type', 'file'); fileSelector.setAttribute('type', 'file');
@ -101,10 +95,10 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
const files = fileSelector.files; const files = fileSelector.files;
if (!files || files.length <= 0) { if (!files || files.length <= 0) {
reject(); 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); document.body.appendChild(fileSelector);
fileSelector.click(); fileSelector.click();
@ -113,7 +107,7 @@ const useStorage = ({ accept = '*' } = { accept: '*' }): IuseStorage => {
[uploadDefault] [uploadDefault]
); );
return {canUpload, upload, uploadDefault, uploading, promptUpload}; return { canUpload, upload, uploadDefault, uploading, promptUpload };
}; };
export default useStorage; export default useStorage;

View File

@ -1,10 +1,10 @@
import { useState, useCallback } from "react"; import { useCallback, useState } from 'react';
export function useToggleState(initial: boolean) { export function useToggleState(initial: boolean) {
const [state, setState] = useState(initial); const [state, setState] = useState(initial);
const toggle = useCallback(() => { const toggle = useCallback(() => {
setState((s) => !s); setState(s => !s);
}, [setState]); }, [setState]);
return [state, toggle] as const; 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) { export function useWaitForProps<P>(props: P, timeout = 0) {
const [resolve, setResolve] = useState<() => void>(() => () => {}); const [resolve, setResolve] = useState<() => void>(() => () => {});

View File

@ -1,14 +1,13 @@
/* eslint-disable max-lines */ /* 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 { Association, Contact } from '@urbit/api';
import { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
import anyAscii from 'any-ascii'; 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(); enableMapSet();
@ -202,7 +201,7 @@ export const hexToUx = (hex) => {
return `0x${ux}`; return `0x${ux}`;
}; };
export function writeText(str: string) { export function writeText(str: string | null): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const range = document.createRange(); const range = document.createRange();
range.selectNodeContents(document.body); 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 { export function useShowNickname(contact: Contact | null, hide?: boolean): boolean {
const hideState = useSettingsState(state => state.calm.hideNicknames); const hideState = useSettingsState(state => state.calm.hideNicknames);
const hideNicknames = typeof hide !== 'undefined' ? hide : hideState; const hideNicknames = typeof hide !== 'undefined' ? hide : hideState;
return !!(contact && contact.nickname && !hideNicknames); return Boolean(contact && contact.nickname && !hideNicknames);
} }
interface useHoveringInterface { interface useHoveringInterface {
@ -414,14 +413,13 @@ interface useHoveringInterface {
export const useHovering = (): useHoveringInterface => { export const useHovering = (): useHoveringInterface => {
const [hovering, setHovering] = useState(false); const [hovering, setHovering] = useState(false);
const onMouseOver = useCallback(() => setHovering(true), []) const onMouseOver = useCallback(() => setHovering(true), []);
const onMouseLeave = useCallback(() => setHovering(false), []) const onMouseLeave = useCallback(() => setHovering(false), []);
const bind = useMemo(() => ({ const bind = useMemo(() => ({
onMouseOver, onMouseOver,
onMouseLeave, onMouseLeave
}), [onMouseLeave, onMouseOver]); }), [onMouseLeave, onMouseOver]);
return useMemo(() => ({ hovering, bind }), [hovering, bind]); return useMemo(() => ({ hovering, bind }), [hovering, bind]);
}; };

View File

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

View File

@ -1,6 +1,6 @@
import React from "react"; import React from 'react';
import { UseStore } from "zustand"; import { UseStore } from 'zustand';
import { BaseState } from "../state/base"; import { BaseState } from '../state/base';
const withStateo = < const withStateo = <
StateType extends BaseState<any> StateType extends BaseState<any>
@ -15,8 +15,8 @@ const withStateo = <
(object, key) => ({ ...object, [key]: state[key] }), {} (object, key) => ({ ...object, [key]: state[key] }), {}
) )
) : useState(); ) : useState();
return <Component ref={ref} {...state} {...props} /> return <Component ref={ref} {...state} {...props} />;
}) });
}; };
const withState = < const withState = <
@ -24,10 +24,10 @@ const withState = <
stateKey extends keyof StateType stateKey extends keyof StateType
>( >(
Component: any, Component: any,
stores: ([UseStore<StateType>, stateKey[]])[], stores: ([UseStore<StateType>, stateKey[]])[]
) => { ) => {
return React.forwardRef((props, ref) => { return React.forwardRef((props, ref) => {
let stateProps: unknown = {}; const stateProps: unknown = {};
stores.forEach(([store, keys]) => { stores.forEach(([store, keys]) => {
const storeProps = Array.isArray(keys) const storeProps = Array.isArray(keys)
? store(state => keys.reduce( ? store(state => keys.reduce(
@ -36,8 +36,8 @@ const withState = <
: store(); : store();
Object.assign(stateProps, storeProps); 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 { Cage } from '~/types/cage';
import { StoreState } from '../store/type';
type LocalState = Pick<StoreState, 'connection'>; 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 { ContactUpdate } from '@urbit/api';
import _ from 'lodash';
import useContactState, { ContactState } from '../state/contact';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useContactState, { ContactState } from '../state/contact';
export const ContactReducer = (json) => { export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false); 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]; const value = data['edit-field'][field];
if(field === 'add-group') { if(field === 'add-group') {
state.contacts[ship].groups.push(value); state.contacts[ship].groups.push(value);
} else if (field === 'remove-group') { } else if (field === 'remove-group') {

View File

@ -1,8 +1,7 @@
import {StoreState} from '../store/type'; import type { Cage } from '~/types/cage';
import type {GcpToken} from '../../types/gcp-state'; import type { GcpToken } from '../../types/gcp-state';
import type {Cage} from '~/types/cage';
import useStorageState, { StorageState } from '../state/storage';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useStorageState, { StorageState } from '../state/storage';
export default class GcpReducer { export default class GcpReducer {
reduce(json: Cage) { reduce(json: Cage) {
@ -13,21 +12,21 @@ export default class GcpReducer {
} }
const reduceToken = (json: Cage, state: StorageState): StorageState => { const reduceToken = (json: Cage, state: StorageState): StorageState => {
let data = json['gcp-token']; const data = json['gcp-token'];
if (data) { if (data) {
setToken(data, state); setToken(data, state);
} }
return state; return state;
} };
const setToken = (data: any, state: StorageState): StorageState => { const setToken = (data: any, state: StorageState): StorageState => {
if (isToken(data)) { if (isToken(data)) {
state.gcp.token = data; state.gcp.token = data;
} }
return state; return state;
} };
const isToken = (token: any): token is GcpToken => { const isToken = (token: any): token is GcpToken => {
return (typeof(token.accessKey) === 'string' && return (typeof(token.accessKey) === 'string' &&
typeof(token.expiresIn) === 'number'); 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 produce from 'immer';
import bigInt, { BigInteger } from "big-integer"; import _ from 'lodash';
import useGraphState, { GraphState } from '../state/graph';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useGraphState, { GraphState } from '../state/graph';
export const GraphReducer = (json) => { export const GraphReducer = (json) => {
const data = _.get(json, 'graph-update', false); const data = _.get(json, 'graph-update', false);
if (data) { if (data) {
reduceState<GraphState, any>(useGraphState, data, [ reduceState<GraphState, any>(useGraphState, data, [
keys, keys,
@ -36,13 +36,13 @@ const addNodesLoose = (json: any, state: GraphState): GraphState => {
_.set(state.looseNodes, [resource], indices); _.set(state.looseNodes, [resource], indices);
} }
return state; return state;
} };
const keys = (json, state: GraphState): GraphState => { const keys = (json, state: GraphState): GraphState => {
const data = _.get(json, 'keys', false); const data = _.get(json, 'keys', false);
if (data) { if (data) {
state.graphKeys = new Set(data.map((res) => { state.graphKeys = new Set(data.map((res) => {
let resource = res.ship + '/' + res.name; const resource = res.ship + '/' + res.name;
return resource; return resource;
})); }));
} }
@ -52,36 +52,32 @@ const keys = (json, state: GraphState): GraphState => {
const processNode = (node) => { const processNode = (node) => {
// is empty // is empty
if (!node.children) { if (!node.children) {
return produce(node, draft => { return produce(node, (draft) => {
draft.children = new BigIntOrderedMap(); draft.children = new BigIntOrderedMap();
}); });
} }
// is graph // is graph
return produce(node, draft => { return produce(node, (draft) => {
draft.children = new BigIntOrderedMap() draft.children = new BigIntOrderedMap()
.gas(_.map(draft.children, (item, idx) => .gas(_.map(draft.children, (item, idx) =>
[bigInt(idx), processNode(item)] as [BigInteger, any] [bigInt(idx), processNode(item)] as [BigInteger, any]
)); ));
}); });
}; };
const addGraph = (json, state: GraphState): GraphState => { const addGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'add-graph', false); const data = _.get(json, 'add-graph', false);
if (data) { if (data) {
if (!('graphs' in state)) { if (!('graphs' in state)) {
state.graphs = {}; state.graphs = {};
} }
let resource = data.resource.ship + '/' + data.resource.name; const resource = data.resource.ship + '/' + data.resource.name;
state.graphs[resource] = new BigIntOrderedMap(); state.graphs[resource] = new BigIntOrderedMap();
state.graphTimesentMap[resource] = {}; 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])]; return [bigInt(idx), processNode(data.graph[idx])];
})); }));
@ -93,11 +89,10 @@ const addGraph = (json, state: GraphState): GraphState => {
const removeGraph = (json, state: GraphState): GraphState => { const removeGraph = (json, state: GraphState): GraphState => {
const data = _.get(json, 'remove-graph', false); const data = _.get(json, 'remove-graph', false);
if (data) { if (data) {
if (!('graphs' in state)) { if (!('graphs' in state)) {
state.graphs = {}; state.graphs = {};
} }
let resource = data.ship + '/' + data.name; const resource = data.ship + '/' + data.name;
state.graphKeys.delete(resource); state.graphKeys.delete(resource);
delete state.graphs[resource]; delete state.graphs[resource];
} }
@ -108,7 +103,7 @@ const mapifyChildren = (children) => {
return new BigIntOrderedMap().gas( return new BigIntOrderedMap().gas(
_.map(children, (node, idx) => { _.map(children, (node, idx) => {
idx = idx && idx.startsWith('/') ? idx.slice(1) : 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]; return [bigInt(idx), nd];
})); }));
}; };
@ -121,12 +116,12 @@ const addNodes = (json, state) => {
} }
// set parent of graph // set parent of graph
let parNode = graph.get(index[0]); const parNode = graph.get(index[0]);
if (!parNode) { if (!parNode) {
console.error('parent node does not exist, cannot add child'); console.error('parent node does not exist, cannot add child');
return graph; 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); draft.children = _addNode(draft.children, index.slice(1), node);
})); }));
}; };
@ -137,7 +132,7 @@ const addNodes = (json, state) => {
} else { } else {
const child = graph.get(index[0]); const child = graph.get(index[0]);
if (child) { 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)); draft.children = _remove(draft.children, index.slice(1));
})); }));
} }
@ -148,10 +143,12 @@ const addNodes = (json, state) => {
const _killByFuzzyTimestamp = (graph, resource, timestamp) => { const _killByFuzzyTimestamp = (graph, resource, timestamp) => {
if (state.graphTimesentMap[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; } if (index.split('/').length === 0) {
let indexArr = index.split('/').slice(1).map((ind) => { return graph;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
@ -174,10 +171,12 @@ const addNodes = (json, state) => {
const data = _.get(json, 'add-nodes', false); const data = _.get(json, 'add-nodes', false);
if (data) { if (data) {
if (!('graphs' in state)) { return state; } if (!('graphs' in state)) {
return state;
}
let resource = data.resource.ship + '/' + data.resource.name; const resource = data.resource.ship + '/' + data.resource.name;
if (!(resource in state.graphs)) { if (!(resource in state.graphs)) {
state.graphs[resource] = new BigIntOrderedMap(); state.graphs[resource] = new BigIntOrderedMap();
} }
@ -186,48 +185,47 @@ const addNodes = (json, state) => {
} }
state.graphKeys.add(resource); state.graphKeys.add(resource);
let indices = Array.from(Object.keys(data.nodes)); const indices = Array.from(Object.keys(data.nodes));
indices.sort((a, b) => { indices.sort((a, b) => {
let aArr = a.split('/'); const aArr = a.split('/');
let bArr = b.split('/'); const bArr = b.split('/');
return aArr.length - bArr.length; return aArr.length - bArr.length;
}); });
indices.forEach((index) => { indices.forEach((index) => {
let node = data.nodes[index]; const node = data.nodes[index];
const old = state.graphs[resource].size; const old = state.graphs[resource].size;
state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource); state.graphs[resource] = _removePending(state.graphs[resource], node.post, resource);
const newSize = state.graphs[resource].size; const newSize = state.graphs[resource].size;
if (index.split('/').length === 0) {
if (index.split('/').length === 0) { return; } return;
let indexArr = index.split('/').slice(1).map((ind) => { }
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
if (indexArr.length === 0) { return state; } if (indexArr.length === 0) {
return state;
}
if (node.post.pending) { if (node.post.pending) {
state.graphTimesentMap[resource][node.post['time-sent']] = index; state.graphTimesentMap[resource][node.post['time-sent']] = index;
} }
state.graphs[resource] = _addNode( state.graphs[resource] = _addNode(
state.graphs[resource], state.graphs[resource],
indexArr, indexArr,
produce(node, draft => { produce(node, (draft) => {
draft.children = mapifyChildren(draft?.children || {}); draft.children = mapifyChildren(draft?.children || {});
}) })
); );
if(newSize !== old) { if(newSize !== old) {
console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`); console.log(`${resource}, (${old}, ${newSize}, ${state.graphs[resource].size})`);
} }
}); });
} }
return state; return state;
}; };
@ -239,7 +237,7 @@ const removePosts = (json, state: GraphState): GraphState => {
if (child) { if (child) {
return graph.set(index[0], { return graph.set(index[0], {
post: child.post.hash || '', post: child.post.hash || '',
children: child.children children: child.children
}); });
} }
} else { } else {
@ -249,8 +247,8 @@ const removePosts = (json, state: GraphState): GraphState => {
} else { } else {
const child = graph.get(index[0]); const child = graph.get(index[0]);
if (child) { if (child) {
return graph.set(index[0], produce(draft => { return graph.set(index[0], produce((draft) => {
draft.children = _remove(draft.children, index.slice(1)) draft.children = _remove(draft.children, index.slice(1));
})); }));
} }
return graph; return graph;
@ -262,11 +260,15 @@ const removePosts = (json, state: GraphState): GraphState => {
if (data) { if (data) {
const { ship, name } = data.resource; const { ship, name } = data.resource;
const res = `${ship}/${name}`; const res = `${ship}/${name}`;
if (!(res in state.graphs)) { return state; } if (!(res in state.graphs)) {
return state;
}
data.indices.forEach((index) => { data.indices.forEach((index) => {
if (index.split('/').length === 0) { return; } if (index.split('/').length === 0) {
let indexArr = index.split('/').slice(1).map((ind) => { return;
}
const indexArr = index.split('/').slice(1).map((ind) => {
return bigInt(ind); return bigInt(ind);
}); });
state.graphs[res] = _remove(state.graphs[res], indexArr); 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 _ from 'lodash';
import { Cage } from '~/types/cage'; 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 { resourceAsPath } from '../lib/util';
import useGroupState, { GroupState } from '../state/group';
import { reduceState } from '../state/base'; import { reduceState } from '../state/base';
import useGroupState, { GroupState } from '../state/group';
function decodeGroup(group: Enc<Group>): Group { function decodeGroup(group: Enc<Group>): Group {
const members = new Set(group.members); const members = new Set(group.members);
@ -69,11 +66,10 @@ export default class GroupReducer {
addGroup, addGroup,
removeGroup, removeGroup,
changePolicy, changePolicy,
expose, expose
]); ]);
} }
} }
} }
const initial = (json: GroupUpdate, state: GroupState): GroupState => { const initial = (json: GroupUpdate, state: GroupState): GroupState => {
const data = json['initial']; const data = json['initial'];
@ -81,7 +77,7 @@ const initial = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups = _.mapValues(data, decodeGroup); state.groups = _.mapValues(data, decodeGroup);
} }
return state; return state;
} };
const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => { const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('initialGroup' in json) { if ('initialGroup' in json) {
@ -90,7 +86,7 @@ const initialGroup = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[path] = decodeGroup(group); state.groups[path] = decodeGroup(group);
} }
return state; return state;
} };
const addGroup = (json: GroupUpdate, state: GroupState): GroupState => { const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addGroup' in json) { if ('addGroup' in json) {
@ -104,7 +100,7 @@ const addGroup = (json: GroupUpdate, state: GroupState): GroupState => {
}; };
} }
return state; return state;
} };
const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => { const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
if('removeGroup' in json) { if('removeGroup' in json) {
@ -113,7 +109,7 @@ const removeGroup = (json: GroupUpdate, state: GroupState): GroupState => {
delete state.groups[resourcePath]; delete state.groups[resourcePath];
} }
return state; return state;
} };
const addMembers = (json: GroupUpdate, state: GroupState): GroupState => { const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addMembers' in json) { if ('addMembers' in json) {
@ -130,7 +126,7 @@ const addMembers = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => { const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
if ('removeMembers' in json) { if ('removeMembers' in json) {
@ -141,7 +137,7 @@ const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const addTag = (json: GroupUpdate, state: GroupState): GroupState => { const addTag = (json: GroupUpdate, state: GroupState): GroupState => {
if ('addTag' in json) { if ('addTag' in json) {
@ -177,7 +173,7 @@ const removeTag = (json: GroupUpdate, state: GroupState): GroupState => {
_.set(tags, tagAccessors, tagged); _.set(tags, tagAccessors, tagged);
} }
return state; return state;
} };
const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => { const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
if ('changePolicy' in json && state) { if ('changePolicy' in json && state) {
@ -195,7 +191,7 @@ const changePolicy = (json: GroupUpdate, state: GroupState): GroupState => {
} }
} }
return state; return state;
} };
const expose = (json: GroupUpdate, state: GroupState): GroupState => { const expose = (json: GroupUpdate, state: GroupState): GroupState => {
if( 'expose' in json && state) { if( 'expose' in json && state) {
@ -204,7 +200,7 @@ const expose = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[resourcePath].hidden = false; state.groups[resourcePath].hidden = false;
} }
return state; return state;
} };
const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => { const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
if ('addInvites' in diff) { if ('addInvites' in diff) {
@ -220,7 +216,7 @@ const inviteChangePolicy = (diff: InvitePolicyDiff, policy: InvitePolicy) => {
} else { } else {
console.log('bad policy change'); console.log('bad policy change');
} }
} };
const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => { const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
if ('allowRanks' in diff) { if ('allowRanks' in diff) {
@ -246,4 +242,4 @@ const openChangePolicy = (diff: OpenPolicyDiff, policy: OpenPolicy) => {
} else { } else {
console.log('bad policy change'); console.log('bad policy change');
} }
} };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,10 +3,10 @@ import { BaseState, createState } from './base';
export interface InviteState extends BaseState<InviteState> { export interface InviteState extends BaseState<InviteState> {
invites: Invites; invites: Invites;
}; }
const useInviteState = createState<InviteState>('Invite', { 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 { Tile, WeatherState } from '~/types/launch-update';
import { BaseState, createState } from './base';
import { BaseState, createState } from "./base";
export interface LaunchState extends BaseState<LaunchState> { export interface LaunchState extends BaseState<LaunchState> {
firstTime: boolean; firstTime: boolean;
@ -12,7 +10,7 @@ export interface LaunchState extends BaseState<LaunchState> {
weather: WeatherState | null | Record<string, never> | boolean, weather: WeatherState | null | Record<string, never> | boolean,
userLocation: string | null; userLocation: string | null;
baseHash: string | null; baseHash: string | null;
}; }
const useLaunchState = createState<LaunchState>('Launch', { const useLaunchState = createState<LaunchState>('Launch', {
firstTime: true, firstTime: true,
@ -23,5 +21,4 @@ const useLaunchState = createState<LaunchState>('Launch', {
baseHash: null 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 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 { export interface LocalState {
theme: 'light' | 'dark' | 'auto'; theme: 'light' | 'dark' | 'auto';
@ -27,7 +26,7 @@ export interface LocalState {
suspendedFocus?: HTMLElement; suspendedFocus?: HTMLElement;
toggleOmnibox: () => void; toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void set: (fn: (state: LocalState) => void) => void
}; }
type LocalStateZus = LocalState & State; type LocalStateZus = LocalState & State;

View File

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

View File

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

View File

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

View File

@ -1,22 +1,20 @@
import _ from 'lodash'; 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 { 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> { export default class GlobalStore extends BaseStore<StoreState> {
inviteReducer = new InviteReducer(); inviteReducer = new InviteReducer();
@ -42,7 +40,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
initialState(): StoreState { initialState(): StoreState {
return { 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 { Path } from '@urbit/api';
import BaseApi from '../api/base';
import BaseStore from '../store/base';
export default class BaseSubscription<S extends object> { export default class BaseSubscription<S extends object> {
private errorCount = 0; private errorCount = 0;

View File

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

View File

@ -1,8 +1,8 @@
if ("serviceWorker" in navigator && process.env.NODE_ENV !== 'development') { if ('serviceWorker' in navigator && process.env.NODE_ENV !== 'development') {
window.addEventListener("load", () => { window.addEventListener('load', () => {
navigator.serviceWorker.register("/~landscape/js/bundle/serviceworker.js", { navigator.serviceWorker.register('/~landscape/js/bundle/serviceworker.js', {
scope: "/", scope: '/'
}).then(reg => { }).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 // Used for filtering matches based on status code, header, or both
import { CacheableResponsePlugin } from 'workbox-cacheable-response'; import { CacheableResponsePlugin } from 'workbox-cacheable-response';
// Used to limit entries in cache, remove entries after a certain period of time // Used to limit entries in cache, remove entries after a certain period of time
import { ExpirationPlugin } from 'workbox-expiration'; 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 // generate a different sw for every build, to bust cache properly
const hash = process.env.LANDSCAPE_SHORTHASH; const hash = process.env.LANDSCAPE_SHORTHASH;
self.addEventListener("install", ev => { self.addEventListener('install', (ev) => {
self.skipWaiting(); self.skipWaiting();
}); });
self.addEventListener('activate', ev => { self.addEventListener('activate', (ev) => {
ev.waitUntil(clients.claim()); ev.waitUntil(clients.claim());
}); });
@ -33,10 +30,10 @@ registerRoute(
plugins: [ plugins: [
// Ensure that only requests that result in a 200 status are cached // Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({ new CacheableResponsePlugin({
statuses: [200], statuses: [200]
}), })
], ]
}), })
); );
// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy // Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
@ -53,12 +50,12 @@ registerRoute(
plugins: [ plugins: [
// Ensure that only requests that result in a 200 status are cached // Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({ new CacheableResponsePlugin({
statuses: [200], statuses: [200]
}), })
], ]
}), })
); );
// Cache images with a Cache First strategy // Cache images with a Cache First strategy
registerRoute( registerRoute(
// Check to see if the request's destination is style for an image // Check to see if the request's destination is style for an image
@ -70,13 +67,13 @@ registerRoute(
plugins: [ plugins: [
// Ensure that only requests that result in a 200 status are cached // Ensure that only requests that result in a 200 status are cached
new CacheableResponsePlugin({ new CacheableResponsePlugin({
statuses: [200], statuses: [200]
}), }),
// Don't cache more than 50 items, and expire them after 30 days // Don't cache more than 50 items, and expire them after 30 days
new ExpirationPlugin({ new ExpirationPlugin({
maxEntries: 50, 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 { ContactUpdate, GroupUpdate, InviteUpdate, MetadataUpdate } from '@urbit/api';
import { SettingsUpdate } from '@urbit/api/settings'; import { SettingsUpdate } from '@urbit/api/settings';
import { ConnectionStatus } from './connection';
import { LaunchUpdate, WeatherState } from './launch-update';
import { LocalUpdate } from './local-update';
interface MarksToTypes { interface MarksToTypes {
readonly json: any; readonly json: any;

View File

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

View File

@ -1,10 +1,11 @@
export * from './cage'; export * from './cage';
export * from './connection'; export * from './connection';
export * from './gcp-state';
export * from './global'; export * from './global';
export * from './launch-update'; export * from './launch-update';
export * from './local-update'; export * from './local-update';
export * from './storage-state';
export * from './gcp-state';
export * from './s3-update'; export * from './s3-update';
export * from './workspace'; export * from './storage-state';
export * from './util'; 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 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]; export type LeapCategories = typeof leapCategories[number];

View File

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

View File

@ -1,42 +1,34 @@
import { hot } from 'react-hot-loader/root'; import dark from '@tlon/indigo-dark';
import 'react-hot-loader'; import light from '@tlon/indigo-light';
import * as React from 'react';
import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js'; import { sigil as sigiljs, stringRenderer } from '@tlon/sigil-js';
import Helmet from 'react-helmet';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import 'mousetrap-global-bind'; import 'mousetrap-global-bind';
import * as React from 'react';
import './css/indigo-static.css'; import Helmet from 'react-helmet';
import './css/fonts.css'; import 'react-hot-loader';
import './apps/chat/css/custom.css'; import { hot } from 'react-hot-loader/root';
import './landscape/css/custom.css'; import { BrowserRouter as Router, withRouter } from 'react-router-dom';
import styled, { ThemeProvider } from 'styled-components';
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 GlobalApi from '~/logic/api/global'; 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 { foregroundFromBackground } from '~/logic/lib/sigil';
import { uxToHex } from '~/logic/lib/util';
import withState from '~/logic/lib/withState'; import withState from '~/logic/lib/withState';
import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contact'; import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group'; import useGroupState from '~/logic/state/group';
import useLocalState from '~/logic/state/local';
import useSettingsState from '~/logic/state/settings'; 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` const Root = withState(styled.div`
font-family: ${p => p.theme.fonts.sans}; font-family: ${p => p.theme.fonts.sans};
@ -126,13 +118,13 @@ class App extends React.Component {
} }
updateTheme(e) { updateTheme(e) {
this.props.set(state => { this.props.set((state) => {
state.dark = e.matches; state.dark = e.matches;
}); });
} }
updateMobile(e) { updateMobile(e) {
this.props.set(state => { this.props.set((state) => {
state.mobile = e.matches; state.mobile = e.matches;
}); });
} }
@ -155,8 +147,8 @@ class App extends React.Component {
getTheme() { getTheme() {
const { props } = this; const { props } = this;
return ((props.dark && props?.display?.theme == "auto") || return ((props.dark && props?.display?.theme == 'auto') ||
props?.display?.theme == "dark" props?.display?.theme == 'dark'
) ? dark : light; ) ? 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 { 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 { 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'; import { ChatPane } from './components/ChatPane';
const getCurrGraphSize = (ship: string, name: string) => { const getCurrGraphSize = (ship: string, name: string) => {
@ -36,20 +23,19 @@ const getCurrGraphSize = (ship: string, name: string) => {
return graph?.size ?? 0; return graph?.size ?? 0;
}; };
type ChatResourceProps = StoreState & { type ChatResourceProps = StoreState & {
association: Association; association: Association;
api: GlobalApi; api: GlobalApi;
baseUrl: string; baseUrl: string;
}; };
function ChatResource(props: ChatResourceProps) { const ChatResource = (props: ChatResourceProps): ReactElement => {
const { association, api } = props; const { association, api } = props;
const { resource } = association; const { resource } = association;
const [toShare, setToShare] = useState<string[] | string | undefined>(); const [toShare, setToShare] = useState<string[] | string | undefined>();
const group = useGroupForAssoc(association)!; const group = useGroupForAssoc(association)!;
const graph = useGraphForAssoc(association); const graph = useGraphForAssoc(association);
const unreads = useHarkState((state) => state.unreads); const unreads = useHarkState(state => state.unreads);
const unreadCount = const unreadCount =
(unreads.graph?.[resource]?.['/']?.unreads as number) || 0; (unreads.graph?.[resource]?.['/']?.unreads as number) || 0;
const canWrite = group ? isWriter(group, resource) : false; const canWrite = group ? isWriter(group, resource) : false;
@ -59,12 +45,12 @@ function ChatResource(props: ChatResourceProps) {
const { ship, name } = resourceFromPath(resource); const { ship, name } = resourceFromPath(resource);
props.api.graph.getNewest(ship, name, count); props.api.graph.getNewest(ship, name, count);
setToShare(undefined); setToShare(undefined);
(async function() { (async function () {
if(group.hidden) { if (group.hidden) {
const members = await props.api.contacts.disallowedShipsForOurContact( const members = await props.api.contacts.disallowedShipsForOurContact(
Array.from(group.members) Array.from(group.members)
); );
if(members.length > 0) { if (members.length > 0) {
setToShare(members); setToShare(members);
} }
} else { } else {
@ -75,7 +61,7 @@ function ChatResource(props: ChatResourceProps) {
groupHost, groupHost,
true true
); );
if(!shared) { if (!shared) {
setToShare(association.group); setToShare(association.group);
} }
} }
@ -108,7 +94,7 @@ function ChatResource(props: ChatResourceProps) {
const expectedSize = graphSize + pageSize; const expectedSize = graphSize + pageSize;
if (newer) { if (newer) {
const index = graph.peekLargest()?.[0]; const index = graph.peekLargest()?.[0];
if(!index) { if (!index) {
return true; return true;
} }
await api.graph.getYoungerSiblings( await api.graph.getYoungerSiblings(
@ -120,7 +106,7 @@ function ChatResource(props: ChatResourceProps) {
return expectedSize !== getCurrGraphSize(ship.slice(1), name); return expectedSize !== getCurrGraphSize(ship.slice(1), name);
} else { } else {
const index = graph.peekSmallest()?.[0]; const index = graph.peekSmallest()?.[0];
if(!index) { if (!index) {
return true; return true;
} }
await api.graph.getOlderSiblings(ship, name, pageSize, `/${index.toString()}`); await api.graph.getOlderSiblings(ship, name, pageSize, `/${index.toString()}`);
@ -131,7 +117,7 @@ function ChatResource(props: ChatResourceProps) {
const onSubmit = useCallback((contents: Content[]) => { const onSubmit = useCallback((contents: Content[]) => {
const { ship, name } = resourceFromPath(resource); const { ship, name } = resourceFromPath(resource);
api.graph.addPost(ship, name, createPost(window.ship, contents)) api.graph.addPost(ship, name, createPost(window.ship, contents));
}, [resource]); }, [resource]);
const dismissUnread = useCallback(() => { const dismissUnread = useCallback(() => {
@ -164,6 +150,6 @@ function ChatResource(props: ChatResourceProps) {
promptShare={toShare} promptShare={toShare}
/> />
); );
} };
export { ChatResource }; export { ChatResource };

View File

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

View File

@ -1,33 +1,28 @@
/* eslint-disable max-lines-per-function */ /* 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 bigInt from 'big-integer';
import React, {
useState,
useEffect,
useMemo
} from 'react';
import moment from 'moment'; import moment from 'moment';
import React, {
useEffect,
useMemo, useState
} from 'react';
import VisibilitySensor from 'react-visibility-sensor'; 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 { Sigil } from '~/logic/lib/sigil';
import { useCopy } from '~/logic/lib/useCopy';
import { import {
uxToHex,
cite, cite,
useShowNickname,
useHovering, daToUnix, useHovering, useShowNickname, uxToHex
daToUnix
} from '~/logic/lib/util'; } from '~/logic/lib/util';
import { Post } from '@urbit/api'; import { useContact } from '~/logic/state/contact';
import { Dropdown } from '~/views/components/Dropdown';
import useLocalState from '~/logic/state/local'; import useLocalState from '~/logic/state/local';
import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import useContactState, {useContact} from '~/logic/state/contact'; import { Dropdown } from '~/views/components/Dropdown';
import { useIdlingState } from '~/logic/lib/idling';
import ProfileOverlay from '~/views/components/ProfileOverlay'; import ProfileOverlay from '~/views/components/ProfileOverlay';
import {useCopy} from '~/logic/lib/useCopy'; import { GraphContentWide } from '~/views/landscape/components/Graph/GraphContentWide';
import {GraphContentWide} from '~/views/landscape/components/Graph/GraphContentWide';
import {Contact} from '@urbit/api';
import GlobalApi from '~/logic/api/global';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D'; export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
@ -36,7 +31,6 @@ interface DayBreakProps {
shimTop?: boolean; shimTop?: boolean;
} }
export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => ( export const DayBreak = ({ when, shimTop = false }: DayBreakProps) => (
<Row <Row
px={2} px={2}
@ -168,12 +162,12 @@ const MessageActions = ({ api, onReply, association, msg, isAdmin, permalink })
{copyDisplay} {copyDisplay}
</MessageActionItem> </MessageActionItem>
{false && (isAdmin() || isOwn()) ? ( {false && (isAdmin() || isOwn()) ? (
<MessageActionItem onClick={(e) => console.log(e)} color='red'> <MessageActionItem onClick={e => console.log(e)} color='red'>
Delete Message Delete Message
</MessageActionItem> </MessageActionItem>
) : null} ) : null}
{false && ( {false && (
<MessageActionItem onClick={(e) => console.log(e)}> <MessageActionItem onClick={e => console.log(e)}>
View Signature View Signature
</MessageActionItem> </MessageActionItem>
)} )}
@ -250,7 +244,7 @@ function ChatMessage(props: ChatMessageProps) {
permalink permalink
} = props; } = props;
let onReply = props?.onReply ?? (() => {}); const onReply = props?.onReply ?? (() => {});
const transcluded = props?.transcluded ?? 0; const transcluded = props?.transcluded ?? 0;
const renderSigil = props.renderSigil ?? (Boolean(nextMsg && msg.author !== nextMsg.author) || const renderSigil = props.renderSigil ?? (Boolean(nextMsg && msg.author !== nextMsg.author) ||
!nextMsg || !nextMsg ||
@ -278,7 +272,7 @@ function ChatMessage(props: ChatMessageProps) {
nextDate && nextDate &&
new Date(date).getDate() !== new Date(date).getDate() !==
new Date(nextDate).getDate() new Date(nextDate).getDate()
, [nextDate, date]) , [nextDate, date]);
const containerClass = `${isPending ? 'o-40' : ''} ${className}`; const containerClass = `${isPending ? 'o-40' : ''} ${className}`;
@ -350,11 +344,11 @@ export const MessageAuthor = ({
timestamp, timestamp,
msg, msg,
api, api,
showOurContact, showOurContact
}) => { }) => {
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); const dark = theme === 'dark' || (theme === 'auto' && osDark);
let contact: Contact | null = useContact(`~${msg.author}`); let contact: Contact | null = useContact(`~${msg.author}`);
@ -473,7 +467,7 @@ export const MessageAuthor = ({
}; };
type MessageProps = { timestamp: string; timestampHover: boolean; } type MessageProps = { timestamp: string; timestampHover: boolean; }
& Pick<ChatMessageProps, "msg" | "api" | "transcluded" | "showOurContact"> & Pick<ChatMessageProps, 'msg' | 'api' | 'transcluded' | 'showOurContact'>
export const Message = React.memo(({ export const Message = React.memo(({
timestamp, timestamp,

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 { Col } from '@tlon/indigo-react';
import _ from 'lodash'; import { Content, Graph, Post } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer'; import bigInt, { BigInteger } from 'big-integer';
import _ from 'lodash';
import { Association } from '@urbit/api/metadata'; import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
import { StoreState } from '~/logic/store/type';
import { useFileDrag } from '~/logic/lib/useDrag';
import ChatWindow from './ChatWindow';
import ChatInput from './ChatInput';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import { ShareProfile } from '~/views/apps/chat/components/ShareProfile'; import { useFileDrag } from '~/logic/lib/useDrag';
import SubmitDragger from '~/views/components/SubmitDragger';
import { useLocalStorageState } from '~/logic/lib/useLocalStorageState'; import { useLocalStorageState } from '~/logic/lib/useLocalStorageState';
import { Loading } from '~/views/components/Loading'; import { useOurContact } from '~/logic/state/contact';
import { isWriter, resourceFromPath } from '~/logic/lib/group';
import useContactState, { useOurContact } from '~/logic/state/contact';
import useGraphState from '~/logic/state/graph'; import useGraphState from '~/logic/state/graph';
import useGroupState from '~/logic/state/group'; import ShareProfile from '~/views/apps/chat/components/ShareProfile';
import useHarkState from '~/logic/state/hark'; import { Loading } from '~/views/components/Loading';
import { Post, Graph, Content } from '@urbit/api'; import SubmitDragger from '~/views/components/SubmitDragger';
import { getPermalinkForGraph } from '~/logic/lib/permalinks'; import ChatInput from './ChatInput';
import ChatWindow from './ChatWindow';
interface ChatPaneProps { interface ChatPaneProps {
/** /**
@ -73,7 +64,7 @@ interface ChatPaneProps {
promptShare?: string[] | string; promptShare?: string[] | string;
} }
export function ChatPane(props: ChatPaneProps) { export function ChatPane(props: ChatPaneProps): ReactElement {
const { const {
api, api,
graph, graph,
@ -87,7 +78,7 @@ export function ChatPane(props: ChatPaneProps) {
promptShare = [], promptShare = [],
fetchMessages fetchMessages
} = props; } = props;
const graphTimesentMap = useGraphState((state) => state.graphTimesentMap); const graphTimesentMap = useGraphState(state => state.graphTimesentMap);
const ourContact = useOurContact(); const ourContact = useOurContact();
const chatInput = useRef<ChatInput>(); const chatInput = useRef<ChatInput>();
@ -109,7 +100,7 @@ export function ChatPane(props: ChatPaneProps) {
); );
const appendUnsent = useCallback( const appendUnsent = useCallback(
(u: string) => setUnsent((s) => ({ ...s, [id]: u })), (u: string) => setUnsent(s => ({ ...s, [id]: u })),
[id] [id]
); );
@ -133,7 +124,7 @@ export function ChatPane(props: ChatPaneProps) {
const onReply = useCallback( const onReply = useCallback(
(msg: Post) => { (msg: Post) => {
const message = props.onReply(msg); const message = props.onReply(msg);
setUnsent((s) => ({ ...s, [id]: message })); setUnsent(s => ({ ...s, [id]: message }));
}, },
[id, props.onReply] [id, props.onReply]
); );

View File

@ -1,43 +1,23 @@
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 } from '@tlon/indigo-react';
import { import {
Patp,
Contacts,
Association,
Associations,
Group,
Groups,
Graph, Graph,
Post,
GraphNode GraphNode, Post
} from '@urbit/api'; } from '@urbit/api';
import bigInt, { BigInteger } from 'big-integer';
import React, { Component } from 'react';
import GlobalApi from '~/logic/api/global'; import GlobalApi from '~/logic/api/global';
import VirtualScroller from '~/views/components/VirtualScroller'; import VirtualScroller from '~/views/components/VirtualScroller';
import ChatMessage, { MessagePlaceholder } from './ChatMessage'; import ChatMessage, { MessagePlaceholder } from './ChatMessage';
import { UnreadNotice } from './unread-notice'; import UnreadNotice from './UnreadNotice';
import withState from '~/logic/lib/withState';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
import useGraphState from '~/logic/state/graph';
const INITIAL_LOAD = 20;
const DEFAULT_BACKLOG_SIZE = 100;
const IDLE_THRESHOLD = 64; const IDLE_THRESHOLD = 64;
const MAX_BACKLOG_SIZE = 1000;
type ChatWindowProps = { type ChatWindowProps = {
unreadCount: number; unreadCount: number;
graph: Graph; graph: Graph;
graphSize: number; graphSize: number;
station: any; station: unknown;
fetchMessages: (newer: boolean) => Promise<boolean>; fetchMessages: (newer: boolean) => Promise<boolean>;
api: GlobalApi; api: GlobalApi;
scrollTo?: BigInteger; scrollTo?: BigInteger;
@ -58,7 +38,6 @@ interface ChatWindowState {
const virtScrollerStyle = { height: '100%' }; const virtScrollerStyle = { height: '100%' };
class ChatWindow extends Component< class ChatWindow extends Component<
ChatWindowProps, ChatWindowProps,
ChatWindowState ChatWindowState
@ -97,7 +76,6 @@ class ChatWindow extends Component<
this.virtualList!.scrollToIndex(this.props.scrollTo); this.virtualList!.scrollToIndex(this.props.scrollTo);
} }
}); });
}, this.INITIALIZATION_MAX_TIME); }, this.INITIALIZATION_MAX_TIME);
} }
@ -121,26 +99,26 @@ class ChatWindow extends Component<
}); });
} }
dismissedInitialUnread() { dismissedInitialUnread(): void {
const { unreadCount, graph } = this.props; const { unreadCount, graph } = this.props;
return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size : return this.state.unreadIndex.eq(bigInt.zero) ? unreadCount > graph.size :
this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero); this.state.unreadIndex.neq(graph.keys()?.[unreadCount]?.[0] ?? bigInt.zero);
} }
handleWindowBlur() { handleWindowBlur(): void {
this.setState({ idle: true }); this.setState({ idle: true });
} }
handleWindowFocus() { handleWindowFocus(): void {
this.setState({ idle: false }); this.setState({ idle: false });
if (this.virtualList?.window?.scrollTop === 0) { if (this.virtualList?.window?.scrollTop === 0) {
this.props.dismissUnread(); this.props.dismissUnread();
} }
} }
componentDidUpdate(prevProps: ChatWindowProps, prevState) { componentDidUpdate(prevProps: ChatWindowProps): void {
const { graph, unreadCount, graphSize, station } = this.props; const { unreadCount, graphSize, station } = this.props;
if(unreadCount === 0 && prevProps.unreadCount !== unreadCount) { if(unreadCount === 0 && prevProps.unreadCount !== unreadCount) {
this.unreadSet = true; this.unreadSet = true;
} }
@ -155,7 +133,6 @@ class ChatWindow extends Component<
this.virtualList!.startOffset() < 5) { this.virtualList!.startOffset() < 5) {
this.props.dismissUnread(); this.props.dismissUnread();
} }
} }
if (unreadCount > prevProps.unreadCount) { if (unreadCount > prevProps.unreadCount) {
@ -168,7 +145,7 @@ class ChatWindow extends Component<
} }
} }
stayLockedIfActive() { stayLockedIfActive(): void {
if (this.virtualList && !this.state.idle) { if (this.virtualList && !this.state.idle) {
this.virtualList.resetScroll(); this.virtualList.resetScroll();
this.props.dismissUnread(); this.props.dismissUnread();
@ -181,7 +158,7 @@ class ChatWindow extends Component<
} }
} }
scrollToUnread() { scrollToUnread(): void {
const { unreadIndex } = this.state; const { unreadIndex } = this.state;
if (unreadIndex.eq(bigInt.zero)) { if (unreadIndex.eq(bigInt.zero)) {
return; return;
@ -190,13 +167,12 @@ class ChatWindow extends Component<
this.virtualList?.scrollToIndex(this.state.unreadIndex); this.virtualList?.scrollToIndex(this.state.unreadIndex);
} }
onScroll = ({ scrollTop, scrollHeight, windowHeight }) => { onScroll = ({ scrollTop }) => {
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) { if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
this.setState({ idle: true }); this.setState({ idle: true });
} }
} }
renderer = React.forwardRef(({ index, scrollWindow }, ref) => { renderer = React.forwardRef(({ index, scrollWindow }, ref) => {
const { const {
api, api,
@ -205,7 +181,7 @@ class ChatWindow extends Component<
onReply, onReply,
getPermalink, getPermalink,
dismissUnread, dismissUnread,
isAdmin, isAdmin
} = this.props; } = this.props;
const permalink = getPermalink(index); const permalink = getPermalink(index);
const messageProps = { const messageProps = {
@ -218,7 +194,8 @@ class ChatWindow extends Component<
}; };
const msg = graph.get(index)?.post; const msg = graph.get(index)?.post;
if (!msg) return null; if (!msg)
return null;
if (!this.state.initialized) { if (!this.state.initialized) {
return ( return (
<MessagePlaceholder <MessagePlaceholder
@ -234,7 +211,7 @@ class ChatWindow extends Component<
); );
const highlighted = index.eq(this.props.scrollTo ?? bigInt.zero); const highlighted = index.eq(this.props.scrollTo ?? bigInt.zero);
const keys = graph.keys(); 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 prevIdx = keys[graphIdx - 1];
const nextIdx = keys[graphIdx + 1]; const nextIdx = keys[graphIdx + 1];
const isLastRead: boolean = this.state.unreadIndex.eq(index); const isLastRead: boolean = this.state.unreadIndex.eq(index);
@ -262,17 +239,15 @@ class ChatWindow extends Component<
render() { render() {
const { const {
unreadCount, unreadCount,
api,
graph, graph,
showOurContact, pendingSize = 0
pendingSize = 0,
} = this.props; } = this.props;
const unreadMsg = graph.get(this.state.unreadIndex); const unreadMsg = graph.get(this.state.unreadIndex);
return ( return (
<Col height='100%' overflow='hidden' position='relative'> <Col height='100%' overflow='hidden' position='relative'>
{ this.dismissedInitialUnread() && { this.dismissedInitialUnread() &&
(<UnreadNotice (<UnreadNotice
unreadCount={unreadCount} unreadCount={unreadCount}
unreadMsg={ unreadMsg={
@ -284,7 +259,7 @@ class ChatWindow extends Component<
} }
dismissUnread={this.props.dismissUnread} dismissUnread={this.props.dismissUnread}
onClick={this.scrollToUnread} onClick={this.scrollToUnread}
/>)} />)}
<VirtualScroller<GraphNode> <VirtualScroller<GraphNode>
ref={(list) => { ref={(list) => {
this.virtualList = list; this.virtualList = list;
@ -306,5 +281,4 @@ class ChatWindow extends Component<
} }
} }
export default ChatWindow;
export default ChatWindow

View File

@ -1,19 +1,20 @@
import React, { import { BaseImage, Box, Row, Text } from '@tlon/indigo-react';
useState, import { Contact } from '@urbit/api';
useEffect import React, { ReactElement } from 'react';
} from 'react'; import GlobalApi from '~/logic/api/global';
import _ from 'lodash';
import { Box, Row, Text, BaseImage } from '@tlon/indigo-react';
import { uxToHex } from '~/logic/lib/util';
import { Sigil } from '~/logic/lib/sigil'; 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 { const {
api, api,
showBanner,
setShowBanner,
group,
groupPath,
recipients recipients
} = props; } = props;
@ -24,18 +25,21 @@ export const ShareProfile = (props) => {
width='24px' width='24px'
height='24px' height='24px'
borderRadius={2} borderRadius={2}
style={{ objectFit: 'cover' }} /> style={{ objectFit: 'cover' }}
/>
) : ( ) : (
<Row <Row
p={1} p={1}
alignItems="center" alignItems="center"
borderRadius={2} borderRadius={2}
backgroundColor={!!props.our ? `#${uxToHex(props.our.color)}` : "#000000"}> backgroundColor={props.our ? `#${uxToHex(props.our.color)}` : '#000000'}
>
<Sigil <Sigil
ship={window.ship} ship={window.ship}
size={16} size={16}
color={!!props.our ? `#${uxToHex(props.our.color)}` : "#000000"} color={props.our ? `#${uxToHex(props.our.color)}` : '#000000'}
icon /> icon
/>
</Row> </Row>
); );
@ -48,8 +52,8 @@ export const ShareProfile = (props) => {
} }
} else if(recipients.length > 0) { } else if(recipients.length > 0) {
await api.contacts.allowShips(recipients); 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(); props.onShare();
}; };
@ -72,3 +76,5 @@ export const ShareProfile = (props) => {
</Row> </Row>
) : null; ) : 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 moment from 'moment';
import { Box, Text, Center, Icon } from '@tlon/indigo-react'; import React, { ReactElement } from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import Timestamp from '~/views/components/Timestamp'; import Timestamp from '~/views/components/Timestamp';
export const UnreadNotice = (props) => { const UnreadNotice = (props): ReactElement | null => {
const { unreadCount, unreadMsg, dismissUnread, onClick } = props; const { unreadCount, unreadMsg, dismissUnread, onClick } = props;
if (unreadCount === 0) { if (unreadCount === 0) {
@ -20,7 +18,7 @@ export const UnreadNotice = (props) => {
p='12px' p='12px'
width='100%' width='100%'
position='absolute' position='absolute'
zIndex='1' zIndex={1}
className='unread-notice' className='unread-notice'
> >
<Center> <Center>
@ -66,3 +64,5 @@ export const UnreadNotice = (props) => {
</Box> </Box>
); );
}; };
export default UnreadNotice;

View File

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

View File

@ -1,39 +1,37 @@
import React, { useState, useMemo, useEffect } from 'react'; /* eslint-disable max-lines-per-function */
import styled from 'styled-components'; import { Box, Button, Col, Icon, Row, Text } from '@tlon/indigo-react';
import f from 'lodash/fp'; import f from 'lodash/fp';
import _ from 'lodash'; import React, { ReactElement, useEffect, useMemo, useState } from 'react';
import { Col, Button, Box, Row, Icon, Text } from '@tlon/indigo-react';
import './css/custom.css';
import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import Groups from './components/Groups';
import ModalButton from './components/ModalButton';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { StarIcon } from '~/views/components/StarIcon';
import { writeText } from '~/logic/lib/util';
import { useModal } from "~/logic/lib/useModal";
import { NewGroup } from "~/views/landscape/components/NewGroup";
import { JoinGroup } from "~/views/landscape/components/JoinGroup";
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import useLocalState from "~/logic/state/local"; import styled from 'styled-components';
import useHarkState from '~/logic/state/hark'; import GlobalApi from '~/logic/api/global';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import { useQuery } from "~/logic/lib/useQuery";
import { import {
hasTutorialGroup, hasTutorialGroup,
TUTORIAL_GROUP,
TUTORIAL_HOST,
TUTORIAL_BOOK, TUTORIAL_BOOK,
TUTORIAL_CHAT, TUTORIAL_CHAT, TUTORIAL_GROUP,
TUTORIAL_HOST,
TUTORIAL_LINKS TUTORIAL_LINKS
} from '~/logic/lib/tutorialModal'; } from '~/logic/lib/tutorialModal';
import { useModal } from '~/logic/lib/useModal';
import { useQuery } from '~/logic/lib/useQuery';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import { writeText } from '~/logic/lib/util';
import useHarkState from '~/logic/state/hark';
import useLaunchState from '~/logic/state/launch'; import useLaunchState from '~/logic/state/launch';
import useSettingsState, { selectCalmState } from '~/logic/state/settings'; import useLocalState from '~/logic/state/local';
import useMetadataState from '~/logic/state/metadata'; import useMetadataState from '~/logic/state/metadata';
import {useHistory} from 'react-router-dom'; import useSettingsState, { selectCalmState } from '~/logic/state/settings';
import { StarIcon } from '~/views/components/StarIcon';
import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { JoinGroup } from '~/views/landscape/components/JoinGroup';
import { NewGroup } from '~/views/landscape/components/NewGroup';
import Groups from './components/Groups';
import ModalButton from './components/ModalButton';
import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import './css/custom.css';
const ScrollbarLessBox = styled(Box)` const ScrollbarLessBox = styled(Box)`
scrollbar-width: none !important; scrollbar-width: none !important;
@ -45,15 +43,19 @@ const ScrollbarLessBox = styled(Box)`
const tutSelector = f.pick(['tutorialProgress', 'nextTutStep', 'hideGroups']); const tutSelector = f.pick(['tutorialProgress', 'nextTutStep', 'hideGroups']);
export default function LaunchApp(props) { interface LaunchAppProps {
connection: string;
api: GlobalApi;
}
export const LaunchApp = (props: LaunchAppProps): ReactElement | null => {
const { connection } = props; const { connection } = props;
const baseHash = useLaunchState(state => state.baseHash); const baseHash = useLaunchState(state => state.baseHash);
const [hashText, setHashText] = useState(baseHash); const [hashText, setHashText] = useState(baseHash);
const [exitingTut, setExitingTut] = useState(false); const [exitingTut, setExitingTut] = useState(false);
const seen = useSettingsState(s => s?.tutorial?.seen) ?? true; const seen = useSettingsState(s => s?.tutorial?.seen) ?? true;
const associations = useMetadataState(s => s.associations); const associations = useMetadataState(s => s.associations);
const history = useHistory(); const hasLoaded = useMemo(() => Boolean(connection === 'connected'), [connection]);
const hasLoaded = useMemo(() => Boolean(connection === "connected"), [connection]);
const notificationsCount = useHarkState(state => state.notificationsCount); const notificationsCount = useHarkState(state => state.notificationsCount);
const calmState = useSettingsState(selectCalmState); const calmState = useSettingsState(selectCalmState);
const { hideUtilities } = calmState; const { hideUtilities } = calmState;
@ -64,7 +66,7 @@ export default function LaunchApp(props) {
const waiter = useWaitForProps({ ...props, associations }); const waiter = useWaitForProps({ ...props, associations });
const hashBox = ( const hashBox = (
<Box <Box
position={["relative", "absolute"]} position={['relative', 'absolute']}
left="0" left="0"
bottom="0" bottom="0"
backgroundColor="white" backgroundColor="white"
@ -90,20 +92,10 @@ export default function LaunchApp(props) {
const { query } = useQuery(); const { query } = useQuery();
useEffect(() => {
if(query.get('tutorial')) {
if(hasTutorialGroup({ associations })) {
nextTutStep();
} else {
showModal();
}
}
}, [query]);
const { modal, showModal } = useModal({ const { modal, showModal } = useModal({
position: 'relative', position: 'relative',
maxWidth: '350px', maxWidth: '350px',
modal: (dismiss) => { modal: function modal(dismiss) {
const onDismiss = (e) => { const onDismiss = (e) => {
e.stopPropagation(); e.stopPropagation();
props.api.settings.putEntry('tutorial', 'seen', true); props.api.settings.putEntry('tutorial', 'seen', true);
@ -111,31 +103,30 @@ export default function LaunchApp(props) {
}; };
const onContinue = async (e) => { const onContinue = async (e) => {
e.stopPropagation(); e.stopPropagation();
if(!hasTutorialGroup({ associations })) { if (!hasTutorialGroup({ associations })) {
await props.api.groups.join(TUTORIAL_HOST, TUTORIAL_GROUP); await props.api.groups.join(TUTORIAL_HOST, TUTORIAL_GROUP);
await props.api.settings.putEntry('tutorial', 'joined', Date.now()); await props.api.settings.putEntry('tutorial', 'joined', Date.now());
await waiter(hasTutorialGroup); await waiter(hasTutorialGroup);
await Promise.all( await Promise.all(
[TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => [TUTORIAL_BOOK, TUTORIAL_CHAT, TUTORIAL_LINKS].map(graph => props.api.graph.joinGraph(TUTORIAL_HOST, graph)));
props.api.graph.joinGraph(TUTORIAL_HOST, graph)));
await waiter(p => { await waiter((p) => {
return `/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}` in p.associations.graph && return `/ship/${TUTORIAL_HOST}/${TUTORIAL_CHAT}` in p.associations.graph &&
`/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}` in p.associations.graph && `/ship/${TUTORIAL_HOST}/${TUTORIAL_BOOK}` in p.associations.graph &&
`/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}` in p.associations.graph; `/ship/${TUTORIAL_HOST}/${TUTORIAL_LINKS}` in p.associations.graph;
}); });
} }
nextTutStep(); nextTutStep();
dismiss(); dismiss();
} };
return exitingTut ? ( return exitingTut ? (
<Col maxWidth="350px" p="3"> <Col maxWidth="350px" p="3">
<Icon icon="Info" fill="black"></Icon> <Icon icon="Info" fill="black"></Icon>
<Text my="3" lineHeight="tall"> <Text my="3" lineHeight="tall">
You can always restart the tutorial by typing "tutorial" in Leap You can always restart the tutorial by typing &ldquo;tutorial&rdquo; in Leap
</Text> </Text>
<Row gapX="2" justifyContent="flex-end"> <Row gapX="2" justifyContent="flex-end">
<Button primary onClick={onDismiss}>Ok</Button> <Button primary onClick={onDismiss}>Ok</Button>
</Row> </Row>
</Col> </Col>
) : ( ) : (
@ -145,7 +136,7 @@ export default function LaunchApp(props) {
</Box> </Box>
<Text mb="3" lineHeight="tall" fontWeight="medium">Welcome</Text> <Text mb="3" lineHeight="tall" fontWeight="medium">Welcome</Text>
<Text mb="3" lineHeight="tall"> <Text mb="3" lineHeight="tall">
You have been invited to use Landscape, an interface to chat You have been invited to use Landscape, an interface to chat
and interact with communities and interact with communities
<br /> <br />
Would you like a tour of Landscape? Would you like a tour of Landscape?
@ -160,9 +151,22 @@ export default function LaunchApp(props) {
</StatelessAsyncButton> </StatelessAsyncButton>
</Row> </Row>
</Col> </Col>
)} );
}
}); });
useEffect(() => {
if(query.get('tutorial')) {
if (hasTutorialGroup({ associations })) {
if (nextTutStep) {
nextTutStep();
}
} else {
showModal();
}
}
}, [query, showModal]);
useEffect(() => { useEffect(() => {
if(hasLoaded && !seen && tutorialProgress === 'hidden') { if(hasLoaded && !seen && tutorialProgress === 'hidden') {
showModal(); showModal();
@ -197,7 +201,8 @@ export default function LaunchApp(props) {
width='100%' width='100%'
bg='scales.black20' bg='scales.black20'
border={1} border={1}
borderColor="lightGray"> borderColor="lightGray"
>
<Row alignItems='center'> <Row alignItems='center'>
<Icon <Icon
color="black" color="black"
@ -232,9 +237,11 @@ export default function LaunchApp(props) {
(<Groups />) (<Groups />)
} }
</Box> </Box>
<Box alignSelf="flex-start" display={["block", "none"]}>{hashBox}</Box> <Box alignSelf="flex-start" display={['block', 'none']}>{hashBox}</Box>
</ScrollbarLessBox> </ScrollbarLessBox>
<Box display={["none", "block"]}>{hashBox}</Box> <Box display={['none', 'block']}>{hashBox}</Box>
</> </>
); );
} };
export default LaunchApp;

View File

@ -1,19 +1,17 @@
import React, { useRef } from 'react'; import { Box, Col, Text } from '@tlon/indigo-react';
import { Box, Text, Col } from '@tlon/indigo-react'; import { Association, Associations, Unreads } from '@urbit/api';
import f from 'lodash/fp'; import f from 'lodash/fp';
import _ from 'lodash';
import moment from 'moment'; import moment from 'moment';
import React, { useRef } from 'react';
import { Associations, Association, Unreads, UnreadStats } from '@urbit/api'; import { getNotificationCount, getUnreadCount } from '~/logic/lib/hark';
import { TUTORIAL_GROUP, TUTORIAL_GROUP_RESOURCE, TUTORIAL_HOST } from '~/logic/lib/tutorialModal';
import { alphabeticalOrder } from '~/logic/lib/util'; import { alphabeticalOrder } from '~/logic/lib/util';
import { getUnreadCount, getNotificationCount } from '~/logic/lib/hark';
import Tile from '../components/tiles/tile';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import useGroupState from '~/logic/state/group'; import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark'; import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata'; import useMetadataState from '~/logic/state/metadata';
import { TUTORIAL_HOST, TUTORIAL_GROUP, TUTORIAL_GROUP_RESOURCE } from '~/logic/lib/tutorialModal';
import useSettingsState, { selectCalmState, SettingsState } from '~/logic/state/settings'; import useSettingsState, { selectCalmState, SettingsState } from '~/logic/state/settings';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import Tile from '../components/tiles/tile';
interface GroupsProps {} interface GroupsProps {}

View File

@ -1,5 +1,5 @@
import { Button, Icon, Row, Text } from '@tlon/indigo-react';
import React from 'react'; import React from 'react';
import { Row, Button, Icon, Text } from '@tlon/indigo-react';
import { useModal } from '~/logic/lib/useModal'; import { useModal } from '~/logic/lib/useModal';
const ModalButton = (props) => { const ModalButton = (props) => {

View File

@ -1,13 +1,16 @@
import React from 'react'; import React, { ReactElement } from 'react';
import GlobalApi from '~/logic/api/global';
import useLaunchState from '~/logic/state/launch';
import BasicTile from './tiles/basic'; import BasicTile from './tiles/basic';
import CustomTile from './tiles/custom';
import ClockTile from './tiles/clock'; import ClockTile from './tiles/clock';
import CustomTile from './tiles/custom';
import WeatherTile from './tiles/weather'; import WeatherTile from './tiles/weather';
import useLaunchState from '~/logic/state/launch'; interface TileProps {
api: GlobalApi;
}
const Tiles = (props) => { const Tiles = (props: TileProps): ReactElement => {
const weather = useLaunchState(state => state.weather); const weather = useLaunchState(state => state.weather);
const tileOrdering = useLaunchState(state => state.tileOrdering); const tileOrdering = useLaunchState(state => state.tileOrdering);
const tileState = useLaunchState(state => state.tiles); const tileState = useLaunchState(state => state.tiles);
@ -44,11 +47,12 @@ const Tiles = (props) => {
} else { } else {
return <CustomTile key={key} />; return <CustomTile key={key} />;
} }
return null;
}); });
return ( return (
<>{tiles}</> <>{tiles}</>
); );
} };
export default Tiles; export default Tiles;

View File

@ -1,32 +0,0 @@
import React from 'react';
import { Text, Icon } from '@tlon/indigo-react';
import Tile from './tile';
export default class BasicTile extends React.PureComponent {
render() {
const { props } = this;
return (
<Tile
bg={props.title === 'Terminal' ? '#000000' : 'white'}
to={props.linkedUrl}
>
<Text color={props.title === 'Terminal' ? '#ffffff' : 'black'}>
{props.title === 'Terminal'
? <Icon
icon='ChevronEast'
color='#fff'
size='12px'
display='inline-block'
verticalAlign='top'
mt='5px'
mr='2'
/>
: null
}{props.title}
</Text>
</Tile>
);
}
}

View File

@ -0,0 +1,32 @@
import { Icon, Text } from '@tlon/indigo-react';
import React, { ReactElement } from 'react';
import Tile from './tile';
export interface BasicTileProps {
title: string;
linkedUrl: string;
}
const BasicTile = (props: BasicTileProps): ReactElement => (
<Tile
bg={props.title === 'Terminal' ? '#000000' : 'white'}
to={props.linkedUrl}
>
<Text color={props.title === 'Terminal' ? '#ffffff' : 'black'}>
{props.title === 'Terminal'
? <Icon
icon='ChevronEast'
color='#fff'
size='12px'
display='inline-block'
verticalAlign='top'
mt='5px'
mr='2'
/>
: null
}{props.title}
</Text>
</Tile>
);
export default BasicTile;

View File

@ -1,8 +1,8 @@
import React from 'react'; /* eslint-disable max-lines-per-function */
import moment from 'moment'; import moment from 'moment';
import SunCalc from 'suncalc'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import SunCalc from 'suncalc';
import Tile from './tile'; import Tile from './tile';
const VIEWBOX_SIZE = 100; const VIEWBOX_SIZE = 100;
@ -32,8 +32,6 @@ const minsToDegs = (mins) => {
return (mins / 1440) * 360; return (mins / 1440) * 360;
}; };
const radToDeg = rad => rad * (180 / Math.PI);
const degToRad = deg => deg * (Math.PI / 180); const degToRad = deg => deg * (Math.PI / 180);
const convert = (date, referenceTime) => { const convert = (date, referenceTime) => {
@ -42,25 +40,25 @@ const convert = (date, referenceTime) => {
// https://github.com/tingletech/moon-phase // https://github.com/tingletech/moon-phase
export const dFromPhase = (moonPhase) => { export const dFromPhase = (moonPhase) => {
let mag, sweep, d = "m50,0"; let mag, sweep, d = 'm50,0';
if (moonPhase <= 0.25) { if (moonPhase <= 0.25) {
sweep = [ 1, 0 ]; sweep = [1, 0];
mag = 20 - 20 * moonPhase * 4; mag = 20 - 20 * moonPhase * 4;
} else if (moonPhase <= 0.50) { } else if (moonPhase <= 0.50) {
sweep = [ 0, 0 ]; sweep = [0, 0];
mag = 20 * (moonPhase - 0.25) * 4; mag = 20 * (moonPhase - 0.25) * 4;
} else if (moonPhase <= 0.75) { } else if (moonPhase <= 0.75) {
sweep = [ 1, 1 ]; sweep = [1, 1];
mag = 20 - 20 * (moonPhase - 0.50) * 4; mag = 20 - 20 * (moonPhase - 0.50) * 4;
} else if (moonPhase <= 1) { } else if (moonPhase <= 1) {
sweep = [ 0, 1 ]; sweep = [0, 1];
mag = 20 * (moonPhase - 0.75) * 4; mag = 20 * (moonPhase - 0.75) * 4;
} }
d = d + "a" + mag + ",20 0 1," + sweep[0] + " 0,100 "; d = d + 'a' + mag + ',20 0 1,' + sweep[0] + ' 0,100 ';
d = d + "a20,20 0 1," + sweep[1] + " 0,-100"; d = d + 'a20,20 0 1,' + sweep[1] + ' 0,-100';
return d; return d;
} };
const Moon = ({ angle, ...props }) => { const Moon = ({ angle, ...props }) => {
const phase = SunCalc.getMoonIllumination(moment().toDate()).phase.toFixed(2); const phase = SunCalc.getMoonIllumination(moment().toDate()).phase.toFixed(2);
@ -70,7 +68,7 @@ const Moon = ({ angle, ...props }) => {
<g> <g>
<mask id="umbra"> <mask id="umbra">
<rect x="-50" y="-50" height="200" width="200" fill="black" /> <rect x="-50" y="-50" height="200" width="200" fill="black" />
<path d={dFromPhase(phase)} fill="white"/> <path d={dFromPhase(phase)} fill="white" />
</mask> </mask>
<circle <circle
id="moonbg" id="moonbg"
@ -83,7 +81,7 @@ const Moon = ({ angle, ...props }) => {
<use <use
width={CELESTIAL_BODY_SIZE} width={CELESTIAL_BODY_SIZE}
height={CELESTIAL_BODY_SIZE} height={CELESTIAL_BODY_SIZE}
xlinkHref="#Moon-symbol" xlinkHref="#Moon-symbol"
id="moon" id="moon"
x={cx} x={cx}
y={cy} y={cy}
@ -91,9 +89,9 @@ const Moon = ({ angle, ...props }) => {
/> />
</g> </g>
); );
} };
const Sun = ({ angle, ...props}) => ( const Sun = ({ angle, ...props }) => (
<circle <circle
id="sun" id="sun"
cx={CX + (RADIUS - 12) * Math.cos(degToRad(angle))} cx={CX + (RADIUS - 12) * Math.cos(degToRad(angle))}
@ -111,7 +109,7 @@ const SvgArc = ({ start, end, ...rest }) => {
const x2 = CX + RADIUS * Math.cos(degToRad(end)); const x2 = CX + RADIUS * Math.cos(degToRad(end));
const y2 = CY + RADIUS * Math.sin(degToRad(end)); const y2 = CY + RADIUS * Math.sin(degToRad(end));
const isLarge = Math.abs((start > 360 ? start - 360 : start) - end) > 180; // const isLarge = Math.abs((start > 360 ? start - 360 : start) - end) > 180;
const d = [ const d = [
'M', CX, CY, 'M', CX, CY,
@ -120,21 +118,25 @@ const SvgArc = ({ start, end, ...rest }) => {
].join(' '); ].join(' ');
return <path d={d} {...rest} />; return <path d={d} {...rest} />;
} };
class ClockText extends React.Component { class ClockText extends React.Component<ClockTextProps, ClockTextState> {
interval?: NodeJS.Timeout;
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
time: Date.now() time: Date.now()
} };
} }
componentDidMount() { componentDidMount() {
this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000); this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000);
} }
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.interval); if (this.interval) {
clearInterval(this.interval);
}
} }
render() { render() {
@ -156,7 +158,8 @@ class ClockText extends React.Component {
begin="0s" begin="0s"
dur="1s" dur="1s"
calcMode="discrete" calcMode="discrete"
repeatCount="indefinite"/> repeatCount="indefinite"
/>
</tspan> </tspan>
<tspan>{now.format('mm A')}</tspan> <tspan>{now.format('mm A')}</tspan>
</text> </text>
@ -167,7 +170,7 @@ class ClockText extends React.Component {
fontSize="10" fontSize="10"
fontFamily="Inter" fontFamily="Inter"
className="date" className="date"
>{now.format('MMM D')}<tspan style={{ fontFeatureSettings: "'sups' 1" }}>{now.format('Do').replace(now.format('D'), '')}</tspan></text> >{now.format('MMM D')}<tspan style={{ fontFeatureSettings: '\'sups\' 1' }}>{now.format('Do').replace(now.format('D'), '')}</tspan></text>
</> </>
); );
} }
@ -203,7 +206,6 @@ class Clock extends React.PureComponent {
initGeolocation() { initGeolocation() {
if (typeof this.props.data === 'object') { if (typeof this.props.data === 'object') {
const { latitude: lat, longitude: lon } = this.props.data; const { latitude: lat, longitude: lon } = this.props.data;
const suncalc = SunCalc.getTimes(new Date(), lat, lon); const suncalc = SunCalc.getTimes(new Date(), lat, lon);
@ -252,7 +254,7 @@ class Clock extends React.PureComponent {
return ( return (
<ApplyClockBg> <ApplyClockBg>
<svg <svg
style={{ height: '100%', width: '100%'}} style={{ height: '100%', width: '100%' }}
viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`} viewBox={`0 0 ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}`}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink" xmlnsXlink="http://www.w3.org/1999/xlink"
@ -266,14 +268,14 @@ class Clock extends React.PureComponent {
</symbol> </symbol>
<mask id="center-mask"> <mask id="center-mask">
<use xlinkHref="#border" fill="white" /> <use xlinkHref="#border" fill="white" />
<use xlinkHref="#clock-center" fill="black"/> <use xlinkHref="#clock-center" fill="black" />
</mask> </mask>
<symbol id="Moon-symbol" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> <symbol id="Moon-symbol" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g> <g>
<path mask="url(#umbra)" d="m50,0 a20,20 0 1,1 0,100 a20,20 0 1,1 0,-100" fill="#fff" stroke="#000"/> <path mask="url(#umbra)" d="m50,0 a20,20 0 1,1 0,100 a20,20 0 1,1 0,-100" fill="#fff" stroke="#000" />
</g> </g>
</symbol> </symbol>
</defs> </defs>
<g mask="url(#center-mask)"> <g mask="url(#center-mask)">
<use xlinkHref="#border" className="background" /> <use xlinkHref="#border" className="background" />
@ -313,12 +315,10 @@ class Clock extends React.PureComponent {
} }
} }
const ClockTile = ({ location = {} }) => ( const ClockTile = ({ location = {} }) => (
<Tile p={0} border={0} bg='transparent' boxShadow='none'> <Tile p={0} border={0} bg='transparent' boxShadow='none'>
<Clock data={location} /> <Clock data={location} />
</Tile> </Tile>
); );
export default ClockTile; export default ClockTile;

View File

@ -1,5 +1,5 @@
import React from 'react'; import { BaseImage, Box } from '@tlon/indigo-react';
import { Box, BaseImage } from '@tlon/indigo-react'; import React from 'react';
import Tile from './tile'; import Tile from './tile';
export default class CustomTile extends React.PureComponent { export default class CustomTile extends React.PureComponent {

View File

@ -1,11 +1,9 @@
import { Box } from '@tlon/indigo-react';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import defaultApps from '~/logic/lib/default-apps'; import defaultApps from '~/logic/lib/default-apps';
import { Box } from "@tlon/indigo-react";
const SquareBox = styled(Box)` const SquareBox = styled(Box)`
&::before { &::before {
content: ""; content: "";
@ -37,17 +35,15 @@ const Tile = React.forwardRef((props, ref) => {
} else { } else {
childElement= (<a href={to}>{childElement}</a>); childElement= (<a href={to}>{childElement}</a>);
} }
} }
return ( return (
<SquareBox <SquareBox
ref={ref} ref={ref}
position="relative" position="relative"
borderRadius={2} borderRadius={2}
overflow="hidden" overflow="hidden"
bg={bg || "white"} bg={bg || 'white'}
color={props?.color || 'lightGray'} color={props?.color || 'lightGray'}
boxShadow={boxShadow || '0 0 0px 1px inset'} boxShadow={boxShadow || '0 0 0px 1px inset'}
style={{ gridColumnStart }} style={{ gridColumnStart }}

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