Skip to content

Commit 7a4c47d

Browse files
committed
Add offline support to <Edit>
1 parent f9ed7cf commit 7a4c47d

File tree

3 files changed

+123
-12
lines changed

3 files changed

+123
-12
lines changed

packages/ra-ui-materialui/src/detail/Edit.stories.tsx

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@ import {
66
useRecordContext,
77
TestMemoryRouter,
88
useEditContext,
9+
IsOffline,
910
} from 'ra-core';
1011
import polyglotI18nProvider from 'ra-i18n-polyglot';
1112
import englishMessages from 'ra-language-english';
12-
import { Box, Card, Stack, ThemeOptions, Typography } from '@mui/material';
13+
import fakeRestDataProvider from 'ra-data-fakerest';
14+
15+
import {
16+
Alert,
17+
Box,
18+
Card,
19+
Stack,
20+
ThemeOptions,
21+
Typography,
22+
} from '@mui/material';
1323

1424
import { TextInput } from '../input';
1525
import { SimpleForm } from '../form/SimpleForm';
1626
import { ShowButton, SaveButton } from '../button';
1727
import TopToolbar from '../layout/TopToolbar';
18-
import { Edit } from './Edit';
28+
import { Edit, EditProps } from './Edit';
1929
import { deepmerge } from '@mui/utils';
2030
import { defaultLightTheme } from '../theme';
31+
import { onlineManager, useMutationState } from '@tanstack/react-query';
2132

2233
export default { title: 'ra-ui-materialui/detail/Edit' };
2334

@@ -30,9 +41,9 @@ const book = {
3041
year: 1869,
3142
};
3243

33-
const dataProvider = {
34-
getOne: () => Promise.resolve({ data: book }),
35-
} as any;
44+
const dataProvider = fakeRestDataProvider({
45+
books: [book],
46+
});
3647

3748
const BookTitle = () => {
3849
const record = useRecordContext();
@@ -418,3 +429,91 @@ export const WithRenderProp = () => (
418429
</Admin>
419430
</TestMemoryRouter>
420431
);
432+
433+
export const Offline = ({
434+
isOnline = true,
435+
offline,
436+
}: {
437+
isOnline?: boolean;
438+
offline?: React.ReactNode;
439+
}) => {
440+
React.useEffect(() => {
441+
onlineManager.setOnline(isOnline);
442+
}, [isOnline]);
443+
return (
444+
<TestMemoryRouter initialEntries={['/books/1/show']}>
445+
<Admin dataProvider={dataProvider}>
446+
<Resource
447+
name="books"
448+
show={<BookEditOffline offline={offline} />}
449+
/>
450+
</Admin>
451+
</TestMemoryRouter>
452+
);
453+
};
454+
455+
const BookEditOffline = (props: EditProps) => {
456+
return (
457+
<Edit
458+
emptyWhileLoading
459+
{...props}
460+
redirect={false}
461+
mutationMode="pessimistic"
462+
>
463+
<OfflineIndicator />
464+
<SimpleForm>
465+
<TextInput source="title" />
466+
<TextInput source="author" />
467+
<TextInput source="summary" />
468+
<TextInput source="year" />
469+
</SimpleForm>
470+
</Edit>
471+
);
472+
};
473+
474+
const OfflineIndicator = () => {
475+
const pendingMutations = useMutationState({
476+
filters: {
477+
status: 'pending',
478+
},
479+
});
480+
481+
if (pendingMutations.length === 0) {
482+
return (
483+
<IsOffline>
484+
<Alert severity="warning">
485+
You are offline, the data may be outdated
486+
</Alert>
487+
</IsOffline>
488+
);
489+
}
490+
return (
491+
<IsOffline>
492+
<Alert severity="warning">You have pending mutations</Alert>
493+
</IsOffline>
494+
);
495+
};
496+
497+
const CustomOffline = () => {
498+
return <Alert severity="warning">You are offline!</Alert>;
499+
};
500+
501+
Offline.args = {
502+
isOnline: true,
503+
offline: 'default',
504+
};
505+
506+
Offline.argTypes = {
507+
isOnline: {
508+
control: { type: 'boolean' },
509+
},
510+
offline: {
511+
name: 'Offline component',
512+
control: { type: 'radio' },
513+
options: ['default', 'custom'],
514+
mapping: {
515+
default: undefined,
516+
custom: <CustomOffline />,
517+
},
518+
},
519+
};

packages/ra-ui-materialui/src/detail/Edit.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import * as React from 'react';
2-
import {
3-
EditBase,
4-
useCheckMinimumRequiredProps,
5-
RaRecord,
6-
EditBaseProps,
7-
} from 'ra-core';
2+
import { EditBase, RaRecord, EditBaseProps } from 'ra-core';
83
import { useThemeProps } from '@mui/material/styles';
94

105
import { EditView, EditViewProps } from './EditView';
@@ -95,6 +90,8 @@ export const Edit = <RecordType extends RaRecord = any>(
9590
transform={transform}
9691
disableAuthentication={disableAuthentication}
9792
loading={loading}
93+
// Disable offline support from ShowBase as it is handled by ShowView to keep the ShowView container
94+
offline={false}
9895
>
9996
<EditView {...rest} />
10097
</EditBase>

packages/ra-ui-materialui/src/detail/EditView.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import {
1818
import { EditActions } from './EditActions';
1919
import { Title } from '../layout';
2020
import { EditProps } from './Edit';
21+
import { Offline } from '../Offline';
2122

2223
const defaultActions = <EditActions />;
24+
const defaultOffline = <Offline />;
2325

2426
export const EditView = (props: EditViewProps) => {
2527
const {
@@ -30,18 +32,30 @@ export const EditView = (props: EditViewProps) => {
3032
className,
3133
component: Content = Card,
3234
emptyWhileLoading = false,
35+
offline = defaultOffline,
3336
title,
3437
...rest
3538
} = props;
3639

3740
const { hasShow } = useResourceDefinition();
3841
const editContext = useEditContext();
3942

40-
const { resource, defaultTitle, record, isPending } = editContext;
43+
const { resource, defaultTitle, record, isPaused, isPending } = editContext;
4144

4245
const finalActions =
4346
typeof actions === 'undefined' && hasShow ? defaultActions : actions;
4447

48+
if (!record && offline !== false && isPaused) {
49+
return (
50+
<Root className={clsx('edit-page', className)} {...rest}>
51+
<div className={clsx(EditClasses.main, EditClasses.noActions)}>
52+
<Content className={EditClasses.card}>{offline}</Content>
53+
{aside}
54+
</div>
55+
</Root>
56+
);
57+
}
58+
4559
if (!record && isPending && emptyWhileLoading) {
4660
return null;
4761
}
@@ -82,6 +96,7 @@ export interface EditViewProps
8296
aside?: ReactElement;
8397
component?: ElementType;
8498
emptyWhileLoading?: boolean;
99+
offline?: ReactNode;
85100
title?: string | ReactElement | false;
86101
sx?: SxProps<Theme>;
87102
render?: (editContext: EditControllerResult) => ReactNode;

0 commit comments

Comments
 (0)