adding dark mode to contact page

This commit is contained in:
Roland Osborne 2024-02-28 13:41:33 -08:00
parent d245081ecf
commit 5db44ebaff
15 changed files with 312 additions and 194 deletions

View File

@ -65,6 +65,7 @@ export const LightTheme = {
inputBorder: '#888888', inputBorder: '#888888',
sectionBorder: '#bbbbbb', sectionBorder: '#bbbbbb',
headerBorder: '#aaaaaa', headerBorder: '#aaaaaa',
drawerBorder: '#cccccc',
}; };
export const DarkTheme = { export const DarkTheme = {
@ -94,5 +95,6 @@ export const DarkTheme = {
inputBorder: '#aaaaaa', inputBorder: '#aaaaaa',
sectionBorder: '#777777', sectionBorder: '#777777',
headerBorder: '#aaaaaa', headerBorder: '#aaaaaa',
drawerBorder: '#444444',
}; };

View File

@ -52,6 +52,7 @@ export function useCardContext() {
}; };
const resyncCard = async (cardId) => { const resyncCard = async (cardId) => {
let success = true;
if (!syncing.current) { if (!syncing.current) {
syncing.current = true; syncing.current = true;
@ -68,10 +69,12 @@ export function useCardContext() {
} }
catch(err) { catch(err) {
console.log(err); console.log(err);
success = false;
} }
syncing.current = false; syncing.current = false;
await sync(); await sync();
} }
return success;
} }
const sync = async () => { const sync = async () => {
@ -359,7 +362,7 @@ export function useCardContext() {
await resync(); await resync();
}, },
resyncCard: async (cardId) => { resyncCard: async (cardId) => {
await resyncCard(cardId); return await resyncCard(cardId);
}, },
} }

View File

