From 25e4e68ded3959b8204ad30634737035f94e0c7d Mon Sep 17 00:00:00 2001 From: balzack Date: Thu, 2 Jan 2025 11:34:43 -0800 Subject: [PATCH] support unsealed image asset upload --- .../mobile/src/channel/Channel.styled.ts | 1 + .../src/conversation/Conversation.styled.ts | 9 ++++++ .../mobile/src/conversation/Conversation.tsx | 29 ++++++++++++++++-- .../src/conversation/useConversation.hook.ts | 30 +++++++++---------- app/sdk/src/focus.ts | 17 ++++++++--- app/sdk/src/types.ts | 2 +- 6 files changed, 66 insertions(+), 22 deletions(-) diff --git a/app/client/mobile/src/channel/Channel.styled.ts b/app/client/mobile/src/channel/Channel.styled.ts index 719effd5..883c0e26 100644 --- a/app/client/mobile/src/channel/Channel.styled.ts +++ b/app/client/mobile/src/channel/Channel.styled.ts @@ -14,6 +14,7 @@ export const styles = StyleSheet.create({ whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden', + height: 16, }, messageUnset: { fontSize: 12, diff --git a/app/client/mobile/src/conversation/Conversation.styled.ts b/app/client/mobile/src/conversation/Conversation.styled.ts index 05e2675e..f3447854 100644 --- a/app/client/mobile/src/conversation/Conversation.styled.ts +++ b/app/client/mobile/src/conversation/Conversation.styled.ts @@ -11,6 +11,15 @@ export const styles = StyleSheet.create({ right: 0, backgroundColor: 'transparent', }, + carousel: { + paddingLeft: 8, + paddingBottom: 8, + }, + assets: { + display: 'flex', + flexDirection: 'row', + gap: 16, + }, modal: { width: '100%', height: '100%', diff --git a/app/client/mobile/src/conversation/Conversation.tsx b/app/client/mobile/src/conversation/Conversation.tsx index 09f596a4..ef4bd2e2 100644 --- a/app/client/mobile/src/conversation/Conversation.tsx +++ b/app/client/mobile/src/conversation/Conversation.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useRef } from 'react'; -import {KeyboardAvoidingView, Modal, Platform, SafeAreaView, Pressable, View, FlatList, TouchableOpacity} from 'react-native'; +import {KeyboardAvoidingView, Modal, Platform, ScrollView, SafeAreaView, Pressable, View, FlatList, TouchableOpacity} from 'react-native'; import {styles} from './Conversation.styled'; import {useConversation} from './useConversation.hook'; import {Message} from '../message/Message'; @@ -9,6 +9,8 @@ import { Colors } from '../constants/Colors'; import { Confirm } from '../confirm/Confirm'; import ColorPicker from 'react-native-wheel-color-picker' import {BlurView} from '@react-native-community/blur'; +import ImagePicker from 'react-native-image-crop-picker' +import { ImageFile } from './imageFile/ImageFile'; const SCROLL_THRESHOLD = 16; @@ -102,6 +104,24 @@ export function Conversation({close}: {close: ()=>void}) { scrollOffset.current = offset; } + const addImage = async () => { + try { + const { path, mime } = await ImagePicker.openPicker({ mediaType: 'photo' }); + actions.addImage(`file://${path}`, mime); + } + catch (err) { + console.log(err); + } + } + + const media = state.assets.map((asset, index) => { + if (asset.type === 'image') { + return {}} /> + } else { + return <> + } + }); + return ( @@ -170,13 +190,18 @@ export function Conversation({close}: {close: ()=>void}) { + { media.length > 0 && ( + + { media } + + )} actions.setMessage(value)} /> - + diff --git a/app/client/mobile/src/conversation/useConversation.hook.ts b/app/client/mobile/src/conversation/useConversation.hook.ts index 6bf1002c..df2e1e36 100644 --- a/app/client/mobile/src/conversation/useConversation.hook.ts +++ b/app/client/mobile/src/conversation/useConversation.hook.ts @@ -44,7 +44,7 @@ export function useConversation() { subjectNames: [], unknownContacts: 0, message: '', - assets: [] as {type: string, file: File, position?: number, label?: string}[], + assets: [] as {type: string, path: string, mime?: string, position?: number, label?: string}[], textColor: '#444444', textColorSet: false, textSize: 16, @@ -178,13 +178,13 @@ export function useConversation() { const uploadAssets = state.assets.map(asset => { if (asset.type === 'image') { if (sealed) { - sources.push({ type: AssetType.Image, source: asset.file, transforms: [ - { type: TransformType.Thumb, appId: `it${sources.length}`, thumb: () => getImageThumb(asset.file) }, + sources.push({ type: AssetType.Image, source: asset.path, transforms: [ + { type: TransformType.Thumb, appId: `it${sources.length}`, thumb: () => getImageThumb(asset.path) }, { type: TransformType.Copy, appId: `ic${sources.length}` } ]}); return { encrypted: { type: 'image', thumb: `it${sources.length-1}`, parts: `ic${sources.length-1}` } }; } else { - sources.push({ type: AssetType.Image, source: asset.file, transforms: [ + sources.push({ type: AssetType.Image, source: asset.path, transforms: [ { type: TransformType.Thumb, appId: `it${sources.length}` }, { type: TransformType.Copy, appId: `ic${sources.length}` } ]}); @@ -194,19 +194,19 @@ export function useConversation() { if (sealed) { const videoThumb = async () => { try { - return await getVideoThumb(asset.file, asset.position); + return await getVideoThumb(asset.path, asset.position); } catch (err) { console.log(err); return placeholder; } }; - sources.push({ type: AssetType.Video, source: asset.file, transforms: [ + sources.push({ type: AssetType.Video, source: asset.path, transforms: [ { type: TransformType.Thumb, appId: `vt${sources.length}`, thumb: videoThumb }, { type: TransformType.Copy, appId: `vc${sources.length}` } ]}); return { encrypted: { type: 'video', thumb: `vt${sources.length-1}`, parts: `vc${sources.length-1}` } }; } else { - sources.push({ type: AssetType.Video, source: asset.file, transforms: [ + sources.push({ type: AssetType.Video, source: asset.path, transforms: [ { type: TransformType.Thumb, appId: `vt${sources.length}`, position: asset.position}, { type: TransformType.HighQuality, appId: `vh${sources.length}` }, { type: TransformType.LowQuality, appId: `vl${sources.length}` } @@ -215,26 +215,26 @@ export function useConversation() { } } else if (asset.type === 'audio') { if (sealed) { - sources.push({ type: AssetType.Audio, source: asset.file, transforms: [ + sources.push({ type: AssetType.Audio, source: asset.path, transforms: [ { type: TransformType.Copy, appId: `ac${sources.length}` } ]}); return { encrypted: { type: 'audio', label: asset.label, parts: `ac${sources.length-1}` } }; } else { - sources.push({ type: AssetType.Video, source: asset.file, transforms: [ + sources.push({ type: AssetType.Video, source: asset.path, transforms: [ { type: TransformType.Copy, appId: `ac${sources.length}` } ]}); return { audio: { label: asset.label, full: `ac${sources.length-1}` } }; } } else { - const extension = asset.file.name.split('.').pop(); - const label = asset.file.name.split('.').shift(); + const extension = asset.path.name.split('.').pop(); + const label = asset.path.name.split('.').shift(); if (sealed) { - sources.push({ type: AssetType.Binary, source: asset.file, transforms: [ + sources.push({ type: AssetType.Binary, source: asset.path, transforms: [ { type: TransformType.Copy, appId: `bc${sources.length}` } ]}); return { encrypted: { type: 'binary', label, extension, parts: `bc${sources.length-1}` } }; } else { - sources.push({ type: AssetType.Binary, source: asset.file, transforms: [ + sources.push({ type: AssetType.Binary, source: asset.path, transforms: [ { type: TransformType.Copy, appId: `bc${sources.length}` } ]}); return { binary: { label, extension, data: `bc${sources.length-1}` } }; @@ -282,9 +282,9 @@ export function useConversation() { updateState({ message: '', assets: [], progress: 0 }); } }, - addImage: (file: File) => { + addImage: (path: string, mime: string) => { const type = 'image'; - updateState({ assets: [ ...state.assets, { type, file } ]}); + updateState({ assets: [ ...state.assets, { type, path, mime } ]}); }, addVideo: (file: File) => { const type = 'video'; diff --git a/app/sdk/src/focus.ts b/app/sdk/src/focus.ts index b5f33082..ce15aac8 100644 --- a/app/sdk/src/focus.ts +++ b/app/sdk/src/focus.ts @@ -303,7 +303,7 @@ export class FocusModule implements Focus { }); } - private mirrorFile(source: any, topicId: string, progress: (percent: number)=>boolean): Promise { + private mirrorFile(source: File|string, topicId: string, progress: (percent: number)=>boolean): Promise { const { cardId, channelId, connection } = this; if (!connection) { throw new Error('disconnected from channel'); @@ -312,7 +312,11 @@ export class FocusModule implements Focus { const params = `${cardId ? 'contact' : 'agent'}=${token}&body=multipart` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}` const formData = new FormData(); - formData.append('asset', source); + if (typeof source === 'string') { // file path used in mobile + formData.append("asset", {uri: source, name: 'asset', type: 'application/octent-stream'}); + } else { // file object used in browser + formData.append('asset', source); + } return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); @@ -336,7 +340,7 @@ export class FocusModule implements Focus { }); } - private transformFile(source: any, topicId: string, transforms: string[], progress: (percent: number)=>boolean): Promise<{assetId: string, transform: string}[]> { + private transformFile(source: File|string, topicId: string, transforms: string[], progress: (percent: number)=>boolean): Promise<{assetId: string, transform: string}[]> { const { cardId, channelId, connection } = this; if (!connection) { throw new Error('disconnected from channel'); @@ -345,7 +349,12 @@ export class FocusModule implements Focus { const params = `${cardId ? 'contact' : 'agent'}=${token}&transforms=${encodeURIComponent(JSON.stringify(transforms))}` const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}` const formData = new FormData(); - formData.append('asset', source); + + if (typeof source === 'string') { // file path used in mobile + formData.append("asset", {uri: source, name: 'asset', type: 'application/octent-stream'}); + } else { // file object used in browser + formData.append('asset', source); + } return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); diff --git a/app/sdk/src/types.ts b/app/sdk/src/types.ts index 715fa82b..7b2a1602 100644 --- a/app/sdk/src/types.ts +++ b/app/sdk/src/types.ts @@ -148,7 +148,7 @@ export enum AssetType { export type AssetSource = { type: AssetType; - source: any; + source: File|string; transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise}[], }