@fto-consult/expo-ui 8.46.3 → 8.48.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.
@@ -10,6 +10,8 @@ import TableDataListScreen from "$screens/TableData/TableDataListScreen";
10
10
  import TableDataScreen from "$screens/TableData/TableDataScreen";
11
11
  import Notifications from "$components/Notifications";
12
12
  import auth from "$src/auth";
13
+ import tablesData, { getTable as getTableData } from "$database/tables";
14
+ import {defaultStr} from "$cutils";
13
15
 
14
16
  export default function AppMainEntry(){
15
17
  return <ExpoUIProvider
@@ -22,8 +24,8 @@ export default function AppMainEntry(){
22
24
  screens,
23
25
  /** {object}, les options du composant Stack.Navigator, voir https://reactnavigation.org/docs/native-stack-navigator */
24
26
  screenOptions : {},
25
- drawerItems, //application main drawer items,
26
- drawerSections,
27
+ drawerItems, //application main drawer items,
28
+ drawerSections, //les différentes sections du drawer principal de l'application
27
29
  /***** mutate drawerItems before rendering
28
30
  @param {object : {[drawerSection1]:{ label:section1Label,items:<Array>},[drawerSection2]:{}, ...[drawerSectionN]:{}}} drawerItems
29
31
  @return {object}
@@ -34,6 +36,8 @@ export default function AppMainEntry(){
34
36
  screenOptions : {},//les options du composant Stack.Navigator de react-navigation, voir https://reactnavigation.org/docs/native-stack-navigator/
35
37
  }}
36
38
  auth = {auth}
39
+ tablesData={tablesData}
40
+ getTableData={getTableData}
37
41
  components = {{
38
42
  /*** utilisé pour le renu du contenu des écran de type liste sur les tables de données */
39
43
  TableDataListScreen,
@@ -63,16 +67,64 @@ export default function AppMainEntry(){
63
67
  MainProvider : function({children,isLoaded,isLoading,isInitialized,hasGedStarted,...props}){
64
68
  return children;
65
69
  },
66
- /*** logo : ReactNode|ReactElement | ReactComponent | object {
67
- image{ReactComponent} :,text {ReactComponent}
70
+ /***
71
+ le composant en charge du rendu du logo de l'application
72
+ logo | Logo : ReactNode | ReactComponent | object {
73
+ image{ReactComponent} :,
74
+ text {ReactComponent}
75
+ },
68
76
  },*/
69
- logo : Logo,//logo component's properties
70
- /**** les form fields personnalisés doivent être définis ici */
71
- customFormFields : {},//custom form fields
72
- /*** la fonction permettant de muter les props du composant TableLink, permetant de lier les tables entre elles */
77
+ logo : Logo,
78
+ /****
79
+ custom form fields
80
+ les form fields personnalisés doivent être définis ici
81
+ de la forme : {
82
+ [typeCustomField1] : <ReactComponent>,
83
+ ...
84
+ [typeCustomFieldn] : <ReactComponent>
85
+ }
86
+ par exemple, si l'on souhaite définir un form field de type test, la déclaration sera de la forme :
87
+ {
88
+ test : Test, //ou test est le fom field associé au type test, ie le composant qui sera rendu pour ce type de Champ,
89
+ }
90
+ */
91
+ customFormFields : {},
92
+ /***
93
+ la fonction permettant de muter les props du composant TableLink, permetant de lier les tables entre elles
94
+ Le composant TableLink permet de lier les données d'une tableData, L'usage dudit composant est définit dans la documentation de l'application
95
+ */
73
96
  tableLinkPropsMutator : (props)=>{
74
- return props;
75
- }
97
+ return {
98
+ ...props,
99
+ /***
100
+ la fonction fetchForeignData est appelée lorsqu'on clique sur un élément du composant TableLink, permetant de lier un objet de la table table Data
101
+ foreignKeyTable {string} represente la table lié à la donnée
102
+ foreignKeyColumn {string} represenet le nom de la colonne qu'on souhaite récupérer la données
103
+ id {any}, represente la valeur actuelle sur laquelle on a cliqué
104
+ */
105
+ fetchForeignData : ({foreignKeyTable,foreignKeyColumn,tableName,table,id,...args})=>{
106
+ const tableName = defaultStr(foreignKeyTable,table,tableName);
107
+ const tableObj = getTableData(tableName); //table object represente l'objet table, lié à la liste des tables data déclaré dans l'application
108
+ if (!tableObj) {
109
+ return Promise.reject({
110
+ message: `Impossible de récupérer la données associée à la table ${tableName}. Rassurez vous qu'elle figure dans la liste des tables supportées par l'application`,
111
+ });
112
+ }
113
+ //Vous pouvez dès cet instant accédes aux props de l'objet tableObj, notemment queryPath, qui permet de récupérer les données liés à la table data
114
+ const fieldName = defaultStr(foreignKeyColumn);
115
+ /*
116
+ implémenter votre logique pour récupérer l'objet associé à la table tableName, dont la colonne est fieldName, et la valeur est id.
117
+ //ajouter l'instruction d'importation de la fonction fetch : import fetch from "$capi/fetch";
118
+ exemple : return fetch(`${table.queryPath}/${id}${fieldName ? `?fieldName=${fieldName}`:""}`).then((resp) => resp.data);
119
+ */
120
+ return Promise.resolve(null);
121
+ },
122
+ };
123
+ },
124
+ /***
125
+ ({object})=><{object}>, la fonction permettant de muter les props du composant Fab, affiché dans les écrans par défaut
126
+ */
127
+ fabPropsMutator : (props)=>props,
76
128
  }}
