displaying contact list

This commit is contained in:
balzack 2024-10-12 09:35:11 -07:00
parent da973b28af
commit d9bdb25079
8 changed files with 131 additions and 26 deletions

View File

@ -2,6 +2,45 @@
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
width: auto;
height: 100%;
cursor: default;
.details {
display: flex;
flex-direction: column;
flex-grow: 1;
padding-left: 8px;
padding-right: 8px;
}
.image {
width: auto;
height: 100%;
}
.nameSet {
font-size: 16px;
text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.nameUnset {
font-size: 16px;
text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-style: italic;
color: var(--mantine-color-text-9);
}
.handle {
font-size: 12px;
text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-weight: bold;
}
}

View File

@ -1,14 +1,24 @@
import React from 'react'
import { Text } from '@mantine/core';
import React, { ReactNode } from 'react'
import { Image, Text } from '@mantine/core';
import classes from './Card.module.css'
export function Card({imageUrl, name, handle, children} : { imageUrl: string, name: string, handle: string, children: ReactNode }) {
console.log("CHILDREN: ", children);
export function Card({ imageUrl, name, placeholder, handle, node, children, className }: { className: string, imageUrl: string, name: string, placeholder: string, handle: string, node: string, children: ReactNode }) {
return (
<div className={classes.card}>
<Text>CARD</Text>
{ children }
<div className={className}>
<div className={classes.card}>
<Image radius="sm" className={classes.image} src={imageUrl} />
<div className={classes.details}>
{ name && (
<Text className={classes.nameSet}>{ name }</Text>
)}
{ !name && (
<Text className={classes.nameUnset}>{ placeholder }</Text>
)}
<Text className={classes.handle}>{ node ? `${handle}/${node}` : handle }</Text>
</div>
{ children }
</div>
</div>
);
}

View File

@ -5,6 +5,7 @@
padding-top: 8px;
gap: 2px;
width: 100%;
height: 100vh;
.header {
display: flex;
@ -28,10 +29,20 @@
}
}
.card {
height: 48px;
padding: 4px;
.cards {
display: flex;
flex-direction: column;
width: 100%;
border: 1px solid white;
overflow: auto;
.card {
width: 100%;
height: 48px;
padding-top: 8px;
padding-bottom: 8px;
padding-right: 16px;
padding-left: 16px;
border-bottom: 1px solid var(--mantine-color-text-9);
}
}
}

View File

@ -8,11 +8,18 @@ import { Card } from '../card/Card';
export function Contacts() {
const { state, actions } = useContacts();
const cards = state.filtered.map((card) => <Card className={classes.card} key={card.cardId} imageUrl={card.imageUrl} name={card.name} handle={card.handle} node={card.node} placeholder={state.strings.name} children={<IconSearch />} />)
return (
<div className={classes.contacts}>
<div className={classes.header}>
<ActionIcon variant="subtle">
<IconSortDescending size={32} />
{ state.sortAsc && (
<IconSortAscending size={32} onClick={actions.toggleSort} />
)}
{ !state.sortAsc && (
<IconSortDescending size={32} onClick={actions.toggleSort} />
)}
</ActionIcon>
<TextInput
className={classes.input}
@ -20,17 +27,15 @@ export function Contacts() {
leftSectionPointerEvents="none"
leftSection={<IconSearch size={20} />}
placeholder={state.strings.contacts}
value={state.filter}
onChange={(event) => actions.setFilter(event.currentTarget.value)}
/>
<Button className={classes.add} leftSection={<IconUserPlus size={20} />}>
{state.strings.add}
</Button>
</div>
<div className={classes.card}>
<Card>
<IconSearch />
</Card>
</div>
<div className={classes.cards}>{ cards }</div>
</div>
);
}

View File

@ -9,6 +9,10 @@ export function useContacts() {
const display = useContext(DisplayContext) as ContextType
const [state, setState] = useState({
strings: display.state.strings,
cards: [] as Card[],
filtered: [] as Card[],
sortAsc: false,
filter: '',
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -16,24 +20,56 @@ export function useContacts() {
setState((s) => ({ ...s, ...value }))
}
const compare = (a: Channel, b: Channel) => {
const aval = `${a.handle}/${a.node}`;
const bval = `${b.handle}/${b.node}`;
if (aval < bval) {
return state.sortAsc ? 1 : -1;
} else if (aval > bval) {
return state.sortAsc ? -1 : 1;
}
return 0;
}
const select = (c: Channel) => {
if (!state.filter) {
return true;
}
const value = state.filter.toLowerCase();
if (c.name && c.name.toLowerCase().includes(value)) {
return true;
}
const handle = c.node ? `${c.handle}/${c.node}` : c.handle;
if (handle.toLowerCase().includes(value)) {
return true;
}
return false;
}
useEffect(() => {
const contact = app.state.session?.getContact();
const setCards = (cards: Card[]) => {
console.log("CARDS", cards);
updateState({ cards });
};
contact.addCardListener(setCards);
const setChannels = ({ cardId, channels }: { cardId: string, channels: Channel[] }) => {
console.log("CHANNELS :", cardId, channels);
};
contact.addChannelListener(null, setChannels);
return () => {
contact.removeCardListener(setCards);
contact.removeChannelListener(setChannels);
}
}, [])
useEffect(() => {
const filtered = state.cards.sort(compare).filter(select);
updateState({ filtered });
}, [state.sortAsc, state.filter, state.cards]);
const actions = {
toggleSort: () => {
const sortAsc = !state.sortAsc;
updateState({ sortAsc });
},
setFilter: (filter) => {
updateState({ filter });
},
}
return { state, actions }

View File

@ -64,6 +64,7 @@
.handle {
text-wrap: nowrap;
text-overflow: ellipsis;
text-align: center;
overflow: hidden;
width: 100%;
}

View File

@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3';
import type { Contact, Logging, Focus } from './api';
import type { FocusModule } from './focus';
import type { Card, Topic, Asset, Tag, Profile, Participant } from './types';
import type { CardEntity, avatar } from './entities';
import { type CardEntity, avatar } from './entities';
import type {
ArticleRevision,
ArticleDetail,
@ -835,6 +835,7 @@ export class ContactModule implements Contact {
private setCard(cardId: string, item: CardItem): Card {
const { node, secure, token } = this;
const { profile, detail } = item;
return {
cardId,
@ -847,6 +848,7 @@ export class ContactModule implements Contact {
name: profile.name,
description: profile.description,
location: profile.location,
imageUrl: profile.imageSet ? getCardImageUrl(node, secure, token, cardId, item.profile.revision) : avatar,
imageSet: profile.imageSet,
node: profile.node
};

View File

@ -9,6 +9,7 @@ export type Card = {
name: string;
description: string;
location: string;
imageUrl: string;
imageSet: boolean;
version: string;
node: string;