@fto-consult/expo-ui 8.8.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "8.8.0",
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,6 +77,7 @@
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",
@@ -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;
@@ -1,43 +1,48 @@
1
- import React, { useMemo } from '$react';
1
+ import React, { useMemo,forwardRef,useEffect,defaultNumber,useRef,useMergeRefs} from '$react';
2
2
  import PropTypes from 'prop-types';
3
- import View from "$ecomponents/View";
4
- import Label from "$ecomponents/Label";
5
- import Svg, { Path } from 'react-native-svg';
6
3
  import barcodes from 'jsbarcode/src/barcodes';
7
- import theme from "$theme";
8
- import {defaultStr,defaultObj,isNonNullString} from "$cutils";
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";
9
11
 
10
- export const barCodeFormats = Object.keys(barcodes);
12
+ export * from "./utils";
11
13
 
12
- const Barcode = ({
14
+ const BarcodeGenerator = forwardRef(({
13
15
  value = '',
14
16
  width = 2,
15
17
  height = 100,
16
18
  format,
17
- color,
18
- text,
19
+ lineColor,
20
+ children,
19
21
  testID,
20
- textStyle,
21
22
  style:cStyle,
22
23
  onError,
24
+ autoConvertToDataURL,
25
+ onConvertToDataURL,
23
26
  maxWidth,
24
- svgProps,
25
- header,
26
- childrenProps,
27
+ backgroundColor,
28
+ dataURLOptions,
29
+ id,
27
30
  ...rest
28
- }) => {
31
+ },ref) => {
32
+ dataURLOptions = defaultObj(dataURLOptions);
29
33
  testID = defaultStr(testID,"RNBarcodeGenerator");
30
- svgProps = defaultObj(svgProps);
34
+ const innerRef = useRef(null);
35
+ const isReadyRef = useRef(false);
36
+ const setReady = ()=> isReadyRef.current = true;
31
37
  const style = theme.flattenStyle(cStyle);
32
- style.backgroundColor = theme.Colors.isValid(style.backgroundColor)? style.backgroundColor : '#ffffff';
33
- color = theme.Colors.isValid(color)? color : '#000000';
34
- header = typeof header =="string" && header ? <Label testID={`${testID}_Header`} style={[{color}]}>header</Label> : React.isComponent(header)? header : null;
35
- children = typeof children =="string" && children ? <Label testID={`${testID}_Header`} style={[theme.styles.textAlignCenter,{color}]}>children</Label> : React.isComponent(children)? children : null;
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';
36
41
  if(!isNonNullString(format)){
37
- format = 'CODE128';
38
- } else if(!barCodeFormats.includes(format)){
39
- console.warn(`Format de code bar [${format}] est invalide, il sera remplacé par le format 'CODE128'. Vous devez spécifier un format parmi la liste : [${barCodeFormats.join(",")}]`,children,rest)
40
- format = 'CODE128';
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;
41
46
  }
42
47
  const drawRect = (x, y, width, height) => {
43
48
  return `M${x},${y}h${width}v${height}h-${width}z`;
@@ -83,30 +88,14 @@ const Barcode = ({
83
88
  return rects;
84
89
  };
85
90
 
86
- const encode = (text, Encoder) => {
87
- if (typeof text !== 'string' || text.length === 0) {
88
- throw new Error('Barcode value must be a non-empty string');
89
- }
90
- const encoder = new Encoder(text, {
91
- width,
92
- format,
93
- height,
94
- color,
95
- flat: true,
96
- });
97
- if (!encoder.valid()) {
98
- throw new Error('Invalid barcode for selected format.');
99
- }
100
- return encoder.encode();
101
- };
91
+
102
92
 
103
93
  const { bars, barCodeWidth } = useMemo(() => {
104
94
  try {
105
- const encoder = barcodes[format];
106
- if (!encoder) {
107
- throw new Error('Invalid barcode format.');
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}`);
108
98
  }
109
- const encoded = encode(value, encoder);
110
99
  const barCodeWidth = encoded.data.length * width;
111
100
  return {
112
101
  bars: drawSvgBarCode(encoded),
@@ -127,37 +116,117 @@ const Barcode = ({
127
116
  bars: [],
128
117
  barCodeWidth: 0,
129
118
  };
130
- }, [value, width, height, format, color, maxWidth]);
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
+ });
131
182
 
132
- return (
133
- <View
134
- {...rest}
135
- style={[theme.styles.alignItemsCenter,style]}
136
- testID = {testID}
137
- >
138
- <Svg
139
- height={height} width={barCodeWidth} fill={color}
140
- {...svgProps}
141
- >
142
- <Path d={bars.join(' ')} />
143
- </Svg>
144
- {children ? <Label>{children}</Label> : null}
145
- </View>
146
- );
147
- };
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
+ }
148
220
 
149
- Barcode.propTypes = {
221
+ BarcodeGenerator.propTypes = {
150
222
  value: PropTypes.string.isRequired,
151
- header : PropTypes.node,//le header à afficher
152
- format: PropTypes.oneOf(barCodeFormats),
153
- width: PropTypes.number,
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
154
226
  maxWidth: PropTypes.number,
155
- height: PropTypes.number,
156
- color: PropTypes.string,//la couleur des ligne du code barre généré
157
- text: PropTypes.node,
158
- textStyle: PropTypes.object,
159
- style: PropTypes.object,
227
+ style: StyleProp,
160
228
  onError: PropTypes.func,
229
+ autoConvertToDataURL : PropTypes.bool,//si la valeur sera auto converti en url lorsque le composant sera monté
161
230
  };
162
231
 
163
- export default Barcode;
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
+ }
@@ -1,6 +1,7 @@
1
1
  import {default as Scanner} from "./Scanner";
2
2
  import {default as Generator} from "./Generator";
3
3
  export * from "./Scanner";
4
+ export * from "./Generator";
4
5
 
5
6
  Scanner.Generator = Generator;
6
7
 
@@ -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";