@ -192,7 +192,7 @@ export function Session() {
</div> </div>
)} )}
{ state.contact && ( { state.contact && (
<div class="reframe"> <div class="reframe base">
<Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} /> <Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} />
</div> </div>
)} )}
@ -212,9 +212,9 @@ export function Session() {
{ state.cards && ( { state.cards && (
<div class="reframe"> <div class="reframe">
<Cards closeCards={closeCards} openContact={actions.openContact} openChannel={openConversation} openListing={actions.openListing} /> <Cards closeCards={closeCards} openContact={actions.openContact} openChannel={openConversation} openListing={actions.openListing} />
<Drawer bodyStyle={drawerStyle} visible={state.listing} closable={false} <Drawer bodyStyle={drawerStyle} visible={state.listing} closable={false} getContainer={false}
onClose={actions.closeListing} getContainer={false} height={'100%'} onClose={actions.closeListing} height={'100%'} width={'80%'}
style={{ width: '80%', position: 'absolute', overflow: 'hidden' }}> style={{ position: 'absolute', overflow: 'hidden' }}>
<Listing closeListing={actions.closeListing} openContact={actions.openContact} /> <Listing closeListing={actions.closeListing} openContact={actions.openContact} />
</Drawer> </Drawer>
</div> </div>
@ -318,7 +318,7 @@ export function Session() {
</div> </div>
)} )}
{ state.contact && ( { state.contact && (
<div class="reframe"> <div class="reframe base">
<Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} /> <Contact close={actions.closeContact} guid={state.contactGuid} listing={state.contactListing} />
</div> </div>
)} )}

View File

@ -239,10 +239,15 @@ export const SessionWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.base {
background-color: ${props => props.theme.frameArea};
}
.top { .top {
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;
} }
.bottom { .bottom {
height: 40px; height: 40px;
position: relative; position: relative;

View File

@ -1,5 +1,5 @@
import { AccountWrapper } from './Account.styled'; import { AccountWrapper } from './Account.styled';
import { RightOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { SettingOutlined } from '@ant-design/icons'; import { SettingOutlined } from '@ant-design/icons';
import { AccountAccess } from './profile/accountAccess/AccountAccess'; import { AccountAccess } from './profile/accountAccess/AccountAccess';
import { useAccount } from './useAccount.hook'; import { useAccount } from './useAccount.hook';
@ -13,7 +13,7 @@ export function Account({ closeAccount, openProfile }) {
<div className="header"> <div className="header">
<div className="label">{state.strings.settings}</div> <div className="label">{state.strings.settings}</div>
<div className="dismiss" onClick={closeAccount}> <div className="dismiss" onClick={closeAccount}>
<RightOutlined /> <CloseOutlined />
</div> </div>
</div> </div>
<div className="content"> <div className="content">

View File

@ -4,7 +4,7 @@ import { LogoutContent, ProfileWrapper, ProfileDetailsWrapper, ProfileImageWrapp
import { useProfile } from './useProfile.hook'; import { useProfile } from './useProfile.hook';
import { Logo } from 'logo/Logo'; import { Logo } from 'logo/Logo';
import { AccountAccess } from './accountAccess/AccountAccess'; import { AccountAccess } from './accountAccess/AccountAccess';
import { LogoutOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { LogoutOutlined, CloseOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
import Cropper from 'react-easy-crop'; import Cropper from 'react-easy-crop';
export function Profile({ closeProfile }) { export function Profile({ closeProfile }) {
@ -82,7 +82,7 @@ export function Profile({ closeProfile }) {
<div className="middleHeader"> <div className="middleHeader">
<div className="handle">{ state.handle }</div> <div className="handle">{ state.handle }</div>
<div className="close" onClick={closeProfile}> <div className="close" onClick={closeProfile}>
<RightOutlined /> <CloseOutlined />
</div> </div>
</div> </div>
)} )}

View File

@ -97,7 +97,6 @@ export const ProfileWrapper = styled.div`
justify-content: center; justify-content: center;
padding-top: 64px; padding-top: 64px;
padding-left: 32px; padding-left: 32px;
align-items: center;
} }
.rightContent { .rightContent {
@ -110,6 +109,10 @@ export const ProfileWrapper = styled.div`
border-radius: 4px; border-radius: 4px;
padding: 8px; padding: 8px;
background-color: ${props => props.theme.selectedArea}; background-color: ${props => props.theme.selectedArea};
.details {
align-items: center;
}
} }
.rightAccess { .rightAccess {
@ -126,7 +129,6 @@ export const ProfileWrapper = styled.div`
.details { .details {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: 16px;
padding-left: 16px; padding-left: 16px;
padding-right: 16px; padding-right: 16px;
@ -140,7 +142,6 @@ export const ProfileWrapper = styled.div`
flex-direction: row; flex-direction: row;
align-items: baseline; align-items: baseline;
cursor: pointer; cursor: pointer;
justify-content: center;
&:hover .icon { &:hover .icon {
color: ${props => props.theme.linkText}; color: ${props => props.theme.linkText};
@ -176,6 +177,7 @@ export const ProfileWrapper = styled.div`
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
padding-top: 8px; padding-top: 8px;
max-width: 500px;
.data { .data {
padding-left: 8px; padding-left: 8px;

View File

@ -1,6 +1,6 @@
import { Input, Modal, List, Button } from 'antd'; import { Input, Modal, List, Button } from 'antd';
import { CardsWrapper } from './Cards.styled'; import { CardsWrapper } from './Cards.styled';
import { SortAscendingOutlined, RightOutlined, UserOutlined, SearchOutlined } from '@ant-design/icons'; import { SortAscendingOutlined, CloseOutlined, UserOutlined, SearchOutlined } from '@ant-design/icons';
import { useCards } from './useCards.hook'; import { useCards } from './useCards.hook';
import { CardItem } from './cardItem/CardItem'; import { CardItem } from './cardItem/CardItem';
@ -62,7 +62,7 @@ export function Cards({ closeCards, openContact, openChannel, openListing }) {
{ state.display === 'xlarge' && ( { state.display === 'xlarge' && (
<div className="inline"> <div className="inline">
<div className="dismiss" onClick={closeCards} > <div className="dismiss" onClick={closeCards} >
<RightOutlined /> <CloseOutlined />
</div> </div>
</div> </div>
)} )}

View File

@ -31,6 +31,7 @@ export const CardsWrapper = styled.div`
border-bottom: 1px solid ${props => props.theme.sectionBorder}; border-bottom: 1px solid ${props => props.theme.sectionBorder};
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 48px;
.filter { .filter {
border: 1px solid ${props => props.theme.sectionBorder}; border: 1px solid ${props => props.theme.sectionBorder};

View File

@ -1,14 +1,17 @@
import { Modal } from 'antd'; import { Modal, Button, Tooltip } from 'antd';
import { ContactWrapper } from './Contact.styled'; import { ContactWrapper } from './Contact.styled';
import { useContact } from './useContact.hook'; import { useContact } from './useContact.hook';
import { Logo } from 'logo/Logo'; import { Logo } from 'logo/Logo';
import { DatabaseOutlined, CloseOutlined, RightOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons'; import { SyncOutlined, UserAddOutlined, UserDeleteOutlined, UserSwitchOutlined, StopOutlined, DeleteOutlined, DatabaseOutlined, CloseOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
export function Contact({ close, guid, listing }) { export function Contact({ close, guid, listing }) {
const [ modal, modalContext ] = Modal.useModal(); const [ modal, modalContext ] = Modal.useModal();
const { state, actions } = useContact(guid, listing, close); const { state, actions } = useContact(guid, listing, close);
console.log(state.busy);
const updateContact = async (action) => { const updateContact = async (action) => {
try { try {
await action(); await action();
@ -16,8 +19,9 @@ export function Contact({ close, guid, listing }) {
catch (err) { catch (err) {
console.log(err); console.log(err);
modal.error({ modal.error({
title: 'Failed to Update Contact', title: <span style={state.menuStyle}>{state.strings.operationFailed}</span>,
content: 'Please try again.', content: <span style={state.menuStyle}>{state.strings.tryAgain}</span>,
bodyStyle: { borderRadius: 8, padding: 16, ...state.menuStyle },
}); });
} }
} }
@ -25,119 +29,140 @@ export function Contact({ close, guid, listing }) {
return ( return (
<ContactWrapper> <ContactWrapper>
{ modalContext } { modalContext }
{ state.display === 'xlarge' && ( <div className={ state.display === 'small' || state.display === 'xlarge' ? 'frame' : 'drawer' }>
<div className="header"> { (state.display === 'xlarge' || state.display === 'small') && (
<div className="handle">{ state.handle }</div> <div className="header">
<div className="close" onClick={close}>
<RightOutlined />
</div>
</div>
)}
{ state.display !== 'xlarge' && (
<div className="view">
<div className="title">
<div className="close" />
<div className="handle">{ state.handle }</div> <div className="handle">{ state.handle }</div>
{ state.display === 'small' && ( <div className="close" onClick={close}>
<div className="close" onClick={close}> <CloseOutlined />
<CloseOutlined /> </div>
</div>
)}
{ state.display !== 'small' && (
<div className="close" onClick={close}>
<RightOutlined />
</div>
)}
</div> </div>
</div> )}
)} { (state.display !== 'xlarge' && state.display !== 'small') && (
<div className="top">{ state.handle }</div>
)}
<div className={ state.display === 'xlarge' ? 'midContent' : 'rightContent' }> <div className={ state.display === 'xlarge' ? 'midContent' : 'rightContent' }>
<div className="logo"> <div className="logo">
<Logo url={state.logo} width={'100%'} radius={8} /> { state.logoSet && (
</div> <Logo url={state.logo} width={'100%'} radius={8} />
<div className="details">
<div className="name">
{ state.name && (
<div className="data">{ state.name }</div>
)}
{ !state.name && (
<div className="data notset">name</div>
)} )}
</div> </div>
{ state.node && ( <div className="details">
<div className="name">
{ state.name && (
<div className="data">{ state.name }</div>
)}
{ !state.name && (
<div className="data notset">Name</div>
)}
</div>
{ state.node && (
<div className="location">
<DatabaseOutlined />
<div className="data">{ state.node }</div>
</div>
)}
<div className="location"> <div className="location">
<DatabaseOutlined /> <EnvironmentOutlined />
<div className="data">{ state.node }</div> { state.location && (
<div className="data">{ state.location }</div>
)}
{ !state.location && (
<div className="data notset">Location</div>
)}
</div>
<div className="description">
<BookOutlined />
{ state.description && (
<div className="data">{ state.description }</div>
)}
{ !state.description && (
<div className="data notset">Description</div>
)}
</div> </div>
)}
<div className="location">
<EnvironmentOutlined />
{ state.location && (
<div className="data">{ state.location }</div>
)}
{ !state.location && (
<div className="data notset">location</div>
)}
</div> </div>
<div className="description">
<BookOutlined />
{ state.description && (
<div className="data">{ state.description }</div>
)}
{ !state.description && (
<div className="data notset">description</div>
)}
</div>
{ state.status === 'connected' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.disconnect)}>Disconnect</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.disconnectRemove)}>Delete Contact</div>
</div>
)}
{ state.status === 'pending' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.confirmContact)}>Save Contact</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.connect)}>Save and Accept</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.remove)}>Ignore Request</div>
</div>
)}
{ state.status === 'request received' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.saveConnect)}>Accept Connection</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.disconnect)}>Ignore Request</div>
</div>
)}
{ state.status === 'request sent' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.disconnect)}>Cancel Request</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.disconnectRemove)}>Delete Contact</div>
</div>
)}
{ state.status === 'saved' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.connect)}>Request Connection</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.remove)}>Delete Contact</div>
</div>
)}
{ state.status === 'unsaved' && (
<div className="controls">
<div className={ state.buttonStatus } onClick={() => updateContact(actions.saveContact)}>Save Contact</div>
<div className={ state.buttonStatus } onClick={() => updateContact(actions.saveConnect)}>Save and Request</div>
</div>
)}
</div> </div>
</div>
<div className="footer"> <div className="actions">
<div className="status">Status: { state.status }</div> <div className="label">Actions</div>
<div className="controls">
{ state.status === 'connected' && (
<Tooltip placement="top" title="Disconnect Contact">
<Button className="button" type="primary" loading={state.busy} icon={<UserDeleteOutlined />} size="medium" onClick={() => updateContact(actions.disconnect)}>Disconnect</Button>
</Tooltip>
)}
{ state.status === 'connected' && (
<Tooltip placement="top" title="Delete Contact">
<Button className="button" type="primary" loading={state.busy} icon={<DeleteOutlined />} size="medium" onClick={() => updateContact(actions.disconnectRemove)}>Delete</Button>
</Tooltip>
)}
{ state.status === 'pending' && (
<Tooltip placement="top" title="Save Contact">
<Button className="button" type="primary" loading={state.busy} icon={<UserAddOutlined />} size="medium" onClick={() => updateContact(actions.confirmContact)}>Save</Button>
</Tooltip>
)}
{ state.status === 'pending' && (
<Tooltip placement="top" title="Save and Accept">
<Button className="button" type="primary" loading={state.busy} icon={<UserSwitchOutlined />} size="medium" onClick={() => updateContact(actions.connect)}>Connect</Button>
</Tooltip>
)}
{ state.status === 'pending' && (
<Tooltip placement="top" title="Ignore Request">
<Button className="button" type="primary" loading={state.busy} icon={<StopOutlined />} size="medium" onClick={() => updateContact(actions.remove)}>Cancel</Button>
</Tooltip>
)}
{ state.status === 'request received' && (
<Tooltip placement="top" title="Accept Connection">
<Button className="button" type="primary" loading={state.busy} icon={<UserSwitchOutlined />} size="medium" onClick={() => updateContact(actions.saveConnect)}>Connect</Button>
</Tooltip>
)}
{ state.status === 'request received' && (
<Tooltip placement="top" title="Ignore Request">
<Button className="button" type="primary" loading={state.busy} icon={<StopOutlined />} size="medium" onClick={() => updateContact(actions.disconnect)}>Cancel</Button>
</Tooltip>
)}
{ state.status === 'request sent' && (
<Tooltip placement="top" title="Cancel Request">
<Button className="button" type="primary" loading={state.busy} icon={<StopOutlined />} size="medium" onClick={() => updateContact(actions.disconnect)}>Cancel</Button>
</Tooltip>
)}
{ state.status === 'request sent' && (
<Tooltip placement="top" title="Delete Contact">
<Button className="button" type="primary" loading={state.busy} icon={<DeleteOutlined />} size="medium" onClick={() => updateContact(actions.disconnectRemove)}>Delete</Button>
</Tooltip>
)}
{ state.status === 'saved' && (
<Tooltip placement="top" title="Request Connection">
<Button className="button" type="primary" loading={state.busy} icon={<UserSwitchOutlined />} size="medium" onClick={() => updateContact(actions.connect)}>Connect</Button>
</Tooltip>
)}
{ state.status === 'saved' && (
<Tooltip placement="top" title="Delete Contact">
<Button className="button" type="primary" loading={state.busy} icon={<DeleteOutlined />} size="medium" onClick={() => updateContact(actions.remove)}>Delete</Button>
</Tooltip>
)}
{ state.status === 'unsaved' && (
<Tooltip placement="top" title="Save Contact">
<Button className="button" type="primary" loading={state.busy} icon={<UserAddOutlined />} size="medium" onClick={() => updateContact(actions.saveContact)}>Save</Button>
</Tooltip>
)}
{ state.status === 'unsaved' && (
<Tooltip placement="top" title="Save and Request">
<Button className="button" type="primary" loading={state.busy} icon={<UserSwitchOutlined />} size="medium" onClick={() => updateContact(actions.saveConnect)}>Connect</Button>
</Tooltip>
)}
{ state.offsync && (
<Tooltip placement="top" title="Resync Contact">
<Button className="button" type="primary" loading={state.busy} icon={<SyncOutlined />} size="medium" onClick={() => updateContact(actions.resync)}>Resync</Button>
</Tooltip>
)}
</div>
</div>
<div className="footer">
<div className="status">Status: { state.status }</div>
</div>
</div> </div>
</ContactWrapper> </ContactWrapper>
); );

