mirror of
https://github.com/balzack/databag.git
synced 2025-05-04 15:35:16 +00:00
rendering message
This commit is contained in:
parent
2e170f693f
commit
c53bfeb039
@ -36,6 +36,14 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -3,7 +3,7 @@ import { Focus } from 'databag-client-sdk'
|
|||||||
import classes from './Conversation.module.css'
|
import classes from './Conversation.module.css'
|
||||||
import { useConversation } from './useConversation.hook';
|
import { useConversation } from './useConversation.hook';
|
||||||
import { IconX } from '@tabler/icons-react'
|
import { IconX } from '@tabler/icons-react'
|
||||||
import { Text } from '@mantine/core'
|
import { Text, Loader } from '@mantine/core'
|
||||||
import { Message } from '../message/Message';
|
import { Message } from '../message/Message';
|
||||||
|
|
||||||
export type MediaAsset = {
|
export type MediaAsset = {
|
||||||
@ -46,9 +46,16 @@ export function Conversation() {
|
|||||||
<IconX size={24} className={classes.close} onClick={actions.close} />
|
<IconX size={24} className={classes.close} onClick={actions.close} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.frame}>
|
<div className={classes.frame}>
|
||||||
<div className={classes.thread}>
|
{ !state.loaded && (
|
||||||
{topics}
|
<div className={classes.spinner}>
|
||||||
</div>
|
<Loader size={64} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ state.loaded && (
|
||||||
|
<div className={classes.thread}>
|
||||||
|
{topics}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.add}>
|
<div className={classes.add}>
|
||||||
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => onSelectImage(e)} style={{display: 'none'}}/>
|
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => onSelectImage(e)} style={{display: 'none'}}/>
|
||||||
|
@ -14,6 +14,7 @@ export function useConversation() {
|
|||||||
layout: null,
|
layout: null,
|
||||||
strings: display.state.strings,
|
strings: display.state.strings,
|
||||||
topics: [] as Topic[],
|
topics: [] as Topic[],
|
||||||
|
loaded: false,
|
||||||
profile: null as Profile | null,
|
profile: null as Profile | null,
|
||||||
cards: new Map<string, Card>(),
|
cards: new Map<string, Card>(),
|
||||||
host: false,
|
host: false,
|
||||||
@ -34,16 +35,18 @@ export function useConversation() {
|
|||||||
const { contact, identity } = app.state.session || { };
|
const { contact, identity } = app.state.session || { };
|
||||||
if (focus && contact && identity) {
|
if (focus && contact && identity) {
|
||||||
const setTopics = (topics: Topic[]) => {
|
const setTopics = (topics: Topic[]) => {
|
||||||
const sorted = topics.sort((a, b) => {
|
if (topics) {
|
||||||
if (a.created < b.created) {
|
const sorted = topics.sort((a, b) => {
|
||||||
return -1;
|
if (a.created < b.created) {
|
||||||
} else if (a.created > b.created) {
|
return -1;
|
||||||
return 1;
|
} else if (a.created > b.created) {
|
||||||
} else {
|
return 1;
|
||||||
return 0;
|
} else {
|
||||||
}
|
return 0;
|
||||||
});
|
}
|
||||||
updateState({ topics: sorted });
|
});
|
||||||
|
updateState({ topics: sorted, loaded: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const setCards = (cards: Card[]) => {
|
const setCards = (cards: Card[]) => {
|
||||||
const contacts = new Map<string, Card>();
|
const contacts = new Map<string, Card>();
|
||||||
@ -63,6 +66,7 @@ export function useConversation() {
|
|||||||
}
|
}
|
||||||
console.log(focused);
|
console.log(focused);
|
||||||
}
|
}
|
||||||
|
updateState({ topics: [], loaded: false });
|
||||||
focus.addTopicListener(setTopics);
|
focus.addTopicListener(setTopics);
|
||||||
focus.addDetailListener(setDetail);
|
focus.addDetailListener(setDetail);
|
||||||
contact.addCardListener(setCards);
|
contact.addCardListener(setCards);
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.3 KiB |
@ -4,21 +4,52 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.media {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.goleft {
|
||||||
|
position: absolute;
|
||||||
|
left: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goright {
|
||||||
|
position: absolute;
|
||||||
|
right: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.assets {
|
.assets {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
padding-left: 72px;
|
padding-left: 72px;
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
gap: 16px;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assets::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.failed {
|
.failed {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { useRef, useState, useCallback } from 'react';
|
||||||
import { avatar } from '../constants/Icons'
|
import { avatar } from '../constants/Icons'
|
||||||
import { Topic, Card, Profile } from 'databag-client-sdk';
|
import { Topic, Card, Profile } from 'databag-client-sdk';
|
||||||
import classes from './Message.module.css'
|
import classes from './Message.module.css'
|
||||||
import { Image, Skeleton } from '@mantine/core'
|
import { Image, Skeleton, ActionIcon } from '@mantine/core'
|
||||||
import { ImageAsset } from './imageAsset/ImageAsset';
|
import { ImageAsset } from './imageAsset/ImageAsset';
|
||||||
import { AudioAsset } from './audioAsset/AudioAsset';
|
import { AudioAsset } from './audioAsset/AudioAsset';
|
||||||
import { VideoAsset } from './videoAsset/VideoAsset';
|
import { VideoAsset } from './videoAsset/VideoAsset';
|
||||||
@ -9,10 +10,12 @@ import { BinaryAsset } from './binaryAsset/BinaryAsset';
|
|||||||
import type { MediaAsset } from '../conversation/Conversation';
|
import type { MediaAsset } from '../conversation/Conversation';
|
||||||
import { useMessage } from './useMessage.hook';
|
import { useMessage } from './useMessage.hook';
|
||||||
import failed from '../images/failed.png'
|
import failed from '../images/failed.png'
|
||||||
|
import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react';
|
||||||
|
import { useResizeDetector } from 'react-resize-detector';
|
||||||
|
|
||||||
export function Message({ topic, card, profile, host }: { topic: Topic, card: Card | null, profile: Profile | null, host: boolean }) {
|
export function Message({ topic, card, profile, host }: { topic: Topic, card: Card | null, profile: Profile | null, host: boolean }) {
|
||||||
const { state, actions } = useMessage();
|
const { state, actions } = useMessage();
|
||||||
|
const scroll = useRef(null as HTMLDivElement | null);
|
||||||
const { locked, data, created, topicId, status, transform } = topic;
|
const { locked, data, created, topicId, status, transform } = topic;
|
||||||
const { name, handle, node } = profile || card || { name: null, handle: null, node: null }
|
const { name, handle, node } = profile || card || { name: null, handle: null, node: null }
|
||||||
const { text, textColor, textSize, assets } = data || { text: null, textColor: null, textSize: null }
|
const { text, textColor, textSize, assets } = data || { text: null, textColor: null, textSize: null }
|
||||||
@ -20,6 +23,22 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
|||||||
const logoUrl = profile ? profile.imageUrl : card ? card.imageUrl : avatar;
|
const logoUrl = profile ? profile.imageUrl : card ? card.imageUrl : avatar;
|
||||||
const timestamp = actions.getTimestamp(created);
|
const timestamp = actions.getTimestamp(created);
|
||||||
|
|
||||||
|
const [showScroll, setShowScroll] = useState(false);
|
||||||
|
const onResize = useCallback(() => {
|
||||||
|
setShowScroll(scroll.current ? scroll.current.scrollWidth > scroll.current.clientWidth : false);
|
||||||
|
}, []);
|
||||||
|
const { ref } = useResizeDetector({ onResize });
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scroll.current) {
|
||||||
|
scroll.current.scrollTo({ top: 0, left: scroll.current.scrollLeft+92, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scroll.current) {
|
||||||
|
scroll.current.scrollTo({ top: 0, left: scroll.current.scrollLeft-92, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
const media = !assets ? [] : assets.map((asset: MediaAsset, index: number) => {
|
const media = !assets ? [] : assets.map((asset: MediaAsset, index: number) => {
|
||||||
@ -29,11 +48,13 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
|||||||
return <AudioAsset key={index} topicId={topicId} asset={asset as MediaAsset} />
|
return <AudioAsset key={index} topicId={topicId} asset={asset as MediaAsset} />
|
||||||
} else if (asset.video || asset.encrypted?.type === 'video') {
|
} else if (asset.video || asset.encrypted?.type === 'video') {
|
||||||
return <VideoAsset key={index} topicId={topicId} asset={asset as MediaAsset} />
|
return <VideoAsset key={index} topicId={topicId} asset={asset as MediaAsset} />
|
||||||
|
} else if (asset.binary || asset.encrypted?.type === 'binary') {
|
||||||
|
return <BinaryAsset key={index} topicId={topicId} asset={asset as MediaAsset} />
|
||||||
} else {
|
} else {
|
||||||
return <></>
|
return <div key={index}></div>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.topic}>
|
<div className={classes.topic}>
|
||||||
<div className={classes.content}>
|
<div className={classes.content}>
|
||||||
@ -71,8 +92,26 @@ export function Message({ topic, card, profile, host }: { topic: Topic, card: Ca
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ !locked && media.length > 0 && transform === 'complete' && (
|
{ !locked && media.length > 0 && transform === 'complete' && (
|
||||||
<div className={classes.assets}>
|
<div className={classes.media}>
|
||||||
{ media }
|
<div ref={scroll} className={classes.assets}>
|
||||||
|
<div ref={ref} className={classes.thumbs}>
|
||||||
|
{ media }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ showScroll && (
|
||||||
|
<div className={classes.goleft}>
|
||||||
|
<ActionIcon variant="light" onClick={scrollLeft}>
|
||||||
|
<IconChevronLeft size={18} stroke={1.5} />
|
||||||
|
</ActionIcon>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ showScroll && (
|
||||||
|
<div className={classes.goright}>
|
||||||
|
<ActionIcon variant="light" onClick={scrollRight}>
|
||||||
|
<IconChevronRight size={18} stroke={1.5} />
|
||||||
|
</ActionIcon>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{ !locked && media.length > 0 && transform === 'incomplete' && (
|
{ !locked && media.length > 0 && transform === 'incomplete' && (
|
||||||
|
@ -1,8 +1,21 @@
|
|||||||
.asset {
|
.asset {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,12 @@ import { Image } from '@mantine/core'
|
|||||||
|
|
||||||
export function AudioAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
|
export function AudioAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
|
||||||
const { state, actions } = useAudioAsset(topicId, asset);
|
const { state, actions } = useAudioAsset(topicId, asset);
|
||||||
|
const { label } = asset.encrypted || asset.audio;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.asset}>
|
<div className={classes.asset}>
|
||||||
<Image className={classes.thumb} src={audio} fit="contain" />
|
<Image className={classes.thumb} src={audio} fit="contain" />
|
||||||
|
<div className={classes.label}>{ label }</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
.asset {
|
.asset {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,14 @@ import { Image } from '@mantine/core'
|
|||||||
|
|
||||||
export function BinaryAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
|
export function BinaryAsset({ topicId, asset }: { topicId: string, asset: MediaAsset }) {
|
||||||
const { state, actions } = useBinaryAsset(topicId, asset);
|
const { state, actions } = useBinaryAsset(topicId, asset);
|
||||||
|
const { label, extension } = asset.encrypted || asset.binary;
|
||||||
return (
|
return (
|
||||||
<div className={classes.asset}>
|
<div className={classes.asset}>
|
||||||
<Image className={classes.thumb} src={binary} fit="contain" />
|
<Image className={classes.thumb} src={binary} fit="contain" />
|
||||||
|
<div className={classes.label}>
|
||||||
|
<div>{ label }</div>
|
||||||
|
<div className={classes.extension}>{ extension }</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -135,8 +135,8 @@ export interface Focus {
|
|||||||
setBlockTopic(topicId: string): Promise<void>;
|
setBlockTopic(topicId: string): Promise<void>;
|
||||||
clearBlockTopic(topicId: string): Promise<void>;
|
clearBlockTopic(topicId: string): Promise<void>;
|
||||||
|
|
||||||
addTopicListener(ev: (topics: Topic[]) => void): void;
|
addTopicListener(ev: (topics: null | Topic[]) => void): void;
|
||||||
removeTopicListener(ev: (topics: Topic[]) => void): void;
|
removeTopicListener(ev: (topics: null | Topic[]) => void): void;
|
||||||
|
|
||||||
addDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
|
addDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
|
||||||
removeDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
|
removeDetailListener(ev: (focused: { cardId: string | null, channelId: string, detail: FocusDetail | null }) => void): void;
|
||||||
|
@ -51,6 +51,7 @@ export class FocusModule implements Focus {
|
|||||||
private markRead: ()=>Promise<void>;
|
private markRead: ()=>Promise<void>;
|
||||||
private flagChannelTopic: (id: string)=>Promise<void>;
|
private flagChannelTopic: (id: string)=>Promise<void>;
|
||||||
private focusDetail: FocusDetail | null;
|
private focusDetail: FocusDetail | null;
|
||||||
|
private loaded: boolean;
|
||||||
|
|
||||||
private markers: Set<string>;
|
private markers: Set<string>;
|
||||||
|
|
||||||
@ -92,6 +93,7 @@ export class FocusModule implements Focus {
|
|||||||
const { guid } = this;
|
const { guid } = this;
|
||||||
this.nextRevision = revision;
|
this.nextRevision = revision;
|
||||||
this.storeView = await this.getChannelTopicRevision();
|
this.storeView = await this.getChannelTopicRevision();
|
||||||
|
this.localComplete = this.storeView.revision == null;
|
||||||
|
|
||||||
// load markers
|
// load markers
|
||||||
const values = await this.store.getMarkers(guid);
|
const values = await this.store.getMarkers(guid);
|
||||||
@ -99,7 +101,6 @@ export class FocusModule implements Focus {
|
|||||||
this.markers.add(value);
|
this.markers.add(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.emitTopics();
|
|
||||||
this.unsealAll = true;
|
this.unsealAll = true;
|
||||||
this.loadMore = true;
|
this.loadMore = true;
|
||||||
this.syncing = false;
|
this.syncing = false;
|
||||||
@ -162,6 +163,7 @@ export class FocusModule implements Focus {
|
|||||||
} else {
|
} else {
|
||||||
this.loadMore = false;
|
this.loadMore = false;
|
||||||
}
|
}
|
||||||
|
await this.markRead();
|
||||||
this.emitTopics();
|
this.emitTopics();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.warn(err);
|
this.log.warn(err);
|
||||||
@ -744,9 +746,6 @@ export class FocusModule implements Focus {
|
|||||||
throw new Error('topic entry not found');
|
throw new Error('topic entry not found');
|
||||||
}
|
}
|
||||||
const { assets } = this.getTopicData(entry.item);
|
const { assets } = this.getTopicData(entry.item);
|
||||||
|
|
||||||
console.log(">>> ", assetId, entry.item, assets);
|
|
||||||
|
|
||||||
const asset = assets.find(item => item.assetId === assetId);
|
const asset = assets.find(item => item.assetId === assetId);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
throw new Error('asset entry not found');
|
throw new Error('asset entry not found');
|
||||||
@ -819,17 +818,18 @@ console.log(">>> ", assetId, entry.item, assets);
|
|||||||
await this.sync();
|
await this.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public addTopicListener(ev: (topics: Topic[]) => void) {
|
public addTopicListener(ev: (topics: null | Topic[]) => void) {
|
||||||
this.emitter.on('topic', ev);
|
this.emitter.on('topic', ev);
|
||||||
const topics = Array.from(this.topicEntries, ([topicId, entry]) => entry.topic);
|
const topics = this.loaded ? Array.from(this.topicEntries, ([topicId, entry]) => entry.topic) : null;
|
||||||
ev(topics);
|
ev(topics);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeTopicListener(ev: (topics: Topic[]) => void) {
|
public removeTopicListener(ev: (topics: | Topic[]) => void) {
|
||||||
this.emitter.off('topic', ev);
|
this.emitter.off('topic', ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitTopics() {
|
private emitTopics() {
|
||||||
|
this.loaded = true;
|
||||||
const topics = Array.from(this.topicEntries, ([topicId, entry]) => entry.topic);
|
const topics = Array.from(this.topicEntries, ([topicId, entry]) => entry.topic);
|
||||||
this.emitter.emit('topic', topics);
|
this.emitter.emit('topic', topics);
|
||||||
}
|
}
|
||||||
|
@ -395,6 +395,7 @@ export class StreamModule {
|
|||||||
|
|
||||||
const markRead = async () => {
|
const markRead = async () => {
|
||||||
try {
|
try {
|
||||||
|
console.log("MARKING AS READ!!!");
|
||||||
await this.setUnreadChannel(channelId, false);
|
await this.setUnreadChannel(channelId, false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.log.error('failed to mark as read');
|
this.log.error('failed to mark as read');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user