mirror of
https://github.com/balzack/databag.git
synced 2025-05-05 07:55:15 +00:00
support unsealed image asset upload
This commit is contained in:
parent
f1033777db
commit
25e4e68ded
@ -14,6 +14,7 @@ export const styles = StyleSheet.create({
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
height: 16,
|
||||||
},
|
},
|
||||||
messageUnset: {
|
messageUnset: {
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
@ -11,6 +11,15 @@ export const styles = StyleSheet.create({
|
|||||||
right: 0,
|
right: 0,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
},
|
},
|
||||||
|
carousel: {
|
||||||
|
paddingLeft: 8,
|
||||||
|
paddingBottom: 8,
|
||||||
|
},
|
||||||
|
assets: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 16,
|
||||||
|
},
|
||||||
modal: {
|
modal: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
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 {styles} from './Conversation.styled';
|
||||||
import {useConversation} from './useConversation.hook';
|
import {useConversation} from './useConversation.hook';
|
||||||
import {Message} from '../message/Message';
|
import {Message} from '../message/Message';
|
||||||
@ -9,6 +9,8 @@ import { Colors } from '../constants/Colors';
|
|||||||
import { Confirm } from '../confirm/Confirm';
|
import { Confirm } from '../confirm/Confirm';
|
||||||
import ColorPicker from 'react-native-wheel-color-picker'
|
import ColorPicker from 'react-native-wheel-color-picker'
|
||||||
import {BlurView} from '@react-native-community/blur';
|
import {BlurView} from '@react-native-community/blur';
|
||||||
|
import ImagePicker from 'react-native-image-crop-picker'
|
||||||
|
import { ImageFile } from './imageFile/ImageFile';
|
||||||
|
|
||||||
const SCROLL_THRESHOLD = 16;
|
const SCROLL_THRESHOLD = 16;
|
||||||
|
|
||||||
@ -102,6 +104,24 @@ export function Conversation({close}: {close: ()=>void}) {
|
|||||||
scrollOffset.current = offset;
|
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 <ImageFile key={index} path={asset.path} disabled={false} remove={()=>{}} />
|
||||||
|
} else {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 50} style={styles.conversation}>
|
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 50} style={styles.conversation}>
|
||||||
<SafeAreaView style={styles.header}>
|
<SafeAreaView style={styles.header}>
|
||||||
@ -170,13 +190,18 @@ export function Conversation({close}: {close: ()=>void}) {
|
|||||||
<Divider style={styles.border} bold={true} />
|
<Divider style={styles.border} bold={true} />
|
||||||
<Confirm show={alert} params={alertParams} />
|
<Confirm show={alert} params={alertParams} />
|
||||||
<View style={styles.add}>
|
<View style={styles.add}>
|
||||||
|
{ media.length > 0 && (
|
||||||
|
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.carousel} contentContainerStyle={styles.assets}>
|
||||||
|
{ media }
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
<TextInput multiline={true} mode="outlined" style={{ ...styles.message, fontSize: state.textSize }}
|
<TextInput multiline={true} mode="outlined" style={{ ...styles.message, fontSize: state.textSize }}
|
||||||
textColor={state.textColorSet ? state.textColor : undefined} outlineColor="transparent" activeOutlineColor="transparent"spellcheck={false}
|
textColor={state.textColorSet ? state.textColor : undefined} outlineColor="transparent" activeOutlineColor="transparent"spellcheck={false}
|
||||||
autoComplete="off" autoCapitalize="none" autoCorrect={false} placeholder={state.strings.newMessage} placeholderTextColor={state.textColorSet ? state.textColor : undefined}
|
autoComplete="off" autoCapitalize="none" autoCorrect={false} placeholder={state.strings.newMessage} placeholderTextColor={state.textColorSet ? state.textColor : undefined}
|
||||||
cursorColor={state.textColorSet ? state.textColor : undefined} value={state.message} onChangeText={value => actions.setMessage(value)} />
|
cursorColor={state.textColorSet ? state.textColor : undefined} value={state.message} onChangeText={value => actions.setMessage(value)} />
|
||||||
|
|
||||||
<View style={styles.controls}>
|
<View style={styles.controls}>
|
||||||
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="camera" size={24} color={Colors.primary} /></Surface></Pressable>
|
<Pressable style={styles.control} onPress={addImage}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="camera" size={24} color={Colors.primary} /></Surface></Pressable>
|
||||||
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="video-outline" size={24} color={Colors.primary} /></Surface></Pressable>
|
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="video-outline" size={24} color={Colors.primary} /></Surface></Pressable>
|
||||||
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="volume-high" size={24} color={Colors.primary} /></Surface></Pressable>
|
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="volume-high" size={24} color={Colors.primary} /></Surface></Pressable>
|
||||||
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="file-outline" size={24} color={Colors.primary} /></Surface></Pressable>
|
<Pressable style={styles.control}><Surface style={styles.surface} elevation={2}><Icon style={styles.button} source="file-outline" size={24} color={Colors.primary} /></Surface></Pressable>
|
||||||
|
@ -44,7 +44,7 @@ export function useConversation() {
|
|||||||
subjectNames: [],
|
subjectNames: [],
|
||||||
unknownContacts: 0,
|
unknownContacts: 0,
|
||||||
message: '',
|
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',
|
textColor: '#444444',
|
||||||
textColorSet: false,
|
textColorSet: false,
|
||||||
textSize: 16,
|
textSize: 16,
|
||||||
@ -178,13 +178,13 @@ export function useConversation() {
|
|||||||
const uploadAssets = state.assets.map(asset => {
|
const uploadAssets = state.assets.map(asset => {
|
||||||
if (asset.type === 'image') {
|
if (asset.type === 'image') {
|
||||||
if (sealed) {
|
if (sealed) {
|
||||||
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}`, thumb: () => getImageThumb(asset.file) },
|
{ type: TransformType.Thumb, appId: `it${sources.length}`, thumb: () => getImageThumb(asset.path) },
|
||||||
{ type: TransformType.Copy, appId: `ic${sources.length}` }
|
{ type: TransformType.Copy, appId: `ic${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { encrypted: { type: 'image', thumb: `it${sources.length-1}`, parts: `ic${sources.length-1}` } };
|
return { encrypted: { type: 'image', thumb: `it${sources.length-1}`, parts: `ic${sources.length-1}` } };
|
||||||
} else {
|
} 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.Thumb, appId: `it${sources.length}` },
|
||||||
{ type: TransformType.Copy, appId: `ic${sources.length}` }
|
{ type: TransformType.Copy, appId: `ic${sources.length}` }
|
||||||
]});
|
]});
|
||||||
@ -194,19 +194,19 @@ export function useConversation() {
|
|||||||
if (sealed) {
|
if (sealed) {
|
||||||
const videoThumb = async () => {
|
const videoThumb = async () => {
|
||||||
try {
|
try {
|
||||||
return await getVideoThumb(asset.file, asset.position);
|
return await getVideoThumb(asset.path, asset.position);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return placeholder;
|
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.Thumb, appId: `vt${sources.length}`, thumb: videoThumb },
|
||||||
{ type: TransformType.Copy, appId: `vc${sources.length}` }
|
{ type: TransformType.Copy, appId: `vc${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { encrypted: { type: 'video', thumb: `vt${sources.length-1}`, parts: `vc${sources.length-1}` } };
|
return { encrypted: { type: 'video', thumb: `vt${sources.length-1}`, parts: `vc${sources.length-1}` } };
|
||||||
} else {
|
} 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.Thumb, appId: `vt${sources.length}`, position: asset.position},
|
||||||
{ type: TransformType.HighQuality, appId: `vh${sources.length}` },
|
{ type: TransformType.HighQuality, appId: `vh${sources.length}` },
|
||||||
{ type: TransformType.LowQuality, appId: `vl${sources.length}` }
|
{ type: TransformType.LowQuality, appId: `vl${sources.length}` }
|
||||||
@ -215,26 +215,26 @@ export function useConversation() {
|
|||||||
}
|
}
|
||||||
} else if (asset.type === 'audio') {
|
} else if (asset.type === 'audio') {
|
||||||
if (sealed) {
|
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}` }
|
{ type: TransformType.Copy, appId: `ac${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { encrypted: { type: 'audio', label: asset.label, parts: `ac${sources.length-1}` } };
|
return { encrypted: { type: 'audio', label: asset.label, parts: `ac${sources.length-1}` } };
|
||||||
} else {
|
} 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}` }
|
{ type: TransformType.Copy, appId: `ac${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { audio: { label: asset.label, full: `ac${sources.length-1}` } };
|
return { audio: { label: asset.label, full: `ac${sources.length-1}` } };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const extension = asset.file.name.split('.').pop();
|
const extension = asset.path.name.split('.').pop();
|
||||||
const label = asset.file.name.split('.').shift();
|
const label = asset.path.name.split('.').shift();
|
||||||
if (sealed) {
|
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}` }
|
{ type: TransformType.Copy, appId: `bc${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { encrypted: { type: 'binary', label, extension, parts: `bc${sources.length-1}` } };
|
return { encrypted: { type: 'binary', label, extension, parts: `bc${sources.length-1}` } };
|
||||||
} else {
|
} 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}` }
|
{ type: TransformType.Copy, appId: `bc${sources.length}` }
|
||||||
]});
|
]});
|
||||||
return { binary: { label, extension, data: `bc${sources.length-1}` } };
|
return { binary: { label, extension, data: `bc${sources.length-1}` } };
|
||||||
@ -282,9 +282,9 @@ export function useConversation() {
|
|||||||
updateState({ message: '', assets: [], progress: 0 });
|
updateState({ message: '', assets: [], progress: 0 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addImage: (file: File) => {
|
addImage: (path: string, mime: string) => {
|
||||||
const type = 'image';
|
const type = 'image';
|
||||||
updateState({ assets: [ ...state.assets, { type, file } ]});
|
updateState({ assets: [ ...state.assets, { type, path, mime } ]});
|
||||||
},
|
},
|
||||||
addVideo: (file: File) => {
|
addVideo: (file: File) => {
|
||||||
const type = 'video';
|
const type = 'video';
|
||||||
|
@ -303,7 +303,7 @@ export class FocusModule implements Focus {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private mirrorFile(source: any, topicId: string, progress: (percent: number)=>boolean): Promise<string> {
|
private mirrorFile(source: File|string, topicId: string, progress: (percent: number)=>boolean): Promise<string> {
|
||||||
const { cardId, channelId, connection } = this;
|
const { cardId, channelId, connection } = this;
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
throw new Error('disconnected from channel');
|
throw new Error('disconnected from channel');
|
||||||
@ -312,7 +312,11 @@ export class FocusModule implements Focus {
|
|||||||
const params = `${cardId ? 'contact' : 'agent'}=${token}&body=multipart`
|
const params = `${cardId ? 'contact' : 'agent'}=${token}&body=multipart`
|
||||||
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}`
|
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/blocks?${params}`
|
||||||
const formData = new FormData();
|
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) {
|
return new Promise(function (resolve, reject) {
|
||||||
const xhr = new XMLHttpRequest();
|
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;
|
const { cardId, channelId, connection } = this;
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
throw new Error('disconnected from channel');
|
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 params = `${cardId ? 'contact' : 'agent'}=${token}&transforms=${encodeURIComponent(JSON.stringify(transforms))}`
|
||||||
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}`
|
const url = `http${secure ? 's' : ''}://${node}/content/channels/${channelId}/topics/${topicId}/assets?${params}`
|
||||||
const formData = new FormData();
|
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) {
|
return new Promise(function (resolve, reject) {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
@ -148,7 +148,7 @@ export enum AssetType {
|
|||||||
|
|
||||||
export type AssetSource = {
|
export type AssetSource = {
|
||||||
type: AssetType;
|
type: AssetType;
|
||||||
source: any;
|
source: File|string;
|
||||||
transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise<string>}[],
|
transforms: {type: TransformType, appId: string, position?: number, thumb?: ()=>Promise<string>}[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user