@squiz/resource-browser 1.69.2 → 2.1.8-rc.0

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +85 -38
  2. package/LICENSE.md +15 -0
  3. package/README.md +9 -0
  4. package/jest.config.ts +22 -21
  5. package/lib/Hooks/useSelectedState.d.ts +15 -0
  6. package/lib/Hooks/useSelectedState.js +16 -0
  7. package/lib/Hooks/useSources.d.ts +5 -4
  8. package/lib/Hooks/useSources.js +25 -1
  9. package/lib/MainContainer/MainContainer.d.ts +17 -0
  10. package/lib/MainContainer/MainContainer.js +61 -0
  11. package/lib/Plugin/Plugin.d.ts +13 -0
  12. package/lib/Plugin/Plugin.js +17 -0
  13. package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
  14. package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
  15. package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
  16. package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
  17. package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
  18. package/lib/ResourcePicker/ResourcePicker.js +14 -8
  19. package/lib/ResourcePicker/States/Selected.d.ts +10 -4
  20. package/lib/ResourcePicker/States/Selected.js +11 -32
  21. package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
  22. package/lib/SourceDropdown/SourceDropdown.js +20 -99
  23. package/lib/SourceList/SourceList.d.ts +5 -16
  24. package/lib/SourceList/SourceList.js +14 -75
  25. package/lib/index.css +42 -202
  26. package/lib/index.d.ts +7 -7
  27. package/lib/index.js +69 -13
  28. package/lib/types.d.ts +41 -59
  29. package/package.json +82 -80
  30. package/src/Hooks/useSelectedState.spec.ts +46 -0
  31. package/src/Hooks/useSelectedState.ts +22 -0
  32. package/src/Hooks/useSources.spec.ts +30 -12
  33. package/src/Hooks/useSources.ts +33 -4
  34. package/src/Icons/CircledLoopIcon.tsx +8 -8
  35. package/src/MainContainer/MainContainer.spec.tsx +203 -0
  36. package/src/MainContainer/MainContainer.stories.tsx +62 -0
  37. package/src/MainContainer/MainContainer.tsx +101 -0
  38. package/src/Plugin/Plugin.spec.tsx +46 -0
  39. package/src/Plugin/Plugin.tsx +20 -0
  40. package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
  41. package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
  42. package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
  43. package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
  44. package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
  45. package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
  46. package/src/ResourcePicker/ResourcePicker.tsx +79 -59
  47. package/src/ResourcePicker/States/Error.tsx +8 -8
  48. package/src/ResourcePicker/States/Loading.tsx +3 -3
  49. package/src/ResourcePicker/States/Selected.tsx +66 -73
  50. package/src/ResourcePicker/mock-image-resource.json +25 -47
  51. package/src/ResourcePicker/mock-resource.json +11 -13
  52. package/src/ResourcePicker/resource-picker.scss +13 -13
  53. package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
  54. package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
  55. package/src/SourceDropdown/SourceDropdown.tsx +80 -258
  56. package/src/SourceList/SourceList.spec.tsx +37 -430
  57. package/src/SourceList/SourceList.stories.tsx +17 -37
  58. package/src/SourceList/SourceList.tsx +28 -155
  59. package/src/__mocks__/MockModels.ts +56 -25
  60. package/src/__mocks__/PluginExample.tsx +98 -0
  61. package/src/__mocks__/StorybookHelpers.tsx +141 -0
  62. package/src/__mocks__/renderWithContext.tsx +14 -18
  63. package/src/__mocks__/sample-sources.json +32 -0
  64. package/src/index.scss +18 -8
  65. package/src/index.spec.tsx +277 -99
  66. package/src/index.stories.tsx +65 -39
  67. package/src/index.tsx +119 -57
  68. package/src/types.ts +54 -63
  69. package/tailwind.config.cjs +92 -92
  70. package/vite.config.js +12 -12
  71. package/lib/Hooks/useCategorisedSources.d.ts +0 -14
  72. package/lib/Hooks/useCategorisedSources.js +0 -38
  73. package/lib/Hooks/useChildResources.d.ts +0 -16
  74. package/lib/Hooks/useChildResources.js +0 -13
  75. package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
  76. package/lib/Hooks/usePreselectedResourcePath.js +0 -31
  77. package/lib/Hooks/useRecentLocations.d.ts +0 -5
  78. package/lib/Hooks/useRecentLocations.js +0 -38
  79. package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
  80. package/lib/Hooks/useRecentResourcesPaths.js +0 -30
  81. package/lib/Hooks/useResource.d.ts +0 -28
  82. package/lib/Hooks/useResource.js +0 -25
  83. package/lib/Hooks/useResourcePath.d.ts +0 -16
  84. package/lib/Hooks/useResourcePath.js +0 -64
  85. package/lib/Icons/HistoryIcon.d.ts +0 -4
  86. package/lib/Icons/HistoryIcon.js +0 -13
  87. package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
  88. package/lib/PreviewPanel/PreviewPanel.js +0 -8
  89. package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
  90. package/lib/PreviewPanel/details/MatrixResource.js +0 -35
  91. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
  92. package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
  93. package/lib/ResourceList/ResourceList.d.ts +0 -18
  94. package/lib/ResourceList/ResourceList.js +0 -49
  95. package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
  96. package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
  97. package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
  98. package/lib/StatusIndicator/StatusIndicator.js +0 -27
  99. package/lib/utils/findBestMatchLineage.d.ts +0 -2
  100. package/lib/utils/findBestMatchLineage.js +0 -28
  101. package/lib/utils/uuid.d.ts +0 -1
  102. package/lib/utils/uuid.js +0 -6
  103. package/src/Hooks/useCategorisedSources.spec.ts +0 -39
  104. package/src/Hooks/useCategorisedSources.ts +0 -46
  105. package/src/Hooks/useChildResources.spec.ts +0 -29
  106. package/src/Hooks/useChildResources.ts +0 -21
  107. package/src/Hooks/usePreselectedResourcePath.ts +0 -54
  108. package/src/Hooks/useRecentLocations.spec.ts +0 -81
  109. package/src/Hooks/useRecentLocations.ts +0 -44
  110. package/src/Hooks/useRecentResourcesPaths.ts +0 -54
  111. package/src/Hooks/useResource.spec.ts +0 -61
  112. package/src/Hooks/useResource.ts +0 -40
  113. package/src/Hooks/useResourcePath.spec.ts +0 -120
  114. package/src/Hooks/useResourcePath.ts +0 -76
  115. package/src/Icons/HistoryIcon.tsx +0 -17
  116. package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
  117. package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
  118. package/src/PreviewPanel/PreviewPanel.tsx +0 -6
  119. package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
  120. package/src/PreviewPanel/details/matrix-resource.scss +0 -16
  121. package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
  122. package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
  123. package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
  124. package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
  125. package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
  126. package/src/ResourceList/ResourceList.spec.tsx +0 -202
  127. package/src/ResourceList/ResourceList.stories.tsx +0 -40
  128. package/src/ResourceList/ResourceList.tsx +0 -83
  129. package/src/ResourceList/sample-resources.json +0 -851
  130. package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
  131. package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
  132. package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
  133. package/src/SourceList/sample-sources.json +0 -251
  134. package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
  135. package/src/StatusIndicator/StatusIndicator.tsx +0 -38
  136. package/src/__mocks__/JestHelpers.ts +0 -65
  137. package/src/__mocks__/StorybookHelpers.ts +0 -128
  138. package/src/__mocks__/jestHelpers.spec.ts +0 -38
  139. package/src/utils/findBestMatchLineage.spec.ts +0 -81
  140. package/src/utils/findBestMatchLineage.ts +0 -30
  141. package/src/utils/uuid.ts +0 -5
