From 86528f49f62bbfba6b3bd70d072cdbeae3123cb8 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 14:21:53 +0000 Subject: [PATCH 01/15] Removed unused multiple paths logic --- .../Dialogs/DeleteFilesFolders.tsx | 38 +++++-------------- client/store/actions/file.ts | 6 --- client/store/state.ts | 5 --- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/client/components/Application/Dialogs/DeleteFilesFolders.tsx b/client/components/Application/Dialogs/DeleteFilesFolders.tsx index 69601e402..b19527d89 100644 --- a/client/components/Application/Dialogs/DeleteFilesFolders.tsx +++ b/client/components/Application/Dialogs/DeleteFilesFolders.tsx @@ -1,48 +1,30 @@ -import TwoButtonDialog from '../../Parts/Dialogs/TwoButton' import * as store from '@client/store' +import TwoButtonDialog from '../../Parts/Dialogs/TwoButton' export default function DeleteFilesFoldersDialog() { const path = store.useStore((state) => state.path) - const files = store.useStore((state) => state.files) - const selectedMultiplePaths = store.useStore((state) => state.selectedMultiplePaths) const isFolder = store.useStore(store.getIsFolder) - const selectedFolders = files - .filter((file) => { - return selectedMultiplePaths?.includes(file.path) && file.type === 'folder' - }) - .map((file) => file.path) - - const selectedFiles = files - .filter((file) => { - return selectedMultiplePaths?.includes(file.path) && file.type !== 'folder' - }) - .map((file) => file.path) - if (!path) return null + const description = `Are you sure you want to delete this ${ + isFolder ? 'folder' : 'file' + }?` + return ( { - if (selectedMultiplePaths) { - if (selectedFolders.length > 0) await store.deleteFolders(selectedFolders) - if (selectedFiles.length > 0) await store.deleteFiles(selectedFiles) - } - // is only one selected file - else { - if (isFolder) await store.deleteFolders([path]) - else await store.deleteFiles([path]) + if (isFolder) { + await store.deleteFolders([path]) + } else { + await store.deleteFiles([path]) } store.closeDialog() }} diff --git a/client/store/actions/file.ts b/client/store/actions/file.ts index f639a025d..7c8132b3f 100644 --- a/client/store/actions/file.ts +++ b/client/store/actions/file.ts @@ -123,12 +123,6 @@ export async function deselectFile() { await selectFile({ path: undefined }) } -export async function selectMultipleFiles(paths: string[]) { - store.setState('select-multiple-files', (state) => { - state.selectedMultiplePaths = paths - }) -} - export async function copyFile(path: string, toPath: string) { const result = await client.fileCopy({ path, toPath, deduplicate: true }) diff --git a/client/store/state.ts b/client/store/state.ts index d84f04000..515f471a6 100644 --- a/client/store/state.ts +++ b/client/store/state.ts @@ -12,11 +12,6 @@ export type IState = { **/ path?: string - /** - * Keeps track of the selected multiple files/folders paths for deletion - **/ - selectedMultiplePaths?: string[] - /** * A recored desribing currently selected file if any is selected **/ From fe025451bc123b345d875f32afd4eb32c247dd74 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 14:25:09 +0000 Subject: [PATCH 02/15] Extracted DeleteFiles dialog --- client/components/Application/Dialog.tsx | 4 +- .../Dialogs/DeleteFiles/DeleteFiles.store.ts | 63 +++++++++++++++++++ .../DeleteFiles.tsx} | 4 +- .../Application/Dialogs/DeleteFiles/index.ts | 1 + 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts rename client/components/Application/Dialogs/{DeleteFilesFolders.tsx => DeleteFiles/DeleteFiles.tsx} (86%) create mode 100644 client/components/Application/Dialogs/DeleteFiles/index.ts diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 3e8578978..898ab5009 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -4,7 +4,7 @@ import CloseWithUnsavedChangesDialog from './Dialogs/CloseWithUnsavedChanges' import ConfigDialog from './Dialogs/Config' import CopyFileDialog from './Dialogs/CopyFile' import CopyFolderDialog from './Dialogs/CopyFolder' -import DeleteFilesFoldersDialog from './Dialogs/DeleteFilesFolders' +import { DeleteFilesDialog } from './Dialogs/DeleteFiles' import { FileUploadDialog } from './Dialogs/FileUpload' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' @@ -31,7 +31,7 @@ const DIALOGS = { configProject: ConfigDialog, copyFile: CopyFileDialog, copyFolder: CopyFolderDialog, - deleteFilesFolders: DeleteFilesFoldersDialog, + deleteFilesFolders: DeleteFilesDialog, publish: PublishDialog, unsavedChanges: UnsavedChangesDialog, welcomeBanner: WelcomeBannerDialog, diff --git a/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts new file mode 100644 index 000000000..53212b2e7 --- /dev/null +++ b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts @@ -0,0 +1,63 @@ +import { client } from '@client/client' +import * as helpers from '@client/helpers' +import * as appStore from '@client/store' + +// We use component level state because dialog state +// needs to be shared between multiple components +// but it is not needed in the global state +class State { + progress?: IProgress +} + +type IProgress = { + type: 'loading' | 'error' + title?: string + message?: string + blocking?: boolean +} + +export const { state, useState } = helpers.createState('DeleteFiles', new State()) + +export async function saveChanges() { + const { grid } = appStore.getRefs() + const { path, resource, table } = appStore.getState() + if (!path || !grid || !table) return + + appStore.openDialog('saveChanges') + state.progress = { + type: 'loading', + title: 'Saving the updated table', + message: 'If the file is large, this may take some time...', + blocking: true, + } + + const appState = appStore.getState() + const isTableUpdated = appStore.getIsTableUpdated(appState) + const isResourceUpdated = appState.isResourceUpdated + + const result = await client.tablePatch({ + path, + history: isTableUpdated ? table.history : undefined, + resource: isResourceUpdated ? resource : undefined, + }) + + if (result instanceof client.Error) { + state.progress = { + type: 'error', + title: 'Error saving changes', + message: result.detail, + } + } + + await appStore.onFileUpdated([path]) + grid.reload() + + state.progress = undefined + closeDialog() +} + +export function closeDialog() { + if (!state.progress?.blocking) { + appStore.closeDialog() + } +} diff --git a/client/components/Application/Dialogs/DeleteFilesFolders.tsx b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx similarity index 86% rename from client/components/Application/Dialogs/DeleteFilesFolders.tsx rename to client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx index b19527d89..35418891b 100644 --- a/client/components/Application/Dialogs/DeleteFilesFolders.tsx +++ b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx @@ -1,7 +1,7 @@ +import TwoButtonDialog from '@client/components/Parts/Dialogs/TwoButton' import * as store from '@client/store' -import TwoButtonDialog from '../../Parts/Dialogs/TwoButton' -export default function DeleteFilesFoldersDialog() { +export function DeleteFilesDialog() { const path = store.useStore((state) => state.path) const isFolder = store.useStore(store.getIsFolder) diff --git a/client/components/Application/Dialogs/DeleteFiles/index.ts b/client/components/Application/Dialogs/DeleteFiles/index.ts new file mode 100644 index 000000000..a39798a82 --- /dev/null +++ b/client/components/Application/Dialogs/DeleteFiles/index.ts @@ -0,0 +1 @@ +export { DeleteFilesDialog } from './DeleteFiles' From 3e73117da48ae730b993e58ab524da413ad0f74b Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 14:56:11 +0000 Subject: [PATCH 03/15] Improved loading indication --- client/components/Application/Dialog.tsx | 4 +- .../Dialogs/DeleteFile/DeleteFile.tsx | 36 +++++++++++ .../Application/Dialogs/DeleteFile/index.ts | 1 + .../Application/Dialogs/DeleteFile/store.ts | 46 ++++++++++++++ .../Dialogs/DeleteFiles/DeleteFiles.store.ts | 63 ------------------- .../Dialogs/DeleteFiles/DeleteFiles.tsx | 33 ---------- .../Application/Dialogs/DeleteFiles/index.ts | 1 - .../Dialogs/FileUpload/FileUpload.tsx | 41 ++---------- .../{FileUpload.store.tsx => store.tsx} | 13 +--- .../Dialogs/SaveChanges/SaveChanges.tsx | 39 ++---------- .../Application/Dialogs/SaveChanges/index.ts | 2 +- .../{SaveChanges.store.ts => store.ts} | 13 +--- client/components/Parts/Progress/Linear.tsx | 32 ++++++++++ client/store/actions/file.ts | 14 ----- client/store/actions/folder.ts | 18 +----- client/types/index.ts | 5 +- client/types/progress.ts | 6 ++ 17 files changed, 142 insertions(+), 225 deletions(-) create mode 100644 client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx create mode 100644 client/components/Application/Dialogs/DeleteFile/index.ts create mode 100644 client/components/Application/Dialogs/DeleteFile/store.ts delete mode 100644 client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts delete mode 100644 client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx delete mode 100644 client/components/Application/Dialogs/DeleteFiles/index.ts rename client/components/Application/Dialogs/FileUpload/{FileUpload.store.tsx => store.tsx} (91%) rename client/components/Application/Dialogs/SaveChanges/{SaveChanges.store.ts => store.ts} (82%) create mode 100644 client/components/Parts/Progress/Linear.tsx create mode 100644 client/types/progress.ts diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 898ab5009..1747d6760 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -4,7 +4,7 @@ import CloseWithUnsavedChangesDialog from './Dialogs/CloseWithUnsavedChanges' import ConfigDialog from './Dialogs/Config' import CopyFileDialog from './Dialogs/CopyFile' import CopyFolderDialog from './Dialogs/CopyFolder' -import { DeleteFilesDialog } from './Dialogs/DeleteFiles' +import { DeleteFileDialog } from './Dialogs/DeleteFile' import { FileUploadDialog } from './Dialogs/FileUpload' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' @@ -31,7 +31,7 @@ const DIALOGS = { configProject: ConfigDialog, copyFile: CopyFileDialog, copyFolder: CopyFolderDialog, - deleteFilesFolders: DeleteFilesDialog, + deleteFilesFolders: DeleteFileDialog, publish: PublishDialog, unsavedChanges: UnsavedChangesDialog, welcomeBanner: WelcomeBannerDialog, diff --git a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx new file mode 100644 index 000000000..4ddec19db --- /dev/null +++ b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx @@ -0,0 +1,36 @@ +import TwoButtonDialog from '@client/components/Parts/Dialogs/TwoButton' +import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as appStore from '@client/store' +import * as React from 'react' +import * as store from './store' + +export function DeleteFileDialog() { + const isFolder = appStore.useStore(appStore.getIsFolder) + const dialog = appStore.useStore((state) => state.dialog) + const { progress } = store.useState() + + React.useEffect(() => { + // TODO: rebase on resetState when it is available after merging: + // https://github.com/okfn/opendataeditor/pull/650 + store.state.progress = undefined + }, [dialog]) + + const description = !progress + ? `Are you sure you want to delete this ${isFolder ? 'folder' : 'file'}?` + : undefined + + return ( + + + + ) +} diff --git a/client/components/Application/Dialogs/DeleteFile/index.ts b/client/components/Application/Dialogs/DeleteFile/index.ts new file mode 100644 index 000000000..a31cbe1b5 --- /dev/null +++ b/client/components/Application/Dialogs/DeleteFile/index.ts @@ -0,0 +1 @@ +export { DeleteFileDialog } from './DeleteFile' diff --git a/client/components/Application/Dialogs/DeleteFile/store.ts b/client/components/Application/Dialogs/DeleteFile/store.ts new file mode 100644 index 000000000..0342947c2 --- /dev/null +++ b/client/components/Application/Dialogs/DeleteFile/store.ts @@ -0,0 +1,46 @@ +import { client } from '@client/client' +import * as helpers from '@client/helpers' +import * as appStore from '@client/store' +import * as types from '@client/types' + +class State { + progress?: types.IProgress +} + +export const { state, useState } = helpers.createState('DeleteFile', new State()) + +export function closeDialog() { + if (!state.progress?.blocking) { + appStore.closeDialog() + } +} + +export async function deleteFile() { + const isFolder = appStore.getIsFolder(appStore.getState()) + const { path } = appStore.getState() + if (!path) return + + const target = isFolder ? 'folder' : 'file' + + state.progress = { + type: 'deleting', + title: `Deleting selected ${target}`, + blocking: true, + } + + const result = isFolder + ? await client.folderDelete({ path }) + : await client.fileDelete({ path }) + + if (result instanceof client.Error) { + state.progress = { + type: 'error', + title: `Error deleting the ${target}`, + message: result.detail, + } + } + + state.progress = undefined + appStore.onFileDeleted([path]) + closeDialog() +} diff --git a/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts deleted file mode 100644 index 53212b2e7..000000000 --- a/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.store.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { client } from '@client/client' -import * as helpers from '@client/helpers' -import * as appStore from '@client/store' - -// We use component level state because dialog state -// needs to be shared between multiple components -// but it is not needed in the global state -class State { - progress?: IProgress -} - -type IProgress = { - type: 'loading' | 'error' - title?: string - message?: string - blocking?: boolean -} - -export const { state, useState } = helpers.createState('DeleteFiles', new State()) - -export async function saveChanges() { - const { grid } = appStore.getRefs() - const { path, resource, table } = appStore.getState() - if (!path || !grid || !table) return - - appStore.openDialog('saveChanges') - state.progress = { - type: 'loading', - title: 'Saving the updated table', - message: 'If the file is large, this may take some time...', - blocking: true, - } - - const appState = appStore.getState() - const isTableUpdated = appStore.getIsTableUpdated(appState) - const isResourceUpdated = appState.isResourceUpdated - - const result = await client.tablePatch({ - path, - history: isTableUpdated ? table.history : undefined, - resource: isResourceUpdated ? resource : undefined, - }) - - if (result instanceof client.Error) { - state.progress = { - type: 'error', - title: 'Error saving changes', - message: result.detail, - } - } - - await appStore.onFileUpdated([path]) - grid.reload() - - state.progress = undefined - closeDialog() -} - -export function closeDialog() { - if (!state.progress?.blocking) { - appStore.closeDialog() - } -} diff --git a/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx b/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx deleted file mode 100644 index 35418891b..000000000 --- a/client/components/Application/Dialogs/DeleteFiles/DeleteFiles.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import TwoButtonDialog from '@client/components/Parts/Dialogs/TwoButton' -import * as store from '@client/store' - -export function DeleteFilesDialog() { - const path = store.useStore((state) => state.path) - const isFolder = store.useStore(store.getIsFolder) - - if (!path) return null - - const description = `Are you sure you want to delete this ${ - isFolder ? 'folder' : 'file' - }?` - - return ( - { - if (isFolder) { - await store.deleteFolders([path]) - } else { - await store.deleteFiles([path]) - } - store.closeDialog() - }} - /> - ) -} diff --git a/client/components/Application/Dialogs/DeleteFiles/index.ts b/client/components/Application/Dialogs/DeleteFiles/index.ts deleted file mode 100644 index a39798a82..000000000 --- a/client/components/Application/Dialogs/DeleteFiles/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { DeleteFilesDialog } from './DeleteFiles' diff --git a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx index 815e6985a..703afe1b7 100644 --- a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx +++ b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx @@ -5,19 +5,17 @@ import iconUploadFileImg from '@client/assets/icon_upload_file.png' import DialogTabs from '@client/components//Parts/Tabs/Dialog' import SimpleButton from '@client/components/Parts/Buttons/SimpleButton' import Columns from '@client/components/Parts/Grids/Columns' +import { LinearProgress } from '@client/components/Parts/Progress/Linear' import CloseIcon from '@mui/icons-material/Close' import Box from '@mui/material/Box' import Dialog from '@mui/material/Dialog' import DialogContent from '@mui/material/DialogContent' import IconButton from '@mui/material/IconButton' import InputAdornment from '@mui/material/InputAdornment' -import LinearProgress from '@mui/material/LinearProgress' -import Stack from '@mui/material/Stack' import TextField from '@mui/material/TextField' import { styled, useTheme } from '@mui/material/styles' -import { startCase } from 'lodash' import * as React from 'react' -import * as store from './FileUpload.store' +import * as store from './store' const TAB_LABELS = ['From your computer', 'Add external data'] @@ -67,11 +65,11 @@ export function FileUploadDialog() { - + - + @@ -150,37 +148,6 @@ function RemoteFileForm() { ) } -function ProgressIndicator() { - const { progress } = store.useState() - - if (!progress) { - return null - } - - if (progress.type === 'error') { - return ( - - {progress.message} - - ) - } - - return ( - - {progress.title || startCase(progress.type)}... - - {progress.message} - - ) -} - function AddRemoteTextField(props: { value?: string invalid?: boolean diff --git a/client/components/Application/Dialogs/FileUpload/FileUpload.store.tsx b/client/components/Application/Dialogs/FileUpload/store.tsx similarity index 91% rename from client/components/Application/Dialogs/FileUpload/FileUpload.store.tsx rename to client/components/Application/Dialogs/FileUpload/store.tsx index 627d5fd84..8a771ccb4 100644 --- a/client/components/Application/Dialogs/FileUpload/FileUpload.store.tsx +++ b/client/components/Application/Dialogs/FileUpload/store.tsx @@ -1,19 +1,10 @@ import { client } from '@client/client' import * as helpers from '@client/helpers' import * as appStore from '@client/store' +import * as types from '@client/types' -// We use component level state because dialog state -// needs to be shared between multiple components -// but it is not needed in the global state class State { - progress?: IProgress -} - -type IProgress = { - type: 'loading' | 'validating' | 'error' - title?: string - message?: string - blocking?: boolean + progress?: types.IProgress } type IFile = { diff --git a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx index 31d9c81dc..641082f5e 100644 --- a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx +++ b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx @@ -1,11 +1,10 @@ import NoButtonDialog from '@client/components/Parts/Dialogs/NoButton' -import Box from '@mui/material/Box' -import LinearProgress from '@mui/material/LinearProgress' -import Stack from '@mui/material/Stack' -import { startCase } from 'lodash' -import * as store from './SaveChanges.store' +import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as store from './store' export function SaveChangesDialog() { + const { progress } = store.useState() + return ( - + ) } - -// TODO: move to common components -function ProgressIndicator() { - const { progress } = store.useState() - - if (!progress) { - return null - } - - if (progress.type === 'error') { - return {progress.message} - } - - return ( - - {progress.title || startCase(progress.type)}... - - {progress.message} - - ) -} diff --git a/client/components/Application/Dialogs/SaveChanges/index.ts b/client/components/Application/Dialogs/SaveChanges/index.ts index dbffc1cfd..18656a0c9 100644 --- a/client/components/Application/Dialogs/SaveChanges/index.ts +++ b/client/components/Application/Dialogs/SaveChanges/index.ts @@ -1,2 +1,2 @@ export { SaveChangesDialog } from './SaveChanges' -export * as saveChangesDialog from './SaveChanges.store' +export * as saveChangesDialog from './store' diff --git a/client/components/Application/Dialogs/SaveChanges/SaveChanges.store.ts b/client/components/Application/Dialogs/SaveChanges/store.ts similarity index 82% rename from client/components/Application/Dialogs/SaveChanges/SaveChanges.store.ts rename to client/components/Application/Dialogs/SaveChanges/store.ts index 8c0b4fdb6..7e7033f5a 100644 --- a/client/components/Application/Dialogs/SaveChanges/SaveChanges.store.ts +++ b/client/components/Application/Dialogs/SaveChanges/store.ts @@ -1,19 +1,10 @@ import { client } from '@client/client' import * as helpers from '@client/helpers' import * as appStore from '@client/store' +import * as types from '@client/types' -// We use component level state because dialog state -// needs to be shared between multiple components -// but it is not needed in the global state class State { - progress?: IProgress -} - -type IProgress = { - type: 'loading' | 'error' - title?: string - message?: string - blocking?: boolean + progress?: types.IProgress } export const { state, useState } = helpers.createState('SaveChangesDialog', new State()) diff --git a/client/components/Parts/Progress/Linear.tsx b/client/components/Parts/Progress/Linear.tsx new file mode 100644 index 000000000..fbbe3138c --- /dev/null +++ b/client/components/Parts/Progress/Linear.tsx @@ -0,0 +1,32 @@ +import * as types from '@client/types' +import Box from '@mui/material/Box' +import MuiLinearProgress from '@mui/material/LinearProgress' +import Stack from '@mui/material/Stack' +import { startCase } from 'lodash' + +export function LinearProgress(props: { progress?: types.IProgress }) { + const { progress } = props + + if (!progress) { + return null + } + + if (progress.type === 'error') { + return {progress.message} + } + + return ( + + {progress.title || startCase(progress.type)}... + + {progress.message} + + ) +} diff --git a/client/store/actions/file.ts b/client/store/actions/file.ts index 7c8132b3f..13211bd74 100644 --- a/client/store/actions/file.ts +++ b/client/store/actions/file.ts @@ -157,20 +157,6 @@ export async function revertFile() { } } -export async function deleteFiles(paths: string[]) { - for (const path of paths) { - const result = await client.fileDelete({ path }) - - if (result instanceof client.Error) { - return store.setState('delete-files-error', (state) => { - state.error = result - }) - } - } - - await onFileDeleted(paths) -} - export async function renameFile(path: string, toPath: string) { const result = await client.fileRename({ path, toPath, deduplicate: true }) diff --git a/client/store/actions/folder.ts b/client/store/actions/folder.ts index 0d1d66814..f516fcb50 100644 --- a/client/store/actions/folder.ts +++ b/client/store/actions/folder.ts @@ -1,6 +1,6 @@ -import * as store from '../store' import { client } from '@client/client' -import { onFileCreated, onFileDeleted } from './file' +import * as store from '../store' +import { onFileCreated } from './file' export async function copyFolder(path: string, toPath: string) { const result = await client.folderCopy({ path, toPath, deduplicate: true }) @@ -26,20 +26,6 @@ export async function createFolder(path: string) { await onFileCreated([result.path]) } -export async function deleteFolders(paths: string[]) { - for (const path of paths) { - const result = await client.folderDelete({ path }) - - if (result instanceof client.Error) { - return store.setState('delete-folder-error', (state) => { - state.error = result - }) - } - } - - await onFileDeleted(paths) -} - export async function renameFolder(path: string, toPath: string) { const result = await client.folderRename({ path, toPath, deduplicate: true }) diff --git a/client/types/index.ts b/client/types/index.ts index b7ee76889..bc4d5f0e2 100644 --- a/client/types/index.ts +++ b/client/types/index.ts @@ -1,17 +1,18 @@ export * from './column' export * from './config' export * from './contributor' +export * from './dialect' export * from './error' export * from './event' export * from './file' export * from './general' export * from './help' export * from './history' -export * from './dialect' export * from './license' +export * from './menu' export * from './package' export * from './portal' -export * from './menu' +export * from './progress' export * from './record' export * from './report' export * from './resource' diff --git a/client/types/progress.ts b/client/types/progress.ts new file mode 100644 index 000000000..474f835d8 --- /dev/null +++ b/client/types/progress.ts @@ -0,0 +1,6 @@ +export type IProgress = { + type: 'error' | string + title?: string + message?: string + blocking?: boolean +} From 641e515d69666ac89d49febd73acee5810d0e83d Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 14:57:30 +0000 Subject: [PATCH 04/15] Added resetState --- .../Application/Dialogs/DeleteFile/store.ts | 5 ++++- .../Dialogs/FileUpload/FileUpload.tsx | 12 ++++++++++-- .../Application/Dialogs/FileUpload/store.tsx | 13 ++++--------- .../Dialogs/SaveChanges/SaveChanges.tsx | 7 +++++++ .../Application/Dialogs/SaveChanges/store.ts | 5 ++++- client/components/Parts/Progress/Linear.tsx | 2 +- client/helpers/store.ts | 18 +++++++++++++++--- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/client/components/Application/Dialogs/DeleteFile/store.ts b/client/components/Application/Dialogs/DeleteFile/store.ts index 0342947c2..f1d458a2b 100644 --- a/client/components/Application/Dialogs/DeleteFile/store.ts +++ b/client/components/Application/Dialogs/DeleteFile/store.ts @@ -7,7 +7,10 @@ class State { progress?: types.IProgress } -export const { state, useState } = helpers.createState('DeleteFile', new State()) +export const { state, useState, resetState } = helpers.createState( + 'DeleteFile', + new State() +) export function closeDialog() { if (!state.progress?.blocking) { diff --git a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx index 703afe1b7..ff99119ff 100644 --- a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx +++ b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx @@ -6,6 +6,7 @@ import DialogTabs from '@client/components//Parts/Tabs/Dialog' import SimpleButton from '@client/components/Parts/Buttons/SimpleButton' import Columns from '@client/components/Parts/Grids/Columns' import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as appStore from '@client/store' import CloseIcon from '@mui/icons-material/Close' import Box from '@mui/material/Box' import Dialog from '@mui/material/Dialog' @@ -20,8 +21,13 @@ import * as store from './store' const TAB_LABELS = ['From your computer', 'Add external data'] export function FileUploadDialog() { + const dialog = appStore.useStore((state) => state.dialog) const { progress } = store.useState() + React.useEffect(() => { + store.resetState() + }, [dialog]) + return ( - + - + @@ -158,6 +164,7 @@ function AddRemoteTextField(props: { ({ boxShadow: '0px 0px 0px 0px rgba(0, 0, 0, 0.25)', })) +// TODO: move to the common library const StyledTextField = styled(TextField)(() => ({ marginTop: '8px', fontSize: '14px', diff --git a/client/components/Application/Dialogs/FileUpload/store.tsx b/client/components/Application/Dialogs/FileUpload/store.tsx index 8a771ccb4..c96867906 100644 --- a/client/components/Application/Dialogs/FileUpload/store.tsx +++ b/client/components/Application/Dialogs/FileUpload/store.tsx @@ -12,7 +12,10 @@ type IFile = { size: number } -export const { state, useState } = helpers.createState('FileUpload', new State()) +export const { state, useState, resetState } = helpers.createState( + 'FileUploadDialog', + new State() +) export function closeDialog() { if (!state.progress?.blocking) { @@ -20,14 +23,6 @@ export function closeDialog() { } } -export function resetState() { - const initialState = new State() - for (const key of Object.keys(state)) { - // @ts-ignore - state[key] = initialState[key] - } -} - export async function ingestFiles(props: { source: FileList | string }) { const files = props.source instanceof FileList diff --git a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx index 641082f5e..3272dfaf8 100644 --- a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx +++ b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx @@ -1,10 +1,17 @@ import NoButtonDialog from '@client/components/Parts/Dialogs/NoButton' import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as appStore from '@client/store' +import * as React from 'react' import * as store from './store' export function SaveChangesDialog() { + const dialog = appStore.useStore((state) => state.dialog) const { progress } = store.useState() + React.useEffect(() => { + store.resetState() + }, [dialog]) + return ( {progress.message} + return Error: {progress.message} } return ( diff --git a/client/helpers/store.ts b/client/helpers/store.ts index e7b79be4a..2c439b2b7 100644 --- a/client/helpers/store.ts +++ b/client/helpers/store.ts @@ -1,9 +1,21 @@ +import { cloneDeep } from 'lodash' import { proxy, useSnapshot } from 'valtio' import { devtools } from 'valtio/utils' export function createState(name: string, source: T) { - const state = proxy(source) - const useState = () => useSnapshot(state) as typeof state + const state = proxy(cloneDeep(source)) + + const useState = () => { + return useSnapshot(state) as typeof state + } + + const resetState = () => { + for (const key of Object.keys(state)) { + // @ts-ignore + state[key] = cloneDeep(source[key]) + } + } + devtools(state, { name }) - return { state, useState } + return { state, useState, resetState } } From c1b217a5dec3ffaa3611299d89de7ba54a4ac005 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:08:33 +0000 Subject: [PATCH 05/15] Fixed dialog spacing --- .../Application/Dialogs/DeleteFile/DeleteFile.tsx | 4 +--- .../Application/Dialogs/FileUpload/FileUpload.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx index 4ddec19db..0eb332d1a 100644 --- a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx +++ b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx @@ -10,9 +10,7 @@ export function DeleteFileDialog() { const { progress } = store.useState() React.useEffect(() => { - // TODO: rebase on resetState when it is available after merging: - // https://github.com/okfn/opendataeditor/pull/650 - store.state.progress = undefined + store.resetState() }, [dialog]) const description = !progress diff --git a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx index ff99119ff..2d7c86ff6 100644 --- a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx +++ b/client/components/Application/Dialogs/FileUpload/FileUpload.tsx @@ -13,6 +13,7 @@ import Dialog from '@mui/material/Dialog' import DialogContent from '@mui/material/DialogContent' import IconButton from '@mui/material/IconButton' import InputAdornment from '@mui/material/InputAdornment' +import Stack from '@mui/material/Stack' import TextField from '@mui/material/TextField' import { styled, useTheme } from '@mui/material/styles' import * as React from 'react' @@ -66,17 +67,17 @@ export function FileUploadDialog() { disabled={progress?.blocking} onChange={store.resetState} > - + - - + + - + From 5f0340d906ca18d7b27ca296146261f204e3afc0 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:13:22 +0000 Subject: [PATCH 06/15] Normalized dilaog naming --- client/components/Application/Dialog.tsx | 4 ++-- client/components/Application/Dialogs/FileUpload/index.ts | 1 - .../{FileUpload/FileUpload.tsx => UploadFile/UploadFile.tsx} | 2 +- client/components/Application/Dialogs/UploadFile/index.ts | 1 + .../Application/Dialogs/{FileUpload => UploadFile}/store.tsx | 0 5 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 client/components/Application/Dialogs/FileUpload/index.ts rename client/components/Application/Dialogs/{FileUpload/FileUpload.tsx => UploadFile/UploadFile.tsx} (99%) create mode 100644 client/components/Application/Dialogs/UploadFile/index.ts rename client/components/Application/Dialogs/{FileUpload => UploadFile}/store.tsx (100%) diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 1747d6760..25e5d733b 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -5,12 +5,12 @@ import ConfigDialog from './Dialogs/Config' import CopyFileDialog from './Dialogs/CopyFile' import CopyFolderDialog from './Dialogs/CopyFolder' import { DeleteFileDialog } from './Dialogs/DeleteFile' -import { FileUploadDialog } from './Dialogs/FileUpload' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' import RenameFileDialog from './Dialogs/RenameFile' import { SaveChangesDialog } from './Dialogs/SaveChanges' import UnsavedChangesDialog from './Dialogs/UnsavedChanges' +import { UploadFileDialog } from './Dialogs/UploadFile' import WelcomeBannerDialog from './Dialogs/WelcomeBanner' export default function Dialog() { @@ -35,7 +35,7 @@ const DIALOGS = { publish: PublishDialog, unsavedChanges: UnsavedChangesDialog, welcomeBanner: WelcomeBannerDialog, - fileUpload: FileUploadDialog, + fileUpload: UploadFileDialog, openLocation: OpenLocationDialog, renameFile: RenameFileDialog, saveChanges: SaveChangesDialog, diff --git a/client/components/Application/Dialogs/FileUpload/index.ts b/client/components/Application/Dialogs/FileUpload/index.ts deleted file mode 100644 index 4d71d0091..000000000 --- a/client/components/Application/Dialogs/FileUpload/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { FileUploadDialog } from './FileUpload' diff --git a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx b/client/components/Application/Dialogs/UploadFile/UploadFile.tsx similarity index 99% rename from client/components/Application/Dialogs/FileUpload/FileUpload.tsx rename to client/components/Application/Dialogs/UploadFile/UploadFile.tsx index 2d7c86ff6..b58b96a82 100644 --- a/client/components/Application/Dialogs/FileUpload/FileUpload.tsx +++ b/client/components/Application/Dialogs/UploadFile/UploadFile.tsx @@ -21,7 +21,7 @@ import * as store from './store' const TAB_LABELS = ['From your computer', 'Add external data'] -export function FileUploadDialog() { +export function UploadFileDialog() { const dialog = appStore.useStore((state) => state.dialog) const { progress } = store.useState() diff --git a/client/components/Application/Dialogs/UploadFile/index.ts b/client/components/Application/Dialogs/UploadFile/index.ts new file mode 100644 index 000000000..05fa7a087 --- /dev/null +++ b/client/components/Application/Dialogs/UploadFile/index.ts @@ -0,0 +1 @@ +export { UploadFileDialog } from './UploadFile' diff --git a/client/components/Application/Dialogs/FileUpload/store.tsx b/client/components/Application/Dialogs/UploadFile/store.tsx similarity index 100% rename from client/components/Application/Dialogs/FileUpload/store.tsx rename to client/components/Application/Dialogs/UploadFile/store.tsx From 2f67db247b69045eac0027049aecda874ca2af61 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:24:53 +0000 Subject: [PATCH 07/15] Implemented proper RenameFile --- client/components/Application/Dialog.tsx | 2 +- .../Application/Dialogs/DeleteFile/store.ts | 5 +- .../Application/Dialogs/RenameFile.tsx | 27 ---------- .../Dialogs/RenameFile/RenameFile.tsx | 32 ++++++++++++ .../Application/Dialogs/RenameFile/index.ts | 1 + .../Application/Dialogs/RenameFile/store.ts | 51 +++++++++++++++++++ client/components/Parts/Progress/Linear.tsx | 2 +- client/store/actions/file.ts | 12 ----- client/store/actions/folder.ts | 12 ----- client/types/progress.ts | 1 + 10 files changed, 90 insertions(+), 55 deletions(-) delete mode 100644 client/components/Application/Dialogs/RenameFile.tsx create mode 100644 client/components/Application/Dialogs/RenameFile/RenameFile.tsx create mode 100644 client/components/Application/Dialogs/RenameFile/index.ts create mode 100644 client/components/Application/Dialogs/RenameFile/store.ts diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 25e5d733b..783e54fc7 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -7,7 +7,7 @@ import CopyFolderDialog from './Dialogs/CopyFolder' import { DeleteFileDialog } from './Dialogs/DeleteFile' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' -import RenameFileDialog from './Dialogs/RenameFile' +import { RenameFileDialog } from './Dialogs/RenameFile' import { SaveChangesDialog } from './Dialogs/SaveChanges' import UnsavedChangesDialog from './Dialogs/UnsavedChanges' import { UploadFileDialog } from './Dialogs/UploadFile' diff --git a/client/components/Application/Dialogs/DeleteFile/store.ts b/client/components/Application/Dialogs/DeleteFile/store.ts index f1d458a2b..de84ca3ce 100644 --- a/client/components/Application/Dialogs/DeleteFile/store.ts +++ b/client/components/Application/Dialogs/DeleteFile/store.ts @@ -38,12 +38,13 @@ export async function deleteFile() { if (result instanceof client.Error) { state.progress = { type: 'error', - title: `Error deleting the ${target}`, + title: `Error deleting ${target}`, message: result.detail, } + } else { + appStore.onFileDeleted([path]) } state.progress = undefined - appStore.onFileDeleted([path]) closeDialog() } diff --git a/client/components/Application/Dialogs/RenameFile.tsx b/client/components/Application/Dialogs/RenameFile.tsx deleted file mode 100644 index 57609378f..000000000 --- a/client/components/Application/Dialogs/RenameFile.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import InputDialog from '../../Parts/Dialogs/Input' -import * as store from '@client/store' - -export default function RenameFileDialog() { - const currentFilePath = store.useStore((state) => state.path) - const isFolder = store.useStore(store.getIsFolder) - - return ( - { - if (currentFilePath) - if (!isFolder) { - await store.renameFile(currentFilePath, newPath) - } else { - await store.renameFolder(currentFilePath, newPath) - } - store.closeDialog() - }} - /> - ) -} diff --git a/client/components/Application/Dialogs/RenameFile/RenameFile.tsx b/client/components/Application/Dialogs/RenameFile/RenameFile.tsx new file mode 100644 index 000000000..8f20aa445 --- /dev/null +++ b/client/components/Application/Dialogs/RenameFile/RenameFile.tsx @@ -0,0 +1,32 @@ +import InputDialog from '@client/components/Parts/Dialogs/Input' +import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as appStore from '@client/store' +import * as React from 'react' +import * as store from './store' + +export function RenameFileDialog() { + const isFolder = appStore.useStore(appStore.getIsFolder) + const dialog = appStore.useStore((state) => state.dialog) + const { progress } = store.useState() + + React.useEffect(() => { + store.resetState() + }, [dialog]) + + const title = `Rename ${isFolder ? 'folder' : 'file'}` + const placeholder = `Name of new ${isFolder ? 'folder' : 'file'}` + + return ( + + + + ) +} diff --git a/client/components/Application/Dialogs/RenameFile/index.ts b/client/components/Application/Dialogs/RenameFile/index.ts new file mode 100644 index 000000000..dc13fe9f7 --- /dev/null +++ b/client/components/Application/Dialogs/RenameFile/index.ts @@ -0,0 +1 @@ +export { RenameFileDialog } from './RenameFile' diff --git a/client/components/Application/Dialogs/RenameFile/store.ts b/client/components/Application/Dialogs/RenameFile/store.ts new file mode 100644 index 000000000..0a756b83c --- /dev/null +++ b/client/components/Application/Dialogs/RenameFile/store.ts @@ -0,0 +1,51 @@ +import { client } from '@client/client' +import * as helpers from '@client/helpers' +import * as appStore from '@client/store' +import * as types from '@client/types' + +class State { + progress?: types.IProgress +} + +export const { state, useState, resetState } = helpers.createState( + 'RenameFile', + new State() +) + +export function closeDialog() { + if (!state.progress?.blocking) { + appStore.closeDialog() + } +} + +export async function renameFile(toPath: string) { + const isFolder = appStore.getIsFolder(appStore.getState()) + const { path } = appStore.getState() + if (!path) return + + const target = isFolder ? 'folder' : 'file' + + state.progress = { + type: 'renaming', + title: `Renaming selected ${target}`, + blocking: true, + hidden: true, + } + + const result = isFolder + ? await client.folderRename({ path, toPath, deduplicate: true }) + : await client.fileRename({ path, toPath, deduplicate: true }) + + if (result instanceof client.Error) { + state.progress = { + type: 'error', + title: `Error renaming ${target}`, + message: result.detail, + } + } else { + appStore.onFileCreated([result.path]) + } + + state.progress = undefined + closeDialog() +} diff --git a/client/components/Parts/Progress/Linear.tsx b/client/components/Parts/Progress/Linear.tsx index c8af696fe..3a53c33e6 100644 --- a/client/components/Parts/Progress/Linear.tsx +++ b/client/components/Parts/Progress/Linear.tsx @@ -7,7 +7,7 @@ import { startCase } from 'lodash' export function LinearProgress(props: { progress?: types.IProgress }) { const { progress } = props - if (!progress) { + if (!progress || progress.hidden) { return null } diff --git a/client/store/actions/file.ts b/client/store/actions/file.ts index 13211bd74..82be228c4 100644 --- a/client/store/actions/file.ts +++ b/client/store/actions/file.ts @@ -157,18 +157,6 @@ export async function revertFile() { } } -export async function renameFile(path: string, toPath: string) { - const result = await client.fileRename({ path, toPath, deduplicate: true }) - - if (result instanceof client.Error) { - return store.setState('move-file-error', (state) => { - state.error = result - }) - } - - await onFileCreated([result.path]) -} - // Handlers export async function onFileCreated(paths: string[]) { diff --git a/client/store/actions/folder.ts b/client/store/actions/folder.ts index f516fcb50..615df7ecc 100644 --- a/client/store/actions/folder.ts +++ b/client/store/actions/folder.ts @@ -25,15 +25,3 @@ export async function createFolder(path: string) { await onFileCreated([result.path]) } - -export async function renameFolder(path: string, toPath: string) { - const result = await client.folderRename({ path, toPath, deduplicate: true }) - - if (result instanceof client.Error) { - return store.setState('move-folder-error', (state) => { - state.error = result - }) - } - - await onFileCreated([result.path]) -} diff --git a/client/types/progress.ts b/client/types/progress.ts index 474f835d8..b3a1f5390 100644 --- a/client/types/progress.ts +++ b/client/types/progress.ts @@ -3,4 +3,5 @@ export type IProgress = { title?: string message?: string blocking?: boolean + hidden?: boolean } From 1e331fb22a3d242cee16cd21f789a6eeeb8eabd4 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:28:10 +0000 Subject: [PATCH 08/15] Removed unused CopyFile/Folder --- client/components/Application/Dialog.tsx | 4 --- .../Application/Dialogs/CopyFile.tsx | 25 ------------------- .../Application/Dialogs/CopyFolder.tsx | 25 ------------------- client/store/actions/file.ts | 12 --------- client/store/actions/folder.ts | 12 --------- 5 files changed, 78 deletions(-) delete mode 100644 client/components/Application/Dialogs/CopyFile.tsx delete mode 100644 client/components/Application/Dialogs/CopyFolder.tsx diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 783e54fc7..1f24b9d69 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -2,8 +2,6 @@ import * as store from '@client/store' import AddEmptyFolderDialog from './Dialogs/AddEmptyFolder' import CloseWithUnsavedChangesDialog from './Dialogs/CloseWithUnsavedChanges' import ConfigDialog from './Dialogs/Config' -import CopyFileDialog from './Dialogs/CopyFile' -import CopyFolderDialog from './Dialogs/CopyFolder' import { DeleteFileDialog } from './Dialogs/DeleteFile' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' @@ -29,8 +27,6 @@ const DIALOGS = { closeWithUnsavedChanges: CloseWithUnsavedChangesDialog, config: ConfigDialog, configProject: ConfigDialog, - copyFile: CopyFileDialog, - copyFolder: CopyFolderDialog, deleteFilesFolders: DeleteFileDialog, publish: PublishDialog, unsavedChanges: UnsavedChangesDialog, diff --git a/client/components/Application/Dialogs/CopyFile.tsx b/client/components/Application/Dialogs/CopyFile.tsx deleted file mode 100644 index 8c6eef252..000000000 --- a/client/components/Application/Dialogs/CopyFile.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import ContentCopyIcon from '@mui/icons-material/ContentCopy' -import InputDialog from '../../Parts/Dialogs/Input' -import * as store from '@client/store' - -export default function CopyFileDialog() { - const path = store.useStore((state) => state.path) - if (!path) return null - - return ( - { - await store.copyFile(path, toPath) - store.closeDialog() - }} - /> - ) -} diff --git a/client/components/Application/Dialogs/CopyFolder.tsx b/client/components/Application/Dialogs/CopyFolder.tsx deleted file mode 100644 index 7ac2b8d94..000000000 --- a/client/components/Application/Dialogs/CopyFolder.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import ContentCopyIcon from '@mui/icons-material/ContentCopy' -import InputDialog from '../../Parts/Dialogs/Input' -import * as store from '@client/store' - -export default function CopyFolderDialog() { - const path = store.useStore((state) => state.path) - if (!path) return null - - return ( - { - await store.copyFolder(path, toPath) - store.closeDialog() - }} - /> - ) -} diff --git a/client/store/actions/file.ts b/client/store/actions/file.ts index 82be228c4..9ffd26f6e 100644 --- a/client/store/actions/file.ts +++ b/client/store/actions/file.ts @@ -123,18 +123,6 @@ export async function deselectFile() { await selectFile({ path: undefined }) } -export async function copyFile(path: string, toPath: string) { - const result = await client.fileCopy({ path, toPath, deduplicate: true }) - - if (result instanceof client.Error) { - return store.setState('copy-file-error', (state) => { - state.error = result - }) - } - - await onFileCreated([result.path]) -} - export async function saveFile() { const { record } = store.getState() invariant(record) diff --git a/client/store/actions/folder.ts b/client/store/actions/folder.ts index 615df7ecc..3530b8beb 100644 --- a/client/store/actions/folder.ts +++ b/client/store/actions/folder.ts @@ -2,18 +2,6 @@ import { client } from '@client/client' import * as store from '../store' import { onFileCreated } from './file' -export async function copyFolder(path: string, toPath: string) { - const result = await client.folderCopy({ path, toPath, deduplicate: true }) - - if (result instanceof client.Error) { - return store.setState('copy-folder-error', (state) => { - state.error = result - }) - } - - await onFileCreated([result.path]) -} - export async function createFolder(path: string) { const result = await client.folderCreate({ path, deduplicate: true }) From 9eb8255320724c3a878354ff8a88fc001dfc9421 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:29:47 +0000 Subject: [PATCH 09/15] Added secondary text to the Remove action --- client/components/Parts/Trees/File.tsx | 192 ++++++++++++++----------- 1 file changed, 109 insertions(+), 83 deletions(-) diff --git a/client/components/Parts/Trees/File.tsx b/client/components/Parts/Trees/File.tsx index 1dcb34bb9..f3321bfcf 100644 --- a/client/components/Parts/Trees/File.tsx +++ b/client/components/Parts/Trees/File.tsx @@ -1,26 +1,25 @@ -import * as React from 'react' -import { alpha, styled } from '@mui/material/styles' -import { keyframes } from '@mui/system' -import { TreeItem, TreeView, TreeItemProps, treeItemClasses } from '@mui/x-tree-view' +import { fileMenuWidth } from '@client/components/Application/Layout' +import * as store from '@client/store' +import MoreHorizIcon from '@mui/icons-material/MoreHoriz' import Box from '@mui/material/Box' +import ListItemIcon from '@mui/material/ListItemIcon' +import ListItemText from '@mui/material/ListItemText' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' import Stack from '@mui/material/Stack' -import ScrollBox from '../Boxes/Scroll' -import * as helpers from '../../../helpers' -import * as types from '../../../types' -import { useTheme } from '@mui/material/styles' -import openFolderIcon from '../../../assets/open_folder_icon.svg' +import { alpha, styled, useTheme } from '@mui/material/styles' +import { keyframes } from '@mui/system' +import { TreeItem, TreeItemProps, TreeView, treeItemClasses } from '@mui/x-tree-view' +import * as React from 'react' import closedFolderIcon from '../../../assets/closed_folder_icon.svg' import deleteIcon from '../../../assets/delete_icon.svg' -import renameIcon from '../../../assets/rename_icon.svg' import openFileLocationIcon from '../../../assets/open_file_location_icon.svg' -import MoreHorizIcon from '@mui/icons-material/MoreHoriz' +import openFolderIcon from '../../../assets/open_folder_icon.svg' +import renameIcon from '../../../assets/rename_icon.svg' +import * as helpers from '../../../helpers' +import * as types from '../../../types' import IconButton from '../../Parts/Buttons/Icon' -import Menu from '@mui/material/Menu' -import MenuItem from '@mui/material/MenuItem' -import ListItemIcon from '@mui/material/ListItemIcon' -import ListItemText from '@mui/material/ListItemText' -import { fileMenuWidth } from '@client/components/Application/Layout' -import * as store from '@client/store' +import ScrollBox from '../Boxes/Scroll' export interface FileTreeProps { files: types.IFile[] @@ -43,12 +42,12 @@ export default function FileTree(props: FileTreeProps) { : props.defaultExpanded || [] setExpanded([...new Set([...expanded, ...defaultExpanded])]) }, [props.event, props.defaultExpanded]) - + const selected = props.selected || '' return ( - + (null); - const open = Boolean(anchorEl); + const [anchorEl, setAnchorEl] = React.useState(null) + const open = Boolean(anchorEl) const handleContextBtnClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); + setAnchorEl(event.currentTarget) } const handleClose = () => { - setAnchorEl(null); + setAnchorEl(null) } const handleDelete = () => { @@ -125,23 +124,26 @@ const StyledTreeItem = styled( handleClose() } - const fileOrFolder = item.type === 'folder' ? 'folder': 'file' - + const fileOrFolder = item.type === 'folder' ? 'folder' : 'file' + return ( - + : null } - className={ item.type === 'folder' ? 'type_folder' : 'type_file' } - sx={{ animation, + endIcon={item.type === 'folder' ? : null} + className={item.type === 'folder' ? 'type_folder' : 'type_file'} + sx={{ + animation, '& .MuiTreeItem-content': { - minWidth: '205px' + minWidth: '205px', }, '&.type_folder > .MuiTreeItem-content': { - padding: '0 24px' + padding: '0 24px', }, '& > .MuiTreeItem-content .MuiTreeItem-iconContainer': { - marginRight: 0 + marginRight: 0, }, '& > .MuiTreeItem-content .MuiTreeItem-label': { maxWidth: '172px', @@ -149,29 +151,30 @@ const StyledTreeItem = styled( '& > .MuiTreeItem-content:hover': { width: `${fileMenuWidth}px`, }, - '& > .MuiTreeItem-content.Mui-selected, & > .MuiTreeItem-content.Mui-selected.Mui-focused': { - zIndex: 0, - position: 'relative', - width: `${fileMenuWidth}px` - }, + '& > .MuiTreeItem-content.Mui-selected, & > .MuiTreeItem-content.Mui-selected.Mui-focused': + { + zIndex: 0, + position: 'relative', + width: `${fileMenuWidth}px`, + }, '& + button': { position: 'sticky', - right: 0 - } + right: 0, + }, }} label={} /> - handleRename()}> - + {} - + handleOpenFileLocation(item.path)}> - + {} - + - + {} - theme.palette.OKFNRed500.main, - }} - primary={`Delete ${fileOrFolder}`} secondary="Only removes this element from the ODE folder" /> + theme.palette.OKFNRed500.main, + }} + primary={`Delete ${fileOrFolder}`} + secondary="Only removes this element from the ODE folder" + /> @@ -255,7 +273,10 @@ function TreeItemIcon(props: { nodeId: string; item: types.IFileTreeItem }) { let color = 'gray' if (props.item.type === 'folder') color = 'primary' - if (props.item.name) color = props.item.errors ? theme.palette.OKFNRed500.main : theme.palette.OKFNGreenBlue.main + if (props.item.name) + color = props.item.errors + ? theme.palette.OKFNRed500.main + : theme.palette.OKFNGreenBlue.main const fontWeight = 'normal' // When data package is enabled consider highlighting it @@ -271,13 +292,18 @@ function TreeItemIcon(props: { nodeId: string; item: types.IFileTreeItem }) { '& div': { mr: 1 }, }} > -
{" "}
+
+ {' '} +
{props.item.label}
) From 34bd6f78abc52336f24f5615669eac6d0c10bcfc Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:39:20 +0000 Subject: [PATCH 10/15] Properly implemented CreateFolder --- client/components/Application/Dialog.tsx | 4 +- .../Application/Dialogs/AddEmptyFolder.tsx | 21 --------- .../Dialogs/CreateFolder/CreateFolder.tsx | 29 +++++++++++++ .../Application/Dialogs/CreateFolder/index.ts | 1 + .../Application/Dialogs/CreateFolder/store.ts | 43 +++++++++++++++++++ .../Dialogs/DeleteFile/DeleteFile.tsx | 3 +- .../Application/Dialogs/DeleteFile/store.ts | 2 +- .../Application/Dialogs/RenameFile/store.ts | 2 +- client/store/actions/folder.ts | 15 ------- client/store/actions/index.ts | 1 - 10 files changed, 79 insertions(+), 42 deletions(-) delete mode 100644 client/components/Application/Dialogs/AddEmptyFolder.tsx create mode 100644 client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx create mode 100644 client/components/Application/Dialogs/CreateFolder/index.ts create mode 100644 client/components/Application/Dialogs/CreateFolder/store.ts delete mode 100644 client/store/actions/folder.ts diff --git a/client/components/Application/Dialog.tsx b/client/components/Application/Dialog.tsx index 1f24b9d69..6470dbac4 100644 --- a/client/components/Application/Dialog.tsx +++ b/client/components/Application/Dialog.tsx @@ -1,7 +1,7 @@ import * as store from '@client/store' -import AddEmptyFolderDialog from './Dialogs/AddEmptyFolder' import CloseWithUnsavedChangesDialog from './Dialogs/CloseWithUnsavedChanges' import ConfigDialog from './Dialogs/Config' +import { CreateFolderDialog } from './Dialogs/CreateFolder' import { DeleteFileDialog } from './Dialogs/DeleteFile' import OpenLocationDialog from './Dialogs/OpenLocation' import PublishDialog from './Dialogs/Publish' @@ -23,7 +23,7 @@ export default function Dialog() { } const DIALOGS = { - addEmptyFolder: AddEmptyFolderDialog, + addEmptyFolder: CreateFolderDialog, closeWithUnsavedChanges: CloseWithUnsavedChangesDialog, config: ConfigDialog, configProject: ConfigDialog, diff --git a/client/components/Application/Dialogs/AddEmptyFolder.tsx b/client/components/Application/Dialogs/AddEmptyFolder.tsx deleted file mode 100644 index 3b3eaa269..000000000 --- a/client/components/Application/Dialogs/AddEmptyFolder.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import InputDialog from '../../Parts/Dialogs/Input' -import * as store from '@client/store' - -export default function AddEmptyFolderDialog() { - const folderPath = store.useStore(store.getFolderPath) - - return ( - { - await store.createFolder(path) - store.closeDialog() - }} - /> - ) -} diff --git a/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx b/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx new file mode 100644 index 000000000..12b8412fd --- /dev/null +++ b/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx @@ -0,0 +1,29 @@ +import InputDialog from '@client/components/Parts/Dialogs/Input' +import { LinearProgress } from '@client/components/Parts/Progress/Linear' +import * as appStore from '@client/store' +import * as React from 'react' +import * as store from './store' + +export function CreateFolderDialog() { + const folderPath = appStore.useStore(appStore.getFolderPath) + const dialog = appStore.useStore((state) => state.dialog) + const { progress } = store.useState() + + React.useEffect(() => { + store.resetState() + }, [dialog]) + + return ( + + + + ) +} diff --git a/client/components/Application/Dialogs/CreateFolder/index.ts b/client/components/Application/Dialogs/CreateFolder/index.ts new file mode 100644 index 000000000..60304b30e --- /dev/null +++ b/client/components/Application/Dialogs/CreateFolder/index.ts @@ -0,0 +1 @@ +export { CreateFolderDialog } from './CreateFolder' diff --git a/client/components/Application/Dialogs/CreateFolder/store.ts b/client/components/Application/Dialogs/CreateFolder/store.ts new file mode 100644 index 000000000..ad2222227 --- /dev/null +++ b/client/components/Application/Dialogs/CreateFolder/store.ts @@ -0,0 +1,43 @@ +import { client } from '@client/client' +import * as helpers from '@client/helpers' +import * as appStore from '@client/store' +import * as types from '@client/types' + +class State { + progress?: types.IProgress +} + +export const { state, useState, resetState } = helpers.createState( + 'CreateFolderDialog', + new State() +) + +export function closeDialog() { + if (!state.progress?.blocking) { + appStore.closeDialog() + } +} + +export async function createFolder(path: string) { + state.progress = { + type: 'creating', + title: 'Creating a folder', + blocking: true, + hidden: true, + } + + const result = await client.folderCreate({ path, deduplicate: true }) + + if (result instanceof client.Error) { + state.progress = { + type: 'error', + title: `Error creating a folder`, + message: result.detail, + } + } else { + appStore.onFileCreated([result.path]) + } + + state.progress = undefined + closeDialog() +} diff --git a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx index 0eb332d1a..3882a2175 100644 --- a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx +++ b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx @@ -13,6 +13,7 @@ export function DeleteFileDialog() { store.resetState() }, [dialog]) + const title = isFolder ? 'Delete Folder' : 'Delete File' const description = !progress ? `Are you sure you want to delete this ${isFolder ? 'folder' : 'file'}?` : undefined @@ -20,7 +21,7 @@ export function DeleteFileDialog() { return ( { - state.error = result - }) - } - - await onFileCreated([result.path]) -} diff --git a/client/store/actions/index.ts b/client/store/actions/index.ts index 2073aa345..1c271a9bb 100644 --- a/client/store/actions/index.ts +++ b/client/store/actions/index.ts @@ -4,7 +4,6 @@ export * from './dialog' export * from './error' export * from './event' export * from './file' -export * from './folder' export * from './panel' export * from './project' export * from './refs' From e6afdcb585dffc92f794c4c439d92941081ae996 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:42:47 +0000 Subject: [PATCH 11/15] Normalized action list --- client/components/Parts/Trees/File.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/components/Parts/Trees/File.tsx b/client/components/Parts/Trees/File.tsx index f3321bfcf..13cf5882c 100644 --- a/client/components/Parts/Trees/File.tsx +++ b/client/components/Parts/Trees/File.tsx @@ -194,7 +194,7 @@ const StyledTreeItem = styled( 'aria-labelledby': 'file-context-menu-btn', }} > - handleRename()}> + handleOpenFileLocation(item.path)}> - {} + {} - handleOpenFileLocation(item.path)}> + handleRename()}> - {} + {} From 29bf5b9546b06988d761228cbfe5c0a61017bd98 Mon Sep 17 00:00:00 2001 From: roll Date: Tue, 26 Nov 2024 15:52:32 +0000 Subject: [PATCH 12/15] Fixed SaveChanges --- .../Application/Dialogs/SaveChanges/SaveChanges.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx index 3272dfaf8..641082f5e 100644 --- a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx +++ b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx @@ -1,17 +1,10 @@ import NoButtonDialog from '@client/components/Parts/Dialogs/NoButton' import { LinearProgress } from '@client/components/Parts/Progress/Linear' -import * as appStore from '@client/store' -import * as React from 'react' import * as store from './store' export function SaveChangesDialog() { - const dialog = appStore.useStore((state) => state.dialog) const { progress } = store.useState() - React.useEffect(() => { - store.resetState() - }, [dialog]) - return ( Date: Tue, 26 Nov 2024 15:57:24 +0000 Subject: [PATCH 13/15] Fixed rename primary title --- client/components/Parts/Trees/File.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/Parts/Trees/File.tsx b/client/components/Parts/Trees/File.tsx index 13cf5882c..c64fb8308 100644 --- a/client/components/Parts/Trees/File.tsx +++ b/client/components/Parts/Trees/File.tsx @@ -226,7 +226,7 @@ const StyledTreeItem = styled( {} From c0709133670c29467242f894f497c3be8874b3e1 Mon Sep 17 00:00:00 2001 From: roll Date: Wed, 27 Nov 2024 10:47:54 +0000 Subject: [PATCH 14/15] Movde LinearProgress to library --- .../Application/Dialogs/CreateFolder/CreateFolder.tsx | 2 +- client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx | 2 +- client/components/Application/Dialogs/RenameFile/RenameFile.tsx | 2 +- .../components/Application/Dialogs/SaveChanges/SaveChanges.tsx | 2 +- client/components/Application/Dialogs/UploadFile/UploadFile.tsx | 2 +- .../{Parts/Progress/Linear.tsx => Library/LinearProgress.tsx} | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename client/components/{Parts/Progress/Linear.tsx => Library/LinearProgress.tsx} (100%) diff --git a/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx b/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx index 12b8412fd..4a3407948 100644 --- a/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx +++ b/client/components/Application/Dialogs/CreateFolder/CreateFolder.tsx @@ -1,5 +1,5 @@ +import { LinearProgress } from '@client/components/Library/LinearProgress' import InputDialog from '@client/components/Parts/Dialogs/Input' -import { LinearProgress } from '@client/components/Parts/Progress/Linear' import * as appStore from '@client/store' import * as React from 'react' import * as store from './store' diff --git a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx index 3882a2175..bf617d020 100644 --- a/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx +++ b/client/components/Application/Dialogs/DeleteFile/DeleteFile.tsx @@ -1,5 +1,5 @@ +import { LinearProgress } from '@client/components/Library/LinearProgress' import TwoButtonDialog from '@client/components/Parts/Dialogs/TwoButton' -import { LinearProgress } from '@client/components/Parts/Progress/Linear' import * as appStore from '@client/store' import * as React from 'react' import * as store from './store' diff --git a/client/components/Application/Dialogs/RenameFile/RenameFile.tsx b/client/components/Application/Dialogs/RenameFile/RenameFile.tsx index 8f20aa445..b18359f50 100644 --- a/client/components/Application/Dialogs/RenameFile/RenameFile.tsx +++ b/client/components/Application/Dialogs/RenameFile/RenameFile.tsx @@ -1,5 +1,5 @@ +import { LinearProgress } from '@client/components/Library/LinearProgress' import InputDialog from '@client/components/Parts/Dialogs/Input' -import { LinearProgress } from '@client/components/Parts/Progress/Linear' import * as appStore from '@client/store' import * as React from 'react' import * as store from './store' diff --git a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx index 641082f5e..451e0d104 100644 --- a/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx +++ b/client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx @@ -1,5 +1,5 @@ +import { LinearProgress } from '@client/components/Library/LinearProgress' import NoButtonDialog from '@client/components/Parts/Dialogs/NoButton' -import { LinearProgress } from '@client/components/Parts/Progress/Linear' import * as store from './store' export function SaveChangesDialog() { diff --git a/client/components/Application/Dialogs/UploadFile/UploadFile.tsx b/client/components/Application/Dialogs/UploadFile/UploadFile.tsx index b58b96a82..7f4272a2a 100644 --- a/client/components/Application/Dialogs/UploadFile/UploadFile.tsx +++ b/client/components/Application/Dialogs/UploadFile/UploadFile.tsx @@ -3,9 +3,9 @@ import iconUploadFolderImg from '@client/assets/folder-open-big-plus.png' import iconLinkTextField from '@client/assets/icon_link_textfield.svg' import iconUploadFileImg from '@client/assets/icon_upload_file.png' import DialogTabs from '@client/components//Parts/Tabs/Dialog' +import { LinearProgress } from '@client/components/Library/LinearProgress' import SimpleButton from '@client/components/Parts/Buttons/SimpleButton' import Columns from '@client/components/Parts/Grids/Columns' -import { LinearProgress } from '@client/components/Parts/Progress/Linear' import * as appStore from '@client/store' import CloseIcon from '@mui/icons-material/Close' import Box from '@mui/material/Box' diff --git a/client/components/Parts/Progress/Linear.tsx b/client/components/Library/LinearProgress.tsx similarity index 100% rename from client/components/Parts/Progress/Linear.tsx rename to client/components/Library/LinearProgress.tsx From e3a7e6a24d5167de2b3134c27e33353b8f30f476 Mon Sep 17 00:00:00 2001 From: roll Date: Thu, 28 Nov 2024 12:06:13 +0000 Subject: [PATCH 15/15] Reverted context menu changes --- client/components/Parts/Trees/File.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/client/components/Parts/Trees/File.tsx b/client/components/Parts/Trees/File.tsx index c64fb8308..0f5cc1610 100644 --- a/client/components/Parts/Trees/File.tsx +++ b/client/components/Parts/Trees/File.tsx @@ -194,7 +194,7 @@ const StyledTreeItem = styled( 'aria-labelledby': 'file-context-menu-btn', }} > - handleOpenFileLocation(item.path)}> + handleRename()}> - {} + {} - + - handleRename()}> + handleOpenFileLocation(item.path)}> - {} + {}