interface: abstracted stores, unified naming

This commit is contained in:
Tyler Brown Cifu Shuster 2021-03-01 10:38:01 -08:00
parent b4fc43a752
commit bd3e00886c
60 changed files with 403 additions and 476 deletions

View File

@ -60,7 +60,6 @@ export default class BaseApi<S extends object = {}> {
}
scry<T>(app: string, path: Path): Promise<T> {
console.log(path);
return fetch(`/~/scry/${app}${path}.json`).then(r => r.json() as Promise<T>);
}

View File

@ -1,11 +1,16 @@
import { useEffect, useState } from 'react';
import _ from 'lodash';
import f, { memoize } from 'lodash/fp';
import f, { compose, memoize } from 'lodash/fp';
import bigInt, { BigInteger } from 'big-integer';
import { Association, Contact } from '@urbit/api';
import useLocalState from '../state/local';
import produce from 'immer';
import produce, { enableMapSet } from 'immer';
import useSettingsState from '../state/settings';
import { State, UseStore } from 'zustand';
import { Cage } from '~/types/cage';
import { BaseState } from '../state/base';
enableMapSet();
export const MOBILE_BROWSER_REGEX = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i;
@ -411,7 +416,43 @@ export function getItemTitle(association: Association) {
return association.metadata.title || association.resource;
}
export const stateSetter = <StateType>(fn: (state: StateType) => void, set): void => {
// TODO this is a stub for the store debugging
export const stateSetter = <StateType>(
fn: (state: StateType) => void,
set
): void => {
// fn = (state: StateType) => {
// // TODO this is a stub for the store debugging
// fn(state);
// }
return set(fn);
// TODO we want to use the below, but it makes everything read-only
return set(produce(fn));
};
export const reduceState = <
StateType extends BaseState<StateType>,
UpdateType
>(
state: UseStore<StateType>,
data: UpdateType,
reducers: ((data: UpdateType, state: StateType) => StateType)[]
): void => {
const oldState = state.getState();
const reducer = compose(reducers.map(reducer => reducer.bind(reducer, data)));
const newState = reducer(oldState);
state.getState().set(state => state = newState);
};
export let stateStorageKeys: string[] = [];
export const stateStorageKey = (stateName: string) => {
stateName = `Landcape${stateName}State`;
stateStorageKeys = [...new Set([...stateStorageKeys, stateName])];
return stateName;
};
(window as any).clearStates = () => {
stateStorageKeys.forEach(key => {
localStorage.removeItem(key);
});
}

View File

@ -0,0 +1,23 @@
import React from "react";
import { ReactElement } from "react";
import { UseStore } from "zustand";
import { BaseState } from "../state/base";
const withState = <
StateType extends BaseState<any>
>(
useState: UseStore<StateType>,
Component: any,
stateMemberKeys?: (keyof StateType)[]
) => {
return React.forwardRef((props, ref) => {
const state = stateMemberKeys ? useState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
) : useState();
return <Component ref={ref} {...state} {...props} />
})
};
export default withState;

View File