@@ -1,166 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- var __importDefault = (this && this.__importDefault) || function (mod) {
26
- return (mod && mod.__esModule) ? mod : { "default": mod };
27
- };
28
- Object.defineProperty(exports, "__esModule", { value: true });
29
- const react_1 = __importStar(require("react"));
30
- const react_stately_1 = require("react-stately");
31
- const SourceList_1 = __importDefault(require("../SourceList/SourceList"));
32
- const ResourceList_1 = __importDefault(require("../ResourceList/ResourceList"));
33
- const ResourceBreadcrumb_1 = __importDefault(require("../ResourceBreadcrumb/ResourceBreadcrumb"));
34
- const PreviewPanel_1 = __importDefault(require("../PreviewPanel/PreviewPanel"));
35
- const SourceDropdown_1 = __importDefault(require("../SourceDropdown/SourceDropdown"));
36
- const useResourcePath_1 = require("../Hooks/useResourcePath");
37
- const useChildResources_1 = require("../Hooks/useChildResources");
38
- const useSources_1 = require("../Hooks/useSources");
39
- const usePreselectedResourcePath_1 = require("../Hooks/usePreselectedResourcePath");
40
- const useRecentLocations_1 = require("../Hooks/useRecentLocations");
41
- const useResource_1 = require("../Hooks/useResource");
42
- const useRecentResourcesPaths_1 = require("../Hooks/useRecentResourcesPaths");
43
- function ResourcePickerContainer({ title, titleAriaProps, allowedTypes, onRequestSources, onRequestResource, onRequestChildren, onChange, onClose, preselectedSourceId, preselectedResource, }) {
44
- const previewModalState = (0, react_stately_1.useOverlayTriggerState)({});
45
- const [selectedSource, setSelectedSource] = (0, react_1.useState)(null);
46
- const [selectedResourceId, setSelectedResourceId] = (0, react_1.useState)(null);
47
- const [previewModalOverlayProps, setPreviewModalOverlayProps] = (0, react_1.useState)({});
48
- const { source, currentResource, hierarchy, setSource, push, popUntil } = (0, useResourcePath_1.useResourcePath)();
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;
65
- const { data: sources, isLoading: isSourceLoading, reload: handleSourceReload, error: sourceError, } = (0, useSources_1.useSources)({ onRequestSources });
66
- const { data: resources, isLoading: isResourcesLoading, reload: handleResourceReload, error: resourceError, } = (0, useChildResources_1.useChildResources)({ source, currentResource, onRequestChildren });
67
- const { data: { source: preselectedSource, path: preselectedPath }, isLoading: isPreselectedResourcePathLoading, } = (0, usePreselectedResourcePath_1.usePreselectedResourcePath)({
68
- sourceId: preselectedSourceId,
69
- resource: preselectedResource,
70
- onRequestResource,
71
- onRequestSources,
72
- });
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]);
79
- const handleResourceDrillDown = (0, react_1.useCallback)((resource) => {
80
- push(resource);
81
- }, [push]);
82
- const handleResourceSelected = (0, react_1.useCallback)((resource, overlayProps) => {
83
- setPreviewModalOverlayProps(overlayProps);
84
- setSelectedSource(null);
85
- setSelectedResourceId(resource.id);
86
- }, []);
87
- const handleSourceDrilldown = (0, react_1.useCallback)((source) => {
88
- setSelectedSource(null);
89
- setSelectedResourceId(null);
90
- setSource(source);
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
- }, []);
98
- const handleReturnToRoot = (0, react_1.useCallback)(() => {
99
- setSelectedSource(null);
100
- setSelectedResourceId(null);
101
- setSource(null);
102
- }, [setSource]);
103
- const handleDetailSelect = (0, react_1.useCallback)((resource) => {
104
- const detailSelectedSource = selectedSource ?? source?.source;
105
- onChange({ resource, source: detailSelectedSource });
106
- // Find the path that got them to where they are
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
- }
115
- onClose();
116
- }, [selectedSource, source, currentResource]);
117
- const handleDetailClose = (0, react_1.useCallback)(() => {
118
- setSelectedSource(null);
119
- setSelectedResourceId(null);
120
- }, []);
121
- // Clear the selected resource if it no longer exists in the list of resources
122
- // (eg. due to navigating up/down the tree).
123
- (0, react_1.useEffect)(() => {
124
- if (resources.length > 0 && selectedResourceId && !selectedResource) {
125
- setSelectedSource(null);
126
- setSelectedResourceId(null);
127
- }
128
- }, [resources, selectedResourceId, selectedResource]);
129
- (0, react_1.useEffect)(() => {
130
- if (preselectedSource && preselectedPath?.length) {
131
- const [rootNode, ...path] = preselectedPath;
132
- const leaf = path.pop();
133
- if (leaf) {
134
- setSource({
135
- source: preselectedSource,
136
- resource: rootNode,
137
- }, path);
138
- setSelectedResourceId(leaf.id);
139
- }
140
- else {
141
- setSelectedSource(preselectedSource);
142
- setSelectedResourceId(rootNode.id);
143
- }
144
- }
145
- }, [preselectedSource, preselectedSource]);
146
- return (react_1.default.createElement("div", { className: "relative flex flex-col h-full text-gray-800" },
147
- react_1.default.createElement("div", { className: "flex items-center p-4.5" },
148
- react_1.default.createElement("h2", { ...titleAriaProps, className: "text-xl leading-6 text-gray-800 font-semibold mr-6" }, title),
149
- react_1.default.createElement("div", { className: "px-3 border-l border-gray-300 w-300px" },
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 })),
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" },
152
- react_1.default.createElement("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
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" })))),
154
- react_1.default.createElement("div", { className: "flex border-t border-gray-300 h-[calc(100%-72px)]" },
155
- react_1.default.createElement("div", { className: "overflow-y-scroll flex-1 grow-[3] border-r border-gray-300" },
156
- react_1.default.createElement("h3", { className: "sr-only" }, "Resource List"),
157
- hierarchy.length > 0 && (react_1.default.createElement(ResourceBreadcrumb_1.default, { hierarchy: hierarchy, onBreadcrumbSelect: popUntil, onReturnToRoot: handleReturnToRoot })),
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 })),
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 }))),
163
- react_1.default.createElement("div", { className: "sm:overflow-y-scroll sm:flex-1 sm:grow-[2] bg-white" },
164
- react_1.default.createElement(PreviewPanel_1.default, { resource: isResourcesLoading ? null : selectedResource, modalState: previewModalState, previewModalOverlayProps: previewModalOverlayProps, allowedTypes: allowedTypes, onSelect: handleDetailSelect, onClose: handleDetailClose })))));
165
- }
166
- exports.default = ResourcePickerContainer;
@@ -1,8 +0,0 @@
1
- import React from 'react';
2
- import { Status } from '../types';
3
- export type StatusIndicatorProps = {
4
- className?: string;
5
- status: Status;
6
- };
7
- declare const StatusIndicator: ({ className, status }: StatusIndicatorProps) => React.JSX.Element;
8
- export default StatusIndicator;
@@ -1,27 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_1 = __importDefault(require("react"));
7
- const clsx_1 = __importDefault(require("clsx"));
8
- const statusColour = {
9
- // Duplicated from the Matrix repository.
10
- // src/Api/AssetManagementApi/Constants/AssetStatuses.php - contains a list of possible statuses.
11
- // frontend/src/styles/common/status-colors.scss - contains the colours used for those statuses in Matrix.
12
- unknown: '#ff0000',
13
- archived: '#c98a67',
14
- under_construction: '#94d1f9',
15
- pending_approval: '#d0bbf0',
16
- approved_to_go_live: '#f7eaa2',
17
- live: '#bfe60a',
18
- up_for_review: '#72cd32',
19
- safe_editing: '#ff97b0',
20
- safe_editing_pending_approval: '#d688db',
21
- safe_edit_approved_to_go_live: '#ffb34a',
22
- };
23
- const StatusIndicator = ({ className, status }) => {
24
- const color = statusColour[status.code] || statusColour.unknown;
25
- return (react_1.default.createElement("span", { style: { backgroundColor: color }, className: (0, clsx_1.default)('block rounded-full w-3 h-3 border-1 border-solid border-black border-opacity-20', className), title: status.name }));
26
- };
27
- exports.default = StatusIndicator;
@@ -1,2 +0,0 @@
1
- import { Source, Resource } from '../types';
2
- export declare const findBestMatchLineage: (source: Source, resource: Resource) => string[];
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findBestMatchLineage = void 0;
4
- const findBestMatchLineage = (source, resource) => {
5
- if (resource.lineages) {
6
- for (const lineage of resource.lineages) {
7
- // Lineage must:
8
- // 1. Appear beneath the root node.
9
- // 2. Not be the root node itself (root nodes can't be selected, to be changed in FEAAS-760).
10
- // TODO: FEAAS-760 update as necessary so the lineage will be returned even if it ends at the root node, eg:
11
- // const rootNode = source.nodes.find(node => lineage.resourceIds.includes(node.id));
12
- const rootNode = source.nodes.find((node) => {
13
- const index = lineage.resourceIds.indexOf(node.id);
14
- return index >= 0 && index < lineage.resourceIds.length - 1;
15
- });
16
- if (rootNode) {
17
- const rootNodeIndex = lineage.resourceIds.indexOf(rootNode.id);
18
- // Return the lineage starting from the root node. eg.
19
- // * Full lineage is: 1 > 10 > 100 > 1000 > 10000
20
- // * The source has a node with an ID of: 100
21
- // * The returned lineage will be: 100 > 1000 > 10000
22
- return lineage.resourceIds.slice(rootNodeIndex);
23
- }
24
- }
25
- }
26
- return [];
27
- };
28
- exports.findBestMatchLineage = findBestMatchLineage;
@@ -1 +0,0 @@
1
- export default function uuid(): string;
package/lib/utils/uuid.js DELETED
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- function uuid() {
4
- return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (Number(c) ^ (window.crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (Number(c) / 4)))).toString(16));
5
- }
6
- exports.default = uuid;
@@ -1,39 +0,0 @@
1
- import { renderHook } from '@testing-library/react';
2
- import { useCategorisedSources } from './useCategorisedSources';
3
- import { mockResource, mockSource } from '../__mocks__/MockModels';
4
-
5
- describe('useCategorisedSources', () => {
6
- it('Should return a list of scoped categories with non-restricted sources in their own category', () => {
7
- const resources = [mockResource({ name: 'Images' }), mockResource({ name: 'Audio files' })];
8
- const sources = [
9
- mockSource({
10
- id: '1',
11
- name: 'Matrix source',
12
- nodes: resources,
13
- }),
14
- mockSource({
15
- id: '2',
16
- name: 'Unrestricted source',
17
- }),
18
- ];
19
- const {
20
- result: { current: result },
21
- } = renderHook(() => useCategorisedSources(sources));
22
-
23
- expect(result).toEqual([
24
- {
25
- key: '1',
26
- label: 'Matrix source',
27
- sources: [
28
- { resource: resources[0], source: sources[0] },
29
- { resource: resources[1], source: sources[0] },
30
- ],
31
- },
32
- {
33
- key: 'other',
34
- label: 'Other systems',
35
- sources: [{ resource: null, source: sources[1] }],
36
- },
37
- ]);
38
- });
39
- });
@@ -1,46 +0,0 @@
1
- import { ScopedSource, Source } from '../types';
2
- import { useMemo } from 'react';
3
-
4
- export type CategorisedSource = {
5
- key: string;
6
- label: string;
7
- sources: ScopedSource[];
8
- };
9
-
10
- /**
11
- * Groups a list of source into scoped/un-scoped for rendering in the source list.
12
- *
13
- * @param {Source[]} sources
14
- *
15
- * @return {SourceList}
16
- */
17
- export const useCategorisedSources = (sources: Source[]): CategorisedSource[] => {
18
- return useMemo(() => {
19
- const categorised: CategorisedSource[] = [];
20
- const uncategorised: ScopedSource[] = [];
21
-
22
- sources.forEach((source) => {
23
- if (source.nodes.length > 0) {
24
- const category: CategorisedSource = { key: source.id, label: source.name, sources: [] };
25
-
26
- source.nodes.forEach((resource) => {
27
- category.sources.push({ source, resource });
28
- });
29
-
30
- categorised.push(category);
31
- } else {
32
- uncategorised.push({ source, resource: null });
33
- }
34
- });
35
-
36
- if (uncategorised.length > 0) {
37
- categorised.push({
38
- key: 'other',
39
- label: 'Other systems',
40
- sources: uncategorised,
41
- });
42
- }
43
-
44
- return categorised;
45
- }, [sources]);
46
- };
@@ -1,29 +0,0 @@
1
- import { renderHook, waitFor } from '@testing-library/react';
2
- import { mockResource, mockScopedSource } from '../__mocks__/MockModels';
3
- import { useChildResources } from './useChildResources';
4
-
5
- describe('useChildResources', () => {
6
- it('Should trigger and return the loaded child resources', async () => {
7
- const source = mockScopedSource();
8
- const currentResource = mockResource({ name: 'Current resource' });
9
- const children = [mockResource({ name: 'Child 1' })];
10
- const onRequestChildren = jest.fn().mockResolvedValue(children);
11
-
12
- const { result } = renderHook(() =>
13
- useChildResources({
14
- source,
15
- currentResource,
16
- onRequestChildren,
17
- }),
18
- );
19
-
20
- expect(result.current.isLoading).toBe(true);
21
- expect(result.current.error).toBe(null);
22
- expect(result.current.data).toEqual([]);
23
-
24
- await waitFor(() => expect(result.current.isLoading).toBe(false));
25
-
26
- expect(result.current.isLoading).toBe(false);
27
- expect(result.current.data).toBe(children);
28
- });
29
- });
@@ -1,21 +0,0 @@
1
- import { Resource, ScopedSource, Source } from '../types';
2
- import { useAsync } from '@squiz/generic-browser-lib';
3
-
4
- type UseChildResourcesProps = {
5
- source: ScopedSource | null;
6
- currentResource: Resource | null;
7
- onRequestChildren: (source: Source, resource: Resource | null) => Promise<Resource[]>;
8
- };
9
-
10
- /**
11
- * Triggers a reload of the child resources when the source or current resource change.
12
- */
13
- export const useChildResources = ({ source, currentResource, onRequestChildren }: UseChildResourcesProps) =>
14
- useAsync(
15
- {
16
- callback: () => (source ? onRequestChildren(source.source, currentResource || source.resource) : []),
17
- defaultValue: [],
18
- ignorePrevious: true,
19
- },
20
- [source, currentResource],
21
- );
@@ -1,54 +0,0 @@
1
- import { Source, Resource, OnRequestResource, OnRequestSources } from '../types';
2
- import { useAsync } from '@squiz/generic-browser-lib';
3
- import { findBestMatchLineage } from '../utils/findBestMatchLineage';
4
-
5
- export type PreselectedResourceProps = {
6
- sourceId?: string;
7
- resource?: Resource | null;
8
- onRequestResource: OnRequestResource;
9
- onRequestSources: OnRequestSources;
10
- };
11
-
12
- export type PreselectedResourcePath = {
13
- source?: Source;
14
- path?: Resource[];
15
- };
16
-
17
- export const usePreselectedResourcePath = ({
18
- sourceId,
19
- resource,
20
- onRequestResource,
21
- onRequestSources,
22
- }: PreselectedResourceProps) => {
23
- return useAsync(
24
- {
25
- callback: async () => {
26
- let source: Source | undefined;
27
- let path: Resource[] | undefined;
28
-
29
- if (sourceId) {
30
- const sources = await onRequestSources();
31
- source = sources.find((source) => source.id === sourceId);
32
- }
33
-
34
- if (sourceId && source && resource) {
35
- const bestMatchLineage = findBestMatchLineage(source, resource);
36
-
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
- }
46
- }
47
-
48
- return { source, path };
49
- },
50
- defaultValue: {} as PreselectedResourcePath,
51
- },
52
- [sourceId, resource],
53
- );
54
- };
@@ -1,81 +0,0 @@
1
- import { act, renderHook } from '@testing-library/react';
2
- import { useRecentLocations } from './useRecentLocations';
3
-
4
- describe('useRecentLocations', () => {
5
- const mockLocalStorageData = [{ resource: '20', source: '1' }];
6
-
7
- beforeEach(() => {
8
- localStorage.clear();
9
- });
10
-
11
- it('should initialize with empty array if no recent locations in storage', () => {
12
- const { result } = renderHook(() => useRecentLocations());
13
- expect(result.current.recentLocations).toEqual([]);
14
- });
15
-
16
- it('should add a new recent location', () => {
17
- const { result } = renderHook(() => useRecentLocations());
18
-
19
- act(() => {
20
- result.current.addRecentLocation({
21
- source: '1',
22
- resource: '32',
23
- });
24
- });
25
-
26
- expect(result.current.recentLocations).toEqual([
27
- {
28
- source: '1',
29
- resource: '32',
30
- },
31
- ]);
32
- });
33
-
34
- it('should not add duplicate recent locations', () => {
35
- const { result } = renderHook(() => useRecentLocations());
36
-
37
- act(() => {
38
- result.current.addRecentLocation({
39
- source: '1',
40
- resource: '55',
41
- });
42
- });
43
-
44
- expect(result.current.recentLocations).toEqual([
45
- {
46
- source: '1',
47
- resource: '55',
48
- },
49
- ]);
50
-
51
- act(() => {
52
- result.current.addRecentLocation({
53
- source: '1',
54
- resource: '55',
55
- });
56
- });
57
-
58
- expect(result.current.recentLocations).toEqual([
59
- {
60
- source: '1',
61
- resource: '55',
62
- },
63
- ]);
64
- });
65
-
66
- it('should load recent locations from local storage on mount', () => {
67
- localStorage.setItem('rb_recent_locations', JSON.stringify(mockLocalStorageData));
68
-
69
- const { result } = renderHook(() => useRecentLocations());
70
-
71
- expect(result.current.recentLocations).toEqual(mockLocalStorageData);
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
- });
81
- });
@@ -1,44 +0,0 @@
1
- import { useState } from 'react';
2
- import { ResourceReference } from '../types';
3
-
4
- export const useRecentLocations = (maxLocations = 3, storageKey = 'rb_recent_locations') => {
5
- let initialRecentLocations: Array<ResourceReference> = [];
6
-
7
- try {
8
- initialRecentLocations = JSON.parse(localStorage.getItem(storageKey) ?? '[]');
9
- } catch (_ignored) {
10
- // Ignore this error (edge case someone messing with the local storage...)
11
- }
12
-
13
- // We just have an extra safety check here in case local storage has been messed with and set as {} for example
14
- if (!Array.isArray(initialRecentLocations)) {
15
- initialRecentLocations = [];
16
- }
17
-
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);
24
-
25
- const addRecentLocation = (newLocation: ResourceReference) => {
26
- // Check if the new location to make sure we don't already have a recent location for this
27
- if (recentLocations.find((item) => item.resource === newLocation.resource && item.source === newLocation.source)) {
28
- return;
29
- }
30
-
31
- const updatedLocations = [newLocation, ...recentLocations.slice(0, maxLocations - 1)];
32
-
33
- // Set state
34
- setRecentLocations(updatedLocations);
35
-
36
- // Update local storage
37
- localStorage.setItem(storageKey, JSON.stringify(updatedLocations));
38
- };
39
-
40
- return {
41
- recentLocations,
42
- addRecentLocation,
43
- };
44
- };
@@ -1,54 +0,0 @@
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
- };