@fto-consult/expo-ui 8.7.1 → 8.8.1

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.
@@ -26,6 +26,7 @@ module.exports = (opts)=>{
26
26
  r["$emedia"] = path.resolve(expo,"media");
27
27
  r["$enavigation"] = path.resolve(expo,"navigation");
28
28
  r["$escreens"] = path.resolve(expo,"screens");
29
+ r["$eview-shot"] = path.resolve(expo,"view-shot");
29
30
  ///les screens principaux de l'application
30
31
  r["$escreen"] = r["$eScreen"] = path.resolve(expo,"layouts/Screen");
31
32
  r["$eassets"] = path.resolve(dir,"assets");
@@ -31,5 +31,6 @@ module.exports = {
31
31
  "react-native-svg": "14.1.0",
32
32
  "react-native-webview": "13.6.4",
33
33
  "react-native-gesture-handler": "~2.14.0",
34
- "react-native-reanimated": "~3.6.0"
34
+ "react-native-reanimated": "~3.6.0",
35
+ "react-native-view-shot": "3.8.0"
35
36
  }
package/bin/create-app.js CHANGED
@@ -135,7 +135,7 @@ const createAPPJSONFile = (projectRoot,{name,version})=>{
135
135
  "slug": "${name.toLowerCase().replace(/\s\s+/g, '-')}",
136
136
  "version":"${version}",
137
137
  "orientation": "portrait",
138
- "plugins":${JSON.stringify(plugins,null,'\t')},
138
+ "plugins":${JSON.stringify(plugins)},
139
139
  "icon": "./assets/icon.png",
140
140
  "jsEngine": "hermes",
141
141
  "splash": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "8.7.1",
3
+ "version": "8.8.1",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "scripts": {
6
6
  "clear-npx-cache": "npx clear-npx-cache",
@@ -77,9 +77,11 @@
77
77
  "crypto-browserify": "^3.12.0",
78
78
  "file-saver": "^2.0.5",
79
79
  "google-libphonenumber": "^3.2.34",
80
+ "html2canvas": "^1.4.1",
80
81
  "htmlparser2-without-node-native": "^3.9.2",
81
82
  "is-plain-obj": "^4.1.0",
82
83
  "js-base64": "^3.7.5",
84
+ "jsbarcode": "^3.11.6",
83
85
  "prop-types": "^15.8.1",
84
86
  "react": "^18.2.0",
85
87
  "react-content-loader": "^6.2.1",
@@ -0,0 +1,34 @@
1
+ import {forwardRef,useRef,useEffect} from "$react";
2
+ import {uniqid,defaultStr} from "$cutils";
3
+ import JsBarcode from "jsbarcode";
4
+ import {jsbarcodePropTypes } from "./utils";
5
+
6
+ ///@see : https://lindell.me/JsBarcode/
7
+ const BarcodeGeneratorComponent = forwardRef(({value,format,id,testID,onReady,text,flat,width,height,displayValue,fontOptions,font,textAlign,textPosition,textMargin,fontSize,background,lineColor,margin,marginTop,marginBottom,marginLeft,marginRight,valid},ref)=>{
8
+ testID = defaultStr(testID,"RN_GeneratorWebSVG");
9
+ const idRef = useRef(defaultStr(id,uniqid("bar-code-generator-web")));
10
+ useEffect(()=>{
11
+ const element = document.querySelector(`#${idRef.current}`);
12
+ if(!element) return;
13
+ JsBarcode(`#${idRef.current}`).init();
14
+ if(typeof onReady ==="function"){
15
+ setTimeout(()=>{
16
+ onReady();
17
+ },50);
18
+ }
19
+ },[value,format,id,testID,width,height,displayValue,flat,text,fontOptions,font,textAlign,textPosition,textMargin,fontSize,background,lineColor,margin,marginTop,marginBottom,marginLeft,marginRight])
20
+ const jsProps = {};
21
+ const supportedProps = {value,format,width,flat,text,height,displayValue,fontOptions,font,textAlign,textPosition,textMargin,fontSize,background,lineColor,margin,marginTop,marginBottom,marginLeft,marginRight};
22
+ Object.keys(supportedProps).map(key=>{
23
+ if(supportedProps[key] !== undefined){
24
+ jsProps[`jsbarcode-${key.toLowerCase()}`] = String(supportedProps[key]);
25
+ }
26
+ });
27
+ return <svg {...jsProps} id={`${idRef.current}`} ref={ref} data-test-id={`${testID}`} className="bar-code-generator-svg"/>
28
+ });
29
+
30
+ BarcodeGeneratorComponent.displayName = "BarcodeGeneratorComponent";
31
+
32
+ BarcodeGeneratorComponent.propTypes = jsbarcodePropTypes
33
+
34
+ export default BarcodeGeneratorComponent;
@@ -0,0 +1,37 @@
1
+ import React,{forwardRef,useEffect} from "$react";
2
+ import Svg, { Path } from 'react-native-svg';
3
+ import Label from "$ecomponents/Label";
4
+ import {defaultStr,defaultNumber} from "$cutils";
5
+ import theme from "$theme";
6
+ import View from "$ecomponents/View";
7
+ import { prepareOptions } from "./utils";
8
+ const BarcodeGeneratorComponent = forwardRef(({width,height,lineColor,bars,value,onReady,format,id,testID,text,flat,displayValue,fontOptions,font,textAlign,textPosition,textMargin,fontSize,backgroun,margin,marginTop,marginBottom,marginLeft,marginRight,containerProps},ref)=>{
9
+ testID = defaultStr(testID,"RNBarCodeGeneratorComponent");
10
+ const child = React.isValidElement(text,true)? text : displayValue !== false && value || null;
11
+ const {style,svgStyle,displayChildOnTop} = prepareOptions({textPosition,fontOptions,fontSize,textMargin,textAlign,margin,marginLeft,marginRight,marginTop,marginBottom});
12
+ const children = child? <Label
13
+ testID = {`${testID}_Label`}
14
+ style = {[theme.styles.w100,style,theme.Colors.isValid(lineColor) && {color:lineColor}]}
15
+ >{child}</Label> : null;
16
+ useEffect(()=>{
17
+ if(typeof onReady =='function'){
18
+ setTimeout(onReady,50);
19
+ }
20
+ },[width,height,lineColor,bars,value,format,id,testID,text,flat,displayValue,fontOptions,font,textAlign,textPosition,textMargin,fontSize,backgroun,margin,marginTop,marginBottom,marginLeft,marginRight])
21
+ containerProps = Object.assign({},containerProps);
22
+ return <View ref = {ref} id={id} testID={testID+"_Container"} {...containerProps} style={[{alignSelf:"center"},containerProps.style]}>
23
+ {displayChildOnTop ? chilren : null}
24
+ <Svg
25
+ testID={testID}
26
+ height={height} width={width} fill={lineColor}
27
+ style = {svgStyle}
28
+ >
29
+ <Path d={bars.join(' ')} />
30
+ </Svg>
31
+ {!displayChildOnTop ? children : null}
32
+ </View>
33
+ });
34
+
35
+ BarcodeGeneratorComponent.displayName = "BarcodeGeneratorComponent";
36
+
37
+ export default BarcodeGeneratorComponent;
@@ -0,0 +1,232 @@
1
+ import React, { useMemo,forwardRef,useEffect,defaultNumber,useRef,useMergeRefs} from '$react';
2
+ import PropTypes from 'prop-types';
3
+ import barcodes from 'jsbarcode/src/barcodes';
4
+ import theme,{StyleProp} from "$theme";
5
+ import {defaultStr,defaultObj,isNonNullString,extendObj,uniqid,isDataURL} from "$cutils";
6
+ import Generator from "./Generator";
7
+ import {isMobileNative} from "$cplatform";
8
+ import { defaultBarcodeFormat,barcodeFormats,jsbarcodePropTypes,prepareOptions } from './utils';
9
+ import { captureRef } from '$expo-ui/view-shot';
10
+ import Base64 from "$base64";
11
+
12
+ export * from "./utils";
13
+
14
+ const BarcodeGenerator = forwardRef(({
15
+ value = '',
16
+ width = 2,
17
+ height = 100,
18
+ format,
19
+ lineColor,
20
+ children,
21
+ testID,
22
+ style:cStyle,
23
+ onError,
24
+ autoConvertToDataURL,
25
+ onConvertToDataURL,
26
+ maxWidth,
27
+ backgroundColor,
28
+ dataURLOptions,
29
+ id,
30
+ ...rest
31
+ },ref) => {
32
+ dataURLOptions = defaultObj(dataURLOptions);
33
+ testID = defaultStr(testID,"RNBarcodeGenerator");
34
+ const innerRef = useRef(null);
35
+ const isReadyRef = useRef(false);
36
+ const setReady = ()=> isReadyRef.current = true;
37
+ const style = theme.flattenStyle(cStyle);
38
+ const idRef = useRef(defaultStr(id,uniqid("bar-code-generator-web")));
39
+ backgroundColor = theme.Colors.isValid(backgroundColor) ? backgroundColor : style.backgroundColor = theme.Colors.isValid(style.backgroundColor)? style.backgroundColor : '#ffffff';
40
+ lineColor = theme.Colors.isValid(lineColor)? lineColor : '#000000';
41
+ if(!isNonNullString(format)){
42
+ format = defaultBarcodeFormat;
43
+ } else if(!barcodeFormats.includes(format)){
44
+ console.warn(`Format de code bar [${format}] est invalide, il sera remplacé par le format [${defaultBarcodeFormat}]. Vous devez spécifier un format parmi la liste : [${barcodeFormats.join(",")}]`,children,rest)
45
+ format = defaultBarcodeFormat;
46
+ }
47
+ const drawRect = (x, y, width, height) => {
48
+ return `M${x},${y}h${width}v${height}h-${width}z`;
49
+ };
50
+
51
+ const drawSvgBarCode = (encoded) => {
52
+ const rects = [];
53
+ const { data: binary } = encoded;
54
+
55
+ const barCodeWidth = binary.length * width;
56
+ const singleBarWidth =
57
+ typeof maxWidth === 'number' && barCodeWidth > maxWidth
58
+ ? maxWidth / binary.length
59
+ : width;
60
+ let barWidth = 0;
61
+ let x = 0;
62
+ let yFrom = 0;
63
+
64
+ for (let b = 0; b < binary.length; b++) {
65
+ x = b * singleBarWidth;
66
+ if (binary[b] === '1') {
67
+ barWidth++;
68
+ } else if (barWidth > 0) {
69
+ rects[rects.length] = drawRect(
70
+ x - singleBarWidth * barWidth,
71
+ yFrom,
72
+ singleBarWidth * barWidth,
73
+ height,
74
+ );
75
+ barWidth = 0;
76
+ }
77
+ }
78
+
79
+ if (barWidth > 0) {
80
+ rects[rects.length] = drawRect(
81
+ x - singleBarWidth * (barWidth - 1),
82
+ yFrom,
83
+ singleBarWidth * barWidth,
84
+ height,
85
+ );
86
+ }
87
+
88
+ return rects;
89
+ };
90
+
91
+
92
+
93
+ const { bars, barCodeWidth } = useMemo(() => {
94
+ try {
95
+ const encoded = encode({value,width,height,format,lineColor,maxWidth});
96
+ if(!encoded){
97
+ throw new Error(`code barre ${value} invalide pour le format sélectionné ${format}`);
98
+ }
99
+ const barCodeWidth = encoded.data.length * width;
100
+ return {
101
+ bars: drawSvgBarCode(encoded),
102
+ barCodeWidth:
103
+ typeof maxWidth === 'number' && barCodeWidth > maxWidth
104
+ ? maxWidth
105
+ : barCodeWidth,
106
+ };
107
+ } catch (error) {
108
+ if (__DEV__) {
109
+ console.error(error.message);
110
+ }
111
+ if (onError) {
112
+ onError(error);
113
+ }
114
+ }
115
+ return {
116
+ bars: [],
117
+ barCodeWidth: 0,
118
+ };
119
+ }, [value, width, height, format, lineColor, maxWidth]);
120
+ useEffect(()=>{
121
+ if(autoConvertToDataURL === true){
122
+ toDataURL();
123
+ }
124
+ },[format,value,width,height,lineColor])
125
+ const toDataURL = ()=>{
126
+ return new Promise((resolve,reject)=>{
127
+ if(!isMobileNative() && typeof document !=="undefined" && typeof document?.querySelector =='function'){
128
+ const element = document.querySelector(`#${idRef.current}`);
129
+ if(element && window?.XMLSerializer){
130
+ try {
131
+ const xml = new XMLSerializer().serializeToString(element);
132
+ const r = 'data:image/svg+xml;base64,' + Base64.encode(xml);
133
+ if(isDataURL(r) && typeof onConvertToDataURL =="function"){
134
+ onConvertToDataURL(r);
135
+ }
136
+ return resolve(r);
137
+ } catch (e){
138
+ console.log(e," isdddddd");
139
+ }
140
+ }
141
+ }
142
+ return innerRef.current?.measureInWindow((x, y, width, height) => {
143
+ const cb = ()=>{
144
+ return captureRef(innerRef.current,extendObj({},{
145
+ quality: 1,
146
+ format: 'png',
147
+ result : "data-uri",
148
+ width,
149
+ height,
150
+ },dataURLOptions)).then((r)=>{
151
+ if(isDataURL(r) && typeof onConvertToDataURL =="function"){
152
+ onConvertToDataURL(r);
153
+ }
154
+ }).catch((e)=>{
155
+ console.log(e," is capturing data url");
156
+ reject(e);
157
+ });
158
+ }
159
+ if(!isReadyRef.current){
160
+ return setTimeout(cb,50);
161
+ }
162
+ return cb();
163
+ });
164
+ })
165
+ }
166
+ React.setRef(ref,toDataURL);
167
+ return (<Generator
168
+ {...rest}
169
+ id = {idRef.current}
170
+ onReady = {setReady}
171
+ value = {value}
172
+ bars = {bars}
173
+ format = {format}
174
+ testID = {testID}
175
+ background = {backgroundColor}
176
+ width = {isMobileNative()?barCodeWidth:width}
177
+ height = {height}
178
+ lineColor = {lineColor}
179
+ ref = {useMergeRefs(ref,innerRef)}
180
+ />);
181
+ });
182
+
183
+ /****
184
+ encode le barcode passé en paramètre
185
+ @return {null|object}
186
+ @param {string|object}
187
+ si object alors : {
188
+ value {string}, la valeur à vérifier
189
+ format {string}, le code du format à vérifier
190
+ }
191
+ si string alors {value:{string}}, le format par défaut est le code128
192
+ @param {string} format, si value est un objet alors le second paramètre peut être considéré comme le format
193
+ */
194
+ export const encode = (options,format)=>{
195
+ if(isNonNullString(options)){
196
+ options = {text:options};
197
+ } else options = defaultObj(options);
198
+ const text = defaultStr(options.value,options.text);
199
+ const {text:cText,value:cValue,format:cFormat,...rest} = options;
200
+ format = defaultStr(format,options.format);
201
+ if(!isNonNullString(text)) return null;
202
+ if(!isNonNullString(format) || !barcodeFormats[format]){
203
+ format = defaultBarcodeFormat
204
+ }
205
+ try {
206
+ const encoder = new barcodes[format](text, {
207
+ format,
208
+ displayValue : true,
209
+ flat: true,
210
+ ...rest,
211
+ });
212
+ if (!encoder.valid()) {
213
+ return null;
214
+ }
215
+ return encoder.encode();
216
+ } catch{
217
+ return null;
218
+ }
219
+ }
220
+
221
+ BarcodeGenerator.propTypes = {
222
+ value: PropTypes.string.isRequired,
223
+ ...jsbarcodePropTypes,
224
+ dataURLOptions : PropTypes.object,//les options à utiliser pour la convertion en data url
225
+ onConvertToDataURL : PropTypes.func,//lorsque la valeur est converti au format data url
226
+ maxWidth: PropTypes.number,
227
+ style: StyleProp,
228
+ onError: PropTypes.func,
229
+ autoConvertToDataURL : PropTypes.bool,//si la valeur sera auto converti en url lorsque le composant sera monté
230
+ };
231
+
232
+ export default BarcodeGenerator;
@@ -0,0 +1,64 @@
1
+ import barcodes from 'jsbarcode/src/barcodes';
2
+ import PropTypes from "prop-types";
3
+ import {defaultObj} from "$cutils";
4
+
5
+ export const barcodeFormats = Object.keys(barcodes);
6
+
7
+ export const defaultBarcodeFormat = 'CODE128';
8
+
9
+ export const jsbarcodePropTypes = {
10
+ value : PropTypes.string.isRequired,
11
+ format : PropTypes.oneOf(barcodeFormats),
12
+ width : PropTypes.number, //The width option is the width of a single bar., default : 2
13
+ height : PropTypes.number,//The height of the barcode., default 100,
14
+ text : PropTypes.string, //Overide the text that is diplayed
15
+ displayValue : PropTypes.bool,
16
+ fontOptions : PropTypes.string,//With fontOptions you can add bold or italic text to the barcode.
17
+ font : PropTypes.string,
18
+ textAlign : PropTypes.oneOf(["center","left","right"]), //Set the horizontal alignment of the text. Can be left / center / right.
19
+ textPosition : PropTypes.oneOf(["bottom","top"]),//Set the vertical position of the text. Can be bottom / top., default bottom
20
+ textMargin : PropTypes.number,//default : 2, Set the space between the barcode and the text.
21
+ fontSize : PropTypes.number,//Set the size of the text., default : 20,
22
+ flat : PropTypes.bool, //Only for EAN8/EAN13
23
+ background : PropTypes.string,//Set the background of the barcode., default #ffffff
24
+ lineColor: PropTypes.string,//Set the color of the bars and the text., default #000000
25
+ margin : PropTypes.number,//deafult : 10, Set the space margin around the barcode. If nothing else is set, all side will inherit the margins property but can be replaced if you want to set them separably.
26
+ marginTop : PropTypes.number,
27
+ marginBottom : PropTypes.number,
28
+ marginLeft : PropTypes.number,
29
+ marginRight : PropTypes.number,
30
+ valid : PropTypes.func,//function(valid){}
31
+ }
32
+
33
+ export const JSBarcodePropTypes = jsbarcodePropTypes;
34
+
35
+ export const prepareOptions = ({textPosition,fontOptions,fontSize,textMargin,textAlign,margin,marginLeft,marginRight,marginTop,marginBottom})=>{
36
+ const displayChildOnTop = defaultStr(textPosition,"bottom").toLowerCase().trim() ==="top";
37
+ fontOptions = defaultStr(fontOptions,"bold").toLowerCase();
38
+ textMargin = defaultNumber(textMargin,2);
39
+ const style = {
40
+ fontSize:defaultNumber(fontSize,20),
41
+ textAlign : ["center","left","right"].includes(textAlign) && textAlign ||"center",
42
+ backgroundColor : "transparent",
43
+ };
44
+ if(fontOptions.includes("bold")){
45
+ style.fontWeight = "bold";
46
+ }
47
+ if(fontOptions.includes("italic")){
48
+ style.fontStyle = "italic";
49
+ }
50
+ if(displayChildOnTop){
51
+ style.marginBottom = textMargin;
52
+ } else style.marginTop = textMargin;
53
+ margin = defaultNumber(margin,10);
54
+ const svgStyle = {};
55
+ Object.map({marginTop,marginBottom,marginLeft,marginRight},(v,i)=>{
56
+ svgStyle[i] = defaultNumber(v,margin);
57
+ });
58
+ return {
59
+ displayChildOnTop,
60
+ fontOptions,
61
+ svgStyle,
62
+ style,
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ import {default as Scanner} from "./Scanner";
2
+ import {default as Generator} from "./Generator";
3
+ export * from "./Scanner";
4
+ export * from "./Generator";
5
+
6
+ Scanner.Generator = Generator;
7
+
8
+ export default Scanner;
9
+
10
+ export {Generator,Scanner};
@@ -0,0 +1,71 @@
1
+ import html2canvas from "html2canvas";
2
+ import { isNonNullString,defaultObj } from "$cutils";
3
+ /***
4
+ @param : view, les options de la vie
5
+ @param {object} :
6
+ options (object) --
7
+ An optional map of optional options
8
+ format (string) -- "png" | "jpg" | "webm", defaults to "png", "webm" supported only on Android.
9
+ quality (number) -- Number between 0 and 1 where 0 is worst quality and 1 is best, defaults to 1
10
+ result (string) -- The type for the resulting image. - 'tmpfile' -- (default) Return a temporary file uri. - 'base64' -- base64 encoded image. - 'data-uri' -- base64 encoded image with data-uri prefix.
11
+ height (number) -- Height of result in pixels
12
+ width (number) -- Width of result in pixels
13
+ snapshotContentContainer (bool) -- if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height
14
+ */
15
+ export async function captureRef(view, options) {
16
+ options = defaultObj(options);
17
+ if (options.result === "tmpfile") {
18
+ return Promise.reject({message : `Tmpfile is not implemented for web. Try base64 or file.\nFor compatibility, it currently returns the same result as data-uri`})
19
+ }
20
+ if(!isNonNullString(options.format) || !["png","jpg","webm"].includes(options.format.toLowerCase())){
21
+ options.format = "png";
22
+ } else options.format = options.format.toLowerCase();
23
+ options.quality = typeof options.quality =="number"? options.quality : 1;
24
+ if(!isNonNullString(options.result) || !['base64','data-uri'].includes(options.result.toLowerCase())){
25
+ options.result = "data-uri";
26
+ } else options.result = options.result.toLowerCase();
27
+ // TODO: implement snapshotContentContainer option
28
+ const h2cOptions = {};
29
+ if(typeof options.width =="number"){
30
+ h2cOptions.width = options.width;
31
+ }
32
+ if(typeof options.height =="number"){
33
+ h2cOptions.height = options.height;
34
+ }
35
+ if(typeof options.x =="number"){
36
+ h2cOptions.x = options.x;
37
+ }
38
+ if(typeof options.y =='number'){
39
+ h2cOptions.y = options.y;
40
+ }
41
+ return html2canvas(view, h2cOptions).then((renderedCanvas)=>{
42
+ if (false && options.width && options.height) {
43
+ // Resize result
44
+ const resizedCanvas = document.createElement('canvas');
45
+ const resizedContext = resizedCanvas.getContext('2d');
46
+ resizedCanvas.height = options.height;
47
+ resizedCanvas.width = options.width;
48
+ console.log(options.width," is dddd ",options);
49
+ resizedContext.drawImage(renderedCanvas, 0, 0, resizedCanvas.width, resizedCanvas.height);
50
+ renderedCanvas = resizedCanvas;
51
+ }
52
+ const dataUrl = renderedCanvas.toDataURL("image/" + options.format, options.quality);
53
+ console.log(dataUrl," is ddddddddddd")
54
+ if (options.result === "data-uri" || options.result === "tmpfile") return dataUrl;
55
+ return dataUrl.replace(/data:image\/(\w+);base64,/, '');
56
+ });
57
+ }
58
+
59
+ export function captureScreen(options) {
60
+ return captureRef(window.document.body, options);
61
+ }
62
+
63
+ export function releaseCapture(uri) {
64
+ return null;
65
+ }
66
+
67
+ export default {
68
+ captureRef,
69
+ captureScreen,
70
+ releaseCapture
71
+ }
@@ -0,0 +1,2 @@
1
+ export * from "react-native-view-shot";
2
+ export {default} from "react-native-view-shot";