adding audio and video attachments

This commit is contained in:
balzack 2024-12-14 16:19:25 -08:00
parent 0c2de2e00d
commit c244c1f92d
11 changed files with 144 additions and 17 deletions

View File

@ -8,6 +8,8 @@ import { Message } from '../message/Message';
import { modals } from '@mantine/modals' import { modals } from '@mantine/modals'
import { ImageFile } from './imageFile/ImageFile'; import { ImageFile } from './imageFile/ImageFile';
import { VideoFile } from './videoFile/VideoFile'; import { VideoFile } from './videoFile/VideoFile';
import { AudioFile } from './audioFile/AudioFile';
import { BinaryFile } from './binaryFile/BinaryFile';
const PAD_HEIGHT = (1024 - 64); const PAD_HEIGHT = (1024 - 64);
const LOAD_DEBOUNCE = 1000; const LOAD_DEBOUNCE = 1000;
@ -28,6 +30,8 @@ export function Conversation() {
const { state, actions } = useConversation(); const { state, actions } = useConversation();
const attachImage = useRef({ click: ()=>{} } as HTMLInputElement); const attachImage = useRef({ click: ()=>{} } as HTMLInputElement);
const attachVideo = useRef({ click: ()=>{} } as HTMLInputElement); const attachVideo = useRef({ click: ()=>{} } as HTMLInputElement);
const attachAudio = useRef({ click: ()=>{} } as HTMLInputElement);
const attachBinary = useRef({ click: ()=>{} } as HTMLInputElement);
const addImage = (image: File | undefined) => { const addImage = (image: File | undefined) => {
if (image) { if (image) {
@ -41,6 +45,18 @@ export function Conversation() {
} }
} }
const addAudio = (audio: File | undefined) => {
if (audio) {
actions.addAudio(audio);
}
}
const addBinary = (binary: File | undefined) => {
if (binary) {
actions.addBinary(binary);
}
}
const sendMessage = async () => { const sendMessage = async () => {
if (!sending) { if (!sending) {
setSending(true); setSending(true);
@ -111,14 +127,13 @@ export function Conversation() {
return <ImageFile key={index} source={asset.file} /> return <ImageFile key={index} source={asset.file} />
} else if (asset.type === 'video') { } else if (asset.type === 'video') {
return <VideoFile key={index} source={asset.file} thumbPosition={(position: number) => actions.setThumbPosition(index, position)} disabled={sending} /> return <VideoFile key={index} source={asset.file} thumbPosition={(position: number) => actions.setThumbPosition(index, position)} disabled={sending} />
} else if (asset.type === 'audio') {
return <AudioFile key={index} source={asset.file} />
} else { } else {
return <div key={index}></div> return <BinaryFile key={index} source={asset.file} />
} }
}); });
console.log(state.assets);
return ( return (
<div className={classes.conversation}> <div className={classes.conversation}>
<div className={classes.header}> <div className={classes.header}>
@ -180,6 +195,8 @@ console.log(state.assets);
<div className={classes.add}> <div className={classes.add}>
<input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => addImage(e.target?.files?.[0])} style={{display: 'none'}}/> <input type='file' name="asset" accept="image/*" ref={attachImage} onChange={e => addImage(e.target?.files?.[0])} style={{display: 'none'}}/>
<input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => addVideo(e.target?.files?.[0])} style={{display: 'none'}}/> <input type='file' name="asset" accept="video/*" ref={attachVideo} onChange={e => addVideo(e.target?.files?.[0])} style={{display: 'none'}}/>
<input type='file' name="asset" accept="audio/*" ref={attachAudio} onChange={e => addAudio(e.target?.files?.[0])} style={{display: 'none'}}/>
<input type='file' name="asset" accept="*/*" ref={attachBinary} onChange={e => addBinary(e.target?.files?.[0])} style={{display: 'none'}}/>
<div className={classes.files}> <div className={classes.files}>
{ media } { media }
</div> </div>
@ -191,10 +208,10 @@ console.log(state.assets);
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachVideo.current.click()}> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachVideo.current.click()}>
<IconVideo /> <IconVideo />
</ActionIcon> </ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachAudio.current.click()}>
<IconDisc /> <IconDisc />
</ActionIcon> </ActionIcon>
<ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending}> <ActionIcon className={classes.attach} variant="light" disabled={!state.detail || state.detail.locked || sending} onClick={() => attachBinary.current.click()}>
<IconFile /> <IconFile />
</ActionIcon> </ActionIcon>
<Divider size="sm" orientation="vertical" /> <Divider size="sm" orientation="vertical" />