View File

@ -1,34 +1,84 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Colors } from 'constants/Colors';
export const ContactWrapper = styled.div` export const ContactWrapper = styled.div`
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex;
flex-direction: column;
background-color: ${Colors.profileForm};
.frame {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
color: ${props => props.theme.mainText};
}
.drawer {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
border-left: 1px solid ${props => props.theme.drawerBorder};
background-color: ${props => props.theme.selectedArea};
color: ${props => props.theme.mainText};
}
.actions {
display: flex;
flex-grow: 1;
justify-content: center;
padding-top: 16px;
flex-direction: column;
align-items: center;
.label {
padding-top: 16px;
border-bottom: 1px solid ${props => props.theme.sectionBorder};
color: ${props => props.theme.hintText};
font-size: 12px;
width: 50%;
max-width: 300px;
display: flex;
align-items: center;
justify-content: center;
}
}
.top {
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
}
.header { .header {
margin-left: 16px; margin-left: 16px;
margin-right: 16px; margin-right: 16px;
height: 48px; height: 48px;
border-bottom: 1px solid ${Colors.profileDivider};
display: flex; display: flex;
border-bottom: 1px solid ${props => props.theme.headerBorder};
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center;
flex-shrink: 0; flex-shrink: 0;
.handle {
font-size: 20px;
font-weight: bold;
flex-grow: 1;
padding-left: 16px;
}
.handle { .handle {
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
flex-grow: 1;
padding-left: 16px;
} }
.close { .close {
font-size: 18px; font-size: 18px;
color: ${Colors.primary}; color: ${props => props.theme.hintText};
cursor: pointer; cursor: pointer;
padding-right: 16px; padding-right: 16px;
} }
@ -51,6 +101,18 @@ export const ContactWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
.details {
align-items: center;
padding-left: 16px;
padding-right: 16px;
max-width: 400px;
}
.logo {
margin-top: 16px;
margin-bottom: 8px;
}
} }
.logo { .logo {
@ -58,6 +120,8 @@ export const ContactWrapper = styled.div`
width: 20vw; width: 20vw;
margin-right: 32px; margin-right: 32px;
margin-left: 32px; margin-left: 32px;
width: 192px;
height: 192px;
} }
.details { .details {
@ -66,13 +130,14 @@ export const ContactWrapper = styled.div`
.notset { .notset {
font-style: italic; font-style: italic;
color: ${Colors.grey}; color: ${props => props.theme.hintText};
} }
.name { .name {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
padding-bottom: 8px;
.data { .data {
padding-right: 8px; padding-right: 8px;
@ -127,7 +192,7 @@ export const ContactWrapper = styled.div`
} }
.close { .close {
color: ${Colors.primary}; color: ${props => props.theme.mainText};
cursor: pointer; cursor: pointer;
width: 64px; width: 64px;
display: flex; display: flex;
@ -140,19 +205,17 @@ export const ContactWrapper = styled.div`
.controls { .controls {
padding-top: 16px; padding-top: 16px;
padding-bottom: 16px; padding-bottom: 16px;
display: flex;
flex-direction: row;
gap: 16px;
.button { .button {
width: 192px;
padding-top: 2px;
padding-bottom: 2px;
margin-top: 16px;
display: flex; display: flex;
flex-direction: row; }
align-items: center;
justify-content: center; .anticon {
border-radius: 2px; font-size: 18px;
color: ${Colors.white}; padding-top: 2px;
background-color: ${Colors.primary};
} }
.label { .label {
@ -173,12 +236,11 @@ export const ContactWrapper = styled.div`
} }
.footer { .footer {
flex-grow: 1;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
padding-bottom: 16px; padding-bottom: 16px;
color: ${Colors.grey}; color: ${props => props.theme.hintText};
} }
` `

View File

@ -7,7 +7,9 @@ import { getCardByGuid } from 'context/cardUtil';
export function useContact(guid, listing, close) { export function useContact(guid, listing, close) {
const [state, setState] = useState({ const [state, setState] = useState({
offsync: false,
logo: null, logo: null,
logoSet: false,
name: null, name: null,
cardId: null, cardId: null,
location: null, location: null,
@ -17,6 +19,8 @@ export function useContact(guid, listing, close) {
status: null, status: null,
busy: false, busy: false,
buttonStatus: 'button idle', buttonStatus: 'button idle',
strings: {},
menuStyle: {},
}); });
const card = useContext(CardContext); const card = useContext(CardContext);
@ -47,22 +51,24 @@ export function useContact(guid, listing, close) {
const { imageSet, name, location, description, handle, node } = profile; const { imageSet, name, location, description, handle, node } = profile;
const status = statusMap(detail.status); const status = statusMap(detail.status);
const cardId = contact.id; const cardId = contact.id;
const offsync = contact.offsync;
const logo = imageSet ? card.actions.getCardImageUrl(cardId) : null; const logo = imageSet ? card.actions.getCardImageUrl(cardId) : null;
updateState({ logo, name, location, description, handle, node, status, cardId }); updateState({ logoSet: true, offsync, logo, name, location, description, handle, node, status, cardId });
} }
else if (listing) { else if (listing) {
const { logo, name, location, description, handle, node } = listing; const { logo, name, location, description, handle, node } = listing;
updateState({ logo, name, location, description, handle, node, status: 'unsaved', cardId: null }); updateState({ logoSet: true, logo, name, location, description, handle, node, status: 'unsaved', cardId: null });
} }
else { else {
updateState({ logo: null, name: null, cardId: null, location: null, description: null, handle: null, updateState({ logoSet: true, logo: null, name: null, cardId: null, location: null, description: null, handle: null,
status: null }); status: null });
} }
// eslint-disable-next-line // eslint-disable-next-line
}, [card.state, guid, listing]); }, [card.state, guid, listing]);
useEffect(() => { useEffect(() => {
updateState({ display: settings.state.display }); const { display, strings, menuStyle } = settings.state;
updateState({ display, strings, menuStyle });
}, [settings.state]); }, [settings.state]);
const applyAction = async (action) => { const applyAction = async (action) => {
@ -149,6 +155,14 @@ export function useContact(guid, listing, close) {
close(); close();
}); });
}, },
resync: async () => {
await applyAction(async () => {
const success = await card.actions.resyncCard(state.cardId);
if (!success) {
throw new Error("resync failed");
}
});
},
remove: async () => { remove: async () => {
await applyAction(async () => { await applyAction(async () => {
await card.actions.removeCard(state.cardId); await card.actions.removeCard(state.cardId);

View File

@ -26,50 +26,52 @@ export function Listing({ closeListing, openContact }) {
return ( return (
<ListingWrapper> <ListingWrapper>
{ modalContext } { modalContext }
<div className="search"> <div className={ state.display === 'small' ? 'frame' : 'drawer' }>
{ !state.showFilter && ( <div className="search">
<div className="showfilter" onClick={actions.showFilter}> { !state.showFilter && (
<FilterOutlined /> <div className="showfilter" onClick={actions.showFilter}>
</div> <FilterOutlined />
)} </div>
{ state.showFilter && ( )}
<div className="hidefilter" onClick={actions.hideFilter}>
<FilterOutlined />
</div>
)}
<div className="params">
<div className="node">
<Input className="nodeControl" bordered={false} placeholder="Server"
prefix={<DatabaseOutlined />} value={state.node} spellCheck="false"
disabled={state.disabled} onChange={(e) => actions.onNode(e.target.value)} />
</div>
{ state.showFilter && ( { state.showFilter && (
<div className="hidefilter" onClick={actions.hideFilter}>
<FilterOutlined />
</div>
)}
<div className="params">
<div className="node"> <div className="node">
<Input className="nodeControl" bordered={false} placeholder="Username" <Input className="nodeControl" bordered={false} placeholder="Server"
prefix={<UserOutlined />} value={state.username} spellCheck="false" prefix={<DatabaseOutlined />} value={state.node} spellCheck="false"
onChange={(e) => actions.setUsername(e.target.value)} /> disabled={state.disabled} onChange={(e) => actions.onNode(e.target.value)} />
</div>
{ state.showFilter && (
<div className="node">
<Input className="nodeControl" bordered={false} placeholder="Username"
prefix={<UserOutlined />} value={state.username} spellCheck="false"
onChange={(e) => actions.setUsername(e.target.value)} />
</div>
)}
</div>
<div className="inline">
<Button type="text" icon={<SearchOutlined />} loading={state.busy} onClick={getListing}></Button>
</div>
{ state.display === 'small' && (
<div className="inline">
<Button type="text" icon={<CloseOutlined />} onClick={closeListing}></Button>
</div> </div>
)} )}
</div> </div>
<div className="inline"> <div className="view">
<Button type="text" icon={<SearchOutlined />} loading={state.busy} onClick={getListing}></Button> { state.contacts.length > 0 && (
<List local={{ emptyText: '' }} itemLayout="horizontal" dataSource={state.contacts} gutter="0"
renderItem={item => (
<ListingItem item={item} open={() => openContact(item.guid, item)} />
)} />
)}
{ state.contacts.length === 0 && (
<div className="empty">No Contacts</div>
)}
</div> </div>
{ state.display === 'small' && (
<div className="inline">
<Button type="text" icon={<CloseOutlined />} onClick={closeListing}></Button>
</div>
)}
</div>
<div className="view">
{ state.contacts.length > 0 && (
<List local={{ emptyText: '' }} itemLayout="horizontal" dataSource={state.contacts} gutter="0"
renderItem={item => (
<ListingItem item={item} open={() => openContact(item.guid, item)} />
)} />
)}
{ state.contacts.length === 0 && (
<div className="empty">No Contacts</div>
)}
</div> </div>
</ListingWrapper> </ListingWrapper>
); );

View File

@ -1,23 +1,26 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Colors } from 'constants/Colors';
export const ListingWrapper = styled.div` export const ListingWrapper = styled.div`
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex;
flex-direction: column;
background-color: ${props => props.theme.itemArea};
color: ${props => props.theme.mainText};
.drawer { .drawer {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-left: 1px solid ${props => props.theme.sectionBorder}; display: flex;
border-left: 1px solid ${props => props.theme.drawerBorder};
flex-direction: column;
background-color: ${props => props.theme.itemArea};
color: ${props => props.theme.mainText};
} }
.screen { .frame {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
flex-direction: column;
background-color: ${props => props.theme.itemArea};
color: ${props => props.theme.mainText};
} }
.view { .view {

View File

@ -1,5 +1,4 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Colors } from 'constants/Colors';
export const ListingItemWrapper = styled.div` export const ListingItemWrapper = styled.div`
height: 48px; height: 48px;