@squiz/resource-browser 1.68.1 → 1.69.1
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/CHANGELOG.md +12 -0
- package/lib/Hooks/usePreselectedResourcePath.js +8 -3
- package/lib/Hooks/useRecentLocations.d.ts +3 -8
- package/lib/Hooks/useRecentLocations.js +5 -1
- package/lib/Hooks/useRecentResourcesPaths.d.ts +20 -0
- package/lib/Hooks/useRecentResourcesPaths.js +30 -0
- package/lib/Hooks/useResource.d.ts +13 -0
- package/lib/Hooks/useResource.js +12 -1
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +62 -21
- package/lib/SourceDropdown/SourceDropdown.d.ts +3 -1
- package/lib/SourceDropdown/SourceDropdown.js +24 -22
- package/lib/SourceList/SourceList.d.ts +5 -1
- package/lib/SourceList/SourceList.js +19 -14
- package/lib/index.css +3 -0
- package/package.json +1 -1
- package/src/Hooks/usePreselectedResourcePath.ts +9 -5
- package/src/Hooks/useRecentLocations.spec.ts +36 -40
- package/src/Hooks/useRecentLocations.ts +10 -11
- package/src/Hooks/useRecentResourcesPaths.ts +54 -0
- package/src/Hooks/useResource.spec.ts +30 -1
- package/src/Hooks/useResource.ts +19 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +18 -0
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +63 -21
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +12 -1
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +79 -30
- package/src/SourceDropdown/SourceDropdown.spec.tsx +92 -27
- package/src/SourceDropdown/SourceDropdown.tsx +33 -29
- package/src/SourceList/SourceList.spec.tsx +133 -71
- package/src/SourceList/SourceList.stories.tsx +14 -6
- package/src/SourceList/SourceList.tsx +55 -29
- package/src/SourceList/sample-sources.json +34 -2
- package/src/__mocks__/StorybookHelpers.ts +30 -1
- package/src/index.stories.tsx +8 -2
@@ -2,35 +2,39 @@
|
|
2
2
|
import React from 'react';
|
3
3
|
import { screen, render, waitFor, within } from '@testing-library/react';
|
4
4
|
import userEvent from '@testing-library/user-event';
|
5
|
-
import { mockSource } from '../__mocks__/MockModels';
|
5
|
+
import { mockResource, mockSource } from '../__mocks__/MockModels';
|
6
6
|
import { useOverlayTriggerState, OverlayTriggerState } from 'react-stately';
|
7
7
|
import SourceList from './SourceList';
|
8
8
|
import { Source } from '../types';
|
9
|
+
import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
9
10
|
|
10
|
-
const
|
11
|
+
const mockRecentSources: RecentResourcesPaths[] = [
|
11
12
|
{
|
12
|
-
|
13
|
-
source: {
|
13
|
+
source: mockSource({
|
14
14
|
id: '1',
|
15
|
-
name: '
|
16
|
-
nodes: [
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
15
|
+
name: 'Source 1',
|
16
|
+
nodes: [
|
17
|
+
{
|
18
|
+
id: '1',
|
19
|
+
type: {
|
20
|
+
code: 'site',
|
21
|
+
name: 'Site',
|
22
|
+
},
|
23
|
+
name: 'Node 1',
|
24
|
+
childCount: 21,
|
25
|
+
},
|
26
|
+
{
|
27
|
+
id: '2',
|
28
|
+
type: {
|
29
|
+
code: 'site',
|
30
|
+
name: 'Site',
|
31
|
+
},
|
32
|
+
name: 'Node 2',
|
33
|
+
childCount: 13,
|
34
|
+
},
|
35
|
+
],
|
36
|
+
}),
|
37
|
+
path: [mockResource()],
|
34
38
|
},
|
35
39
|
];
|
36
40
|
|
@@ -107,9 +111,11 @@ describe('SourceList', () => {
|
|
107
111
|
previewModalState={previewModalState}
|
108
112
|
isLoading={true}
|
109
113
|
onSourceSelect={() => {}}
|
114
|
+
onSourceDrilldown={() => {}}
|
110
115
|
error={null}
|
111
116
|
handleReload={reload}
|
112
117
|
setSource={() => {}}
|
118
|
+
recentSources={mockRecentSources}
|
113
119
|
/>
|
114
120
|
);
|
115
121
|
}}
|
@@ -133,9 +139,11 @@ describe('SourceList', () => {
|
|
133
139
|
previewModalState={previewModalState}
|
134
140
|
isLoading={false}
|
135
141
|
onSourceSelect={() => {}}
|
142
|
+
onSourceDrilldown={() => {}}
|
136
143
|
error={null}
|
137
144
|
handleReload={reload}
|
138
145
|
setSource={() => {}}
|
146
|
+
recentSources={mockRecentSources}
|
139
147
|
/>
|
140
148
|
);
|
141
149
|
}}
|
@@ -160,9 +168,11 @@ describe('SourceList', () => {
|
|
160
168
|
previewModalState={previewModalState}
|
161
169
|
isLoading={false}
|
162
170
|
onSourceSelect={() => {}}
|
171
|
+
onSourceDrilldown={() => {}}
|
163
172
|
error={null}
|
164
173
|
handleReload={reload}
|
165
174
|
setSource={() => {}}
|
175
|
+
recentSources={mockRecentSources}
|
166
176
|
/>
|
167
177
|
);
|
168
178
|
}}
|
@@ -186,9 +196,11 @@ describe('SourceList', () => {
|
|
186
196
|
previewModalState={previewModalState}
|
187
197
|
isLoading={false}
|
188
198
|
onSourceSelect={() => {}}
|
199
|
+
onSourceDrilldown={() => {}}
|
189
200
|
error={null}
|
190
201
|
handleReload={reload}
|
191
202
|
setSource={() => {}}
|
203
|
+
recentSources={mockRecentSources}
|
192
204
|
/>
|
193
205
|
);
|
194
206
|
}}
|
@@ -219,9 +231,11 @@ describe('SourceList', () => {
|
|
219
231
|
previewModalState={previewModalState}
|
220
232
|
isLoading={false}
|
221
233
|
onSourceSelect={onSourceSelect}
|
234
|
+
onSourceDrilldown={() => {}}
|
222
235
|
error={null}
|
223
236
|
handleReload={reload}
|
224
237
|
setSource={() => {}}
|
238
|
+
recentSources={mockRecentSources}
|
225
239
|
/>
|
226
240
|
);
|
227
241
|
}}
|
@@ -229,7 +243,7 @@ describe('SourceList', () => {
|
|
229
243
|
);
|
230
244
|
|
231
245
|
const user = userEvent.setup();
|
232
|
-
const itemButton = screen.getByRole('button', { name: '
|
246
|
+
const itemButton = screen.getByRole('button', { name: 'site Node 1' });
|
233
247
|
user.click(itemButton);
|
234
248
|
|
235
249
|
await waitFor(() => {
|
@@ -244,10 +258,11 @@ describe('SourceList', () => {
|
|
244
258
|
});
|
245
259
|
});
|
246
260
|
|
247
|
-
it('
|
261
|
+
it('Clicking node child count button triggers correct onSourceDrilldown', async () => {
|
262
|
+
const onSourceDrilldown = jest.fn();
|
248
263
|
const reload = jest.fn();
|
249
264
|
|
250
|
-
|
265
|
+
render(
|
251
266
|
<SourceListTestWrapper
|
252
267
|
constructFunction={(previewModalState) => {
|
253
268
|
return (
|
@@ -256,28 +271,34 @@ describe('SourceList', () => {
|
|
256
271
|
previewModalState={previewModalState}
|
257
272
|
isLoading={false}
|
258
273
|
onSourceSelect={() => {}}
|
259
|
-
|
274
|
+
onSourceDrilldown={onSourceDrilldown}
|
275
|
+
error={null}
|
260
276
|
handleReload={reload}
|
261
277
|
setSource={() => {}}
|
278
|
+
recentSources={mockRecentSources}
|
262
279
|
/>
|
263
280
|
);
|
264
281
|
}}
|
265
282
|
/>,
|
266
283
|
);
|
267
284
|
|
285
|
+
const user = userEvent.setup();
|
286
|
+
const itemButton = screen.getByRole('button', { name: 'Drill down to Node 1 children' });
|
287
|
+
user.click(itemButton);
|
288
|
+
|
268
289
|
await waitFor(() => {
|
269
|
-
|
270
|
-
expect(
|
290
|
+
// Provides the item that was clicked and an id reference to the button that was clicked
|
291
|
+
expect(onSourceDrilldown).toHaveBeenCalledWith({
|
292
|
+
source: sources[0],
|
293
|
+
resource: sources[0].nodes[0],
|
294
|
+
});
|
271
295
|
});
|
272
296
|
});
|
273
297
|
|
274
|
-
it('Renders
|
298
|
+
it('Renders error state when an error occurs loading source list', async () => {
|
275
299
|
const reload = jest.fn();
|
276
|
-
const setSource = jest.fn();
|
277
|
-
|
278
|
-
localStorage.setItem('rb_recent_locations', JSON.stringify(mockLocalStorageData));
|
279
300
|
|
280
|
-
render(
|
301
|
+
const { getByText } = render(
|
281
302
|
<SourceListTestWrapper
|
282
303
|
constructFunction={(previewModalState) => {
|
283
304
|
return (
|
@@ -286,9 +307,11 @@ describe('SourceList', () => {
|
|
286
307
|
previewModalState={previewModalState}
|
287
308
|
isLoading={false}
|
288
309
|
onSourceSelect={() => {}}
|
289
|
-
|
310
|
+
onSourceDrilldown={() => {}}
|
311
|
+
error={new Error('Source list error!')}
|
290
312
|
handleReload={reload}
|
291
|
-
setSource={
|
313
|
+
setSource={() => {}}
|
314
|
+
recentSources={mockRecentSources}
|
292
315
|
/>
|
293
316
|
);
|
294
317
|
}}
|
@@ -296,17 +319,15 @@ describe('SourceList', () => {
|
|
296
319
|
);
|
297
320
|
|
298
321
|
await waitFor(() => {
|
299
|
-
|
300
|
-
expect(
|
322
|
+
const errorMessage = getByText('Source list error!');
|
323
|
+
expect(errorMessage).toBeInTheDocument();
|
301
324
|
});
|
302
325
|
});
|
303
326
|
|
304
|
-
it('
|
327
|
+
it('Renders recent locations section when local storage has data present', async () => {
|
305
328
|
const reload = jest.fn();
|
306
329
|
const setSource = jest.fn();
|
307
330
|
|
308
|
-
localStorage.setItem('rb_recent_locations', JSON.stringify(mockLocalStorageData));
|
309
|
-
|
310
331
|
render(
|
311
332
|
<SourceListTestWrapper
|
312
333
|
constructFunction={(previewModalState) => {
|
@@ -316,50 +337,27 @@ describe('SourceList', () => {
|
|
316
337
|
previewModalState={previewModalState}
|
317
338
|
isLoading={false}
|
318
339
|
onSourceSelect={() => {}}
|
340
|
+
onSourceDrilldown={() => {}}
|
319
341
|
error={null}
|
320
342
|
handleReload={reload}
|
321
343
|
setSource={setSource}
|
344
|
+
recentSources={mockRecentSources}
|
322
345
|
/>
|
323
346
|
);
|
324
347
|
}}
|
325
348
|
/>,
|
326
349
|
);
|
327
350
|
|
328
|
-
const user = userEvent.setup();
|
329
|
-
const itemButton = screen.getByRole('button', { name: 'Drill down to Test resource children' });
|
330
|
-
user.click(itemButton);
|
331
|
-
|
332
351
|
await waitFor(() => {
|
333
|
-
|
334
|
-
expect(
|
335
|
-
{
|
336
|
-
source: mockLocalStorageData[0].source,
|
337
|
-
resource: mockLocalStorageData[0].rootNode,
|
338
|
-
},
|
339
|
-
mockLocalStorageData[0].path,
|
340
|
-
);
|
352
|
+
expect(screen.getByText('Recent locations')).toBeInTheDocument();
|
353
|
+
expect(screen.getByText('Test resource')).toBeInTheDocument();
|
341
354
|
});
|
342
355
|
});
|
343
356
|
|
344
|
-
it('
|
357
|
+
it('Handles setSource on click of recent locations item', async () => {
|
345
358
|
const reload = jest.fn();
|
346
359
|
const setSource = jest.fn();
|
347
360
|
|
348
|
-
localStorage.setItem(
|
349
|
-
'rb_recent_locations',
|
350
|
-
JSON.stringify([
|
351
|
-
{
|
352
|
-
path: [],
|
353
|
-
source: {
|
354
|
-
id: '1',
|
355
|
-
name: 'Test source',
|
356
|
-
nodes: [],
|
357
|
-
},
|
358
|
-
rootNode: null,
|
359
|
-
},
|
360
|
-
]),
|
361
|
-
);
|
362
|
-
|
363
361
|
render(
|
364
362
|
<SourceListTestWrapper
|
365
363
|
constructFunction={(previewModalState) => {
|
@@ -369,18 +367,82 @@ describe('SourceList', () => {
|
|
369
367
|
previewModalState={previewModalState}
|
370
368
|
isLoading={false}
|
371
369
|
onSourceSelect={() => {}}
|
370
|
+
onSourceDrilldown={() => {}}
|
372
371
|
error={null}
|
373
372
|
handleReload={reload}
|
374
373
|
setSource={setSource}
|
374
|
+
recentSources={mockRecentSources}
|
375
375
|
/>
|
376
376
|
);
|
377
377
|
}}
|
378
378
|
/>,
|
379
379
|
);
|
380
380
|
|
381
|
+
const user = userEvent.setup();
|
382
|
+
const itemButton = screen.getByRole('button', { name: 'Drill down to Test resource children' });
|
383
|
+
user.click(itemButton);
|
384
|
+
|
381
385
|
await waitFor(() => {
|
382
|
-
|
383
|
-
expect(
|
386
|
+
// Provides the item that was clicked and an id reference to the button that was clicked
|
387
|
+
expect(setSource).toHaveBeenCalledWith(
|
388
|
+
{
|
389
|
+
resource: {
|
390
|
+
childCount: 0,
|
391
|
+
id: '1',
|
392
|
+
lineages: [],
|
393
|
+
name: 'Test resource',
|
394
|
+
status: {
|
395
|
+
code: 'live',
|
396
|
+
name: 'Live',
|
397
|
+
},
|
398
|
+
type: {
|
399
|
+
code: 'folder',
|
400
|
+
name: 'Folder',
|
401
|
+
},
|
402
|
+
url: 'https://no-where.com',
|
403
|
+
urls: [],
|
404
|
+
},
|
405
|
+
source: {
|
406
|
+
id: '1',
|
407
|
+
name: 'Source 1',
|
408
|
+
nodes: [
|
409
|
+
{
|
410
|
+
childCount: 21,
|
411
|
+
id: '1',
|
412
|
+
lineages: [],
|
413
|
+
name: 'Node 1',
|
414
|
+
status: {
|
415
|
+
code: 'live',
|
416
|
+
name: 'Live',
|
417
|
+
},
|
418
|
+
type: {
|
419
|
+
code: 'site',
|
420
|
+
name: 'Site',
|
421
|
+
},
|
422
|
+
url: 'https://no-where.com',
|
423
|
+
urls: [],
|
424
|
+
},
|
425
|
+
{
|
426
|
+
childCount: 13,
|
427
|
+
id: '2',
|
428
|
+
lineages: [],
|
429
|
+
name: 'Node 2',
|
430
|
+
status: {
|
431
|
+
code: 'live',
|
432
|
+
name: 'Live',
|
433
|
+
},
|
434
|
+
type: {
|
435
|
+
code: 'site',
|
436
|
+
name: 'Site',
|
437
|
+
},
|
438
|
+
url: 'https://no-where.com',
|
439
|
+
urls: [],
|
440
|
+
},
|
441
|
+
],
|
442
|
+
},
|
443
|
+
},
|
444
|
+
[],
|
445
|
+
);
|
384
446
|
});
|
385
447
|
});
|
386
448
|
});
|
@@ -10,7 +10,7 @@ export default {
|
|
10
10
|
component: SourceList,
|
11
11
|
} as Meta<typeof SourceList>;
|
12
12
|
|
13
|
-
const Template: StoryFn<typeof SourceList> = ({ sources, isLoading
|
13
|
+
const Template: StoryFn<typeof SourceList> = ({ sources, isLoading }) => {
|
14
14
|
const previewModalState = useOverlayTriggerState({});
|
15
15
|
|
16
16
|
return (
|
@@ -18,9 +18,19 @@ const Template: StoryFn<typeof SourceList> = ({ sources, isLoading, allowedTypes
|
|
18
18
|
sources={sources}
|
19
19
|
previewModalState={previewModalState}
|
20
20
|
isLoading={isLoading}
|
21
|
-
onSourceSelect={({ source,
|
22
|
-
|
23
|
-
|
21
|
+
onSourceSelect={({ source, resource }) =>
|
22
|
+
alert(`Resource Select: ${source.name} - ${resource?.name}[${resource?.id}]`)
|
23
|
+
}
|
24
|
+
onSourceDrilldown={({ source, resource }) =>
|
25
|
+
alert(`Child Drill Down: ${source.name} - ${resource?.name}[${resource?.id}]`)
|
26
|
+
}
|
27
|
+
handleReload={() => {
|
28
|
+
console.log('Handle reload');
|
29
|
+
}}
|
30
|
+
setSource={() => {
|
31
|
+
alert(`Recent Location Selected`);
|
32
|
+
}}
|
33
|
+
error={null}
|
24
34
|
/>
|
25
35
|
);
|
26
36
|
};
|
@@ -29,12 +39,10 @@ export const Primary = Template.bind({});
|
|
29
39
|
Primary.args = {
|
30
40
|
sources: sampleSources,
|
31
41
|
isLoading: false,
|
32
|
-
allowedTypes: ['site', 'image'],
|
33
42
|
};
|
34
43
|
|
35
44
|
export const Loading = Template.bind({});
|
36
45
|
Loading.args = {
|
37
46
|
...Primary.args,
|
38
47
|
isLoading: true,
|
39
|
-
allowedTypes: ['site', 'image'],
|
40
48
|
};
|
@@ -6,31 +6,37 @@ import clsx from 'clsx';
|
|
6
6
|
|
7
7
|
import { Source, ScopedSource, Resource } from '../types';
|
8
8
|
import { useCategorisedSources } from '../Hooks/useCategorisedSources';
|
9
|
-
import { useRecentLocations } from '../Hooks/useRecentLocations';
|
10
9
|
import { HistoryIcon } from '../Icons/HistoryIcon';
|
10
|
+
import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
11
11
|
|
12
12
|
export interface SourceListProps {
|
13
13
|
sources: Source[];
|
14
|
+
selectedResource?: Resource | null;
|
14
15
|
previewModalState: OverlayTriggerState;
|
15
16
|
isLoading: boolean;
|
16
17
|
onSourceSelect: (node: ScopedSource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
18
|
+
onSourceDrilldown: (source: ScopedSource) => void;
|
17
19
|
handleReload: () => void;
|
18
20
|
setSource: (source: ScopedSource | null, path?: Resource[]) => void;
|
21
|
+
recentSources: RecentResourcesPaths[];
|
19
22
|
error: Error | null;
|
20
23
|
}
|
21
24
|
|
22
25
|
const SourceList = function ({
|
23
26
|
sources,
|
27
|
+
selectedResource,
|
24
28
|
previewModalState,
|
25
29
|
isLoading,
|
26
30
|
onSourceSelect,
|
31
|
+
onSourceDrilldown,
|
27
32
|
handleReload,
|
28
33
|
setSource,
|
34
|
+
recentSources,
|
29
35
|
error,
|
30
36
|
}: SourceListProps) {
|
31
37
|
const categorisedSources = useCategorisedSources(sources);
|
32
38
|
const listRef = useRef<HTMLUListElement>(null);
|
33
|
-
const
|
39
|
+
const filteredRecentSources = recentSources.filter((item) => item.path?.length);
|
34
40
|
|
35
41
|
useEffect(() => {
|
36
42
|
if (listRef.current) {
|
@@ -58,7 +64,7 @@ const SourceList = function ({
|
|
58
64
|
>
|
59
65
|
{error && <ResourceState state="error" message={error.message} handleReload={handleReload} />}
|
60
66
|
|
61
|
-
{!error &&
|
67
|
+
{!error && filteredRecentSources.length > 0 && (
|
62
68
|
<li className={`flex flex-col text-sm font-semibold text-grey-800`}>
|
63
69
|
<div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
|
64
70
|
<span className="z-10 bg-gray-100 px-2.5 flex gap-1 items-center">
|
@@ -67,31 +73,34 @@ const SourceList = function ({
|
|
67
73
|
</span>
|
68
74
|
</div>
|
69
75
|
<ul aria-label={`recent location nodes`} className="flex flex-col">
|
70
|
-
{
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
76
|
+
{filteredRecentSources.map((item, index) => {
|
77
|
+
if (item.path) {
|
78
|
+
const lastResource = item.path[item.path.length - 1];
|
79
|
+
const [rootNode, ...path] = item.path;
|
80
|
+
return (
|
81
|
+
<ResourceItem
|
82
|
+
key={`${index}-${item.source?.id}-${lastResource?.id}`}
|
83
|
+
item={{ source: item.source, resource: lastResource }}
|
84
|
+
label={lastResource?.name || item.source?.name || ''}
|
85
|
+
type={lastResource?.type?.code || 'folder'}
|
86
|
+
previewModalState={previewModalState}
|
87
|
+
onSelect={() => {
|
88
|
+
setSource(
|
89
|
+
{
|
90
|
+
source: item.source as Source,
|
91
|
+
resource: rootNode,
|
92
|
+
},
|
93
|
+
path,
|
94
|
+
);
|
95
|
+
}}
|
96
|
+
className={clsx(
|
97
|
+
index === 0 && 'rounded-t-lg mt-3',
|
98
|
+
index === filteredRecentSources.length - 1 && 'rounded-b-lg',
|
99
|
+
)}
|
100
|
+
showChevron
|
101
|
+
/>
|
102
|
+
);
|
103
|
+
}
|
95
104
|
})}
|
96
105
|
</ul>
|
97
106
|
</li>
|
@@ -103,7 +112,7 @@ const SourceList = function ({
|
|
103
112
|
<li
|
104
113
|
key={key}
|
105
114
|
className={`flex flex-col text-sm font-semibold text-grey-800 ${
|
106
|
-
index > 0 ||
|
115
|
+
index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''
|
107
116
|
}`}
|
108
117
|
>
|
109
118
|
<div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
|
@@ -112,14 +121,31 @@ const SourceList = function ({
|
|
112
121
|
{sources.length > 0 && (
|
113
122
|
<ul aria-label={`${label} nodes`} className="flex flex-col">
|
114
123
|
{sources.map(({ source, resource }) => {
|
124
|
+
if (!resource || resource.childCount === 0) {
|
125
|
+
return (
|
126
|
+
<ResourceItem
|
127
|
+
key={`${source.id}-${resource?.id}`}
|
128
|
+
item={{ source, resource }}
|
129
|
+
label={resource?.name || source.name}
|
130
|
+
type={resource?.type.code || 'folder'}
|
131
|
+
previewModalState={previewModalState}
|
132
|
+
onSelect={onSourceDrilldown}
|
133
|
+
className="mt-3 rounded-lg"
|
134
|
+
showChevron
|
135
|
+
/>
|
136
|
+
);
|
137
|
+
}
|
115
138
|
return (
|
116
139
|
<ResourceItem
|
117
140
|
key={`${source.id}-${resource?.id}`}
|
118
141
|
item={{ source, resource }}
|
142
|
+
selected={resource?.id == selectedResource?.id && resource != null}
|
119
143
|
label={resource?.name || source.name}
|
120
144
|
type={resource?.type.code || 'folder'}
|
145
|
+
childCount={resource?.childCount || undefined}
|
121
146
|
previewModalState={previewModalState}
|
122
147
|
onSelect={onSourceSelect}
|
148
|
+
onDrillDown={onSourceDrilldown}
|
123
149
|
className="mt-3 rounded-lg"
|
124
150
|
showChevron
|
125
151
|
/>
|
@@ -41,6 +41,19 @@
|
|
41
41
|
},
|
42
42
|
"name": "Images",
|
43
43
|
"childCount": 100
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"id": "9999",
|
47
|
+
"type": {
|
48
|
+
"code": "site",
|
49
|
+
"name": "Site"
|
50
|
+
},
|
51
|
+
"status": {
|
52
|
+
"code": "live",
|
53
|
+
"name": "Live"
|
54
|
+
},
|
55
|
+
"name": "Another very unique id site",
|
56
|
+
"childCount": 100
|
44
57
|
}
|
45
58
|
]
|
46
59
|
},
|
@@ -49,7 +62,7 @@
|
|
49
62
|
"name": "Acme internal system",
|
50
63
|
"nodes": [
|
51
64
|
{
|
52
|
-
"id": "
|
65
|
+
"id": "32",
|
53
66
|
"type": {
|
54
67
|
"code": "site",
|
55
68
|
"name": "Site"
|
@@ -62,7 +75,7 @@
|
|
62
75
|
"childCount": 15
|
63
76
|
},
|
64
77
|
{
|
65
|
-
"id": "
|
78
|
+
"id": "33",
|
66
79
|
"type": {
|
67
80
|
"code": "site",
|
68
81
|
"name": "Site"
|
@@ -215,5 +228,24 @@
|
|
215
228
|
"id": "30",
|
216
229
|
"name": "Unsplash image library 27",
|
217
230
|
"nodes": []
|
231
|
+
},
|
232
|
+
{
|
233
|
+
"id": "31",
|
234
|
+
"name": "Acme no children",
|
235
|
+
"nodes": [
|
236
|
+
{
|
237
|
+
"id": "3",
|
238
|
+
"type": {
|
239
|
+
"code": "site",
|
240
|
+
"name": "Site"
|
241
|
+
},
|
242
|
+
"status": {
|
243
|
+
"code": "live",
|
244
|
+
"name": "Live"
|
245
|
+
},
|
246
|
+
"name": "What Children?",
|
247
|
+
"childCount": 0
|
248
|
+
}
|
249
|
+
]
|
218
250
|
}
|
219
251
|
]
|
@@ -9,6 +9,29 @@ type CreateCallbacksProps = Partial<{
|
|
9
9
|
error?: string;
|
10
10
|
}>;
|
11
11
|
|
12
|
+
const indexSources = (sources: Source[]) => {
|
13
|
+
const indexed: Record<string, Resource> = {};
|
14
|
+
const pending: Resource[] = [];
|
15
|
+
|
16
|
+
sources.forEach((source) => {
|
17
|
+
if (source && 'nodes' in source) {
|
18
|
+
pending.push(...source.nodes);
|
19
|
+
}
|
20
|
+
});
|
21
|
+
|
22
|
+
while (pending.length > 0) {
|
23
|
+
const resource = pending.shift();
|
24
|
+
|
25
|
+
if (!resource) {
|
26
|
+
continue;
|
27
|
+
}
|
28
|
+
|
29
|
+
indexed[resource.id] = resource;
|
30
|
+
}
|
31
|
+
|
32
|
+
return indexed;
|
33
|
+
};
|
34
|
+
|
12
35
|
const indexResources = (resources: Resource[]) => {
|
13
36
|
const indexed: Record<string, Resource> = {};
|
14
37
|
const pending = [...resources];
|
@@ -36,6 +59,7 @@ export const createResourceBrowserCallbacks = ({
|
|
36
59
|
resourceIsLoading = false,
|
37
60
|
error,
|
38
61
|
}: CreateCallbacksProps = {}) => {
|
62
|
+
const indexSourceResources = indexSources(sampleSources as Source[]);
|
39
63
|
const indexedResources = indexResources(sampleResources as Resource[]);
|
40
64
|
|
41
65
|
return {
|
@@ -78,7 +102,12 @@ export const createResourceBrowserCallbacks = ({
|
|
78
102
|
if (error && Math.random() > 0.5) {
|
79
103
|
reject(new Error(error));
|
80
104
|
} else {
|
81
|
-
|
105
|
+
const foundResource = indexedResources[reference.resource];
|
106
|
+
const foundSourceResource = indexSourceResources[reference.resource];
|
107
|
+
if (foundResource) {
|
108
|
+
resolve(foundResource);
|
109
|
+
}
|
110
|
+
resolve(foundSourceResource);
|
82
111
|
}
|
83
112
|
}, delay);
|
84
113
|
}
|
package/src/index.stories.tsx
CHANGED
@@ -33,8 +33,14 @@ Primary.args = {
|
|
33
33
|
error: '',
|
34
34
|
};
|
35
35
|
|
36
|
-
export const
|
37
|
-
|
36
|
+
export const SourceSelected = Template.bind({});
|
37
|
+
SourceSelected.args = {
|
38
|
+
...Primary.args,
|
39
|
+
value: { resource: '9999', source: '1' },
|
40
|
+
};
|
41
|
+
|
42
|
+
export const ResourceSelected = Template.bind({});
|
43
|
+
ResourceSelected.args = {
|
38
44
|
...Primary.args,
|
39
45
|
value: { resource: '33', source: '1' },
|
40
46
|
};
|