From db8194069b46cebc34cd4701d85109afe81c894a Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 20 Nov 2023 11:28:02 +0000 Subject: [PATCH] feat(deviceImage): allow to pre download device images --- src/components/device-image/index.tsx | 2 +- .../settings-page/image-localiser.tsx | 88 +++++++++++++++++++ src/components/settings-page/index.tsx | 9 +- src/i18n/locales/en.json | 3 +- 4 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 src/components/settings-page/image-localiser.tsx diff --git a/src/components/device-image/index.tsx b/src/components/device-image/index.tsx index b37c09470..4817e8533 100644 --- a/src/components/device-image/index.tsx +++ b/src/components/device-image/index.tsx @@ -15,7 +15,7 @@ type DeviceImageProps = { }; type ImageGeneratorFn = (device: Device) => string | undefined; -const getZ2mDeviceImage = (device: Device): string => +export const getZ2mDeviceImage = (device: Device): string => `https://www.zigbee2mqtt.io/images/devices/${sanitizeZ2MDeviceName(device?.definition?.model)}.jpg`; const getConverterDeviceImage = (device: Device): string | undefined => device.definition?.icon; diff --git a/src/components/settings-page/image-localiser.tsx b/src/components/settings-page/image-localiser.tsx new file mode 100644 index 000000000..24c01dee7 --- /dev/null +++ b/src/components/settings-page/image-localiser.tsx @@ -0,0 +1,88 @@ +import React, { Component, useEffect, useState } from 'react'; + +import { WithTranslation, withTranslation } from "react-i18next"; +import Button from '../button'; +import { Device, IEEEEAddress } from '../../types'; +import { WithDevices } from '../../store'; +import { DeviceApi } from '../../actions/DeviceApi'; +import { getZ2mDeviceImage } from '../device-image'; + +type LocaliserState = 'none' | 'start' | 'inprogress' | 'done'; + +type Props = WithTranslation<'setting'> & WithDevices & Pick; + + +type LStatus = "init" | "error" | "done"; + +function blobToBase64(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = (err) => reject(err); + reader.readAsDataURL(blob); + }); +} + +async function downloadImage(imageSrc: string): Promise { + const image = await fetch(imageSrc); + if (image.ok) { + const blob = await image.blob() + return blobToBase64(blob); + } else { + return Promise.reject(image.status); + } +} + +function ImageLocaliser(props: Props): JSX.Element { + const [currentState, setCurrentState] = useState('none'); + const { setDeviceOptions, t, devices } = props; + const [localisationStatus, setLocalisationStatus] = useState>({}); + + async function localiseImage(device: Device) { + setLocalisationStatus((curr) => { + return { ...curr, [device.ieee_address]: 'init' }; + + }); + + const imageUrl = getZ2mDeviceImage(device); + + const imageContent = await downloadImage(imageUrl); + console.log(device.friendly_name, imageUrl, imageContent); + await setDeviceOptions(device.ieee_address, { icon: imageContent }); + setLocalisationStatus((curr) => { + return { ...curr, [device.ieee_address]: 'done' }; + }); + } + useEffect(() => { + if (currentState == 'start') { + for (const device of Object.values(devices) + .filter(d => d.type !== 'Coordinator')) { + localiseImage(device).catch(err => { + setLocalisationStatus((curr) => { + return { ...curr, [device.ieee_address]: 'error' }; + }); + }).then(); + } + setCurrentState('inprogress'); + } + }, [currentState]); + + switch (currentState) { + case 'none': + return + case 'inprogress': + return
{ + Object.values(devices).map((device) => { + return
{device.friendly_name} {localisationStatus[device.ieee_address]}
+ }) + }
+ case 'done': + return
done
+ } + return
Unknown
+ +} + +export default withTranslation(['settings'])(ImageLocaliser); \ No newline at end of file diff --git a/src/components/settings-page/index.tsx b/src/components/settings-page/index.tsx index d5feb5906..3c0338e9a 100644 --- a/src/components/settings-page/index.tsx +++ b/src/components/settings-page/index.tsx @@ -21,6 +21,8 @@ import frontentPackageJson from '../../../package.json'; import { formatDate } from '../../utils'; import { saveAs } from 'file-saver'; import Spinner from '../spinner'; +import ImageLocaliser from './image-localiser'; +import { DeviceApi } from '../../actions/DeviceApi'; type SettingsTab = 'settings' | 'bridge' | 'about' | 'tools' | 'donate' | 'translate'; @@ -115,7 +117,7 @@ type PropsFromStore = Pick< 'bridgeInfo' | 'missingTranslations' | 'devices' | 'backup' | 'preparingBackup' >; export class SettingsPage extends Component< - PropsFromStore & SettingsPageProps & BridgeApi & UtilsApi & WithTranslation<'setting'>, + PropsFromStore & SettingsPageProps & DeviceApi & BridgeApi & UtilsApi & WithTranslation<'setting'>, SettingsPageState > { state = { @@ -298,7 +300,7 @@ export class SettingsPage extends Component< }; renderTools(): JSX.Element { - const { exportState, restartBridge, t } = this.props; + const { exportState, restartBridge, setDeviceOptions, devices, t } = this.props; return (
+
); } @@ -436,7 +439,7 @@ export class SettingsPage extends Component< const SettingsPageWithRouter = withRouter(SettingsPage); const mappedProps = ['bridgeInfo', 'missingTranslations', 'devices', 'backup', 'preparingBackup']; const ConnectedSettingsPage = withTranslation(['settings', 'common'])( - connect, Record, GlobalState, BridgeApi>( + connect, Record, GlobalState, DeviceApi & BridgeApi>( mappedProps, actions, )(SettingsPageWithRouter), diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 164a5759c..c369e14f5 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -2249,7 +2249,8 @@ "coordinator_ieee_address": "Coordinator IEEE Address", "request_z2m_backup": "Request Z2m backup", "add_install_code": "Add install code", - "homeassistant": "Home Assistant integration" + "homeassistant": "Home Assistant integration", + "localise_images": "Localise device images" }, "touchlink": { "detected_devices_message": "Detected {{count}} touchlink devices.",