@squiz/resource-browser 1.69.0 → 1.69.2
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/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 +14 -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 +21 -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
 
    
        package/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,17 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # @squiz/resource-browser
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ## 1.69.2
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ### Patch Changes
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            - d167ab9: Fixed bug with resource browser inputs
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            ## 1.69.1
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ### Patch Changes
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            - 5cd6ef6: Updated logic & handling of recent locations in resource browser
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       3 
15 
     | 
    
         
             
            ## 1.69.0
         
     | 
| 
       4 
16 
     | 
    
         | 
| 
       5 
17 
     | 
    
         
             
            ### Minor Changes
         
     | 
| 
         @@ -1,10 +1,5 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import {  
     | 
| 
       2 
     | 
    
         
            -
            export interface RecentLocation {
         
     | 
| 
       3 
     | 
    
         
            -
                rootNode: Resource | null;
         
     | 
| 
       4 
     | 
    
         
            -
                source: Source;
         
     | 
| 
       5 
     | 
    
         
            -
                path: Array<Resource>;
         
     | 
| 
       6 
     | 
    
         
            -
            }
         
     | 
| 
      
 1 
     | 
    
         
            +
            import { ResourceReference } from '../types';
         
     | 
| 
       7 
2 
     | 
    
         
             
            export declare const useRecentLocations: (maxLocations?: number, storageKey?: string) => {
         
     | 
| 
       8 
     | 
    
         
            -
                recentLocations:  
     | 
| 
       9 
     | 
    
         
            -
                addRecentLocation: (newLocation:  
     | 
| 
      
 3 
     | 
    
         
            +
                recentLocations: ResourceReference[];
         
     | 
| 
      
 4 
     | 
    
         
            +
                addRecentLocation: (newLocation: ResourceReference) => void;
         
     | 
| 
       10 
5 
     | 
    
         
             
            };
         
     | 
| 
         @@ -14,10 +14,14 @@ const useRecentLocations = (maxLocations = 3, storageKey = 'rb_recent_locations' 
     | 
|
| 
       14 
14 
     | 
    
         
             
                if (!Array.isArray(initialRecentLocations)) {
         
     | 
| 
       15 
15 
     | 
    
         
             
                    initialRecentLocations = [];
         
     | 
| 
       16 
16 
     | 
    
         
             
                }
         
     | 
| 
      
 17 
     | 
    
         
            +
                // Check if any item in the current recent locations is not the right format, if so, we reset it
         
     | 
| 
      
 18 
     | 
    
         
            +
                if (initialRecentLocations.find((item) => !(item?.resource?.length && item?.source?.length))) {
         
     | 
| 
      
 19 
     | 
    
         
            +
                    initialRecentLocations = [];
         
     | 
| 
      
 20 
     | 
    
         
            +
                }
         
     | 
| 
       17 
21 
     | 
    
         
             
                const [recentLocations, setRecentLocations] = (0, react_1.useState)(initialRecentLocations);
         
     | 
| 
       18 
22 
     | 
    
         
             
                const addRecentLocation = (newLocation) => {
         
     | 
| 
       19 
23 
     | 
    
         
             
                    // Check if the new location to make sure we don't already have a recent location for this
         
     | 
| 
       20 
     | 
    
         
            -
                    if ( 
     | 
| 
      
 24 
     | 
    
         
            +
                    if (recentLocations.find((item) => item.resource === newLocation.resource && item.source === newLocation.source)) {
         
     | 
| 
       21 
25 
     | 
    
         
             
                        return;
         
     | 
| 
       22 
26 
     | 
    
         
             
                    }
         
     | 
| 
       23 
27 
     | 
    
         
             
                    const updatedLocations = [newLocation, ...recentLocations.slice(0, maxLocations - 1)];
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { Resource, OnRequestResource, OnRequestSources, Source } from '../types';
         
     | 
| 
      
 2 
     | 
    
         
            +
            export type RecentResourcesPathsProps = {
         
     | 
| 
      
 3 
     | 
    
         
            +
                sourceIds?: string[];
         
     | 
| 
      
 4 
     | 
    
         
            +
                resources?: Resource | null | (Resource | null)[];
         
     | 
| 
      
 5 
     | 
    
         
            +
                onRequestResource: OnRequestResource;
         
     | 
| 
      
 6 
     | 
    
         
            +
                onRequestSources: OnRequestSources;
         
     | 
| 
      
 7 
     | 
    
         
            +
            };
         
     | 
| 
      
 8 
     | 
    
         
            +
            export type RecentResourcesPaths = {
         
     | 
| 
      
 9 
     | 
    
         
            +
                source?: Source;
         
     | 
| 
      
 10 
     | 
    
         
            +
                path?: Resource[];
         
     | 
| 
      
 11 
     | 
    
         
            +
            };
         
     | 
| 
      
 12 
     | 
    
         
            +
            export declare const useRecentResourcesPaths: ({ sourceIds, resources, onRequestResource, onRequestSources, }: RecentResourcesPathsProps) => {
         
     | 
| 
      
 13 
     | 
    
         
            +
                data: RecentResourcesPaths[] | {
         
     | 
| 
      
 14 
     | 
    
         
            +
                    source: Source | undefined;
         
     | 
| 
      
 15 
     | 
    
         
            +
                    path: Resource[] | undefined;
         
     | 
| 
      
 16 
     | 
    
         
            +
                } | null;
         
     | 
| 
      
 17 
     | 
    
         
            +
                error: Error | null;
         
     | 
| 
      
 18 
     | 
    
         
            +
                isLoading: boolean;
         
     | 
| 
      
 19 
     | 
    
         
            +
                reload: () => void;
         
     | 
| 
      
 20 
     | 
    
         
            +
            };
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "use strict";
         
     | 
| 
      
 2 
     | 
    
         
            +
            Object.defineProperty(exports, "__esModule", { value: true });
         
     | 
| 
      
 3 
     | 
    
         
            +
            exports.useRecentResourcesPaths = void 0;
         
     | 
| 
      
 4 
     | 
    
         
            +
            const generic_browser_lib_1 = require("@squiz/generic-browser-lib");
         
     | 
| 
      
 5 
     | 
    
         
            +
            const findBestMatchLineage_1 = require("../utils/findBestMatchLineage");
         
     | 
| 
      
 6 
     | 
    
         
            +
            const useRecentResourcesPaths = ({ sourceIds, resources, onRequestResource, onRequestSources, }) => {
         
     | 
| 
      
 7 
     | 
    
         
            +
                const callbackArray = sourceIds?.map((sourceId, index) => async () => {
         
     | 
| 
      
 8 
     | 
    
         
            +
                    let path;
         
     | 
| 
      
 9 
     | 
    
         
            +
                    const sources = await onRequestSources();
         
     | 
| 
      
 10 
     | 
    
         
            +
                    const source = sources.find((source) => source.id === sourceId);
         
     | 
| 
      
 11 
     | 
    
         
            +
                    const resource = Array.isArray(resources) ? resources[index] : null;
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if (sourceId && source && resource) {
         
     | 
| 
      
 13 
     | 
    
         
            +
                        const bestMatchLineage = (0, findBestMatchLineage_1.findBestMatchLineage)(source, resource);
         
     | 
| 
      
 14 
     | 
    
         
            +
                        if (Array.isArray(bestMatchLineage) && bestMatchLineage.length > 0) {
         
     | 
| 
      
 15 
     | 
    
         
            +
                            path = await Promise.all(bestMatchLineage.map(async (resourceId) => {
         
     | 
| 
      
 16 
     | 
    
         
            +
                                return onRequestResource({ source: sourceId, resource: resourceId });
         
     | 
| 
      
 17 
     | 
    
         
            +
                            }));
         
     | 
| 
      
 18 
     | 
    
         
            +
                        }
         
     | 
| 
      
 19 
     | 
    
         
            +
                        else {
         
     | 
| 
      
 20 
     | 
    
         
            +
                            path = [resource];
         
     | 
| 
      
 21 
     | 
    
         
            +
                        }
         
     | 
| 
      
 22 
     | 
    
         
            +
                    }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    return { source, path };
         
     | 
| 
      
 24 
     | 
    
         
            +
                });
         
     | 
| 
      
 25 
     | 
    
         
            +
                return (0, generic_browser_lib_1.useAsync)({
         
     | 
| 
      
 26 
     | 
    
         
            +
                    callback: callbackArray ? callbackArray : () => null,
         
     | 
| 
      
 27 
     | 
    
         
            +
                    defaultValue: [],
         
     | 
| 
      
 28 
     | 
    
         
            +
                }, [JSON.stringify(sourceIds), resources]);
         
     | 
| 
      
 29 
     | 
    
         
            +
            };
         
     | 
| 
      
 30 
     | 
    
         
            +
            exports.useRecentResourcesPaths = useRecentResourcesPaths;
         
     | 
| 
         @@ -3,6 +3,10 @@ type UseResourceProps = { 
     | 
|
| 
       3 
3 
     | 
    
         
             
                onRequestResource: (reference: ResourceReference) => Promise<Resource | null>;
         
     | 
| 
       4 
4 
     | 
    
         
             
                reference?: ResourceReference | null;
         
     | 
| 
       5 
5 
     | 
    
         
             
            };
         
     | 
| 
      
 6 
     | 
    
         
            +
            type UseResourcesProps = {
         
     | 
| 
      
 7 
     | 
    
         
            +
                onRequestResource: (reference: ResourceReference) => Promise<Resource | null>;
         
     | 
| 
      
 8 
     | 
    
         
            +
                references?: ResourceReference[] | null;
         
     | 
| 
      
 9 
     | 
    
         
            +
            };
         
     | 
| 
       6 
10 
     | 
    
         
             
            /**
         
     | 
| 
       7 
11 
     | 
    
         
             
             * Loads the resource indicated by the provided reference.
         
     | 
| 
       8 
12 
     | 
    
         
             
             */
         
     | 
| 
         @@ -12,4 +16,13 @@ export declare const useResource: ({ onRequestResource, reference }: UseResource 
     | 
|
| 
       12 
16 
     | 
    
         
             
                isLoading: boolean;
         
     | 
| 
       13 
17 
     | 
    
         
             
                reload: () => void;
         
     | 
| 
       14 
18 
     | 
    
         
             
            };
         
     | 
| 
      
 19 
     | 
    
         
            +
            /**
         
     | 
| 
      
 20 
     | 
    
         
            +
             * Loads the resources indicated by the provided reference.
         
     | 
| 
      
 21 
     | 
    
         
            +
             */
         
     | 
| 
      
 22 
     | 
    
         
            +
            export declare const useResources: ({ onRequestResource, references }: UseResourcesProps) => {
         
     | 
| 
      
 23 
     | 
    
         
            +
                data: Resource | null;
         
     | 
| 
      
 24 
     | 
    
         
            +
                error: Error | null;
         
     | 
| 
      
 25 
     | 
    
         
            +
                isLoading: boolean;
         
     | 
| 
      
 26 
     | 
    
         
            +
                reload: () => void;
         
     | 
| 
      
 27 
     | 
    
         
            +
            };
         
     | 
| 
       15 
28 
     | 
    
         
             
            export {};
         
     | 
    
        package/lib/Hooks/useResource.js
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            "use strict";
         
     | 
| 
       2 
2 
     | 
    
         
             
            Object.defineProperty(exports, "__esModule", { value: true });
         
     | 
| 
       3 
     | 
    
         
            -
            exports.useResource = void 0;
         
     | 
| 
      
 3 
     | 
    
         
            +
            exports.useResources = exports.useResource = void 0;
         
     | 
| 
       4 
4 
     | 
    
         
             
            const generic_browser_lib_1 = require("@squiz/generic-browser-lib");
         
     | 
| 
       5 
5 
     | 
    
         
             
            /**
         
     | 
| 
       6 
6 
     | 
    
         
             
             * Loads the resource indicated by the provided reference.
         
     | 
| 
         @@ -8,5 +8,18 @@ const generic_browser_lib_1 = require("@squiz/generic-browser-lib"); 
     | 
|
| 
       8 
8 
     | 
    
         
             
            const useResource = ({ onRequestResource, reference }) => (0, generic_browser_lib_1.useAsync)({
         
     | 
| 
       9 
9 
     | 
    
         
             
                callback: () => (reference ? onRequestResource(reference) : null),
         
     | 
| 
       10 
10 
     | 
    
         
             
                defaultValue: null,
         
     | 
| 
      
 11 
     | 
    
         
            +
                // Avoid the race condition bug found in FEAAS-891
         
     | 
| 
      
 12 
     | 
    
         
            +
                ignorePrevious: true,
         
     | 
| 
       11 
13 
     | 
    
         
             
            }, [reference?.source, reference?.resource]);
         
     | 
| 
       12 
14 
     | 
    
         
             
            exports.useResource = useResource;
         
     | 
| 
      
 15 
     | 
    
         
            +
            /**
         
     | 
| 
      
 16 
     | 
    
         
            +
             * Loads the resources indicated by the provided reference.
         
     | 
| 
      
 17 
     | 
    
         
            +
             */
         
     | 
| 
      
 18 
     | 
    
         
            +
            const useResources = ({ onRequestResource, references }) => {
         
     | 
| 
      
 19 
     | 
    
         
            +
                const callbackArray = references?.map((item) => () => onRequestResource(item));
         
     | 
| 
      
 20 
     | 
    
         
            +
                return (0, generic_browser_lib_1.useAsync)({
         
     | 
| 
      
 21 
     | 
    
         
            +
                    callback: callbackArray ? callbackArray : () => null,
         
     | 
| 
      
 22 
     | 
    
         
            +
                    defaultValue: null,
         
     | 
| 
      
 23 
     | 
    
         
            +
                }, []);
         
     | 
| 
      
 24 
     | 
    
         
            +
            };
         
     | 
| 
      
 25 
     | 
    
         
            +
            exports.useResources = useResources;
         
     | 
| 
         @@ -38,13 +38,30 @@ const useChildResources_1 = require("../Hooks/useChildResources"); 
     | 
|
| 
       38 
38 
     | 
    
         
             
            const useSources_1 = require("../Hooks/useSources");
         
     | 
| 
       39 
39 
     | 
    
         
             
            const usePreselectedResourcePath_1 = require("../Hooks/usePreselectedResourcePath");
         
     | 
| 
       40 
40 
     | 
    
         
             
            const useRecentLocations_1 = require("../Hooks/useRecentLocations");
         
     | 
| 
      
 41 
     | 
    
         
            +
            const useResource_1 = require("../Hooks/useResource");
         
     | 
| 
      
 42 
     | 
    
         
            +
            const useRecentResourcesPaths_1 = require("../Hooks/useRecentResourcesPaths");
         
     | 
| 
       41 
43 
     | 
    
         
             
            function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onRequestSources, onRequestResource, onRequestChildren, onChange, onClose, preselectedSourceId, preselectedResource, }) {
         
     | 
| 
       42 
44 
     | 
    
         
             
                const previewModalState = (0, react_stately_1.useOverlayTriggerState)({});
         
     | 
| 
       43 
45 
     | 
    
         
             
                const [selectedSource, setSelectedSource] = (0, react_1.useState)(null);
         
     | 
| 
       44 
46 
     | 
    
         
             
                const [selectedResourceId, setSelectedResourceId] = (0, react_1.useState)(null);
         
     | 
| 
       45 
47 
     | 
    
         
             
                const [previewModalOverlayProps, setPreviewModalOverlayProps] = (0, react_1.useState)({});
         
     | 
| 
       46 
48 
     | 
    
         
             
                const { source, currentResource, hierarchy, setSource, push, popUntil } = (0, useResourcePath_1.useResourcePath)();
         
     | 
| 
       47 
     | 
    
         
            -
                 
     | 
| 
      
 49 
     | 
    
         
            +
                // Recent locations relevant data
         
     | 
| 
      
 50 
     | 
    
         
            +
                const { addRecentLocation, recentLocations } = (0, useRecentLocations_1.useRecentLocations)();
         
     | 
| 
      
 51 
     | 
    
         
            +
                const { data: recentLocationsResources, isLoading: recentLocationsResourcesLoading } = (0, useResource_1.useResources)({
         
     | 
| 
      
 52 
     | 
    
         
            +
                    onRequestResource,
         
     | 
| 
      
 53 
     | 
    
         
            +
                    references: recentLocations,
         
     | 
| 
      
 54 
     | 
    
         
            +
                });
         
     | 
| 
      
 55 
     | 
    
         
            +
                const { data: recentLocationsSources, isLoading: recentLocationsLoading } = (0, useRecentResourcesPaths_1.useRecentResourcesPaths)({
         
     | 
| 
      
 56 
     | 
    
         
            +
                    sourceIds: recentLocations.map((item) => item.source),
         
     | 
| 
      
 57 
     | 
    
         
            +
                    resources: recentLocationsResources,
         
     | 
| 
      
 58 
     | 
    
         
            +
                    onRequestResource,
         
     | 
| 
      
 59 
     | 
    
         
            +
                    onRequestSources,
         
     | 
| 
      
 60 
     | 
    
         
            +
                });
         
     | 
| 
      
 61 
     | 
    
         
            +
                // Type check the returned values from recent locations requests
         
     | 
| 
      
 62 
     | 
    
         
            +
                let recentSources = [];
         
     | 
| 
      
 63 
     | 
    
         
            +
                if (Array.isArray(recentLocationsSources))
         
     | 
| 
      
 64 
     | 
    
         
            +
                    recentSources = recentLocationsSources;
         
     | 
| 
       48 
65 
     | 
    
         
             
                const { data: sources, isLoading: isSourceLoading, reload: handleSourceReload, error: sourceError, } = (0, useSources_1.useSources)({ onRequestSources });
         
     | 
| 
       49 
66 
     | 
    
         
             
                const { data: resources, isLoading: isResourcesLoading, reload: handleResourceReload, error: resourceError, } = (0, useChildResources_1.useChildResources)({ source, currentResource, onRequestChildren });
         
     | 
| 
       50 
67 
     | 
    
         
             
                const { data: { source: preselectedSource, path: preselectedPath }, isLoading: isPreselectedResourcePathLoading, } = (0, usePreselectedResourcePath_1.usePreselectedResourcePath)({
         
     | 
| 
         @@ -84,19 +101,15 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques 
     | 
|
| 
       84 
101 
     | 
    
         
             
                    setSource(null);
         
     | 
| 
       85 
102 
     | 
    
         
             
                }, [setSource]);
         
     | 
| 
       86 
103 
     | 
    
         
             
                const handleDetailSelect = (0, react_1.useCallback)((resource) => {
         
     | 
| 
       87 
     | 
    
         
            -
                     
     | 
| 
      
 104 
     | 
    
         
            +
                    const detailSelectedSource = selectedSource ?? source?.source;
         
     | 
| 
      
 105 
     | 
    
         
            +
                    onChange({ resource, source: detailSelectedSource });
         
     | 
| 
       88 
106 
     | 
    
         
             
                    // Find the path that got them to where they are
         
     | 
| 
       89 
     | 
    
         
            -
                    const  
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
                    });
         
     | 
| 
       93 
     | 
    
         
            -
                    const [rootNode, ...path] = selectedPath;
         
     | 
| 
       94 
     | 
    
         
            -
                    if (rootNode) {
         
     | 
| 
       95 
     | 
    
         
            -
                        // Update the recent locations in local storage
         
     | 
| 
      
 107 
     | 
    
         
            +
                    const lastPathItem = hierarchy[hierarchy.length - 1]?.node;
         
     | 
| 
      
 108 
     | 
    
         
            +
                    const lastPathResource = lastPathItem && 'resource' in lastPathItem ? lastPathItem?.resource : lastPathItem;
         
     | 
| 
      
 109 
     | 
    
         
            +
                    if (lastPathResource) {
         
     | 
| 
       96 
110 
     | 
    
         
             
                        addRecentLocation({
         
     | 
| 
       97 
     | 
    
         
            -
                             
     | 
| 
       98 
     | 
    
         
            -
                             
     | 
| 
       99 
     | 
    
         
            -
                            source: selectedSource ?? source?.source,
         
     | 
| 
      
 111 
     | 
    
         
            +
                            resource: lastPathResource.id,
         
     | 
| 
      
 112 
     | 
    
         
            +
                            source: detailSelectedSource.id,
         
     | 
| 
       100 
113 
     | 
    
         
             
                        });
         
     | 
| 
       101 
114 
     | 
    
         
             
                    }
         
     | 
| 
       102 
115 
     | 
    
         
             
                    onClose();
         
     | 
| 
         @@ -134,7 +147,7 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques 
     | 
|
| 
       134 
147 
     | 
    
         
             
                    react_1.default.createElement("div", { className: "flex items-center p-4.5" },
         
     | 
| 
       135 
148 
     | 
    
         
             
                        react_1.default.createElement("h2", { ...titleAriaProps, className: "text-xl leading-6 text-gray-800 font-semibold mr-6" }, title),
         
     | 
| 
       136 
149 
     | 
    
         
             
                        react_1.default.createElement("div", { className: "px-3 border-l border-gray-300 w-300px" },
         
     | 
| 
       137 
     | 
    
         
            -
                            react_1.default.createElement(SourceDropdown_1.default, { sources: sources, selectedSource: source, isLoading: isSourceLoading, onSourceSelect: handleSourceDrilldown, onRootSelect: handleReturnToRoot, setSource: setSource, currentResource: currentResource })),
         
     | 
| 
      
 150 
     | 
    
         
            +
                            react_1.default.createElement(SourceDropdown_1.default, { sources: sources, selectedSource: source, isLoading: isSourceLoading || recentLocationsLoading || recentLocationsResourcesLoading, onSourceSelect: handleSourceDrilldown, onRootSelect: handleReturnToRoot, setSource: setSource, currentResource: currentResource, recentSources: recentSources })),
         
     | 
| 
       138 
151 
     | 
    
         
             
                        react_1.default.createElement("button", { type: "button", "aria-label": `Close ${title} dialog`, onClick: onClose, className: "absolute top-2 right-2 p-2.5 rounded hover:bg-blue-100 focus:bg-blue-100" },
         
     | 
| 
       139 
152 
     | 
    
         
             
                            react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
         
     | 
| 
       140 
153 
     | 
    
         
             
                                react_1.default.createElement("path", { d: "M13.3 0.710017C13.1131 0.522765 12.8595 0.417532 12.595 0.417532C12.3305 0.417532 12.0768 0.522765 11.89 0.710017L6.99997 5.59002L2.10997 0.700017C1.92314 0.512765 1.66949 0.407532 1.40497 0.407532C1.14045 0.407532 0.886802 0.512765 0.699971 0.700017C0.309971 1.09002 0.309971 1.72002 0.699971 2.11002L5.58997 7.00002L0.699971 11.89C0.309971 12.28 0.309971 12.91 0.699971 13.3C1.08997 13.69 1.71997 13.69 2.10997 13.3L6.99997 8.41002L11.89 13.3C12.28 13.69 12.91 13.69 13.3 13.3C13.69 12.91 13.69 12.28 13.3 11.89L8.40997 7.00002L13.3 2.11002C13.68 1.73002 13.68 1.09002 13.3 0.710017Z", fill: "currentColor" })))),
         
     | 
| 
         @@ -142,7 +155,10 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques 
     | 
|
| 
       142 
155 
     | 
    
         
             
                        react_1.default.createElement("div", { className: "overflow-y-scroll flex-1 grow-[3] border-r border-gray-300" },
         
     | 
| 
       143 
156 
     | 
    
         
             
                            react_1.default.createElement("h3", { className: "sr-only" }, "Resource List"),
         
     | 
| 
       144 
157 
     | 
    
         
             
                            hierarchy.length > 0 && (react_1.default.createElement(ResourceBreadcrumb_1.default, { hierarchy: hierarchy, onBreadcrumbSelect: popUntil, onReturnToRoot: handleReturnToRoot })),
         
     | 
| 
       145 
     | 
    
         
            -
                            !source && (react_1.default.createElement(SourceList_1.default, { sources: sources, selectedResource: selectedResource, previewModalState: previewModalState, isLoading: isSourceLoading || 
     | 
| 
      
 158 
     | 
    
         
            +
                            !source && (react_1.default.createElement(SourceList_1.default, { sources: sources, selectedResource: selectedResource, previewModalState: previewModalState, isLoading: isSourceLoading ||
         
     | 
| 
      
 159 
     | 
    
         
            +
                                    isPreselectedResourcePathLoading ||
         
     | 
| 
      
 160 
     | 
    
         
            +
                                    recentLocationsLoading ||
         
     | 
| 
      
 161 
     | 
    
         
            +
                                    recentLocationsResourcesLoading, onSourceSelect: handleSourceSelected, onSourceDrilldown: handleSourceDrilldown, handleReload: handleSourceReload, setSource: setSource, recentSources: recentSources, error: sourceError })),
         
     | 
| 
       146 
162 
     | 
    
         
             
                            source && (react_1.default.createElement(ResourceList_1.default, { previewModalState: previewModalState, resources: resources, selectedResource: selectedResource, isLoading: isResourcesLoading, onResourceSelect: handleResourceSelected, onResourceDrillDown: handleResourceDrillDown, allowedTypes: allowedTypes, handleReturnToRoot: handleReturnToRoot, handleReload: handleResourceReload, error: resourceError }))),
         
     | 
| 
       147 
163 
     | 
    
         
             
                        react_1.default.createElement("div", { className: "sm:overflow-y-scroll sm:flex-1 sm:grow-[2] bg-white" },
         
     | 
| 
       148 
164 
     | 
    
         
             
                            react_1.default.createElement(PreviewPanel_1.default, { resource: isResourcesLoading ? null : selectedResource, modalState: previewModalState, previewModalOverlayProps: previewModalOverlayProps, allowedTypes: allowedTypes, onSelect: handleDetailSelect, onClose: handleDetailClose })))));
         
     | 
| 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import React from 'react';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import type { Source, ScopedSource, Resource } from '../types';
         
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
      
 3 
     | 
    
         
            +
            import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
         
     | 
| 
      
 4 
     | 
    
         
            +
            export default function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSourceSelect, setSource, currentResource, recentSources, }: {
         
     | 
| 
       4 
5 
     | 
    
         
             
                sources: Source[];
         
     | 
| 
       5 
6 
     | 
    
         
             
                selectedSource: ScopedSource | null;
         
     | 
| 
       6 
7 
     | 
    
         
             
                isLoading: boolean;
         
     | 
| 
         @@ -8,4 +9,5 @@ export default function SourceDropdown({ sources, selectedSource, isLoading, onR 
     | 
|
| 
       8 
9 
     | 
    
         
             
                onSourceSelect: (source: ScopedSource) => void;
         
     | 
| 
       9 
10 
     | 
    
         
             
                setSource: (source: ScopedSource | null, path?: Resource[]) => void;
         
     | 
| 
       10 
11 
     | 
    
         
             
                currentResource: Resource | null;
         
     | 
| 
      
 12 
     | 
    
         
            +
                recentSources: RecentResourcesPaths[];
         
     | 
| 
       11 
13 
     | 
    
         
             
            }): React.JSX.Element;
         
     | 
| 
         @@ -31,11 +31,10 @@ const interactions_1 = require("@react-aria/interactions"); 
     | 
|
| 
       31 
31 
     | 
    
         
             
            const generic_browser_lib_1 = require("@squiz/generic-browser-lib");
         
     | 
| 
       32 
32 
     | 
    
         
             
            const uuid_1 = __importDefault(require("../utils/uuid"));
         
     | 
| 
       33 
33 
     | 
    
         
             
            const useCategorisedSources_1 = require("../Hooks/useCategorisedSources");
         
     | 
| 
       34 
     | 
    
         
            -
            const useRecentLocations_1 = require("../Hooks/useRecentLocations");
         
     | 
| 
       35 
34 
     | 
    
         
             
            const HistoryIcon_1 = require("../Icons/HistoryIcon");
         
     | 
| 
       36 
     | 
    
         
            -
            function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSourceSelect, setSource, currentResource, }) {
         
     | 
| 
      
 35 
     | 
    
         
            +
            function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSourceSelect, setSource, currentResource, recentSources, }) {
         
     | 
| 
       37 
36 
     | 
    
         
             
                const categorisedSources = (0, useCategorisedSources_1.useCategorisedSources)(sources);
         
     | 
| 
       38 
     | 
    
         
            -
                const  
     | 
| 
      
 37 
     | 
    
         
            +
                const filteredRecentSources = recentSources.filter((item) => item.path?.length);
         
     | 
| 
       39 
38 
     | 
    
         
             
                const [recentLocationSelection, setRecentLocationSelection] = (0, react_1.useState)();
         
     | 
| 
       40 
39 
     | 
    
         
             
                const [uniqueId] = (0, react_1.useState)((0, uuid_1.default)());
         
     | 
| 
       41 
40 
     | 
    
         
             
                const buttonRef = (0, react_1.useRef)(null);
         
     | 
| 
         @@ -71,17 +70,22 @@ function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSo 
     | 
|
| 
       71 
70 
     | 
    
         
             
                    setIsOpen(false);
         
     | 
| 
       72 
71 
     | 
    
         
             
                    setRecentLocationSelection(location);
         
     | 
| 
       73 
72 
     | 
    
         
             
                    buttonRef.current?.focus();
         
     | 
| 
       74 
     | 
    
         
            -
                     
     | 
| 
       75 
     | 
    
         
            -
                         
     | 
| 
       76 
     | 
    
         
            -
                         
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
      
 73 
     | 
    
         
            +
                    if (location.path) {
         
     | 
| 
      
 74 
     | 
    
         
            +
                        const [rootNode, ...path] = location.path;
         
     | 
| 
      
 75 
     | 
    
         
            +
                        setSource({
         
     | 
| 
      
 76 
     | 
    
         
            +
                            source: location.source,
         
     | 
| 
      
 77 
     | 
    
         
            +
                            resource: rootNode,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        }, path);
         
     | 
| 
      
 79 
     | 
    
         
            +
                    }
         
     | 
| 
       78 
80 
     | 
    
         
             
                };
         
     | 
