From 81b68cefa49d28e8b42782f3bf58aa6fa8792f03 Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Tue, 3 Dec 2024 10:40:33 +0100 Subject: [PATCH] [CP-3310] Implement Refresh Functionality for FilesManager Data (by feature key) --- .../lib/api-features/api-features.service.ts | 34 +++++++ .../get-file-manager-data.request.ts | 18 ++++ .../feature/src/lib/api-features/index.ts | 1 + .../api-features-service-events.ts | 1 + .../models/src/lib/mc-file-manager-view.ts | 26 ++++- .../generic-view/models/src/lib/paragraphs.ts | 5 +- .../src/lib/features/featues-action-keys.ts | 1 + .../features/get-file-manager-data.actions.ts | 43 +++++++++ .../lib/features/get-single-feature-data.ts | 4 + .../src/lib/features/get-single-feature.ts | 5 + .../store/src/lib/views/reducer.ts | 12 +++ .../file-category-list.ts | 6 +- .../ui/src/lib/texts/paragraphs.tsx | 65 +++++++++---- libs/generic-view/views/src/index.ts | 1 + .../views/src/lib/file-manager.ts | 95 +++++++++++++++++++ 15 files changed, 292 insertions(+), 25 deletions(-) create mode 100644 libs/device/feature/src/lib/api-features/get-file-manager-data.request.ts create mode 100644 libs/generic-view/store/src/lib/features/get-file-manager-data.actions.ts create mode 100644 libs/generic-view/views/src/lib/file-manager.ts 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..bf68348a56 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 @@ -19,6 +19,7 @@ import { import { AppError } from "Core/core/errors" import { DeviceId } from "Core/device/constants/device-id" import { View } from "generic-view/utils" +import { mcFileManagerData, McFileManagerData } from "generic-view/models" export class APIFeaturesService { constructor(private deviceProtocol: DeviceProtocol) {} @@ -158,4 +159,37 @@ export class APIFeaturesService { return Result.failed(response.error) } + + @IpcEvent(APIFeaturesServiceEvents.GetFileManagerData) + public async getFileManagerData({ + deviceId, + }: { + deviceId?: DeviceId + }): Promise> { + const device = deviceId + ? this.deviceProtocol.getAPIDeviceById(deviceId) + : this.deviceProtocol.apiDevice + + if (!device) { + return Result.failed(new AppError(GeneralError.NoDevice, "")) + } + + const response = await device.request({ + endpoint: "FEATURE_DATA", + method: "GET", + body: { + feature: "fileManager", + lang: "en-US", + }, + }) + if (response.ok) { + const overviewData = mcFileManagerData.safeParse(response.data.body) + + return overviewData.success + ? Result.success(overviewData.data) + : Result.failed(new AppError(GeneralError.IncorrectResponse, "")) + } + + return Result.failed(response.error) + } } diff --git a/libs/device/feature/src/lib/api-features/get-file-manager-data.request.ts b/libs/device/feature/src/lib/api-features/get-file-manager-data.request.ts new file mode 100644 index 0000000000..e4eed68fee --- /dev/null +++ b/libs/device/feature/src/lib/api-features/get-file-manager-data.request.ts @@ -0,0 +1,18 @@ +/** + * 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 { McFileManagerData } 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 getFileManagerDataRequest = ( + deviceId: DeviceId +): Promise> => { + return ipcRenderer.callMain(APIFeaturesServiceEvents.GetFileManagerData, { + deviceId, + }) +} diff --git a/libs/device/feature/src/lib/api-features/index.ts b/libs/device/feature/src/lib/api-features/index.ts index b3dc5fd59e..b7165489be 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-file-manager-data.request" diff --git a/libs/device/models/src/lib/renderer-to-main-events/api-features-service-events.ts b/libs/device/models/src/lib/renderer-to-main-events/api-features-service-events.ts index 0215646135..18d1e53906 100644 --- a/libs/device/models/src/lib/renderer-to-main-events/api-features-service-events.ts +++ b/libs/device/models/src/lib/renderer-to-main-events/api-features-service-events.ts @@ -8,4 +8,5 @@ export enum APIFeaturesServiceEvents { FeatureData = "apiservice_feature-data", GetOverviewConfiguration = "apiservice_feature-get-overview-configuration", GetOverviewData = "apiservice_feature-get-overview-data", + GetFileManagerData = "apiservice_feature-get-file-manager-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..204333557a 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 @@ -7,6 +7,7 @@ export enum FeaturesActions { GetOverviewConfig = "api-actions-get-overview-config", GetGenericConfig = "api-actions-get-generic-config", GetOverviewData = "api-actions-get-overview-data", + GetFileManagerData = "api-actions-get-file-manager-data", GetGenericData = "api-actions-get-generic-data", GetAllFeatures = "api-actions-get-all-features", GetSingleFeature = "api-actions-get-single-feature", diff --git a/libs/generic-view/store/src/lib/features/get-file-manager-data.actions.ts b/libs/generic-view/store/src/lib/features/get-file-manager-data.actions.ts new file mode 100644 index 0000000000..13e0713a25 --- /dev/null +++ b/libs/generic-view/store/src/lib/features/get-file-manager-data.actions.ts @@ -0,0 +1,43 @@ +/** + * 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 { ReduxRootState } from "Core/__deprecated__/renderer/store" +import { DeviceId } from "Core/device/constants/device-id" +import { getFileManagerDataRequest } from "device/feature" +import { generateFileManagerData } from "generic-view/views" +import { selectConfiguredDevice } from "../selectors/select-configured-devices" +import { FeaturesActions } from "./featues-action-keys" + +export const getFileManagerData = createAsyncThunk< + { + deviceId: DeviceId + data: Record + }, + { deviceId: DeviceId }, + { state: ReduxRootState } +>( + FeaturesActions.GetFileManagerData, + async ({ deviceId }, { rejectWithValue, getState }) => { + const apiConfig = selectConfiguredDevice(getState(), deviceId)?.apiConfig + + const features = selectConfiguredDevice(getState(), deviceId)?.features + const fileManagerConfig = features?.["fileManager"]?.config + + if (apiConfig === undefined || fileManagerConfig === undefined) { + return rejectWithValue("no device") + } + + const response = await getFileManagerDataRequest(deviceId) + + if (response.ok) { + return { + deviceId, + data: generateFileManagerData(response.data, fileManagerConfig), + } + } + return rejectWithValue(response.error) + } +) 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..6b890f9ac2 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 { getFileManagerData } from "./get-file-manager-data.actions" export const getSingleFeatureData = createAsyncThunk< undefined, @@ -20,6 +21,9 @@ export const getSingleFeatureData = createAsyncThunk< case "mc-overview": await dispatch(getOverviewData({ deviceId })) break + case "fileManager": + await dispatch(getFileManagerData({ deviceId })) + break } 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..b5fe09ef64 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 { getFileManagerData } from "./get-file-manager-data.actions" export const getSingleFeatures = createAsyncThunk< undefined, @@ -23,6 +24,10 @@ export const getSingleFeatures = createAsyncThunk< await dispatch(getOverviewConfig({ deviceId })) await dispatch(getOverviewData({ deviceId })) break + case "fileManager": + await dispatch(getGenericConfig({ deviceId, feature })) + await dispatch(getFileManagerData({ deviceId })) + break default: await dispatch(getGenericConfig({ deviceId, feature })) break diff --git a/libs/generic-view/store/src/lib/views/reducer.ts b/libs/generic-view/store/src/lib/views/reducer.ts index 6bb5a03773..5a2da060dc 100644 --- a/libs/generic-view/store/src/lib/views/reducer.ts +++ b/libs/generic-view/store/src/lib/views/reducer.ts @@ -25,6 +25,7 @@ import { setMenu, } from "./actions" import { transformGenericComponents } from "../features/transform-generic-components" +import { getFileManagerData } from "../features/get-file-manager-data.actions" export interface GenericState { menu: MenuElement[] | undefined @@ -123,6 +124,17 @@ export const genericViewsReducer = createReducer(initialState, (builder) => { : {}), } }) + builder.addCase(getFileManagerData.fulfilled, (state, action) => { + state.lastResponse = action.payload + const deviceId = action.payload.deviceId + state.devices[deviceId].features = { + ...state.devices[deviceId].features, + fileManager: { + config: state.devices[deviceId].features?.["fileManager"]?.config, + data: action.payload.data, + }, + } + }) builder.addCase(getOverviewConfig.fulfilled, (state, action) => { state.lastResponse = action.payload const deviceId = action.payload.deviceId 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-view/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-view/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/texts/paragraphs.tsx b/libs/generic-view/ui/src/lib/texts/paragraphs.tsx index 87796df57e..75247011dc 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,23 @@ export const P2 = styled.p` ${commonStyles}; ` -export const Paragraph3: APIFC = ({ +export const Paragraph3: APIFC = ({ config, + data, children, ...props }) => { - const text = applyTextTransform( - config?.text, + console.log(data) + const text = data?.text ?? config?.text + + console.log(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 +147,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 +187,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} ) } diff --git a/libs/generic-view/views/src/index.ts b/libs/generic-view/views/src/index.ts index 8a05cc9909..8ec88d5fb1 100644 --- a/libs/generic-view/views/src/index.ts +++ b/libs/generic-view/views/src/index.ts @@ -5,3 +5,4 @@ export * from "./lib/mc-overview/mc-overview" export * from "./lib/mc-about/mc-about" +export * from "./lib/file-manager" diff --git a/libs/generic-view/views/src/lib/file-manager.ts b/libs/generic-view/views/src/lib/file-manager.ts new file mode 100644 index 0000000000..88fbb0eb3d --- /dev/null +++ b/libs/generic-view/views/src/lib/file-manager.ts @@ -0,0 +1,95 @@ +/** + * 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, 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 +) => { + 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, + } +}