rendering contact profile

This commit is contained in:
Roland Osborne 2022-08-17 00:47:39 -07:00
parent 8401a2295a
commit bdf4df5844
16 changed files with 490 additions and 27 deletions

View File

@ -27,6 +27,7 @@ export function useViewportContext() {
};
useEffect(() => {
setTimeout(handleResize, 1000); //cludge for my mobile browser
handleResize();
window.addEventListener('resize', handleResize);
window.addEventListener('orientationchange', handleResize);

View File

@ -3,7 +3,6 @@ import { Drawer } from 'antd';
import { useNavigate } from 'react-router-dom';
import { SessionWrapper } from './Session.styled';
import { AppContext } from 'context/AppContext';
import { ViewportContext } from 'context/ViewportContext';
import { useSession } from './useSession.hook';
import { Conversation } from './conversation/Conversation';
import { Details } from './details/Details';
@ -21,7 +20,6 @@ export function Session() {
const { state, actions } = useSession();
const app = useContext(AppContext);
const viewport = useContext(ViewportContext);
const navigate = useNavigate();
useEffect(() => {
@ -60,7 +58,7 @@ console.log(state);
return (
<SessionWrapper>
{ (viewport.state.display === 'xlarge') && (
{ (state.display === 'xlarge') && (
<div class="desktop-layout noselect">
<div class="left">
<Identity openAccount={openAccount} openCards={openCards} cardUpdated={state.cardUpdated} />
@ -95,8 +93,9 @@ console.log(state);
{ state.cards && (
<div class="reframe">
<Cards closeCards={closeCards} openContact={actions.openContact} openListing={actions.openListing} />
<Drawer title="Basic Drawer" placement="bottom" closable={false} visible={state.listing}
onClose={actions.closeListing} getContainer={false} height={'80%'} style={{ position: 'absolute', overflow: 'hidden' }}>
<Drawer bodyStyle={{ padding: 0 }} placement="bottom" closable={false} visible={state.listing}
onClose={actions.closeListing} getContainer={false} height={'80%'}
style={{ position: 'absolute', overflow: 'hidden' }}>
<Listing openContact={actions.openContact} />
</Drawer>
</div>
@ -109,7 +108,7 @@ console.log(state);
</div>
</div>
)}
{ (viewport.state.display === 'large' || viewport.state.display === 'medium') && (
{ (state.display === 'large' || state.display === 'medium') && (
<div class="tablet-layout noselect">
<div class="left">
<Identity openAccount={actions.openProfile} openCards={actions.openCards} cardUpdated={state.cardUpdated} />
@ -129,17 +128,18 @@ console.log(state);
<Details cardId={state.cardId} conversationId={state.conversationId} />
)}
</Drawer>
<Drawer bodyStyle={{ padding: 0 }} width={'33%'} closable={false} onClose={actions.closeCards} visible={state.cards} zIndex={20} push={state.contact}>
<Drawer bodyStyle={{ padding: 0 }} width={'33%'} closable={false} onClose={closeCards} visible={state.cards} zIndex={20} push={state.contact}>
{ state.cards && (
<Cards closeCards={closeCards} openContact={actions.openContact} openListing={actions.openListing} />
)}
<Drawer title="Basic Drawer" placement="bottom" closable={false} visible={state.listing}
onClose={actions.closeListing} getContainer={false} height={'80%'} style={{ overflow: 'hidden', position: 'absolute' }}>
<Drawer bodyStyle={{ padding: 0 }} placement="bottom" closable={false} visible={state.listing}
onClose={actions.closeListing} getContainer={false} height={'80%'}
style={{ overflow: 'hidden', position: 'absolute' }}>
<Listing openContact={actions.openContact} />
</Drawer>
<Drawer bodyStyle={{ padding: 0 }} width={'33%'} closable={false} onClose={actions.closeContact} visible={state.contact} zIndex={30}>
{ state.contact && (
<Contact close={actions.closeContact} guid={state.contactGuid} node={state.contactNode} />
<Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} />
)}
</Drawer>
</Drawer>
@ -151,7 +151,7 @@ console.log(state);
</div>
</div>
)}
{ (viewport.state.display === 'small') && (
{ (state.display === 'small') && (
<div class="mobile-layout noselect">
<div class="top">
<div class="reframe">
@ -179,7 +179,7 @@ console.log(state);
)}
{ state.contact && (
<div class="reframe">
<Contact close={actions.closeContact} guid={state.contactGuid} node={state.contactNode} />
<Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} />
</div>
)}
{ (state.profile || state.account) && (

View File

@ -1,6 +1,6 @@
import { Drawer, Input, List } from 'antd';
import { CardsWrapper } from './Cards.styled';
import { SortAscendingOutlined, DoubleRightOutlined, UserOutlined, SearchOutlined } from '@ant-design/icons';
import { SortAscendingOutlined, UpOutlined, DoubleRightOutlined, UserOutlined, SearchOutlined } from '@ant-design/icons';
import { useCards } from './useCards.hook';
import { CardItem } from './cardItem/CardItem';
@ -50,8 +50,8 @@ export function Cards({ closeCards, openContact, openListing }) {
{ state.display !== 'small' && (
<div class="bar">
<div class="add" onClick={openListing}>
<UserOutlined />
<div class="label">New Contact</div>
<UpOutlined />
<div class="label">Find New Contact</div>
</div>
</div>
)}

View File

@ -86,8 +86,7 @@ export const CardsWrapper = styled.div`
.add {
display: flex;
flex-direction: row;
background-color: ${Colors.primary};
color: ${Colors.white};
color: ${Colors.primary};
align-items: center;
justify-content: center;
padding-left: 16px;

View File

@ -21,7 +21,7 @@ export function CardItem({ item, open }) {
}
return (
<CardItemWrapper onClick={() => open(profile.guid, profile.node)}>
<CardItemWrapper onClick={() => open(profile.guid)}>
<Logo url={state.logo} width={32} height={32} radius={8} />
<div class="details">
<div class="name">{ profile?.name }</div>

View File

@ -1,6 +1,63 @@
import { ContactWrapper } from './Contact.styled';
import { useContact } from './useContact.hook';
import { Logo } from 'logo/Logo';
import { RightOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
export function Contact({ close, guid, node }) {
return <ContactWrapper />;
export function Contact({ close, guid, listing }) {
const { state, actions } = useContact(guid, listing);
const Image = (
<div class="logo">
<Logo url={state.logo} width={'100%'} radius={8} />
</div>
);
const Details = (
<div class="details">
<div class="name">
<div class="data">{ state.name }</div>
</div>
<div class="location">
<EnvironmentOutlined />
<div class="data">{ state.location }</div>
</div>
<div class="description">
<BookOutlined />
<div class="data">{ state.description }</div>
</div>
</div>
);
return (
<ContactWrapper>
{ state.init && state.display === 'xlarge' && (
<>
<div class="header">
<div class="handle">{ state.handle }</div>
<div class="close" onClick={close}>
<RightOutlined />
</div>
</div>
<div class="content">
{ Image }
{ Details }
</div>
</>
)}
{ state.init && state.display !== 'xlarge' && (
<div class="view">
<div class="title">{ state.handle }</div>
<div class="section">Contact Profile</div>
<div class="controls">
{ Image }
{ Details }
</div>
</div>
)}
</ContactWrapper>
);
}

View File

@ -7,5 +7,115 @@ export const ContactWrapper = styled.div`
display: flex;
flex-direction: column;
background-color: ${Colors.profileForm};
.header {
margin-left: 16px;
margin-right: 16px;
height: 48px;
border-bottom: 1px solid ${Colors.profileDivider};
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
.handle {
font-size: 20px;
font-weight: bold;
flex-grow: 1;
padding-left: 16px;
}
.close {
font-size: 16px;
color: ${Colors.primary};
cursor: pointer;
padding-right: 16px;
}
}
.content {
min-height: 0;
width: 100%;
overflow: scroll;
display: flex;
flex-direction: row;
justify-content: center;
padding-top: 32px;
.logo {
position: relative;
width: 20vw;
margin-right: 32px;
}
}
.view {
width: 100%;
height: 100%;
overflow: scroll;
display: flex;
flex-direction: column;
align-items: center;
.title {
font-size: 18px;
font-weight: bold;
}
.logo {
position: relative;
width: 80%;
margin-bottom: 16px;
}
.section {
width: 100%;
color: ${Colors.grey};
padding-top: 16px;
font-size: 12px;
display: flex;
justify-content: center;
}
}
.details {
display: flex;
flex-direction: column;
.name {
display: flex;
flex-direction: row;
align-items: center;
.data {
padding-right: 8px;
font-size: 24px;
font-weight: bold;
}
}
.location {
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 8px;
.data {
padding-left: 8px;
}
}
.description {
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 8px;
.data {
padding-left: 8px;
}
}
}
`

View File

@ -0,0 +1,70 @@
import { useContext, useState, useEffect } from 'react';
import { CardContext } from 'context/CardContext';
import { ProfileContext } from 'context/ProfileContext';
import { ViewportContext } from 'context/ViewportContext';
import { getListingImageUrl } from 'api/getListingImageUrl';
export function useContact(guid, listing) {
const [state, setState] = useState({
logo: null,
name: null,
location: null,
description: null,
handle: null,
removed: false,
init: false,
});
const card = useContext(CardContext);
const profile = useContext(ProfileContext);
const viewport = useContext(ViewportContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
let logo, name, location, description, handle;
let contact = card.actions.getCardByGuid(guid);
if (contact) {
let cardProfile = contact?.data?.cardProfile;
if (cardProfile.node != profile.state.profile.node) {
handle = cardProfile.handle + '@' + cardProfile.node;
}
else {
handle = cardProfile.handle;
}
logo = card.actions.getImageUrl(contact.id);
name = cardProfile.name;
location = cardProfile.location;
description = cardProfile.description;
}
else if (listing) {
if (listing.node != profile.state.profile.node) {
handle = listing.handle + '@' + listing.node;
}
else {
handle = listing.handle;
}
logo = listing.imageSet ? getListingImageUrl(listing.node, listing.guid) : null;
name = listing.name;
location = listing.location;
description = listing.description;
}
else {
updateState({ removed: true });
}
updateState({ init: true, logo, name, location, description, handle });
}, [card, guid, listing]);
useEffect(() => {
updateState({ display: viewport.state.display });
}, [viewport]);
const actions = {
};
return { state, actions };
}

View File

@ -35,6 +35,7 @@ export const IdentityWrapper = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
line-height: 16px;
.name {
font-size: 14px;

View File

@ -1,4 +1,45 @@
import { Modal, Button, Drawer, Input, List } from 'antd';
import { ListingWrapper } from './Listing.styled';
import { DatabaseOutlined, SearchOutlined } from '@ant-design/icons';
import { useListing } from './useListing.hook';
import { ListingItem } from './listingItem/ListingItem';
export function Listing({ openContact }) {
return <div onClick={() => openContact('asdf', 'qwer')}>LISTING</div>
const { state, actions } = useListing();
const getListing = async () => {
try {
await actions.getListing();
}
catch(err) {
console.log(err);
Modal.error({
title: 'Communication Error',
content: 'Please confirm your server name.',
});
}
}
return (
<ListingWrapper>
<div class="search">
<div class="node">
<Input bordered={false} allowClear={true} placeholder="Server"
prefix={<DatabaseOutlined />} value={state.node} spellCheck="false"
disabled={state.disabled} onChange={(e) => actions.onNode(e.target.value)} />
</div>
<div class="inline">
<Button type="text" icon={<SearchOutlined />} loading={state.busy} onClick={getListing}></Button>
</div>
</div>
<div class="view">
<List local={{ emptyText: '' }} itemLayout="horizontal" dataSource={state.contacts} gutter="0"
renderItem={item => (
<ListingItem item={item} node={state.node} open={openContact} />
)} />
</div>
</ListingWrapper>
);
}

View File

@ -0,0 +1,45 @@
import styled from 'styled-components';
import Colors from 'constants/Colors';
export const ListingWrapper = styled.div`
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
background-color: ${Colors.card};
.view {
min-height: 0;
overflow: scroll;
flex-grow: 1;
}
.search {
border-bottom: 1px solid ${Colors.divider};
display: flex;
flex-direction: row;
height: 48px;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
.node {
border: 1px solid ${Colors.divider};
background-color: ${Colors.white};
border-radius: 8px;
flex-grow: 1;
}
.inline {
padding-left: 8px;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
`;

View File

@ -0,0 +1,18 @@
import { ListingItemWrapper } from './ListingItem.styled';
import { useListingItem } from './useListingItem.hook';
import { Logo } from 'logo/Logo';
export function ListingItem({ item, node, open }) {
const { state, actions } = useListingItem(node, item);
return (
<ListingItemWrapper onClick={() => open(item.guid, item)}>
<Logo url={state.logo} width={32} height={32} radius={8} />
<div class="details">
<div class="name">{ state.name }</div>
<div class="handle">{ state.handle }</div>
</div>
</ListingItemWrapper>
);
}

View File

@ -0,0 +1,41 @@
import styled from 'styled-components';
import Colors from 'constants/Colors';
export const ListingItemWrapper = styled.div`
height: 48px;
width: 100%;
display: flex;
align-items: center;
border-bottom: 1px solid ${Colors.divider};
padding-left: 16px;
padding-right: 16px;
&:hover {
background-color: ${Colors.formHover};
cursor: pointer;
}
.details {
flex-grow: 1;
display: flex;
flex-direction: column;
padding-left: 16px;
justify-content: center;
min-width: 0;
.name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 15px;
}
.handle {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 12px;
}
}
`

View File

@ -0,0 +1,21 @@
import { useContext, useState, useEffect } from 'react';
import { getListingImageUrl } from 'api/getListingImageUrl';
export function useListingItem(server, item) {
const [state, setState] = useState({
logo: item.imageSet ? getListingImageUrl(server, item.guid) : null,
name: item.name,
handle: item.handle,
});
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const actions = {
};
return { state, actions };
}

View File

@ -0,0 +1,54 @@
import { useContext, useState, useEffect } from 'react';
import { ProfileContext } from 'context/ProfileContext';
import { getListing } from 'api/getListing';
export function useListing() {
const [state, setState] = useState({
contacts: [],
node: null,
busy: false,
disabled: true,
});
const profile = useContext(ProfileContext);
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
}
const actions = {
onNode: (value) => {
updateState({ node: value });
},
getListing: async () => {
updateState({ busy: true });
try {
let contacts = await getListing(state.node);
console.log(contacts);
let filtered = contacts.filter(contact => (contact.guid !== profile.state.profile.guid));
console.log(filtered);
let sorted = filtered.sort((a, b) => {
if (a?.name < b?.name) {
return -1;
}
return 1;
});
console.log(sorted);
updateState({ busy: false, contacts: sorted });
}
catch (err) {
console.log(err);
updateState({ busy: false });
throw new Error("failed to list contacts");
}
},
};
useEffect(() => {
let node = profile?.state?.profile?.node;
updateState({ disabled: node == null || node == '', node });
}, [profile]);
return { state, actions };
}

View File

@ -1,14 +1,14 @@
import { useContext, useState, useEffect, useRef } from 'react';
import { CardContext } from 'context/CardContext';
import { StoreContext } from 'context/StoreContext';
import { ViewportContext } from 'context/ViewportContext';
export function useSession() {
const [state, setState] = useState({
cardUpdated: false,
contactGuid: null,
contactNode: null,
listingNode: null,
contactListing: null,
conversation: false,
details: false,
cards: false,
@ -20,6 +20,7 @@ export function useSession() {
const card = useContext(CardContext);
const store = useContext(StoreContext);
const viewport = useContext(ViewportContext);
const storeStatus = useRef(null);
const cardStatus = useRef(null);
@ -28,6 +29,10 @@ export function useSession() {
setState((s) => ({ ...s, ...value }));
}
useEffect(() => {
updateState({ display: viewport.state.display });
}, [viewport]);
useEffect(() => {
const contacts = Array.from(card.state.cards.values());
@ -62,14 +67,14 @@ export function useSession() {
closeCards: () => {
updateState({ cards: false });
},
openListing: (listingNode) => {
updateState({ listing: true, listingNode });
openListing: () => {
updateState({ listing: true });
},
closeListing: () => {
updateState({ listing: false });
},
openContact: (contactGuid, contactNode) => {
updateState({ contact: true, contactGuid, contactNode });
openContact: (contactGuid, contactListing) => {
updateState({ contact: true, contactGuid, contactListing });
},
closeContact: () => {
updateState({ contact: false });