Skip to content

Commit 4f4f31b

Browse files
committed
feat(hooks): add useACL
1 parent de80fb8 commit 4f4f31b

File tree

9 files changed

+43
-53
lines changed

9 files changed

+43
-53
lines changed

packages/hooks/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { useStorage } from './storage';
22
export { createGlobalState } from './createGlobalState';
3+
export { useACL } from './useACL';
34
export { useAsync } from './useAsync';
45
export { useEvent } from './useEvent';
56
export { useEventCallback } from './useEventCallback';
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,46 @@
11
import { isArray } from 'lodash';
2-
import { useEffect } from 'react';
3-
4-
import { useForceUpdate } from '@react-devui/hooks';
2+
import { useSyncExternalStore } from 'react';
53

64
export type Control = string | number;
75
export type ControlMode = 'one' | 'all';
8-
export interface ACLConfig {
9-
full?: boolean;
10-
controls?: Control[];
11-
}
126

137
export class ACL {
14-
public controlMode: ControlMode = 'one';
8+
private _full = false;
9+
private _controls = new Set<Control>();
1510

16-
private _updates = new Set<() => void>();
17-
private _update() {
18-
for (const cb of this._updates) {
19-
cb();
20-
}
21-
}
22-
public addUpdateListener(cb: () => void) {
23-
this._updates.add(cb);
24-
return () => {
25-
this._updates.delete(cb);
26-
};
27-
}
28-
29-
private _full: boolean;
3011
public get full(): boolean {
3112
return this._full;
3213
}
33-
public setFull(full: boolean) {
34-
this._full = full;
35-
this._update();
36-
}
3714

38-
private _controls: Set<Control>;
3915
public get controls(): Control[] {
4016
return Array.from(this._controls);
4117
}
18+
19+
public setFull(full: boolean) {
20+
this._full = full;
21+
emitChange();
22+
}
23+
4224
public set(control: Control[]): void {
4325
this._controls = new Set(control);
44-
this._update();
26+
emitChange();
4527
}
28+
4629
public add(control: Control | Control[]): void {
4730
for (const v of isArray(control) ? control : [control]) {
4831
this._controls.add(v);
4932
}
50-
this._update();
33+
emitChange();
5134
}
35+
5236
public remove(control: Control | Control[]): void {
5337
for (const v of isArray(control) ? control : [control]) {
5438
this._controls.delete(v);
5539
}
56-
this._update();
40+
emitChange();
5741
}
5842

59-
public can(control: Control | Control[], mode = this.controlMode): boolean {
43+
public can(control: Control | Control[], mode: ControlMode = 'one'): boolean {
6044
if (this._full) {
6145
return true;
6246
}
@@ -75,21 +59,29 @@ export class ACL {
7559
}
7660
return false;
7761
}
78-
79-
constructor(config?: ACLConfig) {
80-
this._full = config?.full ?? false;
81-
this._controls = new Set(config?.controls ?? []);
82-
}
8362
}
8463

8564
const acl = new ACL();
8665

87-
export function useACL() {
88-
const forceUpdate = useForceUpdate();
66+
let listeners: (() => void)[] = [];
8967

90-
useEffect(() => {
91-
return acl.addUpdateListener(forceUpdate);
92-
}, [forceUpdate]);
68+
function subscribe(onStoreChange: () => void) {
69+
listeners = listeners.concat([onStoreChange]);
70+
return () => {
71+
listeners = listeners.filter((f) => f !== onStoreChange);
72+
};
73+
}
9374

75+
function getSnapshot() {
9476
return acl;
9577
}
78+
79+
function emitChange() {
80+
for (const listener of listeners) {
81+
listener();
82+
}
83+
}
84+
85+
export function useACL() {
86+
return useSyncExternalStore(subscribe, getSnapshot);
87+
}

packages/platform/src/app/Routes.guard.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import type { CanActivateFn } from './Routes';
33
import { isNull, isObject } from 'lodash';
44
import { Navigate, useLocation } from 'react-router-dom';
55

6+
import { useACL } from '@react-devui/hooks';
7+
68
import { LOGIN_PATH, PREV_ROUTE_KEY } from './config/other';
7-
import { TOKEN, useACL } from './core';
9+
import { TOKEN } from './core';
810

911
export function useACLGuard(): CanActivateFn {
1012
const acl = useACL();

packages/platform/src/app/Routes.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Control, ControlMode } from './core/useACL';
1+
import type { Control, ControlMode } from '@react-devui/hooks/useACL';
22
import type { IndexRouteObject, NonIndexRouteObject, RouteMatch } from 'react-router-dom';
33

44
import { isFunction, isUndefined, nth } from 'lodash';

packages/platform/src/app/config/menu.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Control, ControlMode } from '../core/useACL';
1+
import type { Control, ControlMode } from '@react-devui/hooks/useACL';
22

33
import { DashboardOutlined, ExceptionOutlined, ExperimentOutlined, ProfileOutlined } from '@react-devui/icons';
44

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export { useHttp } from './http';
22
export { TOKEN, useRefreshToken } from './token';
33
export { GlobalStore } from './store';
4-
export { useACL } from './useACL';
54
export { useInit } from './useInit';
65
export { useMenu } from './useMenu';

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { AppNotification, AppUser } from './store';
22

3+
import { useACL } from '@react-devui/hooks';
4+
35
import { ROLE_ACL } from '../config/acl';
46
import { useHttp } from './http';
57
import { GlobalStore } from './store';
68
import { useRefreshToken } from './token';
7-
import { useACL } from './useACL';
89

910
export function useInit() {
1011
const http = useHttp();

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import React from 'react';
77
import { useTranslation } from 'react-i18next';
88
import { Link, useLocation } from 'react-router-dom';
99

10-
import { useMount } from '@react-devui/hooks';
10+
import { useACL, useMount } from '@react-devui/hooks';
1111

1212
import { MENU } from '../config/menu';
1313
import { GlobalStore } from './store';
14-
import { useACL } from './useACL';
1514

1615
export function useMenu() {
1716
const acl = useACL();

packages/platform/src/app/routes/test/acl/ACL.tsx

+1-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useNavigate } from 'react-router-dom';
44

5+
import { useACL } from '@react-devui/hooks';
56
import { DAlert, DButton, DCard, DTable, DTag } from '@react-devui/ui';
67

78
import { AppRouteHeader } from '../../../components';
8-
import { useACL } from '../../../core';
99
import { AppRoute } from '../../../utils';
1010

1111
import styles from './ACL.module.scss';
@@ -40,10 +40,6 @@ export default AppRoute(() => {
4040
<DTable.Th>Controls</DTable.Th>
4141
<DTable.Td>{acl.controls.join(', ') || '-'}</DTable.Td>
4242
</tr>
43-
<tr>
44-
<DTable.Th>ControlMode</DTable.Th>
45-
<DTable.Td>{acl.controlMode}</DTable.Td>
46-
</tr>
4743
</tbody>
4844
</table>
4945
</DTable>

0 commit comments

Comments
 (0)