@squiz/resource-browser 1.68.1 → 1.69.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/lib/Hooks/usePreselectedResourcePath.js +8 -3
- package/lib/Hooks/useRecentLocations.d.ts +3 -8
- package/lib/Hooks/useRecentLocations.js +5 -1
- package/lib/Hooks/useRecentResourcesPaths.d.ts +20 -0
- package/lib/Hooks/useRecentResourcesPaths.js +30 -0
- package/lib/Hooks/useResource.d.ts +13 -0
- package/lib/Hooks/useResource.js +12 -1
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +62 -21
- package/lib/SourceDropdown/SourceDropdown.d.ts +3 -1
- package/lib/SourceDropdown/SourceDropdown.js +24 -22
- package/lib/SourceList/SourceList.d.ts +5 -1
- package/lib/SourceList/SourceList.js +19 -14
- package/lib/index.css +3 -0
- package/package.json +1 -1
- package/src/Hooks/usePreselectedResourcePath.ts +9 -5
- package/src/Hooks/useRecentLocations.spec.ts +36 -40
- package/src/Hooks/useRecentLocations.ts +10 -11
- package/src/Hooks/useRecentResourcesPaths.ts +54 -0
- package/src/Hooks/useResource.spec.ts +30 -1
- package/src/Hooks/useResource.ts +19 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +18 -0
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +63 -21
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +12 -1
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +79 -30
- package/src/SourceDropdown/SourceDropdown.spec.tsx +92 -27
- package/src/SourceDropdown/SourceDropdown.tsx +33 -29
- package/src/SourceList/SourceList.spec.tsx +133 -71
- package/src/SourceList/SourceList.stories.tsx +14 -6
- package/src/SourceList/SourceList.tsx +55 -29
- package/src/SourceList/sample-sources.json +34 -2
- package/src/__mocks__/StorybookHelpers.ts +30 -1
- package/src/index.stories.tsx +8 -2
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# @squiz/resource-browser
|
2
2
|
|
3
|
+
## 1.69.1
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- 5cd6ef6: Updated logic & handling of recent locations in resource browser
|
8
|
+
|
9
|
+
## 1.69.0
|
10
|
+
|
11
|
+
### Minor Changes
|
12
|
+
|
13
|
+
- b642152: updates to source list node selection
|
14
|
+
|
3
15
|
## 1.68.1
|
4
16
|
|
5
17
|
### Patch Changes
|
@@ -14,9 +14,14 @@ const usePreselectedResourcePath = ({ sourceId, resource, onRequestResource, onR
|
|
14
14
|
}
|
15
15
|
if (sourceId && source && resource) {
|
16
16
|
const bestMatchLineage = (0, findBestMatchLineage_1.findBestMatchLineage)(source, resource);
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
if (Array.isArray(bestMatchLineage) && bestMatchLineage.length > 0) {
|
18
|
+
path = await Promise.all(bestMatchLineage.map(async (resourceId) => {
|
19
|
+
return onRequestResource({ source: sourceId, resource: resourceId });
|
20
|
+
}));
|
21
|
+
}
|
22
|
+
else {
|
23
|
+
path = [resource];
|
24
|
+
}
|
20
25
|
}
|
21
26
|
return { source, path };
|
22
27
|
},
|
@@ -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.
|
@@ -10,3 +10,14 @@ const useResource = ({ onRequestResource, reference }) => (0, generic_browser_li
|
|
10
10
|
defaultValue: null,
|
11
11
|
}, [reference?.source, reference?.resource]);
|
12
12
|
exports.useResource = useResource;
|
13
|
+
/**
|
14
|
+
* Loads the resources indicated by the provided reference.
|
15
|
+
*/
|
16
|
+
const useResources = ({ onRequestResource, references }) => {
|
17
|
+
const callbackArray = references?.map((item) => () => onRequestResource(item));
|
18
|
+
return (0, generic_browser_lib_1.useAsync)({
|
19
|
+
callback: callbackArray ? callbackArray : () => null,
|
20
|
+
defaultValue: null,
|
21
|
+
}, []);
|
22
|
+
};
|
23
|
+
exports.useResources = useResources;
|
@@ -38,12 +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)({});
|
45
|
+
const [selectedSource, setSelectedSource] = (0, react_1.useState)(null);
|
43
46
|
const [selectedResourceId, setSelectedResourceId] = (0, react_1.useState)(null);
|
44
47
|
const [previewModalOverlayProps, setPreviewModalOverlayProps] = (0, react_1.useState)({});
|
45
48
|
const { source, currentResource, hierarchy, setSource, push, popUntil } = (0, useResourcePath_1.useResourcePath)();
|
46
|
-
|
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;
|
47
65
|
const { data: sources, isLoading: isSourceLoading, reload: handleSourceReload, error: sourceError, } = (0, useSources_1.useSources)({ onRequestSources });
|
48
66
|
const { data: resources, isLoading: isResourcesLoading, reload: handleResourceReload, error: resourceError, } = (0, useChildResources_1.useChildResources)({ source, currentResource, onRequestChildren });
|
49
67
|
const { data: { source: preselectedSource, path: preselectedPath }, isLoading: isPreselectedResourcePathLoading, } = (0, usePreselectedResourcePath_1.usePreselectedResourcePath)({
|
@@ -52,43 +70,59 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques
|
|
52
70
|
onRequestResource,
|
53
71
|
onRequestSources,
|
54
72
|
});
|
55
|
-
const selectedResource = (0, react_1.useMemo)(() =>
|
73
|
+
const selectedResource = (0, react_1.useMemo)(() => {
|
74
|
+
if (selectedSource) {
|
75
|
+
return selectedSource?.nodes.find((resource) => resource.id === selectedResourceId) || null;
|
76
|
+
}
|
77
|
+
return resources.find((resource) => resource.id === selectedResourceId) || null;
|
78
|
+
}, [selectedResourceId, resources, selectedSource]);
|
56
79
|
const handleResourceDrillDown = (0, react_1.useCallback)((resource) => {
|
57
80
|
push(resource);
|
58
81
|
}, [push]);
|
59
82
|
const handleResourceSelected = (0, react_1.useCallback)((resource, overlayProps) => {
|
60
83
|
setPreviewModalOverlayProps(overlayProps);
|
84
|
+
setSelectedSource(null);
|
61
85
|
setSelectedResourceId(resource.id);
|
62
86
|
}, []);
|
63
87
|
const handleSourceDrilldown = (0, react_1.useCallback)((source) => {
|
88
|
+
setSelectedSource(null);
|
89
|
+
setSelectedResourceId(null);
|
64
90
|
setSource(source);
|
65
91
|
}, [setSource]);
|
92
|
+
const handleSourceSelected = (0, react_1.useCallback)((node, overlayProps) => {
|
93
|
+
const { source, resource } = node;
|
94
|
+
setPreviewModalOverlayProps(overlayProps);
|
95
|
+
setSelectedSource(source || null);
|
96
|
+
setSelectedResourceId(resource?.id || null);
|
97
|
+
}, []);
|
66
98
|
const handleReturnToRoot = (0, react_1.useCallback)(() => {
|
99
|
+
setSelectedSource(null);
|
100
|
+
setSelectedResourceId(null);
|
67
101
|
setSource(null);
|
68
102
|
}, [setSource]);
|
69
103
|
const handleDetailSelect = (0, react_1.useCallback)((resource) => {
|
70
|
-
|
104
|
+
const detailSelectedSource = selectedSource ?? source?.source;
|
105
|
+
onChange({ resource, source: detailSelectedSource });
|
71
106
|
// Find the path that got them to where they are
|
72
|
-
const
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
path: path,
|
81
|
-
source: source?.source,
|
82
|
-
});
|
107
|
+
const lastPathItem = hierarchy[hierarchy.length - 1]?.node;
|
108
|
+
const lastPathResource = lastPathItem && 'resource' in lastPathItem ? lastPathItem?.resource : lastPathItem;
|
109
|
+
if (lastPathResource) {
|
110
|
+
addRecentLocation({
|
111
|
+
resource: lastPathResource.id,
|
112
|
+
source: detailSelectedSource.id,
|
113
|
+
});
|
114
|
+
}
|
83
115
|
onClose();
|
84
|
-
}, [source, currentResource]);
|
116
|
+
}, [selectedSource, source, currentResource]);
|
85
117
|
const handleDetailClose = (0, react_1.useCallback)(() => {
|
118
|
+
setSelectedSource(null);
|
86
119
|
setSelectedResourceId(null);
|
87
120
|
}, []);
|
88
121
|
// Clear the selected resource if it no longer exists in the list of resources
|
89
122
|
// (eg. due to navigating up/down the tree).
|
90
123
|
(0, react_1.useEffect)(() => {
|
91
124
|
if (resources.length > 0 && selectedResourceId && !selectedResource) {
|
125
|
+
setSelectedSource(null);
|
92
126
|
setSelectedResourceId(null);
|
93
127
|
}
|
94
128
|
}, [resources, selectedResourceId, selectedResource]);
|
@@ -96,20 +130,24 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques
|
|
96
130
|
if (preselectedSource && preselectedPath?.length) {
|
97
131
|
const [rootNode, ...path] = preselectedPath;
|
98
132
|
const leaf = path.pop();
|
99
|
-
setSource({
|
100
|
-
source: preselectedSource,
|
101
|
-
resource: rootNode,
|
102
|
-
}, path);
|
103
133
|
if (leaf) {
|
134
|
+
setSource({
|
135
|
+
source: preselectedSource,
|
136
|
+
resource: rootNode,
|
137
|
+
}, path);
|
104
138
|
setSelectedResourceId(leaf.id);
|
105
139
|
}
|
140
|
+
else {
|
141
|
+
setSelectedSource(preselectedSource);
|
142
|
+
setSelectedResourceId(rootNode.id);
|
143
|
+
}
|
106
144
|
}
|
107
145
|
}, [preselectedSource, preselectedSource]);
|
108
146
|
return (react_1.default.createElement("div", { className: "relative flex flex-col h-full text-gray-800" },
|
109
147
|
react_1.default.createElement("div", { className: "flex items-center p-4.5" },
|
110
148
|
react_1.default.createElement("h2", { ...titleAriaProps, className: "text-xl leading-6 text-gray-800 font-semibold mr-6" }, title),
|
111
149
|
react_1.default.createElement("div", { className: "px-3 border-l border-gray-300 w-300px" },
|
112
|
-
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 })),
|
113
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" },
|
114
152
|
react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
|
115
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" })))),
|
@@ -117,7 +155,10 @@ function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onReques
|
|
117
155
|
react_1.default.createElement("div", { className: "overflow-y-scroll flex-1 grow-[3] border-r border-gray-300" },
|
118
156
|
react_1.default.createElement("h3", { className: "sr-only" }, "Resource List"),
|
119
157
|
hierarchy.length > 0 && (react_1.default.createElement(ResourceBreadcrumb_1.default, { hierarchy: hierarchy, onBreadcrumbSelect: popUntil, onReturnToRoot: handleReturnToRoot })),
|
120
|
-
!source && (react_1.default.createElement(SourceList_1.default, { sources: sources, 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 })),
|
121
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 }))),
|
122
163
|
react_1.default.createElement("div", { className: "sm:overflow-y-scroll sm:flex-1 sm:grow-[2] bg-white" },
|
123
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,14 +2,18 @@ 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[];
|
8
|
+
selectedResource?: Resource | null;
|
7
9
|
previewModalState: OverlayTriggerState;
|
8
10
|
isLoading: boolean;
|
9
11
|
onSourceSelect: (node: ScopedSource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
12
|
+
onSourceDrilldown: (source: ScopedSource) => void;
|
10
13
|
handleReload: () => void;
|
11
14
|
setSource: (source: ScopedSource | null, path?: Resource[]) => void;
|
15
|
+
recentSources: RecentResourcesPaths[];
|
12
16
|
error: Error | null;
|
13
17
|
}
|
14
|
-
declare const SourceList: ({ sources, previewModalState, isLoading, onSourceSelect, 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;
|
15
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, previewModalState, isLoading, onSourceSelect, 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,27 +49,33 @@ const SourceList = function ({ sources, previewModalState, isLoading, onSourceSe
|
|
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 }) => {
|
73
|
-
|
75
|
+
if (!resource || resource.childCount === 0) {
|
76
|
+
return (react_1.default.createElement(generic_browser_lib_1.ResourceItem, { key: `${source.id}-${resource?.id}`, item: { source, resource }, label: resource?.name || source.name, type: resource?.type.code || 'folder', previewModalState: previewModalState, onSelect: onSourceDrilldown, className: "mt-3 rounded-lg", showChevron: true }));
|
77
|
+
}
|
78
|
+
return (react_1.default.createElement(generic_browser_lib_1.ResourceItem, { key: `${source.id}-${resource?.id}`, item: { source, resource }, selected: resource?.id == selectedResource?.id && resource != null, label: resource?.name || source.name, type: resource?.type.code || 'folder', childCount: resource?.childCount || undefined, previewModalState: previewModalState, onSelect: onSourceSelect, onDrillDown: onSourceDrilldown, className: "mt-3 rounded-lg", showChevron: true }));
|
74
79
|
})))));
|
75
80
|
})));
|
76
81
|
};
|
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
@@ -34,11 +34,15 @@ export const usePreselectedResourcePath = ({
|
|
34
34
|
if (sourceId && source && resource) {
|
35
35
|
const bestMatchLineage = findBestMatchLineage(source, resource);
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
if (Array.isArray(bestMatchLineage) && bestMatchLineage.length > 0) {
|
38
|
+
path = await Promise.all(
|
39
|
+
bestMatchLineage.map(async (resourceId) => {
|
40
|
+
return onRequestResource({ source: sourceId, resource: resourceId });
|
41
|
+
}),
|
42
|
+
);
|
43
|
+
} else {
|
44
|
+
path = [resource];
|
45
|
+
}
|
42
46
|
}
|
43
47
|
|
44
48
|
return { source, path };
|