mirror of
https://github.com/balzack/databag.git
synced 2025-05-04 15:35:16 +00:00
add topic modal for web app
This commit is contained in:
parent
20b42bf670
commit
fef6c7b1dd
@ -78,7 +78,7 @@
|
|||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
border-bottom: 1px solid var(--mantine-color-text-9);
|
border-bottom: 1px solid var(--mantine-color-text-8);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--mantine-color-surface-4);
|
background: var(--mantine-color-surface-4);
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
border-bottom: 2px solid var(--mantine-color-text-9);
|
border-bottom: 2px solid var(--mantine-color-text-7);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
@ -37,7 +37,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-top: 2px solid var(--mantine-color-text-9);
|
border-top: 2px solid var(--mantine-color-text-7);
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
padding-left: 48px;
|
padding-left: 48px;
|
||||||
@ -68,7 +68,7 @@
|
|||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
border-bottom: 1px solid var(--mantine-color-text-9);
|
border-bottom: 1px solid var(--mantine-color-text-8);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--mantine-color-surface-4);
|
background: var(--mantine-color-surface-4);
|
||||||
@ -76,3 +76,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.addContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
.addMembers {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
background: var(--mantine-color-surface-0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
import React from 'react';
|
import React, {useState} from 'react';
|
||||||
import { useContent } from './useContent.hook'
|
import { useContent } from './useContent.hook'
|
||||||
import { TextInput, Button } from '@mantine/core'
|
import { Modal, TextInput, Button } from '@mantine/core'
|
||||||
import { IconSearch, IconMessagePlus } from '@tabler/icons-react'
|
import { IconSearch, IconMessagePlus, IconLabel } from '@tabler/icons-react'
|
||||||
import classes from './Content.module.css'
|
import classes from './Content.module.css'
|
||||||
import { Channel } from '../channel/Channel'
|
import { Channel } from '../channel/Channel'
|
||||||
import { Focus } from 'databag-client-sdk'
|
import { Focus } from 'databag-client-sdk'
|
||||||
|
|
||||||
export function Content({ select }: { select: (focus: Focus) => void }) {
|
export function Content({ select }: { select: (focus: Focus) => void }) {
|
||||||
const { state, actions } = useContent()
|
const { state, actions } = useContent()
|
||||||
|
const [add, setAdd] = useState(false);
|
||||||
|
|
||||||
|
const addTopic = async () => {
|
||||||
|
}
|
||||||
|
|
||||||
const channels = state.filtered.map((channel, idx) => {
|
const channels = state.filtered.map((channel, idx) => {
|
||||||
return (
|
return (
|
||||||
@ -41,7 +45,7 @@ export function Content({ select }: { select: (focus: Focus) => void }) {
|
|||||||
onChange={(event) => actions.setFilter(event.currentTarget.value)}
|
onChange={(event) => actions.setFilter(event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
{state.layout === 'small' && (
|
{state.layout === 'small' && (
|
||||||
<Button className={classes.add} leftSection={<IconMessagePlus size={20} />} onClick={actions.addChannel}>
|
<Button className={classes.add} leftSection={<IconMessagePlus size={20} />} onClick={() => setAdd(true)}>
|
||||||
{state.strings.add}
|
{state.strings.add}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@ -50,11 +54,27 @@ export function Content({ select }: { select: (focus: Focus) => void }) {
|
|||||||
{channels.length !== 0 && <div className={classes.channels}>{channels}</div>}
|
{channels.length !== 0 && <div className={classes.channels}>{channels}</div>}
|
||||||
{state.layout === 'large' && (
|
{state.layout === 'large' && (
|
||||||
<div className={classes.bar}>
|
<div className={classes.bar}>
|
||||||
<Button className={classes.add} leftSection={<IconMessagePlus size={20} />} onClick={actions.addChannel}>
|
<Button className={classes.add} leftSection={<IconMessagePlus size={20} />} onClick={() => setAdd(true)}>
|
||||||
{state.strings.add}
|
{state.strings.add}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Modal title={state.strings.newTopic} opened={add} onClose={() => setAdd(false)} overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} centered>
|
||||||
|
<div className={classes.addContainer}>
|
||||||
|
<TextInput
|
||||||
|
className={classes.input}
|
||||||
|
size="sm"
|
||||||
|
leftSectionPointerEvents="none"
|
||||||
|
leftSection={<IconLabel size={20} />}
|
||||||
|
placeholder={state.strings.subjectOptional}
|
||||||
|
value={state.filter}
|
||||||
|
onChange={(event) => actions.setFilter(event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<div className={classes.addMembers}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,180 +1,243 @@
|
|||||||
import { useState, useContext, useEffect, useRef } from 'react'
|
import {useState, useContext, useEffect, useRef} from 'react';
|
||||||
import { AppContext } from '../context/AppContext'
|
import {AppContext} from '../context/AppContext';
|
||||||
import { DisplayContext } from '../context/DisplayContext'
|
import {DisplayContext} from '../context/DisplayContext';
|
||||||
import { ContextType } from '../context/ContextType'
|
import {ContextType} from '../context/ContextType';
|
||||||
import { Channel, Card, Profile } from 'databag-client-sdk'
|
import {Channel, Card, Profile, Config} from 'databag-client-sdk';
|
||||||
import { notes, unknown, iii_group, iiii_group, iiiii_group, group } from '../constants/Icons'
|
import {notes, unknown, iii_group, iiii_group, iiiii_group, group} from '../constants/Icons';
|
||||||
|
|
||||||
type ChannelParams = {
|
type ChannelParams = {
|
||||||
cardId: string
|
cardId: string;
|
||||||
channelId: string
|
channelId: string;
|
||||||
sealed: boolean
|
sealed: boolean;
|
||||||
hosted: boolean
|
hosted: boolean;
|
||||||
unread: boolean
|
unread: boolean;
|
||||||
imageUrl: string
|
imageUrl: string;
|
||||||
subject: (string | null)[]
|
subject: (string | null)[];
|
||||||
message: string
|
message: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function useContent() {
|
export function useContent() {
|
||||||
const cardChannels = useRef(new Map<string | null, Channel[]>())
|
const cardChannels = useRef(new Map<string | null, Channel[]>());
|
||||||
const app = useContext(AppContext) as ContextType
|
const app = useContext(AppContext) as ContextType;
|
||||||
const display = useContext(DisplayContext) as ContextType
|
const display = useContext(DisplayContext) as ContextType;
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
strings: display.state.strings,
|
strings: display.state.strings,
|
||||||
layout: null,
|
layout: null,
|
||||||
guid: '',
|
guid: '',
|
||||||
cards: [] as Card[],
|
connected: [] as Card[],
|
||||||
|
connectedAndSealable: [] as Cards[],
|
||||||
sorted: [] as Channel[],
|
sorted: [] as Channel[],
|
||||||
filtered: [] as ChannelParams[],
|
filtered: [] as ChannelParams[],
|
||||||
filter: '',
|
filter: '',
|
||||||
})
|
topic: '',
|
||||||
|
sealable: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const compare = (a: Card, b: Card) => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const updateState = (value: any) => {
|
const updateState = (value: any) => {
|
||||||
setState((s) => ({ ...s, ...value }))
|
setState(s => ({...s, ...value}));
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { layout } = display.state
|
const {layout} = display.state;
|
||||||
updateState({ layout })
|
updateState({layout});
|
||||||
}, [display.state])
|
}, [display.state]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const channels = state.sorted.map((channel) => {
|
const channels = state.sorted.map(channel => {
|
||||||
const { cardId, channelId, unread, sealed, members, data, lastTopic } = channel
|
const {cardId, channelId, unread, sealed, members, dataType, data, lastTopic} = channel;
|
||||||
const contacts = [] as (Card | undefined)[]
|
const contacts = [] as (Card | undefined)[];
|
||||||
if (cardId) {
|
if (cardId) {
|
||||||
const card = state.cards.find((contact) => contact.cardId === cardId)
|
const card = state.cards.find(contact => contact.cardId === cardId);
|
||||||
if (card) {
|
if (card) {
|
||||||
contacts.push(card)
|
contacts.push(card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const guests = members.filter((contact) => contact.guid !== state.guid)
|
const guests = members.filter(contact => contact.guid !== state.guid);
|
||||||
const guestCards = guests
|
const guestCards = guests
|
||||||
.map((contact) => state.cards.find((card) => card.guid === contact.guid))
|
.map(contact => state.cards.find(card => card.guid === contact.guid))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (!a && !b) {
|
if (!a && !b) {
|
||||||
return 0
|
return 0;
|
||||||
} else if (!a) {
|
} else if (!a) {
|
||||||
return 1
|
return 1;
|
||||||
} else if (!b) {
|
} else if (!b) {
|
||||||
return -1
|
return -1;
|
||||||
} else if (a.handle > b.handle) {
|
} else if (a.handle > b.handle) {
|
||||||
return 1
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return 0
|
return 0;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
contacts.push(...guestCards)
|
contacts.push(...guestCards);
|
||||||
|
|
||||||
const buildSubject = () => {
|
const buildSubject = () => {
|
||||||
if (contacts.length === 0) {
|
if (contacts.length === 0) {
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
return contacts.map((contact) => (contact ? contact.handle : null))
|
return contacts.map(contact => (contact ? contact.handle : null));
|
||||||
}
|
};
|
||||||
|
|
||||||
const selectImage = () => {
|
const selectImage = () => {
|
||||||
if (contacts.length == 0) {
|
if (contacts.length == 0) {
|
||||||
return notes
|
return notes;
|
||||||
} else if (contacts.length == 1) {
|
} else if (contacts.length == 1) {
|
||||||
if (contacts[0]) {
|
if (contacts[0]) {
|
||||||
return contacts[0].imageUrl
|
return contacts[0].imageUrl;
|
||||||
} else {
|
} else {
|
||||||
return unknown
|
return unknown;
|
||||||
}
|
}
|
||||||
} else if (contacts.length == 2) {
|
} else if (contacts.length == 2) {
|
||||||
return iii_group
|
return iii_group;
|
||||||
} else if (contacts.length == 3) {
|
} else if (contacts.length == 3) {
|
||||||
return iiii_group
|
return iiii_group;
|
||||||
} else if (contacts.length == 4) {
|
} else if (contacts.length == 4) {
|
||||||
return iiiii_group
|
return iiiii_group;
|
||||||
} else {
|
} else {
|
||||||
return group
|
return group;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMessage = () => {
|
||||||
|
if (!lastTopic || !lastTopic.status) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (lastTopic.dataType === 'superbasictopic') {
|
||||||
|
if (lastTopic.data?.text) {
|
||||||
|
return lastTopic.data.text;
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} else if (lastTopic.dataType === 'sealedtopic') {
|
||||||
|
if (lastTopic.data) {
|
||||||
|
if (lastTopic.data.message?.text) {
|
||||||
|
return lastTopic.data.message.text;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hosted = cardId == null
|
const hosted = cardId == null;
|
||||||
const subject = data?.subject ? [data.subject] : buildSubject()
|
const subject = data?.subject ? [data.subject] : buildSubject();
|
||||||
const message = lastTopic ? (lastTopic.data ? lastTopic.data.text : null) : ''
|
const message = getMessage();
|
||||||
const imageUrl = selectImage()
|
const imageUrl = selectImage();
|
||||||
|
|
||||||
return { cardId, channelId, sealed, hosted, unread, imageUrl, subject, message }
|
return {cardId, channelId, sealed, hosted, unread, imageUrl, subject, message};
|
||||||
})
|
});
|
||||||
|
|
||||||
const search = state.filter?.toLowerCase()
|
const search = state.filter?.toLowerCase();
|
||||||
const filtered = channels.filter((item) => {
|
const filtered = channels.filter(item => {
|
||||||
if (search) {
|
if (search) {
|
||||||
if (item.subject?.find((value) => value?.toLowerCase().includes(search))) {
|
if (item.subject?.find(value => value?.toLowerCase().includes(search))) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
if (item.message?.toLowerCase().includes(search)) {
|
if (item.message?.toLowerCase().includes(search)) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
return true
|
return true;
|
||||||
})
|
});
|
||||||
|
|
||||||
updateState({ filtered })
|
updateState({filtered});
|
||||||
}, [state.sorted, state.cards, state.guid, state.filter])
|
}, [state.sorted, state.cards, state.guid, state.filter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const setConfig = (config: Config) => {
|
||||||
|
const { sealSet, sealUnlocked } = config;
|
||||||
|
updateState({ sealSet: sealSet && sealUnlocked });
|
||||||
|
};
|
||||||
const setProfile = (profile: Profile) => {
|
const setProfile = (profile: Profile) => {
|
||||||
const { guid } = profile
|
const {guid} = profile;
|
||||||
updateState({ guid })
|
updateState({guid});
|
||||||
}
|
};
|
||||||
const setCards = (cards: Card[]) => {
|
const setCards = (cards: Card[]) => {
|
||||||
updateState({ cards })
|
const sorted = cards.sort(compare);
|
||||||
}
|
const connected = [];
|
||||||
const setChannels = ({ channels, cardId }: { channels: Channel[], cardId: string | null }) => {
|
const sealable = [];
|
||||||
cardChannels.current.set(cardId, channels)
|
sorted.forEach(card => {
|
||||||
const merged = [] as Channel[]
|
if (card.status === 'connected') {
|
||||||
cardChannels.current.forEach((values) => {
|
connected.push(card);
|
||||||
merged.push(...values)
|
if (card.sealable) {
|
||||||
})
|
sealable.push(card);
|
||||||
const sorted = merged.sort((a, b) => {
|
}
|
||||||
const aUpdated = a?.lastTopic?.created
|
|
||||||
const bUpdated = b?.lastTopic?.created
|
|
||||||
if (aUpdated == bUpdated) {
|
|
||||||
return 0
|
|
||||||
} else if (!aUpdated) {
|
|
||||||
return 1
|
|
||||||
} else if (!bUpdated) {
|
|
||||||
return -1
|
|
||||||
} else if (aUpdated < bUpdated) {
|
|
||||||
return 1
|
|
||||||
} else {
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
updateState({ sorted })
|
updateState({cards, connected, sealable});
|
||||||
}
|
};
|
||||||
|
const setChannels = ({channels, cardId}: {channels: Channel[]; cardId: string | null}) => {
|
||||||
|
cardChannels.current.set(cardId, channels);
|
||||||
|
const merged = [] as Channel[];
|
||||||
|
cardChannels.current.forEach(values => {
|
||||||
|
merged.push(...values);
|
||||||
|
});
|
||||||
|
const sorted = merged.sort((a, b) => {
|
||||||
|
const aUpdated = a?.lastTopic?.created;
|
||||||
|
const bUpdated = b?.lastTopic?.created;
|
||||||
|
if (aUpdated == bUpdated) {
|
||||||
|
return 0;
|
||||||
|
} else if (!aUpdated) {
|
||||||
|
return 1;
|
||||||
|
} else if (!bUpdated) {
|
||||||
|
return -1;
|
||||||
|
} else if (aUpdated < bUpdated) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateState({sorted});
|
||||||
|
};
|
||||||
|
|
||||||
const { identity, contact, content } = app.state.session
|
const {identity, contact, content, settings} = app.state.session;
|
||||||
identity.addProfileListener(setProfile)
|
identity.addProfileListener(setProfile);
|
||||||
contact.addCardListener(setCards)
|
contact.addCardListener(setCards);
|
||||||
content.addChannelListener(setChannels)
|
content.addChannelListener(setChannels);
|
||||||
|
settings.addConfigListener(setConfig);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
identity.removeProfileListener(setProfile)
|
identity.removeProfileListener(setProfile);
|
||||||
contact.removeCardListener(setCards)
|
contact.removeCardListener(setCards);
|
||||||
content.removeChannelListener(setChannels)
|
content.removeChannelListener(setChannels);
|
||||||
}
|
settings.removeConfigListener(setConfig);
|
||||||
}, [])
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
addChannel: () => {
|
|
||||||
console.log('add channel');
|
|
||||||
},
|
|
||||||
setFilter: (filter: string) => {
|
setFilter: (filter: string) => {
|
||||||
updateState({ filter })
|
updateState({filter});
|
||||||
|
},
|
||||||
|
setTopic: (topic: string) => {
|
||||||
|
updateState({topic});
|
||||||
},
|
},
|
||||||
getFocus: (cardId: string | null, channelId: string) => {
|
getFocus: (cardId: string | null, channelId: string) => {
|
||||||
return app.state.session.setFocus(cardId, channelId)
|
return app.state.session.setFocus(cardId, channelId);
|
||||||
},
|
},
|
||||||
}
|
addTopic: async (sealed: boolean, subject: string, contacts: string[]) => {
|
||||||
|
const content = app.state.session.getContent();
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
if (sealed) {
|
||||||
|
await content.addChannel(true, 'sealed', {subject}, contacts);
|
||||||
|
} else {
|
||||||
|
await content.addChannel(false, 'superbasic', {subject}, contacts);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return { state, actions }
|
return {state, actions};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user