@fto-consult/expo-ui 8.46.2 → 8.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-app/dependencies.js +2 -1
- package/package.json +1 -1
- package/src/components/Date/TimePickerModal/index.js +8 -1
- package/src/components/Image/Cropper/ExpoImageManipulator.js +311 -0
- package/src/components/Image/Cropper/ImageCropOverlay.js +219 -0
- package/src/components/Image/Cropper/index.js +83 -0
- package/src/components/Image/Editor/index.js +0 -1
- package/src/components/Image/index.js +27 -18
- package/src/screens/Help/openLibraries.js +8 -18
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@fto-consult/expo-ui",
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.47.0",
|
4
4
|
"description": "Bibliothèque de composants UI Expo,react-native",
|
5
5
|
"react-native-paper-doc": "https://github.com/callstack/react-native-paper/tree/main/docs/docs/guides",
|
6
6
|
"scripts": {
|
@@ -1,9 +1,16 @@
|
|
1
1
|
import {TimePickerModal} from 'react-native-paper-dates';
|
2
2
|
import {PaperProvider} from 'react-native-paper';
|
3
3
|
import theme from "$theme";
|
4
|
+
import FontIcon from "$ecomponents/Icon/Font";
|
4
5
|
|
5
6
|
export default function TimePickerModalComponent(props){
|
6
|
-
return <PaperProvider theme={theme}
|
7
|
+
return <PaperProvider theme={theme}
|
8
|
+
settings={{
|
9
|
+
icon: (props) => {
|
10
|
+
return <FontIcon {...props}/>
|
11
|
+
},
|
12
|
+
}}
|
13
|
+
>
|
7
14
|
<TimePickerModal {...props}/>
|
8
15
|
</PaperProvider>
|
9
16
|
}
|
@@ -0,0 +1,311 @@
|
|
1
|
+
import React, { Component } from 'react'
|
2
|
+
import {
|
3
|
+
Dimensions,
|
4
|
+
Image,
|
5
|
+
ScrollView,
|
6
|
+
Modal,
|
7
|
+
View,
|
8
|
+
Text,
|
9
|
+
SafeAreaView,
|
10
|
+
TouchableOpacity,
|
11
|
+
} from 'react-native'
|
12
|
+
import * as ImageManipulator from 'expo-image-manipulator'
|
13
|
+
import PropTypes from 'prop-types'
|
14
|
+
import AutoHeightImage from '../../Avatar/AutoHeightImage'
|
15
|
+
import Icon from "$ecomponents/Icon";
|
16
|
+
import {isIphoneX } from 'react-native-iphone-x-helper'
|
17
|
+
import ImageCropOverlay from './ImageCropOverlay'
|
18
|
+
|
19
|
+
const { width, height } = Dimensions.get('window')
|
20
|
+
|
21
|
+
|
22
|
+
class ExpoImageManipulator extends Component {
|
23
|
+
constructor(props) {
|
24
|
+
super(props)
|
25
|
+
const { squareAspect } = this.props
|
26
|
+
this.state = {
|
27
|
+
cropMode: true,
|
28
|
+
processing: false,
|
29
|
+
zoomScale: 1,
|
30
|
+
squareAspect,
|
31
|
+
}
|
32
|
+
|
33
|
+
this.scrollOffset = 0
|
34
|
+
|
35
|
+
this.currentPos = {
|
36
|
+
left: 0,
|
37
|
+
top: 0,
|
38
|
+
}
|
39
|
+
|
40
|
+
this.currentSize = {
|
41
|
+
width: 0,
|
42
|
+
height: 0,
|
43
|
+
}
|
44
|
+
|
45
|
+
this.maxSizes = {
|
46
|
+
width: 0,
|
47
|
+
height: 0,
|
48
|
+
}
|
49
|
+
|
50
|
+
this.actualSize = {
|
51
|
+
width: 0,
|
52
|
+
height: 0
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
async componentDidMount() {
|
57
|
+
await this.onConvertImageToEditableSize()
|
58
|
+
}
|
59
|
+
|
60
|
+
async onConvertImageToEditableSize() {
|
61
|
+
const { photo: { uri: rawUri } } = this.props
|
62
|
+
const { uri, width, height } = await ImageManipulator.manipulateAsync(rawUri,
|
63
|
+
[
|
64
|
+
{
|
65
|
+
resize: {
|
66
|
+
width: 1080,
|
67
|
+
},
|
68
|
+
},
|
69
|
+
])
|
70
|
+
this.setState({
|
71
|
+
uri,
|
72
|
+
})
|
73
|
+
this.actualSize.width = width
|
74
|
+
this.actualSize.height = height
|
75
|
+
}
|
76
|
+
onCropImage = () => {
|
77
|
+
this.setState({ processing: true })
|
78
|
+
const { uri } = this.state
|
79
|
+
Image.getSize(uri, async (actualWidth, actualHeight) => {
|
80
|
+
let cropObj = this.getCropBounds(actualWidth, actualHeight);
|
81
|
+
if (cropObj.height > 0 && cropObj.width > 0) {
|
82
|
+
let uriToCrop = uri
|
83
|
+
const { uri: uriCroped, base64, width: croppedWidth, height: croppedHeight } = await this.crop(cropObj, uriToCrop)
|
84
|
+
|
85
|
+
this.actualSize.width = croppedWidth
|
86
|
+
this.actualSize.height = croppedHeight
|
87
|
+
|
88
|
+
this.setState({
|
89
|
+
uri: uriCroped, base64, cropMode: false, processing: false,
|
90
|
+
})
|
91
|
+
} else {
|
92
|
+
this.setState({cropMode: false, processing: false})
|
93
|
+
}
|
94
|
+
})
|
95
|
+
}
|
96
|
+
|
97
|
+
onRotateImage = async () => {
|
98
|
+
const { uri } = this.state
|
99
|
+
let uriToCrop = uri
|
100
|
+
Image.getSize(uri, async (width2, height2) => {
|
101
|
+
const { uri: rotUri, base64 } = await this.rotate(uriToCrop, width2, height2)
|
102
|
+
this.setState({ uri: rotUri, base64 })
|
103
|
+
})
|
104
|
+
}
|
105
|
+
|
106
|
+
onFlipImage = async (orientation) => {
|
107
|
+
const { uri } = this.state
|
108
|
+
let uriToCrop = uri
|
109
|
+
Image.getSize(uri, async (width2, height2) => {
|
110
|
+
const { uri: rotUri, base64 } = await this.filp(uriToCrop, orientation)
|
111
|
+
this.setState({ uri: rotUri, base64 })
|
112
|
+
})
|
113
|
+
}
|
114
|
+
|
115
|
+
onHandleScroll = (event) => {
|
116
|
+
this.scrollOffset = event.nativeEvent.contentOffset.y
|
117
|
+
}
|
118
|
+
|
119
|
+
getCropBounds = (actualWidth, actualHeight) => {
|
120
|
+
let imageRatio = actualHeight / actualWidth
|
121
|
+
var originalHeight = Dimensions.get('window').height - 64
|
122
|
+
if (isIphoneX()) {
|
123
|
+
originalHeight = Dimensions.get('window').height - 122
|
124
|
+
}
|
125
|
+
let renderedImageWidth = imageRatio < (originalHeight / width) ? width : originalHeight / imageRatio
|
126
|
+
let renderedImageHeight = imageRatio < (originalHeight / width) ? width * imageRatio : originalHeight
|
127
|
+
|
128
|
+
let renderedImageY = (originalHeight - renderedImageHeight) / 2.0
|
129
|
+
let renderedImageX = (width - renderedImageWidth) / 2.0
|
130
|
+
|
131
|
+
const renderImageObj = {
|
132
|
+
left: renderedImageX,
|
133
|
+
top: renderedImageY,
|
134
|
+
width: renderedImageWidth,
|
135
|
+
height: renderedImageHeight,
|
136
|
+
}
|
137
|
+
const cropOverlayObj = {
|
138
|
+
left: this.currentPos.left,
|
139
|
+
top: this.currentPos.top,
|
140
|
+
width: this.currentSize.width,
|
141
|
+
height: this.currentSize.height,
|
142
|
+
}
|
143
|
+
|
144
|
+
var intersectAreaObj = {}
|
145
|
+
|
146
|
+
let x = Math.max(renderImageObj.left, cropOverlayObj.left);
|
147
|
+
let num1 = Math.min(renderImageObj.left + renderImageObj.width, cropOverlayObj.left + cropOverlayObj.width);
|
148
|
+
let y = Math.max(renderImageObj.top, cropOverlayObj.top);
|
149
|
+
let num2 = Math.min(renderImageObj.top + renderImageObj.height, cropOverlayObj.top + cropOverlayObj.height);
|
150
|
+
if (num1 >= x && num2 >= y)
|
151
|
+
intersectAreaObj = {
|
152
|
+
originX: (x - renderedImageX) * (actualWidth / renderedImageWidth) ,
|
153
|
+
originY: (y - renderedImageY) * (actualWidth / renderedImageWidth),
|
154
|
+
width: (num1 - x) * (actualWidth / renderedImageWidth),
|
155
|
+
height: (num2 - y) * (actualWidth / renderedImageWidth)
|
156
|
+
}
|
157
|
+
else {
|
158
|
+
intersectAreaObj = {
|
159
|
+
originX: x - renderedImageX,
|
160
|
+
originY: y - renderedImageY,
|
161
|
+
width: 0,
|
162
|
+
height: 0
|
163
|
+
}
|
164
|
+
}
|
165
|
+
return intersectAreaObj
|
166
|
+
}
|
167
|
+
|
168
|
+
filp = async (uri, orientation) => {
|
169
|
+
const { saveOptions } = this.props
|
170
|
+
const manipResult = await ImageManipulator.manipulateAsync(uri, [{
|
171
|
+
flip: orientation == 'vertical' ? ImageManipulator.FlipType.Vertical : ImageManipulator.FlipType.Horizontal
|
172
|
+
}],
|
173
|
+
saveOptions
|
174
|
+
);
|
175
|
+
return manipResult;
|
176
|
+
};
|
177
|
+
|
178
|
+
rotate = async (uri, width2, height2) => {
|
179
|
+
const { saveOptions } = this.props
|
180
|
+
const manipResult = await ImageManipulator.manipulateAsync(uri, [{
|
181
|
+
rotate: -90,
|
182
|
+
}, {
|
183
|
+
resize: {
|
184
|
+
width: this.trueWidth || width2,
|
185
|
+
// height: this.trueHeight || height2,
|
186
|
+
},
|
187
|
+
}], saveOptions)
|
188
|
+
return manipResult
|
189
|
+
}
|
190
|
+
|
191
|
+
crop = async (cropObj, uri) => {
|
192
|
+
const { saveOptions } = this.props
|
193
|
+
if (cropObj.height > 0 && cropObj.width > 0) {
|
194
|
+
const manipResult = await ImageManipulator.manipulateAsync(
|
195
|
+
uri,
|
196
|
+
[{
|
197
|
+
crop: cropObj,
|
198
|
+
}],
|
199
|
+
saveOptions,
|
200
|
+
)
|
201
|
+
return manipResult
|
202
|
+
}
|
203
|
+
return {
|
204
|
+
uri: null,
|
205
|
+
base64: null,
|
206
|
+
}
|
207
|
+
};
|
208
|
+
|
209
|
+
calculateMaxSizes = (event) => {
|
210
|
+
let w1 = event.nativeEvent.layout.width || 100
|
211
|
+
let h1 = event.nativeEvent.layout.height || 100
|
212
|
+
if (this.state.squareAspect) {
|
213
|
+
if (w1 < h1) h1 = w1
|
214
|
+
else w1 = h1
|
215
|
+
}
|
216
|
+
this.maxSizes.width = w1
|
217
|
+
this.maxSizes.height = h1
|
218
|
+
};
|
219
|
+
|
220
|
+
// eslint-disable-next-line camelcase
|
221
|
+
async UNSAFE_componentWillReceiveProps() {
|
222
|
+
await this.onConvertImageToEditableSize()
|
223
|
+
}
|
224
|
+
|
225
|
+
zoomImage() {
|
226
|
+
// this.refs.imageScrollView.zoomScale = 5
|
227
|
+
// this.setState({width: width})
|
228
|
+
// this.setState({zoomScale: 5})
|
229
|
+
|
230
|
+
// this.setState(curHeight)
|
231
|
+
}
|
232
|
+
|
233
|
+
render() {
|
234
|
+
const {
|
235
|
+
uri,
|
236
|
+
base64,
|
237
|
+
cropMode,
|
238
|
+
processing,
|
239
|
+
zoomScale
|
240
|
+
} = this.state
|
241
|
+
|
242
|
+
let imageRatio = this.actualSize.height / this.actualSize.width
|
243
|
+
var originalHeight = Dimensions.get('window').height - 64
|
244
|
+
if (isIphoneX()) {
|
245
|
+
originalHeight = Dimensions.get('window').height - 122
|
246
|
+
}
|
247
|
+
|
248
|
+
let cropRatio = originalHeight / width
|
249
|
+
|
250
|
+
let cropWidth = imageRatio < cropRatio ? width : originalHeight / imageRatio
|
251
|
+
let cropHeight = imageRatio < cropRatio ? width * imageRatio : originalHeight
|
252
|
+
|
253
|
+
let cropInitialTop = (originalHeight - cropHeight) / 2.0
|
254
|
+
let cropInitialLeft = (width - cropWidth) / 2.0
|
255
|
+
|
256
|
+
|
257
|
+
if (this.currentSize.width == 0 && cropMode) {
|
258
|
+
this.currentSize.width = cropWidth;
|
259
|
+
this.currentSize.height = cropHeight;
|
260
|
+
|
261
|
+
this.currentPos.top = cropInitialTop;
|
262
|
+
this.currentPos.left = cropInitialLeft;
|
263
|
+
}
|
264
|
+
return <>
|
265
|
+
<AutoHeightImage
|
266
|
+
style={{ backgroundColor: 'black' }}
|
267
|
+
source={{ uri }}
|
268
|
+
resizeMode={imageRatio >= 1 ? "contain" : 'contain'}
|
269
|
+
width={width}
|
270
|
+
height={originalHeight}
|
271
|
+
onLayout={this.calculateMaxSizes}
|
272
|
+
/>
|
273
|
+
{!!cropMode && (
|
274
|
+
<ImageCropOverlay onLayoutChanged={(top, left, width, height) => {
|
275
|
+
this.currentSize.width = width;
|
276
|
+
this.currentSize.height = height;
|
277
|
+
this.currentPos.top = top
|
278
|
+
this.currentPos.left = left
|
279
|
+
}} initialWidth={cropWidth} initialHeight={cropHeight} initialTop={cropInitialTop} initialLeft={cropInitialLeft} minHeight={100} minWidth={100} />
|
280
|
+
)}
|
281
|
+
</>
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
export default ExpoImageManipulator
|
286
|
+
|
287
|
+
ExpoImageManipulator.defaultProps = {
|
288
|
+
onPictureChoosed: ({ uri, base64 }) => console.log('URI:', uri, base64),
|
289
|
+
btnTexts: {
|
290
|
+
crop: 'Crop',
|
291
|
+
rotate: 'Rotate',
|
292
|
+
done: 'Done',
|
293
|
+
processing: 'Processing',
|
294
|
+
},
|
295
|
+
dragVelocity: 100,
|
296
|
+
resizeVelocity: 50,
|
297
|
+
saveOptions: {
|
298
|
+
compress: 1,
|
299
|
+
format: ImageManipulator.SaveFormat.PNG,
|
300
|
+
base64: false,
|
301
|
+
},
|
302
|
+
}
|
303
|
+
|
304
|
+
ExpoImageManipulator.propTypes = {
|
305
|
+
onPictureChoosed: PropTypes.func,
|
306
|
+
btnTexts: PropTypes.object,
|
307
|
+
saveOptions: PropTypes.object,
|
308
|
+
photo: PropTypes.object.isRequired,
|
309
|
+
dragVelocity: PropTypes.number,
|
310
|
+
resizeVelocity: PropTypes.number,
|
311
|
+
}
|
@@ -0,0 +1,219 @@
|
|
1
|
+
import React, { Component } from 'react'
|
2
|
+
import { View, PanResponder, Dimensions } from 'react-native';
|
3
|
+
|
4
|
+
class ImageCropOverlay extends React.Component {
|
5
|
+
|
6
|
+
state = {
|
7
|
+
draggingTL: false,
|
8
|
+
draggingTM: false,
|
9
|
+
draggingTR: false,
|
10
|
+
draggingML: false,
|
11
|
+
draggingMM: false,
|
12
|
+
draggingMR: false,
|
13
|
+
draggingBL: false,
|
14
|
+
draggingBM: false,
|
15
|
+
draggingBR: false,
|
16
|
+
initialTop: this.props.initialTop,
|
17
|
+
initialLeft: this.props.initialLeft,
|
18
|
+
initialWidth: this.props.initialWidth,
|
19
|
+
initialHeight: this.props.initialHeight,
|
20
|
+
|
21
|
+
offsetTop: 0,
|
22
|
+
offsetLeft: 0,
|
23
|
+
}
|
24
|
+
|
25
|
+
panResponder = {}
|
26
|
+
|
27
|
+
UNSAFE_componentWillMount() {
|
28
|
+
this.panResponder = PanResponder.create({
|
29
|
+
onStartShouldSetPanResponder: this.handleStartShouldSetPanResponder,
|
30
|
+
onPanResponderGrant: this.handlePanResponderGrant,
|
31
|
+
onPanResponderMove: this.handlePanResponderMove,
|
32
|
+
onPanResponderRelease: this.handlePanResponderEnd,
|
33
|
+
onPanResponderTerminate: this.handlePanResponderEnd,
|
34
|
+
})
|
35
|
+
}
|
36
|
+
|
37
|
+
render() {
|
38
|
+
const { draggingTL, draggingTM, draggingTR, draggingML, draggingMM, draggingMR, draggingBL, draggingBM, draggingBR, initialTop, initialLeft, initialHeight, initialWidth, offsetTop, offsetLeft} = this.state
|
39
|
+
const style = {}
|
40
|
+
|
41
|
+
style.top = initialTop + ((draggingTL || draggingTM || draggingTR || draggingMM) ? offsetTop : 0)
|
42
|
+
style.left = initialLeft + ((draggingTL || draggingML || draggingBL || draggingMM) ? offsetLeft : 0)
|
43
|
+
style.width = initialWidth + ((draggingTL || draggingML || draggingBL) ? - offsetLeft : (draggingTM || draggingMM || draggingBM) ? 0 : offsetLeft)
|
44
|
+
style.height = initialHeight + ((draggingTL || draggingTM || draggingTR) ? - offsetTop : (draggingML || draggingMM || draggingMR) ? 0 : offsetTop)
|
45
|
+
|
46
|
+
if (style.width > this.props.initialWidth) {
|
47
|
+
style.width = this.props.initialWidth
|
48
|
+
}
|
49
|
+
if (style.width < this.props.minWidth) {
|
50
|
+
style.width = this.props.minWidth
|
51
|
+
}
|
52
|
+
if (style.height > this.props.initialHeight) {
|
53
|
+
style.height = this.props.initialHeight
|
54
|
+
}
|
55
|
+
if (style.height < this.props.minHeight) {
|
56
|
+
style.height = this.props.minHeight
|
57
|
+
}
|
58
|
+
return (
|
59
|
+
<View {...this.panResponder.panHandlers} style={[{flex: 1, justifyContent: 'center', alignItems: 'center', position: 'absolute', borderStyle: 'solid', borderWidth: 2, borderColor: '#a4a4a4', backgroundColor: 'rgb(0,0,0,0.5)'}, style]}>
|
60
|
+
<View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
|
61
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTL ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
62
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
63
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingTR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
64
|
+
</View>
|
65
|
+
<View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
|
66
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingML ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
67
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingMM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
68
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingMR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
69
|
+
</View>
|
70
|
+
<View style={{flexDirection: 'row', width: '100%', flex: 1/3, backgroundColor: 'transparent'}}>
|
71
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBL ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
72
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBM ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
73
|
+
<View style={{borderWidth: '#a4a4a4', borderWidth: 0, backgroundColor: draggingBR ? 'transparent' : 'transparent', flex: 1/3, height: '100%'}}></View>
|
74
|
+
</View>
|
75
|
+
<View style={{top: 0, left: 0, width: '100%', height: '100%', position: 'absolute', backgroundColor: 'rgba(0, 0, 0, 0.5)'}}>
|
76
|
+
<View style={{flex: 1/3, flexDirection: 'row'}}>
|
77
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
78
|
+
<View style={{ position: 'absolute', left: 5, top: 5, borderLeftWidth: 2, borderTopWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
|
79
|
+
</View>
|
80
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
81
|
+
</View>
|
82
|
+
<View style={{ flex: 3, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
83
|
+
<View style={{ position: 'absolute', right: 5, top: 5, borderRightWidth: 2, borderTopWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
|
84
|
+
</View>
|
85
|
+
</View>
|
86
|
+
<View style={{flex: 1/3, flexDirection: 'row'}}>
|
87
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
88
|
+
</View>
|
89
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
90
|
+
</View>
|
91
|
+
<View style={{ flex: 3, borderBottomWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
92
|
+
</View>
|
93
|
+
</View>
|
94
|
+
<View style={{flex: 1/3, flexDirection: 'row'}}>
|
95
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid', position: 'relative', }}>
|
96
|
+
<View style={{ position: 'absolute', left: 5, bottom: 5, borderLeftWidth: 2, borderBottomWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
|
97
|
+
</View>
|
98
|
+
<View style={{ flex: 3, borderRightWidth: 1, borderColor: '#c9c9c9', borderStyle: 'solid' }}>
|
99
|
+
</View>
|
100
|
+
<View style={{ flex: 3, position: 'relative' }}>
|
101
|
+
<View style={{ position: 'absolute', right: 5, bottom: 5, borderRightWidth: 2, borderBottomWidth: 2, height: 48, width: 48, borderColor: '#f4f4f4', borderStyle: 'solid' }}/>
|
102
|
+
</View>
|
103
|
+
</View>
|
104
|
+
</View>
|
105
|
+
</View>
|
106
|
+
)
|
107
|
+
}
|
108
|
+
|
109
|
+
getTappedItem(x, y) {
|
110
|
+
const { initialLeft, initialTop, initialWidth, initialHeight } = this.state
|
111
|
+
let xPos = parseInt((x - initialLeft) / (initialWidth / 3))
|
112
|
+
let yPos = parseInt((y - initialTop - 64) / (initialHeight / 3))
|
113
|
+
|
114
|
+
let index = yPos * 3 + xPos
|
115
|
+
if (index == 0) {
|
116
|
+
return 'tl';
|
117
|
+
} else if (index == 1) {
|
118
|
+
return 'tm';
|
119
|
+
} else if (index == 2) {
|
120
|
+
return 'tr';
|
121
|
+
} else if (index == 3) {
|
122
|
+
return 'ml';
|
123
|
+
} else if (index == 4) {
|
124
|
+
return 'mm';
|
125
|
+
} else if (index == 5) {
|
126
|
+
return 'mr';
|
127
|
+
} else if (index == 6) {
|
128
|
+
return 'bl';
|
129
|
+
} else if (index == 7) {
|
130
|
+
return 'bm';
|
131
|
+
} else if (index == 8) {
|
132
|
+
return 'br';
|
133
|
+
} else {
|
134
|
+
return '';
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
// Should we become active when the user presses down on the square?
|
139
|
+
handleStartShouldSetPanResponder = (event) => {
|
140
|
+
return true
|
141
|
+
}
|
142
|
+
|
143
|
+
// We were granted responder status! Let's update the UI
|
144
|
+
handlePanResponderGrant = (event) => {
|
145
|
+
// console.log(event.nativeEvent.locationX + ', ' + event.nativeEvent.locationY)
|
146
|
+
|
147
|
+
let selectedItem = this.getTappedItem(event.nativeEvent.pageX, event.nativeEvent.pageY)
|
148
|
+
if (selectedItem == 'tl') {
|
149
|
+
this.setState({draggingTL: true})
|
150
|
+
} else if (selectedItem == 'tm') {
|
151
|
+
this.setState({draggingTM: true})
|
152
|
+
} else if (selectedItem == 'tr') {
|
153
|
+
this.setState({draggingTR: true})
|
154
|
+
} else if (selectedItem == 'ml') {
|
155
|
+
this.setState({draggingML: true})
|
156
|
+
} else if (selectedItem == 'mm') {
|
157
|
+
this.setState({draggingMM: true})
|
158
|
+
} else if (selectedItem == 'mr') {
|
159
|
+
this.setState({draggingMR: true})
|
160
|
+
} else if (selectedItem == 'bl') {
|
161
|
+
this.setState({draggingBL: true})
|
162
|
+
} else if (selectedItem == 'bm') {
|
163
|
+
this.setState({draggingBM: true})
|
164
|
+
} else if (selectedItem == 'br') {
|
165
|
+
this.setState({draggingBR: true})
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
// Every time the touch/mouse moves
|
170
|
+
handlePanResponderMove = (e, gestureState) => {
|
171
|
+
// Keep track of how far we've moved in total (dx and dy)
|
172
|
+
this.setState({
|
173
|
+
offsetTop: gestureState.dy,
|
174
|
+
offsetLeft: gestureState.dx,
|
175
|
+
})
|
176
|
+
}
|
177
|
+
|
178
|
+
// When the touch/mouse is lifted
|
179
|
+
handlePanResponderEnd = (e, gestureState) => {
|
180
|
+
const {initialTop, initialLeft, initialWidth, initialHeight, draggingTL, draggingTM, draggingTR, draggingML, draggingMM, draggingMR, draggingBL, draggingBM, draggingBR } = this.state
|
181
|
+
|
182
|
+
const state = {
|
183
|
+
draggingTL: false,
|
184
|
+
draggingTM: false,
|
185
|
+
draggingTR: false,
|
186
|
+
draggingML: false,
|
187
|
+
draggingMM: false,
|
188
|
+
draggingMR: false,
|
189
|
+
draggingBL: false,
|
190
|
+
draggingBM: false,
|
191
|
+
draggingBR: false,
|
192
|
+
offsetTop: 0,
|
193
|
+
offsetLeft: 0,
|
194
|
+
}
|
195
|
+
|
196
|
+
state.initialTop = initialTop + ((draggingTL || draggingTM || draggingTR || draggingMM) ? gestureState.dy : 0)
|
197
|
+
state.initialLeft = initialLeft + ((draggingTL || draggingML || draggingBL || draggingMM) ? gestureState.dx : 0)
|
198
|
+
state.initialWidth = initialWidth + ((draggingTL || draggingML || draggingBL) ? - gestureState.dx : (draggingTM || draggingMM || draggingBM) ? 0 : gestureState.dx)
|
199
|
+
state.initialHeight = initialHeight + ((draggingTL || draggingTM || draggingTR) ? - gestureState.dy : (draggingML || draggingMM || draggingMR) ? 0 : gestureState.dy)
|
200
|
+
|
201
|
+
if (state.initialWidth > this.props.initialWidth) {
|
202
|
+
state.initialWidth = this.props.initialWidth
|
203
|
+
}
|
204
|
+
if (state.initialWidth < this.props.minWidth) {
|
205
|
+
state.initialWidth = this.props.minWidth
|
206
|
+
}
|
207
|
+
if (state.initialHeight > this.props.initialHeight) {
|
208
|
+
state.initialHeight = this.props.initialHeight
|
209
|
+
}
|
210
|
+
if (state.initialHeight < this.props.minHeight) {
|
211
|
+
state.initialHeight = this.props.minHeight
|
212
|
+
}
|
213
|
+
|
214
|
+
this.setState(state)
|
215
|
+
this.props.onLayoutChanged(state.initialTop, state.initialLeft, state.initialWidth, state.initialHeight)
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
export default ImageCropOverlay;
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import React, { useState, useEffect,useMemo,useRef } from '$react';
|
2
|
+
import { View, StyleSheet,ImageBackground} from 'react-native';
|
3
|
+
import theme from "$theme";
|
4
|
+
import ActivityIndicator from "$ecomponents/ActivityIndicator";
|
5
|
+
import Label from "$ecomponents/Label";
|
6
|
+
import PropTypes from "prop-types";
|
7
|
+
import { isNonNullString,defaultStr,defaultObj } from '$cutils';
|
8
|
+
import Button from "$ecomponents/Button";
|
9
|
+
import Dialog from "$ecomponents/Dialog";
|
10
|
+
import ExpoImageManipulator from './ExpoImageManipulator';
|
11
|
+
import ImageCropOverlay from './ImageCropOverlay';
|
12
|
+
|
13
|
+
|
14
|
+
/***@see : https://docs.expo.dev/versions/latest/sdk/bar-code-scanner/ */
|
15
|
+
export default function ImageCropperComponent({src,testID,onCancel,dialogProps}) {
|
16
|
+
testID = defaultStr(testID,"RN_ImageCropperComponent");
|
17
|
+
const [visible,setVisible] = useState(true);
|
18
|
+
dialogProps = Object.assign({},dialogProps);
|
19
|
+
const prevVisible = React.usePrevious(visible);
|
20
|
+
const cancelRef = React.useRef(false);
|
21
|
+
const cancel = ()=>{
|
22
|
+
cancelRef.current = true;
|
23
|
+
setVisible(false);
|
24
|
+
}
|
25
|
+
useEffect(()=>{
|
26
|
+
if(prevVisible === visible) return;
|
27
|
+
if(prevVisible && !visible && cancelRef.current && typeof onCancel =="function"){
|
28
|
+
onCancel();
|
29
|
+
}
|
30
|
+
cancelRef.current = false;
|
31
|
+
},[visible]);
|
32
|
+
return <Dialog
|
33
|
+
fullPage
|
34
|
+
actions={[]}
|
35
|
+
title = {`Rogner l'image`}
|
36
|
+
{...dialogProps}
|
37
|
+
onBackActionPress={cancel}
|
38
|
+
visible = {visible}
|
39
|
+
>
|
40
|
+
<ImageBackground
|
41
|
+
resizeMode="contain"
|
42
|
+
style={styles.imageBackground}
|
43
|
+
source={{ uri : src }}
|
44
|
+
>
|
45
|
+
<ImageCropOverlay onLayoutChanged={(top, left, width, height) => {
|
46
|
+
console.log(top,lef,width,height," is to lefff")
|
47
|
+
}} initialWidth={50} initialHeight={50}
|
48
|
+
initialTop={0} initialLeft={0}
|
49
|
+
minHeight={100} minWidth={100}
|
50
|
+
/>
|
51
|
+
</ImageBackground>
|
52
|
+
</Dialog>;
|
53
|
+
}
|
54
|
+
const styles = StyleSheet.create({
|
55
|
+
center : {
|
56
|
+
justifyContent : "center",
|
57
|
+
alignItems : "center",
|
58
|
+
flexDirection : "column",
|
59
|
+
flex : 1,
|
60
|
+
},
|
61
|
+
row : {
|
62
|
+
flexDirection : "row",
|
63
|
+
justifyContent : "center",
|
64
|
+
alignItems : "center",
|
65
|
+
flexWrap :"wrap",
|
66
|
+
},
|
67
|
+
imageBackground : {
|
68
|
+
flex : 1,
|
69
|
+
width : "100%",
|
70
|
+
height : "100%",
|
71
|
+
justifyContent: 'center',
|
72
|
+
padding: 20, alignItems: 'center',
|
73
|
+
}
|
74
|
+
});
|
75
|
+
|
76
|
+
/***
|
77
|
+
@see : https://docs.expo.dev/versions/latest/sdk/camera-next
|
78
|
+
*/
|
79
|
+
ImageCropperComponent.propTypes = {
|
80
|
+
onScan : PropTypes.func,
|
81
|
+
onGrantAccess : PropTypes.func, //lorsque la permission est allouée
|
82
|
+
onDenyAccess : PropTypes.func, //lorsque la permission est refusée
|
83
|
+
}
|
@@ -6,7 +6,6 @@ import {defaultObj} from "$cutils";
|
|
6
6
|
|
7
7
|
const ImageEditorComponent = React.forwardRef((props,ref)=>{
|
8
8
|
let {source,uri,onSuccess,imageUri,lockAspectRatio,dialogProps,onDismiss,visible,imageProps,...rest} = props;
|
9
|
-
const isMounted = React.useIsMounted();
|
10
9
|
const [context] = React.useState({});
|
11
10
|
imageProps = defaultObj(imageProps);
|
12
11
|
dialogProps = defaultObj(dialogProps);
|
@@ -3,15 +3,15 @@ import Menu from "$ecomponents/Menu";
|
|
3
3
|
import Avatar from "$ecomponents/Avatar";
|
4
4
|
import {isDecimal,setQueryParams,isValidURL,defaultDecimal,defaultStr as defaultString,isDataURL,isPromise,defaultBool,isObj,isNonNullString} from "$cutils";
|
5
5
|
import notify from "$enotify";
|
6
|
-
let maxWidthDiff =
|
6
|
+
let maxWidthDiff = 100, maxHeightDiff = 100;
|
7
7
|
import {StyleSheet} from "react-native";
|
8
8
|
import React from "$react";
|
9
9
|
import PropTypes from "prop-types";
|
10
10
|
import {isMobileNative} from "$cplatform";
|
11
|
+
import {uniqid} from "$cutils";
|
11
12
|
//import Signature from "$ecomponents/Signature";
|
12
13
|
import Label from "$ecomponents/Label";
|
13
|
-
//import
|
14
|
-
import {Component as CameraComponent} from "$emedia/camera";
|
14
|
+
//import Cropper from "./Cropper";
|
15
15
|
|
16
16
|
import {pickImage,nonZeroMin,canTakePhoto,takePhoto} from "$emedia";
|
17
17
|
import addPhoto from "$eassets/add_photo.png";
|
@@ -48,11 +48,8 @@ export const getUri = (src,onlySting)=>{
|
|
48
48
|
|
49
49
|
export default function ImageComponent(props){
|
50
50
|
const [src,setSrc] = React.useState(defaultVal(props.src));
|
51
|
+
const [cropWindowProp,setCropWindowProp] = React.useState(null);
|
51
52
|
const prevSrc = React.usePrevious(src);
|
52
|
-
/*const [editorProps,setEditorProps] = React.useState({
|
53
|
-
visible : false,
|
54
|
-
options : {}
|
55
|
-
})*/
|
56
53
|
const [isDrawing,setIsDrawing] = React.useState(false);
|
57
54
|
let {disabled,onMount,defaultSource,editable,onUnmount,label,text,labelProps,readOnly,beforeRemove,
|
58
55
|
onChange,draw,round,drawText,drawLabel,rounded,defaultSrc,
|
@@ -114,8 +111,8 @@ export default function ImageComponent(props){
|
|
114
111
|
if(!imageWidth && !imageHeight){
|
115
112
|
imageWidth = imageHeight = rest.size;
|
116
113
|
}
|
117
|
-
let cropWidth = nonZeroMin(cropProps.width,width)
|
118
|
-
let cropHeight = nonZeroMin(cropProps.height,height);
|
114
|
+
let cropWidth = nonZeroMin(cropProps.width,imageWidth,width,size)
|
115
|
+
let cropHeight = nonZeroMin(cropProps.height,imageHeight,height,size);
|
119
116
|
if(!cropWidth) cropWidth = undefined;
|
120
117
|
if(!cropHeight) cropHeight = undefined;
|
121
118
|
|
@@ -125,24 +122,26 @@ export default function ImageComponent(props){
|
|
125
122
|
if(cropWidth || cropHeight){
|
126
123
|
canCrop = true;
|
127
124
|
if(cropWidth) opts.width = cropWidth;
|
128
|
-
|
125
|
+
if(cropHeight) opts.height = cropHeight;
|
129
126
|
}
|
130
127
|
opts.allowsEditing = canCrop;
|
131
|
-
return opts;
|
128
|
+
return {...cropProps,...opts};
|
132
129
|
}
|
133
130
|
const handlePickedImage = (image,opts)=>{
|
134
131
|
opts = defaultObj(opts);
|
135
132
|
if(!isDataURL(image.dataURL)){
|
136
133
|
return notify.error(`Le fichier sélectionné est une image non valide`);
|
137
134
|
}
|
138
|
-
let diffWidth = image.width - cropWidth - maxWidthDiff,
|
139
|
-
diffHeight = image.height - cropHeight - maxHeightDiff;
|
140
|
-
let canCrop = isMobileNative()? false : ((width && diffWidth > 0) || (height && diffHeight > 0)? true : false);
|
141
135
|
const imageSrc = pickUri ? image.uri : image.dataURL;
|
142
|
-
if(
|
143
|
-
|
144
|
-
|
145
|
-
|
136
|
+
if(imageSrc){
|
137
|
+
const diffWidth = image.width - cropWidth - maxWidthDiff,diffHeight = image.height - cropHeight - maxHeightDiff;
|
138
|
+
const canCrop = isMobileNative()? false : ((diffWidth > 0) || (diffHeight > 0)? true : false);
|
139
|
+
if(canCrop){
|
140
|
+
const cProps = getCropProps(opts);
|
141
|
+
return context.cropImage({...cProps,source:image,uri:image.dataURL,src:imageSrc}).then((props)=>{
|
142
|
+
setSrc(imageSrc)
|
143
|
+
});
|
144
|
+
}
|
146
145
|
}
|
147
146
|
pickedImageRef.current = image;
|
148
147
|
setSrc(imageSrc);
|
@@ -170,6 +169,11 @@ export default function ImageComponent(props){
|
|
170
169
|
},[src]),
|
171
170
|
cropImage : (props)=>{
|
172
171
|
return Promise.resolve(props);
|
172
|
+
if(!isMobileNative()){
|
173
|
+
return new Promise((resolve,reject)=>{
|
174
|
+
setCropWindowProp(props);
|
175
|
+
});
|
176
|
+
}
|
173
177
|
return new Promise((resolve,reject)=>{
|
174
178
|
console.log({...editorProps,visible:true,...props},"is editor props");
|
175
179
|
setEditorProps({...editorProps,visible:true,...props})
|
@@ -263,6 +267,11 @@ export default function ImageComponent(props){
|
|
263
267
|
const _label = withLabel !== false ? defaultString(label) : "";
|
264
268
|
const isDisabled = menuItems.length > 0 ? true : false;
|
265
269
|
return <View testID={testID+"_FagmentContainer"}>
|
270
|
+
{false && src && !isMobileNative() && isObj(cropWindowProp) && Object.size(cropWindowProp,true) ? <Cropper
|
271
|
+
src={src}
|
272
|
+
{...cropWindowProp}
|
273
|
+
key = {uniqid("crop-image")}
|
274
|
+
/> : null}
|
266
275
|
{!createSignatureOnly ? (<Menu
|
267
276
|
{...menuProps}
|
268
277
|
disabled = {isDisabled}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module.exports = {
|
2
2
|
"@fto-consult/expo-ui": {
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.46.3",
|
4
4
|
"url": "https://github.com/borispipo/expo-ui#readme",
|
5
5
|
"license": "ISC"
|
6
6
|
},
|
@@ -15,7 +15,7 @@ module.exports = {
|
|
15
15
|
"license": "MIT"
|
16
16
|
},
|
17
17
|
"@expo/html-elements": {
|
18
|
-
"version": "0.
|
18
|
+
"version": "0.9.1",
|
19
19
|
"url": "https://github.com/expo/expo/tree/main/packages/html-elements",
|
20
20
|
"license": "MIT"
|
21
21
|
},
|
@@ -45,22 +45,22 @@ module.exports = {
|
|
45
45
|
"license": "MIT"
|
46
46
|
},
|
47
47
|
"@react-native/assets-registry": {
|
48
|
-
"version": "0.
|
49
|
-
"url": "
|
48
|
+
"version": "0.74.0",
|
49
|
+
"url": "https://github.com/facebook/react-native/tree/HEAD/packages/assets#readme",
|
50
50
|
"license": "MIT"
|
51
51
|
},
|
52
52
|
"@react-navigation/native": {
|
53
|
-
"version": "6.1.
|
53
|
+
"version": "6.1.10",
|
54
54
|
"url": "https://reactnavigation.org",
|
55
55
|
"license": "MIT"
|
56
56
|
},
|
57
57
|
"@react-navigation/native-stack": {
|
58
|
-
"version": "6.9.
|
58
|
+
"version": "6.9.18",
|
59
59
|
"url": "https://github.com/software-mansion/react-native-screens#readme",
|
60
60
|
"license": "MIT"
|
61
61
|
},
|
62
62
|
"@react-navigation/stack": {
|
63
|
-
"version": "6.3.
|
63
|
+
"version": "6.3.21",
|
64
64
|
"url": "https://reactnavigation.org/docs/stack-navigator/",
|
65
65
|
"license": "MIT"
|
66
66
|
},
|
@@ -104,11 +104,6 @@ module.exports = {
|
|
104
104
|
"url": "https://docs.expo.dev/versions/latest/sdk/imagepicker/",
|
105
105
|
"license": "MIT"
|
106
106
|
},
|
107
|
-
"expo-intent-launcher": {
|
108
|
-
"version": "10.11.0",
|
109
|
-
"url": "https://docs.expo.dev/versions/latest/sdk/intent-launcher/",
|
110
|
-
"license": "MIT"
|
111
|
-
},
|
112
107
|
"expo-linking": {
|
113
108
|
"version": "6.2.2",
|
114
109
|
"url": "https://docs.expo.dev/versions/latest/sdk/linking",
|
@@ -159,7 +154,7 @@ module.exports = {
|
|
159
154
|
"license": "MIT"
|
160
155
|
},
|
161
156
|
"react-native-reanimated": {
|
162
|
-
"version": "3.6.
|
157
|
+
"version": "3.6.2",
|
163
158
|
"url": "https://github.com/software-mansion/react-native-reanimated#readme",
|
164
159
|
"license": "MIT"
|
165
160
|
},
|
@@ -187,10 +182,5 @@ module.exports = {
|
|
187
182
|
"version": "13.6.4",
|
188
183
|
"url": "https://github.com/react-native-webview/react-native-webview#readme",
|
189
184
|
"license": "MIT"
|
190
|
-
},
|
191
|
-
"vm": {
|
192
|
-
"version": "0.1.0",
|
193
|
-
"url": "https://github.com/DiegoRBaquero/node-vm#readme",
|
194
|
-
"license": "MIT"
|
195
185
|
}
|
196
186
|
};
|