diff --git a/packages/app/package-lock.json b/packages/app/package-lock.json index ff82a7174f..e59e35d5a9 100644 --- a/packages/app/package-lock.json +++ b/packages/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "@mudita/mudita-center-app", - "version": "2.0.2", + "version": "2.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/app/package.json b/packages/app/package.json index 5313fb95a4..40c0b160e4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@mudita/mudita-center-app", - "version": "2.0.2", + "version": "2.2.1", "description": "Mudita Center", "main": "./dist/main.js", "productName": "Mudita Center", diff --git a/packages/app/src/__deprecated__/renderer/locales/default/en-US.json b/packages/app/src/__deprecated__/renderer/locales/default/en-US.json index ce9e43b85a..27debd6337 100644 --- a/packages/app/src/__deprecated__/renderer/locales/default/en-US.json +++ b/packages/app/src/__deprecated__/renderer/locales/default/en-US.json @@ -855,7 +855,6 @@ "module.templates.emptyTemplate": "Empty template", "module.templates.modalTitle": "Use Template", "module.templates.newButton": "New template", - "module.templates.newLine": "The template contains new line character", "module.templates.newTemplate": "new", "module.templates.newTitle": "New template", "module.templates.orderError": "Reordering was not successful", diff --git a/packages/app/src/device/types/mudita-os/serialport-request.type.ts b/packages/app/src/device/types/mudita-os/serialport-request.type.ts index ed9fcb1202..e39ed87e64 100644 --- a/packages/app/src/device/types/mudita-os/serialport-request.type.ts +++ b/packages/app/src/device/types/mudita-os/serialport-request.type.ts @@ -329,7 +329,7 @@ export interface Thread { threadID: number } -export interface Template { +export interface PureTemplate { templateID: number lastUsedAt: number templateBody: string @@ -380,7 +380,7 @@ export interface GetTemplatesRequestConfig } export interface GetTemplatesResponseBody { - entries: Template[] + entries: PureTemplate[] totalCount: number nextPage?: PaginationBody } diff --git a/packages/app/src/feature-flags/features/index.ts b/packages/app/src/feature-flags/features/index.ts index 3b9eea9bd4..ccbfe290fd 100644 --- a/packages/app/src/feature-flags/features/index.ts +++ b/packages/app/src/feature-flags/features/index.ts @@ -6,7 +6,8 @@ import { EnvironmentConfig } from "App/feature-flags/types" import { Feature, Environment } from "App/feature-flags/constants" -const muditaCenterPrereleaseEnabled = process.env.MUDITA_CENTER_PRERELEASE_ENABLED === "1" +const muditaCenterPrereleaseEnabled = + process.env.MUDITA_CENTER_PRERELEASE_ENABLED === "1" const loggerEnabled = process.env.DEV_DEVICE_LOGGER_ENABLED !== "0" export const features: EnvironmentConfig = { @@ -127,7 +128,7 @@ export const features: EnvironmentConfig = { }, [Feature.OrderTemplate]: { [Environment.Development]: true, - [Environment.Production]: false, + [Environment.Production]: true, [Environment.AlphaProduction]: true, }, [Feature.AlphaRelaseWarning]: { diff --git a/packages/app/src/files-manager/actions/get-files.action.ts b/packages/app/src/files-manager/actions/get-files.action.ts index c9112d4ed0..f5ef488055 100644 --- a/packages/app/src/files-manager/actions/get-files.action.ts +++ b/packages/app/src/files-manager/actions/get-files.action.ts @@ -7,7 +7,7 @@ import { createAsyncThunk } from "@reduxjs/toolkit" import { FilesManagerEvent, DeviceDirectory, - EligibleFormat, + eligibleFormat, } from "App/files-manager/constants" import { getFilesRequest } from "App/files-manager/requests/get-files.request" import { File } from "App/files-manager/dto" @@ -17,7 +17,7 @@ export const getFiles = createAsyncThunk( async (payload, { rejectWithValue }) => { const result = await getFilesRequest({ directory: payload, - filter: { extensions: Object.values(EligibleFormat) }, + filter: { extensions: eligibleFormat }, }) if (!result.ok || !result.data) { diff --git a/packages/app/src/files-manager/actions/upload-file.action.test.ts b/packages/app/src/files-manager/actions/upload-file.action.test.ts index aa99b55fa2..2b923b6213 100644 --- a/packages/app/src/files-manager/actions/upload-file.action.test.ts +++ b/packages/app/src/files-manager/actions/upload-file.action.test.ts @@ -12,11 +12,8 @@ import { Result, SuccessResult } from "App/core/builder" import { AppError } from "App/core/errors" import { pendingAction } from "App/__deprecated__/renderer/store/helpers" import { testError } from "App/__deprecated__/renderer/store/constants" -import { getPathsRequest } from "App/file-system/requests" import { uploadFilesRequest } from "App/files-manager/requests" -import { EligibleFormat } from "App/files-manager/constants/eligible-format.constant" import { DeviceDirectory } from "App/files-manager/constants/device-directory.constant" -import { GetPathsInput } from "App/file-system/dto" import { uploadFile } from "App/files-manager/actions/upload-file.action" import { setUploadBlocked, @@ -44,8 +41,6 @@ jest.mock("App/device/actions/load-storage-info.action", () => ({ const pathsMock = ["/path/file-1.mp3", "/path/file-2.wav"] const errorMock = new AppError("SOME_ERROR_TYPE", "Luke, I'm your error") -const successGetPathResponse = new SuccessResult(pathsMock) -const failedGetPathResponse = Result.failed(errorMock) const successUploadResponse = new SuccessResult(pathsMock) const failedUploadResponse = Result.failed(errorMock) const initialStore = { @@ -57,98 +52,73 @@ const initialStore = { }, } -const getFilesPathsResponseMock: GetPathsInput = { - filters: [ - { - name: "Audio", - extensions: Object.values(EligibleFormat), - }, - ], - properties: ["openFile", "multiSelections"], -} - -describe("when `getPathRequest` request return Result.success with files list", () => { - describe("when `uploadFileRequest` request return Result.success with uploaded files list", () => { - beforeAll(() => { - ;(getPathsRequest as jest.Mock).mockResolvedValue(successGetPathResponse) - ;(uploadFilesRequest as jest.Mock).mockReturnValue(successUploadResponse) - ;(getFiles as unknown as jest.Mock).mockReturnValue(GET_FILES_MOCK_RESULT) - }) - - afterEach(() => { - jest.resetAllMocks() - }) +describe("when `uploadFileRequest` request return Result.success with uploaded files list", () => { + beforeAll(() => { + ;(uploadFilesRequest as jest.Mock).mockReturnValue(successUploadResponse) + ;(getFiles as unknown as jest.Mock).mockReturnValue(GET_FILES_MOCK_RESULT) + }) - test("dispatch `setUploadingState` with `State.Loaded` and `getFiles` with provided directory", async () => { - const mockStore = createMockStore([thunk])(initialStore) + afterEach(() => { + jest.resetAllMocks() + }) - const { - meta: { requestId }, - // AUTO DISABLED - fix me if you like :) - // eslint-disable-next-line @typescript-eslint/await-thenable - } = await mockStore.dispatch(uploadFile() as unknown as AnyAction) + test("dispatch `setUploadingState` with `State.Loaded` and `getFiles` with provided directory", async () => { + const mockStore = createMockStore([thunk])(initialStore) - expect(mockStore.getActions()).toEqual([ - uploadFile.pending(requestId), - setUploadBlocked(true), - setUploadingFileCount(2), - setUploadingState(State.Loading), - setUploadBlocked(false), - GET_FILES_MOCK_RESULT, - { - type: pendingAction("DEVICE_LOAD_STORAGE_INFO"), - payload: undefined, - }, + const { + meta: { requestId }, + // AUTO DISABLED - fix me if you like :) + // eslint-disable-next-line @typescript-eslint/await-thenable + } = await mockStore.dispatch(uploadFile(pathsMock) as unknown as AnyAction) - setUploadingState(State.Loaded), - uploadFile.fulfilled(undefined, requestId), - ]) + expect(mockStore.getActions()).toEqual([ + uploadFile.pending(requestId, pathsMock), + setUploadBlocked(true), + setUploadingFileCount(2), + setUploadingState(State.Loading), + GET_FILES_MOCK_RESULT, + { + type: pendingAction("DEVICE_LOAD_STORAGE_INFO"), + payload: undefined, + }, + setUploadingState(State.Loaded), + setUploadBlocked(false), + uploadFile.fulfilled(undefined, requestId, pathsMock), + ]) - expect(getPathsRequest).toHaveBeenLastCalledWith( - getFilesPathsResponseMock - ) - expect(uploadFilesRequest).toHaveBeenLastCalledWith({ - directory: DeviceDirectory.Music, - filePaths: pathsMock, - }) + expect(uploadFilesRequest).toHaveBeenLastCalledWith({ + directory: DeviceDirectory.Music, + filePaths: pathsMock, }) }) describe("when `uploadFileRequest` request return Result.success with empty files list", () => { - beforeAll(() => { - ;(getPathsRequest as jest.Mock).mockResolvedValue(Result.success([])) - }) - afterEach(() => { jest.resetAllMocks() }) - test("any action is dispatch", async () => { + test("Action is dispatched with empty array as a argument", async () => { const mockStore = createMockStore([thunk])(initialStore) const { meta: { requestId }, // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/await-thenable - } = await mockStore.dispatch(uploadFile() as unknown as AnyAction) + } = await mockStore.dispatch(uploadFile([]) as unknown as AnyAction) expect(mockStore.getActions()).toEqual([ - uploadFile.pending(requestId), + uploadFile.pending(requestId, []), setUploadBlocked(true), setUploadBlocked(false), - uploadFile.fulfilled(undefined, requestId), + uploadFile.rejected(null, requestId, [], "no files to upload"), ]) - expect(getPathsRequest).toHaveBeenLastCalledWith( - getFilesPathsResponseMock - ) expect(uploadFilesRequest).not.toHaveBeenCalled() }) }) describe("when `uploadFileRequest` request return Result.failed", () => { - beforeAll(() => { - ;(getPathsRequest as jest.Mock).mockResolvedValue(successGetPathResponse) + beforeEach(() => { ;(uploadFilesRequest as jest.Mock).mockReturnValue(failedUploadResponse) ;(getFiles as unknown as jest.Mock).mockReturnValue(GET_FILES_MOCK_RESULT) }) @@ -164,21 +134,20 @@ describe("when `getPathRequest` request return Result.success with files list", meta: { requestId }, // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/await-thenable - } = await mockStore.dispatch(uploadFile() as unknown as AnyAction) + } = await mockStore.dispatch( + uploadFile(pathsMock) as unknown as AnyAction + ) expect(mockStore.getActions()).toEqual([ - uploadFile.pending(requestId), + uploadFile.pending(requestId, pathsMock), setUploadBlocked(true), setUploadingFileCount(2), setUploadingState(State.Loading), - setUploadBlocked(false), GET_FILES_MOCK_RESULT, - uploadFile.rejected(testError, requestId, undefined, { ...errorMock }), + + uploadFile.rejected(testError, requestId, pathsMock, { ...errorMock }), ]) - expect(getPathsRequest).toHaveBeenLastCalledWith( - getFilesPathsResponseMock - ) expect(uploadFilesRequest).toHaveBeenLastCalledWith({ directory: DeviceDirectory.Music, filePaths: pathsMock, @@ -186,32 +155,3 @@ describe("when `getPathRequest` request return Result.success with files list", }) }) }) - -describe("when `getPathRequest` request return Result.failed", () => { - beforeAll(() => { - ;(getPathsRequest as jest.Mock).mockResolvedValue(failedGetPathResponse) - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - test("failed with receive from `uploadFileRequest` error", async () => { - const mockStore = createMockStore([thunk])(initialStore) - - const { - meta: { requestId }, - // AUTO DISABLED - fix me if you like :) - // eslint-disable-next-line @typescript-eslint/await-thenable - } = await mockStore.dispatch(uploadFile() as unknown as AnyAction) - - expect(mockStore.getActions()).toEqual([ - uploadFile.pending(requestId), - setUploadBlocked(true), - uploadFile.rejected(testError, requestId, undefined, { ...errorMock }), - ]) - - expect(getPathsRequest).toHaveBeenLastCalledWith(getFilesPathsResponseMock) - expect(uploadFilesRequest).not.toHaveBeenCalled() - }) -}) diff --git a/packages/app/src/files-manager/actions/upload-file.action.ts b/packages/app/src/files-manager/actions/upload-file.action.ts index 4b24844ee4..b0aa57f431 100644 --- a/packages/app/src/files-manager/actions/upload-file.action.ts +++ b/packages/app/src/files-manager/actions/upload-file.action.ts @@ -9,11 +9,9 @@ import { State } from "App/core/constants/state.constant" import { ReduxRootState } from "App/__deprecated__/renderer/store" import { FilesManagerEvent, - EligibleFormat, DeviceDirectory, FilesManagerError, } from "App/files-manager/constants" -import { getPathsRequest } from "App/file-system/requests" import { uploadFilesRequest } from "App/files-manager/requests" import { getFiles } from "App/files-manager/actions/get-files.action" import { @@ -32,21 +30,12 @@ import { checkFilesExtensions } from "../helpers/check-files-extensions.helper" export const uploadFile = createAsyncThunk< void, - void, + string[], { state: ReduxRootState } >( FilesManagerEvent.UploadFiles, - async (_, { getState, dispatch, rejectWithValue }) => { + async (filePaths, { getState, dispatch, rejectWithValue }) => { dispatch(setUploadBlocked(true)) - const filesToUpload = await getPathsRequest({ - filters: [ - { - name: "Audio", - extensions: Object.values(EligibleFormat), - }, - ], - properties: ["openFile", "multiSelections"], - }) const state = getState() @@ -58,15 +47,9 @@ export const uploadFile = createAsyncThunk< return rejectWithValue("files are not yet loaded") } - if (!filesToUpload.ok || !filesToUpload.data) { - return rejectWithValue(filesToUpload.error) - } - - const filePaths = filesToUpload.data ?? [] - - if (filePaths.length === 0) { + if (!filePaths || !filePaths.length) { dispatch(setUploadBlocked(false)) - return + return rejectWithValue("no files to upload") } const allFilesSupported = checkFilesExtensions(filePaths) @@ -125,7 +108,6 @@ export const uploadFile = createAsyncThunk< dispatch(setUploadingFileCount(filePaths.length)) dispatch(setUploadingState(State.Loading)) - dispatch(setUploadBlocked(false)) const directory = state.device.deviceType === DeviceType.MuditaHarmony @@ -145,6 +127,7 @@ export const uploadFile = createAsyncThunk< void dispatch(loadStorageInfoAction()) dispatch(setUploadingState(State.Loaded)) + dispatch(setUploadBlocked(false)) return } diff --git a/packages/app/src/files-manager/components/files-manager/files-manager.component.tsx b/packages/app/src/files-manager/components/files-manager/files-manager.component.tsx index 29823f296b..a4c736446d 100644 --- a/packages/app/src/files-manager/components/files-manager/files-manager.component.tsx +++ b/packages/app/src/files-manager/components/files-manager/files-manager.component.tsx @@ -4,7 +4,7 @@ */ import { DeviceType } from "App/device/constants" -import React, { useEffect, useState } from "react" +import React, { useEffect, useRef, useState } from "react" import { State } from "App/core/constants" import { FunctionComponent } from "App/__deprecated__/renderer/types/function-component.interface" import { FilesManagerContainer } from "App/files-manager/components/files-manager/files-manager.styled" @@ -18,6 +18,7 @@ import { FilesManagerTestIds } from "App/files-manager/components/files-manager/ import { DeviceDirectory, DiskSpaceCategoryType, + eligibleFormat, filesSummaryElements, } from "App/files-manager/constants" import FilesStorage from "App/files-manager/components/files-storage/files-storage.component" @@ -30,7 +31,6 @@ import { useDispatch } from "react-redux" import { resetFiles } from "App/files-manager/actions/base.action" import { uploadFile } from "App/files-manager/actions" import { Dispatch } from "App/__deprecated__/renderer/store" -import { noop } from "App/__deprecated__/renderer/utils/noop" const FilesManager: FunctionComponent = ({ memorySpace = { @@ -62,6 +62,7 @@ const FilesManager: FunctionComponent = ({ abortPendingUpload, continuePendingUpload, }) => { + const fileInputRef = useRef(null) const { noFoundFiles, searchValue, filteredFiles, handleSearchValueChange } = useFilesFilter({ files: files ?? [] }) const { states, updateFieldState } = useLoadingState({ @@ -85,10 +86,7 @@ const FilesManager: FunctionComponent = ({ const dispatch = useDispatch() const dispatchThunk = useDispatch() - const [uploadActionTrigger, setUploadActionTrigger] = useState() - const disableUpload = uploadBlocked ? uploadBlocked : freeSpace === 0 - const downloadFiles = () => { // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/await-thenable @@ -196,21 +194,15 @@ const FilesManager: FunctionComponent = ({ } }, [resetUploadingState]) - useEffect(() => { - if (uploadActionTrigger) { - const uploadActionPromise = dispatchThunk(uploadFile()) - - //abort on dismount or when a new upload has been triggered - return () => { - if ("abort" in uploadActionPromise) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any - ;(uploadActionPromise as any).abort() - } - } + const onFileInputChange = () => { + if (fileInputRef.current?.files?.length) { + void dispatchThunk( + uploadFile( + Array.from(fileInputRef.current?.files).map((file) => file.path) + ) + ) } - return noop - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uploadActionTrigger]) + } const getDiskSpaceCategories = (element: DiskSpaceCategory) => { const elements = { @@ -275,7 +267,7 @@ const FilesManager: FunctionComponent = ({ } const handleUploadFiles = () => { - setUploadActionTrigger(new Date().getTime()) + fileInputRef.current?.click() } return ( @@ -328,6 +320,14 @@ const FilesManager: FunctionComponent = ({ deviceType={deviceType} /> )} + `audio/${format}`).join(",")} + hidden + multiple + onChange={onFileInputChange} + /> ) } diff --git a/packages/app/src/files-manager/constants/eligible-format.constant.ts b/packages/app/src/files-manager/constants/eligible-format.constant.ts index 04c3e0aaa4..da7793b5b5 100644 --- a/packages/app/src/files-manager/constants/eligible-format.constant.ts +++ b/packages/app/src/files-manager/constants/eligible-format.constant.ts @@ -3,8 +3,4 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -export enum EligibleFormat { - MP3 = "mp3", - WAV = "wav", - FLAC = "flac", -} +export const eligibleFormat = ["mp3", "wav", "flac"] diff --git a/packages/app/src/files-manager/helpers/check-files-extensions.helper.test.ts b/packages/app/src/files-manager/helpers/check-files-extensions.helper.test.ts index f817d5ceec..67adbfa03a 100644 --- a/packages/app/src/files-manager/helpers/check-files-extensions.helper.test.ts +++ b/packages/app/src/files-manager/helpers/check-files-extensions.helper.test.ts @@ -4,11 +4,9 @@ */ import { checkFilesExtensions } from "App/files-manager/helpers/check-files-extensions.helper" -import { EligibleFormat } from "App/files-manager/constants/eligible-format.constant" +import { eligibleFormat } from "App/files-manager/constants/eligible-format.constant" -const supportedFiles = (Object.values(EligibleFormat) as string[]).map( - (extension) => `file.${extension}` -) +const supportedFiles = eligibleFormat.map((extension) => `file.${extension}`) describe("`checkFilesExtensions` helper", () => { test("all correct extensions", () => { diff --git a/packages/app/src/files-manager/helpers/check-files-extensions.helper.ts b/packages/app/src/files-manager/helpers/check-files-extensions.helper.ts index c2afab28c9..1b035b3cfa 100644 --- a/packages/app/src/files-manager/helpers/check-files-extensions.helper.ts +++ b/packages/app/src/files-manager/helpers/check-files-extensions.helper.ts @@ -3,11 +3,11 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -import { EligibleFormat } from "App/files-manager/constants/eligible-format.constant" +import { eligibleFormat } from "App/files-manager/constants/eligible-format.constant" export const checkFilesExtensions = (filesPaths: string[]): boolean => { return filesPaths.every((filePath) => { - return (Object.values(EligibleFormat) as string[]).includes( + return eligibleFormat.includes( (filePath.split(".").pop() ?? "").toLocaleLowerCase() ) }) diff --git a/packages/app/src/messages/messages.container.tsx b/packages/app/src/messages/messages.container.tsx index d2a60b1527..30ccb11437 100644 --- a/packages/app/src/messages/messages.container.tsx +++ b/packages/app/src/messages/messages.container.tsx @@ -49,6 +49,7 @@ import { CreateMessageDataResponse } from "App/messages/services" import { PayloadAction } from "@reduxjs/toolkit" import { search, searchPreview } from "App/search/actions" import { SearchParams } from "App/search/dto" +import { templatesListSelector } from "App/templates/selectors" const mapStateToProps = (state: RootState & ReduxRootState) => ({ error: state.messages.error, @@ -70,7 +71,7 @@ const mapStateToProps = (state: RootState & ReduxRootState) => ({ NotificationResourceType.Message, NotificationMethod.Layout )(state), - templates: state.templates.data, + templates: templatesListSelector(state), selectedItems: state.messages.selectedItems, searchResult: state.messages.data.searchResult, searchPreviewResult: state.messages.data.searchPreviewResult, diff --git a/packages/app/src/overview/components/overview-screens/pure-overview/pure-overview.component.tsx b/packages/app/src/overview/components/overview-screens/pure-overview/pure-overview.component.tsx index a439adbb16..1a1eeafa6a 100644 --- a/packages/app/src/overview/components/overview-screens/pure-overview/pure-overview.component.tsx +++ b/packages/app/src/overview/components/overview-screens/pure-overview/pure-overview.component.tsx @@ -224,27 +224,29 @@ export const PureOverview: FunctionComponent = ({ return ( <> - + {!forceUpdateNeeded && ( + + )} {flags.get(Feature.ForceUpdate) && ( = ({ onClose, open, testId, fileSize, ...rest }) => { + const requirerSize = Math.ceil(fileSize / 1000000) * 3 + return ( {intl.formatMessage(messages.updatingNotEnoughSpaceDescription, { - value: Math.ceil(fileSize / 1000000), + value: requirerSize, })} diff --git a/packages/app/src/settings/components/about/about-ui.component.tsx b/packages/app/src/settings/components/about/about-ui.component.tsx index e96f8752c7..0b018fab2d 100644 --- a/packages/app/src/settings/components/about/about-ui.component.tsx +++ b/packages/app/src/settings/components/about/about-ui.component.tsx @@ -3,7 +3,7 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -import React from "react" +import React, { useState, useEffect } from "react" import { FunctionComponent } from "App/__deprecated__/renderer/types/function-component.interface" import { ActionsWrapper } from "App/__deprecated__/renderer/components/rest/messages/threads-table.component" import { TextDisplayStyle } from "App/__deprecated__/renderer/components/core/text/text.component" @@ -29,6 +29,7 @@ import { UpdateFailedModal } from "App/settings/components/about/update-failed-m import { AboutLoaderModal } from "App/settings/components/about/about-loader.component" import { ModalLayers } from "App/modals-manager/constants/modal-layers.enum" import { useOnlineChecker } from "App/settings/hooks/use-online-checker" +import registerErrorAppUpdateListener from "App/__deprecated__/main/functions/register-error-app-update-listener" const AvailableUpdate = styled(Text)` margin-top: 0.8rem; @@ -86,25 +87,52 @@ const AboutUI: FunctionComponent = ({ appUpdateFailedShow, hideAppUpdateFailed, }) => { + const [updateCheck, setUpdateCheck] = useState(false) const online = useOnlineChecker() + const appUpdateAvailableCheckHandler = () => { + setUpdateCheck(true) + onAppUpdateAvailableCheck() + } + const hideAppUpdateNotAvailableHandler = () => { + setUpdateCheck(false) + hideAppUpdateNotAvailable() + } + const hideAppUpdateFailedHandler = () => { + setUpdateCheck(false) + hideAppUpdateNotAvailable() + hideAppUpdateFailed() + } + + const showUpToDateModal = + updateCheck && !appUpdateFailedShow && !checkingForUpdate + + useEffect(() => { + const unregister = registerErrorAppUpdateListener(() => { + setUpdateCheck(false) + }) + return () => unregister() + }, []) + return ( <> - + {showUpToDateModal && ( + + )} @@ -128,7 +156,7 @@ const AboutUI: FunctionComponent = ({ id: "module.overview.systemCheckForUpdates", }} data-testid={AboutTestIds.UpdateButton} - onClick={onAppUpdateAvailableCheck} + onClick={appUpdateAvailableCheckHandler} /> )} @@ -147,7 +175,7 @@ const AboutUI: FunctionComponent = ({ labelMessage={{ id: "module.settings.aboutAppUpdateAction", }} - onClick={onAppUpdateAvailableCheck} + onClick={appUpdateAvailableCheckHandler} data-testid={AboutTestIds.UpdateButton} /> @@ -165,7 +193,7 @@ const AboutUI: FunctionComponent = ({ id: "module.overview.systemCheckForUpdates", }} data-testid={AboutTestIds.UpdateButton} - onClick={onAppUpdateAvailableCheck} + onClick={appUpdateAvailableCheckHandler} /> )} diff --git a/packages/app/src/settings/components/about/about.component.tsx b/packages/app/src/settings/components/about/about.component.tsx index 9512a1ec47..f1189b3a5e 100644 --- a/packages/app/src/settings/components/about/about.component.tsx +++ b/packages/app/src/settings/components/about/about.component.tsx @@ -45,7 +45,7 @@ export const About: FunctionComponent = ({ useEffect(() => { setAppUpdateNotAvailableShow(updateAvailable === false) - }, [updateAvailable]) + }, [updateAvailable, checkingForUpdate]) const handleAppUpdateAvailableCheck = (): void => { if (!window.navigator.onLine) { diff --git a/packages/app/src/settings/components/about/about.test.tsx b/packages/app/src/settings/components/about/about.test.tsx index 2c1d2d7165..4f8a5fd06c 100644 --- a/packages/app/src/settings/components/about/about.test.tsx +++ b/packages/app/src/settings/components/about/about.test.tsx @@ -8,12 +8,15 @@ import "@testing-library/jest-dom/extend-expect" import React, { ComponentProps } from "react" import AboutUI from "./about-ui.component" import { noop } from "App/__deprecated__/renderer/utils/noop" -import { AboutTestIds } from "./about.enum" +import { AboutTestIds } from "App/settings/components/about/about.enum" import { fireEvent, screen } from "@testing-library/dom" import { AppUpdateStepModalTestIds } from "App/__deprecated__/renderer/wrappers/app-update-step-modal/app-update-step-modal-test-ids.enum" import { flags } from "App/feature-flags" jest.mock("App/feature-flags") +jest.mock( + "App/__deprecated__/main/functions/register-error-app-update-listener" +) type Props = ComponentProps const defaultProps: Props = { @@ -55,12 +58,14 @@ test("renders at least one table row", () => { }) test("Opens update modal properly when app update is not available", () => { - renderer({ + const { getByTestId } = renderer({ appLatestVersion: "0.20.2", appCurrentVersion: "0.20.2", appUpdateNotAvailableShow: true, }) + getByTestId(AboutTestIds.UpdateButton).click() + expect( screen.getByTestId(AppUpdateStepModalTestIds.AppUpdateNotAvailableModal) ).toBeInTheDocument() diff --git a/packages/app/src/templates/components/templates/templates.component.tsx b/packages/app/src/templates/components/templates/templates.component.tsx index 76cbb4c962..d8bca4772b 100644 --- a/packages/app/src/templates/components/templates/templates.component.tsx +++ b/packages/app/src/templates/components/templates/templates.component.tsx @@ -181,28 +181,29 @@ export const Templates: FunctionComponent = ({ const handleCreateTemplate = async (template: NewTemplate) => { updateFieldState("creating", true) setTemplateFormOpenState(false) - const lastTemplateOrder = templates[templates.length - 1]?.order - const newTemplateOrder = lastTemplateOrder ? lastTemplateOrder + 1 : 1 - await createTemplate({ ...template, order: newTemplateOrder }) + await createTemplate(template) } const handleCloseCreatingErrorModal = () => { updateFieldState("creating", false) } - const onDragEnd = (result: DropResult) => { - updateFieldState("updatingOrder", true) - if (!result.destination) { - return - } + const onDragEnd = ({ source, destination }: DropResult) => { + if (source.index !== destination?.index) { + updateFieldState("updatingOrder", true) + if (!destination) { + return + } - const list = Array.from(templatesList) - const [removed] = list.splice(result.source.index, 1) - list.splice(result.destination.index, 0, removed) - setTemplatesList(list) - const updatedTemplates = reorder(list) - void updateTemplateOrder(updatedTemplates) + const list = Array.from(templatesList) + const [removed] = list.splice(source.index, 1) + list.splice(destination.index, 0, removed) + setTemplatesList(list) + const updatedTemplates = reorder(list) + void updateTemplateOrder(updatedTemplates) + } } + return ( <> { const response = await subject.createTemplate(newTemplate) // AUTO DISABLED - fix me if you like :) // eslint-disable-next-line @typescript-eslint/unbound-method - expect(deviceManager.device.request).toHaveBeenLastCalledWith({ + expect(deviceManager.device.request).toHaveBeenNthCalledWith(1, { endpoint: Endpoint.Messages, method: Method.Post, body: { diff --git a/packages/app/src/templates/services/template.service.ts b/packages/app/src/templates/services/template.service.ts index 29f999d0cd..66202ec75b 100644 --- a/packages/app/src/templates/services/template.service.ts +++ b/packages/app/src/templates/services/template.service.ts @@ -5,12 +5,11 @@ import { NewTemplate, Template } from "App/templates/dto" import { DeviceManager } from "App/device-manager/services" +import { Endpoint, Method, MessagesCategory } from "App/device/constants" import { - Endpoint, - Method, - MessagesCategory as PureMessagesCategory, -} from "App/device/constants" -import { CreateTemplateResponseBody } from "App/device/types/mudita-os" + CreateTemplateResponseBody, + PureTemplate, +} from "App/device/types/mudita-os" import { RequestResponse, RequestResponseStatus, @@ -31,33 +30,41 @@ export class TemplateService { public async createTemplate( template: NewTemplate ): Promise> { - const response = + const createResponse = await this.deviceManager.device.request({ endpoint: Endpoint.Messages, method: Method.Post, body: TemplatePresenter.mapToPureNewTemplateBody(template), }) - if (response.ok && response.data) { - const templateData = TemplatePresenter.mapToTemplate({ - ...response.data, - templateBody: template.text, - order: response.data.templateID, - lastUsedAt: 0, - }) - - this.templateRepository.create(templateData) - - return { - status: RequestResponseStatus.Ok, - data: templateData, - } - } else { - return { - status: RequestResponseStatus.Error, - error: { message: "Create template: Something went wrong" }, + if (createResponse.ok && createResponse.data) { + const getResponse = await this.deviceManager.device.request( + { + endpoint: Endpoint.Messages, + method: Method.Get, + body: { + templateID: createResponse.data.templateID, + category: MessagesCategory.template, + }, + } + ) + + if (getResponse.ok && getResponse.data) { + const templateData = TemplatePresenter.mapToTemplate(getResponse.data) + + this.templateRepository.create(templateData) + + return { + status: RequestResponseStatus.Ok, + data: templateData, + } } } + + return { + status: RequestResponseStatus.Error, + error: { message: "Create template: Something went wrong" }, + } } public async deleteTemplates( @@ -68,7 +75,7 @@ export class TemplateService { endpoint: Endpoint.Messages, method: Method.Delete, body: { - category: PureMessagesCategory.template, + category: MessagesCategory.template, templateID: Number(id), }, }) diff --git a/packages/app/src/update/services/device-update.service.test.ts b/packages/app/src/update/services/device-update.service.test.ts index 7e7995d204..e1706d09fb 100644 --- a/packages/app/src/update/services/device-update.service.test.ts +++ b/packages/app/src/update/services/device-update.service.test.ts @@ -77,7 +77,7 @@ beforeEach(() => { } as unknown as DeviceFileSystemService deviceInfoService = { - getDeviceFreeSpace: jest.fn().mockReturnValue({ ok: true, data: 2 }), + getDeviceFreeSpace: jest.fn().mockReturnValue({ ok: true, data: 3 }), getDeviceInfo: jest .fn() .mockReturnValue(Result.success(deviceInfoResponseMock)), diff --git a/packages/app/src/update/services/device-update.service.ts b/packages/app/src/update/services/device-update.service.ts index 64be63caf4..5c000dff51 100644 --- a/packages/app/src/update/services/device-update.service.ts +++ b/packages/app/src/update/services/device-update.service.ts @@ -63,12 +63,13 @@ export class DeviceUpdateService { await this.deviceFileSystem.removeDeviceFile(targetPath) const fileSizeInMB = fs.lstatSync(filePath).size / (1024 * 1024) + const requirerSizeInMB = fileSizeInMB * 3 const freeSpaceResult = await this.deviceInfoService.getDeviceFreeSpace() if ( freeSpaceResult.ok && !isNaN(freeSpaceResult.data) && - freeSpaceResult.data < fileSizeInMB + freeSpaceResult.data < requirerSizeInMB ) { return Result.failed( new AppError(