add sending text message

This commit is contained in:
Roland Osborne 2024-12-13 12:02:03 -08:00
parent 23728243df
commit 5417db7caf
4 changed files with 106 additions and 8 deletions

View File

@ -55,8 +55,8 @@
.add {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 128px;
border-top: 1px solid var(--mantine-color-text-7);
width: calc(100% - 32px);
margin-left: 16px;
@ -112,4 +112,32 @@
text-overflow: ellipsis;
overflow: hidden;
}
.message {
width: calc(100% - 16px);
margin: 8px;
}
.controls {
display: flex;
flex-direction: row;
gap: 12px;
width: 100%;
padding-left: 8px;
padding-right: 8px;
padding-bottom: 12px;
}
.attach {
padding: 4px;
width: 32px;
height: 32px;
}
.send {
flex-grow: 1;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
}

View File

@ -1,10 +1,11 @@
import React, {useEffect, useRef, useCallback} from 'react'
import React, {useState, useEffect, useRef, useCallback} from 'react'
import { Focus } from 'databag-client-sdk'
import classes from './Conversation.module.css'
import { useConversation } from './useConversation.hook';
import { IconX, IconSettings, IconHome, IconServer, IconShield, IconLock, IconExclamationCircle } from '@tabler/icons-react'
import { Divider, Text, ActionIcon, Loader } from '@mantine/core'
import { IconSend, IconTextSize, IconTextColor, IconVideo, IconFile, IconDisc, IconCamera, IconX, IconSettings, IconHome, IconServer, IconShield, IconLock, IconExclamationCircle } from '@tabler/icons-react'
import { Divider, Text, Textarea, ActionIcon, Loader } from '@mantine/core'
import { Message } from '../message/Message';
import { modals } from '@mantine/modals'
const PAD_HEIGHT = (1024 - 64);
const LOAD_DEBOUNCE = 1000;
@ -21,6 +22,7 @@ export function Conversation() {
const thread = useRef(null as HTMLDivElement | null);
const scrollPos = useRef(0);
const debounce = useRef(false);
const [sending, setSending] = useState(false);
const { state, actions } = useConversation();
const attachImage = useRef({ click: ()=>{} } as HTMLInputElement);
@ -28,6 +30,33 @@ export function Conversation() {
actions.add(e.target.files[0]);
};
const sendMessage = async () => {
if (!sending) {
setSending(true);
try {
await actions.send();
} catch (err) {
console.log(err);
showError();
}
setSending(false);
}
}
const showError = () => {
modals.openConfirmModal({
title: state.strings.operationFailed,
withCloseButton: true,
overlayProps: {
backgroundOpacity: 0.55,
blur: 3,
},
children: <Text>{state.strings.tryAgain}</Text>,
cancelProps: { display: 'none' },
confirmProps: { display: 'none' },
})
}
const onScroll = () => {
if (thread.current) {
const { scrollHeight, clientHeight, scrollTop } = thread.current;
@ -87,7 +116,7 @@ export function Conversation() {
<IconExclamationCircle size={24} />
)}
</div>
<div className={classes.title} onClick={() => attachImage.current.click()}>
<div className={classes.title}>
{ state.detailSet && state.subject && (
<Text className={classes.label}>{ state.subject }</Text>
)}
@ -125,6 +154,33 @@ export function Conversation() {
</div>
<div className={classes.add}>
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => onSelectImage(e)} style={{display: 'none'}}/>
<Textarea className={classes.message} placeholder={state.strings.newMessage} value={state.message} onChange={(event) => actions.setMessage(event.currentTarget.value)} disabled={!state.detail || state.detail.locked} />
<div className={classes.controls}>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconCamera />
</ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconVideo />
</ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconDisc />
</ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconFile />
</ActionIcon>
<Divider size="sm" orientation="vertical" />
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconTextSize />
</ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked}>
<IconTextColor />
</ActionIcon>
<div className={classes.send}>
<ActionIcon className={classes.attach} variant="light" disabled={!state.message || !state.detail || state.detail.locked} onClick={sendMessage} loading={sending}>
<IconSend />
</ActionIcon>
</div>
</div>
</div>
</div>
);

View File

@ -29,6 +29,7 @@ export function useConversation() {
subject: '',
subjectNames: [],
unknownContacts: 0,
message: '',
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -108,6 +109,9 @@ export function useConversation() {
close: () => {
app.actions.clearFocus();
},
setMessage: (message: string) => {
updateState({ message });
},
more: async () => {
const focus = app.state.focus;
if (focus) {
@ -120,6 +124,16 @@ export function useConversation() {
}
}
},
send: async () => {
const focus = app.state.focus;
const sealed = state.detail?.sealed ? true : false;
if (focus) {
const subject = (assets: {assetId: string, appId: string}[]) => ({ text: state.message });
const progress = (precent: number) => {};
await focus.addTopic(sealed, sealed ? 'sealedtopic' : 'superbasictopic', subject, [], progress);
updateState({ message: '' });
}
},
add: async (file: File) => {
const focus = app.state.focus;
if (focus) {

View File

@ -201,7 +201,7 @@ export class FocusModule implements Focus {
await this.removeLocalChannelTopic(id);
}
}
this.storeView = { revision: nextRev, marker: delta.marker };
this.storeView = { revision: nextRev, marker: this.storeView.marker };
await this.setChannelTopicRevision(this.storeView);
if (this.nextRevision === nextRev) {
@ -1117,9 +1117,9 @@ export class FocusModule implements Focus {
}
const { node, secure, token } = connection
if (cardId) {
return await getContactChannelTopics(node, secure, token, channelId, revision, (begin || !revision) ? BATCH_COUNT : null, begin, end);
return await getContactChannelTopics(node, secure, token, channelId, revision, (end || !revision) ? BATCH_COUNT : null, begin, end);
} else {
return await getChannelTopics(node, secure, token, channelId, revision, (begin || !revision) ? BATCH_COUNT : null, begin, end);
return await getChannelTopics(node, secure, token, channelId, revision, (end || !revision) ? BATCH_COUNT : null, begin, end);
}
}