@dxos/react-ui-menu 0.8.4-main.40e3dcdf1b → 0.8.4-main.422d1c7879
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +99 -98
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +99 -98
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/builder.d.ts +23 -4
- package/dist/types/src/builder.d.ts.map +1 -1
- package/dist/types/src/builder.test.d.ts +2 -0
- package/dist/types/src/builder.test.d.ts.map +1 -0
- package/dist/types/src/components/DropdownMenu.d.ts +3 -1
- package/dist/types/src/components/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu.d.ts +8 -4
- package/dist/types/src/components/Menu.d.ts.map +1 -1
- package/dist/types/src/components/{MenuItems.stories.d.ts → Menu.stories.d.ts} +1 -1
- package/dist/types/src/components/Menu.stories.d.ts.map +1 -0
- package/dist/types/src/components/ToolbarMenu.d.ts +4 -4
- package/dist/types/src/components/ToolbarMenu.d.ts.map +1 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts +3 -1
- package/dist/types/src/components/ToolbarMenu.stories.d.ts.map +1 -1
- package/dist/types/src/hooks/useMenuActions.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +7 -3
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -20
- package/src/builder.test.ts +224 -0
- package/src/builder.ts +41 -18
- package/src/components/DropdownMenu.tsx +0 -1
- package/src/components/Menu.test.tsx +0 -1
- package/src/components/Menu.tsx +9 -10
- package/src/components/ToolbarMenu.stories.tsx +13 -11
- package/src/components/ToolbarMenu.tsx +109 -85
- package/src/hooks/useMenuActions.ts +6 -7
- package/src/index.ts +1 -1
- package/src/testing/generator.ts +15 -10
- package/src/types.ts +1 -0
- package/dist/types/src/components/MenuItems.stories.d.ts.map +0 -1
- /package/src/components/{MenuItems.stories.tsx → Menu.stories.tsx} +0 -0
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
6
|
|
|
7
7
|
import { Icon, Toolbar as NaturalToolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
|
|
8
8
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
9
|
-
import {
|
|
9
|
+
import { composable, composableProps } from '@dxos/ui-theme';
|
|
10
10
|
import { type MenuActionProperties } from '@dxos/ui-types';
|
|
11
11
|
|
|
12
12
|
import { translationKey } from '../translations';
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
isSeparator,
|
|
21
21
|
} from '../types';
|
|
22
22
|
import { executeMenuAction } from '../util';
|
|
23
|
-
|
|
24
23
|
import { ActionLabel, actionLabel } from './ActionLabel';
|
|
25
24
|
import { DropdownMenu } from './DropdownMenu';
|
|
26
25
|
import { type MenuScopedProps, useMenuItems, useMenuScoped } from './Menu';
|
|
@@ -41,7 +40,7 @@ export type ToolbarMenuActionGroupProperties = (
|
|
|
41
40
|
) &
|
|
42
41
|
(MenuSingleSelectActionGroup | MenuMultipleSelectActionGroup);
|
|
43
42
|
|
|
44
|
-
export type ToolbarMenuProps = ToolbarRootProps
|
|
43
|
+
export type ToolbarMenuProps = ToolbarRootProps;
|
|
45
44
|
|
|
46
45
|
export type ToolbarMenuActionGroupProps = {
|
|
47
46
|
group: MenuItemGroup<ToolbarMenuActionGroupProperties>;
|
|
@@ -53,15 +52,13 @@ export type ToolbarMenuActionProps = {
|
|
|
53
52
|
action: MenuAction;
|
|
54
53
|
};
|
|
55
54
|
|
|
56
|
-
const ActionToolbarItem = ({
|
|
55
|
+
const ActionToolbarItem = ({ __menuScope, action }: MenuScopedProps<{ action: MenuAction }>) => {
|
|
57
56
|
const { iconSize, onAction } = useMenuScoped('ActionToolbarItem', __menuScope);
|
|
58
57
|
const { t } = useTranslation(translationKey);
|
|
59
58
|
|
|
60
|
-
const { icon, iconOnly = true, disabled, testId, hidden, classNames
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
? { icon, size: iconSize, iconOnly, label: actionLabel(action, t) }
|
|
64
|
-
: { children: <ActionLabel action={action} /> };
|
|
59
|
+
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
60
|
+
const buttonVariant =
|
|
61
|
+
(action.properties as { variant?: string }).variant === 'primary' ? ('primary' as const) : ('ghost' as const);
|
|
65
62
|
|
|
66
63
|
const handleClick = useCallback(() => {
|
|
67
64
|
if (onAction) {
|
|
@@ -71,23 +68,38 @@ const ActionToolbarItem = ({ action, __menuScope }: MenuScopedProps<{ action: Me
|
|
|
71
68
|
}
|
|
72
69
|
}, [action, onAction]);
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
const commonProps = {
|
|
72
|
+
variant: buttonVariant,
|
|
73
|
+
disabled,
|
|
74
|
+
classNames,
|
|
75
|
+
onClick: handleClick,
|
|
76
|
+
...(testId && { 'data-testid': testId }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (hidden) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return icon ? (
|
|
84
|
+
<NaturalToolbar.IconButton
|
|
76
85
|
key={action.id}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
{
|
|
82
|
-
classNames={classNames}
|
|
86
|
+
{...commonProps}
|
|
87
|
+
icon={icon}
|
|
88
|
+
size={iconSize}
|
|
89
|
+
iconOnly={iconOnly}
|
|
90
|
+
label={actionLabel(action, t)}
|
|
83
91
|
/>
|
|
92
|
+
) : (
|
|
93
|
+
<NaturalToolbar.Button key={action.id} {...commonProps}>
|
|
94
|
+
<ActionLabel action={action} />
|
|
95
|
+
</NaturalToolbar.Button>
|
|
84
96
|
);
|
|
85
97
|
};
|
|
86
98
|
|
|
87
99
|
const DropdownMenuToolbarItem = ({
|
|
100
|
+
__menuScope,
|
|
88
101
|
group,
|
|
89
102
|
items: propsItems,
|
|
90
|
-
__menuScope,
|
|
91
103
|
}: MenuScopedProps<ToolbarMenuActionGroupProps>) => {
|
|
92
104
|
const { iconOnly, disabled, testId } = group.properties;
|
|
93
105
|
const { t } = useTranslation(translationKey);
|
|
@@ -127,10 +139,11 @@ const DropdownMenuToolbarItem = ({
|
|
|
127
139
|
);
|
|
128
140
|
};
|
|
129
141
|
|
|
130
|
-
const ToggleGroupItem = ({ group, action
|
|
142
|
+
const ToggleGroupItem = ({ __menuScope, group, action }: MenuScopedProps<ToolbarMenuActionProps>) => {
|
|
131
143
|
const { iconSize, onAction } = useMenuScoped('ToggleGroupItem', __menuScope);
|
|
132
144
|
const { t } = useTranslation(translationKey);
|
|
133
145
|
const { icon, iconOnly = true, disabled, testId, hidden, classNames } = action.properties;
|
|
146
|
+
|
|
134
147
|
const handleClick = useCallback(() => {
|
|
135
148
|
if (onAction) {
|
|
136
149
|
onAction(action, { parent: group });
|
|
@@ -138,91 +151,102 @@ const ToggleGroupItem = ({ group, action, __menuScope }: MenuScopedProps<Toolbar
|
|
|
138
151
|
void executeMenuAction(action, { parent: group });
|
|
139
152
|
}
|
|
140
153
|
}, [action, group, onAction]);
|
|
141
|
-
const Root = icon ? NaturalToolbar.ToggleGroupIconItem : NaturalToolbar.ToggleGroupItem;
|
|
142
|
-
const rootProps = icon
|
|
143
|
-
? { icon, size: iconSize, iconOnly, label: actionLabel(action, t) }
|
|
144
|
-
: { children: <ActionLabel action={action} /> };
|
|
145
154
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
const commonProps = {
|
|
156
|
+
value: action.id,
|
|
157
|
+
disabled,
|
|
158
|
+
variant: 'ghost' as const,
|
|
159
|
+
classNames,
|
|
160
|
+
onClick: handleClick,
|
|
161
|
+
...(testId && { 'data-testid': testId }),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return hidden ? null : icon ? (
|
|
165
|
+
<NaturalToolbar.ToggleGroupIconItem
|
|
166
|
+
{...commonProps}
|
|
167
|
+
icon={icon}
|
|
168
|
+
size={iconSize}
|
|
169
|
+
iconOnly={iconOnly}
|
|
170
|
+
label={actionLabel(action, t)}
|
|
156
171
|
/>
|
|
172
|
+
) : (
|
|
173
|
+
<NaturalToolbar.ToggleGroupItem {...commonProps}>
|
|
174
|
+
<ActionLabel action={action} />
|
|
175
|
+
</NaturalToolbar.ToggleGroupItem>
|
|
157
176
|
);
|
|
158
177
|
};
|
|
159
178
|
|
|
160
179
|
const ToggleGroupToolbarItem = ({
|
|
161
|
-
group,
|
|
162
|
-
items: propsItems,
|
|
163
180
|
__menuScope,
|
|
181
|
+
group,
|
|
182
|
+
items: itemsProp,
|
|
164
183
|
}: MenuScopedProps<ToolbarMenuActionGroupProps>) => {
|
|
165
|
-
const items = useMenuItems(group,
|
|
166
|
-
const { selectCardinality
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
const items = useMenuItems(group, itemsProp, 'ToggleGroupToolbarItem', __menuScope);
|
|
185
|
+
const { selectCardinality } = group.properties;
|
|
186
|
+
|
|
187
|
+
// TODO(thure): Handle other menu item types.
|
|
188
|
+
const children = (items as MenuAction[]).map((action) => (
|
|
189
|
+
<ToggleGroupItem key={action.id} group={group} action={action} />
|
|
190
|
+
));
|
|
191
|
+
|
|
192
|
+
if (selectCardinality === 'multiple') {
|
|
193
|
+
return (
|
|
194
|
+
<NaturalToolbar.ToggleGroup type='multiple' value={group.properties.value}>
|
|
195
|
+
{children}
|
|
196
|
+
</NaturalToolbar.ToggleGroup>
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
return (
|
|
200
|
+
<NaturalToolbar.ToggleGroup type='single' value={group.properties.value}>
|
|
201
|
+
{children}
|
|
202
|
+
</NaturalToolbar.ToggleGroup>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
180
205
|
};
|
|
181
206
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
return (
|
|
198
|
-
<NaturalToolbar.Root
|
|
199
|
-
{...props}
|
|
200
|
-
classNames={[attendableId, classNames]}
|
|
201
|
-
disabled={!alwaysActive && !hasAttention}
|
|
202
|
-
layoutManaged={textBlockWidthProp}
|
|
203
|
-
>
|
|
204
|
-
<InnerRoot {...innerRootProps}>
|
|
205
|
-
{items?.map((item: MenuItem, i: number) => (
|
|
206
|
-
<ToolbarMenuItem key={item.id ?? i} item={item} />
|
|
207
|
+
export const ToolbarMenu = composable<HTMLDivElement, MenuScopedProps<ToolbarMenuProps>>(
|
|
208
|
+
({ __menuScope, children, ...props }, forwardedRef) => {
|
|
209
|
+
const items = useMenuItems(undefined, undefined, 'ToolbarMenu', __menuScope);
|
|
210
|
+
const { attendableId, alwaysActive } = useMenuScoped('ToolbarMenu', __menuScope);
|
|
211
|
+
const { hasAttention } = useAttention(attendableId);
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<NaturalToolbar.Root
|
|
215
|
+
{...composableProps(props, { classNames: attendableId })}
|
|
216
|
+
disabled={!alwaysActive && !hasAttention}
|
|
217
|
+
ref={forwardedRef}
|
|
218
|
+
>
|
|
219
|
+
{items?.map((item: MenuItem) => (
|
|
220
|
+
<ToolbarMenuItem key={item.id} __menuScope={__menuScope} item={item} />
|
|
207
221
|
))}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
222
|
+
{children}
|
|
223
|
+
</NaturalToolbar.Root>
|
|
224
|
+
);
|
|
225
|
+
},
|
|
226
|
+
);
|
|
212
227
|
|
|
213
|
-
const ToolbarMenuItem = ({ item }: { item: MenuItem }) => {
|
|
228
|
+
const ToolbarMenuItem = ({ __menuScope, item }: MenuScopedProps<{ item: MenuItem }>) => {
|
|
214
229
|
if (isSeparator(item)) {
|
|
215
230
|
return <NaturalToolbar.Separator variant={item.properties.variant} />;
|
|
216
231
|
}
|
|
217
232
|
|
|
218
|
-
// TODO(thure): Figure out type narrowing that doesn’t require so much use of `as`.
|
|
219
233
|
if (isMenuGroup(item)) {
|
|
220
234
|
if (item.properties.variant === 'dropdownMenu') {
|
|
221
|
-
return
|
|
235
|
+
return (
|
|
236
|
+
<DropdownMenuToolbarItem
|
|
237
|
+
__menuScope={__menuScope}
|
|
238
|
+
group={item as MenuItemGroup<ToolbarMenuActionGroupProperties>}
|
|
239
|
+
/>
|
|
240
|
+
);
|
|
222
241
|
} else {
|
|
223
|
-
return
|
|
242
|
+
return (
|
|
243
|
+
<ToggleGroupToolbarItem
|
|
244
|
+
__menuScope={__menuScope}
|
|
245
|
+
group={item as MenuItemGroup<ToolbarMenuActionGroupProperties>}
|
|
246
|
+
/>
|
|
247
|
+
);
|
|
224
248
|
}
|
|
225
249
|
}
|
|
226
250
|
|
|
227
|
-
return <ActionToolbarItem action={item as MenuAction} />;
|
|
251
|
+
return <ActionToolbarItem __menuScope={__menuScope} action={item as MenuAction} />;
|
|
228
252
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
-
import { useCallback, useContext,
|
|
6
|
+
import { useCallback, useContext, useMemo } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Graph, Node } from '@dxos/app-graph';
|
|
9
9
|
|
|
@@ -26,14 +26,13 @@ export const useMenuActions = (props: Atom.Atom<ActionGraphProps>): MenuActions
|
|
|
26
26
|
|
|
27
27
|
// Create a new graph whenever props change to preserve correct order.
|
|
28
28
|
// (Graph.addEdges appends rather than replaces, which breaks ordering on updates.)
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// NOTE: Using useMemo rather than a ref-mutation pattern to avoid calling registry.set during render,
|
|
30
|
+
// which would trigger atom state updates in other components (setState-in-render React warning).
|
|
31
|
+
const graph = useMemo(() => {
|
|
31
32
|
const newGraph = Graph.make({ registry });
|
|
32
33
|
newGraph.pipe(Graph.addNodes(menuGraphProps.nodes as Node.NodeArg<any>[]), Graph.addEdges(menuGraphProps.edges));
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const graph = graphRef.current.graph;
|
|
34
|
+
return newGraph;
|
|
35
|
+
}, [registry, menuGraphProps]);
|
|
37
36
|
|
|
38
37
|
const items: MenuItemsAccessor = useCallback(
|
|
39
38
|
(group?: MenuItemGroup) => {
|
package/src/index.ts
CHANGED
|
@@ -6,4 +6,4 @@ export * from './components';
|
|
|
6
6
|
export * from './hooks';
|
|
7
7
|
export * from './types';
|
|
8
8
|
export { createMenuAction, createMenuItemGroup, createLineSeparator, createGapSeparator } from './util';
|
|
9
|
-
export { MenuBuilder } from './builder';
|
|
9
|
+
export { type ActionGroupBuilder, type ActionGroupBuilderFn, MenuBuilder } from './builder';
|
package/src/testing/generator.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Atom, RegistryContext } from '@effect-atom/atom-react';
|
|
5
|
+
import { Atom, Registry, RegistryContext } from '@effect-atom/atom-react';
|
|
6
6
|
import { useContext, useEffect } from 'react';
|
|
7
7
|
|
|
8
8
|
import { Graph, Node } from '@dxos/app-graph';
|
|
9
|
-
import {
|
|
9
|
+
import { random } from '@dxos/random';
|
|
10
10
|
|
|
11
11
|
import { type ActionGraphProps } from '../hooks/useMenuActions';
|
|
12
12
|
import { type MenuItem, type MenuItemGroup, type MenuItemsAccessor } from '../types';
|
|
@@ -38,15 +38,15 @@ const icons = {
|
|
|
38
38
|
|
|
39
39
|
export const createActions = (params?: CreateActionsProps) => {
|
|
40
40
|
const { callback = () => console.log('invoke'), count = 12, type = Node.ActionType } = params ?? {};
|
|
41
|
-
return
|
|
41
|
+
return random.helpers.multiple(
|
|
42
42
|
() => ({
|
|
43
|
-
id:
|
|
43
|
+
id: random.string.uuid(),
|
|
44
44
|
type,
|
|
45
45
|
data: type === Node.ActionGroupType ? Node.actionGroupSymbol : callback,
|
|
46
46
|
properties: {
|
|
47
|
-
label:
|
|
48
|
-
icon:
|
|
49
|
-
disabled:
|
|
47
|
+
label: random.lorem.words(2),
|
|
48
|
+
icon: random.helpers.arrayElement(icons[random.helpers.arrayElement(Object.keys(icons)) as keyof typeof icons]),
|
|
49
|
+
disabled: random.helpers.arrayElement([true, false]),
|
|
50
50
|
...(type === Node.ActionGroupType && { variant: 'dropdownMenu' }),
|
|
51
51
|
},
|
|
52
52
|
}),
|
|
@@ -70,8 +70,13 @@ const buildNestedActions = (): ActionGraphProps => {
|
|
|
70
70
|
|
|
71
71
|
export const createNestedActions = Atom.make(buildNestedActions()).pipe(Atom.keepAlive);
|
|
72
72
|
|
|
73
|
-
export const createNestedActionsResolver = (
|
|
74
|
-
|
|
73
|
+
export const createNestedActionsResolver = (props?: {
|
|
74
|
+
groupParams?: CreateActionsProps;
|
|
75
|
+
params?: CreateActionsProps;
|
|
76
|
+
registry?: Registry.Registry;
|
|
77
|
+
}) => {
|
|
78
|
+
const { groupParams, params, registry } = props ?? {};
|
|
79
|
+
const graph = Graph.make({ ...(registry && { registry }) });
|
|
75
80
|
const actionGroups = createActions({ type: Node.ActionGroupType, ...groupParams });
|
|
76
81
|
actionGroups.forEach((group) => {
|
|
77
82
|
const actions = createActions(params);
|
|
@@ -107,7 +112,7 @@ export const useMutateActions = (actionsAtom: Atom.Writable<ActionGraphProps>) =
|
|
|
107
112
|
...node,
|
|
108
113
|
properties: {
|
|
109
114
|
...node.properties,
|
|
110
|
-
icon:
|
|
115
|
+
icon: random.helpers.arrayElement(node.properties?.icon?.endsWith('regular') ? icons.fill : icons.regular),
|
|
111
116
|
disabled: !node.properties?.disabled,
|
|
112
117
|
},
|
|
113
118
|
};
|
package/src/types.ts
CHANGED
|
@@ -50,6 +50,7 @@ export type MenuContextValue = {
|
|
|
50
50
|
items: MenuItemsAccessor;
|
|
51
51
|
iconSize: IconButtonProps['size'];
|
|
52
52
|
attendableId?: string;
|
|
53
|
+
/** TODO(burdon): Remove and assume always active if attendableId is undefined. */
|
|
53
54
|
/** If true, the menu is always active regardless of attention state. */
|
|
54
55
|
alwaysActive?: boolean;
|
|
55
56
|
/** Atom holding the current set of imperatively added menu items. */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MenuItems.stories.d.ts","sourceRoot":"","sources":["../../../../src/components/MenuItems.stories.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAuC,MAAM,OAAO,CAAC;AAgB5D,QAAA,MAAM,IAAI;;;;;;;;;;;CAOwB,CAAC;AAEnC,eAAe,IAAI,CAAC;AAEpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AA0CnC,eAAO,MAAM,WAAW,EAAE,KAsBzB,CAAC;AAaF,eAAO,MAAM,aAAa,EAAE,KA8C3B,CAAC;AAsBF,eAAO,MAAM,eAAe,EAAE,KAqB7B,CAAC;AA0CF,eAAO,MAAM,gBAAgB,EAAE,KAuB9B,CAAC"}
|
|
File without changes
|