@ -3,22 +3,20 @@ import { compose } from 'lodash/fp';
import { ContactUpdate } from '@urbit/api';
import useContactState, { ContactState } from '../state/contacts';
import useContactState, { ContactState } from '../state/contact';
import { reduceState } from '../lib/util';
export const ContactReducer = (json) => {
const data: ContactUpdate = _.get(json, 'contact-update', false);
if (data) {
useContactState.setState(
compose([
reduceState<ContactState, ContactUpdate>(useContactState, data, [
initial,
add,
remove,
edit,
setPublic
].map(reducer => reducer.bind(reducer, data))
)(useContactState.getState())
);
]);
}
// TODO: better isolation

View File

@ -3,20 +3,18 @@ import { BigIntOrderedMap } from "~/logic/lib/BigIntOrderedMap";
import bigInt, { BigInteger } from "big-integer";
import useGraphState, { GraphState } from '../state/graph';
import { compose } from 'lodash/fp';
import { reduceState } from '../lib/util';
export const GraphReducer = (json) => {
const data = _.get(json, 'graph-update', false);
if (data) {
useGraphState.setState(
compose([
reduceState<GraphState, any>(useGraphState, data, [
keys,
addGraph,
removeGraph,
addNodes,
removeNodes
].map(reducer => reducer.bind(reducer, data))
)(useGraphState.getState())
);
]);
}
};

View File

@ -13,8 +13,8 @@ import {
InvitePolicy
} from '@urbit/api/groups';
import { Enc, PatpNoSig } from '@urbit/api';
import { resourceAsPath } from '../lib/util';
import useGroupState, { GroupState } from '../state/groups';
import { reduceState, resourceAsPath } from '../lib/util';
import useGroupState, { GroupState } from '../state/group';
import { compose } from 'lodash/fp';
function decodeGroup(group: Enc<Group>): Group {
@ -61,22 +61,18 @@ export default class GroupReducer {
reduce(json: Cage) {
const data = json.groupUpdate;
if (data) {
useGroupState.setState(
compose([
reduceState<GroupState, GroupUpdate>(useGroupState, data, [
initial,
addMembers,
addTag,
removeMembers,
initialGroup,
removeTag,
initial,
addGroup,
removeGroup,
changePolicy,
expose,
].map(reducer => reducer.bind(reducer, data))
)(useGroupState.getState())
);
]);
}
}
@ -84,9 +80,7 @@ export default class GroupReducer {
const initial = (json: GroupUpdate, state: GroupState): GroupState => {
const data = json['initial'];
if (data) {
state.set(st => {
st.groups = _.mapValues(data, decodeGroup);
});
state.groups = _.mapValues(data, decodeGroup);
}
return state;
}
@ -148,6 +142,7 @@ const removeMembers = (json: GroupUpdate, state: GroupState): GroupState => {
state.groups[resourcePath].members.delete(member);
}
}
return state;
}
const addTag = (json: GroupUpdate, state: GroupState): GroupState => {

View File

@ -6,7 +6,7 @@ import {
UnreadStats,
Timebox
} from '@urbit/api';
import { makePatDa } from '~/logic/lib/util';
import { makePatDa, reduceState } from '~/logic/lib/util';
import _ from 'lodash';
import { StoreState } from '../store/type';
import { BigIntOrderedMap } from '../lib/BigIntOrderedMap';
@ -20,30 +20,44 @@ export const HarkReducer = (json: any) => {
}
const graphHookData = _.get(json, 'hark-graph-hook-update', false);
if (graphHookData) {
useHarkState.setState(
compose([
reduceState<HarkState, any>(useHarkState, graphHookData, [
graphInitial,
graphIgnore,
graphListen,
graphWatchSelf,
graphMentions,
].map(reducer => reducer.bind(reducer, graphHookData))
)(useHarkState.getState())
);
]);
}
const groupHookData = _.get(json, 'hark-group-hook-update', false);
if (groupHookData) {
useHarkState.setState(
compose([
reduceState<HarkState, any>(useHarkState, groupHookData, [
groupInitial,
groupListen,
groupIgnore,
].map(reducer => reducer.bind(reducer, groupHookData))
)(useHarkState.getState())
)
]);
}
};
function reduce(data) {
reduceState<HarkState, any>(useHarkState, data, [
unread,
read,
archive,
timebox,
more,
dnd,
added,
unreads,
readEach,
readSince,
unreadSince,
unreadEach,
seenIndex,
removeGraph,
readAll,
]);
}
function groupInitial(json: any, state: HarkState): HarkState {
const data = _.get(json, 'initial', false);
if (data) {
@ -115,29 +129,6 @@ function graphWatchSelf(json: any, state: HarkState): HarkState {
return state;
}
function reduce(data: any) {
useHarkState.setState(
compose([
unread,
read,
archive,
timebox,
more,
dnd,
added,
unreads,
readEach,
readSince,
unreadSince,
unreadEach,
seenIndex,
removeGraph,
readAll,
].map(reducer => reducer.bind(reducer, data))
)(useHarkState.getState())
);
}
function readAll(json: any, state: HarkState): HarkState {
const data = _.get(json, 'read-all');
if(data) {
@ -197,7 +188,6 @@ function unreadEach(json: any, state: HarkState): HarkState {
function unreads(json: any, state: HarkState): HarkState {
const data = _.get(json, 'unreads');
if(data) {
clearState(state);
data.forEach(({ index, stats }) => {
const { unreads, notifications, last } = stats;
updateNotificationStats(state, index, 'notifications', x => x + notifications);
@ -236,28 +226,30 @@ function clearState(state: HarkState) {
});
}
function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: number) => number) {
function updateUnreadCount(state: HarkState, index: NotifIndex, count: (c: number) => number): HarkState {
if(!('graph' in index)) {
return;
return state;
}
const property = [index.graph.graph, index.graph.index, 'unreads'];
const curr = _.get(state.unreads.graph, property, 0);
const newCount = count(curr);
_.set(state.unreads.graph, property, newCount);
return state;
}
function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>) => void) {
function updateUnreads(state: HarkState, index: NotifIndex, f: (us: Set<string>) => void): HarkState {
if(!('graph' in index)) {
return;
return state;
}
const unreads = _.get(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], new Set<string>());
const oldSize = unreads.size;
f(unreads);
const newSize = unreads.size;
_.set(state.unreads.graph, [index.graph.graph, index.graph.index, 'unreads'], unreads);
return state;
}
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number) {
function updateNotificationStats(state: HarkState, index: NotifIndex, statField: 'notifications' | 'unreads' | 'last', f: (x: number) => number): HarkState {
if(statField === 'notifications') {
state.notificationsCount = f(state.notificationsCount);
}
@ -268,6 +260,7 @@ function updateNotificationStats(state: HarkState, index: NotifIndex, statField:
const curr = _.get(state.unreads.group, [index.group, statField], 0);
_.set(state.unreads.group, [index.group, statField], f(curr));
}
return state;
}
function added(json: any, state: HarkState): HarkState {
@ -282,12 +275,13 @@ function added(json: any, state: HarkState): HarkState {
);
if (arrIdx !== -1) {
if (timebox[arrIdx]?.notification?.read) {
updateNotificationStats(state, index, 'notifications', x => x+1);
// TODO this is additive, and with a persistent state it keeps incrementing
state = updateNotificationStats(state, index, 'notifications', x => x+1);
}
timebox[arrIdx] = { index, notification };
state.notifications.set(time, timebox);
} else {
updateNotificationStats(state, index, 'notifications', x => x+1);
state = updateNotificationStats(state, index, 'notifications', x => x+1);
state.notifications.set(time, [...timebox, { index, notification }]);
}
}
@ -316,7 +310,7 @@ const timebox = (json: any, state: HarkState): HarkState => {
function more(json: any, state: HarkState): HarkState {
const data = _.get(json, 'more', false);
if (data) {
_.forEach(data, d => reduce(d, state));
_.forEach(data, d => reduce(d));
}
return state;
}

View File

@ -5,22 +5,20 @@ import { InviteUpdate } from '@urbit/api/invite';
import { Cage } from '~/types/cage';
import useInviteState, { InviteState } from '../state/invite';
import { reduceState } from '../lib/util';
export default class InviteReducer {
reduce(json: Cage) {
const data = json['invite-update'];
if (data) {
useInviteState.setState(
compose([
reduceState<InviteState, InviteUpdate>(useInviteState, data, [
initial,
create,
deleteInvite,
invite,
accepted,
decline,
].map(reducer => reducer.bind(reducer, data))
)(useInviteState.getState())
);
]);
}
}
}

View File

@ -3,31 +3,33 @@ import { LaunchState, LaunchUpdate, WeatherState } from '~/types/launch-update';
import { Cage } from '~/types/cage';
import useLaunchState from '../state/launch';
import { compose } from 'lodash/fp';
import { reduceState } from '../lib/util';
export default class LaunchReducer {
reduce(json: Cage) {
const data = _.get(json, 'launch-update', false);
if (data) {
useLaunchState.setState(
compose([
reduceState<LaunchState, LaunchUpdate>(useLaunchState, data, [
initial,
changeFirstTime,
changeOrder,
changeFirstTime,
changeIsShown,
].map(reducer => reducer.bind(reducer, data))
)(useLaunchState.getState())
)
]);
}
const weatherData: WeatherState = _.get(json, 'weather', false);
if (weatherData) {
useLaunchState.setState({ weather: weatherData });
useLaunchState.getState().set(state => {
state.weather = weatherData;
});
}
const locationData = _.get(json, 'location', false);
if (locationData) {
useLaunchState.setState({ userLocation: locationData });
useLaunchState.getState().set(state => {
state.userLocation = locationData;
});
}
}
}

View File

@ -5,28 +5,25 @@ import { MetadataUpdate } from '@urbit/api/metadata';
import { Cage } from '~/types/cage';
import useMetadataState, { MetadataState } from '../state/metadata';
import { reduceState } from '../lib/util';
export default class MetadataReducer {
reduce(json: Cage) {
const data = json['metadata-update'];
if (data) {
useMetadataState.setState(
compose([
reduceState<MetadataState, MetadataUpdate>(useMetadataState, data, [
associations,
add,
update,
remove,
groupInitial,
].map(reducer => reducer.bind(reducer, data))
)(useMetadataState.getState())
);
]);
}
}
}
const groupInitial = (json: MetadataUpdate, state: MetadataState): MetadataState => {
const data = _.get(json, 'initial-group', false);
console.log(data);
if(data) {
state = associations(data, state);
}

View File

@ -2,6 +2,7 @@ import _ from 'lodash';
import { compose } from 'lodash/fp';
import { Cage } from '~/types/cage';
import { S3Update } from '~/types/s3-update';
import { reduceState } from '../lib/util';
import useS3State, { S3State } from '../state/s3';
@ -9,8 +10,7 @@ export default class S3Reducer {
reduce(json: Cage) {
const data = _.get(json, 's3-update', false);
if (data) {
useS3State.setState(
compose([
reduceState<S3State, S3Update>(useS3State, data, [
credentials,
configuration,
currentBucket,
@ -19,9 +19,7 @@ export default class S3Reducer {
endpoint,
accessKeyId,
secretAccessKey,
].map(reducer => reducer.bind(reducer, data))
)(useS3State.getState())
)
]);
}
}
}

View File

@ -0,0 +1,21 @@
import create, { State, UseStore } from "zustand";
import { persist } from "zustand/middleware";
import { stateSetter, stateStorageKey } from "../lib/util";
export interface BaseState<StateType> extends State {
set: (fn: (state: StateType) => void) => void;
}
export const createState = <StateType extends BaseState<any>>(
name: string,
properties: Omit<StateType, 'set'>,
blacklist: string[] = []
): UseStore<StateType> => create(persist((set, get) => ({
// TODO why does this typing break?
set: fn => stateSetter(fn, set),
...properties
}), {
blacklist,
name: stateStorageKey(name),
version: 1, // TODO version these according to base hash
}));

View File

@ -0,0 +1,31 @@
import { Patp, Rolodex, Scry } from "@urbit/api";
import { BaseState, createState } from "./base";
export interface ContactState extends BaseState<ContactState> {
contacts: Rolodex;
isContactPublic: boolean;
nackedContacts: Set<Patp>;
// fetchIsAllowed: (entity, name, ship, personal) => Promise<boolean>;
};
const useContactState = createState<ContactState>('Contact', {
contacts: {},
nackedContacts: new Set(),
isContactPublic: false,
// fetchIsAllowed: async (
// entity,
// name,
// ship,
// personal
// ): Promise<boolean> => {
// const isPersonal = personal ? 'true' : 'false';
// const api = useApi();
// return api.scry({
// app: 'contact-store',
// path: `/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
// });
// },
}, ['nackedContacts']);
export default useContactState;

View File

@ -1,52 +0,0 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { Patp, Rolodex, Scry } from "@urbit/api";
import { stateSetter } from "~/logic/lib/util";
// import useApi from "~/logic/lib/useApi";
export interface ContactState extends State {
contacts: Rolodex;
isContactPublic: boolean;
nackedContacts: Set<Patp>;
// fetchIsAllowed: (entity, name, ship, personal) => Promise<boolean>;
set: (fn: (state: ContactState) => void) => void;
};
const useContactState = create<ContactState>(persist((set, get) => ({
contacts: {},
nackedContacts: new Set(),
isContactPublic: false,
// fetchIsAllowed: async (
// entity,
// name,
// ship,
// personal
// ): Promise<boolean> => {
// const isPersonal = personal ? 'true' : 'false';
// const api = useApi();
// return api.scry({
// app: 'contact-store',
// path: `/is-allowed/${entity}/${name}/${ship}/${isPersonal}`
// });
// },
set: fn => stateSetter(fn, set)
}), {
blacklist: ['nackedContacts'],
name: 'LandscapeContactState'
}));
function withContactState<P, S extends keyof ContactState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const contactState = stateMemberKeys ? useContactState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useContactState();
return <Component ref={ref} {...contactState} {...props} />
});
}
export { useContactState as default, withContactState };

View File

@ -1,13 +1,8 @@
import { Graphs, decToUd, numToUd } from "@urbit/api";
import React from "react";
import create, { State } from "zustand";
import { persist } from "zustand/middleware";
// import useApi from "~/logic/lib/useApi";
import { stateSetter } from "~/logic/lib/util";
// import { graphReducer } from "~/logic/subscription/graph";
import { BaseState, createState } from "./base";
export interface GraphState extends State {
export interface GraphState extends BaseState<GraphState> {
graphs: Graphs;
graphKeys: Set<string>;
// getKeys: () => Promise<void>;
@ -19,10 +14,9 @@ export interface GraphState extends State {
// getYoungerSiblings: (ship: string, resource: string, count: number, index?: string) => Promise<void>;
// getGraphSubset: (ship: string, resource: string, start: string, end: string) => Promise<void>;
// getNode: (ship: string, resource: string, index: string) => Promise<void>;
set: (fn: (state: GraphState) => void) => void;
};
const useGraphState = create<GraphState>(persist((set, get) => ({
const useGraphState = createState<GraphState>('Graph', {
graphs: {},
graphKeys: new Set(),
// getKeys: async () => {
@ -124,21 +118,6 @@ const useGraphState = create<GraphState>(persist((set, get) => ({
// });
// graphReducer(node);
// },
set: fn => stateSetter(fn, set)
}), {
blacklist: ['graphKeys', 'graphs'],
name: 'LandscapeGraphState'
}));
}, ['graphs', 'graphKeys']);
function withGraphState<P, S extends keyof GraphState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const graphState = stateMemberKeys ? useGraphState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useGraphState();
return <Component ref={ref} {...graphState} {...props} />
});
}
export { useGraphState as default, withGraphState };
export default useGraphState;

View File

@ -0,0 +1,15 @@
import { Path, JoinRequests } from "@urbit/api";
import { BaseState, createState } from "./base";
export interface GroupState extends BaseState<GroupState> {
groups: Set<Path>;
pendingJoin: JoinRequests;
};
const useGroupState = createState<GroupState>('Group', {
groups: new Set(),
pendingJoin: {},
}, ['groups']);
export default useGroupState;

View File

@ -1,36 +0,0 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { JoinRequests } from '@urbit/api/groups';
import { Path } from "@urbit/api";
import { stateSetter } from "~/logic/lib/util";
export interface GroupState extends State {
groups: Set<Path>;
pendingJoin: JoinRequests;
set: (fn: (state: GroupState) => void) => void;
};
const useGroupState = create<GroupState>(persist((set, get) => ({
groups: new Set(),
pendingJoin: {},
set: fn => stateSetter(fn, set)
}), {
blacklist: ['groups'],
name: 'LandscapeGroupState'
}));
function withGroupState<P, S extends keyof GroupState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const groupState = stateMemberKeys ? useGroupState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useGroupState();
return <Component ref={ref} {...groupState} {...props} />
});
}
export { useGroupState as default, withGroupState };

View File

@ -1,17 +1,12 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { NotificationGraphConfig, Timebox, Unreads, dateToDa } from "@urbit/api";
import BigIntOrderedMap from "@urbit/api/lib/BigIntOrderedMap";
// import useApi from "~/logic/lib/useApi";
// import { harkGraphHookReducer, harkGroupHookReducer, harkReducer } from "~/logic/subscription/hark";
import { stateSetter } from "~/logic/lib/util";
import { BaseState, createState } from "./base";
export const HARK_FETCH_MORE_COUNT = 3;
export interface HarkState extends State {
export interface HarkState extends BaseState<HarkState> {
archivedNotifications: BigIntOrderedMap<Timebox>;
doNotDisturb: boolean;
// getMore: () => Promise<boolean>;
@ -21,11 +16,10 @@ export interface HarkState extends State {
notificationsCount: number;
notificationsGraphConfig: NotificationGraphConfig; // TODO unthread this everywhere
notificationsGroupConfig: []; // TODO type this
set: (fn: (state: HarkState) => void) => void;
unreads: Unreads;
};
const useHarkState = create<HarkState>(persist((set, get) => ({
const useHarkState = createState('Hark', {
archivedNotifications: new BigIntOrderedMap<Timebox>(),
doNotDisturb: false,
// getMore: async (): Promise<boolean> => {
@ -65,25 +59,11 @@ const useHarkState = create<HarkState>(persist((set, get) => ({
watching: []
},
notificationsGroupConfig: [],
set: fn => stateSetter(fn, set),
unreads: {
graph: {},
group: {}
},
}), {
blacklist: ['notifications', 'archivedNotifications', 'unreads'],
name: 'LandscapeHarkState'
}));
}, ['notifications', 'archivedNotifications', 'unreads']);
function withHarkState<P, S extends keyof HarkState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const harkState = stateMemberKeys ? useHarkState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useHarkState();
return <Component ref={ref} {...harkState} {...props} />
});
}
export { useHarkState as default, withHarkState };
export default useHarkState;

View File

@ -0,0 +1,13 @@
import { Invites } from '@urbit/api';
import { BaseState, createState } from "./base";
export interface InviteState extends BaseState<InviteState> {
invites: Invites;
};
const useInviteState = createState<InviteState>('Invite', {
invites: {},
});
export default useInviteState;

View File

@ -1,32 +0,0 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { Invites } from '@urbit/api';
import { stateSetter } from "../lib/util";
export interface InviteState extends State {
invites: Invites;
set: (fn: (state: InviteState) => void) => void;
};
const useInviteState = create<InviteState>(persist((set, get) => ({
invites: {},
set: fn => stateSetter(fn, set),
}), {
name: 'LandscapeInviteState'
}));
function withInviteState<P, S extends keyof InviteState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const inviteState = stateMemberKeys ? useInviteState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useInviteState();
return <Component ref={ref} {...inviteState} {...props} />
});
}
export { useInviteState as default, withInviteState };

View File

@ -0,0 +1,25 @@
import { Tile, WeatherState } from "~/types/launch-update";
import { BaseState, createState } from "./base";
export interface LaunchState extends BaseState<LaunchState> {
firstTime: boolean;
tileOrdering: string[];
tiles: {
[app: string]: Tile;
},
weather: WeatherState | null,
userLocation: string | null;
};
const useLaunchState = createState<LaunchState>('Launch', {
firstTime: true,
tileOrdering: [],
tiles: {},
weather: null,
userLocation: null,
});
export default useLaunchState;

View File

@ -1,41 +0,0 @@
import React from "react";
import create, { State } from "zustand";
import { persist } from "zustand/middleware";
import { Tile, WeatherState } from "~/types/launch-update";
import { stateSetter } from "../lib/util";
export interface LaunchState extends State {
firstTime: boolean;
tileOrdering: string[];
tiles: {
[app: string]: Tile;
},
weather: WeatherState | null,
userLocation: string | null;
set: (fn: (state: LaunchState) => void) => void;
};
const useLaunchState = create<LaunchState>(persist((set, get) => ({
firstTime: true,
tileOrdering: [],
tiles: {},
weather: null,
userLocation: null,
set: fn => stateSetter(fn, set)
}), {
name: 'LandscapeLaunchState'
}));
function withLaunchState<P, S extends keyof LaunchState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const launchState = stateMemberKeys ? useLaunchState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useLaunchState();
return <Component ref={ref} {...launchState} {...props} />
});
}
export { useLaunchState as default, withLaunchState };

View File

@ -1,21 +1,15 @@
import React from "react";
import create, { State } from 'zustand';
import { persist } from 'zustand/middleware';
import { MetadataUpdatePreview, Associations } from "@urbit/api";
// import useApi from "~/logic/lib/useApi";
import { stateSetter } from "~/logic/lib/util";
import { BaseState, createState } from "./base";
export const METADATA_MAX_PREVIEW_WAIT = 150000;
export interface MetadataState extends State {
export interface MetadataState extends BaseState<MetadataState> {
associations: Associations;
// preview: (group: string) => Promise<MetadataUpdatePreview>;
set: (fn: (state: MetadataState) => void) => void;
};
const useMetadataState = create<MetadataState>(persist((set, get) => ({
const useMetadataState = createState<MetadataState>('Metadata', {
associations: { groups: {}, graph: {}, contacts: {}, chat: {}, link: {}, publish: {} },
// preview: async (group): Promise<MetadataUpdatePreview> => {
// return new Promise<MetadataUpdatePreview>((resolve, reject) => {
@ -57,20 +51,7 @@ const useMetadataState = create<MetadataState>(persist((set, get) => ({
// });
// });
// },
set: fn => stateSetter(fn, set),
}), {
name: 'LandscapeMetadataState'
}));
function withMetadataState<P, S extends keyof MetadataState>(Component: any, stateMemberKeys?: S[]) {
return React.forwardRef((props: Omit<P, S>, ref) => {
const metadataState = stateMemberKeys ? useMetadataState(
state => stateMemberKeys.reduce(
(object, key) => ({ ...object, [key]: state[key] }), {}
)
): useMetadataState();
return <Component ref={ref} {...metadataState} {...props} />
});
}
export { useMetadataState as default, withMetadataState };
export default useMetadataState;

View File

@ -0,0 +1,19 @@
import { BaseState, createState } from "./base";
export interface S3State extends BaseState<S3State> {
configuration: {
buckets: Set<string>;
currentBucket: string;
};
credentials: any | null; // TODO better type
};
const useS3State = createState<S3State>('S3', {
configuration: {
buckets: new Set(),
currentBucket: ''
},
credentials: null,
}, ['configuration']);
export default useS3State;

View File

@ -1,26 +0,0 @@
import create, { State } from "zustand";
import { persist } from "zustand/middleware";
import { stateSetter } from "../lib/util";
export interface S3State extends State {
configuration: {
buckets: Set<string>;
currentBucket: string;
};
credentials: any | null; // TODO better type
set: (fn: (state: S3State) => void) => void;
};
const useS3State = create<S3State>(persist((set, get) => ({
configuration: {
buckets: new Set(),
currentBucket: ''
},
credentials: null,
set: fn => stateSetter(fn, set)
}), {
blacklist: ['configuration'],
name: 'LandscapeS3State'
}));
export default useS3State;

View File

@ -27,9 +27,10 @@ import GlobalSubscription from '~/logic/subscription/global';
import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import { withLocalState } from '~/logic/state/local';
import { withContactState } from '~/logic/state/contacts';
import { withGroupState } from '~/logic/state/groups';
import withState from '~/logic/lib/withState';
import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import { withSettingsState } from '~/logic/state/settings';
@ -175,7 +176,6 @@ class App extends React.Component {
ship={this.ship}
api={this.api}
subscription={this.subscription}
{...state}
/>
</ErrorBoundary>
</Router>
@ -186,5 +186,6 @@ class App extends React.Component {
}
}
export default withGroupState(withContactState(withLocalState(process.env.NODE_ENV === 'production' ? App : hot(App))));
export default withState(useGroupState, withState(useContactState, withState(useLocalState, process.env.NODE_ENV === 'production' ? App : hot(App))));

View File

@ -17,9 +17,9 @@ import useS3 from '~/logic/lib/useS3';
import { isWriter, resourceFromPath } from '~/logic/lib/group';
import './css/custom.css';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
import useGraphState from '~/logic/state/graph';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
type ChatResourceProps = StoreState & {

View File

@ -35,7 +35,7 @@ import styled from 'styled-components';
import useLocalState from '~/logic/state/local';
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
import Timestamp from '~/views/components/Timestamp';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';

View File

@ -7,7 +7,7 @@ import _ from 'lodash';
import { Col, Button, Box, Row, Icon, Text } from '@tlon/indigo-react';
import './css/custom.css';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
import Tiles from './components/tiles';
import Tile from './components/tiles/tile';
import Groups from './components/Groups';

View File

@ -9,7 +9,7 @@ import { getUnreadCount, getNotificationCount } from '~/logic/lib/hark';
import Tile from '../components/tiles/tile';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';

View File

@ -2,7 +2,8 @@ import React from 'react';
import moment from 'moment';
import { Box, Icon, Text, BaseAnchor, BaseInput } from '@tlon/indigo-react';
import ErrorBoundary from '~/views/components/ErrorBoundary';
import { withLaunchState } from '~/logic/state/launch';
import withState from '~/logic/lib/withState';
import useLaunchState from '~/logic/state/launch';
import Tile from './tile';
@ -290,4 +291,4 @@ class WeatherTile extends React.Component {
}
}
export default withLaunchState(WeatherTile);
export default withState(useLaunchState, WeatherTile);

View File

@ -18,8 +18,8 @@ import { getSnippet } from '~/logic/lib/publish';
import styled from 'styled-components';
import { MentionText } from '~/views/components/MentionText';
import ChatMessage from '../chat/components/ChatMessage';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
function getGraphModuleIcon(module: string) {
if (module === 'link') {

View File

@ -9,7 +9,7 @@ import { Associations, Contact, Contacts, Rolodex } from '@urbit/api';
import { PropFunc } from '~/types/util';
import { useShowNickname } from '~/logic/lib/util';
import Timestamp from '~/views/components/Timestamp';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
import useMetadataState from '~/logic/state/metadata';
const Text = (props: PropFunc<typeof Text>) => (

View File

@ -20,7 +20,7 @@ import { ImageInput } from '~/views/components/ImageInput';
import { MarkdownField } from '~/views/apps/publish/components/MarkdownField';
import { resourceFromPath } from '~/logic/lib/group';
import GroupSearch from '~/views/components/GroupSearch';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
const formSchema = Yup.object({
nickname: Yup.string(),

View File

@ -17,7 +17,7 @@ import { EditProfile } from './EditProfile';
import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
import { uxToHex } from '~/logic/lib/util';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';

View File

@ -15,7 +15,7 @@ import RichText from '~/views/components/RichText';
import { GroupLink } from '~/views/components/GroupLink';
import { lengthOrder } from '~/logic/lib/util';
import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
export function ViewProfile(props: any): ReactElement {
const { hideNicknames } = useSettingsState(selectCalmState);

View File

@ -5,7 +5,7 @@ import Helmet from 'react-helmet';
import { Box } from '@tlon/indigo-react';
import { Profile } from './components/Profile';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
import useHarkState from '~/logic/state/hark';
export default function ProfileScreen(props: any) {

View File

@ -7,8 +7,8 @@ import { Contacts, Rolodex, Groups, Associations, Graph, Association, Unreads }
import { NotebookPosts } from './NotebookPosts';
import GlobalApi from '~/logic/api/global';
import { useShowNickname } from '~/logic/lib/util';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
interface NotebookProps {
api: GlobalApi;

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { Col } from '@tlon/indigo-react';
import { NotePreview } from './NotePreview';
import { Contacts, Graph, Unreads, Group } from '@urbit/api';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
interface NotebookPostsProps {
graph: Graph;

View File

@ -18,7 +18,7 @@ import Notebook from './Notebook';
import NewPost from './new-post';
import { NoteRoutes } from './NoteRoutes';
import useGraphState from '~/logic/state/graph';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
interface NotebookRoutesProps {
api: GlobalApi;

View File

@ -10,7 +10,8 @@ import { Box, Col } from '@tlon/indigo-react';
import Api from './api';
import Store from './store';
import Subscription from './subscription';
import { withHarkState } from '~/logic/state/hark';
import withState from '~/logic/lib/withState';
import useHarkState from '~/logic/state/hark';
import './css/custom.css';
@ -94,4 +95,4 @@ class TermApp extends Component {
}
}
export default withHarkState(TermApp);
export default withState(useHarkState, TermApp);

View File

@ -11,7 +11,7 @@ import OverlaySigil from './OverlaySigil';
import { Sigil } from '~/logic/lib/sigil';
import GlobalApi from '~/logic/api/global';
import Timestamp from './Timestamp';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
interface AuthorProps {
ship: string;

View File

@ -18,7 +18,7 @@ import { Associations, Association } from '@urbit/api/metadata';
import { roleForShip } from '~/logic/lib/group';
import { DropdownSearch } from './DropdownSearch';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
interface GroupSearchProps<I extends string> {

View File

@ -18,7 +18,7 @@ import { GroupInvite } from './Group';
import { InviteSkeleton } from './InviteSkeleton';
import { JoinSkeleton } from './JoinSkeleton';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
interface InviteItemProps {

View File

@ -6,7 +6,7 @@ import RichText from '~/views/components/RichText';
import { cite, useShowNickname, uxToHex } from '~/logic/lib/util';
import OverlaySigil from '~/views/components/OverlaySigil';
import { useHistory } from 'react-router-dom';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
interface MentionTextProps {
contact?: Contact;

View File

@ -24,8 +24,8 @@ import { Rolodex, Groups } from '@urbit/api';
import { DropdownSearch } from './DropdownSearch';
import { cite, deSig } from '~/logic/lib/util';
import { HoverBox } from './HoverBox';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
interface InviteSearchProps<I extends string> {
autoFocus?: boolean;

View File

@ -17,8 +17,8 @@ import {useOutsideClick} from '~/logic/lib/useOutsideClick';
import {Portal} from '../Portal';
import useSettingsState, {SettingsState} from '~/logic/state/settings';
import { Tile } from '~/types';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import useInviteState from '~/logic/state/invite';
import useLaunchState from '~/logic/state/launch';

View File

@ -3,9 +3,10 @@ import { Box, Row, Icon, Text } from '@tlon/indigo-react';
import defaultApps from '~/logic/lib/default-apps';
import Sigil from '~/logic/lib/sigil';
import { uxToHex, cite } from '~/logic/lib/util';
import { withHarkState } from '~/logic/state/hark';
import { withContactState } from '~/logic/state/contacts';
import { withInviteState } from '~/logic/state/invite';
import withState from '~/logic/lib/withState';
import useHarkState from '~/logic/state/hark';
import useContactState from '~/logic/state/contact';
import useInviteState from '~/logic/state/invite';
export class OmniboxResult extends Component {
constructor(props) {
@ -122,4 +123,4 @@ export class OmniboxResult extends Component {
}
}
export default withInviteState(withHarkState(withContactState(OmniboxResult), ['notificationsCount']));
export default withState(useInviteState, withState(useHarkState, withState(useContactState, OmniboxResult), ['notificationsCount']));

View File

@ -9,7 +9,7 @@ import { Groups, Associations, Association } from '@urbit/api';
import GlobalApi from '~/logic/api/global';
import GroupSearch from '~/views/components/GroupSearch';
import { AsyncButton } from '~/views/components/AsyncButton';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
const formSchema = Yup.object({
group: Yup.string().nullable()

View File

@ -27,8 +27,8 @@ import '~/views/apps/publish/css/custom.css';
import { getGroupFromWorkspace } from '~/logic/lib/workspace';
import { GroupSummary } from './GroupSummary';
import { Workspace } from '~/types/workspace';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';

View File

@ -22,7 +22,7 @@ import { StatelessAsyncButton } from '~/views/components/StatelessAsyncButton';
import { getModuleIcon } from '~/logic/lib/util';
import { FormError } from '~/views/components/FormError';
import { GroupSummary } from './GroupSummary';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
const formSchema = Yup.object({

View File

@ -22,7 +22,7 @@ import { Rolodex } from '@urbit/api';
import { IconRadio } from '~/views/components/IconRadio';
import { ChannelWriteFieldSchema, ChannelWritePerms } from './ChannelWritePerms';
import { Workspace } from '~/types/workspace';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
type FormSchema = {
name: string;

View File

@ -16,7 +16,7 @@ import { AsyncButton } from '~/views/components/AsyncButton';
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
import GlobalApi from '~/logic/api/global';
import { stringToSymbol } from '~/logic/lib/util';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
const formSchema = Yup.object({

View File

@ -30,7 +30,7 @@ import { Dropdown } from '~/views/components/Dropdown';
import GlobalApi from '~/logic/api/global';
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
import useLocalState from '~/logic/state/local';
import useContactState from '~/logic/state/contacts';
import useContactState from '~/logic/state/contact';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
const TruncText = styled(Text)`

View File

@ -11,8 +11,8 @@ import { StoreState } from '~/logic/store/type';
import GlobalApi from '~/logic/api/global';
import { ResourceSkeleton } from './ResourceSkeleton';
import { ChannelPopoverRoutes } from './ChannelPopoverRoutes';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/group';
import useContactState from '~/logic/state/contact';
import useHarkState from '~/logic/state/hark';
import useMetadataState from '~/logic/state/metadata';

View File

@ -11,8 +11,8 @@ import RichText from '~/views/components/RichText';
import GlobalApi from '~/logic/api/global';
import { isWriter } from '~/logic/lib/group';
import { getItemTitle } from '~/logic/lib/util';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
const TruncatedText = styled(RichText)`
white-space: pre;
@ -55,7 +55,7 @@ export function ResourceSkeleton(props: ResourceSkeletonProps): ReactElement {
recipient = title;
title = (contacts?.[title]?.nickname) ? contacts[title].nickname : title;
} else {
recipient = Array.from(group.members).map(e => `~${e}`).join(", ")
recipient = Array.from(group ? group.members : []).map(e => `~${e}`).join(", ")
}
const [, , ship, resource] = rid.split('/');

View File

@ -20,7 +20,7 @@ import { SidebarAppConfigs } from './types';
import { SidebarList } from './SidebarList';
import { roleForShip } from '~/logic/lib/group';
import { useTutorialModal } from '~/views/components/useTutorialModal';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
const ScrollbarLessCol = styled(Col)`

View File

@ -11,8 +11,8 @@ import { useTutorialModal } from '~/views/components/useTutorialModal';
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
import { SidebarAppConfigs, SidebarItemStatus } from './types';
import { Workspace } from '~/types/workspace';
import useContactState from '~/logic/state/contacts';
import useGroupState from '~/logic/state/groups';
import useContactState from '~/logic/state/contact';
import useGroupState from '~/logic/state/group';
import useSettingsState, { selectCalmState } from '~/logic/state/settings';

View File

@ -21,7 +21,7 @@ import { roleForShip } from '~/logic/lib/group';
import { NewChannel } from '~/views/landscape/components/NewChannel';
import GlobalApi from '~/logic/api/global';
import { Workspace } from '~/types/workspace';
import useGroupState from '~/logic/state/groups';
import useGroupState from '~/logic/state/group';
import useMetadataState from '~/logic/state/metadata';
export function SidebarListHeader(props: {

View File

@ -18,7 +18,8 @@ import { Loading } from '../components/Loading';
import { Workspace } from '~/types/workspace';
import GlobalSubscription from '~/logic/subscription/global';
import useGraphState from '~/logic/state/graph';
import { withHarkState } from '~/logic/state/hark';
import useHarkState, { withHarkState } from '~/logic/state/hark';
import withState from '~/logic/lib/withState';
type LandscapeProps = StoreState & {
ship: PatpNoSig;
@ -155,4 +156,6 @@ class Landscape extends Component<LandscapeProps, Record<string, never>> {
}
}
export default withHarkState(Landscape);
export default withState(useHarkState, Landscape, ['notificationsCount']);
// export default withHarkState(Landscape);