View File

@ -0,0 +1,9 @@
.asset {
padding-top: 16px;
.thumb {
width: auto;
height: 92px;
}
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Image } from '@mantine/core'
import { useAudioFile } from './useAudioFile.hook';
import classes from './AudioFile.module.css'
import audio from '../../images/audio.png'
export function AudioFile({ source }: {source: File}) {
const { state, actions } = useAudioFile(source);
return (
<div className={classes.asset}>
<Image radius="sm" className={classes.thumb} src={audio} />
</div>
);
}

View File

@ -0,0 +1,23 @@
import { useState, useEffect } from 'react'
export function useAudioFile(source: File) {
const [state, setState] = useState({
thumbUrl: null,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
useEffect(() => {
const thumbUrl = URL.createObjectURL(source);
updateState({ thumbUrl });
return () => { URL.revokeObjectURL(thumbUrl) };
}, [source]);
const actions = {
}
return { state, actions }
}

View File

@ -0,0 +1,9 @@
.asset {
padding-top: 16px;
.thumb {
width: auto;
height: 92px;
}
}

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Image } from '@mantine/core'
import { useBinaryFile } from './useBinaryFile.hook';
import classes from './BinaryFile.module.css'
import binary from '../../images/binary.png'
export function BinaryFile({ source }: {source: File}) {
const { state, actions } = useBinaryFile(source);
return (
<div className={classes.asset}>
<Image radius="sm" className={classes.thumb} src={binary} />
</div>
);
}

View File

@ -0,0 +1,19 @@
import { useState, useEffect } from 'react'
export function useBinaryFile(source: File) {
const [state, setState] = useState({
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const updateState = (value: any) => {
setState((s) => ({ ...s, ...value }))
}
useEffect(() => {
}, [source]);
const actions = {
}
return { state, actions }
}

View File

@ -32,7 +32,7 @@ function getImageThumb(file: File) {
}); });
} }
function getVideoThumb(file: File, position: number) { function getVideoThumb(file: File, position?: number) {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
var video = document.createElement("video"); var video = document.createElement("video");
@ -71,7 +71,7 @@ function getVideoThumb(file: File, position: number) {
video.src = url; video.src = url;
video.muted = true; video.muted = true;
video.playsInline = true; video.playsInline = true;
video.currentTime = position; video.currentTime = position ? position : 0;
video.play(); video.play();
}); });
} }
@ -98,7 +98,7 @@ export function useConversation() {
subjectNames: [], subjectNames: [],
unknownContacts: 0, unknownContacts: 0,
message: '', message: '',
assets: [] as {type: string, file: File}[], assets: [] as {type: string, file: File, position?: number}[],
}) })
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -106,7 +106,7 @@ export function useConversation() {
setState((s) => ({ ...s, ...value })) setState((s) => ({ ...s, ...value }))
} }
const updateAsset = (index, value) => { const updateAsset = (index: number, value: any) => {
setState((s) => { setState((s) => {
s.assets[index] = { ...s.assets[index], ...value }; s.assets[index] = { ...s.assets[index], ...value };
return { ...s }; return { ...s };
@ -270,7 +270,11 @@ export function useConversation() {
const type = asset.encrypted.type; const type = asset.encrypted.type;
const thumb = uploaded.find(upload => upload.appId === asset.encrypted.thumb)?.assetId; const thumb = uploaded.find(upload => upload.appId === asset.encrypted.thumb)?.assetId;
const parts = uploaded.find(upload => upload.appId === asset.encrypted.parts)?.assetId; const parts = uploaded.find(upload => upload.appId === asset.encrypted.parts)?.assetId;
return { encrypted: { type, thumb, parts }}; if (type === 'image' || type === 'video') {
return { encrypted: { type, thumb, parts }};
} else {
return { encrypted: { type, thumb, parts }};
}
} else if (asset.image) { } else if (asset.image) {
const thumb = uploaded.find(upload => upload.appId === asset.image.thumb)?.assetId; const thumb = uploaded.find(upload => upload.appId === asset.image.thumb)?.assetId;
const full = uploaded.find(upload => upload.appId === asset.image.full)?.assetId; const full = uploaded.find(upload => upload.appId === asset.image.full)?.assetId;
@ -288,6 +292,8 @@ export function useConversation() {
return { binary: { data } }; return { binary: { data } };
} }
}); });
console.log(assets, uploaded);
return { text: state.message, assets }; return { text: state.message, assets };
} }
const progress = (precent: number) => {}; const progress = (precent: number) => {};
@ -303,6 +309,14 @@ export function useConversation() {
const type = 'video'; const type = 'video';
updateState({ assets: [ ...state.assets, { type, file } ]}); updateState({ assets: [ ...state.assets, { type, file } ]});
}, },
addAudio: (file: File) => {
const type = 'audio';
updateState({ assets: [ ...state.assets, { type, file } ]});
},
addBinary: (file: File) => {
const type = 'binary';
updateState({ assets: [ ...state.assets, { type, file } ]});
},
} }
return { state, actions } return { state, actions }

