@squiz/resource-browser 1.69.0 → 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 +6 -0
- 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 +30 -14
- package/lib/SourceDropdown/SourceDropdown.d.ts +3 -1
- package/lib/SourceDropdown/SourceDropdown.js +24 -22
- package/lib/SourceList/SourceList.d.ts +3 -1
- package/lib/SourceList/SourceList.js +15 -13
- package/lib/index.css +3 -0
- package/package.json +1 -1
- 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 +17 -2
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +40 -15
- package/src/SourceDropdown/SourceDropdown.spec.tsx +92 -27
- package/src/SourceDropdown/SourceDropdown.tsx +33 -29
- package/src/SourceList/SourceList.spec.tsx +89 -72
- package/src/SourceList/SourceList.tsx +34 -29
@@ -1,6 +1,6 @@
|
|
1
1
|
import { renderHook, waitFor } from '@testing-library/react';
|
2
2
|
import { mockResource } from '../__mocks__/MockModels';
|
3
|
-
import { useResource } from './useResource';
|
3
|
+
import { useResource, useResources } from './useResource';
|
4
4
|
|
5
5
|
describe('useResource', () => {
|
6
6
|
it('Should load the resource', async () => {
|
@@ -30,3 +30,32 @@ describe('useResource', () => {
|
|
30
30
|
expect(onRequestResource).not.toBeCalled();
|
31
31
|
});
|
32
32
|
});
|
33
|
+
|
34
|
+
describe('useResources', () => {
|
35
|
+
it('Should load the resources', async () => {
|
36
|
+
const resources = mockResource();
|
37
|
+
const references = [{ source: 'source-id', resource: 'resource-id' }];
|
38
|
+
const onRequestResource = jest.fn().mockResolvedValue(resources);
|
39
|
+
const { result } = renderHook(() => useResources({ onRequestResource, references }));
|
40
|
+
|
41
|
+
expect(result.current.isLoading).toBe(true);
|
42
|
+
expect(result.current.error).toBe(null);
|
43
|
+
expect(result.current.data).toEqual(null);
|
44
|
+
|
45
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
46
|
+
|
47
|
+
expect(result.current.isLoading).toBe(false);
|
48
|
+
expect(result.current.data).toStrictEqual([resources]);
|
49
|
+
});
|
50
|
+
|
51
|
+
it('Should not load the resources if no references are provided', async () => {
|
52
|
+
const references = null;
|
53
|
+
const onRequestResource = jest.fn();
|
54
|
+
const { result } = renderHook(() => useResources({ onRequestResource, references }));
|
55
|
+
|
56
|
+
expect(result.current.isLoading).toBe(false);
|
57
|
+
expect(result.current.error).toBe(null);
|
58
|
+
expect(result.current.data).toEqual(null);
|
59
|
+
expect(onRequestResource).not.toBeCalled();
|
60
|
+
});
|
61
|
+
});
|
package/src/Hooks/useResource.ts
CHANGED
@@ -6,6 +6,11 @@ type UseResourceProps = {
|
|
6
6
|
reference?: ResourceReference | null;
|
7
7
|
};
|
8
8
|
|
9
|
+
type UseResourcesProps = {
|
10
|
+
onRequestResource: (reference: ResourceReference) => Promise<Resource | null>;
|
11
|
+
references?: ResourceReference[] | null;
|
12
|
+
};
|
13
|
+
|
9
14
|
/**
|
10
15
|
* Loads the resource indicated by the provided reference.
|
11
16
|
*/
|
@@ -17,3 +22,17 @@ export const useResource = ({ onRequestResource, reference }: UseResourceProps)
|
|
17
22
|
},
|
18
23
|
[reference?.source, reference?.resource],
|
19
24
|
);
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Loads the resources indicated by the provided reference.
|
28
|
+
*/
|
29
|
+
export const useResources = ({ onRequestResource, references }: UseResourcesProps) => {
|
30
|
+
const callbackArray = references?.map((item) => () => onRequestResource(item));
|
31
|
+
return useAsync(
|
32
|
+
{
|
33
|
+
callback: callbackArray ? callbackArray : () => null,
|
34
|
+
defaultValue: null,
|
35
|
+
},
|
36
|
+
[],
|
37
|
+
);
|
38
|
+
};
|
@@ -102,4 +102,22 @@ describe('Resource picker', () => {
|
|
102
102
|
expect(pickerLabel).toBeInTheDocument();
|
103
103
|
expect(removeButton).not.toBeInTheDocument();
|
104
104
|
});
|
105
|
+
|
106
|
+
it('should display a default status if an unsupported status is found for resource', () => {
|
107
|
+
render(
|
108
|
+
<ResourcePicker
|
109
|
+
{...defaultProps}
|
110
|
+
resource={{
|
111
|
+
...mockResource,
|
112
|
+
status: {
|
113
|
+
code: 'this_is_not_real',
|
114
|
+
name: 'This is not real',
|
115
|
+
},
|
116
|
+
}}
|
117
|
+
/>,
|
118
|
+
);
|
119
|
+
|
120
|
+
const item = screen.getByTitle('This is not real');
|
121
|
+
expect(item?.style.backgroundColor).toEqual('rgb(255, 0, 0)');
|
122
|
+
});
|
105
123
|
});
|
@@ -71,6 +71,16 @@ const baseProps = {
|
|
71
71
|
};
|
72
72
|
|
73
73
|
describe('ResourcePickerContainer', () => {
|
74
|
+
beforeEach(() => {
|
75
|
+
localStorage.setItem(
|
76
|
+
'rb_recent_locations',
|
77
|
+
JSON.stringify([
|
78
|
+
{ resource: '32', source: '1' },
|
79
|
+
{ resource: '20', source: '1' },
|
80
|
+
]),
|
81
|
+
);
|
82
|
+
});
|
83
|
+
|
74
84
|
it('Queries onRequestSources for source list on startup', async () => {
|
75
85
|
const onRequestSources = jest.fn(() => {
|
76
86
|
return Promise.resolve([]);
|
@@ -209,6 +219,8 @@ describe('ResourcePickerContainer', () => {
|
|
209
219
|
['the preselected resource lineage does not exist under a root node', 100],
|
210
220
|
['the preselected resource lineage does not appear under a root node', 200],
|
211
221
|
])('The source list is displayed if %s', async (description: string, preselectedResourceId: number) => {
|
222
|
+
localStorage.clear();
|
223
|
+
|
212
224
|
const resources: Record<string, Resource> = {
|
213
225
|
10: mockResource({
|
214
226
|
id: '100',
|
@@ -256,8 +268,10 @@ describe('ResourcePickerContainer', () => {
|
|
256
268
|
// Breadcrumbs should not be displayed.
|
257
269
|
// Source list should be displayed.
|
258
270
|
// "Leaf" resource should be selected, "Another leaf" resource should not be selected.
|
259
|
-
expect(screen.queryByLabelText('Resource breadcrumb')).not.toBeInTheDocument();
|
260
|
-
|
271
|
+
await waitFor(() => expect(screen.queryByLabelText('Resource breadcrumb')).not.toBeInTheDocument());
|
272
|
+
await waitFor(() =>
|
273
|
+
expect(screen.getByRole('button', { name: 'Drill down to Source root node #1 children' })).toBeInTheDocument(),
|
274
|
+
);
|
261
275
|
});
|
262
276
|
|
263
277
|
it('Selecting a child count drills down', async () => {
|
@@ -734,6 +748,7 @@ describe('ResourcePickerContainer', () => {
|
|
734
748
|
await waitFor(() => expect(onChangeMock).toHaveBeenCalled());
|
735
749
|
await waitFor(() => expect(onCloseMock).toHaveBeenCalled());
|
736
750
|
});
|
751
|
+
|
737
752
|
it('Resource select works', async () => {
|
738
753
|
const onChangeMock = jest.fn();
|
739
754
|
const onCloseMock = jest.fn();
|
@@ -20,6 +20,8 @@ import { useChildResources } from '../Hooks/useChildResources';
|
|
20
20
|
import { useSources } from '../Hooks/useSources';
|
21
21
|
import { usePreselectedResourcePath } from '../Hooks/usePreselectedResourcePath';
|
22
22
|
import { useRecentLocations } from '../Hooks/useRecentLocations';
|
23
|
+
import { useResources } from '../Hooks/useResource';
|
24
|
+
import { RecentResourcesPaths, useRecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
23
25
|
|
24
26
|
interface ResourcePickerContainerProps {
|
25
27
|
title: string;
|
@@ -51,7 +53,25 @@ function ResourcePickerContainer({
|
|
51
53
|
const [selectedResourceId, setSelectedResourceId] = useState<string | null>(null);
|
52
54
|
const [previewModalOverlayProps, setPreviewModalOverlayProps] = useState<DOMAttributes>({});
|
53
55
|
const { source, currentResource, hierarchy, setSource, push, popUntil } = useResourcePath();
|
54
|
-
|
56
|
+
|
57
|
+
// Recent locations relevant data
|
58
|
+
const { addRecentLocation, recentLocations } = useRecentLocations();
|
59
|
+
|
60
|
+
const { data: recentLocationsResources, isLoading: recentLocationsResourcesLoading } = useResources({
|
61
|
+
onRequestResource,
|
62
|
+
references: recentLocations,
|
63
|
+
});
|
64
|
+
|
65
|
+
const { data: recentLocationsSources, isLoading: recentLocationsLoading } = useRecentResourcesPaths({
|
66
|
+
sourceIds: recentLocations.map((item) => item.source),
|
67
|
+
resources: recentLocationsResources,
|
68
|
+
onRequestResource,
|
69
|
+
onRequestSources,
|
70
|
+
});
|
71
|
+
|
72
|
+
// Type check the returned values from recent locations requests
|
73
|
+
let recentSources: RecentResourcesPaths[] = [];
|
74
|
+
if (Array.isArray(recentLocationsSources)) recentSources = recentLocationsSources;
|
55
75
|
|
56
76
|
const {
|
57
77
|
data: sources,
|
@@ -59,12 +79,14 @@ function ResourcePickerContainer({
|
|
59
79
|
reload: handleSourceReload,
|
60
80
|
error: sourceError,
|
61
81
|
} = useSources({ onRequestSources });
|
82
|
+
|
62
83
|
const {
|
63
84
|
data: resources,
|
64
85
|
isLoading: isResourcesLoading,
|
65
86
|
reload: handleResourceReload,
|
66
87
|
error: resourceError,
|
67
88
|
} = useChildResources({ source, currentResource, onRequestChildren });
|
89
|
+
|
68
90
|
const {
|
69
91
|
data: { source: preselectedSource, path: preselectedPath },
|
70
92
|
isLoading: isPreselectedResourcePathLoading,
|
@@ -74,6 +96,7 @@ function ResourcePickerContainer({
|
|
74
96
|
onRequestResource,
|
75
97
|
onRequestSources,
|
76
98
|
});
|
99
|
+
|
77
100
|
const selectedResource = useMemo(() => {
|
78
101
|
if (selectedSource) {
|
79
102
|
return selectedSource?.nodes.find((resource: Resource) => resource.id === selectedResourceId) || null;
|
@@ -118,22 +141,17 @@ function ResourcePickerContainer({
|
|
118
141
|
|
119
142
|
const handleDetailSelect = useCallback(
|
120
143
|
(resource: Resource) => {
|
121
|
-
|
144
|
+
const detailSelectedSource = selectedSource ?? (source?.source as Source);
|
145
|
+
onChange({ resource, source: detailSelectedSource });
|
122
146
|
|
123
147
|
// Find the path that got them to where they are
|
124
|
-
const
|
125
|
-
|
126
|
-
return 'resource' in pathNode ? pathNode.resource : pathNode;
|
127
|
-
});
|
128
|
-
|
129
|
-
const [rootNode, ...path] = selectedPath;
|
148
|
+
const lastPathItem = hierarchy[hierarchy.length - 1]?.node as ScopedSource;
|
149
|
+
const lastPathResource = lastPathItem && 'resource' in lastPathItem ? lastPathItem?.resource : lastPathItem;
|
130
150
|
|
131
|
-
if (
|
132
|
-
// Update the recent locations in local storage
|
151
|
+
if (lastPathResource) {
|
133
152
|
addRecentLocation({
|
134
|
-
|
135
|
-
|
136
|
-
source: selectedSource ?? (source?.source as Source),
|
153
|
+
resource: lastPathResource.id,
|
154
|
+
source: detailSelectedSource.id,
|
137
155
|
});
|
138
156
|
}
|
139
157
|
|
@@ -188,11 +206,12 @@ function ResourcePickerContainer({
|
|
188
206
|
<SourceDropdown
|
189
207
|
sources={sources}
|
190
208
|
selectedSource={source}
|
191
|
-
isLoading={isSourceLoading}
|
209
|
+
isLoading={isSourceLoading || recentLocationsLoading || recentLocationsResourcesLoading}
|
192
210
|
onSourceSelect={handleSourceDrilldown}
|
193
211
|
onRootSelect={handleReturnToRoot}
|
194
212
|
setSource={setSource}
|
195
213
|
currentResource={currentResource}
|
214
|
+
recentSources={recentSources}
|
196
215
|
/>
|
197
216
|
</div>
|
198
217
|
<button
|
@@ -224,11 +243,17 @@ function ResourcePickerContainer({
|
|
224
243
|
sources={sources}
|
225
244
|
selectedResource={selectedResource}
|
226
245
|
previewModalState={previewModalState}
|
227
|
-
isLoading={
|
246
|
+
isLoading={
|
247
|
+
isSourceLoading ||
|
248
|
+
isPreselectedResourcePathLoading ||
|
249
|
+
recentLocationsLoading ||
|
250
|
+
recentLocationsResourcesLoading
|
251
|
+
}
|
228
252
|
onSourceSelect={handleSourceSelected}
|
229
253
|
onSourceDrilldown={handleSourceDrilldown}
|
230
254
|
handleReload={handleSourceReload}
|
231
255
|
setSource={setSource}
|
256
|
+
recentSources={recentSources}
|
232
257
|
error={sourceError}
|
233
258
|
/>
|
234
259
|
)}
|
@@ -5,31 +5,35 @@ import userEvent from '@testing-library/user-event';
|
|
5
5
|
import SourceDropdown from './SourceDropdown';
|
6
6
|
import { Source } from '../types';
|
7
7
|
import { mockScopedSource, mockSource, mockResource } from '../__mocks__/MockModels';
|
8
|
+
import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
8
9
|
|
9
|
-
const
|
10
|
+
const mockRecentSources: RecentResourcesPaths[] = [
|
10
11
|
{
|
11
|
-
|
12
|
-
source: {
|
12
|
+
source: mockSource({
|
13
13
|
id: '1',
|
14
|
-
name: '
|
15
|
-
nodes: [
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
14
|
+
name: 'Source 1',
|
15
|
+
nodes: [
|
16
|
+
{
|
17
|
+
id: '1',
|
18
|
+
type: {
|
19
|
+
code: 'site',
|
20
|
+
name: 'Site',
|
21
|
+
},
|
22
|
+
name: 'Node 1',
|
23
|
+
childCount: 21,
|
24
|
+
},
|
25
|
+
{
|
26
|
+
id: '2',
|
27
|
+
type: {
|
28
|
+
code: 'site',
|
29
|
+
name: 'Site',
|
30
|
+
},
|
31
|
+
name: 'Node 2',
|
32
|
+
childCount: 13,
|
33
|
+
},
|
34
|
+
],
|
35
|
+
}),
|
36
|
+
path: [mockResource()],
|
33
37
|
},
|
34
38
|
];
|
35
39
|
|
@@ -95,6 +99,7 @@ describe('SourceDropdown', () => {
|
|
95
99
|
onSourceSelect={() => {}}
|
96
100
|
setSource={() => {}}
|
97
101
|
currentResource={mockResource()}
|
102
|
+
recentSources={mockRecentSources}
|
98
103
|
/>,
|
99
104
|
);
|
100
105
|
|
@@ -115,6 +120,7 @@ describe('SourceDropdown', () => {
|
|
115
120
|
onSourceSelect={() => {}}
|
116
121
|
setSource={() => {}}
|
117
122
|
currentResource={mockResource()}
|
123
|
+
recentSources={mockRecentSources}
|
118
124
|
/>,
|
119
125
|
);
|
120
126
|
|
@@ -140,6 +146,7 @@ describe('SourceDropdown', () => {
|
|
140
146
|
onSourceSelect={() => {}}
|
141
147
|
setSource={() => {}}
|
142
148
|
currentResource={mockResource()}
|
149
|
+
recentSources={mockRecentSources}
|
143
150
|
/>,
|
144
151
|
);
|
145
152
|
|
@@ -160,6 +167,7 @@ describe('SourceDropdown', () => {
|
|
160
167
|
onSourceSelect={() => {}}
|
161
168
|
setSource={() => {}}
|
162
169
|
currentResource={mockResource()}
|
170
|
+
recentSources={mockRecentSources}
|
163
171
|
/>,
|
164
172
|
);
|
165
173
|
|
@@ -186,6 +194,7 @@ describe('SourceDropdown', () => {
|
|
186
194
|
onSourceSelect={() => {}}
|
187
195
|
setSource={() => {}}
|
188
196
|
currentResource={mockResource()}
|
197
|
+
recentSources={mockRecentSources}
|
189
198
|
/>
|
190
199
|
<input />
|
191
200
|
</div>,
|
@@ -229,6 +238,7 @@ describe('SourceDropdown', () => {
|
|
229
238
|
onSourceSelect={() => {}}
|
230
239
|
setSource={() => {}}
|
231
240
|
currentResource={mockResource()}
|
241
|
+
recentSources={mockRecentSources}
|
232
242
|
/>
|
233
243
|
<input />
|
234
244
|
</div>,
|
@@ -273,6 +283,7 @@ describe('SourceDropdown', () => {
|
|
273
283
|
onSourceSelect={() => {}}
|
274
284
|
setSource={() => {}}
|
275
285
|
currentResource={mockResource()}
|
286
|
+
recentSources={mockRecentSources}
|
276
287
|
/>,
|
277
288
|
);
|
278
289
|
|
@@ -298,6 +309,7 @@ describe('SourceDropdown', () => {
|
|
298
309
|
onSourceSelect={onSourceSelect}
|
299
310
|
setSource={() => {}}
|
300
311
|
currentResource={mockResource()}
|
312
|
+
recentSources={mockRecentSources}
|
301
313
|
/>,
|
302
314
|
);
|
303
315
|
|
@@ -312,8 +324,6 @@ describe('SourceDropdown', () => {
|
|
312
324
|
});
|
313
325
|
|
314
326
|
it('Recent location sources are rendered when dropdown clicked', async () => {
|
315
|
-
localStorage.setItem('rb_recent_locations', JSON.stringify(mockLocalStorageData));
|
316
|
-
|
317
327
|
render(
|
318
328
|
<SourceDropdown
|
319
329
|
sources={sources}
|
@@ -323,6 +333,7 @@ describe('SourceDropdown', () => {
|
|
323
333
|
onSourceSelect={() => {}}
|
324
334
|
setSource={() => {}}
|
325
335
|
currentResource={mockResource()}
|
336
|
+
recentSources={mockRecentSources}
|
326
337
|
/>,
|
327
338
|
);
|
328
339
|
|
@@ -347,6 +358,7 @@ describe('SourceDropdown', () => {
|
|
347
358
|
onSourceSelect={onSourceSelect}
|
348
359
|
setSource={setSource}
|
349
360
|
currentResource={mockResource()}
|
361
|
+
recentSources={mockRecentSources}
|
350
362
|
/>,
|
351
363
|
);
|
352
364
|
|
@@ -357,11 +369,64 @@ describe('SourceDropdown', () => {
|
|
357
369
|
await waitFor(() => {
|
358
370
|
expect(setSource).toHaveBeenCalledWith(
|
359
371
|
{
|
360
|
-
|
361
|
-
|
372
|
+
resource: {
|
373
|
+
childCount: 0,
|
374
|
+
id: '1',
|
375
|
+
lineages: [],
|
376
|
+
name: 'Test resource',
|
377
|
+
status: {
|
378
|
+
code: 'live',
|
379
|
+
name: 'Live',
|
380
|
+
},
|
381
|
+
type: {
|
382
|
+
code: 'folder',
|
383
|
+
name: 'Folder',
|
384
|
+
},
|
385
|
+
url: 'https://no-where.com',
|
386
|
+
urls: [],
|
387
|
+
},
|
388
|
+
source: {
|
389
|
+
id: '1',
|
390
|
+
name: 'Source 1',
|
391
|
+
nodes: [
|
392
|
+
{
|
393
|
+
childCount: 21,
|
394
|
+
id: '1',
|
395
|
+
lineages: [],
|
396
|
+
name: 'Node 1',
|
397
|
+
status: {
|
398
|
+
code: 'live',
|
399
|
+
name: 'Live',
|
400
|
+
},
|
401
|
+
type: {
|
402
|
+
code: 'site',
|
403
|
+
name: 'Site',
|
404
|
+
},
|
405
|
+
url: 'https://no-where.com',
|
406
|
+
urls: [],
|
407
|
+
},
|
408
|
+
{
|
409
|
+
childCount: 13,
|
410
|
+
id: '2',
|
411
|
+
lineages: [],
|
412
|
+
name: 'Node 2',
|
413
|
+
status: {
|
414
|
+
code: 'live',
|
415
|
+
name: 'Live',
|
416
|
+
},
|
417
|
+
type: {
|
418
|
+
code: 'site',
|
419
|
+
name: 'Site',
|
420
|
+
},
|
421
|
+
url: 'https://no-where.com',
|
422
|
+
urls: [],
|
423
|
+
},
|
424
|
+
],
|
425
|
+
},
|
362
426
|
},
|
363
|
-
|
427
|
+
[],
|
364
428
|
);
|
429
|
+
|
365
430
|
expect(screen.queryByRole('button', { name: 'All available sources' })).toBeFalsy();
|
366
431
|
});
|
367
432
|
});
|
@@ -6,8 +6,8 @@ import type { Source, ScopedSource, Resource } from '../types';
|
|
6
6
|
|
7
7
|
import uuid from '../utils/uuid';
|
8
8
|
import { useCategorisedSources } from '../Hooks/useCategorisedSources';
|
9
|
-
import { useRecentLocations, RecentLocation } from '../Hooks/useRecentLocations';
|
10
9
|
import { HistoryIcon } from '../Icons/HistoryIcon';
|
10
|
+
import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
11
11
|
|
12
12
|
export default function SourceDropdown({
|
13
13
|
sources,
|
@@ -17,6 +17,7 @@ export default function SourceDropdown({
|
|
17
17
|
onSourceSelect,
|
18
18
|
setSource,
|
19
19
|
currentResource,
|
20
|
+
recentSources,
|
20
21
|
}: {
|
21
22
|
sources: Source[];
|
22
23
|
selectedSource: ScopedSource | null;
|
@@ -25,10 +26,11 @@ export default function SourceDropdown({
|
|
25
26
|
onSourceSelect: (source: ScopedSource) => void;
|
26
27
|
setSource: (source: ScopedSource | null, path?: Resource[]) => void;
|
27
28
|
currentResource: Resource | null;
|
29
|
+
recentSources: RecentResourcesPaths[];
|
28
30
|
}) {
|
29
31
|
const categorisedSources = useCategorisedSources(sources);
|
30
|
-
const
|
31
|
-
const [recentLocationSelection, setRecentLocationSelection] = useState<
|
32
|
+
const filteredRecentSources = recentSources.filter((item) => item.path?.length);
|
33
|
+
const [recentLocationSelection, setRecentLocationSelection] = useState<RecentResourcesPaths | null>();
|
32
34
|
|
33
35
|
const [uniqueId] = useState(uuid());
|
34
36
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
@@ -65,25 +67,31 @@ export default function SourceDropdown({
|
|
65
67
|
onRootSelect();
|
66
68
|
};
|
67
69
|
|
68
|
-
const handleRecentLocationClick = (location:
|
70
|
+
const handleRecentLocationClick = (location: RecentResourcesPaths) => {
|
69
71
|
setIsOpen(false);
|
70
72
|
setRecentLocationSelection(location);
|
71
73
|
buttonRef.current?.focus();
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
74
|
+
|
75
|
+
if (location.path) {
|
76
|
+
const [rootNode, ...path] = location.path;
|
77
|
+
setSource(
|
78
|
+
{
|
79
|
+
source: location.source as Source,
|
80
|
+
resource: rootNode,
|
81
|
+
},
|
82
|
+
path,
|
83
|
+
);
|
84
|
+
}
|
79
85
|
};
|
80
86
|
|
81
87
|
useEffect(() => {
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
88
|
+
if (recentLocationSelection?.path) {
|
89
|
+
const lastResource = recentLocationSelection.path[recentLocationSelection.path.length - 1];
|
90
|
+
// If the current resource selected in the resource browser is no longer the item selected in the
|
91
|
+
// recent locations section dropdown then we set the selection to null to prevent active statuses.
|
92
|
+
if (currentResource && currentResource.id !== lastResource?.id) {
|
93
|
+
setRecentLocationSelection(null);
|
94
|
+
}
|
87
95
|
}
|
88
96
|
}, [recentLocationSelection, currentResource]);
|
89
97
|
|
@@ -161,7 +169,7 @@ export default function SourceDropdown({
|
|
161
169
|
</li>
|
162
170
|
)}
|
163
171
|
|
164
|
-
{!isLoading &&
|
172
|
+
{!isLoading && filteredRecentSources.length > 0 && (
|
165
173
|
<li className={`flex flex-col text-sm font-semibold text-grey-800`}>
|
166
174
|
<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">
|
167
175
|
<span className="z-10 bg-gray-100 px-2.5 flex gap-1 items-center">
|
@@ -170,17 +178,15 @@ export default function SourceDropdown({
|
|
170
178
|
</span>
|
171
179
|
</div>
|
172
180
|
<ul aria-label="recent location nodes" className="flex flex-col mt-2">
|
173
|
-
{
|
174
|
-
const lastResource = item.path[item.path.length - 1];
|
181
|
+
{filteredRecentSources.map((item, index) => {
|
182
|
+
const lastResource = item.path && item.path[item.path.length - 1];
|
175
183
|
const isSelectedSource =
|
176
184
|
item.source?.id === selectedSource?.source.id &&
|
177
|
-
|
178
|
-
lastResource?.id === currentResource?.id &&
|
179
|
-
recentLocationSelection;
|
185
|
+
lastResource?.id === recentLocationSelection?.path?.[recentLocationSelection.path?.length - 1]?.id;
|
180
186
|
|
181
187
|
return (
|
182
188
|
<li
|
183
|
-
key={`${index}-${item.source?.id}-${
|
189
|
+
key={`${index}-${item.source?.id}-${lastResource?.id}`}
|
184
190
|
className="flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b"
|
185
191
|
>
|
186
192
|
<button
|
@@ -189,14 +195,12 @@ export default function SourceDropdown({
|
|
189
195
|
className={`relative grow flex items-center p-2.5 hover:bg-gray-100 focus:bg-gray-100`}
|
190
196
|
>
|
191
197
|
<Icon
|
192
|
-
icon={(lastResource?.type.code ||
|
198
|
+
icon={(lastResource?.type.code || 'folder') as IconOptions}
|
193
199
|
resourceSource="matrix"
|
194
|
-
aria-label={lastResource?.name || item.
|
200
|
+
aria-label={lastResource?.name || item.source?.name}
|
195
201
|
className="shrink-0 mr-2.5"
|
196
202
|
/>
|
197
|
-
<span className="text-left mr-7">
|
198
|
-
{lastResource?.name || item.rootNode?.name || item.source?.name}
|
199
|
-
</span>
|
203
|
+
<span className="text-left mr-7">{lastResource?.name || item.source?.name}</span>
|
200
204
|
{isSelectedSource && (
|
201
205
|
<Icon icon={'selected' as IconOptions} aria-label="selected" className="absolute right-4" />
|
202
206
|
)}
|
@@ -214,7 +218,7 @@ export default function SourceDropdown({
|
|
214
218
|
<li
|
215
219
|
key={key}
|
216
220
|
className={`flex flex-col text-sm font-semibold text-grey-800 ${
|
217
|
-
index > 0 ||
|
221
|
+
index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''
|
218
222
|
}`}
|
219
223
|
>
|
220
224
|
<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">
|