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