View File

@ -8,7 +8,7 @@ export function VideoFile({ source, thumbPosition, disabled }: {source: File, th
const { state, actions } = useVideoFile(source); const { state, actions } = useVideoFile(source);
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
const position = useRef(0); const position = useRef(0);
const player = useRef(); const player = useRef(null as null | HTMLVideoElement);
const seek = (offset: number) => { const seek = (offset: number) => {
if (player.current) { if (player.current) {
@ -29,13 +29,15 @@ export function VideoFile({ source, thumbPosition, disabled }: {source: File, th
} }
const onPause = () => { const onPause = () => {
player.current.pause(); if (player.current) {
player.current.pause();
}
} }
return ( return (
<div className={classes.asset}> <div className={classes.asset}>
{ state.videoUrl && ( { state.videoUrl && (
<video ref={player} muted onLoadedMetadata={() => setLoaded(true)} onPlay={onPause} src={state.videoUrl} width={'auto'} height={'100%'} playsinline="true" /> <video ref={player} muted onLoadedMetadata={() => setLoaded(true)} onPlay={onPause} src={state.videoUrl} width={'auto'} height={'100%'} playsInline={true} />
)} )}
{ loaded && !disabled && ( { loaded && !disabled && (
<ActionIcon className={classes.right} variant="light" onClick={() => seek(1)}> <ActionIcon className={classes.right} variant="light" onClick={() => seek(1)}>

View File

@ -21,8 +21,6 @@ export function useVideoAsset(topicId: string, asset: MediaAsset) {
if (focus && assetId != null) { if (focus && assetId != null) {
try { try {
const thumbUrl = await focus.getTopicAssetUrl(topicId, assetId); const thumbUrl = await focus.getTopicAssetUrl(topicId, assetId);
console.log("THUMB:", thumbUrl);
updateState({ thumbUrl }); updateState({ thumbUrl });
} catch (err) { } catch (err) {
console.log(err); console.log(err);

View File

@ -526,7 +526,11 @@ export class FocusModule implements Focus {
const mapped = filtered.map((asset: any) => { const mapped = filtered.map((asset: any) => {
if (asset.encrypted) { if (asset.encrypted) {
const { type, thumb, parts } = asset.encrypted; const { type, thumb, parts } = asset.encrypted;
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } }; if (type === 'image' || type === 'video') {
return { encrypted: { type, thumb: getAsset(thumb), parts: getAsset(parts) } };
} else {
return { encrypted: { type, parts: getAsset(parts) } };
}
} else if (asset.image) { } else if (asset.image) {
const { thumb, full } = asset.image; const { thumb, full } = asset.image;
return { image: { thumb: getAsset(thumb), full: getAsset(full) } }; return { image: { thumb: getAsset(thumb), full: getAsset(full) } };