77
129
  /*** //for application initialization
78
130
  @param {
@@ -7,6 +7,7 @@ module.exports = function(api) {
7
7
  $components : path.resolve($src,"components"),
8
8
  $navigation : path.resolve($src,"navigation"),
9
9
  $screens : path.resolve($src,"screens"),
10
+ $database: path.resolve($src, "database"), //le repertoire dédié au données d'accès à la base de données
10
11
  //...your custom module resolver alias, @see : https://www.npmjs.com/package/babel-plugin-module-resolver
11
12
  }
12
13
  return require("@fto-consult/expo-ui/babel.config")(api,{
@@ -31,6 +31,7 @@
31
31
  "react-native-gesture-handler": "~2.14.0",
32
32
  "react-native-reanimated": "~3.6.2",
33
33
  "react-native-view-shot": "3.8.0",
34
- "expo-intent-launcher": "~10.11.0"
34
+ "expo-intent-launcher": "~10.11.0",
35
+ "expo-image-manipulator": "~11.8.0"
35
36
  };
36
37
 
@@ -1,6 +1,5 @@
1
1
  export default {
2
2
  enabled : false,//la gestion de l'authentification est désactivée par défaut
3
- loginPropsMutator : (props)=>props,//({object})=><{object}>, la fonction permettant de muter les props du composant Login,
4
3
  profilePropsMutator : ({fields,...props})=>({fields,...props}),//la fonction permettant de muter les champs liés à l'écran de mise à jour d'un profil utilisateur
5
4
  signIn : ({user})=>Promise.resolve({message:"Connecté avec success"}), //la fonction permettant de connecter un utilisateur
6
5
  signOut : ({user})=>Promise.resolve({message:"Déconnecté avec success"}),//la fonction permettant de déconnecter un utilisateur
@@ -24,5 +23,65 @@ export default {
24
23
  getUserPseudo: (user) => user.pseudo,
25
24
  getUserFirstName : (user)=>user.firstName,
26
25
  getUserLastName : (user)=>user.lastName,
27
- getUserFullName : (user)=> user.fullName || `${user.firstName && user.firstName ||''}${user.lastName && ` ${user.lastName}` ||''}`
26
+ getUserFullName : (user)=> user.fullName || `${user.firstName && user.firstName ||''}${user.lastName && ` ${user.lastName}` ||''}`,
27
+
28
+ /****
29
+ Le composant à définir pour override le composant Login par défaut de l'application. example : Login : (porps)=><React.Component {...props}/>
30
+ Pour override l'interface de connexion par défaut, vous dévez définir le contenu de cette propriété comme étant un composant React qui sera rendu
31
+ rendu en lieu et place du composant de connexion par défaut : Ce composant aura comme props :
32
+ {
33
+ withPortal : {boolean}, //si le contenu de l'écran doit être rendu dans un portal
34
+ onSuccess <function> : (data)=><any>, la fonction appelée en cas de success
35
+ appBarProps <object>, les props à passer au composant ApppBar de l'écran de connexion, lorsque withPortal est à true
36
+ auth <object>, //le composant auth récupérer à l'aide du hook useAuth de $cauth. définit les fonctions suivantes :
37
+ {
38
+ signIn : (data)=><Promise>, la fonction permettant de connecter l'utilisateur
39
+ signOut : ()=><Promise>, la fonction permettant de déconnecter l'utilisateur, /
40
+ ...rest,
41
+ }
42
+ }
43
+ */
44
+ Login : null,
45
+ /*
46
+ la fonction loginPropsMutator de muter les props du composant Login par défaut, prise en compte lorsque le composant de connexion n'est pas remplacer par celui définit dans la prop login,
47
+ @param {object} props : les props de la fonction login, les props ont des propriétés suivantes :
48
+ {
49
+ onSuccess : ({object})=><Any>, la fonction appelée en cas de success
50
+ setState : (newState)=>(...newState),//la fonction utilisée pour update le state du composant. elle doit remplacer le state du composant
51
+ state : <Object: data,...rest>, le state actuel à l'instant t du composant
52
+ nextButton : <Object :
53
+ {
54
+ ref : nextButtonRef, //la référence vers le bouton next (le boutn Suivant)
55
+ isDisabled : x=> typeof buttonRef?.current?.isDisabled ==="function" && buttonRef.current?.isDisabled(),
56
+ enable : x=>typeof buttonRef?.current?.enable =="function" && buttonRef.current.enable(),
57
+ disable : x=> typeof buttonRef?.current?.disable =="function" && buttonRef?.current.disable(),
58
+ }
59
+ >,
60
+ prevButton : <Object :
61
+ {
62
+ ref : prevButtonRef, //la référence react ver le buton previous (le bouton Précédent)
63
+ isDisabled : x=> typeof buttonRef?.current?.isDisabled ==="function" && buttonRef.current?.isDisabled(),
64
+ enable : x=>typeof buttonRef?.current?.enable =="function" && buttonRef.current.enable(),
65
+ disable : x=> typeof buttonRef?.current?.disable =="function" && buttonRef?.current.disable(),
66
+ }
67
+ >,
68
+ showError <function> : (message,title)=><void>, la fonction permettant de notifier l'utilisateur en cas d'erreur
69
+ getData <function> : ()=><Object>, la fonction permettant de récupérer les données en cours de modification du formulaire de connextion à l'instant t
70
+ focusField <function> : (fieldName)=><void>, la fonction permettant d'activer le focus sur le champ fieldName à l'instant t
71
+ formName <string>, //le nom du formulaire Form, passé à la formData
72
+ nextButtonRef <{current:<any>}>, la référence vers le bouton next
73
+ previousButtonRef <{current:<any>}, la référence vers le bouton previous
74
+ }
75
+ @return <{object}>, l'objet a retourné doit être de la forme :
76
+ {
77
+ headerTopContent : <ReactComponent | ReactNode, le contenu a afficher au headerTop de l'interface de connexion
78
+ header : <ReactComponent| ReactNode>, le contenu du qui sera rendu immédiatement après le composant Header, par défaut, le texte "Connectez vous svp est affiché". Ce contenu doit être rendu si l'on souhaite override le texte "Connectez vous SVP"
79
+ containerProps : <object>, les props du composant <Surface/>, le composant qui est le wrapper du composant FormData en charge de récupérer les données de l'interface de connexion
80
+ withHeaderAvatar : <boolean>, si l'avatar ou l'iconne de connexion sera afficher à l'interface de connexion par défaut
81
+ ...loginProps {object}, les composant Supplémentaires à passer au composant FormData utilisé pour le rendu du formulaire de connexion
82
+ }
83
+ */
84
+ loginPropsMutator : (props)=>{
85
+ return props;
86
+ },
28
87
  }
