mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-09-21 07:28:30 +03:00
chat: create dm route, restore participants option
This commit is contained in:
parent
a580f1fba4
commit
f01fdf9efa
@ -114,7 +114,6 @@ export function ChatResource(props: ChatResourceProps) {
|
||||
group={group}
|
||||
ship={owner}
|
||||
station={station}
|
||||
allStations={Object.keys(props.inbox)}
|
||||
api={props.api}
|
||||
hideNicknames={props.hideNicknames}
|
||||
hideAvatars={props.hideAvatars}
|
||||
|
@ -46,7 +46,6 @@ interface ChatMessageProps {
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
style?: any;
|
||||
allStations: any;
|
||||
scrollWindow: HTMLDivElement;
|
||||
isLastMessage?: boolean;
|
||||
unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
@ -87,7 +86,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
scrollWindow,
|
||||
isLastMessage,
|
||||
unreadMarkerRef,
|
||||
allStations,
|
||||
history,
|
||||
api
|
||||
} = this.props;
|
||||
@ -118,7 +116,6 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
style,
|
||||
containerClass,
|
||||
isPending,
|
||||
allStations,
|
||||
history,
|
||||
api,
|
||||
scrollWindow
|
||||
@ -165,7 +162,6 @@ interface MessageProps {
|
||||
containerClass: string;
|
||||
isPending: boolean;
|
||||
style: any;
|
||||
allStations: any;
|
||||
measure(element): void;
|
||||
scrollWindow: HTMLDivElement;
|
||||
};
|
||||
@ -182,7 +178,6 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
hideAvatars,
|
||||
remoteContentPolicy,
|
||||
measure,
|
||||
allStations,
|
||||
history,
|
||||
api,
|
||||
scrollWindow
|
||||
@ -218,7 +213,6 @@ export class MessageWithSigil extends PureComponent<MessageProps> {
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={hideNicknames}
|
||||
scrollWindow={scrollWindow}
|
||||
allStations={allStations}
|
||||
history={history}
|
||||
api={api}
|
||||
className="fl pr3 v-top bg-white bg-gray0-d pt1"
|
||||
|
@ -39,7 +39,6 @@ type ChatWindowProps = RouteComponentProps<{
|
||||
group: Group;
|
||||
ship: Patp;
|
||||
station: any;
|
||||
allStations: any;
|
||||
api: GlobalApi;
|
||||
hideNicknames: boolean;
|
||||
hideAvatars: boolean;
|
||||
@ -56,7 +55,7 @@ interface ChatWindowState {
|
||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
||||
private virtualList: VirtualScroller | null;
|
||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
|
||||
INITIALIZATION_MAX_TIME = 1500;
|
||||
|
||||
constructor(props) {
|
||||
@ -68,14 +67,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
initialized: false,
|
||||
lastRead: props.unreadCount ? props.mailboxSize - props.unreadCount : Infinity,
|
||||
};
|
||||
|
||||
|
||||
this.dismissUnread = this.dismissUnread.bind(this);
|
||||
this.scrollToUnread = this.scrollToUnread.bind(this);
|
||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||
this.handleWindowFocus = this.handleWindowFocus.bind(this);
|
||||
this.stayLockedIfActive = this.stayLockedIfActive.bind(this);
|
||||
this.dismissIfLineVisible = this.dismissIfLineVisible.bind(this);
|
||||
|
||||
|
||||
this.virtualList = null;
|
||||
this.unreadMarkerRef = React.createRef();
|
||||
}
|
||||
@ -88,7 +87,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.setState({ initialized: true });
|
||||
}, this.INITIALIZATION_MAX_TIME);
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('blur', this.handleWindowBlur);
|
||||
window.removeEventListener('focus', this.handleWindowFocus);
|
||||
@ -192,10 +191,10 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
}
|
||||
|
||||
this.setState({ fetchPending: true });
|
||||
|
||||
|
||||
start = Math.min(mailboxSize - start, mailboxSize);
|
||||
end = Math.max(mailboxSize - end, 0, start - MAX_BACKLOG_SIZE);
|
||||
|
||||
|
||||
return api.chat
|
||||
.fetchMessages(end, start, station)
|
||||
.finally(() => {
|
||||
@ -224,7 +223,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.dismissUnread();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const {
|
||||
envelopes,
|
||||
@ -243,7 +242,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
remoteContentPolicy,
|
||||
allStations,
|
||||
history
|
||||
} = this.props;
|
||||
|
||||
@ -251,7 +249,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
|
||||
const messages = new Map();
|
||||
let lastMessage = 0;
|
||||
|
||||
|
||||
[...envelopes]
|
||||
.sort((a, b) => a.number - b.number)
|
||||
.forEach(message => {
|
||||
@ -267,8 +265,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
lastMessage = mailboxSize + index;
|
||||
});
|
||||
|
||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, allStations, history, api };
|
||||
|
||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef, history, api };
|
||||
|
||||
return (
|
||||
<>
|
||||
<UnreadNotice
|
||||
|
@ -55,7 +55,7 @@ export class OverlaySigil extends PureComponent {
|
||||
|
||||
render() {
|
||||
const { props, state } = this;
|
||||
const { hideAvatars, allStations } = props;
|
||||
const { hideAvatars } = props;
|
||||
|
||||
const img = (props.contact && (props.contact.avatar !== null) && !hideAvatars)
|
||||
? <img src={props.contact.avatar} height={16} width={16} className="dib" />
|
||||
@ -84,7 +84,6 @@ export class OverlaySigil extends PureComponent {
|
||||
association={props.association}
|
||||
group={props.group}
|
||||
onDismiss={this.profileHide}
|
||||
allStations={allStations}
|
||||
hideAvatars={hideAvatars}
|
||||
hideNicknames={props.hideNicknames}
|
||||
history={props.history}
|
||||
|
@ -12,7 +12,6 @@ export class ProfileOverlay extends PureComponent {
|
||||
|
||||
this.popoverRef = React.createRef();
|
||||
this.onDocumentClick = this.onDocumentClick.bind(this);
|
||||
this.createAndRedirectToDM = this.createAndRedirectToDM.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -25,42 +24,6 @@ export class ProfileOverlay extends PureComponent {
|
||||
document.removeEventListener('touchstart', this.onDocumentClick);
|
||||
}
|
||||
|
||||
createAndRedirectToDM() {
|
||||
const { api, ship, history, allStations } = this.props;
|
||||
const station = `/~${window.ship}/dm--${ship}`;
|
||||
const theirStation = `/~${ship}/dm--${window.ship}`;
|
||||
|
||||
if (allStations.indexOf(station) !== -1) {
|
||||
history.push(`/~landscape/home/resource/chat${station}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allStations.indexOf(theirStation) !== -1) {
|
||||
history.push(`/~landscape/home/resource/chat${theirStation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const groupPath = `/ship/~${window.ship}/dm--${ship}`;
|
||||
const aud = ship !== window.ship ? [`~${ship}`] : [];
|
||||
const title = `${cite(window.ship)} <-> ${cite(ship)}`;
|
||||
|
||||
api.chat.create(
|
||||
title,
|
||||
'',
|
||||
station,
|
||||
groupPath,
|
||||
{ invite: { pending: aud } },
|
||||
aud,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
// TODO: make a pretty loading state
|
||||
setTimeout(() => {
|
||||
history.push(`/~landscape/home/resource/chat${station}`);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
onDocumentClick(event) {
|
||||
const { popoverRef } = this;
|
||||
// Do nothing if clicking ref's element or descendent elements
|
||||
@ -72,7 +35,7 @@ export class ProfileOverlay extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { contact, ship, color, topSpace, bottomSpace, group, association, hideNicknames, hideAvatars, history } = this.props;
|
||||
const { contact, ship, color, topSpace, bottomSpace, group, hideNicknames, hideAvatars, history } = this.props;
|
||||
|
||||
let top, bottom;
|
||||
if (topSpace < OVERLAY_HEIGHT / 2) {
|
||||
@ -132,7 +95,7 @@ export class ProfileOverlay extends PureComponent {
|
||||
)}
|
||||
<Text mono gray>{cite(`~${ship}`)}</Text>
|
||||
{!isOwn && (
|
||||
<Button mt={2} width="100%" style={{ cursor: 'pointer' }} onClick={this.createAndRedirectToDM}>
|
||||
<Button mt={2} width="100%" style={{ cursor: 'pointer' }} onClick={() => history.push(`/~landscape/dm/${ship}`)}>
|
||||
Send Message
|
||||
</Button>
|
||||
)}
|
||||
|
@ -1,38 +1,36 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
ManagedTextInputField as Input,
|
||||
Col,
|
||||
ManagedRadioButtonField as Radio,
|
||||
Text,
|
||||
} from "@tlon/indigo-react";
|
||||
import { Formik, Form } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { AsyncButton } from "~/views/components/AsyncButton";
|
||||
import { FormError } from "~/views/components/FormError";
|
||||
import { RouteComponentProps } from "react-router-dom";
|
||||
import { stringToSymbol } from "~/logic/lib/util";
|
||||
import GroupSearch from "~/views/components/GroupSearch";
|
||||
import { Associations } from "~/types/metadata-update";
|
||||
import { useWaitForProps } from "~/logic/lib/useWaitForProps";
|
||||
import { Notebooks } from "~/types/publish-update";
|
||||
import { Groups } from "~/types/group-update";
|
||||
import { ShipSearch } from "~/views/components/ShipSearch";
|
||||
import { Rolodex } from "~/types";
|
||||
Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Formik, Form } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { AsyncButton } from '~/views/components/AsyncButton';
|
||||
import { FormError } from '~/views/components/FormError';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { stringToSymbol } from '~/logic/lib/util';
|
||||
import { Associations } from '~/types/metadata-update';
|
||||
import { useWaitForProps } from '~/logic/lib/useWaitForProps';
|
||||
import { Groups } from '~/types/group-update';
|
||||
import { ShipSearch } from '~/views/components/ShipSearch';
|
||||
import { Rolodex } from '~/types';
|
||||
|
||||
interface FormSchema {
|
||||
name: string;
|
||||
description: string;
|
||||
ships: string[];
|
||||
type: "chat" | "publish" | "links";
|
||||
type: 'chat' | 'publish' | 'links';
|
||||
}
|
||||
|
||||
const formSchema = Yup.object({
|
||||
name: Yup.string().required("Channel must have a name"),
|
||||
name: Yup.string().required('Channel must have a name'),
|
||||
description: Yup.string(),
|
||||
ships: Yup.array(Yup.string()),
|
||||
type: Yup.string().required("Must choose channel type"),
|
||||
type: Yup.string().required('Must choose channel type')
|
||||
});
|
||||
|
||||
interface NewChannelProps {
|
||||
@ -43,7 +41,6 @@ interface NewChannelProps {
|
||||
group?: string;
|
||||
}
|
||||
|
||||
|
||||
export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||
const { history, api, group, workspace } = props;
|
||||
|
||||
@ -54,7 +51,7 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||
try {
|
||||
const { name, description, type, ships } = values;
|
||||
switch (type) {
|
||||
case "chat":
|
||||
case 'chat':
|
||||
const appPath = `/~${window.ship}/${resId}`;
|
||||
const groupPath = group || `/ship${appPath}`;
|
||||
|
||||
@ -63,46 +60,46 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||
description,
|
||||
appPath,
|
||||
groupPath,
|
||||
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
||||
ships.map((s) => `~${s}`),
|
||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||
ships.map(s => `~${s}`),
|
||||
true,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case "publish":
|
||||
case 'publish':
|
||||
await props.api.publish.newBook(resId, name, description, group);
|
||||
break;
|
||||
case "links":
|
||||
case 'links':
|
||||
if (group) {
|
||||
await api.graph.createManagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
group,
|
||||
"link"
|
||||
'link'
|
||||
);
|
||||
} else {
|
||||
await api.graph.createUnmanagedGraph(
|
||||
resId,
|
||||
name,
|
||||
description,
|
||||
{ invite: { pending: ships.map((s) => `~${s}`) } },
|
||||
"link"
|
||||
{ invite: { pending: ships.map(s => `~${s}`) } },
|
||||
'link'
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("fallthrough");
|
||||
console.log('fallthrough');
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
await waiter((p) => !!p?.groups?.[`/ship/~${window.ship}/${resId}`]);
|
||||
await waiter(p => Boolean(p?.groups?.[`/ship/~${window.ship}/${resId}`]));
|
||||
}
|
||||
actions.setStatus({ success: null });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
actions.setStatus({ error: "Channel creation failed" });
|
||||
actions.setStatus({ error: 'Channel creation failed' });
|
||||
}
|
||||
};
|
||||
return (
|
||||
@ -113,11 +110,11 @@ export function NewChannel(props: NewChannelProps & RouteComponentProps) {
|
||||
<Formik
|
||||
validationSchema={formSchema}
|
||||
initialValues={{
|
||||
type: "chat",
|
||||
name: "",
|
||||
description: "",
|
||||
group: "",
|
||||
ships: [],
|
||||
type: 'chat',
|
||||
name: '',
|
||||
description: '',
|
||||
group: '',
|
||||
ships: []
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
|
@ -3,8 +3,8 @@ import React, {
|
||||
useMemo,
|
||||
useCallback,
|
||||
SyntheticEvent,
|
||||
ChangeEvent,
|
||||
} from "react";
|
||||
ChangeEvent
|
||||
} from 'react';
|
||||
import {
|
||||
Col,
|
||||
Box,
|
||||
@ -14,23 +14,23 @@ import {
|
||||
Center,
|
||||
Button,
|
||||
Action,
|
||||
StatelessTextInput as Input,
|
||||
} from "@tlon/indigo-react";
|
||||
import _ from "lodash";
|
||||
import f from "lodash/fp";
|
||||
import VisibilitySensor from "react-visibility-sensor";
|
||||
StatelessTextInput as Input
|
||||
} from '@tlon/indigo-react';
|
||||
import _ from 'lodash';
|
||||
import f from 'lodash/fp';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
|
||||
import { Contact, Contacts } from "~/types/contact-update";
|
||||
import { Sigil } from "~/logic/lib/sigil";
|
||||
import { cite, uxToHex } from "~/logic/lib/util";
|
||||
import { Group, RoleTags } from "~/types/group-update";
|
||||
import { roleForShip, resourceFromPath } from "~/logic/lib/group";
|
||||
import { Association } from "~/types/metadata-update";
|
||||
import { useHistory, Link } from "react-router-dom";
|
||||
import { Dropdown } from "~/views/components/Dropdown";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
import { StatelessAsyncAction } from "~/views/components/StatelessAsyncAction";
|
||||
import styled from "styled-components";
|
||||
import { Contact, Contacts } from '~/types/contact-update';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { cite, uxToHex } from '~/logic/lib/util';
|
||||
import { Group, RoleTags } from '~/types/group-update';
|
||||
import { roleForShip, resourceFromPath } from '~/logic/lib/group';
|
||||
import { Association } from '~/types/metadata-update';
|
||||
import { useHistory, Link } from 'react-router-dom';
|
||||
import { Dropdown } from '~/views/components/Dropdown';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StatelessAsyncAction } from '~/views/components/StatelessAsyncAction';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const TruncText = styled(Box)`
|
||||
white-space: nowrap;
|
||||
@ -39,7 +39,7 @@ const TruncText = styled(Box)`
|
||||
`;
|
||||
|
||||
type Participant = Contact & { patp: string; pending: boolean };
|
||||
type ParticipantsTabId = "total" | "pending" | "admin";
|
||||
type ParticipantsTabId = 'total' | 'pending' | 'admin';
|
||||
|
||||
const searchParticipant = (search: string) => (p: Participant) => {
|
||||
if (search.length == 0) {
|
||||
@ -54,36 +54,36 @@ function getParticipants(cs: Contacts, group: Group) {
|
||||
const contacts: Participant[] = _.map(cs, (c, patp) => ({
|
||||
...c,
|
||||
patp,
|
||||
pending: false,
|
||||
pending: false
|
||||
}));
|
||||
const members: Participant[] = _.map(Array.from(group.members), (m) =>
|
||||
const members: Participant[] = _.map(Array.from(group.members), m =>
|
||||
emptyContact(m, false)
|
||||
);
|
||||
const allMembers = _.unionBy(contacts, members, "patp");
|
||||
const allMembers = _.unionBy(contacts, members, 'patp');
|
||||
const pending: Participant[] =
|
||||
"invite" in group.policy
|
||||
? _.map(Array.from(group.policy.invite.pending), (m) =>
|
||||
'invite' in group.policy
|
||||
? _.map(Array.from(group.policy.invite.pending), m =>
|
||||
emptyContact(m, true)
|
||||
)
|
||||
: [];
|
||||
|
||||
return [
|
||||
_.unionBy(allMembers, pending, "patp"),
|
||||
_.unionBy(allMembers, pending, 'patp'),
|
||||
pending.length,
|
||||
allMembers.length,
|
||||
allMembers.length
|
||||
] as const;
|
||||
}
|
||||
|
||||
const emptyContact = (patp: string, pending: boolean): Participant => ({
|
||||
nickname: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
color: "",
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
color: '',
|
||||
avatar: null,
|
||||
notes: "",
|
||||
website: "",
|
||||
notes: '',
|
||||
website: '',
|
||||
patp,
|
||||
pending,
|
||||
pending
|
||||
});
|
||||
|
||||
const Tab = ({ selected, id, label, setSelected }) => (
|
||||
@ -95,7 +95,7 @@ const Tab = ({ selected, id, label, setSelected }) => (
|
||||
cursor="pointer"
|
||||
onClick={() => setSelected(id)}
|
||||
>
|
||||
<Text color={selected === id ? "black" : "gray"}>{label}</Text>
|
||||
<Text color={selected === id ? 'black' : 'gray'}>{label}</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@ -113,18 +113,18 @@ export function Participants(props: {
|
||||
(p: Participant) => boolean
|
||||
> = useMemo(
|
||||
() => ({
|
||||
total: (p) => !p.pending,
|
||||
pending: (p) => p.pending,
|
||||
admin: (p) => props.group.tags?.role?.admin?.has(p.patp),
|
||||
total: p => !p.pending,
|
||||
pending: p => p.pending,
|
||||
admin: p => props.group.tags?.role?.admin?.has(p.patp)
|
||||
}),
|
||||
[props.group]
|
||||
);
|
||||
|
||||
const ourRole = roleForShip(props.group, window.ship);
|
||||
|
||||
const [filter, setFilter] = useState<ParticipantsTabId>("total");
|
||||
const [filter, setFilter] = useState<ParticipantsTabId>('total');
|
||||
|
||||
const [search, _setSearch] = useState("");
|
||||
const [search, _setSearch] = useState('');
|
||||
const setSearch = useMemo(() => _.debounce(_setSearch, 200), [_setSearch]);
|
||||
const onSearchChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
@ -134,7 +134,7 @@ export function Participants(props: {
|
||||
);
|
||||
|
||||
const adminCount = props.group.tags?.role?.admin?.size || 0;
|
||||
const isInvite = "invite" in props.group.policy;
|
||||
const isInvite = 'invite' in props.group.policy;
|
||||
|
||||
const [participants, pendingCount, memberCount] = getParticipants(
|
||||
props.contacts,
|
||||
@ -156,7 +156,7 @@ export function Participants(props: {
|
||||
// TODO: remove when resolved
|
||||
const isSafari = useMemo(() => {
|
||||
const ua = window.navigator.userAgent;
|
||||
return ua.includes("Safari") && !ua.includes("Chrome");
|
||||
return ua.includes('Safari') && !ua.includes('Chrome');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -166,7 +166,7 @@ export function Participants(props: {
|
||||
border={1}
|
||||
borderColor="washedGray"
|
||||
borderRadius={1}
|
||||
position={isSafari ? "static" : "sticky"}
|
||||
position={isSafari ? 'static' : 'sticky'}
|
||||
top="0px"
|
||||
mb={2}
|
||||
px={2}
|
||||
@ -192,7 +192,7 @@ export function Participants(props: {
|
||||
selected={filter}
|
||||
setSelected={setFilter}
|
||||
id="admin"
|
||||
label={`${adminCount} Admin${adminCount > 1 ? "s" : ""}`}
|
||||
label={`${adminCount} Admin${adminCount > 1 ? 's' : ''}`}
|
||||
/>
|
||||
</Row>
|
||||
</Row>
|
||||
@ -210,8 +210,8 @@ export function Participants(props: {
|
||||
</Row>
|
||||
<Box
|
||||
display="grid"
|
||||
gridAutoRows={["48px 48px 1px", "48px 1px"]}
|
||||
gridTemplateColumns={["48px 1fr", "48px 2fr 1fr", "48px 3fr 1fr"]}
|
||||
gridAutoRows={['48px 48px 1px', '48px 1px']}
|
||||
gridTemplateColumns={['48px 1fr', '48px 2fr 1fr', '48px 3fr 1fr']}
|
||||
gridRowGap={2}
|
||||
alignItems="center"
|
||||
>
|
||||
@ -224,7 +224,7 @@ export function Participants(props: {
|
||||
>
|
||||
{({ isVisible }) =>
|
||||
isVisible ? (
|
||||
cs.map((c) => (
|
||||
cs.map(c => (
|
||||
<Participant
|
||||
api={api}
|
||||
key={c.patp}
|
||||
@ -261,37 +261,37 @@ function Participant(props: {
|
||||
const { title } = association.metadata;
|
||||
|
||||
const color = uxToHex(contact.color);
|
||||
const isInvite = "invite" in group.policy;
|
||||
const isInvite = 'invite' in group.policy;
|
||||
|
||||
const role = useMemo(
|
||||
() =>
|
||||
contact.pending
|
||||
? "pending"
|
||||
: roleForShip(group, contact.patp) || "member",
|
||||
? 'pending'
|
||||
: roleForShip(group, contact.patp) || 'member',
|
||||
[contact, group]
|
||||
);
|
||||
|
||||
const onPromote = useCallback(async () => {
|
||||
const resource = resourceFromPath(association["group-path"]);
|
||||
await api.groups.addTag(resource, { tag: "admin" }, [`~${contact.patp}`]);
|
||||
const resource = resourceFromPath(association['group-path']);
|
||||
await api.groups.addTag(resource, { tag: 'admin' }, [`~${contact.patp}`]);
|
||||
}, [api, association]);
|
||||
|
||||
const onDemote = useCallback(async () => {
|
||||
const resource = resourceFromPath(association["group-path"]);
|
||||
await api.groups.removeTag(resource, { tag: "admin" }, [
|
||||
`~${contact.patp}`,
|
||||
const resource = resourceFromPath(association['group-path']);
|
||||
await api.groups.removeTag(resource, { tag: 'admin' }, [
|
||||
`~${contact.patp}`
|
||||
]);
|
||||
}, [api, association]);
|
||||
|
||||
const onBan = useCallback(async () => {
|
||||
const resource = resourceFromPath(association["group-path"]);
|
||||
const resource = resourceFromPath(association['group-path']);
|
||||
await api.groups.changePolicy(resource, {
|
||||
open: { banShips: [`~${contact.patp}`] },
|
||||
open: { banShips: [`~${contact.patp}`] }
|
||||
});
|
||||
}, [api, association]);
|
||||
|
||||
const onKick = useCallback(async () => {
|
||||
const resource = resourceFromPath(association["group-path"]);
|
||||
const resource = resourceFromPath(association['group-path']);
|
||||
await api.groups.remove(resource, [`~${contact.patp}`]);
|
||||
}, [api, association]);
|
||||
|
||||
@ -319,7 +319,7 @@ function Participant(props: {
|
||||
</Col>
|
||||
<Row
|
||||
justifyContent="space-between"
|
||||
gridColumn={["1 / 3", "auto"]}
|
||||
gridColumn={['1 / 3', 'auto']}
|
||||
alignItems="center"
|
||||
>
|
||||
<Col>
|
||||
@ -340,7 +340,12 @@ function Participant(props: {
|
||||
gapY={2}
|
||||
p={2}
|
||||
>
|
||||
{props.role === "admin" && (
|
||||
<Action bg="transparent">
|
||||
<Link to={`/~landscape/dm/${contact.patp}`}>
|
||||
<Text color="green">Send Message</Text>
|
||||
</Link>
|
||||
</Action>
|
||||
{props.role === 'admin' && (
|
||||
<>
|
||||
{!isInvite && (
|
||||
<>
|
||||
@ -352,7 +357,7 @@ function Participant(props: {
|
||||
</StatelessAsyncAction>
|
||||
</>
|
||||
)}
|
||||
{role === "admin" ? (
|
||||
{role === 'admin' ? (
|
||||
<StatelessAsyncAction onClick={onDemote} bg="transparent">
|
||||
Demote from Admin
|
||||
</StatelessAsyncAction>
|
||||
@ -372,7 +377,7 @@ function Participant(props: {
|
||||
<Box
|
||||
borderBottom={1}
|
||||
borderBottomColor="washedGray"
|
||||
gridColumn={["1 / 3", "1 / 4"]}
|
||||
gridColumn={['1 / 3', '1 / 4']}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@ -382,7 +387,7 @@ function BlankParticipant({ length }) {
|
||||
return (
|
||||
<Box
|
||||
gridRow={[`auto / span ${3 * length}`, `auto / span ${2 * length}`]}
|
||||
gridColumn={["1 / 3", "1 / 4"]}
|
||||
gridColumn={['1 / 3', '1 / 4']}
|
||||
height="100%"
|
||||
/>
|
||||
);
|
||||
|
@ -1,20 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Box, Center } from '@tlon/indigo-react';
|
||||
|
||||
import './css/custom.css';
|
||||
|
||||
import { PatpNoSig, AppName } from '~/types/noun';
|
||||
import { PatpNoSig } from '~/types/noun';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
import { StoreState } from '~/logic/store/type';
|
||||
import GlobalSubscription from '~/logic/subscription/global';
|
||||
import { Resource } from '~/views/components/Resource';
|
||||
import { PopoverRoutes } from './components/PopoverRoutes';
|
||||
import { UnjoinedResource } from '~/views/components/UnjoinedResource';
|
||||
import { GroupsPane } from './components/GroupsPane';
|
||||
import { Workspace } from '~/types';
|
||||
import {NewGroup} from './components/NewGroup';
|
||||
import {JoinGroup} from './components/JoinGroup';
|
||||
import { NewGroup } from './components/NewGroup';
|
||||
import { JoinGroup } from './components/JoinGroup';
|
||||
|
||||
import { cite } from '~/logic/lib/util';
|
||||
|
||||
|
||||
type LandscapeProps = StoreState & {
|
||||
@ -34,25 +31,46 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
||||
this.props.subscription.startApp('publish');
|
||||
this.props.subscription.startApp('graph');
|
||||
this.props.api.publish.fetchNotebooks();
|
||||
|
||||
}
|
||||
|
||||
createandRedirectToDM(api, ship, history, allStations) {
|
||||
const station = `/~${window.ship}/dm--${ship}`;
|
||||
const theirStation = `/~${ship}/dm--${window.ship}`;
|
||||
|
||||
if (allStations.indexOf(station) !== -1) {
|
||||
history.push(`/~landscape/home/resource/chat${station}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allStations.indexOf(theirStation) !== -1) {
|
||||
history.push(`/~landscape/home/resource/chat${theirStation}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const groupPath = `/ship/~${window.ship}/dm--${ship}`;
|
||||
const aud = ship !== window.ship ? [`~${ship}`] : [];
|
||||
const title = `${cite(window.ship)} <-> ${cite(ship)}`;
|
||||
|
||||
api.chat.create(
|
||||
title,
|
||||
'',
|
||||
station,
|
||||
groupPath,
|
||||
{ invite: { pending: aud } },
|
||||
aud,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
// TODO: make a pretty loading state
|
||||
setTimeout(() => {
|
||||
history.push(`/~landscape/home/resource/chat${station}`);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
|
||||
const contacts = props.contacts || {};
|
||||
const defaultContacts =
|
||||
(Boolean(props.contacts) && '/~/default' in props.contacts) ?
|
||||
props.contacts['/~/default'] : {};
|
||||
|
||||
const invites =
|
||||
(Boolean(props.invites) && '/contacts' in props.invites) ?
|
||||
props.invites['/contacts'] : {};
|
||||
const s3 = props.s3 ? props.s3 : {};
|
||||
const groups = props.groups || {};
|
||||
const associations = props.associations || {};
|
||||
const { api } = props;
|
||||
|
||||
const { api, inbox } = props;
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
@ -73,7 +91,6 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
||||
<Route path="/~landscape/home"
|
||||
render={routeProps => {
|
||||
const ws: Workspace = { type: 'home' };
|
||||
|
||||
return (
|
||||
<GroupsPane workspace={ws} baseUrl="/~landscape/home" {...props} />
|
||||
);
|
||||
@ -85,21 +102,27 @@ export default class Landscape extends Component<LandscapeProps, {}> {
|
||||
<NewGroup
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
api={props.api}
|
||||
{...routeProps}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Route path='/~landscape/dm/:ship?'
|
||||
render={routeProps => {
|
||||
const { ship } = routeProps.match.params;
|
||||
return this.createandRedirectToDM(api, ship, routeProps.history, Object.keys(inbox));
|
||||
}}
|
||||
/>
|
||||
<Route path="/~landscape/join/:ship?/:name?"
|
||||
render={routeProps=> {
|
||||
const { ship, name } = routeProps.match.params;
|
||||
const autojoin = ship && name ? `${ship}/${name}` : null;
|
||||
return (
|
||||
<JoinGroup
|
||||
<JoinGroup
|
||||
groups={props.groups}
|
||||
contacts={props.contacts}
|
||||
api={props.api}
|
||||
api={props.api}
|
||||
autojoin={autojoin}
|
||||
{...routeProps}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user