mirror of
https://github.com/urbit/shrub.git
synced 2024-12-23 19:05:48 +03:00
interface: convert metadata store to zustand
This commit is contained in:
parent
d17794f93d
commit
041be1d8fe
@ -1,103 +1,111 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { StoreState } from '../../store/type';
|
||||
import { compose } from 'lodash/fp';
|
||||
|
||||
import { MetadataUpdate } from '@urbit/api/metadata';
|
||||
|
||||
import { Cage } from '~/types/cage';
|
||||
import useMetadataState, { MetadataState } from '../state/metadata';
|
||||
|
||||
type MetadataState = Pick<StoreState, 'associations'>;
|
||||
|
||||
export default class MetadataReducer<S extends MetadataState> {
|
||||
reduce(json: Cage, state: S) {
|
||||
export default class MetadataReducer {
|
||||
reduce(json: Cage) {
|
||||
const data = json['metadata-update'];
|
||||
if (data) {
|
||||
console.log(data);
|
||||
this.associations(data, state);
|
||||
this.add(data, state);
|
||||
this.update(data, state);
|
||||
this.remove(data, state);
|
||||
this.groupInitial(data, state);
|
||||
useMetadataState.setState(
|
||||
compose([
|
||||
associations,
|
||||
add,
|
||||
update,
|
||||
remove,
|
||||
groupInitial,
|
||||
].map(reducer => reducer.bind(reducer, data))
|
||||
)(useMetadataState.getState())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groupInitial(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'initial-group', false);
|
||||
console.log(data);
|
||||
if(data) {
|
||||
this.associations(data, state);
|
||||
}
|
||||
const groupInitial = (json: MetadataUpdate, state: MetadataState): MetadataState => {
|
||||
const data = _.get(json, 'initial-group', false);
|
||||
console.log(data);
|
||||
if(data) {
|
||||
state = associations(data, state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
associations(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
Object.keys(data).forEach((key) => {
|
||||
const val = data[key];
|
||||
const appName = val['app-name'];
|
||||
const rid = val.resource;
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
if (!(rid in metadata[appName])) {
|
||||
metadata[appName][rid] = {};
|
||||
}
|
||||
metadata[appName][rid] = val;
|
||||
});
|
||||
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
add(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const appPath = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
if (!(appPath in metadata[appName])) {
|
||||
metadata[appName][appPath] = {};
|
||||
}
|
||||
metadata[appName][appPath] = data;
|
||||
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
update(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
const associations = (json: MetadataUpdate, state: MetadataState): MetadataState => {
|
||||
const data = _.get(json, 'associations', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
Object.keys(data).forEach((key) => {
|
||||
const val = data[key];
|
||||
const appName = val['app-name'];
|
||||
const rid = val.resource;
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
if (!(rid in metadata[appName])) {
|
||||
metadata[appName][rid] = {};
|
||||
}
|
||||
metadata[appName][rid] = data;
|
||||
metadata[appName][rid] = val;
|
||||
});
|
||||
|
||||
state.associations = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
remove(json: MetadataUpdate, state: S) {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (appName in metadata && rid in metadata[appName]) {
|
||||
delete metadata[appName][rid];
|
||||
}
|
||||
state.associations = metadata;
|
||||
}
|
||||
state.associations = metadata;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
const add = (json: MetadataUpdate, state: MetadataState): MetadataState => {
|
||||
const data = _.get(json, 'add', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const appPath = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
if (!(appPath in metadata[appName])) {
|
||||
metadata[appName][appPath] = {};
|
||||
}
|
||||
metadata[appName][appPath] = data;
|
||||
|
||||
state.associations = metadata;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
const update = (json: MetadataUpdate, state: MetadataState): MetadataState => {
|
||||
const data = _.get(json, 'update-metadata', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (!(appName in metadata)) {
|
||||
metadata[appName] = {};
|
||||
}
|
||||
if (!(rid in metadata[appName])) {
|
||||
metadata[appName][rid] = {};
|
||||
}
|
||||
metadata[appName][rid] = data;
|
||||
|
||||
state.associations = metadata;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
const remove = (json: MetadataUpdate, state: MetadataState): MetadataState => {
|
||||
const data = _.get(json, 'remove', false);
|
||||
if (data) {
|
||||
const metadata = state.associations;
|
||||
const appName = data['app-name'];
|
||||
const rid = data.resource;
|
||||
|
||||
if (appName in metadata && rid in metadata[appName]) {
|
||||
delete metadata[appName][rid];
|
||||
}
|
||||
state.associations = metadata;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ const useContactState = create<ContactState>(persist((set, get) => ({
|
||||
// },
|
||||
set: fn => stateSetter(fn, set)
|
||||
}), {
|
||||
blacklist: ['nackedContacts'],
|
||||
name: 'LandscapeContactState'
|
||||
}));
|
||||
|
||||
|
76
pkg/interface/src/logic/state/metadata.tsx
Normal file
76
pkg/interface/src/logic/state/metadata.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
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";
|
||||
|
||||
export const METADATA_MAX_PREVIEW_WAIT = 150000;
|
||||
|
||||
export interface MetadataState extends State {
|
||||
associations: Associations;
|
||||
// preview: (group: string) => Promise<MetadataUpdatePreview>;
|
||||
set: (fn: (state: MetadataState) => void) => void;
|
||||
};
|
||||
|
||||
const useMetadataState = create<MetadataState>(persist((set, get) => ({
|
||||
associations: { groups: {}, graph: {}, contacts: {}, chat: {}, link: {}, publish: {} },
|
||||
// preview: async (group): Promise<MetadataUpdatePreview> => {
|
||||
// return new Promise<MetadataUpdatePreview>((resolve, reject) => {
|
||||
// const api = useApi();
|
||||
// let done = false;
|
||||
|
||||
// setTimeout(() => {
|
||||
// if (done) {
|
||||
// return;
|
||||
// }
|
||||
// done = true;
|
||||
// reject(new Error('offline'));
|
||||
// }, METADATA_MAX_PREVIEW_WAIT);
|
||||
|
||||
// api.subscribe({
|
||||
// app: 'metadata-pull-hook',
|
||||
// path: `/preview${group}`,
|
||||
// // TODO type this message?
|
||||
// event: (message) => {
|
||||
// if ('metadata-hook-update' in message) {
|
||||
// done = true;
|
||||
// const update = message['metadata-hook-update'].preview as MetadataUpdatePreview;
|
||||
// resolve(update);
|
||||
// } else {
|
||||
// done = true;
|
||||
// reject(new Error('no-permissions'));
|
||||
// }
|
||||
// // TODO how to delete this subscription? Perhaps return the susbcription ID as the second parameter of all the handlers
|
||||
// },
|
||||
// err: (error) => {
|
||||
// console.error(error);
|
||||
// reject(error);
|
||||
// },
|
||||
// quit: () => {
|
||||
// if (!done) {
|
||||
// reject(new Error('offline'));
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
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 };
|
@ -102,7 +102,7 @@ export default class GlobalStore extends BaseStore<StoreState> {
|
||||
this.pastActions[tag] = [data[tag], ...oldActions.slice(0,14)];
|
||||
|
||||
this.inviteReducer.reduce(data);
|
||||
this.metadataReducer.reduce(data, this.state);
|
||||
this.metadataReducer.reduce(data);
|
||||
this.localReducer.reduce(data, this.state);
|
||||
this.s3Reducer.reduce(data, this.state);
|
||||
this.groupReducer.reduce(data);
|
||||
|
@ -154,7 +154,6 @@ class App extends React.Component {
|
||||
<ErrorBoundary>
|
||||
<StatusBarWithRouter
|
||||
props={this.props}
|
||||
associations={associations}
|
||||
ourContact={ourContact}
|
||||
api={this.api}
|
||||
connection={this.state.connection}
|
||||
|
@ -165,7 +165,6 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
contacts : modifiedContacts
|
||||
}
|
||||
association={props.association}
|
||||
associations={props.associations}
|
||||
group={group}
|
||||
ship={owner}
|
||||
station={station}
|
||||
|
@ -131,8 +131,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
api,
|
||||
highlighted,
|
||||
fontSize,
|
||||
groups,
|
||||
associations
|
||||
} = this.props;
|
||||
|
||||
let { renderSigil } = this.props;
|
||||
@ -174,8 +172,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
scrollWindow,
|
||||
highlighted,
|
||||
fontSize,
|
||||
associations,
|
||||
groups
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
@ -221,8 +217,6 @@ export const MessageAuthor = ({
|
||||
measure,
|
||||
group,
|
||||
api,
|
||||
associations,
|
||||
groups,
|
||||
history,
|
||||
scrollWindow,
|
||||
...rest
|
||||
@ -366,8 +360,6 @@ export const Message = ({
|
||||
measure,
|
||||
group,
|
||||
api,
|
||||
associations,
|
||||
groups,
|
||||
scrollWindow,
|
||||
timestampHover,
|
||||
...rest
|
||||
@ -396,7 +388,6 @@ export const Message = ({
|
||||
case 'text':
|
||||
return (
|
||||
<TextContent
|
||||
associations={associations}
|
||||
measure={measure}
|
||||
api={api}
|
||||
fontSize={1}
|
||||
|
@ -38,7 +38,6 @@ type ChatWindowProps = RouteComponentProps<{
|
||||
station: any;
|
||||
api: GlobalApi;
|
||||
scrollTo?: number;
|
||||
associations: Associations;
|
||||
};
|
||||
|
||||
interface ChatWindowState {
|
||||
|
@ -134,7 +134,6 @@ export default function TextContent(props) {
|
||||
measure={props.measure}
|
||||
resource={resource}
|
||||
api={props.api}
|
||||
associations={props.associations}
|
||||
pl='2'
|
||||
border='1'
|
||||
borderRadius='2'
|
||||
|
@ -3,55 +3,52 @@ import { Switch, Route } from 'react-router-dom';
|
||||
import { Center, Text } from "@tlon/indigo-react";
|
||||
import { deSig } from '~/logic/lib/util';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const GraphApp = (props) => {
|
||||
const associations= useMetadataState(state => state.associations);
|
||||
const graphKeys = useGraphState(state => state.graphKeys);
|
||||
|
||||
const { api } = this.props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/~graph/join/ship/:ship/:name/:module?"
|
||||
render={ (props) => {
|
||||
const resource =
|
||||
`${deSig(props.match.params.ship)}/${props.match.params.name}`;
|
||||
const { ship, name } = props.match.params;
|
||||
const path = `/ship/~${deSig(ship)}/${name}`;
|
||||
const association = associations.graph[path];
|
||||
|
||||
|
||||
export default class GraphApp extends PureComponent {
|
||||
render() {
|
||||
const { props } = this;
|
||||
const associations =
|
||||
props.associations ? props.associations : { graph: {}, contacts: {} };
|
||||
const graphKeys = useGraphState(state => state.graphKeys);
|
||||
|
||||
const { api } = this.props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/~graph/join/ship/:ship/:name/:module?"
|
||||
render={ (props) => {
|
||||
const resource =
|
||||
`${deSig(props.match.params.ship)}/${props.match.params.name}`;
|
||||
const { ship, name } = props.match.params;
|
||||
const path = `/ship/~${deSig(ship)}/${name}`;
|
||||
const association = associations.graph[path];
|
||||
|
||||
|
||||
const autoJoin = () => {
|
||||
try {
|
||||
api.graph.joinGraph(
|
||||
`~${deSig(props.match.params.ship)}`,
|
||||
props.match.params.name
|
||||
);
|
||||
|
||||
|
||||
} catch(err) {
|
||||
setTimeout(autoJoin, 2000);
|
||||
}
|
||||
};
|
||||
|
||||
if(!graphKeys.has(resource)) {
|
||||
autoJoin();
|
||||
} else if(!!association) {
|
||||
props.history.push(`/~landscape/home/resource/${association.metadata.module}${path}`);
|
||||
const autoJoin = () => {
|
||||
try {
|
||||
api.graph.joinGraph(
|
||||
`~${deSig(props.match.params.ship)}`,
|
||||
props.match.params.name
|
||||
);
|
||||
|
||||
|
||||
} catch(err) {
|
||||
setTimeout(autoJoin, 2000);
|
||||
}
|
||||
return (
|
||||
<Center width="100%" height="100%">
|
||||
<Text fontSize={1}>Redirecting...</Text>
|
||||
</Center>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if(!graphKeys.has(resource)) {
|
||||
autoJoin();
|
||||
} else if(!!association) {
|
||||
props.history.push(`/~landscape/home/resource/${association.metadata.module}${path}`);
|
||||
}
|
||||
return (
|
||||
<Center width="100%" height="100%">
|
||||
<Text fontSize={1}>Redirecting...</Text>
|
||||
</Center>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
export default GraphApp;
|
@ -197,7 +197,7 @@ export default function LaunchApp(props) {
|
||||
<JoinGroup {...props} />
|
||||
</ModalButton>
|
||||
|
||||
<Groups associations={props.associations} />
|
||||
<Groups />
|
||||
</Box>
|
||||
<Box alignSelf="flex-start" display={["block", "none"]}>{hashBox}</Box>
|
||||
</ScrollbarLessBox>
|
||||
|
@ -11,10 +11,9 @@ import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import { TUTORIAL_HOST, TUTORIAL_GROUP } from '~/logic/lib/tutorialModal';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
interface GroupsProps {
|
||||
associations: Associations;
|
||||
}
|
||||
interface GroupsProps {}
|
||||
|
||||
const sortGroupsAlph = (a: Association, b: Association) =>
|
||||
alphabeticalOrder(a.metadata.title, b.metadata.title);
|
||||
@ -36,9 +35,10 @@ const getGraphNotifications = (associations: Associations, unreads: Unreads) =>
|
||||
)(associations.graph);
|
||||
|
||||
export default function Groups(props: GroupsProps & Parameters<typeof Box>[0]) {
|
||||
const { associations, inbox, ...boxProps } = props;
|
||||
const { inbox, ...boxProps } = props;
|
||||
const unreads = useHarkState(state => state.unreads);
|
||||
const groupState = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const groups = Object.values(associations?.groups || {})
|
||||
.filter(e => e?.group in groupState)
|
||||
|
@ -14,6 +14,7 @@ import { Comments } from '~/views/components/Comments';
|
||||
import './css/custom.css';
|
||||
import { Association } from '@urbit/api/metadata';
|
||||
import useGraphState from '~/logic/state/graph';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const emptyMeasure = () => {};
|
||||
|
||||
@ -29,13 +30,13 @@ export function LinkResource(props: LinkResourceProps) {
|
||||
api,
|
||||
baseUrl,
|
||||
groups,
|
||||
associations,
|
||||
s3,
|
||||
} = props;
|
||||
|
||||
const rid = association.resource;
|
||||
|
||||
const relativePath = (p: string) => `${baseUrl}/resource/link${rid}${p}`;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const [, , ship, name] = rid.split('/');
|
||||
const resourcePath = `${ship.slice(1)}/${name}`;
|
||||
|
@ -233,7 +233,6 @@ export function GraphNotification(props: {
|
||||
read: boolean;
|
||||
time: number;
|
||||
timebox: BigInteger;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
}) {
|
||||
const { contents, index, read, time, api, timebox } = props;
|
||||
@ -265,7 +264,6 @@ export function GraphNotification(props: {
|
||||
channel={graph}
|
||||
group={group}
|
||||
description={desc}
|
||||
associations={props.associations}
|
||||
/>
|
||||
<Box flexGrow={1} width='100%' pl={5} gridArea='main'>
|
||||
{_.map(contents, (content, idx) => (
|
||||
|
@ -41,12 +41,11 @@ interface GroupNotificationProps {
|
||||
read: boolean;
|
||||
time: number;
|
||||
timebox: BigInteger;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
export function GroupNotification(props: GroupNotificationProps): ReactElement {
|
||||
const { contents, index, read, time, api, timebox, associations } = props;
|
||||
const { contents, index, read, time, api, timebox } = props;
|
||||
|
||||
const authors = _.flatten(_.map(contents, getGroupUpdateParticipants));
|
||||
|
||||
@ -70,7 +69,6 @@ export function GroupNotification(props: GroupNotificationProps): ReactElement {
|
||||
group={group}
|
||||
authors={authors}
|
||||
description={desc}
|
||||
associations={associations}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import { PropFunc } from '~/types/util';
|
||||
import { useShowNickname } from '~/logic/lib/util';
|
||||
import Timestamp from '~/views/components/Timestamp';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const Text = (props: PropFunc<typeof Text>) => (
|
||||
<NormalText fontWeight="500" {...props} />
|
||||
@ -39,9 +40,9 @@ export function Header(props: {
|
||||
moduleIcon?: string;
|
||||
time: number;
|
||||
read: boolean;
|
||||
associations: Associations;
|
||||
} & PropFunc<typeof Row> ): ReactElement {
|
||||
const { description, channel, moduleIcon, read } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const authors = _.uniq(props.authors);
|
||||
|
||||
@ -65,11 +66,11 @@ export function Header(props: {
|
||||
|
||||
const time = moment(props.time).format('HH:mm');
|
||||
const groupTitle =
|
||||
props.associations.groups?.[props.group]?.metadata?.title;
|
||||
associations.groups?.[props.group]?.metadata?.title;
|
||||
|
||||
const app = 'graph';
|
||||
const channelTitle =
|
||||
(channel && props.associations?.[app]?.[channel]?.metadata?.title) ||
|
||||
(channel && associations?.[app]?.[channel]?.metadata?.title) ||
|
||||
channel;
|
||||
|
||||
return (
|
||||
|
@ -48,11 +48,10 @@ export default function Inbox(props: {
|
||||
archive: Notifications;
|
||||
showArchive?: boolean;
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
filter: string[];
|
||||
pendingJoin: JoinRequests;
|
||||
}) {
|
||||
const { api, associations } = props;
|
||||
const { api } = props;
|
||||
useEffect(() => {
|
||||
let seen = false;
|
||||
setTimeout(() => {
|
||||
@ -117,7 +116,7 @@ export default function Inbox(props: {
|
||||
|
||||
return (
|
||||
<Col ref={scrollRef} position="relative" height="100%" overflowY="auto">
|
||||
<Invites pendingJoin={props.pendingJoin} api={api} associations={associations} />
|
||||
<Invites pendingJoin={props.pendingJoin} api={api} />
|
||||
{[...notificationsByDayMap.keys()].sort().reverse().map((day, index) => {
|
||||
const timeboxes = notificationsByDayMap.get(day)!;
|
||||
return timeboxes.length > 0 && (
|
||||
@ -126,7 +125,6 @@ export default function Inbox(props: {
|
||||
label={day === 'latest' ? 'Today' : moment(day).calendar(null, calendar)}
|
||||
timeboxes={timeboxes}
|
||||
archive={Boolean(props.showArchive)}
|
||||
associations={props.associations}
|
||||
api={api}
|
||||
/>
|
||||
);
|
||||
@ -161,7 +159,6 @@ function DaySection({
|
||||
label,
|
||||
archive,
|
||||
timeboxes,
|
||||
associations,
|
||||
api,
|
||||
}) {
|
||||
const lent = timeboxes.map(([,nots]) => nots.length).reduce(f.add, 0);
|
||||
@ -186,7 +183,6 @@ function DaySection({
|
||||
)}
|
||||
<Notification
|
||||
api={api}
|
||||
associations={associations}
|
||||
notification={not}
|
||||
archived={archive}
|
||||
time={date}
|
||||
|
@ -11,7 +11,6 @@ import useInviteState from '~/logic/state/invite';
|
||||
|
||||
interface InvitesProps {
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
pendingJoin: JoinRequests;
|
||||
}
|
||||
|
||||
@ -53,7 +52,6 @@ export function Invites(props: InvitesProps): ReactElement {
|
||||
return (
|
||||
<InviteItem
|
||||
key={resource}
|
||||
associations={props.associations}
|
||||
resource={resource}
|
||||
pendingJoin={pendingJoin}
|
||||
api={api}
|
||||
@ -71,7 +69,6 @@ export function Invites(props: InvitesProps): ReactElement {
|
||||
uid={uid}
|
||||
pendingJoin={pendingJoin}
|
||||
resource={resource}
|
||||
associations={props.associations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import useHarkState from '~/logic/state/hark';
|
||||
interface NotificationProps {
|
||||
notification: IndexedNotification;
|
||||
time: BigInteger;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
archived: boolean;
|
||||
}
|
||||
@ -136,7 +135,6 @@ export function Notification(props: NotificationProps) {
|
||||
archived={archived}
|
||||
timebox={props.time}
|
||||
time={time}
|
||||
associations={associations}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
@ -154,7 +152,6 @@ export function Notification(props: NotificationProps) {
|
||||
timebox={props.time}
|
||||
archived={archived}
|
||||
time={time}
|
||||
associations={associations}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ import { FormikOnBlur } from '~/views/components/FormikOnBlur';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const baseUrl = '/~notifications';
|
||||
|
||||
@ -40,6 +41,7 @@ export default function NotificationsScreen(props: any): ReactElement {
|
||||
const relativePath = (p: string) => baseUrl + p;
|
||||
|
||||
const [filter, setFilter] = useState<NotificationFilter>({ groups: [] });
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const onSubmit = async ({ groups } : NotificationFilter) => {
|
||||
setFilter({ groups });
|
||||
};
|
||||
@ -50,7 +52,7 @@ export default function NotificationsScreen(props: any): ReactElement {
|
||||
filter.groups.length === 0
|
||||
? 'All'
|
||||
: filter.groups
|
||||
.map(g => props.associations?.groups?.[g]?.metadata?.title)
|
||||
.map(g => associations.groups?.[g]?.metadata?.title)
|
||||
.join(', ');
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
useTutorialModal('notifications', true, anchorRef.current);
|
||||
@ -124,7 +126,6 @@ export default function NotificationsScreen(props: any): ReactElement {
|
||||
id="groups"
|
||||
label="Filter Groups"
|
||||
caption="Only show notifications from this group"
|
||||
associations={props.associations}
|
||||
/>
|
||||
</FormikOnBlur>
|
||||
</Col>
|
||||
|
@ -121,7 +121,7 @@ export function EditProfile(props: any): ReactElement {
|
||||
</Col>
|
||||
</Row>
|
||||
<Checkbox mb={3} id="isPublic" label="Public Profile" />
|
||||
<GroupSearch label="Pinned Groups" id="groups" associations={props.associations} publicOnly />
|
||||
<GroupSearch label="Pinned Groups" id="groups" publicOnly />
|
||||
<AsyncButton primary loadingText="Updating..." border mt={3}>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
|
@ -110,7 +110,6 @@ export function Profile(props: any): ReactElement {
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
associations={props.associations}
|
||||
/>
|
||||
) : (
|
||||
<ViewProfile
|
||||
@ -118,7 +117,6 @@ export function Profile(props: any): ReactElement {
|
||||
nacked={nacked}
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
associations={props.associations}
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { ReactElement } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
@ -20,7 +20,7 @@ export function ViewProfile(props: any): ReactElement {
|
||||
const { hideNicknames } = useLocalState(({ hideNicknames }) => ({
|
||||
hideNicknames
|
||||
}));
|
||||
const { api, contact, nacked, ship, associations, groups } = props;
|
||||
const { api, contact, nacked, ship } = props;
|
||||
|
||||
const isPublic = useContactState(state => state.isContactPublic);
|
||||
|
||||
@ -66,7 +66,6 @@ export function ViewProfile(props: any): ReactElement {
|
||||
<GroupLink
|
||||
api={api}
|
||||
resource={g}
|
||||
associations={associations}
|
||||
measure={() => {}}
|
||||
/>
|
||||
))}
|
||||
|
@ -39,7 +39,6 @@ export default function ProfileScreen(props: any) {
|
||||
<Profile
|
||||
ship={ship}
|
||||
hasLoaded={Object.keys(contacts).length !== 0}
|
||||
associations={props.associations}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
|
@ -24,7 +24,6 @@ export function PublishResource(props: PublishResourceProps) {
|
||||
api={api}
|
||||
ship={ship}
|
||||
book={book}
|
||||
associations={props.associations}
|
||||
association={association}
|
||||
rootUrl={baseUrl}
|
||||
baseUrl={`${baseUrl}/resource/publish/ship/${ship}/${book}`}
|
||||
|
@ -16,13 +16,12 @@ interface NotebookProps {
|
||||
book: string;
|
||||
graph: Graph;
|
||||
association: Association;
|
||||
associations: Associations;
|
||||
baseUrl: string;
|
||||
rootUrl: string;
|
||||
unreads: Unreads;
|
||||
}
|
||||
|
||||
export function Notebook(props: NotebookProps & RouteComponentProps): ReactElement {
|
||||
export function Notebook(props: NotebookProps & RouteComponentProps): ReactElement | null {
|
||||
const {
|
||||
ship,
|
||||
book,
|
||||
|
@ -27,7 +27,6 @@ interface NotebookRoutesProps {
|
||||
baseUrl: string;
|
||||
rootUrl: string;
|
||||
association: Association;
|
||||
associations: Associations;
|
||||
s3: S3State;
|
||||
}
|
||||
|
||||
|
@ -9,21 +9,22 @@ import { JoinGroup } from '../landscape/components/JoinGroup';
|
||||
import { useModal } from '~/logic/lib/useModal';
|
||||
import { GroupSummary } from '../landscape/components/GroupSummary';
|
||||
import { PropFunc } from '~/types';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
export function GroupLink(
|
||||
props: {
|
||||
api: GlobalApi;
|
||||
resource: string;
|
||||
associations: Associations;
|
||||
measure: () => void;
|
||||
detailed?: boolean;
|
||||
} & PropFunc<typeof Row>
|
||||
): ReactElement {
|
||||
const { resource, api, associations, measure, ...rest } = props;
|
||||
const { resource, api, measure, ...rest } = props;
|
||||
const name = resource.slice(6);
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const joined = resource in props.associations.groups;
|
||||
const joined = resource in associations.groups;
|
||||
|
||||
const { modal, showModal } = useModal({
|
||||
modal:
|
||||
@ -37,7 +38,6 @@ export function GroupLink(
|
||||
</Box>
|
||||
) : (
|
||||
<JoinGroup
|
||||
associations={associations}
|
||||
api={api}
|
||||
autojoin={name}
|
||||
/>
|
||||
|
@ -19,12 +19,12 @@ import { Associations, Association } from '@urbit/api/metadata';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { DropdownSearch } from './DropdownSearch';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
interface GroupSearchProps<I extends string> {
|
||||
disabled?: boolean;
|
||||
adminOnly?: boolean;
|
||||
publicOnly?: boolean;
|
||||
associations: Associations;
|
||||
label: string;
|
||||
caption?: string;
|
||||
id: I;
|
||||
@ -87,34 +87,35 @@ export function GroupSearch<I extends string, V extends FormValues<I>>(props: Gr
|
||||
const touched = touchedFields[id] ?? false;
|
||||
const error = _.compact(errors[id] as string[]);
|
||||
const groupState = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const groups: Association[] = useMemo(() => {
|
||||
if (props.adminOnly) {
|
||||
return Object.values(
|
||||
Object.keys(props.associations?.groups)
|
||||
Object.keys(associations.groups)
|
||||
.filter(
|
||||
e => roleForShip(groupState[e], window.ship) === 'admin'
|
||||
)
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = props.associations?.groups[key];
|
||||
obj[key] = associations.groups[key];
|
||||
return obj;
|
||||
}, {}) || {}
|
||||
);
|
||||
} else if (props.publicOnly) {
|
||||
return Object.values(
|
||||
Object.keys(props.associations?.groups)
|
||||
Object.keys(associations.groups)
|
||||
.filter(
|
||||
e => groupState?.[e]?.policy?.open
|
||||
)
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = props.associations?.groups[key];
|
||||
obj[key] = associations.groups[key];
|
||||
return obj;
|
||||
}, {}) || {}
|
||||
);
|
||||
} else {
|
||||
return Object.values(props.associations?.groups || {});
|
||||
return Object.values(associations.groups || {});
|
||||
}
|
||||
}, [props.associations?.groups]);
|
||||
}, [associations.groups]);
|
||||
|
||||
return (
|
||||
<FieldArray
|
||||
@ -156,7 +157,7 @@ export function GroupSearch<I extends string, V extends FormValues<I>>(props: Gr
|
||||
{value?.length > 0 && (
|
||||
value.map((e, idx: number) => {
|
||||
const { title } =
|
||||
props.associations.groups?.[e]?.metadata || {};
|
||||
associations.groups?.[e]?.metadata || {};
|
||||
return (
|
||||
<Row
|
||||
key={e}
|
||||
|
@ -19,12 +19,11 @@ import { InviteSkeleton } from './InviteSkeleton';
|
||||
import { JoinSkeleton } from './JoinSkeleton';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
interface InviteItemProps {
|
||||
invite?: Invite;
|
||||
resource: string;
|
||||
associations: Associations;
|
||||
|
||||
pendingJoin: JoinRequests;
|
||||
app?: string;
|
||||
uid?: string;
|
||||
@ -33,11 +32,12 @@ interface InviteItemProps {
|
||||
|
||||
export function InviteItem(props: InviteItemProps) {
|
||||
const [preview, setPreview] = useState<MetadataUpdatePreview | null>(null);
|
||||
const { associations, pendingJoin, invite, resource, uid, app, api } = props;
|
||||
const { pendingJoin, invite, resource, uid, app, api } = props;
|
||||
const { ship, name } = resourceFromPath(resource);
|
||||
const waiter = useWaitForProps(props, 50000);
|
||||
const status = pendingJoin[resource];
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const history = useHistory();
|
||||
const inviteAccept = useCallback(async () => {
|
||||
|
@ -21,9 +21,9 @@ import useGroupState from '~/logic/state/groups';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useInviteState from '~/logic/state/invite';
|
||||
import useLaunchState from '~/logic/state/launch';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
interface OmniboxProps {
|
||||
associations: Associations;
|
||||
tiles: {
|
||||
[app: string]: Tile;
|
||||
};
|
||||
@ -55,6 +55,7 @@ export function Omnibox(props: OmniboxProps) {
|
||||
}, [contactState, query]);
|
||||
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const index = useMemo(() => {
|
||||
const selectedGroup = location.pathname.startsWith('/~landscape/ship/')
|
||||
@ -62,12 +63,12 @@ export function Omnibox(props: OmniboxProps) {
|
||||
: null;
|
||||
return makeIndex(
|
||||
contacts,
|
||||
props.associations,
|
||||
associations,
|
||||
tiles,
|
||||
selectedGroup,
|
||||
groups
|
||||
);
|
||||
}, [location.pathname, contacts, props.associations, groups, tiles]);
|
||||
}, [location.pathname, contacts, associations, groups, tiles]);
|
||||
|
||||
const onOutsideClick = useCallback(() => {
|
||||
props.show && props.toggle();
|
||||
|
@ -7,16 +7,17 @@ import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
||||
import { getModuleIcon } from '~/logic/lib/util';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import { resourceFromPath, roleForShip } from '~/logic/lib/group';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
interface GroupChannelSettingsProps {
|
||||
group: Group;
|
||||
association: Association;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
export function GroupChannelSettings(props: GroupChannelSettingsProps) {
|
||||
const { api, associations, association, group } = props;
|
||||
const { api, association, group } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const channels = Object.values(associations.graph).filter(
|
||||
({ group }) => association.group === group
|
||||
);
|
||||
|
@ -20,7 +20,6 @@ const Section = ({ children }) => (
|
||||
interface GroupSettingsProps {
|
||||
group: Group;
|
||||
association: Association;
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
notificationsGroupConfig: GroupNotificationsConfig;
|
||||
s3: S3State;
|
||||
|
@ -14,6 +14,7 @@ import { Dropdown } from '~/views/components/Dropdown';
|
||||
import { getTitleFromWorkspace } from '~/logic/lib/workspace';
|
||||
import { MetadataIcon } from './MetadataIcon';
|
||||
import { Workspace } from '~/types/workspace';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
||||
<Link to={to}>
|
||||
@ -31,10 +32,11 @@ const GroupSwitcherItem = ({ to, children, bottom = false, ...rest }) => (
|
||||
);
|
||||
|
||||
function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
||||
const { associations, recent } = props;
|
||||
const { recent } = props;
|
||||
if (recent.length < 2) {
|
||||
return null;
|
||||
}
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
return (
|
||||
<Col borderBottom={1} borderBottomColor="lightGray" p={1}>
|
||||
@ -70,13 +72,13 @@ function RecentGroups(props: { recent: string[]; associations: Associations }) {
|
||||
}
|
||||
|
||||
export function GroupSwitcher(props: {
|
||||
associations: Associations;
|
||||
workspace: Workspace;
|
||||
baseUrl: string;
|
||||
recentGroups: string[];
|
||||
isAdmin: any;
|
||||
}) {
|
||||
const { associations, workspace, isAdmin } = props;
|
||||
const { workspace, isAdmin } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const title = getTitleFromWorkspace(associations, workspace);
|
||||
const metadata = (workspace.type === 'home' || workspace.type === 'messages')
|
||||
? undefined
|
||||
@ -136,7 +138,6 @@ export function GroupSwitcher(props: {
|
||||
</GroupSwitcherItem>}
|
||||
<RecentGroups
|
||||
recent={props.recentGroups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
<GroupSwitcherItem to="/~landscape/new">
|
||||
<Icon mr="2" color="gray" icon="CreateGroup" />
|
||||
|
@ -21,7 +21,6 @@ interface FormSchema {
|
||||
|
||||
interface GroupifyFormProps {
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
association: Association;
|
||||
}
|
||||
|
||||
@ -78,7 +77,6 @@ export function GroupifyForm(props: GroupifyFormProps) {
|
||||
id="group"
|
||||
label="Group"
|
||||
caption="Optionally, if you have admin privileges, you can add this channel to a group, or leave this blank to place the channel in its own group"
|
||||
associations={props.associations}
|
||||
adminOnly
|
||||
maxLength={1}
|
||||
/>
|
||||
|
@ -30,6 +30,7 @@ import { Workspace } from '~/types/workspace';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
type GroupsPaneProps = StoreState & {
|
||||
baseUrl: string;
|
||||
@ -38,7 +39,8 @@ type GroupsPaneProps = StoreState & {
|
||||
};
|
||||
|
||||
export function GroupsPane(props: GroupsPaneProps) {
|
||||
const { baseUrl, associations, api, workspace } = props;
|
||||
const { baseUrl, api, workspace } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
const notificationsCount = useHarkState(state => state.notificationsCount);
|
||||
const relativePath = (path: string) => baseUrl + path;
|
||||
@ -77,7 +79,6 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
group={group!}
|
||||
api={api}
|
||||
s3={props.s3}
|
||||
associations={associations}
|
||||
|
||||
{...routeProps}
|
||||
baseUrl={baseUrl}
|
||||
@ -180,7 +181,6 @@ export function GroupsPane(props: GroupsPaneProps) {
|
||||
{...routeProps}
|
||||
api={api}
|
||||
baseUrl={baseUrl}
|
||||
associations={associations}
|
||||
group={groupPath}
|
||||
workspace={workspace}
|
||||
/>
|
||||
|
@ -23,6 +23,7 @@ import { getModuleIcon } from '~/logic/lib/util';
|
||||
import { FormError } from '~/views/components/FormError';
|
||||
import { GroupSummary } from './GroupSummary';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
group: Yup.string()
|
||||
@ -41,7 +42,6 @@ interface FormSchema {
|
||||
}
|
||||
|
||||
interface JoinGroupProps {
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
autojoin?: string;
|
||||
}
|
||||
@ -59,7 +59,8 @@ function Autojoin(props: { autojoin: string | null }) {
|
||||
}
|
||||
|
||||
export function JoinGroup(props: JoinGroupProps): ReactElement {
|
||||
const { api, autojoin, associations } = props;
|
||||
const { api, autojoin } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const history = useHistory();
|
||||
const initialValues: FormSchema = {
|
||||
|
@ -42,7 +42,6 @@ const formSchema = (members?: string[]) => Yup.object({
|
||||
|
||||
interface NewChannelProps {
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
group?: string;
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ 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 useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
title: Yup.string().required('Group must have a name'),
|
||||
@ -31,7 +32,6 @@ interface FormSchema {
|
||||
}
|
||||
|
||||
interface NewGroupProps {
|
||||
associations: Associations;
|
||||
api: GlobalApi;
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps): ReactEleme
|
||||
|
||||
const waiter = useWaitForProps(props);
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (values: FormSchema, actions: FormikHelpers<FormSchema>) => {
|
||||
@ -65,7 +66,7 @@ export function NewGroup(props: NewGroupProps & RouteComponentProps): ReactEleme
|
||||
};
|
||||
await api.groups.create(name, policy, title, description);
|
||||
const path = `/ship/~${window.ship}/${name}`;
|
||||
await waiter(({ associations }) => {
|
||||
await waiter(() => {
|
||||
return path in groups && path in associations.groups;
|
||||
});
|
||||
|
||||
|
@ -22,7 +22,6 @@ export function PopoverRoutes(
|
||||
baseUrl: string;
|
||||
group: Group;
|
||||
association: Association;
|
||||
associations: Associations;
|
||||
s3: S3State;
|
||||
api: GlobalApi;
|
||||
notificationsGroupConfig: GroupNotificationsConfig;
|
||||
@ -125,8 +124,6 @@ export function PopoverRoutes(
|
||||
group={props.group}
|
||||
association={props.association}
|
||||
api={props.api}
|
||||
notificationsGroupConfig={props.notificationsGroupConfig}
|
||||
associations={props.associations}
|
||||
s3={props.s3}
|
||||
/>
|
||||
)}
|
||||
|
@ -14,6 +14,7 @@ import { ChannelPopoverRoutes } from './ChannelPopoverRoutes';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useContactState from '~/logic/state/contacts';
|
||||
import useHarkState from '~/logic/state/hark';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
type ResourceProps = StoreState & {
|
||||
association: Association;
|
||||
@ -25,6 +26,7 @@ export function Resource(props: ResourceProps): ReactElement {
|
||||
const { association, api, notificationsGraphConfig } = props;
|
||||
const groups = useGroupState(state => state.groups);
|
||||
const notificationsCount = useHarkState(state => state.notificationsCount);
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const contacts = useContactState(state => state.contacts);
|
||||
const app = association.metadata.module || association['app-name'];
|
||||
const rid = association.resource;
|
||||
@ -34,8 +36,8 @@ export function Resource(props: ResourceProps): ReactElement {
|
||||
const skelProps = { api, association, groups, contacts };
|
||||
let title = props.association.metadata.title;
|
||||
if ('workspace' in props) {
|
||||
if ('group' in props.workspace && props.workspace.group in props.associations.groups) {
|
||||
title = `${props.associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
if ('group' in props.workspace && props.workspace.group in associations.groups) {
|
||||
title = `${associations.groups[props.workspace.group].metadata.title} - ${props.association.metadata.title}`;
|
||||
}
|
||||
}
|
||||
return (
|
||||
@ -66,7 +68,6 @@ export function Resource(props: ResourceProps): ReactElement {
|
||||
api={props.api}
|
||||
baseUrl={relativePath('')}
|
||||
rootUrl={props.baseUrl}
|
||||
notificationsGraphConfig={notificationsGraphConfig}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
@ -21,6 +21,7 @@ import { SidebarList } from './SidebarList';
|
||||
import { roleForShip } from '~/logic/lib/group';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
import useGroupState from '~/logic/state/groups';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
const ScrollbarLessCol = styled(Col)`
|
||||
scrollbar-width: none !important;
|
||||
@ -34,7 +35,6 @@ interface SidebarProps {
|
||||
children: ReactNode;
|
||||
recentGroups: string[];
|
||||
api: GlobalApi;
|
||||
associations: Associations;
|
||||
selected?: string;
|
||||
selectedGroup?: string;
|
||||
includeUnmanaged?: boolean;
|
||||
@ -44,8 +44,9 @@ interface SidebarProps {
|
||||
workspace: Workspace;
|
||||
}
|
||||
|
||||
export function Sidebar(props: SidebarProps): ReactElement {
|
||||
const { associations, selected, workspace } = props;
|
||||
export function Sidebar(props: SidebarProps): ReactElement | null {
|
||||
const { selected, workspace } = props;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const groupPath = getGroupFromWorkspace(workspace);
|
||||
const display = props.mobileHide ? ['none', 'flex'] : 'flex';
|
||||
if (!associations) {
|
||||
@ -83,14 +84,12 @@ export function Sidebar(props: SidebarProps): ReactElement {
|
||||
position="relative"
|
||||
>
|
||||
<GroupSwitcher
|
||||
associations={associations}
|
||||
recentGroups={props.recentGroups}
|
||||
baseUrl={props.baseUrl}
|
||||
isAdmin={isAdmin}
|
||||
workspace={props.workspace}
|
||||
/>
|
||||
<SidebarListHeader
|
||||
associations={associations}
|
||||
baseUrl={props.baseUrl}
|
||||
initialValues={config}
|
||||
handleSubmit={setConfig}
|
||||
@ -101,7 +100,6 @@ export function Sidebar(props: SidebarProps): ReactElement {
|
||||
/>
|
||||
<SidebarList
|
||||
config={config}
|
||||
associations={associations}
|
||||
selected={selected}
|
||||
group={groupPath}
|
||||
apps={props.apps}
|
||||
|
@ -5,6 +5,7 @@ import { alphabeticalOrder } from '~/logic/lib/util';
|
||||
import { SidebarAppConfigs, SidebarListConfig, SidebarSort } from './types';
|
||||
import { SidebarItem } from './SidebarItem';
|
||||
import { Workspace } from '~/types/workspace';
|
||||
import useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
function sidebarSort(
|
||||
associations: AppAssociations,
|
||||
@ -40,24 +41,24 @@ function sidebarSort(
|
||||
export function SidebarList(props: {
|
||||
apps: SidebarAppConfigs;
|
||||
config: SidebarListConfig;
|
||||
associations: Associations;
|
||||
baseUrl: string;
|
||||
group?: string;
|
||||
selected?: string;
|
||||
workspace: Workspace;
|
||||
}): ReactElement {
|
||||
const { selected, group, config, workspace } = props;
|
||||
const associations = { ...props.associations.graph };
|
||||
const associationState = useMetadataState(state => state.associations);
|
||||
const associations = { ...associationState.graph };
|
||||
|
||||
const ordered = Object.keys(associations)
|
||||
.filter((a) => {
|
||||
const assoc = associations[a];
|
||||
if (workspace?.type === 'messages') {
|
||||
return (!(assoc.group in props.associations.groups) && assoc.metadata.module === 'chat');
|
||||
return (!(assoc.group in associationState.groups) && assoc.metadata.module === 'chat');
|
||||
} else {
|
||||
return group
|
||||
? assoc.group === group
|
||||
: (!(assoc.group in props.associations.groups) && assoc.metadata.module !== 'chat');
|
||||
: (!(assoc.group in associationState.groups) && assoc.metadata.module !== 'chat');
|
||||
}
|
||||
})
|
||||
.sort(sidebarSort(associations, props.apps)[config.sortBy]);
|
||||
|
@ -22,11 +22,11 @@ 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 useMetadataState from '~/logic/state/metadata';
|
||||
|
||||
export function SidebarListHeader(props: {
|
||||
api: GlobalApi;
|
||||
initialValues: SidebarListConfig;
|
||||
associations: Associations;
|
||||
baseUrl: string;
|
||||
selected: string;
|
||||
workspace: Workspace;
|
||||
@ -43,8 +43,9 @@ export function SidebarListHeader(props: {
|
||||
|
||||
const groupPath = getGroupFromWorkspace(props.workspace);
|
||||
const role = groupPath && groups?.[groupPath] ? roleForShip(groups[groupPath], window.ship) : undefined;
|
||||
const associations = useMetadataState(state => state.associations);
|
||||
const memberMetadata =
|
||||
groupPath ? props.associations.groups?.[groupPath].metadata.vip === 'member-metadata' : false;
|
||||
groupPath ? associations.groups?.[groupPath].metadata.vip === 'member-metadata' : false;
|
||||
|
||||
const isAdmin = memberMetadata || (role === 'admin') || (props.workspace?.type === 'home') || (props.workspace?.type === 'messages');
|
||||
|
||||
@ -86,7 +87,6 @@ export function SidebarListHeader(props: {
|
||||
<NewChannel
|
||||
api={props.api}
|
||||
history={props.history}
|
||||
associations={props.associations}
|
||||
workspace={props.workspace}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -15,7 +15,6 @@ import useHarkState from '~/logic/state/hark';
|
||||
interface SkeletonProps {
|
||||
children: ReactNode;
|
||||
recentGroups: string[];
|
||||
associations: Associations;
|
||||
linkListening: Set<Path>;
|
||||
selected?: string;
|
||||
selectedApp?: AppName;
|
||||
@ -51,7 +50,6 @@ export function Skeleton(props: SkeletonProps): ReactElement {
|
||||
api={props.api}
|
||||
recentGroups={props.recentGroups}
|
||||
selected={props.selected}
|
||||
associations={props.associations}
|
||||
apps={config}
|
||||
baseUrl={props.baseUrl}
|
||||
mobileHide={props.mobileHide}
|
||||
|
@ -118,7 +118,6 @@ class Landscape extends Component<LandscapeProps, Record<string, never>> {
|
||||
<Body>
|
||||
<Box maxWidth="300px">
|
||||
<NewGroup
|
||||
associations={props.associations}
|
||||
api={props.api}
|
||||
{...routeProps}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user