@@ -0,0 +1,10 @@
1
+ /*****
2
+ le contenu de cette fonction peut être généré automatiquement via les commandes suivantes (étant dans le repertoire de l'application)
3
+ npm run generate-getTable | npx @fto-consult/expo-ui generate-getTable
4
+ Notons que le script generate-getTable est définit comme étant l'un des scripts du package.json de l'application
5
+ @param {string} tableName, le nom de la table data
6
+ @return {object | null}, table, l'objet table associé
7
+ */
8
+ export default function(tableName){
9
+ return null;
10
+ }
@@ -0,0 +1,57 @@
1
+ /****
2
+ la liste des tables de donnéees à exporter, de la forme :
3
+ [tableName1] : {
4
+ fields : { //la liste des champs liés à la table de données
5
+ [field1] : {
6
+ label | text : <string | ReactComponent>, le texte ou libelé lié au champ
7
+ type : <string>, le type de field par exemple : text,password, email, switch, checkbox, tel, select, et bien d'autres
8
+
9
+ //cette fonction est appelée à chaque fois que les premiers règles de validations ont été conclus lors de la validation du champ. Elle doit retourner soit un boolean, une chaine de caractère, un objet ou une promesse
10
+ //si une chaine de caractère est retournée, alors la validation du champ considère qu'il s'agit d'une erreur et le message est affiché comme erreur de validation du champ
11
+ //si un objet est retourné et cet objet contient un champ message | msg de type string, alors le validateur considère qu'il s'agit d'une erreur et le champ en question est affiché comme message de l'erreur
12
+ //si une promese est retournée, alors la fonction attendra que la promesse soit résolue
13
+ 1. si la promesse resoud une chaine de caractère ou un objet idem au cas précédent, ladite chaine est condisérée comme une erreur
14
+ 2. si la promesse renvoie une erreur, alors le validateur considère comme un échec de validation et le message lié à l'erreur est affiché comme message d'erreur de validation
15
+ //si false est retournée, alors rien n'est fait et le status de ce champ reste toujours à invalide. il sera donc impossible d'enregistrer le formulaire form data
16
+ //si true est retourneé, alors la formField est valide
17
+ onValidatorValid : ({value,context,....rest}) => <boolean | object {} | string | Promise <boolean | object : {} | string>>,
18
+
19
+ //Cette fonction est appélée à chaque échec de validation du form field
20
+ onValidatorNoValid : ({value,context,...rest}) => <any>
21
+ }
22
+ },
23
+ tableName <string>, le nom de la table name
24
+ label | text <string>, le titre à donner à la table data
25
+ icon : <string | ReactComponent>, l'icon de la table data,
26
+ queryPath <string>, //le chemin lié à l'api REST utilisée pour effectuer un query sur les données liés à la table Data en question
27
+ perms <object>, //l'objet perms définissant les permissions pouvant être assignés à l'utilisateur pour la table de donénes
28
+ showInFab <boolean | function()=><boolean>, //si la table de données sera affiché dans le layout Fab, le composant Fab qui est rendu pour les écrans dont la propriété withFab est à true
29
+ showInDrawer <boolean | function()=><boolean>, //détermine si la table data sera affichée dans le drawer principal de l'application
30
+ datagrid <object>, //les props à passer au composant datagrid lié à la table de données
31
+ drawerSection <string>, //le nom de la section associé au drawer dans lequel figurera le table data
32
+ print <function ({data,...settings})>=> <Promise<{content:[],...rest}>, //la fonction utile pour l'impression de la tabel de données suivant les recommandation de la libraririe pdfmake
33
+ printOptions <object>, //les options à passer à la fonction print,
34
+
35
+ databaseStatistics <function ()=> <boolean> | boolean>, //si la table data figurera dans les Statistiques en BD, validable si le composant DatabaseStatistics est appélé dans l'application
36
+
37
+ showInFab <boolean | function()=><boolean>, //spécifie si un bouton lié à la table sera affiché dans le composant Fab
38
+
39
+ //le champ fabProps doit retourner les props à appliquer au composant fab lié à la table data,si elle définit une propriété nomée actions de types tableau, alors, ces actions seront utilisées commes actions personnalisées du fab
40
+ // il doit s'agit d'un objet de la forme : {
41
+ actions : <array<Item> | object <Item>>. Item doit être un objet avec les propriétés suivantes :
42
+ Item : {
43
+ text | label <string> : "le texte à afficher au bouton fab",
44
+ icon <string | ReactComponent>, l'icone du bouton de fab,
45
+ backgroundColor <string, //la couleur d'arrière plan du bouton
46
+ color <string>, //la couleur du bouton de fab
47
+ onPress <func ()=>void>, //la fonction appelée lorsqu'on clique sur le bouton
48
+ }
49
+ }
50
+ fabProps { boolean<false> | object|function({tableName})}, //si fabProps vaux false ou retourne false, alors le table data ne s'affichera pas dans le composant Fab
51
+ }
52
+ */
53
+ export default {
54
+
55
+ }
56
+
57
+ export {default as getTable} from "./getTable";
package/bin/create-app.js CHANGED
@@ -31,8 +31,8 @@ module.exports = function(appName,{projectRoot:root}){
31
31
  name,
32
32
  version : "1.0.0",
33
33
  "description": "",
34
- "main": "index.js",
35
34
  "main": "App.js",
35
+ "tablesDataPath": "./src/database/tables",
36
36
  "scripts" : {
37
37
  start : "npx expo start -c",
38
38
  "dev" : "npx expo start --no-dev --minify -c",
@@ -40,6 +40,7 @@ module.exports = function(appName,{projectRoot:root}){
40
40
  "build-web" : "npx expo export:web",
41
41
  "build-android" : "npx eas build --platform android --profile preview",
42
42
  "build-ios" : "eas build --platform ios",
43
+ "generate-getTable" : "nxp @fto-consult/expo-ui generate-getTable"
43
44
  },
44
45
  "dependencies" : {
45
46
  [euModule] : packageObj.version,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "8.46.3",
3
+ "version": "8.48.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": {
package/src/auth/Login.js CHANGED
@@ -27,7 +27,7 @@ const WIDTH = 400;
27
27
 
28
28
  export default function LoginComponent(props){
29
29
  let {formName,step,appBarProps,onSuccess,withPortal,testID} = props;
30
- const {auth:{loginPropsMutator}} = useContext();
30
+ const {auth:{loginPropsMutator,Login}} = useContext();
31
31
  const loginTitle = getTitle();
32
32
  testID = defaultStr(testID,"RN_Auth.LoginComponent");
33
33
  formName = React.useRef(uniqid(defaultStr(formName,"login-formname"))).current;
@@ -74,7 +74,6 @@ export default function LoginComponent(props){
74
74
  }
75
75
  }
76
76
 
77
-
78
77
  if(withPortal){
79
78
  appBarProps = defaultObj(appBarProps);
80
79
  appBarProps.backAction = false;
@@ -86,6 +85,27 @@ export default function LoginComponent(props){
86
85
  },1000)
87
86
  }
88
87
  },[withPortal]);
88
+ React.useEffect(()=>{
89
+ Preloader.closeAll();
90
+ /*** pour initializer les cordonnées du composant login */
91
+ if(typeof initialize =='function'){
92
+ initialize();
93
+ }
94
+ },[]);
95
+ const prevStep = React.usePrevious(state.step);
96
+ React.useEffect(()=>{
97
+ /*** lorsque le state du composant change */
98
+ if(typeof onStepChange =='function'){
99
+ return onStepChange({...state,previousStep:prevStep,focusField,nextButtonRef})
100
+ }
101
+ },[state.step]);
102
+ if(React.isComponent(Login)) return <Login
103
+ {...props}
104
+ withPortal = {withPortal}
105
+ appBarProps = {appBarProps}
106
+ onSuccess = {onSuccess}
107
+ auth = {auth}
108
+ />
89
109
  const getButtonAction = (buttonRef)=>{
90
110
  return {
91
111
  ref : buttonRef,
@@ -122,20 +142,6 @@ export default function LoginComponent(props){
122
142
  const containerProps = defaultObj(customContainerProps);
123
143
  const contentProps = defaultObj(customContentProps);
124
144
 
125
- React.useEffect(()=>{
126
- Preloader.closeAll();
127
- /*** pour initializer les cordonnées du composant login */
128
- if(typeof initialize =='function'){
129
- initialize();
130
- }
131
- },[]);
132
- const prevStep = React.usePrevious(state.step);
133
- React.useEffect(()=>{
134
- /*** lorsque le state du composant change */
135
- if(typeof onStepChange =='function'){
136
- return onStepChange({...state,previousStep:prevStep,focusField,nextButtonRef})
137
- }
138
- },[state.step]);
139
145
  /****la fonction à utiliser pour vérifier si l'on peut envoyer les données pour connextion
140
146
  * par défaut, on envoie les données lorssqu'on est à l'étappe 2
141
147
  * **/
@@ -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 = 150, maxHeightDiff = 150;
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 Editor from "./Editor";
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
- else if(cropHeight) opts.height = cropHeight;
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(canCrop){
143
- return context.cropImage({source:image,uri:image.dataURL,...opts}).then((props)=>{
144
- setSrc(imageSrc)
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,7 +1,7 @@
1
1
  module.exports = {
2
2
  "@fto-consult/expo-ui": {
3
3
  "name": "@fto-consult/expo-ui",
4
- "version": "8.46.2",
4
+ "version": "8.48.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/borispipo/expo-ui.git"