Skip to content

Commit 0b5936f

Browse files
committed
chore(platform): some improvements
- user-state shouldn't be undefined - add dialog-service
1 parent 02d4072 commit 0b5936f

File tree

11 files changed

+149
-73
lines changed

11 files changed

+149
-73
lines changed

packages/platform/src/app/App.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { DRootProps } from '@react-devui/ui';
33
import type { DLang } from '@react-devui/ui/utils/types';
44

55
import { isNull } from 'lodash';
6-
import { useEffect, useMemo, useState } from 'react';
6+
import React, { useEffect, useMemo, useState } from 'react';
77

88
import { useAsync, useMount, useStorage } from '@react-devui/hooks';
99
import { DNotification, DToast } from '@react-devui/ui';
@@ -12,7 +12,7 @@ import { DRoot } from '@react-devui/ui';
1212
import { AppRoutes } from './Routes';
1313
import { STORAGE_KEY } from './config/storage';
1414
import { TOKEN, useHttp, useInit } from './core';
15-
import { useNotifications, useToasts } from './core/state';
15+
import { useDialogs, useNotifications, useToasts } from './core/state';
1616

1717
export type AppTheme = 'light' | 'dark';
1818

@@ -23,6 +23,7 @@ export function App() {
2323
const [loading, setLoading] = useState(!isNull(TOKEN.value));
2424
const languageStorage = useStorage<DLang>(...STORAGE_KEY.language);
2525
const themeStorage = useStorage<AppTheme>(...STORAGE_KEY.theme);
26+
const [dialogs] = useDialogs();
2627
const [notifications] = useNotifications();
2728
const [toasts] = useToasts();
2829

@@ -91,6 +92,7 @@ export function App() {
9192
return (
9293
<DRoot context={rootContext}>
9394
{loading ? null : <AppRoutes />}
95+
{dialogs.map(({ key, type, props }) => React.createElement(type, { key, ...props }))}
9496
{notifications.map(({ key, ...props }) => (
9597
<DNotification {...props} key={key}></DNotification>
9698
))}

packages/platform/src/app/core/state.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface UserState {
77
avatar?: string;
88
permission: (string | number)[];
99
}
10-
export const useUserState = createGlobalState<UserState>();
10+
export const useUserState = createGlobalState<UserState>({} as any);
1111

1212
export interface NotificationItem {
1313
id: string;
@@ -22,3 +22,5 @@ export const useNotificationState = createGlobalState<NotificationItem[]>();
2222
export const useNotifications = createGlobalState<(DNotificationProps & { key: string | number })[]>([]);
2323

2424
export const useToasts = createGlobalState<(DToastProps & { key: string | number })[]>([]);
25+
26+
export const useDialogs = createGlobalState<{ key: string | number; type: any; props: any }[]>([]);

packages/platform/src/app/routes/layout/header/user/User.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { isUndefined } from 'lodash';
21
import { useTranslation } from 'react-i18next';
32
import { useNavigate } from 'react-router-dom';
43

@@ -13,7 +12,7 @@ export function AppUser(props: React.ButtonHTMLAttributes<HTMLButtonElement>): J
1312
const { t } = useTranslation();
1413
const navigate = useNavigate();
1514

16-
return isUndefined(user) ? null : (
15+
return (
1716
<DDropdown
1817
style={{ minWidth: 160 }}
1918
dList={[
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { OpenSettingFn } from '../../../utils/types';
1+
import type { AppDialogServiceSupport } from '../../../utils/dialog-service';
22
import type { DeviceData } from './StandardTable';
33
import type { DSelectItem } from '@react-devui/ui/components/select';
44

55
import { isUndefined } from 'lodash';
6-
import React, { useImperativeHandle, useState } from 'react';
6+
import { useState } from 'react';
77

8-
import { useEventCallback } from '@react-devui/hooks';
8+
import { useMount } from '@react-devui/hooks';
99
import { FormControl, FormGroup, useForm, Validators } from '@react-devui/ui';
1010
import { DForm, DInput, DModal, DSelect } from '@react-devui/ui';
1111

@@ -14,38 +14,31 @@ import { useHttp } from '../../../core';
1414
import { useAPI } from '../../../hooks';
1515

1616
export interface AppDeviceModalProps {
17+
aDevice: DeviceData | undefined;
1718
onSuccess: () => void;
1819
}
1920

20-
function DeviceModal(props: AppDeviceModalProps, ref: React.ForwardedRef<OpenSettingFn<DeviceData>>): JSX.Element | null {
21-
const { onSuccess } = props;
21+
export function AppDeviceModal(props: AppDeviceModalProps): JSX.Element | null {
22+
const { aDevice, onSuccess, aVisible, onClose, afterVisibleChange } = props as AppDeviceModalProps & AppDialogServiceSupport;
2223

2324
const http = useHttp();
24-
const httpOfInit = useHttp();
2525
const modelApi = useAPI(http, '/device/model');
26-
const modelApiOfInit = useAPI(httpOfInit, '/device/model');
2726

28-
const [visible, setVisible] = useState(false);
29-
const [device, setDevice] = useState<DeviceData>();
30-
const [form, updateForm] = useForm(
31-
() =>
32-
new FormGroup({
33-
name: new FormControl('', Validators.required),
34-
model: new FormControl<string | null>(null, Validators.required),
35-
})
36-
);
27+
const [form, updateForm] = useForm(() => {
28+
const form = new FormGroup({
29+
name: new FormControl('', Validators.required),
30+
model: new FormControl<string | null>(null, Validators.required),
31+
});
32+
if (aDevice) {
33+
form.reset({ name: aDevice.name });
34+
}
35+
return form;
36+
});
3737

3838
const [modelList, setModelList] = useState<DSelectItem<string>[]>();
3939

40-
const open = useEventCallback<OpenSettingFn<DeviceData>>((device) => {
41-
setVisible(true);
42-
setDevice(device);
43-
44-
form.reset(device ? { name: device.name } : undefined);
45-
updateForm();
46-
47-
setModelList(undefined);
48-
modelApiOfInit.list().subscribe({
40+
useMount(() => {
41+
modelApi.list().subscribe({
4942
next: (res) => {
5043
setModelList(
5144
res.resources.map((model) => ({
@@ -54,20 +47,18 @@ function DeviceModal(props: AppDeviceModalProps, ref: React.ForwardedRef<OpenSet
5447
disabled: model.disabled,
5548
}))
5649
);
57-
if (device) {
58-
form.patchValue({ model: device.model });
50+
if (aDevice) {
51+
form.patchValue({ model: aDevice.model });
5952
updateForm();
6053
}
6154
},
6255
});
6356
});
6457

65-
useImperativeHandle(ref, () => open, [open]);
66-
6758
return (
6859
<DModal
69-
dVisible={visible}
70-
dHeader={`${device ? 'Edit' : 'Add'} Device`}
60+
dVisible={aVisible}
61+
dHeader={`${aDevice ? 'Edit' : 'Add'} Device`}
7162
dFooter={
7263
<DModal.Footer
7364
dOkProps={{ disabled: !form.valid }}
@@ -83,11 +74,10 @@ function DeviceModal(props: AppDeviceModalProps, ref: React.ForwardedRef<OpenSet
8374
}
8475
></DModal.Footer>
8576
}
77+
dSkipFirstTransition={false}
8678
dMaskClosable={false}
87-
onClose={() => {
88-
httpOfInit.abortAll();
89-
setVisible(false);
90-
}}
79+
onClose={onClose}
80+
afterVisibleChange={afterVisibleChange}
9181
>
9282
<AppResponsiveForm>
9383
<DForm dUpdate={updateForm} dLabelWidth="6em">
@@ -106,5 +96,3 @@ function DeviceModal(props: AppDeviceModalProps, ref: React.ForwardedRef<OpenSet
10696
</DModal>
10797
);
10898
}
109-
110-
export const AppDeviceModal = React.forwardRef(DeviceModal);

packages/platform/src/app/routes/list/standard-table/StandardTable.tsx

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { DeviceDoc, OpenSettingFn, StandardQueryParams } from '../../../utils/types';
1+
import type { DeviceDoc, StandardQueryParams } from '../../../utils/types';
22
import type { DSelectItem } from '@react-devui/ui/components/select';
33

44
import { isUndefined } from 'lodash';
5-
import { useEffect, useRef, useState } from 'react';
5+
import { useEffect, useState } from 'react';
66
import { useTranslation } from 'react-i18next';
77
import { Link } from 'react-router-dom';
88

@@ -13,7 +13,7 @@ import { DButton, DCard, DCheckbox, DDropdown, DModal, DPagination, DSelect, DSe
1313
import { AppRouteHeader, AppStatusDot, AppTableFilter } from '../../../components';
1414
import { useHttp } from '../../../core';
1515
import { useAPI, useQueryParams } from '../../../hooks';
16-
import { AppRoute } from '../../../utils';
16+
import { AppRoute, DialogService } from '../../../utils';
1717
import { AppDeviceModal } from './DeviceModal';
1818
import styles from './StandardTable.module.scss';
1919

@@ -29,8 +29,6 @@ interface DeviceQueryParams {
2929
}
3030

3131
export default AppRoute(() => {
32-
const deviceModalRef = useRef<OpenSettingFn<DeviceData>>(null);
33-
3432
const { t } = useTranslation();
3533
const http = useHttp();
3634
const modelApi = useAPI(http, '/device/model');
@@ -118,6 +116,15 @@ export default AppRoute(() => {
118116
// eslint-disable-next-line react-hooks/exhaustive-deps
119117
}, [updateDeviceTable]);
120118

119+
const openDeviceModal = (device?: DeviceData) => {
120+
DialogService.open(AppDeviceModal, {
121+
aDevice: device,
122+
onSuccess: () => {
123+
setUpdateDeviceTable((n) => n + 1);
124+
},
125+
});
126+
};
127+
121128
return (
122129
<>
123130
{paramsOfDeleteModal && (
@@ -156,12 +163,6 @@ export default AppRoute(() => {
156163
}}
157164
/>
158165
)}
159-
<AppDeviceModal
160-
ref={deviceModalRef}
161-
onSuccess={() => {
162-
setUpdateDeviceTable((n) => n + 1);
163-
}}
164-
/>
165166
<AppRouteHeader>
166167
<AppRouteHeader.Breadcrumb
167168
aList={[
@@ -173,7 +174,7 @@ export default AppRoute(() => {
173174
aActions={[
174175
<DButton
175176
onClick={() => {
176-
deviceModalRef.current?.();
177+
openDeviceModal();
177178
}}
178179
dIcon={<PlusOutlined />}
179180
>
@@ -337,7 +338,7 @@ export default AppRoute(() => {
337338
onItemClick={(action) => {
338339
switch (action) {
339340
case 'edit':
340-
deviceModalRef.current?.(data);
341+
openDeviceModal(data);
341342
break;
342343

343344
case 'delete':

packages/platform/src/app/routes/list/standard-table/detail/Detail.tsx

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import type { OpenSettingFn } from '../../../../utils/types';
21
import type { DeviceData } from '../StandardTable';
32

43
import { isUndefined } from 'lodash';
5-
import { useEffect, useRef, useState } from 'react';
4+
import { useEffect, useState } from 'react';
65
import { useTranslation } from 'react-i18next';
76
import { useParams } from 'react-router-dom';
87

@@ -12,13 +11,11 @@ import { DButton, DCard, DSeparator, DSpinner, DTable } from '@react-devui/ui';
1211
import { AppDetailView, AppRouteHeader } from '../../../../components';
1312
import { useHttp } from '../../../../core';
1413
import { useAPI } from '../../../../hooks';
15-
import { AppRoute } from '../../../../utils';
14+
import { AppRoute, DialogService } from '../../../../utils';
1615
import { AppDeviceModal } from '../DeviceModal';
1716
import styles from './Detail.module.scss';
1817

1918
export default AppRoute(() => {
20-
const deviceModalRef = useRef<OpenSettingFn<DeviceData>>(null);
21-
2219
const { t } = useTranslation();
2320

2421
const http = useHttp();
@@ -44,12 +41,6 @@ export default AppRoute(() => {
4441

4542
return (
4643
<>
47-
<AppDeviceModal
48-
ref={deviceModalRef}
49-
onSuccess={() => {
50-
setUpdateDevice((n) => n + 1);
51-
}}
52-
/>
5344
<AppRouteHeader>
5445
<AppRouteHeader.Breadcrumb
5546
aList={[
@@ -68,7 +59,12 @@ export default AppRoute(() => {
6859
<DButton
6960
disabled={isUndefined(device)}
7061
onClick={() => {
71-
deviceModalRef.current?.(device);
62+
DialogService.open(AppDeviceModal, {
63+
aDevice: device,
64+
onSuccess: () => {
65+
setUpdateDevice((n) => n + 1);
66+
},
67+
});
7268
}}
7369
dIcon={<EditOutlined />}
7470
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/* eslint-disable @typescript-eslint/ban-types */
2+
import { useDialogs } from '../core/state';
3+
import { getGlobalKey } from './vars';
4+
5+
export interface AppDialogServiceSupport {
6+
aVisible: boolean;
7+
onClose: () => void;
8+
afterVisibleChange: (visible: boolean) => void;
9+
}
10+
11+
interface DialogInstance<P extends {}> {
12+
key: string | number;
13+
close: () => void;
14+
rerender: (props: P) => void;
15+
}
16+
17+
export class DialogService {
18+
static open<P extends {}>(type: React.FC<P>, props: P, _key?: string | number): DialogInstance<P> {
19+
const key = _key ?? getGlobalKey();
20+
21+
useDialogs.setState((draft) => {
22+
draft.push({
23+
key,
24+
type,
25+
props: {
26+
...props,
27+
aVisible: true,
28+
onClose: () => {
29+
props['onClose']?.();
30+
31+
DialogService.close(key);
32+
},
33+
afterVisibleChange: (visible: boolean) => {
34+
props['afterVisibleChange']?.(visible);
35+
36+
if (!visible) {
37+
const index = useDialogs.state.findIndex((n) => n.key === key);
38+
if (index !== -1) {
39+
useDialogs.setState((draft) => {
40+
draft.splice(index, 1);
41+
});
42+
}
43+
}
44+
},
45+
},
46+
});
47+
});
48+
49+
return {
50+
key,
51+
close: () => {
52+
DialogService.close(key);
53+
},
54+
rerender: (props) => {
55+
DialogService.rerender(key, type, props);
56+
},
57+
};
58+
}
59+
60+
static close(key: string | number) {
61+
const index = useDialogs.state.findIndex((n) => n.key === key);
62+
if (index !== -1) {
63+
useDialogs.setState((draft) => {
64+
draft[index].props.aVisible = false;
65+
});
66+
}
67+
}
68+
69+
static rerender(key: string | number, type: any, props: any) {
70+
const index = useDialogs.state.findIndex((n) => n.key === key);
71+
if (index !== -1) {
72+
useDialogs.setState((draft) => {
73+
draft.splice(index, 1, { key, type, props: Object.assign(draft[index].props, props) });
74+
});
75+
}
76+
}
77+
78+
static closeAll(animation = true) {
79+
if (animation) {
80+
useDialogs.setState((draft) => {
81+
draft.forEach((toast) => {
82+
toast.props.aVisible = false;
83+
});
84+
});
85+
} else {
86+
useDialogs.setState([]);
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)