diff --git a/libs/device/feature/src/lib/api-features/api-features.service.ts b/libs/device/feature/src/lib/api-features/api-features.service.ts index e856367297..ec126cbc86 100644 --- a/libs/device/feature/src/lib/api-features/api-features.service.ts +++ b/libs/device/feature/src/lib/api-features/api-features.service.ts @@ -98,8 +98,17 @@ export class APIFeaturesService { } @IpcEvent(APIFeaturesServiceEvents.FeatureData) - public async getFeatureData(feature: string): Promise> { - const device = this.deviceProtocol.apiDevice + public async getFeatureData({ + feature, + deviceId, + }: { + feature: string + deviceId?: DeviceId + }): Promise> { + const device = deviceId + ? this.deviceProtocol.getAPIDeviceById(deviceId) + : this.deviceProtocol.apiDevice + if (!device) { return Result.failed(new AppError(GeneralError.NoDevice, "")) } diff --git a/libs/device/feature/src/lib/api-features/get-feature-data.request.ts b/libs/device/feature/src/lib/api-features/get-feature-data.request.ts new file mode 100644 index 0000000000..8feca9afcc --- /dev/null +++ b/libs/device/feature/src/lib/api-features/get-feature-data.request.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { ipcRenderer } from "electron-better-ipc" +import { Feature } from "generic-view/models" +import { APIFeaturesServiceEvents } from "device/models" +import { ResultObject } from "Core/core/builder" +import { DeviceId } from "Core/device/constants/device-id" + +export const getFeatureDataRequest = ( + deviceId: DeviceId, + feature: string +): Promise> => { + return ipcRenderer.callMain(APIFeaturesServiceEvents.FeatureData, { + deviceId, + feature, + }) +} diff --git a/libs/device/feature/src/lib/api-features/index.ts b/libs/device/feature/src/lib/api-features/index.ts index b3dc5fd59e..15f59196df 100644 --- a/libs/device/feature/src/lib/api-features/index.ts +++ b/libs/device/feature/src/lib/api-features/index.ts @@ -7,3 +7,4 @@ export * from "./api-features.service" export * from "./get-overview-data.request" export * from "./get-overview-configuration.request" export * from "./get-feature-configuration.request" +export * from "./get-feature-data.request" diff --git a/libs/generic-view/models/src/lib/device.ts b/libs/generic-view/models/src/lib/device.ts index 2187ab70e0..b0535647fe 100644 --- a/libs/generic-view/models/src/lib/device.ts +++ b/libs/generic-view/models/src/lib/device.ts @@ -9,7 +9,7 @@ import { DeviceProperties } from "device-manager/models" import { ApiConfig, MenuConfig, OverviewData } from "device/models" import { DeviceId } from "Core/device/constants/device-id" -interface Feature> { +export interface Feature> { config?: View data?: Data } diff --git a/libs/generic-view/models/src/lib/mc-file-manager-view.ts b/libs/generic-view/models/src/lib/mc-file-manager-view.ts index d9be1c8feb..293d0f991a 100644 --- a/libs/generic-view/models/src/lib/mc-file-manager-view.ts +++ b/libs/generic-view/models/src/lib/mc-file-manager-view.ts @@ -5,7 +5,29 @@ import { z } from "zod" -const dataValidator = z.undefined() +const storageCategoryValidator = z.object({ + spaceUsedBytes: z.number(), + spaceUsedString: z.string(), + storageCategory: z.string(), +}) + +export const mcFileManagerData = z.object({ + storageInformation: z.array( + z.object({ + storageType: z.string(), + totalSpaceBytes: z.number(), + usedSpaceBytes: z.number(), + totalSpaceString: z.string(), + usedSpaceString: z.string(), + categoriesSpaceInformation: z.record( + z.string(), + storageCategoryValidator + ), + }) + ), +}) + +export type McFileManagerData = z.infer const configValidator = z.object({ entityTypes: z.array(z.string()).min(1), @@ -15,6 +37,6 @@ export type McFileManagerView = z.infer export const mcFileManagerView = { key: "mc-file-manager-view", - dataValidator, + dataValidator: mcFileManagerData, configValidator, } as const diff --git a/libs/generic-view/models/src/lib/paragraphs.ts b/libs/generic-view/models/src/lib/paragraphs.ts index de244af610..45e8b09fdf 100644 --- a/libs/generic-view/models/src/lib/paragraphs.ts +++ b/libs/generic-view/models/src/lib/paragraphs.ts @@ -6,7 +6,10 @@ import { z } from "zod" import { commonTextValidators } from "./common-text-validators" -const dataValidator = z.undefined() +const dataValidator = z.object({ + text: z.string().optional(), +}) +export type ParagraphData = z.infer const configValidator = z .object({ diff --git a/libs/generic-view/store/src/lib/features/featues-action-keys.ts b/libs/generic-view/store/src/lib/features/featues-action-keys.ts index 9b7769e1ae..8ab2fe47e7 100644 --- a/libs/generic-view/store/src/lib/features/featues-action-keys.ts +++ b/libs/generic-view/store/src/lib/features/featues-action-keys.ts @@ -10,6 +10,4 @@ export enum FeaturesActions { GetGenericData = "api-actions-get-generic-data", GetAllFeatures = "api-actions-get-all-features", GetSingleFeature = "api-actions-get-single-feature", - GetSingleFeatureConfig = "api-actions-get-single-feature-config", - GetSingleFeatureData = "api-actions-get-single-feature-data", } diff --git a/libs/generic-view/store/src/lib/features/get-generic-data.actions.ts b/libs/generic-view/store/src/lib/features/get-generic-data.actions.ts new file mode 100644 index 0000000000..271549582c --- /dev/null +++ b/libs/generic-view/store/src/lib/features/get-generic-data.actions.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { createAsyncThunk } from "@reduxjs/toolkit" +import { Feature } from "generic-view/models" +import { getFeatureDataRequest } from "device/feature" +import { DeviceId } from "Core/device/constants/device-id" +import { ReduxRootState } from "Core/__deprecated__/renderer/store" +import { selectConfiguredDevice } from "../selectors" +import { FeaturesActions } from "./featues-action-keys" +import { transformDataComponents } from "./transform-data-components" + +export const getGenericData = createAsyncThunk< + { + deviceId: DeviceId + feature: string + data: Feature["data"] + }, + { deviceId: DeviceId; feature: string }, + { state: ReduxRootState } +>( + FeaturesActions.GetGenericData, + async ({ deviceId, feature }, { rejectWithValue, getState }) => { + const { ok, data } = await getFeatureDataRequest(deviceId, feature) + + const features = selectConfiguredDevice(getState(), deviceId)?.features + const featureConfig = features?.[feature]?.config + + if (featureConfig === undefined) { + return rejectWithValue("no device") + } + + if (ok) { + return { + deviceId, + feature, + data: transformDataComponents(feature, data, featureConfig), + } + } + + return rejectWithValue(undefined) + } +) diff --git a/libs/generic-view/store/src/lib/features/get-single-feature-config.ts b/libs/generic-view/store/src/lib/features/get-single-feature-config.ts deleted file mode 100644 index 620463de5a..0000000000 --- a/libs/generic-view/store/src/lib/features/get-single-feature-config.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Mudita sp. z o.o. All rights reserved. - * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md - */ - -import { createAsyncThunk } from "@reduxjs/toolkit" -import { DeviceId } from "Core/device/constants/device-id" -import { ReduxRootState } from "Core/__deprecated__/renderer/store" -import { FeaturesActions } from "./featues-action-keys" -import { getOverviewConfig } from "./get-overview-config.actions" - -export const getSingleFeatureConfig = createAsyncThunk< - undefined, - { deviceId: DeviceId; feature: string }, - { state: ReduxRootState } ->( - FeaturesActions.GetSingleFeatureConfig, - async ({ deviceId, feature }, { getState, dispatch }) => { - switch (feature) { - case "mc-overview": - await dispatch(getOverviewConfig({ deviceId })) - break - } - - return undefined - } -) diff --git a/libs/generic-view/store/src/lib/features/get-single-feature-data.ts b/libs/generic-view/store/src/lib/features/get-single-feature-data.ts index 2fd2c62ef3..52a5903782 100644 --- a/libs/generic-view/store/src/lib/features/get-single-feature-data.ts +++ b/libs/generic-view/store/src/lib/features/get-single-feature-data.ts @@ -8,6 +8,7 @@ import { DeviceId } from "Core/device/constants/device-id" import { ReduxRootState } from "Core/__deprecated__/renderer/store" import { FeaturesActions } from "./featues-action-keys" import { getOverviewData } from "./get-overview-data.actions" +import { getGenericData } from "./get-generic-data.actions" export const getSingleFeatureData = createAsyncThunk< undefined, @@ -15,11 +16,13 @@ export const getSingleFeatureData = createAsyncThunk< { state: ReduxRootState } >( FeaturesActions.GetSingleFeature, - async ({ deviceId, feature }, { getState, dispatch }) => { + async ({ deviceId, feature }, { dispatch }) => { switch (feature) { case "mc-overview": await dispatch(getOverviewData({ deviceId })) break + default: + await dispatch(getGenericData({ deviceId, feature })) } return undefined diff --git a/libs/generic-view/store/src/lib/features/get-single-feature.ts b/libs/generic-view/store/src/lib/features/get-single-feature.ts index 45959c47c5..a273cd7c20 100644 --- a/libs/generic-view/store/src/lib/features/get-single-feature.ts +++ b/libs/generic-view/store/src/lib/features/get-single-feature.ts @@ -10,6 +10,7 @@ import { FeaturesActions } from "./featues-action-keys" import { getOverviewConfig } from "./get-overview-config.actions" import { getOverviewData } from "./get-overview-data.actions" import { getGenericConfig } from "./get-generic-config.actions" +import { getGenericData } from "./get-generic-data.actions" export const getSingleFeatures = createAsyncThunk< undefined, @@ -25,6 +26,7 @@ export const getSingleFeatures = createAsyncThunk< break default: await dispatch(getGenericConfig({ deviceId, feature })) + await dispatch(getGenericData({ deviceId, feature })) break } diff --git a/libs/generic-view/store/src/lib/features/transform-data-components.ts b/libs/generic-view/store/src/lib/features/transform-data-components.ts new file mode 100644 index 0000000000..ae348275ef --- /dev/null +++ b/libs/generic-view/store/src/lib/features/transform-data-components.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { Feature } from "generic-view/models" +import { View } from "generic-view/utils" +import { generatedData } from "../../../../ui/src/lib/generated" + +export const transformDataComponents = ( + feature: string, + data: Feature["data"], + config: View +): Feature["data"] => { + return generatedData[feature](data, config) +} diff --git a/libs/generic-view/store/src/lib/features/transform-generic-components.ts b/libs/generic-view/store/src/lib/features/transform-generic-components.ts index 84dd552da0..8a5b386139 100644 --- a/libs/generic-view/store/src/lib/features/transform-generic-components.ts +++ b/libs/generic-view/store/src/lib/features/transform-generic-components.ts @@ -4,12 +4,13 @@ */ import { View } from "generic-view/utils" -import { generated } from "../../../../ui/src/lib/generated" +import { generatedViews } from "../../../../ui/src/lib/generated" export const transformGenericComponents = (view: View): View => { const fullView = { ...view } for (const [key, { component, config, layout }] of Object.entries(fullView)) { - const transformComponent = generated[component as keyof typeof generated] + const transformComponent = + generatedViews[component as keyof typeof generatedViews] if (transformComponent) { Object.assign( fullView, diff --git a/libs/generic-view/store/src/lib/views/reducer.ts b/libs/generic-view/store/src/lib/views/reducer.ts index 4d17160ffe..ba3c250138 100644 --- a/libs/generic-view/store/src/lib/views/reducer.ts +++ b/libs/generic-view/store/src/lib/views/reducer.ts @@ -16,6 +16,7 @@ import { getAPIAny } from "../get-api-any" import { getMenuConfig } from "../get-menu-config" import { getOutboxData } from "../outbox/get-outbox-data.action" import { getGenericConfig } from "../features/get-generic-config.actions" +import { getGenericData } from "../features/get-generic-data.actions" import { addDevice, removeDevice, @@ -163,6 +164,17 @@ export const genericViewsReducer = createReducer(initialState, (builder) => { }, } }) + builder.addCase(getGenericData.fulfilled, (state, action) => { + const { deviceId, feature, data } = action.payload + state.lastResponse = action.payload + state.devices[deviceId].features = { + ...state.devices[deviceId].features, + [feature]: { + config: state.devices[deviceId].features?.[feature]?.config, + data, + }, + } + }) builder.addCase(setDeviceState, (state, action) => { if (action.payload) { const id = action.payload.id diff --git a/libs/generic-view/ui/src/lib/generated/index.ts b/libs/generic-view/ui/src/lib/generated/index.ts index 50fc7b0ec7..3fc3ce1652 100644 --- a/libs/generic-view/ui/src/lib/generated/index.ts +++ b/libs/generic-view/ui/src/lib/generated/index.ts @@ -4,19 +4,29 @@ */ import { + Feature, mcContactsView, mcFileManagerView, mcImportContactsButton, } from "generic-view/models" import { generateMcContactsView } from "./mc-contacts-view" -import { generateMcFileManagerView } from "./mc-file-manager-view/mc-file-manager-view" +import { generateMcFileManagerView } from "./mc-file-manager/mc-file-manager-view" +import { generateFileManagerData } from "./mc-file-manager/mc-file-manager-data" import { generateMcImportContactsButton } from "./mc-import-contacts-button" +import { View } from "generic-view/utils" export * from "./mc-import-contacts-button" export * from "./mc-contacts-view" -export const generated = { +export const generatedViews = { [mcContactsView.key]: generateMcContactsView, [mcFileManagerView.key]: generateMcFileManagerView, [mcImportContactsButton.key]: generateMcImportContactsButton, } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type generatedDataFn = (data: any, config: View) => Feature["data"] + +export const generatedData: Record = { + ["fileManager"]: generateFileManagerData, +} diff --git a/libs/generic-view/ui/src/lib/generated/mc-file-manager-view/file-category-list.ts b/libs/generic-view/ui/src/lib/generated/mc-file-manager/file-category-list.ts similarity index 97% rename from libs/generic-view/ui/src/lib/generated/mc-file-manager-view/file-category-list.ts rename to libs/generic-view/ui/src/lib/generated/mc-file-manager/file-category-list.ts index 2ea4ada533..8497f6e176 100644 --- a/libs/generic-view/ui/src/lib/generated/mc-file-manager-view/file-category-list.ts +++ b/libs/generic-view/ui/src/lib/generated/mc-file-manager/file-category-list.ts @@ -149,8 +149,12 @@ const generateFileCategoryListItem = ({ [`${id}CategoryListItemStorageText`]: { component: "p3-component", config: { - text: "0 KB", + text: "0", color: "black", + textTransform: "format-bytes", + textTransformOptions: { + minUnit: "KB", + }, }, }, [`${id}CategoryListItemStorageMarker`]: { diff --git a/libs/generic-view/ui/src/lib/generated/mc-file-manager-view/file-list.ts b/libs/generic-view/ui/src/lib/generated/mc-file-manager/file-list.ts similarity index 100% rename from libs/generic-view/ui/src/lib/generated/mc-file-manager-view/file-list.ts rename to libs/generic-view/ui/src/lib/generated/mc-file-manager/file-list.ts diff --git a/libs/generic-view/ui/src/lib/generated/mc-file-manager/mc-file-manager-data.ts b/libs/generic-view/ui/src/lib/generated/mc-file-manager/mc-file-manager-data.ts new file mode 100644 index 0000000000..6f1597a45e --- /dev/null +++ b/libs/generic-view/ui/src/lib/generated/mc-file-manager/mc-file-manager-data.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { + EntitiesLoaderConfig, + Feature, + McFileManagerData, +} from "generic-view/models" +import { View } from "generic-view/utils" + +const isEntitiesLoaderConfig = ( + subview: unknown +): subview is EntitiesLoaderConfig => { + return ( + typeof subview === "object" && subview !== null && "entityTypes" in subview + ) +} + +const findInternalStorageInformation = ( + storageInformation: McFileManagerData["storageInformation"] +) => { + return storageInformation.find( + (storage) => storage.storageType === "INTERNAL" + ) +} + +const generateCategoryListItemStorageTexts = ( + entityTypes: string[], + internalStorageInformation: NonNullable< + ReturnType + > +) => { + return entityTypes.reduce((prevState, entityType, id) => { + const categoriesSpaceInformation = + internalStorageInformation.categoriesSpaceInformation[entityType] + if (!categoriesSpaceInformation) { + return prevState + } + + prevState[`${id}CategoryListItemStorageText`] = { + text: categoriesSpaceInformation.spaceUsedBytes, + } + + return prevState + }, {} as Record) +} + +const generateOtherFilesSpaceInformation = ( + internalStorageInformation: NonNullable< + ReturnType + > +) => { + const otherFilesSpaceInformation = + internalStorageInformation.categoriesSpaceInformation["otherFiles"] + + if (!otherFilesSpaceInformation) { + return {} + } + + return { + fileCategoryOtherFilesItemNameSize: { + text: `(${otherFilesSpaceInformation.spaceUsedString})`, + }, + } +} + +export const generateFileManagerData = ( + data: McFileManagerData, + config: View +): Feature["data"] => { + const entitiesLoaderConfig = config.fileManagerLoader.config + const entityTypes = isEntitiesLoaderConfig(entitiesLoaderConfig) + ? entitiesLoaderConfig.entityTypes + : [] + + const internalStorageInformation = findInternalStorageInformation( + data.storageInformation + ) + + if (!internalStorageInformation) { + return {} + } + + const categoryListItemStorageTexts = generateCategoryListItemStorageTexts( + entityTypes, + internalStorageInformation + ) + + const otherFilesSpaceInformation = generateOtherFilesSpaceInformation( + internalStorageInformation + ) + + return { + ...otherFilesSpaceInformation, + ...categoryListItemStorageTexts, + } +} diff --git a/libs/generic-view/ui/src/lib/generated/mc-file-manager-view/mc-file-manager-view.ts b/libs/generic-view/ui/src/lib/generated/mc-file-manager/mc-file-manager-view.ts similarity index 100% rename from libs/generic-view/ui/src/lib/generated/mc-file-manager-view/mc-file-manager-view.ts rename to libs/generic-view/ui/src/lib/generated/mc-file-manager/mc-file-manager-view.ts diff --git a/libs/generic-view/ui/src/lib/generated/mc-file-manager-view/other-files-list.tsx b/libs/generic-view/ui/src/lib/generated/mc-file-manager/other-files-list.tsx similarity index 100% rename from libs/generic-view/ui/src/lib/generated/mc-file-manager-view/other-files-list.tsx rename to libs/generic-view/ui/src/lib/generated/mc-file-manager/other-files-list.tsx diff --git a/libs/generic-view/ui/src/lib/texts/paragraphs.tsx b/libs/generic-view/ui/src/lib/texts/paragraphs.tsx index 87796df57e..1c20ae6a4f 100644 --- a/libs/generic-view/ui/src/lib/texts/paragraphs.tsx +++ b/libs/generic-view/ui/src/lib/texts/paragraphs.tsx @@ -6,8 +6,8 @@ import React from "react" import { APIFC } from "generic-view/utils" import styled, { css } from "styled-components" -import { ParagraphConfig } from "generic-view/models" import { isEmpty } from "lodash" +import { ParagraphConfig, ParagraphData } from "generic-view/models" import { Content } from "../data-rows/text-formatted" import { CommonTextProps, commonTextStyles } from "./common-text-styles" import { applyTextTransform } from "./apply-text-transform" @@ -21,16 +21,20 @@ const commonStyles = css` ${commonTextStyles}; ` -export const Paragraph1: APIFC = ({ +export const Paragraph1: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + const text = data?.text ?? config?.text + + const transformedText = applyTextTransform( + text, config?.textTransform, config?.textTransformOptions ) + return ( = ({ $singleLine={config?.singleLine} $textAlign={config?.textAlign} > - {isEmpty(children) ? text : children} + {isEmpty(children) ? transformedText : children} ) } @@ -60,16 +64,20 @@ export const P1 = styled.p` } ` -export const Paragraph2: APIFC = ({ +export const Paragraph2: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + const text = data?.text ?? config?.text + + const transformedText = applyTextTransform( + text, config?.textTransform, config?.textTransformOptions ) + return ( = ({ $singleLine={config?.singleLine} $textAlign={config?.textAlign} > - {isEmpty(children) ? text : children} + {isEmpty(children) ? transformedText : children} ) } @@ -96,16 +104,20 @@ export const P2 = styled.p` ${commonStyles}; ` -export const Paragraph3: APIFC = ({ +export const Paragraph3: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + const text = data?.text ?? config?.text + + const transformedText = applyTextTransform( + text, config?.textTransform, config?.textTransformOptions ) + return ( = ({ $singleLine={config?.singleLine} $textAlign={config?.textAlign} > - {isEmpty(children) ? text : children} + {isEmpty(children) ? transformedText : children} ) } @@ -132,16 +144,20 @@ export const P3 = styled.p` ${commonStyles}; ` -export const Paragraph4: APIFC = ({ +export const Paragraph4: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + const text = data?.text ?? config?.text + + const transformedText = applyTextTransform( + text, config?.textTransform, config?.textTransformOptions ) + return ( = ({ $singleLine={config?.singleLine} $textAlign={config?.textAlign} > - {isEmpty(children) ? text : children} + {isEmpty(children) ? transformedText : children} ) } @@ -168,16 +184,20 @@ export const P4 = styled.p` ${commonStyles}; ` -export const Paragraph5: APIFC = ({ +export const Paragraph5: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + const text = data?.text ?? config?.text + + const transformedText = applyTextTransform( + text, config?.textTransform, config?.textTransformOptions ) + return ( = ({ $singleLine={config?.singleLine} $textAlign={config?.textAlign} > - {isEmpty(children) ? text : children} + {isEmpty(children) ? transformedText : children} ) }