@fto-consult/expo-ui 7.8.11 → 7.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fto-consult/expo-ui",
3
- "version": "7.8.11",
3
+ "version": "7.11.0",
4
4
  "description": "Bibliothèque de composants UI Expo,react-native",
5
5
  "scripts": {
6
6
  "clear-npx-cache": "npx clear-npx-cache",
@@ -72,7 +72,7 @@
72
72
  "@expo/vector-icons": "^13.0.0",
73
73
  "@faker-js/faker": "^8.0.2",
74
74
  "@fto-consult/common": "^3.75.2",
75
- "@fto-consult/electron-gen": "^1.3.7",
75
+ "@fto-consult/electron-gen": "^2.1.0",
76
76
  "@pchmn/expo-material3-theme": "^1.3.1",
77
77
  "@react-native-async-storage/async-storage": "1.18.2",
78
78
  "@react-native-community/datetimepicker": "7.2.0",
@@ -45,7 +45,7 @@ import {MORE_ICON} from "$ecomponents/Icon"
45
45
  import ActivityIndicator from "$ecomponents/ActivityIndicator";
46
46
  import {createTableHeader,fields as pdfFields,pageHeaderMargin,sprintf as pdfSprintf} from "$cpdf";
47
47
  import {isWeb,isMobileNative} from "$cplatform";
48
- import { createPDF,getFields as getPdfFields } from '../../../pdf';
48
+ import { createPDF,getFields as getPdfFields } from '$expo-ui/pdf';
49
49
 
50
50
  export const TIMEOUT = 100;
51
51
 
@@ -1835,16 +1835,7 @@ export default class CommonDatagridComponent extends AppComponent {
1835
1835
  if(pdfDocumentTitle){
1836
1836
  content.unshift(pdfDocumentTitle);
1837
1837
  }
1838
- const pdf = createPDF({...config,content});
1839
- if(isWeb()){
1840
- return pdf.open();
1841
- }
1842
- return;
1843
- FileSystem.writeExcel({...config,workbook:wb}).then(({path})=>{
1844
- if(isNonNullString(path)){
1845
- notify.success("Fichier enregistré dans le répertoire {0}".sprintf(path))
1846
- }
1847
- })
1838
+ return createPDF({...config,content});
1848
1839
  }).finally(Preloader.close);
1849
1840
  }
