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 { ImageFile } from './imageFile/ImageFile';
import { VideoFile } from './videoFile/VideoFile';
import { AudioFile } from './audioFile/AudioFile';
import { BinaryFile } from './binaryFile/BinaryFile';
const PAD_HEIGHT = (1024 - 64);
const LOAD_DEBOUNCE = 1000;
@ -28,6 +30,8 @@ export function Conversation() {
const { state, actions } = useConversation();
const attachImage = 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) => {
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 () => {
if (!sending) {
setSending(true);
@ -111,14 +127,13 @@ export function Conversation() {
return <ImageFile key={index} source={asset.file} />
} else if (asset.type === 'video') {
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 {
return <div key={index}></div>
return <BinaryFile key={index} source={asset.file} />
}
});
console.log(state.assets);
return (
<div className={classes.conversation}>
<div className={classes.header}>
@ -180,6 +195,8 @@ console.log(state.assets);
<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="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}>
{ media }
</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()}>
<IconVideo />
</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 />
</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 />
</ActionIcon>
<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) => {
const url = URL.createObjectURL(file);
var video = document.createElement("video");
@ -71,7 +71,7 @@ function getVideoThumb(file: File, position: number) {
video.src = url;
video.muted = true;
video.playsInline = true;
video.currentTime = position;
video.currentTime = position ? position : 0;
video.play();
});
}
@ -98,7 +98,7 @@ export function useConversation() {
subjectNames: [],
unknownContacts: 0,
message: '',
assets: [] as {type: string, file: File}[],
assets: [] as {type: string, file: File, position?: number}[],
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -106,7 +106,7 @@ export function useConversation() {
setState((s) => ({ ...s, ...value }))
}
const updateAsset = (index, value) => {
const updateAsset = (index: number, value: any) => {
setState((s) => {
s.assets[index] = { ...s.assets[index], ...value };
return { ...s };
@ -270,7 +270,11 @@ export function useConversation() {
const type = asset.encrypted.type;
const thumb = uploaded.find(upload => upload.appId === asset.encrypted.thumb)?.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) {
const thumb = uploaded.find(upload => upload.appId === asset.image.thumb)?.assetId;
const full = uploaded.find(upload => upload.appId === asset.image.full)?.assetId;
@ -288,6 +292,8 @@ export function useConversation() {
return { binary: { data } };
}
});
console.log(assets, uploaded);
return { text: state.message, assets };
}
const progress = (precent: number) => {};
@ -303,6 +309,14 @@ export function useConversation() {
const type = 'video';
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 }

View File

@ -8,7 +8,7 @@ export function VideoFile({ source, thumbPosition, disabled }: {source: File, th
const { state, actions } = useVideoFile(source);
const [loaded, setLoaded] = useState(false);
const position = useRef(0);
const player = useRef();
const player = useRef(null as null | HTMLVideoElement);
const seek = (offset: number) => {
if (player.current) {
@ -29,13 +29,15 @@ export function VideoFile({ source, thumbPosition, disabled }: {source: File, th
}
const onPause = () => {
player.current.pause();
if (player.current) {
player.current.pause();
}
}
return (
<div className={classes.asset}>
{ 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 && (
<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) {
try {
const thumbUrl = await focus.getTopicAssetUrl(topicId, assetId);
console.log("THUMB:", thumbUrl);
updateState({ thumbUrl });
} catch (err) {
console.log(err);

View File

@ -526,7 +526,11 @@ export class FocusModule implements Focus {
const mapped = filtered.map((asset: any) => {
if (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) {
const { thumb, full } = asset.image;
return { image: { thumb: getAsset(thumb), full: getAsset(full) } };