| 
       79 
81 
     | 
    
         
             
                (0, react_1.useEffect)(() => {
         
     | 
| 
       80 
     | 
    
         
            -
                     
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
                         
     | 
| 
      
 82 
     | 
    
         
            +
                    if (recentLocationSelection?.path) {
         
     | 
| 
      
 83 
     | 
    
         
            +
                        const lastResource = recentLocationSelection.path[recentLocationSelection.path.length - 1];
         
     | 
| 
      
 84 
     | 
    
         
            +
                        // If the current resource selected in the resource browser is no longer the item selected in the
         
     | 
| 
      
 85 
     | 
    
         
            +
                        // recent locations section dropdown then we set the selection to null to prevent active statuses.
         
     | 
| 
      
 86 
     | 
    
         
            +
                        if (currentResource && currentResource.id !== lastResource?.id) {
         
     | 
| 
      
 87 
     | 
    
         
            +
                            setRecentLocationSelection(null);
         
     | 
| 
      
 88 
     | 
    
         
            +
                        }
         
     | 
| 
       85 
89 
     | 
    
         
             
                    }
         
     | 
| 
       86 
90 
     | 
    
         
             
                }, [recentLocationSelection, currentResource]);
         
     | 
| 
       87 
91 
     | 
    
         
             
                return (react_1.default.createElement("div", { ...focusWithinProps, ...keyboardProps, className: "relative w-72 border-2 rounded border-gray-300" },
         
     | 
| 
         @@ -112,26 +116,24 @@ function SourceDropdown({ sources, selectedSource, isLoading, onRootSelect, onSo 
     | 
|
| 
       112 
116 
     | 
    
         
             
                                "All available sources")),
         
     | 
| 
       113 
117 
     | 
    
         
             
                        isLoading && (react_1.default.createElement("li", { className: "mt-2" },
         
     | 
| 
       114 
118 
     | 
    
         
             
                            react_1.default.createElement(generic_browser_lib_1.Spinner, { size: "sm", label: "Loading sources", className: "m-3" }))),
         
     | 
| 
       115 
     | 
    
         
            -
                        !isLoading &&  
     | 
| 
      
 119 
     | 
    
         
            +
                        !isLoading && filteredRecentSources.length > 0 && (react_1.default.createElement("li", { className: `flex flex-col text-sm font-semibold text-grey-800` },
         
     | 
| 
       116 
120 
     | 
    
         
             
                            react_1.default.createElement("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" },
         
     | 
| 
       117 
121 
     | 
    
         
             
                                react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5 flex gap-1 items-center" },
         
     | 
| 
       118 
122 
     | 
    
         
             
                                    react_1.default.createElement(HistoryIcon_1.HistoryIcon, null),
         
     | 
| 
       119 
123 
     | 
    
         
             
                                    "Recent locations")),
         
     | 
| 
       120 
     | 
    
         
            -
                            react_1.default.createElement("ul", { "aria-label": "recent location nodes", className: "flex flex-col mt-2" },  
     | 
| 
       121 
     | 
    
         
            -
                                const lastResource = item.path[item.path.length - 1];
         
     | 
| 
      
 124 
     | 
    
         
            +
                            react_1.default.createElement("ul", { "aria-label": "recent location nodes", className: "flex flex-col mt-2" }, filteredRecentSources.map((item, index) => {
         
     | 
| 
      
 125 
     | 
    
         
            +
                                const lastResource = item.path && item.path[item.path.length - 1];
         
     | 
| 
       122 
126 
     | 
    
         
             
                                const isSelectedSource = item.source?.id === selectedSource?.source.id &&
         
     | 
| 
       123 
     | 
    
         
            -
                                     
     | 
| 
       124 
     | 
    
         
            -
             
     | 
| 
       125 
     | 
    
         
            -
                                    recentLocationSelection;
         
     | 
| 
       126 
     | 
    
         
            -
                                return (react_1.default.createElement("li", { key: `${index}-${item.source?.id}-${item.rootNode?.id}`, className: "flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b" },
         
     | 
| 
      
 127 
     | 
    
         
            +
                                    lastResource?.id === recentLocationSelection?.path?.[recentLocationSelection.path?.length - 1]?.id;
         
     | 
| 
      
 128 
     | 
    
         
            +
                                return (react_1.default.createElement("li", { key: `${index}-${item.source?.id}-${lastResource?.id}`, className: "flex items-center bg-white border border-b-0 last:border-b border-grey-200 first:rounded-t last:rounded-b" },
         
     | 
| 
       127 
129 
     | 
    
         
             
                                    react_1.default.createElement("button", { type: "button", onClick: () => handleRecentLocationClick(item), className: `relative grow flex items-center p-2.5 hover:bg-gray-100 focus:bg-gray-100` },
         
     | 
| 
       128 
     | 
    
         
            -
                                        react_1.default.createElement(generic_browser_lib_1.Icon, { icon: (lastResource?.type.code ||  
     | 
| 
       129 
     | 
    
         
            -
                                        react_1.default.createElement("span", { className: "text-left mr-7" }, lastResource?.name || item. 
     | 
| 
      
 130 
     | 
    
         
            +
                                        react_1.default.createElement(generic_browser_lib_1.Icon, { icon: (lastResource?.type.code || 'folder'), resourceSource: "matrix", "aria-label": lastResource?.name || item.source?.name, className: "shrink-0 mr-2.5" }),
         
     | 
| 
      
 131 
     | 
    
         
            +
                                        react_1.default.createElement("span", { className: "text-left mr-7" }, lastResource?.name || item.source?.name),
         
     | 
| 
       130 
132 
     | 
    
         
             
                                        isSelectedSource && (react_1.default.createElement(generic_browser_lib_1.Icon, { icon: 'selected', "aria-label": "selected", className: "absolute right-4" })))));
         
     | 
| 
       131 
133 
     | 
    
         
             
                            })))),
         
     | 
| 
       132 
134 
     | 
    
         
             
                        !isLoading &&
         
     | 
| 
       133 
135 
     | 
    
         
             
                            categorisedSources.map(({ key, label, sources }, index) => {
         
     | 
| 
       134 
     | 
    
         
            -
                                return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ||  
     | 
| 
      
 136 
     | 
    
         
            +
                                return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''}` },
         
     | 
| 
       135 
137 
     | 
    
         
             
                                    react_1.default.createElement("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" },
         
     | 
| 
       136 
138 
     | 
    
         
             
                                        react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" }, label)),
         
     | 
| 
       137 
139 
     | 
    
         
             
                                    sources?.length > 0 && (react_1.default.createElement("ul", { "aria-label": `${label} nodes`, className: "flex flex-col mt-2" }, sources.map(({ source, resource }) => {
         
     | 
| 
         @@ -2,6 +2,7 @@ import React from 'react'; 
     | 
|
| 
       2 
2 
     | 
    
         
             
            import { OverlayTriggerState } from 'react-stately';
         
     | 
| 
       3 
3 
     | 
    
         
             
            import { DOMAttributes, FocusableElement } from '@react-types/shared';
         
     | 
| 
       4 
4 
     | 
    
         
             
            import { Source, ScopedSource, Resource } from '../types';
         
     | 
| 
      
 5 
     | 
    
         
            +
            import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
         
     | 
| 
       5 
6 
     | 
    
         
             
            export interface SourceListProps {
         
     | 
| 
       6 
7 
     | 
    
         
             
                sources: Source[];
         
     | 
| 
       7 
8 
     | 
    
         
             
                selectedResource?: Resource | null;
         
     | 
| 
         @@ -11,7 +12,8 @@ export interface SourceListProps { 
     | 
|
| 
       11 
12 
     | 
    
         
             
                onSourceDrilldown: (source: ScopedSource) => void;
         
     | 
| 
       12 
13 
     | 
    
         
             
                handleReload: () => void;
         
     | 
| 
       13 
14 
     | 
    
         
             
                setSource: (source: ScopedSource | null, path?: Resource[]) => void;
         
     | 
| 
      
 15 
     | 
    
         
            +
                recentSources: RecentResourcesPaths[];
         
     | 
| 
       14 
16 
     | 
    
         
             
                error: Error | null;
         
     | 
| 
       15 
17 
     | 
    
         
             
            }
         
     | 
| 
       16 
     | 
    
         
            -
            declare const SourceList: ({ sources, selectedResource, previewModalState, isLoading, onSourceSelect, onSourceDrilldown, handleReload, setSource, error, }: SourceListProps) => React.JSX.Element;
         
     | 
| 
      
 18 
     | 
    
         
            +
            declare const SourceList: ({ sources, selectedResource, previewModalState, isLoading, onSourceSelect, onSourceDrilldown, handleReload, setSource, recentSources, error, }: SourceListProps) => React.JSX.Element;
         
     | 
| 
       17 
19 
     | 
    
         
             
            export default SourceList;
         
     | 
| 
         @@ -30,12 +30,11 @@ const react_1 = __importStar(require("react")); 
     | 
|
| 
       30 
30 
     | 
    
         
             
            const generic_browser_lib_1 = require("@squiz/generic-browser-lib");
         
     | 
| 
       31 
31 
     | 
    
         
             
            const clsx_1 = __importDefault(require("clsx"));
         
     | 
| 
       32 
32 
     | 
    
         
             
            const useCategorisedSources_1 = require("../Hooks/useCategorisedSources");
         
     | 
| 
       33 
     | 
    
         
            -
            const useRecentLocations_1 = require("../Hooks/useRecentLocations");
         
     | 
| 
       34 
33 
     | 
    
         
             
            const HistoryIcon_1 = require("../Icons/HistoryIcon");
         
     | 
| 
       35 
     | 
    
         
            -
            const SourceList = function ({ sources, selectedResource, previewModalState, isLoading, onSourceSelect, onSourceDrilldown, handleReload, setSource, error, }) {
         
     | 
| 
      
 34 
     | 
    
         
            +
            const SourceList = function ({ sources, selectedResource, previewModalState, isLoading, onSourceSelect, onSourceDrilldown, handleReload, setSource, recentSources, error, }) {
         
     | 
| 
       36 
35 
     | 
    
         
             
                const categorisedSources = (0, useCategorisedSources_1.useCategorisedSources)(sources);
         
     | 
| 
       37 
36 
     | 
    
         
             
                const listRef = (0, react_1.useRef)(null);
         
     | 
| 
       38 
     | 
    
         
            -
                const  
     | 
| 
      
 37 
     | 
    
         
            +
                const filteredRecentSources = recentSources.filter((item) => item.path?.length);
         
     | 
| 
       39 
38 
     | 
    
         
             
                (0, react_1.useEffect)(() => {
         
     | 
| 
       40 
39 
     | 
    
         
             
                    if (listRef.current) {
         
     | 
| 
       41 
40 
     | 
    
         
             
                        listRef.current?.focus({
         
     | 
| 
         @@ -50,23 +49,26 @@ const SourceList = function ({ sources, selectedResource, previewModalState, isL 
     | 
|
| 
       50 
49 
     | 
    
         
             
                }
         
     | 
| 
       51 
50 
     | 
    
         
             
                return (react_1.default.createElement("ul", { ref: listRef, tabIndex: -1, "aria-label": `Source list`, className: (0, clsx_1.default)('flex flex-col bg-gray-100 min-h-full focus-visible:outline-0 px-7 py-4') },
         
     | 
| 
       52 
51 
     | 
    
         
             
                    error && react_1.default.createElement(generic_browser_lib_1.ResourceState, { state: "error", message: error.message, handleReload: handleReload }),
         
     | 
| 
       53 
     | 
    
         
            -
                    !error &&  
     | 
| 
      
 52 
     | 
    
         
            +
                    !error && filteredRecentSources.length > 0 && (react_1.default.createElement("li", { className: `flex flex-col text-sm font-semibold text-grey-800` },
         
     | 
| 
       54 
53 
     | 
    
         
             
                        react_1.default.createElement("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" },
         
     | 
| 
       55 
54 
     | 
    
         
             
                            react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5 flex gap-1 items-center" },
         
     | 
| 
       56 
55 
     | 
    
         
             
                                react_1.default.createElement(HistoryIcon_1.HistoryIcon, null),
         
     | 
| 
       57 
56 
     | 
    
         
             
                                "Recent locations")),
         
     | 
| 
       58 
     | 
    
         
            -
                        react_1.default.createElement("ul", { "aria-label": `recent location nodes`, className: "flex flex-col" },  
     | 
| 
       59 
     | 
    
         
            -
                             
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
             
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
                                         
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
      
 57 
     | 
    
         
            +
                        react_1.default.createElement("ul", { "aria-label": `recent location nodes`, className: "flex flex-col" }, filteredRecentSources.map((item, index) => {
         
     | 
| 
      
 58 
     | 
    
         
            +
                            if (item.path) {
         
     | 
| 
      
 59 
     | 
    
         
            +
                                const lastResource = item.path[item.path.length - 1];
         
     | 
| 
      
 60 
     | 
    
         
            +
                                const [rootNode, ...path] = item.path;
         
     | 
| 
      
 61 
     | 
    
         
            +
                                return (react_1.default.createElement(generic_browser_lib_1.ResourceItem, { key: `${index}-${item.source?.id}-${lastResource?.id}`, item: { source: item.source, resource: lastResource }, label: lastResource?.name || item.source?.name || '', type: lastResource?.type?.code || 'folder', previewModalState: previewModalState, onSelect: () => {
         
     | 
| 
      
 62 
     | 
    
         
            +
                                        setSource({
         
     | 
| 
      
 63 
     | 
    
         
            +
                                            source: item.source,
         
     | 
| 
      
 64 
     | 
    
         
            +
                                            resource: rootNode,
         
     | 
| 
      
 65 
     | 
    
         
            +
                                        }, path);
         
     | 
| 
      
 66 
     | 
    
         
            +
                                    }, className: (0, clsx_1.default)(index === 0 && 'rounded-t-lg mt-3', index === filteredRecentSources.length - 1 && 'rounded-b-lg'), showChevron: true }));
         
     | 
| 
      
 67 
     | 
    
         
            +
                            }
         
     | 
| 
       66 
68 
     | 
    
         
             
                        })))),
         
     | 
| 
       67 
69 
     | 
    
         
             
                    !error &&
         
     | 
| 
       68 
70 
     | 
    
         
             
                        categorisedSources.map(({ key, label, sources }, index) => {
         
     | 
| 
       69 
     | 
    
         
            -
                            return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 ||  
     | 
| 
      
 71 
     | 
    
         
            +
                            return (react_1.default.createElement("li", { key: key, className: `flex flex-col text-sm font-semibold text-grey-800 ${index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''}` },
         
     | 
| 
       70 
72 
     | 
    
         
             
                                react_1.default.createElement("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" },
         
     | 
| 
       71 
73 
     | 
    
         
             
                                    react_1.default.createElement("span", { className: "z-10 bg-gray-100 px-2.5" }, label)),
         
     | 
| 
       72 
74 
     | 
    
         
             
                                sources.length > 0 && (react_1.default.createElement("ul", { "aria-label": `${label} nodes`, className: "flex flex-col" }, sources.map(({ source, resource }) => {
         
     | 
    
        package/lib/index.css
    CHANGED
    
    | 
         @@ -983,6 +983,9 @@ 
     | 
|
| 
       983 
983 
     | 
    
         
             
              --tw-blur: blur(8px);
         
     | 
| 
       984 
984 
     | 
    
         
             
              filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
         
     | 
| 
       985 
985 
     | 
    
         
             
            }
         
     | 
| 
      
 986 
     | 
    
         
            +
            .squiz-rb-scope .filter {
         
     | 
| 
      
 987 
     | 
    
         
            +
              filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
         
     | 
| 
      
 988 
     | 
    
         
            +
            }
         
     | 
| 
       986 
989 
     | 
    
         
             
            .squiz-rb-scope .resource-breadcrumb--collapsed .resource-breadcrumb__label {
         
     | 
| 
       987 
990 
     | 
    
         
             
              max-width: 250px;
         
     | 
| 
       988 
991 
     | 
    
         
             
              cursor: pointer;
         
     | 
    
        package/package.json
    CHANGED
    
    
| 
         @@ -1,35 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import { act, renderHook } from '@testing-library/react';
         
     | 
| 
       2 
2 
     | 
    
         
             
            import { useRecentLocations } from './useRecentLocations';
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            import { mockResource, mockSource } from '../__mocks__/MockModels';
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
4 
     | 
    
         
             
            describe('useRecentLocations', () => {
         
     | 
| 
       7 
     | 
    
         
            -
              const mockLocalStorageData = [
         
     | 
| 
       8 
     | 
    
         
            -
                {
         
     | 
| 
       9 
     | 
    
         
            -
                  path: [],
         
     | 
| 
       10 
     | 
    
         
            -
                  source: {
         
     | 
| 
       11 
     | 
    
         
            -
                    id: '1',
         
     | 
| 
       12 
     | 
    
         
            -
                    name: 'Test source',
         
     | 
| 
       13 
     | 
    
         
            -
                    nodes: [],
         
     | 
| 
       14 
     | 
    
         
            -
                  },
         
     | 
| 
       15 
     | 
    
         
            -
                  rootNode: {
         
     | 
| 
       16 
     | 
    
         
            -
                    childCount: 0,
         
     | 
| 
       17 
     | 
    
         
            -
                    id: '1',
         
     | 
| 
       18 
     | 
    
         
            -
                    lineages: [],
         
     | 
| 
       19 
     | 
    
         
            -
                    name: 'Test resource',
         
     | 
| 
       20 
     | 
    
         
            -
                    status: {
         
     | 
| 
       21 
     | 
    
         
            -
                      code: 'live',
         
     | 
| 
       22 
     | 
    
         
            -
                      name: 'Live',
         
     | 
| 
       23 
     | 
    
         
            -
                    },
         
     | 
| 
       24 
     | 
    
         
            -
                    type: {
         
     | 
| 
       25 
     | 
    
         
            -
                      code: 'folder',
         
     | 
| 
       26 
     | 
    
         
            -
                      name: 'Folder',
         
     | 
| 
       27 
     | 
    
         
            -
                    },
         
     | 
| 
       28 
     | 
    
         
            -
                    url: 'https://no-where.com',
         
     | 
| 
       29 
     | 
    
         
            -
                    urls: [],
         
     | 
| 
       30 
     | 
    
         
            -
                  },
         
     | 
| 
       31 
     | 
    
         
            -
                },
         
     | 
| 
       32 
     | 
    
         
            -
              ];
         
     | 
| 
      
 5 
     | 
    
         
            +
              const mockLocalStorageData = [{ resource: '20', source: '1' }];
         
     | 
| 
       33 
6 
     | 
    
         | 
| 
       34 
7 
     | 
    
         
             
              beforeEach(() => {
         
     | 
| 
       35 
8 
     | 
    
         
             
                localStorage.clear();
         
     | 
| 
         @@ -45,13 +18,17 @@ describe('useRecentLocations', () => { 
     | 
|
| 
       45 
18 
     | 
    
         | 
| 
       46 
19 
     | 
    
         
             
                act(() => {
         
     | 
| 
       47 
20 
     | 
    
         
             
                  result.current.addRecentLocation({
         
     | 
| 
       48 
     | 
    
         
            -
                     
     | 
| 
       49 
     | 
    
         
            -
                     
     | 
| 
       50 
     | 
    
         
            -
                    rootNode: mockResource(),
         
     | 
| 
      
 21 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 22 
     | 
    
         
            +
                    resource: '32',
         
     | 
| 
       51 
23 
     | 
    
         
             
                  });
         
     | 
| 
       52 
24 
     | 
    
         
             
                });
         
     | 
| 
       53 
25 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
                expect(result.current.recentLocations).toEqual( 
     | 
| 
      
 26 
     | 
    
         
            +
                expect(result.current.recentLocations).toEqual([
         
     | 
| 
      
 27 
     | 
    
         
            +
                  {
         
     | 
| 
      
 28 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 29 
     | 
    
         
            +
                    resource: '32',
         
     | 
| 
      
 30 
     | 
    
         
            +
                  },
         
     | 
| 
      
 31 
     | 
    
         
            +
                ]);
         
     | 
| 
       55 
32 
     | 
    
         
             
              });
         
     | 
| 
       56 
33 
     | 
    
         | 
| 
       57 
34 
     | 
    
         
             
              it('should not add duplicate recent locations', () => {
         
     | 
| 
         @@ -59,20 +36,31 @@ describe('useRecentLocations', () => { 
     | 
|
| 
       59 
36 
     | 
    
         | 
| 
       60 
37 
     | 
    
         
             
                act(() => {
         
     | 
| 
       61 
38 
     | 
    
         
             
                  result.current.addRecentLocation({
         
     | 
| 
       62 
     | 
    
         
            -
                     
     | 
| 
       63 
     | 
    
         
            -
                     
     | 
| 
       64 
     | 
    
         
            -
                    rootNode: mockResource(),
         
     | 
| 
      
 39 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 40 
     | 
    
         
            +
                    resource: '55',
         
     | 
| 
       65 
41 
     | 
    
         
             
                  });
         
     | 
| 
      
 42 
     | 
    
         
            +
                });
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                expect(result.current.recentLocations).toEqual([
         
     | 
| 
      
 45 
     | 
    
         
            +
                  {
         
     | 
| 
      
 46 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 47 
     | 
    
         
            +
                    resource: '55',
         
     | 
| 
      
 48 
     | 
    
         
            +
                  },
         
     | 
| 
      
 49 
     | 
    
         
            +
                ]);
         
     | 
| 
       66 
50 
     | 
    
         | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
                act(() => {
         
     | 
| 
       68 
52 
     | 
    
         
             
                  result.current.addRecentLocation({
         
     | 
| 
       69 
     | 
    
         
            -
                     
     | 
| 
       70 
     | 
    
         
            -
                     
     | 
| 
       71 
     | 
    
         
            -
                    rootNode: mockResource(),
         
     | 
| 
      
 53 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 54 
     | 
    
         
            +
                    resource: '55',
         
     | 
| 
       72 
55 
     | 
    
         
             
                  });
         
     | 
| 
       73 
56 
     | 
    
         
             
                });
         
     | 
| 
       74 
57 
     | 
    
         | 
| 
       75 
     | 
    
         
            -
                expect(result.current.recentLocations).toEqual( 
     | 
| 
      
 58 
     | 
    
         
            +
                expect(result.current.recentLocations).toEqual([
         
     | 
| 
      
 59 
     | 
    
         
            +
                  {
         
     | 
| 
      
 60 
     | 
    
         
            +
                    source: '1',
         
     | 
| 
      
 61 
     | 
    
         
            +
                    resource: '55',
         
     | 
| 
      
 62 
     | 
    
         
            +
                  },
         
     | 
| 
      
 63 
     | 
    
         
            +
                ]);
         
     | 
| 
       76 
64 
     | 
    
         
             
              });
         
     | 
| 
       77 
65 
     | 
    
         | 
| 
       78 
66 
     | 
    
         
             
              it('should load recent locations from local storage on mount', () => {
         
     | 
| 
         @@ -82,4 +70,12 @@ describe('useRecentLocations', () => { 
     | 
|
| 
       82 
70 
     | 
    
         | 
| 
       83 
71 
     | 
    
         
             
                expect(result.current.recentLocations).toEqual(mockLocalStorageData);
         
     | 
| 
       84 
72 
     | 
    
         
             
              });
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
              it('should handle local storage recent locations not being in the correct format', () => {
         
     | 
| 
      
 75 
     | 
    
         
            +
                localStorage.setItem('rb_recent_locations', JSON.stringify({}));
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                const { result } = renderHook(() => useRecentLocations());
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                expect(result.current.recentLocations).toEqual([]);
         
     | 
| 
      
 80 
     | 
    
         
            +
              });
         
     | 
| 
       85 
81 
     | 
    
         
             
            });
         
     | 
| 
         @@ -1,14 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            import { useState } from 'react';
         
     | 
| 
       2 
     | 
    
         
            -
            import {  
     | 
| 
       3 
     | 
    
         
            -
             
     | 
| 
       4 
     | 
    
         
            -
            export interface RecentLocation {
         
     | 
| 
       5 
     | 
    
         
            -
              rootNode: Resource | null;
         
     | 
| 
       6 
     | 
    
         
            -
              source: Source;
         
     | 
| 
       7 
     | 
    
         
            -
              path: Array<Resource>;
         
     | 
| 
       8 
     | 
    
         
            -
            }
         
     | 
| 
      
 2 
     | 
    
         
            +
            import { ResourceReference } from '../types';
         
     | 
| 
       9 
3 
     | 
    
         | 
| 
       10 
4 
     | 
    
         
             
            export const useRecentLocations = (maxLocations = 3, storageKey = 'rb_recent_locations') => {
         
     | 
| 
       11 
     | 
    
         
            -
              let initialRecentLocations = [];
         
     | 
| 
      
 5 
     | 
    
         
            +
              let initialRecentLocations: Array<ResourceReference> = [];
         
     | 
| 
       12 
6 
     | 
    
         | 
| 
       13 
7 
     | 
    
         
             
              try {
         
     | 
| 
       14 
8 
     | 
    
         
             
                initialRecentLocations = JSON.parse(localStorage.getItem(storageKey) ?? '[]');
         
     | 
| 
         @@ -21,11 +15,16 @@ export const useRecentLocations = (maxLocations = 3, storageKey = 'rb_recent_loc 
     | 
|
| 
       21 
15 
     | 
    
         
             
                initialRecentLocations = [];
         
     | 
| 
       22 
16 
     | 
    
         
             
              }
         
     | 
| 
       23 
17 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
               
     | 
| 
      
 18 
     | 
    
         
            +
              // Check if any item in the current recent locations is not the right format, if so, we reset it
         
     | 
| 
      
 19 
     | 
    
         
            +
              if (initialRecentLocations.find((item) => !(item?.resource?.length && item?.source?.length))) {
         
     | 
| 
      
 20 
     | 
    
         
            +
                initialRecentLocations = [];
         
     | 
| 
      
 21 
     | 
    
         
            +
              }
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              const [recentLocations, setRecentLocations] = useState<Array<ResourceReference>>(initialRecentLocations);
         
     | 
| 
       25 
24 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
              const addRecentLocation = (newLocation:  
     | 
| 
      
 25 
     | 
    
         
            +
              const addRecentLocation = (newLocation: ResourceReference) => {
         
     | 
| 
       27 
26 
     | 
    
         
             
                // Check if the new location to make sure we don't already have a recent location for this
         
     | 
| 
       28 
     | 
    
         
            -
                if ( 
     | 
| 
      
 27 
     | 
    
         
            +
                if (recentLocations.find((item) => item.resource === newLocation.resource && item.source === newLocation.source)) {
         
     | 
| 
       29 
28 
     | 
    
         
             
                  return;
         
     | 
| 
       30 
29 
     | 
    
         
             
                }
         
     | 
| 
       31 
30 
     | 
    
         | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import { Resource, OnRequestResource, OnRequestSources, Source } from '../types';
         
     | 
| 
      
 2 
     | 
    
         
            +
            import { useAsync } from '@squiz/generic-browser-lib';
         
     | 
| 
      
 3 
     | 
    
         
            +
            import { findBestMatchLineage } from '../utils/findBestMatchLineage';
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            export type RecentResourcesPathsProps = {
         
     | 
| 
      
 6 
     | 
    
         
            +
              sourceIds?: string[];
         
     | 
| 
      
 7 
     | 
    
         
            +
              resources?: Resource | null | (Resource | null)[];
         
     | 
| 
      
 8 
     | 
    
         
            +
              onRequestResource: OnRequestResource;
         
     | 
| 
      
 9 
     | 
    
         
            +
              onRequestSources: OnRequestSources;
         
     | 
| 
      
 10 
     | 
    
         
            +
            };
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            export type RecentResourcesPaths = {
         
     | 
| 
      
 13 
     | 
    
         
            +
              source?: Source;
         
     | 
| 
      
 14 
     | 
    
         
            +
              path?: Resource[];
         
     | 
| 
      
 15 
     | 
    
         
            +
            };
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            export const useRecentResourcesPaths = ({
         
     | 
| 
      
 18 
     | 
    
         
            +
              sourceIds,
         
     | 
| 
      
 19 
     | 
    
         
            +
              resources,
         
     | 
| 
      
 20 
     | 
    
         
            +
              onRequestResource,
         
     | 
| 
      
 21 
     | 
    
         
            +
              onRequestSources,
         
     | 
| 
      
 22 
     | 
    
         
            +
            }: RecentResourcesPathsProps) => {
         
     | 
| 
      
 23 
     | 
    
         
            +
              const callbackArray = sourceIds?.map((sourceId, index) => async () => {
         
     | 
| 
      
 24 
     | 
    
         
            +
                let path: Resource[] | undefined;
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                const sources = await onRequestSources();
         
     | 
| 
      
 27 
     | 
    
         
            +
                const source = sources.find((source) => source.id === sourceId);
         
     | 
| 
      
 28 
     | 
    
         
            +
                const resource = Array.isArray(resources) ? resources[index] : null;
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                if (sourceId && source && resource) {
         
     | 
| 
      
 31 
     | 
    
         
            +
                  const bestMatchLineage = findBestMatchLineage(source, resource);
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  if (Array.isArray(bestMatchLineage) && bestMatchLineage.length > 0) {
         
     | 
| 
      
 34 
     | 
    
         
            +
                    path = await Promise.all(
         
     | 
| 
      
 35 
     | 
    
         
            +
                      bestMatchLineage.map(async (resourceId) => {
         
     | 
| 
      
 36 
     | 
    
         
            +
                        return onRequestResource({ source: sourceId, resource: resourceId });
         
     | 
| 
      
 37 
     | 
    
         
            +
                      }),
         
     | 
| 
      
 38 
     | 
    
         
            +
                    );
         
     | 
| 
      
 39 
     | 
    
         
            +
                  } else {
         
     | 
| 
      
 40 
     | 
    
         
            +
                    path = [resource];
         
     | 
| 
      
 41 
     | 
    
         
            +
                  }
         
     | 
| 
      
 42 
     | 
    
         
            +
                }
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                return { source, path };
         
     | 
| 
      
 45 
     | 
    
         
            +
              });
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              return useAsync(
         
     | 
| 
      
 48 
     | 
    
         
            +
                {
         
     | 
| 
      
 49 
     | 
    
         
            +
                  callback: callbackArray ? callbackArray : () => null,
         
     | 
| 
      
 50 
     | 
    
         
            +
                  defaultValue: [] as RecentResourcesPaths[],
         
     | 
| 
      
 51 
     | 
    
         
            +
                },
         
     | 
| 
      
 52 
     | 
    
         
            +
                [JSON.stringify(sourceIds), resources],
         
     | 
| 
      
 53 
     | 
    
         
            +
              );
         
     | 
| 
      
 54 
     | 
    
         
            +
            };
         
     |