From 489239b10b4a96e75556302f9938199e6f5ce680 Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Sat, 9 Apr 2022 01:36:03 -0700 Subject: [PATCH] rendering basic channel list --- doc/api.oa3 | 4 +- net/server/internal/modelUtil.go | 2 +- net/server/internal/models.go | 7 +- net/server/internal/ucChannelShare_test.go | 6 +- net/web/src/Api/getCardImageUrl.js | 4 + net/web/src/AppContext/useAppContext.hook.js | 2 + .../Channels/ChannelItem/ChannelItem.jsx | 25 ++++ .../ChannelItem/ChannelItem.styled.js | 18 +++ .../ChannelItem/ChannelLabel/ChannelLabel.jsx | 72 ++++++++++ .../ChannelLabel/ChannelLabel.styled.js | 22 +++ .../ChannelLabel/useChannelLabel.hook.js | 27 ++++ .../ChannelItem/ChannelLogo/ChannelLogo.jsx | 136 ++++++++++++++++++ .../ChannelLogo/ChannelLogo.styled.js | 90 ++++++++++++ .../ChannelLogo/useChannelLogo.hook.js | 32 +++++ .../SideBar/Contacts/Channels/Channels.jsx | 12 +- .../Contacts/Channels/Channels.styled.js | 14 +- 16 files changed, 440 insertions(+), 33 deletions(-) create mode 100644 net/web/src/Api/getCardImageUrl.js create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.jsx create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.styled.js create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/useChannelLabel.hook.js create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.jsx create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.styled.js create mode 100644 net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/useChannelLogo.hook.js diff --git a/doc/api.oa3 b/doc/api.oa3 index 4f4063f3..3c96fbe0 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -3495,7 +3495,9 @@ components: contacts: $ref: '#/components/schemas/ChannelContacts' members: - $ref: '#/components/schemas/ChannelMembers' + type: array + item: + type: string ChannelContacts: type: object diff --git a/net/server/internal/modelUtil.go b/net/server/internal/modelUtil.go index 7638bb6c..c4c20e55 100644 --- a/net/server/internal/modelUtil.go +++ b/net/server/internal/modelUtil.go @@ -204,7 +204,7 @@ func getChannelModel(slot *store.ChannelSlot, showData bool, showList bool) *Cha Created: slot.Channel.Created, Updated: slot.Channel.Updated, Contacts: contacts, - Members: &ChannelMembers{ members }, + Members: members, }, }, } diff --git a/net/server/internal/models.go b/net/server/internal/models.go index d5bb272a..a7f3673b 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -190,12 +190,7 @@ type ChannelDetail struct { Contacts *ChannelContacts `json:"contacts,omitempty"` - Members *ChannelMembers `json:"members,omitempty"` -} - -type ChannelMembers struct { - - Members []string `json:"members,omitempty"` + Members []string `json:"members"` } type ChannelParams struct { diff --git a/net/server/internal/ucChannelShare_test.go b/net/server/internal/ucChannelShare_test.go index e3fdde59..3151e9e8 100644 --- a/net/server/internal/ucChannelShare_test.go +++ b/net/server/internal/ucChannelShare_test.go @@ -83,8 +83,8 @@ func TestChannelShare(t *testing.T) { assert.NoError(t, ApiTestMsg(GetChannel, "GET", "/content/channels/{channelId}", ¶ms, nil, APP_TOKENCONTACT, set.B.A.Token, channel, nil)) assert.Equal(t, "channeldatatype", channel.Data.ChannelDetail.DataType) - assert.Equal(t, 1, len(channel.Data.ChannelDetail.Members.Members)) - assert.Equal(t, set.B.Guid, channel.Data.ChannelDetail.Members.Members[0]) + assert.Equal(t, 1, len(channel.Data.ChannelDetail.Members)) + assert.Equal(t, set.B.Guid, channel.Data.ChannelDetail.Members[0]) assert.Nil(t, channel.Data.ChannelDetail.Contacts) // get revision @@ -154,7 +154,7 @@ func TestChannelShare(t *testing.T) { assert.NoError(t, ApiTestMsg(GetChannel, "GET", "/content/channels/{channelId}", ¶ms, nil, APP_TOKENCONTACT, set.C.A.Token, channel, nil)) assert.Equal(t, "channeldatatype", channel.Data.ChannelDetail.DataType) - assert.Equal(t, 2, len(channel.Data.ChannelDetail.Members.Members)) + assert.Equal(t, 2, len(channel.Data.ChannelDetail.Members)) assert.Nil(t, channel.Data.ChannelDetail.Contacts) // reset notification diff --git a/net/web/src/Api/getCardImageUrl.js b/net/web/src/Api/getCardImageUrl.js new file mode 100644 index 00000000..40e130e6 --- /dev/null +++ b/net/web/src/Api/getCardImageUrl.js @@ -0,0 +1,4 @@ +export function getCardImageUrl(token, cardId, revision) { + return `/contact/cards/${cardId}/profile/image?agent=${token}&revision=${revision}` +} + diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 766f260f..bcdbd525 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -223,6 +223,7 @@ export function useAppContext() { profileRevision.current = null; groupRevision.current = null; cardRevision.current = null; + channels.current = new Map(); cards.current = new Map(); groups.current = new Map(); setState({}); @@ -376,6 +377,7 @@ export function useAppContext() { }, []); if (!state) { +console.log("STATE IS NULL"); return {} } if (state.access === 'user') { diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx new file mode 100644 index 00000000..67d4afc4 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.jsx @@ -0,0 +1,25 @@ +import React, { useEffect, useState } from 'react' +import { ChannelItemWrapper } from './ChannelItem.styled'; +import { ChannelLogo } from './ChannelLogo/ChannelLogo'; +import { ChannelLabel } from './ChannelLabel/ChannelLabel'; + +export function ChannelItem({ item }) { + + // if 0 or 5+ render number in big border + // if 2 renber other in big border + // if 3 or 4 render others in small borders + + // subject, hosting, username list, last msg, updated time, unread flag + + const onSelect = () => { + console.log(item); + } + + return ( + onSelect()}> + + + + ) +} + diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js new file mode 100644 index 00000000..f306a1ff --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelItem.styled.js @@ -0,0 +1,18 @@ +import styled from 'styled-components'; +import { List } from 'antd'; + +export const ChannelItemWrapper = styled(List.Item)` + padding-left: 16px; + padding-right: 16px; + padding-top: 4px; + padding-bottom: 4px; + height: 48px; + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + &:hover { + background-color: #eeeeee; + } +`; + diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.jsx new file mode 100644 index 00000000..01474ffb --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.jsx @@ -0,0 +1,72 @@ +import React, { useState, useEffect } from 'react'; +import { useChannelLabel } from './useChannelLabel.hook'; +import { LabelWrapper } from './ChannelLabel.styled'; +import { HomeOutlined, DatabaseOutlined } from '@ant-design/icons'; + +export function ChannelLabel({ item }) { + + const [host, setHost] = useState(null); + const [members, setMembers] = useState([]); + const [subject, setSubject] = useState(''); + + const { state, actions } = useChannelLabel(); + + useEffect(() => { + + let contacts = []; + if (item?.guid) { + setHost(actions.getCard(item.guid)); + for (let member of item.channel.data.channelDetail.members) { + if (member != state.guid) { + contacts.push(actions.getCard(member)); + } + } + setMembers(contacts); + } + else { + setHost(null); + for (let member of item.channel.data.channelDetail.members) { + contacts.push(actions.getCard(member)); + } + setMembers(contacts); + } + + if (item?.channel?.data?.channelDetail?.data) { + let data = JSON.parse(item.channel.data.channelDetail.data); + if (data.subject != '' && data.subject != null) { + setSubject(data.subject); + return + } + } + + let names = '' + for (let contact of contacts) { + if (contact != null) { + if (names != '') { + names += ', '; + } + names += contact.data.cardProfile.handle; + } + } + if (names != '') { + names = '[' + names + ']'; + } + setSubject(names) + + }, [item, state]); + + const Host = () => { + if (host) { + return (
 { host.data.cardProfile.handle }
) + } + return + } + + return ( + +
{subject}
+
+
+ ) +} + diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.styled.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.styled.js new file mode 100644 index 00000000..bea82789 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/ChannelLabel.styled.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +export const LabelWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + padding-right: 8px; + overflow: hidden; + color: #444444; + + .subject { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: right; + } + + .host { + text-align: right; + } +`; diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/useChannelLabel.hook.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/useChannelLabel.hook.js new file mode 100644 index 00000000..f04a9061 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLabel/useChannelLabel.hook.js @@ -0,0 +1,27 @@ +import { useContext, useState, useEffect } from 'react'; +import { AppContext } from '../../../../../../AppContext/AppContext'; +import { getCardImageUrl } from '../../../../../../Api/getCardImageUrl'; + +export function useChannelLabel() { + + const [state, setState] = useState({ + guid: null + }); + + const app = useContext(AppContext); + + const actions = { + getCard: app?.actions?.getCard, + }; + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + useEffect(() => { + updateState({ guid: app?.state?.Data?.profile?.guid }) + }, [app]) + + return { state, actions }; +} + diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.jsx b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.jsx new file mode 100644 index 00000000..749946e9 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.jsx @@ -0,0 +1,136 @@ +import React, { useEffect, useState, useContext } from 'react' +import { DisconnectOutlined, UserOutlined } from '@ant-design/icons'; +import { LogoWrapper, Contact, Host, ChannelImage } from './ChannelLogo.styled'; +import { useChannelLogo } from './useChannelLogo.hook'; + +export function ChannelLogo({ item }) { + + const [home, setHome] = useState(false); + const [host, setHost] = useState(null); + const [members, setMembers] = useState([]); + + const { state, actions } = useChannelLogo(); + + useEffect(() => { + + if (item?.guid) { + setHome(false); + setHost(actions.getCard(item.guid)); + let contacts = []; + for (let member of item.channel.data.channelDetail.members) { + if (member != state.guid) { + contacts.push(actions.getCard(member)); + } + } + setMembers(contacts); + } + else { + setHome(true); + let contacts = []; + for (let member of item.channel.data.channelDetail.members) { + contacts.push(actions.getCard(member)); + } + setMembers(contacts); + } + + }, [item, state]); + + const Logo = ({card}) => { + if (card?.data?.cardProfile?.imageSet) { + let imageUrl = actions.getCardImageUrl(card?.id, card?.revision); + return + } + return + } + + if (members.length == 0 && home) { + return ( + +
+
+
+
+ ) + } + else if (members.length == 0 && !home) { + return ( + +
+
+
+
+ ) + } + else if (members.length == 1 && home) { + return ( + +
+
+
+
+ ) + } + else if (members.length == 1 && !home) { + return ( + +
+
+
+
+
+ ) + } + else if (members.length == 2 && home) { + return ( + +
+
+
+
+
+ ) + } + else if (members.length == 2 && !home) { + return ( + +
+
+
+
+
+
+ ) + } + else if (members.length == 3 && home) { + return ( + +
+
+
+
+
+
+ ) + } + else if (members.length > 2 && !home) { + return ( + +
+
+
{members.length}
+
+
+ ) + } + else if (members.length > 3 && home) { + return ( + +
+
{members.length}
+
+
+ ) + } + return <> +} + diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.styled.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.styled.js new file mode 100644 index 00000000..309106b7 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/ChannelLogo.styled.js @@ -0,0 +1,90 @@ +import styled from 'styled-components'; + +export const LogoWrapper = styled.div` + height: 48px; + width: 48px; + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + + .container { + display: flex; + align-items: center; + justify-content: center; + } + + .grid { + position: relative; + margin-left: 2px; + margin-right: 2px; + width: 44px; + height: 40px; + } + + .large { + border-radius: 18px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + } + + .medium { + border-radius: 16px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + } + + .small { + border-radius: 16px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + } + + .host { + overflow: hidden; + border: 2px solid #88cc88; + } + + .contact { + overflow: hidden; + border: 1px solid #777777; + } + + .topleft { + position: absolute; + top: 0px; + left: 0px; + } + + .topright { + position: absolute; + top: 0px; + right: 0px; + } + + .bottomright { + position: absolute; + bottom: 0px; + right: 0px; + } + + .bottom { + position: absolute; + bottom: 0px; + left: 12px; + } +`; + +export const ChannelImage = styled.img` + width: 100%; + height: 100%; +`; diff --git a/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/useChannelLogo.hook.js b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/useChannelLogo.hook.js new file mode 100644 index 00000000..d5f467b5 --- /dev/null +++ b/net/web/src/User/SideBar/Contacts/Channels/ChannelItem/ChannelLogo/useChannelLogo.hook.js @@ -0,0 +1,32 @@ +import { useContext, useState, useEffect } from 'react'; +import { AppContext } from '../../../../../../AppContext/AppContext'; +import { getCardImageUrl } from '../../../../../../Api/getCardImageUrl'; + +export function useChannelLogo() { + + const [state, setState] = useState({ + guid: null + }); + + const app = useContext(AppContext); + + const actions = { + getCardImageUrl: (cardId, revision) => { + if (app?.state?.token) { + return getCardImageUrl(app.state.token, cardId, revision) + } + return null; + }, + getCard: app?.actions?.getCard, + }; + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + useEffect(() => { + updateState({ guid: app?.state?.Data?.profile?.guid }) + }, [app]) + + return { state, actions }; +} diff --git a/net/web/src/User/SideBar/Contacts/Channels/Channels.jsx b/net/web/src/User/SideBar/Contacts/Channels/Channels.jsx index aa2bbe6a..0a037133 100644 --- a/net/web/src/User/SideBar/Contacts/Channels/Channels.jsx +++ b/net/web/src/User/SideBar/Contacts/Channels/Channels.jsx @@ -1,25 +1,20 @@ import React, { useEffect, useState } from 'react' -import { ChannelsWrapper, ChannelItem } from './Channels.styled'; +import { ChannelsWrapper } from './Channels.styled'; import { List, Button, Select, Modal } from 'antd'; import { useChannels } from './useChannels.hook'; import { AddChannel } from './AddChannel/AddChannel'; +import { ChannelItem } from './ChannelItem/ChannelItem'; export function Channels({ showAdd, setShowAdd }) { const { state, actions } = useChannels(); -console.log(state.channels); - const onStart = async () => { if (await actions.addChannel()) { setShowAdd(false); } } - const onSelect = (item) => { - console.log(item); - } - return ( ( - onSelect(item)}> - + )} />