1850
1841
  handleTableExport(args){
@@ -0,0 +1,364 @@
1
+ import * as FileSaver from "$efile-system/utils/FileSaver";
2
+ import {isElectron} from "$cplatform";
3
+ import notify from "$cnotify";
4
+ import Preloader from "$preloader";
5
+ import {defaultStr,isNonNullString,defaultObj,getFileExtension,isPromise} from "$cutils";
6
+ import {HStack} from "$ecomponents/Stack";
7
+ import Label from "$ecomponents/Label";
8
+ import DialogProvider from "$ecomponents/Form/FormData/DialogProvider";
9
+ import session from "$session";
10
+
11
+ const startSessionKey = "desktop-capturer-session";
12
+ const actionsSessionKey = "desktop-capturer-actions";
13
+
14
+ export const canRecord = x=> isElectron()? true : typeof navigator !=="undefined" && window?.navigator && (navigator?.mediaDevices) && typeof navigator?.mediaDevices?.getDisplayMedia === 'function';
15
+
16
+ export const updateSystemTray = x => isElectron() && typeof ELECTRON !=="undefined" && ELECTRON && typeof ELECTRON?.desktopCapturer?.updateSystemTray ==="function" ? ELECTRON.desktopCapturer.updateSystemTray() : undefined;
17
+
18
+
19
+ export function getUserMedia(constraints) {
20
+ // if Promise-based API is available, use it
21
+ if ((navigator?.mediaDevices && typeof navigator?.mediaDevices?.getUserMedia =="function")) {
22
+ return navigator.mediaDevices.getUserMedia(constraints);
23
+ }
24
+ // otherwise try falling back to old, possibly prefixed API...
25
+ const legacyApi = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
26
+ if (legacyApi) {
27
+ return new Promise(function (resolve, reject) {
28
+ legacyApi.bind(navigator)(constraints, resolve, reject);
29
+ });
30
+ }
31
+ return Promise.reject({status:false,msg:"user media not available"})
32
+ }
33
+ export const getAudioConstraint = x=>{
34
+ return {audio: true,echoCancellation:{exact: true},noiseSuppression:{exact:true}};
35
+ }
36
+ export async function getUserMediaAsync(constraints,video) {
37
+ try {
38
+ const stream = await (video === false ? getUserMedia(constraints): navigator.mediaDevices.getDisplayMedia(constraints));
39
+ return stream;
40
+ } catch (e) {
41
+ console.error('navigator.getUserMedia error:', e);
42
+ }
43
+ return null;
44
+ }
45
+ export function handleUserMediaError(e) {
46
+ console.error(e," stream recording error");
47
+ notify.error(e);
48
+ }
49
+
50
+ export function getSupportedMimeTypes(mediaTypes,filter) {
51
+ if(isNonNullString(mediaTypes)){
52
+ mediaTypes = mediaTypes.split(",");
53
+ }
54
+ mediaTypes = Array.isArray(mediaTypes)? mediaTypes : isNonNullString(mediaTypes)? mediaTypes.split(",") : [];
55
+ filter = typeof filter =="function"? filter : x=>true;
56
+ if (!mediaTypes.length) mediaTypes.push(...['video', 'audio'])
57
+ const FILE_EXTENSIONS = ['webm', 'ogg', 'mp4', 'x-matroska']
58
+ const CODECS = ['vp9', 'vp9.0', 'vp8', 'vp8.0', 'avc1', 'av1', 'h265', 'h.265', 'h264', 'h.264', 'opus']
59
+ return [...new Set(
60
+ FILE_EXTENSIONS.flatMap(ext =>
61
+ CODECS.flatMap(codec =>
62
+ mediaTypes.flatMap(mediaType => [
63
+ `${mediaType}/${ext};codecs:${codec}`,
64
+ `${mediaType}/${ext};codecs=${codec}`,
65
+ `${mediaType}/${ext};codecs:${codec.toUpperCase()}`,
66
+ `${mediaType}/${ext};codecs=${codec.toUpperCase()}`,
67
+ `${mediaType}/${ext}`,
68
+ ]),
69
+ ),
70
+ ),
71
+ )].filter(variation => MediaRecorder.isTypeSupported(variation) && filter(variation))
72
+ }
73
+ const recordingKeys = ['isRecording','isPaused','isInactive'];
74
+ function mainDesktopCapturer (){
75
+ let mimeType = "video/webm;codecs=vp9";
76
+ let recordingOptions = {};
77
+ var recorder;
78
+ let blobs = [];
79
+ const electronDesktopCapturer = isElectron() && typeof window?.ELECTRON !=="undefined" && typeof window?.ELECTRON?.desktopCapturer =="object" && ELECTRON?.desktopCapturer || {};
80
+ const getRecordingStatus = ()=>{
81
+ const ret = {}
82
+ if(recorder){
83
+ recordingKeys.map((v)=>{
84
+ if(recorder){
85
+ ret[v] = recorder.state == v.toLowerCase().split("is")[1]? true : false
86
+ } else {
87
+ ret[v] = false;
88
+ }
89
+ });
90
+ } else if(electronDesktopCapturer?.getRecordingStatus){
91
+ return electronDesktopCapturer?.getRecordingStatus();
92
+ }
93
+ return ret;
94
+ };
95
+ function handleStream(stream,opts) {
96
+ opts = Object.assign({},opts);
97
+ recorder = new MediaRecorder(stream, { mimeType});
98
+ blobs = [];
99
+ recorder.ondataavailable = function(event) {
100
+ if(event.data.size > 0){
101
+ blobs.push(event.data);
102
+ }
103
+ updateSystemTray();
104
+ };
105
+ recorder.onstop = function(event){
106
+ updateSystemTray();
107
+ if(!blobs.length) return false;
108
+ const opts = defaultObj(recordingOptions);
109
+ let {fileName} = opts;
110
+ fileName = defaultStr(fileName,"video-"+APP.getName()+"-"+(new Date().toFormat("dd-mm-yyyy HHMM"))).trim();
111
+ fileName = (fileName.rtrim(getFileExtension(fileName,false)))+".webm";
112
+ return FileSaver.save({content:new Blob(blobs, {type: mimeType}),mimeType,fileName}).then(({path,fileName})=>{
113
+ if(isNonNullString(path) || isNonNullString(fileName)){
114
+ notify.info(`Vidéo sauvegardée ${isNonNullString(path)?` à l'emplacement [${path}]`:` avec comme nom de fichier ${fileName}`}`);
115
+ }
116
+ }).catch(notify.error).finally(()=>{
117
+ recorder = undefined;
118
+ blobs = [];
119
+ });
120
+ }
121
+ recorder.start(1000);
122
+ updateSystemTray();
123
+ }
124
+
125
+ function startRecording(opts) {
126
+ if(recorder){
127
+ recorder.stop();
128
+ }
129
+ recorder = undefined;
130
+ opts = defaultObj(opts)
131
+ if(!canRecord()){
132
+ return Promise.reject({stauts:false,isRecording:false,msg:"unable to get user media, get user media is not a function"})
133
+ }
134
+ if(isNonNullString(opts.mimeType)){
135
+ const mimeTypes = getSupportedMimeTypes(x=>!x.startsWith("audio/"))
136
+ if(mimeTypes.includes(opts.mimeType)){
137
+ mimeType = opts.mimeType;
138
+ }
139
+ }
140
+ if(typeof electronDesktopCapturer?.startRecording ==='function'){
141
+ try {
142
+ const e = electronDesktopCapturer.startRecording({...opts,mimeType,updateSystemTray,handleUserMediaError})
143
+ const cb = (e)=>{
144
+ console.log(e," is e of recorder gettted");
145
+ if(e instanceof MediaRecorder){
146
+ recorder = e;
147
+ }
148
+ return e;
149
+ }
150
+ return Promise.resolve(e).then(cb).catch(notify.error);
151
+ } catch(e){
152
+ notify.error(e);
153
+ return Promise.reject(e);
154
+ }
155
+ }
156
+ opts.video = defaultObj(opts.video);
157
+ const audio = isBool(opts.audio) && !opts.audio ? false : defaultObj(opts.audio);
158
+ const video = {
159
+ ...opts.video,
160
+ mediaSource: "screen"
161
+ }
162
+ recordingOptions = opts;
163
+ return new Promise((resolve,reject)=>{
164
+ if(audio){
165
+ (async() => {
166
+ const audioStream = await getUserMediaAsync(getAudioConstraint(),false)
167
+ const videoStream = await getUserMediaAsync({audio:false,video})
168
+ if(audioStream && videoStream){
169
+ const combinedStream = new MediaStream([...videoStream.getVideoTracks(), ...audioStream.getAudioTracks()])
170
+ handleStream(combinedStream,opts)
171
+ }
172
+ resolve({isRecording:true});
173
+ })();
174
+ } else {
175
+ return getUserMediaSync({audio:false,video}).then((stream)=>{
176
+ handleStream(stream,opts);
177
+ resolve({isRecording:true})
178
+ }).catch(handleUserMediaError);
179
+ }
180
+ return resolve({isRecording:false});
181
+ })
182
+ }
183
+
184
+ function pauseRecording(){
185
+ if(electronDesktopCapturer?.pauseRecording){
186
+ return electronDesktopCapturer?.pauseRecording();
187
+ }
188
+ if(!recorder || !getRecordingStatus().isRecording) return;
189
+ recorder.pause();
190
+ updateSystemTray();
191
+ return true;
192
+ }
193
+ function resumeRecording(){
194
+ if(electronDesktopCapturer?.resumeRecording) return electronDesktopCapturer.resumeRecording();
195
+ if(!recorder || !getRecordingStatus().isPaused) return;
196
+ recorder.resume();
197
+ updateSystemTray();
198
+ return true;
199
+ }
200
+ function stopRecording(opts) {
201
+ if(electronDesktopCapturer.stopRecording) return electronDesktopCapturer.stopRecording();
202
+ if(!recorder) return false;
203
+ let s = getRecordingStatus();
204
+ if(!s.isPaused && !s.isRecording){
205
+ recorder = undefined;
206
+ return false;
207
+ }
208
+ if(recorder){
209
+ let s = getRecordingStatus();
210
+ if(s.isRecording || s.isPaused){
211
+ recorder.stop();
212
+ }
213
+ }
214
+ recorder = undefined;
215
+ return true;
216
+ }
217
+ return {
218
+ canRecord,
219
+ isUserMediaAvailable:canRecord,
220
+ getRecordingStatus,
221
+ startRecording,
222
+ pauseRecording,
223
+ resumeRecording,
224
+ getAudioConstraint,
225
+ stopRecording,
226
+ getSupportedMimeTypes,
227
+ }
228
+ }
229
+
230
+ export const looopForTimer = (timer)=>{
231
+ return new Promise((resolve,reject)=>{
232
+ let timerCB = undefined;
233
+ timer = typeof timer =='number'? timer : 3000;
234
+ const loopCB = ()=>{
235
+ clearTimeout(timerCB);
236
+ const d = Math.ceil(timer/1000);
237
+ const testID = "RN_PreloaderLooper";
238
+ if(timer >= 1000){
239
+ Preloader.open({
240
+ content : <HStack testID={testID}>
241
+ <Label>Début capture dans</Label>
242
+ <Label textBold fontSize={40}>{" "+(d).formatNumber()+" "}</Label>
243
+ <Label>seconde{d>1 &&"s"}</Label>
244
+ </HStack>
245
+ })
246
+ timer-=1000;
247
+ timerCB = setTimeout(loopCB,900);
248
+ return;
249
+ }
250
+ Preloader.close();
251
+ resolve();
252
+ }
253
+ return loopCB();
254
+ })
255
+ }
256
+ const desktopCapturer = mainDesktopCapturer();
257
+
258
+ export function handleCapture(){
259
+ if(!canRecord()){
260
+ const message = "Impossible de faire des enregistrements vidéo sur ce type de périférique";
261
+ notify.error(message);
262
+ return Promise.reject({message});
263
+ }
264
+ const {isRecording,isPaused} = desktopCapturer.getRecordingStatus();
265
+ let fields = {},title = "Capture d'écran vidéo en cours", yes = null, no = "Annuler";
266
+ const mimeTypes = getSupportedMimeTypes("video");
267
+ let onSuccess = undefined;
268
+ const sKey = !isPaused && !isRecording ? startSessionKey : actionsSessionKey;
269
+ const data = Object.assign({},session.get(sKey));
270
+ if(!isPaused && !isRecording){
271
+ title = "Effectuer une capture d'écran vidéo";
272
+ fields = {
273
+ audio : {
274
+ text : "Enregistrer le son",
275
+ type : 'switch',
276
+ defaultValue : true,
277
+ checkedValue : true,
278
+ uncheckedValue : false,
279
+ },
280
+ timer : {
281
+ text : 'Délai d\'attente en secondes',
282
+ type : 'number',
283
+ format : 'number',
284
+ defaultValue : !isElectron() ? 0 : 3,
285
+ },
286
+ mimeType : {
287
+ text : 'Format de la vidéo',
288
+ type : 'select',
289
+ items : mimeTypes,
290
+ defaultValue : mimeTypes[0],
291
+ itemValue : ({item,index})=>item,
292
+ renderText : ({item,index}) => item,
293
+ renderItem : ({item,index}) => item,
294
+ }
295
+ }
296
+ yes = {
297
+ text : 'Capturer',
298
+ icon : "record"
299
+ }
300
+ onSuccess = ({data})=>{
301
+ const timer = Math.ceil(typeof data.timer =="number"? data.timer : 0);
302
+ if(timer > 0){
303
+ return looopForTimer(timer*1000).then(()=>{
304
+ desktopCapturer.startRecording(data);
305
+ });
306
+ }
307
+ desktopCapturer.startRecording(data);
308
+ }
309
+ } else {
310
+ const type = isRecording || isPaused ? "radio" : undefined;
311
+ fields = {
312
+ action : {
313
+ text : 'Que voulez vous faire?',
314
+ type : 'select',
315
+ items : [
316
+ isRecording? {
317
+ code :'pauseRecording',
318
+ label:'Mettre la capture en pause',
319
+ type,
320
+ } : undefined,
321
+ isPaused ? {
322
+ code :"resumeRecording",
323
+ label:'Reprendre la capture vidéo',
324
+ type,
325
+ } : undefined,
326
+ {
327
+ code:'stopRecording',
328
+ label:'Arréter la capture vidéo',
329
+ },
330
+ ],
331
+ defaultValue : isPaused?'resumeRecording' : 'stopRecording',
332
+ multiple : false,
333
+ required : true,
334
+ }
335
+ }
336
+ yes = {
337
+ text : "Exécuter",
338
+ icon : "play",
339
+ }
340
+ no = {
341
+ text : "Annuler",
342
+ icon :"cancel",
343
+ }
344
+ onSuccess = ({data})=>{
345
+ if(typeof desktopCapturer[data.action] ==='function'){
346
+ return desktopCapturer[data.action]();
347
+ }
348
+ }
349
+ }
350
+ return DialogProvider.open({
351
+ title,
352
+ actions : [yes],
353
+ onSuccess : ({data,...rest})=>{
354
+ if(onSuccess) onSuccess({data,...rest});
355
+ DialogProvider.close();
356
+ session.set(sKey,data);
357
+ },
358
+ data,
359
+ fields,
360
+ });
361
+ }
362
+
363
+
364
+ export default desktopCapturer;
@@ -1,7 +1,7 @@
1
1
  import React from '$react';
2
2
  import {StyleSheet} from 'react-native';
3
3
  import PropTypes from "prop-types";
4
- import {defaultObj,defaultStr,defaultNumber,defaultBool,uniqid} from "$cutils";
4
+ import {defaultObj,defaultStr,defaultNumber,isValidUrl,uniqid} from "$cutils";
5
5
  import View from "$ecomponents/View";
6
6
  import { useNavigation} from '$cnavigation';
7
7
  import Fab from "$elayouts/Fab";
@@ -75,7 +75,7 @@ export default function MainScreenScreenWithoutAuthContainer(props) {
75
75
 
76
76
 
77
77
  React.useEffect(() => {
78
- if((title||subtitle) && navigation && navigation.setOptions){
78
+ if(navigation && typeof navigation?.setOptions ==="function"){
79
79
  const appName = APP.getName().toUpperCase();
80
80
  subtitle = React.getTextContent(subtitle);
81
81
  let screenTitle = getDefaultTitle(title,true);
@@ -85,15 +85,18 @@ export default function MainScreenScreenWithoutAuthContainer(props) {
85
85
  if(!screenTitle.toUpperCase().contains(appName)){
86
86
  screenTitle+=" | "+appName;
87
87
  }
88
+ if(isElectron() && typeof window?.ELECTRON !== "undefined" && typeof ELECTRON?.getLoadedAppUrl =='function'){
89
+ const loadedUrl = ELECTRON.getLoadedAppUrl();
90
+ if(isValidUrl(loadedUrl) && !screenTitle.includes(loadedUrl)){
91
+ screenTitle = `${screenTitle} [${loadedUrl}]`;
92
+ }
93
+ }
88
94
  navigation.setOptions({
89
95
  ...options,
90
96
  appBarProps:{...options.appBarProps,...appBarProps,title,subtitle},
91
97
  subtitle :subtitle,
92
98
  title : screenTitle,
93
99
  });
94
- if(isElectron() && typeof window?.ELECTRON !== "undefined" && typeof ELECTRON?.setTitle =='function'){
95
- ELECTRON.setTitle(screenTitle);
96
- }
97
100
  }
98
101
  }, [title,subtitle]);
99
102
  const fab = withFab ? <Fab
@@ -0,0 +1,7 @@
1
+ import {save as saveWeb} from "./Fsaver.web";
2
+ import {isElectron} from "$cplatform";
3
+
4
+ export const save = (options,...rest)=>{
5
+ if(!isElectron() || typeof window?.ELECTRON =="undefined" || typeof window?.ELECTRON?.FILE !=="object" || typeof window?.ELECTRON?.FILE?.write !=="function") return saveWeb(options,...rest);
6
+ return ELECTRON.FILE.write(options,...rest);
7
+ }
@@ -1,18 +1,8 @@
1
- const FileSaver = require('file-saver');
2
- import {defaultNumber} from "$cutils";
1
+ import {save as saveWeb} from "./Fsaver.web";
2
+ import {save as saveEelectron} from "./FileSaver.electron";
3
+ import {isElectron} from "$cplatform";
3
4
 
4
- /***
5
- sauvegarde par défaut un fichier blob
6
- */
7
- export const save = ({content,fileName,timeout,delay})=>{
8
- return new Promise((resolve,reject)=>{
9
- try {
10
- FileSaver.saveAs(content, fileName);
11
- setTimeout(() => {
12
- resolve({path:fileName,isWeb : true});
13
- }, defaultNumber(timeout,delay,3000));
14
- } catch(e){
15
- reject(e);
16
- }
17
- })
5
+ export const save = (options,...rest)=> {
6
+ if(isElectron()) return saveEelectron(options,...rest);
7
+ return saveWeb(options,...rest);
18
8
  }
@@ -1,5 +1,5 @@
1
1
  import { Directories,FileSystem } from "./native";
2
- import {defaultStr,defaultBool} from "$cutils";
2
+ import {defaultStr,defaultBool,isBase64} from "$cutils";
3
3
  import p from "../path";
4
4
  import * as Sharing from 'expo-sharing';
5
5
 
@@ -0,0 +1,18 @@
1
+ const FileSaver = require('file-saver');
2
+ import {defaultNumber} from "$cutils";
3
+
4
+ /***
5
+ sauvegarde par défaut un fichier blob
6
+ */
7
+ export const save = ({content,fileName,timeout,delay})=>{
8
+ return new Promise((resolve,reject)=>{
9
+ try {
10
+ FileSaver.saveAs(content, fileName);
11
+ setTimeout(() => {
12
+ resolve({path:fileName,isWeb : true});
13
+ }, defaultNumber(timeout,delay,3000));
14
+ } catch(e){
15
+ reject(e);
16
+ }
17
+ })
18
+ }
@@ -6,8 +6,7 @@ const mime = require('react-native-mime-types')
6
6
  const XLSX = require("xlsx");
7
7
  import Preloader from "$preloader";
8
8
  import * as FileSaver from "./FileSaver";
9
- import {isWeb,isMobileNative} from "$cplatform";
10
-
9
+ import {isMobileNative,isElectron} from "$cplatform";
11
10
 
12
11
  /**** sauvegarde un fichier sur le disque
13
12
  * @param {object} {
@@ -28,14 +27,15 @@ import {isWeb,isMobileNative} from "$cplatform";
28
27
  if(!isNonNullString(fileName)){
29
28
  return Promise.reject({status:false,msg:'Nom de fichier invalide'});
30
29
  }
30
+ const isNative = isMobileNative() || isElectron();
31
31
  if(isBase64(content)){
32
- if(isMobileNative()){
32
+ if(isNative){
33
33
  return FileSaver.save({content,contentType,isBase64:true,fileName,...rest});
34
34
  }
35
35
  content = new Blob([base64toBlob(content,contentType)], {});
36
36
  } else if(isDataURL(content)){
37
- if(isMobileNative()){
38
- return FileSaver.save({content:dataURLToBase64(content),contentType,isBase64:true,fileName,...rest});
37
+ if(isNative){
38
+ return FileSaver.save({content:dataURLToBase64(content),contentType,mime:contentType,isBase64:true,fileName,...rest});
39
39
  }
40
40
  const type = getTypeFromDataURL(content);
41
41
  content = dataURLToBlob(content);
@@ -61,16 +61,16 @@ export const writeExcel = ({workbook,content,contentType,fileName,...rest})=>{
61
61
  if(!isNonNullString(fileName)){
62
62
  return Promise.reject({status:false,message:'Nom de fichier invalide pour le contenu excel à créer'});
63
63
  }
64
-
64
+ const isNative = isMobileNative() || isElectron();
65
65
  Preloader.open("génération du fichier excel "+fileName);
66
66
  if(isBase64(content)){
67
- if(isMobileNative()) return FileSaver.save({content,isBase64:true,contentType,fileName,...rest}).finally(Preloader.close);
67
+ if(isNative) return FileSaver.save({content,isBase64:true,contentType,fileName,...rest}).finally(Preloader.close);
68
68
  content = new Blob([base64toBlob(content, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')], {});
69
69
  }
70
70
  if(isBlob(content)){
71
71
  return write({...rest,content,fileName,contentType}).finally(Preloader.close)
72
72
  }
73
- if(isMobileNative()){
73
+ if(isNative){
74
74
  return FileSaver.save({...rest,content:XLSX.write(workbook, {type:'base64', bookType:ext}),fileName}).finally(Preloader.close).finally(Preloader.close);
75
75
  }
76
76
  return new Promise((resolve,reject)=>{
@@ -7,6 +7,7 @@ import {defaultObj,sortBy,defaultStr,isObj} from "$cutils";
7
7
  import appConfig from "$capp/config";
8
8
  import useContext from "$econtext/hooks";
9
9
  import {useMemo,useEffect,useRef} from "react";
10
+ import eDesktopCapturer,{handleCapture} from "$expo-ui/desktopCapturer";
10
11
  ///les items du drawer
11
12
  import { screenName as aboutScreenName} from "$escreens/Help/About";
12
13
  import theme from "$theme";
@@ -16,7 +17,7 @@ import Auth,{useIsSignedIn,tableDataPerms} from "$cauth";
16
17
  import {getTableDataListRouteName} from "$enavigation/utils";
17
18
  import {isValidElement,usePrevious} from "$react";
18
19
  const useGetItems = (options)=>{
19
- const {navigation:{drawerItems,drawerSections,drawerItemsMutator},tablesData} = useContext();
20
+ const {navigation:{drawerItems,drawerSections,drawerItemsMutator},desktopCapturer,tablesData} = useContext();
20
21
  options = defaultObj(options);
21
22
  const {refresh,force} = options;
22
23
  const showProfilOnDrawer = theme.showProfilAvatarOnDrawer;
@@ -94,7 +95,23 @@ const useGetItems = (options)=>{
94
95
  if(isObj(item) && isNonNullString(item.drawerSection) && (item.drawerSection.trim()) in items){
95
96
  items[item.drawerSection.trim()].items.push(item);
96
97
  }
97
- })
98
+ });
99
+ const canCaptureDesktop = !eDesktopCapturer.canRecord()? false : typeof desktopCapturer =="function"? !!desktopCapturer() : typeof desktopCapturer =="boolean"? desktopCapturer : true;
100
+ const captureSide = canCaptureDesktop ? {
101
+ text : 'Capture d\'écran vidéo',
102
+ icon : "record",
103
+ onPress :()=>{
104
+ return handleCapture();
105
+ }
106
+ }:{};
107
+ let hasCapture = canCaptureDesktop ? false : true;
108
+ if(!hasCapture){
109
+ if(isObj(items.admin) && Array.isArray(items.admin.items)){
110
+ items.admin = Object.clone(items.admin);
111
+ items.admin.items.push(captureSide);
112
+ hasCapture = true;
113
+ }
114
+ }
98
115
  if(handleHelp){
99
116
  const dHelp = isObj(items.help)? Object.clone(items.help) : {};
100
117
  items.help = {
@@ -105,6 +122,10 @@ const useGetItems = (options)=>{
105
122
  ...dHelp,
106
123
  items : Array.isArray(dHelp.items)? dHelp.items : [],
107
124
  };
125
+ if(!hasCapture){
126
+ items.help.items.push(captureSide);
127
+ hasCapture = true;
128
+ }
108
129
  items.help.items.push({
109
130
  icon : 'help',
110
131
  label : 'A propos de '+APP.getName(),