mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 13:54:20 +03:00
Merge branch 'release/next-js' into la/release-2021-03-04
This commit is contained in:
commit
4c2ce9a338
@ -128,13 +128,14 @@ class ChatInput extends Component<ChatInputProps, ChatInputState> {
|
||||
|
||||
const avatar = (
|
||||
props.ourContact &&
|
||||
((props.ourContact.avatar !== null) && !props.hideAvatars)
|
||||
((props.ourContact?.avatar) && !props.hideAvatars)
|
||||
)
|
||||
? <BaseImage
|
||||
src={props.ourContact.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
? <BaseImage
|
||||
src={props.ourContact.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
style={{ objectFit: 'cover' }}
|
||||
borderRadius={1}
|
||||
display='inline-block'
|
||||
/>
|
||||
: <Sigil
|
||||
|
@ -298,6 +298,7 @@ export const MessageAuthor = ({
|
||||
src={contact.avatar}
|
||||
height={16}
|
||||
width={16}
|
||||
borderRadius={1}
|
||||
/>
|
||||
) : (
|
||||
<Sigil
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { ReactElement, useRef, useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import _ from 'lodash';
|
||||
import { Formik } from 'formik';
|
||||
@ -11,6 +11,7 @@ import {
|
||||
Col,
|
||||
Text,
|
||||
Row,
|
||||
Button
|
||||
} from '@tlon/indigo-react';
|
||||
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
@ -20,6 +21,12 @@ import { ImageInput } from '~/views/components/ImageInput';
|
||||
import { MarkdownField } from '~/views/apps/publish/components/MarkdownField';
|
||||
import { resourceFromPath } from '~/logic/lib/group';
|
||||
import GroupSearch from '~/views/components/GroupSearch';
|
||||
import {
|
||||
ProfileHeader,
|
||||
ProfileControls,
|
||||
ProfileStatus,
|
||||
ProfileImages
|
||||
} from './Profile';
|
||||
|
||||
const formSchema = Yup.object({
|
||||
nickname: Yup.string(),
|
||||
@ -33,57 +40,91 @@ const emptyContact = {
|
||||
bio: '',
|
||||
status: '',
|
||||
color: '0',
|
||||
avatar: null,
|
||||
cover: null,
|
||||
avatar: '',
|
||||
cover: '',
|
||||
groups: [],
|
||||
'last-updated': 0,
|
||||
isPublic: false
|
||||
};
|
||||
|
||||
export function ProfileHeaderImageEdit(props: any): ReactElement {
|
||||
const { contact, s3, setFieldValue, handleHideCover } = { ...props };
|
||||
const [editCover, setEditCover] = useState(false);
|
||||
const [removedCoverLabel, setRemovedCoverLabel] = useState('Remove Header');
|
||||
const handleClear = (e) => {
|
||||
e.preventDefault();
|
||||
handleHideCover(true);
|
||||
setFieldValue('cover', '');
|
||||
setRemovedCoverLabel('Header Removed');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contact?.cover ? (
|
||||
<div>
|
||||
{editCover ? (
|
||||
<ImageInput id='cover' s3={s3} marginTop='-8px' />
|
||||
) : (
|
||||
<Row>
|
||||
<Button mr='2' onClick={() => setEditCover(true)}>
|
||||
Replace Header
|
||||
</Button>
|
||||
<Button onClick={(e) => handleClear(e)}>
|
||||
{removedCoverLabel}
|
||||
</Button>
|
||||
</Row>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ImageInput id='cover' s3={s3} marginTop='-8px' />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditProfile(props: any): ReactElement {
|
||||
const { contact, ship, api, isPublic } = props;
|
||||
const [hideCover, setHideCover] = useState(false);
|
||||
|
||||
const handleHideCover = (value) => {
|
||||
setHideCover(value);
|
||||
};
|
||||
|
||||
const history = useHistory();
|
||||
if (contact) {
|
||||
contact.isPublic = isPublic;
|
||||
}
|
||||
|
||||
const onSubmit = async (values: any, actions: any) => {
|
||||
console.log(values);
|
||||
try {
|
||||
await Object.keys(values).reduce((acc, key) => {
|
||||
console.log(key);
|
||||
const newValue = key !== 'color' ? values[key] : uxToHex(values[key]);
|
||||
|
||||
if (newValue !== contact[key]) {
|
||||
if (key === 'isPublic') {
|
||||
return acc.then(() =>
|
||||
api.contacts.setPublic(newValue)
|
||||
);
|
||||
return acc.then(() => api.contacts.setPublic(newValue));
|
||||
} else if (key === 'groups') {
|
||||
const toRemove: string[] = _.difference(contact?.groups || [], newValue);
|
||||
console.log(toRemove);
|
||||
const toAdd: string[] = _.difference(newValue, contact?.groups || []);
|
||||
console.log(toAdd);
|
||||
const toRemove: string[] = _.difference(
|
||||
contact?.groups || [],
|
||||
newValue
|
||||
);
|
||||
const toAdd: string[] = _.difference(
|
||||
newValue,
|
||||
contact?.groups || []
|
||||
);
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
promises.concat(
|
||||
toRemove.map(e =>
|
||||
toRemove.map((e) =>
|
||||
api.contacts.edit(ship, { 'remove-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
promises.concat(
|
||||
toAdd.map(e =>
|
||||
toAdd.map((e) =>
|
||||
api.contacts.edit(ship, { 'add-group': resourceFromPath(e) })
|
||||
)
|
||||
);
|
||||
return acc.then(() => Promise.all(promises));
|
||||
} else if (
|
||||
key !== 'last-updated' &&
|
||||
key !== 'isPublic'
|
||||
) {
|
||||
return acc.then(() =>
|
||||
api.contacts.edit(ship, { [key]: newValue })
|
||||
);
|
||||
} else if (key !== 'last-updated' && key !== 'isPublic') {
|
||||
return acc.then(() => api.contacts.edit(ship, { [key]: newValue }));
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
@ -103,28 +144,78 @@ export function EditProfile(props: any): ReactElement {
|
||||
initialValues={contact || emptyContact}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Form width="100%" height="100%" p={2}>
|
||||
<Input id="nickname" label="Name" mb={3} />
|
||||
<Col width="100%">
|
||||
<Text mb={2}>Description</Text>
|
||||
<MarkdownField id="bio" mb={3} s3={props.s3} />
|
||||
</Col>
|
||||
<ColorInput id="color" label="Sigil Color" mb={3} />
|
||||
<Row mb={3} width="100%">
|
||||
<Col pr={2} width="50%">
|
||||
<ImageInput id="cover" label="Cover Image" s3={props.s3} />
|
||||
</Col>
|
||||
<Col pl={2} width="50%">
|
||||
<ImageInput id="avatar" label="Profile Image" s3={props.s3} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Checkbox mb={3} id="isPublic" label="Public Profile" />
|
||||
<GroupSearch label="Pinned Groups" id="groups" groups={props.groups} associations={props.associations} publicOnly />
|
||||
<AsyncButton primary loadingText="Updating..." border mt={3}>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
</Formik>
|
||||
</>
|
||||
{({ setFieldValue }) => (
|
||||
<Form width='100%' height='100%'>
|
||||
<ProfileHeader>
|
||||
<ProfileControls>
|
||||
<Row>
|
||||
<Button
|
||||
type='submit'
|
||||
display='inline'
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
color='blue'
|
||||
pl='0'
|
||||
pr='0'
|
||||
border='0'
|
||||
style={{ appearance: 'none', background: 'transparent' }}
|
||||
>
|
||||
Save Edits
|
||||
</Button>
|
||||
<Text
|
||||
py='2'
|
||||
ml='3'
|
||||
fontWeight='500'
|
||||
cursor='pointer'
|
||||
onClick={() => {
|
||||
history.push(`/~profile/${ship}`);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Text>
|
||||
</Row>
|
||||
<ProfileStatus contact={contact} />
|
||||
</ProfileControls>
|
||||
<ProfileImages hideCover={hideCover} contact={contact} ship={ship}>
|
||||
<ProfileHeaderImageEdit
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
setFieldValue={setFieldValue}
|
||||
handleHideCover={handleHideCover}
|
||||
/>
|
||||
</ProfileImages>
|
||||
</ProfileHeader>
|
||||
<Row mb={3} pt={5} width='100%'>
|
||||
<Col pr={2} width='25%'>
|
||||
<ColorInput id='color' label='Sigil Color' />
|
||||
</Col>
|
||||
<Col pl={2} width='75%'>
|
||||
<ImageInput
|
||||
id='avatar'
|
||||
label='Overlay Avatar (may be hidden by other users)'
|
||||
s3={props.s3}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Input id='nickname' label='Custom Name' mb={3} />
|
||||
<Col width='100%'>
|
||||
<Text mb={2}>Description</Text>
|
||||
<MarkdownField id='bio' mb={3} s3={props.s3} />
|
||||
</Col>
|
||||
<Checkbox mb={3} id='isPublic' label='Public Profile' />
|
||||
<GroupSearch
|
||||
label='Pinned Groups'
|
||||
id='groups'
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
publicOnly
|
||||
/>
|
||||
<AsyncButton primary loadingText='Updating...' border mt={3}>
|
||||
Submit
|
||||
</AsyncButton>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,8 @@
|
||||
import React, { ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Row,
|
||||
BaseImage,
|
||||
Text
|
||||
} from "@tlon/indigo-react";
|
||||
|
||||
import RichText from '~/views/components/RichText'
|
||||
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
|
||||
import { Center, Box, Row, BaseImage, Text } from '@tlon/indigo-react';
|
||||
import RichText from '~/views/components/RichText';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { ViewProfile } from './ViewProfile';
|
||||
import { EditProfile } from './EditProfile';
|
||||
@ -18,101 +10,162 @@ import { SetStatusBarModal } from '~/views/components/SetStatusBarModal';
|
||||
import { uxToHex } from '~/logic/lib/util';
|
||||
import { useTutorialModal } from '~/views/components/useTutorialModal';
|
||||
|
||||
export function ProfileHeader(props: any): ReactElement {
|
||||
return (
|
||||
<Box
|
||||
border='1px solid'
|
||||
borderColor='lightGray'
|
||||
borderRadius='2'
|
||||
overflow='hidden'
|
||||
marginBottom='calc(64px + 2rem)'
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileImages(props: any): ReactElement {
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const { contact, hideCover, ship } = { ...props };
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
|
||||
const cover =
|
||||
contact?.cover && !hideCover ? (
|
||||
<BaseImage
|
||||
src={contact.cover}
|
||||
width='100%'
|
||||
height='100%'
|
||||
style={{ objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
display='block'
|
||||
width='100%'
|
||||
height='100%'
|
||||
backgroundColor='washedGray'
|
||||
/>
|
||||
);
|
||||
|
||||
const image =
|
||||
!hideAvatars && contact?.avatar ? (
|
||||
<BaseImage
|
||||
src={contact.avatar}
|
||||
width='100%'
|
||||
height='100%'
|
||||
style={{ objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<Sigil padding={24} ship={ship} size={128} color={hexColor} />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row width='100%' height='300px' position='relative'>
|
||||
{cover}
|
||||
<Center position='absolute' width='100%' height='100%'>
|
||||
{props.children}
|
||||
</Center>
|
||||
</Row>
|
||||
<Box
|
||||
height='128px'
|
||||
width='128px'
|
||||
borderRadius='2'
|
||||
overflow='hidden'
|
||||
position='absolute'
|
||||
left='50%'
|
||||
marginTop='-64px'
|
||||
marginLeft='-64px'
|
||||
>
|
||||
{image}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileControls(props: any): ReactElement {
|
||||
return (
|
||||
<Row alignItems='center' justifyContent='space-between' px='3'>
|
||||
{props.children}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileStatus(props: any): ReactElement {
|
||||
const { contact } = { ...props };
|
||||
return (
|
||||
<RichText
|
||||
mb='0'
|
||||
py='2'
|
||||
disableRemoteContent
|
||||
maxWidth='18rem'
|
||||
overflowX='hidden'
|
||||
textOverflow='ellipsis'
|
||||
whiteSpace='nowrap'
|
||||
overflow='hidden'
|
||||
display='inline-block'
|
||||
verticalAlign='middle'
|
||||
color='gray'
|
||||
>
|
||||
{contact?.status ?? ''}
|
||||
</RichText>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileOwnControls(props: any): ReactElement {
|
||||
const { ship, isPublic, contact, api } = { ...props };
|
||||
const history = useHistory();
|
||||
return (
|
||||
<Row>
|
||||
{ship === `~${window.ship}` ? (
|
||||
<>
|
||||
<Text
|
||||
py='2'
|
||||
cursor='pointer'
|
||||
fontWeight='500'
|
||||
onClick={() => {
|
||||
history.push(`/~profile/${ship}/edit`);
|
||||
}}
|
||||
>
|
||||
Edit {isPublic ? 'Public' : 'Private'} Profile
|
||||
</Text>
|
||||
<SetStatusBarModal
|
||||
isControl
|
||||
py='2'
|
||||
ml='3'
|
||||
api={api}
|
||||
ship={`~${window.ship}`}
|
||||
contact={contact}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export function Profile(props: any): ReactElement {
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const history = useHistory();
|
||||
const history = useHistory();
|
||||
|
||||
if (!props.ship) {
|
||||
return null;
|
||||
}
|
||||
const { contact, nackedContacts, hasLoaded, isPublic, isEdit, ship } = props;
|
||||
const nacked = nackedContacts.has(ship);
|
||||
const formRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if(hasLoaded && !contact && !nacked) {
|
||||
if (hasLoaded && !contact && !nacked) {
|
||||
props.api.contacts.retrieve(ship);
|
||||
}
|
||||
}, [hasLoaded, contact]);
|
||||
|
||||
const hexColor = contact?.color ? `#${uxToHex(contact.color)}` : '#000000';
|
||||
const cover = (contact?.cover)
|
||||
? <BaseImage src={contact.cover} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Box display="block" width='100%' height='100%' backgroundColor='washedGray' />;
|
||||
|
||||
const image = (!hideAvatars && contact?.avatar)
|
||||
? <BaseImage src={contact.avatar} width='100%' height='100%' style={{ objectFit: 'cover' }} />
|
||||
: <Sigil padding={24} ship={ship} size={128} color={hexColor} />;
|
||||
|
||||
const anchorRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
useTutorialModal('profile', ship === `~${window.ship}`, anchorRef);
|
||||
|
||||
return (
|
||||
<Center
|
||||
p={[0,4]}
|
||||
height="100%"
|
||||
width="100%"
|
||||
>
|
||||
|
||||
<Box
|
||||
ref={anchorRef}
|
||||
maxWidth="600px"
|
||||
width="100%"
|
||||
>
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Row>
|
||||
{ship === `~${window.ship}` ? (
|
||||
<>
|
||||
<Text
|
||||
py='2'
|
||||
cursor='pointer'
|
||||
onClick={() => {
|
||||
history.push(`/~profile/${ship}/edit`);
|
||||
}}
|
||||
>
|
||||
Edit Profile
|
||||
</Text>
|
||||
<SetStatusBarModal
|
||||
py='2'
|
||||
ml='3'
|
||||
api={props.api}
|
||||
ship={`~${window.ship}`}
|
||||
contact={contact}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</Row>
|
||||
<RichText mb='0' py='2' disableRemoteContent maxWidth='18rem' overflowX='hidden' textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden" display="inline-block" verticalAlign="middle">{contact?.status ?? ""}</RichText>
|
||||
</Row>
|
||||
<Row width="100%" height="300px">
|
||||
{cover}
|
||||
</Row>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%" marginTop="-48px">
|
||||
<Box height='128px' width='128px' borderRadius="2" overflow="hidden">
|
||||
{image}
|
||||
</Box>
|
||||
</Center>
|
||||
</Row>
|
||||
{ isEdit ? (
|
||||
<EditProfile
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
) : (
|
||||
const ViewInterface = () => {
|
||||
return (
|
||||
<Center p={[0, 4]} height='100%' width='100%'>
|
||||
<Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'>
|
||||
<ViewProfile
|
||||
api={props.api}
|
||||
nacked={nacked}
|
||||
@ -122,8 +175,28 @@ export function Profile(props: any): ReactElement {
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
/>
|
||||
) }
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
const EditInterface = () => {
|
||||
return (
|
||||
<Center p={[0, 4]} height='100%' width='100%'>
|
||||
<Box ref={anchorRef} maxWidth='600px' width='100%' position='relative'>
|
||||
<EditProfile
|
||||
ship={ship}
|
||||
contact={contact}
|
||||
s3={props.s3}
|
||||
api={props.api}
|
||||
groups={props.groups}
|
||||
associations={props.associations}
|
||||
isPublic={isPublic}
|
||||
/>
|
||||
</Box>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
return isEdit ? <EditInterface /> : <ViewInterface />;
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { Center, Box, Text, Row, Col } from '@tlon/indigo-react';
|
||||
import RichText from '~/views/components/RichText';
|
||||
import useSettingsState, { selectCalmState } from '~/logic/state/settings';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import { GroupLink } from '~/views/components/GroupLink';
|
||||
import { lengthOrder } from '~/logic/lib/util';
|
||||
import useLocalState from '~/logic/state/local';
|
||||
import {
|
||||
Center,
|
||||
Box,
|
||||
Text,
|
||||
Row,
|
||||
Col,
|
||||
} from "@tlon/indigo-react";
|
||||
import RichText from "~/views/components/RichText";
|
||||
import {GroupLink} from "~/views/components/GroupLink";
|
||||
import {lengthOrder} from "~/logic/lib/util";
|
||||
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
|
||||
|
||||
ProfileHeader,
|
||||
ProfileControls,
|
||||
ProfileOwnControls,
|
||||
ProfileStatus,
|
||||
ProfileImages
|
||||
} from './Profile';
|
||||
|
||||
export function ViewProfile(props: any) {
|
||||
const history = useHistory();
|
||||
@ -22,43 +23,44 @@ export function ViewProfile(props: any) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%">
|
||||
<ProfileHeader>
|
||||
<ProfileControls>
|
||||
<ProfileOwnControls
|
||||
ship={ship}
|
||||
isPublic={isPublic}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
/>
|
||||
<ProfileStatus contact={contact} />
|
||||
</ProfileControls>
|
||||
<ProfileImages contact={contact} ship={ship} />
|
||||
</ProfileHeader>
|
||||
<Row pb={2} alignItems='center' width='100%'>
|
||||
<Center width='100%'>
|
||||
<Text>
|
||||
{((!hideNicknames && contact?.nickname) ? contact.nickname : '')}
|
||||
{!hideNicknames && contact?.nickname ? contact.nickname : ''}
|
||||
</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Row
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
width="100%"
|
||||
>
|
||||
<Center width="100%">
|
||||
<Text mono color="darkGray">{ship}</Text>
|
||||
<Row pb={2} alignItems='center' width='100%'>
|
||||
<Center width='100%'>
|
||||
<Text mono color='darkGray'>
|
||||
{ship}
|
||||
</Text>
|
||||
</Center>
|
||||
</Row>
|
||||
<Col
|
||||
pb={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
width="100%"
|
||||
>
|
||||
<Center flexDirection="column" maxWidth='32rem'>
|
||||
<Col pb={2} alignItems='center' justifyContent='center' width='100%'>
|
||||
<Center flexDirection='column' maxWidth='32rem'>
|
||||
<RichText width='100%' disableRemoteContent>
|
||||
{(contact?.bio ? contact.bio : '')}
|
||||
{contact?.bio ? contact.bio : ''}
|
||||
</RichText>
|
||||
</Center>
|
||||
</Col>
|
||||
{ (contact?.groups || []).length > 0 && (
|
||||
<Col gapY="3" mb="3" mt="6" alignItems="flex-start">
|
||||
</Center>
|
||||
</Col>
|
||||
{(contact?.groups || []).length > 0 && (
|
||||
<Col gapY='3' mb='3' mt='6' alignItems='flex-start'>
|
||||
<Text gray>Pinned Groups</Text>
|
||||
<Col>
|
||||
{ contact?.groups.sort(lengthOrder).map(g => (
|
||||
{contact?.groups.sort(lengthOrder).map((g) => (
|
||||
<GroupLink
|
||||
api={api}
|
||||
resource={g}
|
||||
@ -68,25 +70,25 @@ export function ViewProfile(props: any) {
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
</Col>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
{ (nacked || (!isPublic && ship === `~${window.ship}`)) ? (
|
||||
{nacked || (!isPublic && ship === `~${window.ship}`) ? (
|
||||
<Box
|
||||
height="200px"
|
||||
height='200px'
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
bg='white'
|
||||
border={1}
|
||||
borderColor="washedGray"
|
||||
borderColor='washedGray'
|
||||
>
|
||||
<Center height="100%">
|
||||
<Text mono pr={1} color="gray">{ship}</Text>
|
||||
<Text color="gray">remains private</Text>
|
||||
<Center height='100%'>
|
||||
<Text mono pr={1} color='gray'>
|
||||
{ship}
|
||||
</Text>
|
||||
<Text color='gray'>remains private</Text>
|
||||
</Center>
|
||||
</Box>
|
||||
) : null
|
||||
}
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,48 +10,53 @@ export default function ProfileScreen(props: any) {
|
||||
const { dark } = props;
|
||||
return (
|
||||
<>
|
||||
<Helmet defer={false}>
|
||||
<title>{ props.notificationsCount ? `(${String(props.notificationsCount) }) `: '' }Landscape - Profile</title>
|
||||
</Helmet>
|
||||
<Route
|
||||
path={'/~profile/:ship/:edit?'}
|
||||
render={({ match }) => {
|
||||
const ship = match.params.ship;
|
||||
const isEdit = match.url.includes('edit');
|
||||
const isPublic = props.isContactPublic;
|
||||
const contact = props.contacts?.[ship];
|
||||
<Helmet defer={false}>
|
||||
<title>
|
||||
{props.notificationsCount
|
||||
? `(${String(props.notificationsCount)}) `
|
||||
: ''}
|
||||
Landscape - Profile
|
||||
</title>
|
||||
</Helmet>
|
||||
<Route
|
||||
path={'/~profile/:ship/:edit?'}
|
||||
render={({ match }) => {
|
||||
const ship = match.params.ship;
|
||||
const isEdit = match.url.includes('edit');
|
||||
const isPublic = props.isContactPublic;
|
||||
const contact = props.contacts?.[ship];
|
||||
|
||||
return (
|
||||
<Box height="100%" px={[0, 3]} pb={[0, 3]} borderRadius={1}>
|
||||
<Box
|
||||
height="100%"
|
||||
width="100%"
|
||||
borderRadius={1}
|
||||
bg="white"
|
||||
border={1}
|
||||
borderColor="washedGray"
|
||||
overflowY="auto"
|
||||
flexGrow
|
||||
>
|
||||
<Box>
|
||||
<Profile
|
||||
ship={ship}
|
||||
hasLoaded={Object.keys(props.contacts).length !== 0}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
isEdit={isEdit}
|
||||
isPublic={isPublic}
|
||||
nackedContacts={props.nackedContacts}
|
||||
/>
|
||||
return (
|
||||
<Box height='100%' px={[0, 3]} pb={[0, 3]} borderRadius={2}>
|
||||
<Box
|
||||
height='100%'
|
||||
width='100%'
|
||||
borderRadius={2}
|
||||
bg='white'
|
||||
border={1}
|
||||
borderColor='washedGray'
|
||||
overflowY='auto'
|
||||
flexGrow
|
||||
>
|
||||
<Box>
|
||||
<Profile
|
||||
ship={ship}
|
||||
hasLoaded={Object.keys(props.contacts).length !== 0}
|
||||
associations={props.associations}
|
||||
groups={props.groups}
|
||||
contact={contact}
|
||||
api={props.api}
|
||||
s3={props.s3}
|
||||
isEdit={isEdit}
|
||||
isPublic={isPublic}
|
||||
nackedContacts={props.nackedContacts}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { Contacts } from '@urbit/api/contacts';
|
||||
import { Group } from '@urbit/api';
|
||||
|
||||
import { uxToHex, cite, useShowNickname, deSig } from '~/logic/lib/util';
|
||||
import useSettingsState, {selectCalmState} from "~/logic/state/settings";
|
||||
import OverlaySigil from './OverlaySigil';
|
||||
import { Sigil } from '~/logic/lib/sigil';
|
||||
import GlobalApi from '~/logic/api/global';
|
||||
@ -33,6 +34,7 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
}
|
||||
const color = contact?.color ? `#${uxToHex(contact?.color)}` : '#000000';
|
||||
const showNickname = useShowNickname(contact);
|
||||
const { hideAvatars } = useSettingsState(selectCalmState);
|
||||
const name = showNickname ? contact.nickname : cite(ship);
|
||||
const stamp = moment(date);
|
||||
|
||||
@ -43,13 +45,14 @@ export default function Author(props: AuthorProps): ReactElement {
|
||||
};
|
||||
|
||||
const img =
|
||||
contact && contact.avatar !== null ? (
|
||||
contact?.avatar && !hideAvatars ? (
|
||||
<BaseImage
|
||||
display='inline-block'
|
||||
src={contact.avatar}
|
||||
style={{ objectFit: 'cover' }}
|
||||
height={16}
|
||||
width={16}
|
||||
borderRadius={1}
|
||||
/>
|
||||
) : (
|
||||
<Sigil ship={ship} size={16} color={color} icon padding={2} />
|
||||
|
@ -23,7 +23,7 @@ export function ColorInput(props: ColorInputProps) {
|
||||
const { id, placeholder, label, caption, disabled, ...rest } = props;
|
||||
const [{ value, onBlur }, meta, { setValue }] = useField(id);
|
||||
|
||||
const hex = value.replace('#', '').replace('0x','').replace('.', '');
|
||||
const hex = value.replace('#', '').replace('0x', '').replace('.', '');
|
||||
const padded = hex.padStart(6, '0');
|
||||
|
||||
const onChange = (e: FormEvent<HTMLInputElement>) => {
|
||||
@ -39,15 +39,16 @@ export function ColorInput(props: ColorInputProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" {...rest}>
|
||||
<Box display='flex' flexDirection='column' {...rest}>
|
||||
<Label htmlFor={id}>{label}</Label>
|
||||
{caption ? (
|
||||
<Label mt="2" gray>
|
||||
<Label mt='2' gray>
|
||||
{caption}
|
||||
</Label>
|
||||
) : null}
|
||||
<Row mt="2" alignItems="flex-end">
|
||||
<Row mt='2' alignItems='flex-end'>
|
||||
<Input
|
||||
id={id}
|
||||
borderTopRightRadius={0}
|
||||
borderBottomRightRadius={0}
|
||||
onBlur={onBlur}
|
||||
@ -62,25 +63,25 @@ export function ColorInput(props: ColorInputProps) {
|
||||
borderTopRightRadius={1}
|
||||
border={1}
|
||||
borderLeft={0}
|
||||
borderColor="lightGray"
|
||||
width="32px"
|
||||
alignSelf="stretch"
|
||||
borderColor='lightGray'
|
||||
width='32px'
|
||||
alignSelf='stretch'
|
||||
bg={`#${padded}`}
|
||||
>
|
||||
<Input
|
||||
width="100%"
|
||||
height="100%"
|
||||
alignSelf="stretch"
|
||||
onInput={onChange}
|
||||
width='100%'
|
||||
height='100%'
|
||||
alignSelf='stretch'
|
||||
onChange={onChange}
|
||||
value={`#${padded}`}
|
||||
disabled={disabled || false}
|
||||
type="color"
|
||||
type='color'
|
||||
opacity={0}
|
||||
overflow="hidden"
|
||||
overflow='hidden'
|
||||
/>
|
||||
</Box>
|
||||
</Row>
|
||||
<ErrorLabel mt="2" hasError={Boolean(meta.touched && meta.error)}>
|
||||
<ErrorLabel mt='2' hasError={Boolean(meta.touched && meta.error)}>
|
||||
{meta.error}
|
||||
</ErrorLabel>
|
||||
</Box>
|
||||
|
@ -37,7 +37,7 @@ const RichText = React.memo(({ disableRemoteContent, ...props }) => (
|
||||
return <RemoteContent className="mw-100" url={linkProps.href} />;
|
||||
}
|
||||
|
||||
return <Anchor target='_blank' rel='noreferrer noopener' borderBottom='1px solid' remoteContentPolicy={remoteContentPolicy} {...linkProps}>{linkProps.children}</Anchor>;
|
||||
return <Anchor display="inline" target='_blank' rel='noreferrer noopener' borderBottom='1px solid' remoteContentPolicy={remoteContentPolicy} {...linkProps}>{linkProps.children}</Anchor>;
|
||||
},
|
||||
linkReference: (linkProps) => {
|
||||
const linkText = String(linkProps.children[0].props.children);
|
||||
|
@ -1,31 +1,18 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Box,
|
||||
Text
|
||||
} from '@tlon/indigo-react';
|
||||
import { Row, Box, Text } from '@tlon/indigo-react';
|
||||
|
||||
import { SetStatus } from '~/views/apps/profile/components/SetStatus';
|
||||
|
||||
|
||||
export const SetStatusBarModal = (props) => {
|
||||
const {
|
||||
ship,
|
||||
contact,
|
||||
api,
|
||||
...rest
|
||||
} = props;
|
||||
const { ship, contact, api, isControl, ...rest } = props;
|
||||
const [modalShown, setModalShown] = useState(false);
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
setModalShown(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
@ -40,28 +27,28 @@ export const SetStatusBarModal = (props) => {
|
||||
{modalShown && (
|
||||
<Box
|
||||
backgroundColor='scales.black30'
|
||||
left="0px"
|
||||
top="0px"
|
||||
width="100%"
|
||||
height="100%"
|
||||
left='0px'
|
||||
top='0px'
|
||||
width='100%'
|
||||
height='100%'
|
||||
zIndex={4}
|
||||
position="fixed"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
position='fixed'
|
||||
display='flex'
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
onClick={() => setModalShown(false)}
|
||||
>
|
||||
<Box
|
||||
maxWidth="500px"
|
||||
width="100%"
|
||||
bg="white"
|
||||
maxWidth='500px'
|
||||
width='100%'
|
||||
bg='white'
|
||||
borderRadius={2}
|
||||
border={[0, 1]}
|
||||
borderColor={["washedGray", "washedGray"]}
|
||||
onClick={e => e.stopPropagation()}
|
||||
display="flex"
|
||||
alignItems="stretch"
|
||||
flexDirection="column"
|
||||
borderColor={['washedGray', 'washedGray']}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
display='flex'
|
||||
alignItems='stretch'
|
||||
flexDirection='column'
|
||||
>
|
||||
<Box m={3}>
|
||||
<SetStatus
|
||||
@ -70,23 +57,23 @@ export const SetStatusBarModal = (props) => {
|
||||
api={api}
|
||||
callback={() => {
|
||||
setModalShown(false);
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Row
|
||||
{...rest}
|
||||
flexShrink={0}
|
||||
onClick={() => setModalShown(true)}>
|
||||
<Text color='black'
|
||||
<Row {...rest} flexShrink={0} onClick={() => setModalShown(true)}>
|
||||
<Text
|
||||
color='black'
|
||||
cursor='pointer'
|
||||
fontWeight={isControl ? '500' : '400'}
|
||||
flexShrink={0}
|
||||
fontSize={1}>
|
||||
fontSize={1}
|
||||
>
|
||||
Set Status
|
||||
</Text>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ export function MetadataIcon(props: MetadataIconProps) {
|
||||
const bgColor = metadata.picture ? {} : { bg: `#${uxToHex(metadata.color)}` };
|
||||
|
||||
return (
|
||||
<Box {...bgColor} {...rest} borderRadius={2} boxShadow="inset 0 0 0 -1px" color="lightGray" overflow="hidden">
|
||||
<Box {...bgColor} {...rest} borderRadius={2} boxShadow="inset 0 0 0 1px" color="lightGray" overflow="hidden">
|
||||
{metadata.picture && <Image height="100%" src={metadata.picture} />}
|
||||
</Box>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user