From d42654f33d077edb28ad86790b9472a996fac4f9 Mon Sep 17 00:00:00 2001 From: Maxime Baconnais Date: Wed, 1 May 2024 00:19:08 +0200 Subject: [PATCH] Permissions page --- package-lock.json | 46 +++++++-------- package.json | 10 ++-- resources/i18n/de.json | 13 +++- resources/i18n/en.json | 13 +++- resources/i18n/eo.json | 13 +++- resources/i18n/es.json | 13 +++- resources/i18n/fr.json | 13 +++- resources/i18n/it.json | 13 +++- resources/i18n/pl.json | 13 +++- resources/i18n/pt.json | 13 +++- src/config.js | 1 + src/main/actions.js | 26 +++++++- src/renderer/App.jsx | 2 + src/renderer/Exporter.js | 2 +- src/renderer/actions/index.js | 44 ++++++++++++++ src/renderer/cameras/NativeProxy.js | 4 +- .../components/CameraSettingsWindow/index.jsx | 4 +- src/renderer/components/MediaStatus/index.jsx | 20 +++++++ .../components/MediaStatus/style.module.css | 3 + src/renderer/components/Player/index.jsx | 4 +- .../components/SettingsForm/index.jsx | 20 +++++++ src/renderer/components/Text/index.jsx | 7 +++ src/renderer/components/Text/style.module.css | 8 +++ src/renderer/hooks/useCamera.js | 25 +++++++- src/renderer/styles/vars.module.css | 1 + src/renderer/views/Home.jsx | 9 +++ src/renderer/views/Permissions.jsx | 59 +++++++++++++++++++ 27 files changed, 353 insertions(+), 46 deletions(-) create mode 100644 src/renderer/components/MediaStatus/index.jsx create mode 100644 src/renderer/components/MediaStatus/style.module.css create mode 100644 src/renderer/components/Text/index.jsx create mode 100644 src/renderer/components/Text/style.module.css create mode 100644 src/renderer/views/Permissions.jsx diff --git a/package-lock.json b/package-lock.json index 579bc73..54a2e1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@brick-a-brack/eagle-animation", - "version": "2.6.0", + "version": "2.6.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@brick-a-brack/eagle-animation", - "version": "2.6.0", + "version": "2.6.2", "hasInstallScript": true, "license": "GPL-3.0", "dependencies": { @@ -44,7 +44,7 @@ "react-keyboard-event-handler": "^1.5.4", "react-router-dom": "^6.23.0", "react-select": "^5.8.0", - "react-tooltip": "^5.26.3", + "react-tooltip": "^5.26.4", "rimraf": "^5.0.5", "uuid": "^9.0.1", "vite-plugin-top-level-await": "^1.4.1" @@ -60,14 +60,14 @@ "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.1.0", - "globals": "^15.0.0", + "globals": "^15.1.0", "husky": "^9.0.11", "i18next-scanner": "^4.4.0", "lightningcss": "^1.24.1", "prettier": "^3.2.5", "process": "^0.11.10", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "stylelint": "^16.4.0", "stylelint-config-standard": "^36.0.0", "stylelint-order": "^6.0.4", @@ -6144,9 +6144,9 @@ } }, "node_modules/globals": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.0.0.tgz", - "integrity": "sha512-m/C/yR4mjO6pXDTm9/R/SpYTAIyaUB4EOzcaaMEl7mds7Mshct9GfejiJNQGjHHbdMPey13Kpu4TMbYi9ex1pw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.1.0.tgz", + "integrity": "sha512-926gJqg+4mkxwYKiFvoomM4J0kWESfk3qfTvRL2/oc/tK/eTDBbrfcKnSa2KtfdxB5onoL7D3A3qIHQFpd4+UA==", "dev": true, "engines": { "node": ">=18" @@ -8825,9 +8825,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -8836,15 +8836,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-hook-form": { @@ -8957,9 +8957,9 @@ } }, "node_modules/react-tooltip": { - "version": "5.26.3", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.26.3.tgz", - "integrity": "sha512-MpYAws8CEHUd/RC4GaDCdoceph/T4KHM5vS5Dbk8FOmLMvvIht2ymP2htWdrke7K6lqPO8rz8+bnwWUIXeDlzg==", + "version": "5.26.4", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.26.4.tgz", + "integrity": "sha512-5WyDrsfw1+6qNVSr3IjqElqJ+cCwE8+44b+HpJ8qRLv7v0a3mcKf8wvv+NfgALFS6QpksGFqTLV2JQ60c+okZQ==", "dependencies": { "@floating-ui/dom": "^1.6.1", "classnames": "^2.3.0" @@ -9379,9 +9379,9 @@ "dev": true }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index 1057012..d32b4fd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@brick-a-brack/eagle-animation", "description": "EagleAnimation is an awesome, modern, free and open-source animation software", - "version": "2.6.1", + "version": "2.6.2", "private": false, "author": { "name": "Brick à Brack", @@ -70,7 +70,7 @@ "react-keyboard-event-handler": "^1.5.4", "react-router-dom": "^6.23.0", "react-select": "^5.8.0", - "react-tooltip": "^5.26.3", + "react-tooltip": "^5.26.4", "rimraf": "^5.0.5", "uuid": "^9.0.1", "vite-plugin-top-level-await": "^1.4.1" @@ -86,14 +86,14 @@ "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "eslint-plugin-simple-import-sort": "^12.1.0", - "globals": "^15.0.0", + "globals": "^15.1.0", "husky": "^9.0.11", "i18next-scanner": "^4.4.0", "lightningcss": "^1.24.1", "prettier": "^3.2.5", "process": "^0.11.10", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "stylelint": "^16.4.0", "stylelint-config-standard": "^36.0.0", "stylelint-order": "^6.0.4", diff --git a/resources/i18n/de.json b/resources/i18n/de.json index f9b7837..d43b636 100644 --- a/resources/i18n/de.json +++ b/resources/i18n/de.json @@ -195,5 +195,16 @@ "The exported frames resolution": "Die Auflösung der exportierten Bilder", "Use project ratio": "Verwenden Sie das Projektverhältnis", "Normalize all the frames to match the project aspect ratio": "Normalisieren Sie alle Bilder, damit sie dem Seitenverhältnis des Projekts entsprechen", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Wählen Sie eine Kamera", + "Camera access": "Kamerazugriff", + "Microphone access": "Mikrofonzugriff", + "Grant access to the camera to capture photos": "Gewähren Sie Zugriff auf die Kamera, um Fotos aufzunehmen", + "Grant access to the microphone to record voices and sounds": "Gewähren Sie Zugriff auf das Mikrofon, um Stimmen und Geräusche aufzuzeichnen", + "Acces granted": "Zugriff gewährt", + "Grant permission": "Berechtigung erteilen", + "Permissions": "Berechtigungen", + "Permissions setup": "Einrichtung von Berechtigungen", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation benötigt Kamera- und Mikrofonberechtigungen, um zu funktionieren. Wenn Sie dies überspringen, können Sie später in den Einstellungen Zugriff gewähren.", + "Let's go!": "Los geht's!", + "Skip setup": "Einrichtung überspringen" } \ No newline at end of file diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 6a09624..e5d5d81 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -194,5 +194,16 @@ "The exported frames resolution": "The exported frames resolution", "Use project ratio": "Use project ratio", "Normalize all the frames to match the project aspect ratio": "Normalize all the frames to match the project aspect ratio", - "Choose a camera": "Choose a camera" + "Choose a camera": "Choose a camera", + "Camera access": "Camera access", + "Microphone access": "Microphone access", + "Grant access to the camera to capture photos": "Grant access to the camera to capture photos", + "Grant access to the microphone to record voices and sounds": "Grant access to the microphone to record voices and sounds", + "Acces granted": "Acces granted", + "Grant permission": "Grant permission", + "Permissions": "Permissions", + "Permissions setup": "Permissions setup", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.", + "Let's go!": "Let's go!", + "Skip setup": "Skip setup" } diff --git a/resources/i18n/eo.json b/resources/i18n/eo.json index 63ef264..9ffeb9d 100644 --- a/resources/i18n/eo.json +++ b/resources/i18n/eo.json @@ -195,5 +195,16 @@ "The exported frames resolution": "La rezolucio de eksportitaj bildoj", "Use project ratio": "Uzi la projekta proporcio", "Normalize all the frames to match the project aspect ratio": "Normigi ĉiujn bildojn por ke ili kongruu kun la projekta aspekta proporcio", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Elektu kameron", + "Camera access": "Alirebligo al la kamerao", + "Microphone access": "Alirebligo al la mikrofono", + "Grant access to the camera to capture photos": "Permesi aliron al la kamerao por kapti fotojn", + "Grant access to the microphone to record voices and sounds": "Permesi aliron al la mikrofono por registri voĉojn kaj sonojn", + "Acces granted": "Aliro permesita", + "Grant permission": "Doni permeson", + "Permissions": "Permesoj", + "Permissions setup": "Agordo de permesoj", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation postulas kameraajn kaj mikrofonajn permesojn por funkcii, se vi preterlasas, vi povos doni aliron en la agordoj poste.", + "Let's go!": "Ni ek!", + "Skip setup": "Preterigi agordon" } \ No newline at end of file diff --git a/resources/i18n/es.json b/resources/i18n/es.json index e6c6703..55c7e2b 100644 --- a/resources/i18n/es.json +++ b/resources/i18n/es.json @@ -195,5 +195,16 @@ "The exported frames resolution": "La resolución de los fotogramas exportados", "Use project ratio": "Usar la proporción del proyecto", "Normalize all the frames to match the project aspect ratio": "Normalizar todos los fotogramas para que coincidan con la proporción del proyecto", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Elige una cámara", + "Camera access": "Acceso a la cámara", + "Microphone access": "Acceso al micrófono", + "Grant access to the camera to capture photos": "Permitir el acceso a la cámara para capturar fotos", + "Grant access to the microphone to record voices and sounds": "Permitir el acceso al micrófono para grabar voces y sonidos", + "Acces granted": "Acceso concedido", + "Grant permission": "Conceder permiso", + "Permissions": "Permisos", + "Permissions setup": "Configuración de permisos", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation requiere permisos de cámara y micrófono para funcionar, si se omite, podrás conceder el acceso en la configuración más tarde.", + "Let's go!": "¡Vamos!", + "Skip setup": "Omitir configuración" } \ No newline at end of file diff --git a/resources/i18n/fr.json b/resources/i18n/fr.json index 39a48e4..a0f92c5 100644 --- a/resources/i18n/fr.json +++ b/resources/i18n/fr.json @@ -194,5 +194,16 @@ "The exported frames resolution": "Résolution des images exportées", "Use project ratio": "Utiliser le ratio du projet", "Normalize all the frames to match the project aspect ratio": "Normaliser toutes les images pour qu'elles correspondent au ratio du projet", - "Choose a camera": "Choisir une caméra" + "Choose a camera": "Choisir une caméra", + "Camera access": "Accès à la caméra", + "Microphone access": "Accès au microphone", + "Grant access to the camera to capture photos": "Permettre l'accès à la caméra pour prendre des photos", + "Grant access to the microphone to record voices and sounds": "Permettre l'accès au microphone pour enregistrer des voix et des sons", + "Acces granted": "Accès autorisé", + "Grant permission": "Donner la permission", + "Permissions": "Permissions", + "Permissions setup": "Configuration des permissions", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation requiert l'accès à la caméra et au microphone pour fonctionner. Vous pourrez accorder l'accès dans les paramètres plus tard.", + "Let's go!": "C'est parti", + "Skip setup": "Continuer sans configurer" } diff --git a/resources/i18n/it.json b/resources/i18n/it.json index eb2c989..3e49c7e 100644 --- a/resources/i18n/it.json +++ b/resources/i18n/it.json @@ -195,5 +195,16 @@ "The exported frames resolution": "La risoluzione dei fotogrammi esportati", "Use project ratio": "Usa il rapporto del progetto", "Normalize all the frames to match the project aspect ratio": "Normalizza tutti i fotogrammi per farli corrispondere al rapporto di aspetto del progetto", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Scegli una fotocamera", + "Camera access": "Accesso alla fotocamera", + "Microphone access": "Accesso al microfono", + "Grant access to the camera to capture photos": "Concedi l'accesso alla fotocamera per scattare foto", + "Grant access to the microphone to record voices and sounds": "Concedi l'accesso al microfono per registrare voci e suoni", + "Acces granted": "Accesso concesso", + "Grant permission": "Concedi il permesso", + "Permissions": "Permessi", + "Permissions setup": "Configurazione dei permessi", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation richiede i permessi per la fotocamera e il microfono per funzionare. Se saltato, sarai in grado di concedere l'accesso nelle impostazioni più tardi.", + "Let's go!": "Andiamo!", + "Skip setup": "Salta configurazione" } \ No newline at end of file diff --git a/resources/i18n/pl.json b/resources/i18n/pl.json index 10c3ee1..d247e60 100644 --- a/resources/i18n/pl.json +++ b/resources/i18n/pl.json @@ -196,5 +196,16 @@ "The exported frames resolution": "Rozdzielczość eksportowanych klatek", "Use project ratio": "Użyj proporcji projektu", "Normalize all the frames to match the project aspect ratio": "Normalizuj wszystkie klatki, aby pasowały do proporcji projektu", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Wybierz kamerę", + "Camera access": "Dostęp do kamery", + "Microphone access": "Dostęp do mikrofonu", + "Grant access to the camera to capture photos": "Zezwól na dostęp do kamery, aby robić zdjęcia", + "Grant access to the microphone to record voices and sounds": "Zezwól na dostęp do mikrofonu, aby nagrywać głosy i dźwięki", + "Acces granted": "Dostęp przyznany", + "Grant permission": "Przyznać uprawnienia", + "Permissions": "Uprawnienia", + "Permissions setup": "Konfiguracja uprawnień", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation wymaga uprawnień do kamery i mikrofonu, aby działać. Jeśli pominiesz, będziesz mógł przyznać dostęp w ustawieniach.", + "Let's go!": "Zaczynajmy!", + "Skip setup": "Pomiń konfigurację" } \ No newline at end of file diff --git a/resources/i18n/pt.json b/resources/i18n/pt.json index 6f49c54..f8296b4 100644 --- a/resources/i18n/pt.json +++ b/resources/i18n/pt.json @@ -195,5 +195,16 @@ "The exported frames resolution": "A resolução dos quadros exportados", "Use project ratio": "Use a proporção do projeto", "Normalize all the frames to match the project aspect ratio": "Normalize todos os quadros para corresponder à proporção do projeto", - "Choose a camera": "__NOT_TRANSLATED__" + "Choose a camera": "Escolha uma câmera", + "Camera access": "Acesso à câmera", + "Microphone access": "Acesso ao microfone", + "Grant access to the camera to capture photos": "Permitir acesso à câmera para capturar fotos", + "Grant access to the microphone to record voices and sounds": "Permitir acesso ao microfone para gravar vozes e sons", + "Acces granted": "Acesso concedido", + "Grant permission": "Conceder permissão", + "Permissions": "Permissões", + "Permissions setup": "Configuração de permissões", + "Eagle Animation requires camera and microphone permissions to work, if skipped, you will be able to grant access in the settings.": "Eagle Animation requer permissões de câmera e microfone para funcionar, se ignorado, você poderá conceder acesso nas configurações mais tarde.", + "Let's go!": "Vamos lá!", + "Skip setup": "Pular configuração" } \ No newline at end of file diff --git a/src/config.js b/src/config.js index c08473c..c9f5419 100644 --- a/src/config.js +++ b/src/config.js @@ -8,6 +8,7 @@ export const VERSION = json.version; export const LS_PREFIX = 'ea_'; export const LS_LANGUAGE = `${LS_PREFIX}language`; export const LS_SETTINGS = `${LS_PREFIX}settings`; +export const LS_PERMISSIONS = `${LS_PREFIX}permissions`; // Eagle Animation files export const DIRECTORY_NAME = 'EagleAnimation'; diff --git a/src/main/actions.js b/src/main/actions.js index a5a7ca7..1e13c31 100644 --- a/src/main/actions.js +++ b/src/main/actions.js @@ -1,8 +1,8 @@ import { existsSync } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; +import { readFile, writeFile } from 'node:fs/promises'; import { homedir } from 'node:os'; -import { shell } from 'electron'; +import { shell, systemPreferences } from 'electron'; import envPaths from 'env-paths'; import { mkdirp } from 'mkdirp'; import fetch from 'node-fetch'; @@ -67,6 +67,25 @@ const computeProject = (data) => { }; const actions = { + GET_MEDIA_PERMISSIONS: async () => { + if (typeof systemPreferences.getMediaAccessStatus === 'function') { + const [camera, microphone] = await Promise.all([systemPreferences.getMediaAccessStatus('camera'), systemPreferences.getMediaAccessStatus('microphone')]); + return { + camera, + microphone, + }; + } + return { + camera: 'granted', + microphone: 'granted', + }; + }, + ASK_MEDIA_PERMISSION: async (evt, { mediaType }) => { + if (typeof systemPreferences.askForMediaAccess === 'function') { + return systemPreferences.askForMediaAccess(mediaType); + } + return true; + }, GET_LAST_VERSION: async () => { if (CONTRIBUTE_REPOSITORY) { const res = await fetch(`https://raw.githubusercontent.com/${CONTRIBUTE_REPOSITORY}/master/package.json`).then((res) => res.json()); @@ -78,6 +97,9 @@ const actions = { const projects = await getProjectsList(PROJECTS_PATH); return projects.map(computeProject); }, + GET_PICTURE: async (evt, { project_id, track, filename }) => { + return readFile(getPictureLink(join(PROJECTS_PATH, project_id), track, filename)); + }, NEW_PROJECT: async (evt, { title }) => { const data = await createProject(PROJECTS_PATH, title); return computeProject(data); diff --git a/src/renderer/App.jsx b/src/renderer/App.jsx index 68b698a..33dccd6 100644 --- a/src/renderer/App.jsx +++ b/src/renderer/App.jsx @@ -5,6 +5,7 @@ import { WindowProvider } from './contexts/WindowContext'; import AnimatorView from './views/Animator'; import ExportView from './views/Export'; import HomeView from './views/Home'; +import PermissionsView from './views/Permissions'; import SettingsView from './views/Settings'; import ShortcutsView from './views/Shortcuts'; @@ -16,6 +17,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> 404} /> diff --git a/src/renderer/Exporter.js b/src/renderer/Exporter.js index d6de8eb..06c2e31 100644 --- a/src/renderer/Exporter.js +++ b/src/renderer/Exporter.js @@ -19,7 +19,7 @@ const generateFakeFrame = (resolution, format) => ); }); -const ExportFrame = ( +export const ExportFrame = ( link, resolution = null, // {width, height} format = null, // png | jpg | webp diff --git a/src/renderer/actions/index.js b/src/renderer/actions/index.js index e656ba7..dab246b 100644 --- a/src/renderer/actions/index.js +++ b/src/renderer/actions/index.js @@ -71,7 +71,51 @@ const computeProject = async (data) => { return output; }; +let dedupProms = {}; + export const Actions = { + GET_MEDIA_PERMISSIONS: async () => { + if (isFirefox()) { + dedupProms.testCamera = + dedupProms.testCamera || + navigator.mediaDevices + .getUserMedia({ video: true }) + .then(() => true) + .catch(() => false); + + dedupProms.testMicrophone = + dedupProms.testMicrophone || + navigator.mediaDevices + .getUserMedia({ audio: true }) + .then(() => true) + .catch(() => false); + + const [firefoxCameraPermission, firefoxMicrophonePermission] = await Promise.all([dedupProms.testCamera, dedupProms.testMicrophone]); + return { + camera: firefoxCameraPermission ? 'granted' : 'denied', + microphone: firefoxMicrophonePermission ? 'granted' : 'denied', + }; + } else { + const [cameraPermission, microphonePermission] = await Promise.all([ + navigator.permissions.query({ name: 'camera' }).catch(() => null), + navigator.permissions.query({ name: 'microphone' }).catch(() => null), + ]); + return { + camera: cameraPermission?.state === 'granted' ? 'granted' : 'denied', + microphone: microphonePermission?.state === 'granted' ? 'granted' : 'denied', + }; + } + }, + ASK_MEDIA_PERMISSION: async (evt, { mediaType }) => { + const permission = await navigator.mediaDevices + .getUserMedia({ + ...(mediaType === 'camera' ? { video: true } : {}), + ...(mediaType === 'microphone' ? { audio: true } : {}), + }) + .then(() => true) + .catch(() => false); + return permission; + }, GET_LAST_VERSION: async () => { return { version: null }; // Web version is always up-to-date, ignore update detection }, diff --git a/src/renderer/cameras/NativeProxy.js b/src/renderer/cameras/NativeProxy.js index c08fef8..9e4d65b 100644 --- a/src/renderer/cameras/NativeProxy.js +++ b/src/renderer/cameras/NativeProxy.js @@ -67,8 +67,8 @@ class NativeProxy { async connect({ imageDOM } = { imageDOM: false }, settings = {}, onBinded = () => {}) { this.video = imageDOM; this.settings = settings; - window.EA('CONNECT_NATIVE_CAMERA', { camera_id: this.context.id }); - this.initPreview(); + window.EA('CONNECT_NATIVE_CAMERA', { camera_id: this.context.id }); + this.initPreview(); if (typeof onBinded === 'function') { onBinded(); } diff --git a/src/renderer/components/CameraSettingsWindow/index.jsx b/src/renderer/components/CameraSettingsWindow/index.jsx index e9a3de6..487ddba 100644 --- a/src/renderer/components/CameraSettingsWindow/index.jsx +++ b/src/renderer/components/CameraSettingsWindow/index.jsx @@ -26,9 +26,9 @@ const CameraSettingsWindow = ({ t, cameraCapabilities, onCapabilityChange, - onDevicesListRefresh = () => { }, + onDevicesListRefresh = () => {}, onCapabilitiesReset, - onSettingsChange = () => { }, + onSettingsChange = () => {}, appCapabilities = [], devices = [], settings = {}, diff --git a/src/renderer/components/MediaStatus/index.jsx b/src/renderer/components/MediaStatus/index.jsx new file mode 100644 index 0000000..2ddc9e1 --- /dev/null +++ b/src/renderer/components/MediaStatus/index.jsx @@ -0,0 +1,20 @@ +import { withTranslation } from 'react-i18next'; + +import ActionCard from '../ActionCard'; +import FormGroup from '../FormGroup'; + +import * as style from './style.module.css'; + +const MediaStatus = ({ type, permission, action, t }) => { + return ( + + {permission === 'granted' &&
{t('Acces granted')}
} + {permission !== 'granted' && } +
+ ); +}; + +export default withTranslation()(MediaStatus); diff --git a/src/renderer/components/MediaStatus/style.module.css b/src/renderer/components/MediaStatus/style.module.css new file mode 100644 index 0000000..4efbeea --- /dev/null +++ b/src/renderer/components/MediaStatus/style.module.css @@ -0,0 +1,3 @@ +.granted { + color: var(--color-success); +} \ No newline at end of file diff --git a/src/renderer/components/Player/index.jsx b/src/renderer/components/Player/index.jsx index a46630c..9825842 100644 --- a/src/renderer/components/Player/index.jsx +++ b/src/renderer/components/Player/index.jsx @@ -187,7 +187,7 @@ class Player extends Component { this.resize(); }); this.videoFrameObserver.observe(this.dom.videoFrame.current, { - attributeFilter: ["height", "width"], + attributeFilter: ['height', 'width'], }); this.showFrame(false); @@ -343,7 +343,7 @@ class Player extends Component { ref={this.dom.picture} className={style.layout} style={{ - opacity: !isCameraReady && frameIndex === false ? 0 : (frameIndex !== false || blendMode ? 1 : 1 - onionValue), + opacity: !isCameraReady && frameIndex === false ? 0 : frameIndex !== false || blendMode ? 1 : 1 - onionValue, mixBlendMode: !blendMode ? 'normal' : 'difference', }} /> diff --git a/src/renderer/components/SettingsForm/index.jsx b/src/renderer/components/SettingsForm/index.jsx index dcbdc9b..bf0bdcf 100644 --- a/src/renderer/components/SettingsForm/index.jsx +++ b/src/renderer/components/SettingsForm/index.jsx @@ -4,18 +4,21 @@ import { withTranslation } from 'react-i18next'; import { LANGUAGES } from '../../config'; import useAppCapabilities from '../../hooks/useAppCapabilities'; +import useCamera from '../../hooks/useCamera'; import CustomSlider from '../CustomSlider'; import FormGroup from '../FormGroup'; import FormLayout from '../FormLayout'; import GridIcon from '../GridIcon'; import Heading from '../Heading'; import Input from '../Input'; +import MediaStatus from '../MediaStatus'; import NumberInput from '../NumberInput'; import Select from '../Select'; import Switch from '../Switch'; const SettingsForm = ({ settings = {}, onUpdate = () => {}, t }) => { const { appCapabilities } = useAppCapabilities(); + const { permissions, actions: cameraActions } = useCamera(); const form = useForm({ mode: 'all', defaultValues: settings, @@ -47,6 +50,23 @@ const SettingsForm = ({ settings = {}, onUpdate = () => {}, t }) => {