Skip to content

Commit

Permalink
Fix saving errored tables (#648)
Browse files Browse the repository at this point in the history
* Separate Upload components for maintainablity; fixed tabs height

* Implemented loading state for file uploading

* Implemented UploadFiles state management

* Added component state

* Normalized asset locations

* Disabled controls while busy

* Recovered folder uploading

* Merged local files button

* Recovered remote file

* Rebased on progress

* Removed unused create actions

* Fixed loading message

* Return file size from the server

* Added large file size message

* Removed unused dialog

* Removed unused AdjustFile dialog

* Removed unused IndexFiles dialog

* Removed unused file actions

* Validating -> Checking Errors

* Fixed merging

* Removed unused toPath prop from table/patch

* Maked table/patch atomic action

* Add SaveChanges process indication

* Added TODO

* Extracted updated_cells

* Reconstruct previous errors

* Fixed table saving shortcut
  • Loading branch information
roll authored Nov 26, 2024
1 parent e5c38d1 commit c08961b
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 115 deletions.
1 change: 0 additions & 1 deletion client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ export class Client {

async tablePatch(props: {
path: string
toPath?: string
history?: types.IHistory
resource?: types.IResource
}) {
Expand Down
2 changes: 2 additions & 0 deletions client/components/Application/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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 WelcomeBannerDialog from './Dialogs/WelcomeBanner'

Expand Down Expand Up @@ -37,4 +38,5 @@ const DIALOGS = {
fileUpload: FileUploadDialog,
openLocation: OpenLocationDialog,
renameFile: RenameFileDialog,
saveChanges: SaveChangesDialog,
} as const
Original file line number Diff line number Diff line change
@@ -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('SaveChangesDialog', 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()
}
}
47 changes: 47 additions & 0 deletions client/components/Application/Dialogs/SaveChanges/SaveChanges.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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'

export function SaveChangesDialog() {
return (
<NoButtonDialog
open={true}
maxWidth="md"
title="Saving Changes"
onClose={store.closeDialog}
>
<ProgressIndicator />
</NoButtonDialog>
)
}

// TODO: move to common components
function ProgressIndicator() {
const { progress } = store.useState()

if (!progress) {
return null
}

if (progress.type === 'error') {
return <Box sx={{ color: 'red' }}>{progress.message}</Box>
}

return (
<Stack spacing={1}>
<Box>{progress.title || startCase(progress.type)}...</Box>
<LinearProgress
sx={{
'& .MuiLinearProgress-bar': {
backgroundColor: '#00D1FF',
},
padding: '10px',
}}
/>
<Box sx={{ color: 'gray' }}>{progress.message}</Box>
</Stack>
)
}
2 changes: 2 additions & 0 deletions client/components/Application/Dialogs/SaveChanges/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SaveChangesDialog } from './SaveChanges'
export * as saveChangesDialog from './SaveChanges.store'
11 changes: 6 additions & 5 deletions client/components/Controllers/Table/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import TableEditor from '../../Editors/Table'
import { saveChangesDialog } from '@client/components/Application/Dialogs/SaveChanges'
import * as store from '@client/store'
import * as types from '@client/types'
import { ClickAwayListener } from '@mui/base'
import Box from '@mui/material/Box'
import * as React from 'react'
import { useKeyPress } from 'ahooks'
import * as store from '@client/store'
import * as types from '@client/types'
import * as React from 'react'
import TableEditor from '../../Editors/Table'

export default function Editor() {
const schema = store.useStore((state) => state.record?.resource.schema)
Expand Down Expand Up @@ -40,7 +41,7 @@ export default function Editor() {
})

useKeyPress(['ctrl+s', 'meta+s'], () => {
store.saveTable()
saveChangesDialog.saveChanges()
})

// Ensure that when the user interact with other parts on the application
Expand Down
8 changes: 6 additions & 2 deletions client/components/Controllers/Table/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as menu from '../../Parts/Bars/Menu'
import { saveChangesDialog } from '@client/components/Application/Dialogs/SaveChanges'
import * as store from '@client/store'
import Box from '@mui/material/Box'
import * as action from '../../Parts/Bars/Action'
import * as menu from '../../Parts/Bars/Menu'

export default function Menu() {
const panel = store.useStore((state) => state.panel)
Expand Down Expand Up @@ -56,7 +57,10 @@ export default function Menu() {
disabled={isTableUpdated}
onClick={() => store.openDialog('publish')}
/>
<action.SaveButton updated={isTableUpdated} onClick={store.saveTable} />
<action.SaveButton
updated={isTableUpdated}
onClick={saveChangesDialog.saveChanges}
/>
</Box>
</Box>
</menu.MenuBar>
Expand Down
48 changes: 48 additions & 0 deletions client/components/Parts/Dialogs/NoButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import CloseIcon from '@mui/icons-material/Close'
import Dialog, { DialogProps } from '@mui/material/Dialog'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import IconButton from '@mui/material/IconButton'

export default function NoButtonDialog(
props: DialogProps & { title: string; onClose: () => void }
) {
const handleClose = () => {
props.onClose()
}

return (
<Dialog
fullWidth
maxWidth={props.maxWidth}
open={!!props.open}
onClose={props.onClose}
aria-labelledby={props.title}
>
<IconButton
aria-label="close"
onClick={handleClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
<DialogTitle
id="dialog-title"
sx={{
paddingBottom: 1,
marginBottom: 2,
borderBottom: 'solid 1px #ddd',
backgroundColor: (theme) => theme.palette.OKFNGray100.main,
}}
>
{props.title}
</DialogTitle>
<DialogContent>{props.children}</DialogContent>
</Dialog>
)
}
5 changes: 3 additions & 2 deletions client/store/actions/file.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { client } from '@client/client'
import { saveChangesDialog } from '@client/components/Application/Dialogs/SaveChanges'
import * as helpers from '@client/helpers'
import * as settings from '@client/settings'
import { cloneDeep } from 'lodash'
Expand All @@ -7,7 +8,7 @@ import * as store from '../store'
import { openDialog } from './dialog'
import { emitEvent } from './event'
import { loadSource } from './source'
import { closeTable, getIsTableUpdated, openTable, revertTable, saveTable } from './table'
import { closeTable, getIsTableUpdated, openTable, revertTable } from './table'
import { closeText, getIsTextUpdated, openText, revertText, saveText } from './text'

export async function loadFiles(throwError?: boolean) {
Expand Down Expand Up @@ -141,7 +142,7 @@ export async function saveFile() {
invariant(record)

if (record.type === 'table') {
await saveTable()
await saveChangesDialog.saveChanges()
} else if (record.type === 'text') {
await saveText()
}
Expand Down
56 changes: 5 additions & 51 deletions client/store/actions/table.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { client } from '@client/client'
import invariant from 'tiny-invariant'
import { mapValues, isNull } from 'lodash'
import { onFileCreated, onFileUpdated } from './file'
import { cloneDeep } from 'lodash'
import { revertResource } from './resource'
import { getRefs } from './refs'
import * as helpers from '@client/helpers'
import * as settings from '@client/settings'
import * as types from '@client/types'
import { cloneDeep, isNull, mapValues } from 'lodash'
import invariant from 'tiny-invariant'
import * as store from '../store'
import { onFileUpdated } from './file'
import { getRefs } from './refs'
import { revertResource } from './resource'

export async function openTable() {
const { path, record } = store.getState()
Expand Down Expand Up @@ -36,26 +35,6 @@ export async function closeTable() {
})
}

export async function forkTable(toPath: string) {
const { path, table, resource } = store.getState()
if (!path || !table) return

const result = await client.tablePatch({
path,
toPath,
history: table.history,
resource,
})

if (result instanceof client.Error) {
return store.setState('fork-table-error', (state) => {
state.error = result
})
}

await onFileCreated([result.path])
}

export async function publishTable(control: types.IControl) {
const { record } = store.getState()

Expand Down Expand Up @@ -93,31 +72,6 @@ export async function revertTable() {
revertResource()
}

export async function saveTable() {
const { grid } = getRefs()
const { path, resource, table } = store.getState()
if (!path || !grid || !table) return

const state = store.getState()
const isTableUpdated = getIsTableUpdated(state)
const isResourceUpdated = state.isResourceUpdated

const result = await client.tablePatch({
path,
history: isTableUpdated ? table.history : undefined,
resource: isResourceUpdated ? resource : undefined,
})

if (result instanceof client.Error) {
return store.setState('save-table-error', (state) => {
state.error = result
})
}

await onFileUpdated([path])
grid.reload()
}

export function setTableSelection(selection: types.ITableSelection) {
store.setState('set-table-selection', (state) => {
state.table!.selection = selection
Expand Down
1 change: 1 addition & 0 deletions client/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './actions'
export { getRefs } from './actions/refs'
export * from './state'
export { getState, useStore } from './store'
1 change: 1 addition & 0 deletions client/store/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export type IDialog =
| 'fileUpload'
| 'openLocation'
| 'renameFile'
| 'saveChanges'

export type IPanel = 'metadata' | 'report' | 'changes' | 'source'

Expand Down
Loading

0 comments on commit c08961b

Please sign in to comment.