@squiz/resource-browser 1.69.2 → 2.1.9-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.
- package/CHANGELOG.md +89 -38
- package/LICENSE.md +15 -0
- package/README.md +9 -0
- package/jest.config.ts +22 -21
- package/lib/Hooks/useSelectedState.d.ts +15 -0
- package/lib/Hooks/useSelectedState.js +16 -0
- package/lib/Hooks/useSources.d.ts +6 -6
- package/lib/Hooks/useSources.js +26 -1
- package/lib/MainContainer/MainContainer.d.ts +17 -0
- package/lib/MainContainer/MainContainer.js +61 -0
- package/lib/Plugin/Plugin.d.ts +13 -0
- package/lib/Plugin/Plugin.js +17 -0
- package/lib/ResourceBrowserContext/ResourceBrowserContext.d.ts +2 -3
- package/lib/ResourceBrowserContext/ResourceBrowserContext.js +4 -17
- package/lib/ResourceBrowserInput/ResourceBrowserInput.d.ts +24 -0
- package/lib/ResourceBrowserInput/ResourceBrowserInput.js +16 -0
- package/lib/ResourcePicker/ResourcePicker.d.ts +6 -4
- package/lib/ResourcePicker/ResourcePicker.js +14 -8
- package/lib/ResourcePicker/States/Selected.d.ts +10 -4
- package/lib/ResourcePicker/States/Selected.js +11 -32
- package/lib/SourceDropdown/SourceDropdown.d.ts +5 -11
- package/lib/SourceDropdown/SourceDropdown.js +20 -99
- package/lib/SourceList/SourceList.d.ts +5 -16
- package/lib/SourceList/SourceList.js +14 -75
- package/lib/index.css +42 -202
- package/lib/index.d.ts +7 -7
- package/lib/index.js +69 -13
- package/lib/types.d.ts +41 -59
- package/package.json +82 -80
- package/src/Hooks/useSelectedState.spec.ts +46 -0
- package/src/Hooks/useSelectedState.ts +22 -0
- package/src/Hooks/useSources.spec.ts +60 -13
- package/src/Hooks/useSources.ts +35 -5
- package/src/Icons/CircledLoopIcon.tsx +8 -8
- package/src/MainContainer/MainContainer.spec.tsx +203 -0
- package/src/MainContainer/MainContainer.stories.tsx +62 -0
- package/src/MainContainer/MainContainer.tsx +101 -0
- package/src/Plugin/Plugin.spec.tsx +46 -0
- package/src/Plugin/Plugin.tsx +20 -0
- package/src/ResourceBrowserContext/ResourceBrowserContext.spec.tsx +65 -106
- package/src/ResourceBrowserContext/ResourceBrowserContext.tsx +24 -39
- package/src/ResourceBrowserInput/ResourceBrowserInput.spec.tsx +192 -0
- package/src/ResourceBrowserInput/ResourceBrowserInput.tsx +81 -0
- package/src/ResourcePicker/ResourcePicker.spec.tsx +159 -116
- package/src/ResourcePicker/ResourcePicker.stories.tsx +28 -24
- package/src/ResourcePicker/ResourcePicker.tsx +79 -59
- package/src/ResourcePicker/States/Error.tsx +8 -8
- package/src/ResourcePicker/States/Loading.tsx +3 -3
- package/src/ResourcePicker/States/Selected.tsx +66 -73
- package/src/ResourcePicker/mock-image-resource.json +25 -47
- package/src/ResourcePicker/mock-resource.json +11 -13
- package/src/ResourcePicker/resource-picker.scss +13 -13
- package/src/SourceDropdown/SourceDropdown.spec.tsx +65 -391
- package/src/SourceDropdown/SourceDropdown.stories.tsx +21 -24
- package/src/SourceDropdown/SourceDropdown.tsx +80 -258
- package/src/SourceList/SourceList.spec.tsx +37 -430
- package/src/SourceList/SourceList.stories.tsx +17 -37
- package/src/SourceList/SourceList.tsx +28 -155
- package/src/__mocks__/MockModels.ts +56 -25
- package/src/__mocks__/PluginExample.tsx +98 -0
- package/src/__mocks__/StorybookHelpers.tsx +141 -0
- package/src/__mocks__/renderWithContext.tsx +14 -18
- package/src/__mocks__/sample-sources.json +32 -0
- package/src/index.scss +18 -8
- package/src/index.spec.tsx +277 -99
- package/src/index.stories.tsx +65 -39
- package/src/index.tsx +119 -57
- package/src/types.ts +54 -63
- package/tailwind.config.cjs +92 -92
- package/vite.config.js +12 -12
- package/lib/Hooks/useCategorisedSources.d.ts +0 -14
- package/lib/Hooks/useCategorisedSources.js +0 -38
- package/lib/Hooks/useChildResources.d.ts +0 -16
- package/lib/Hooks/useChildResources.js +0 -13
- package/lib/Hooks/usePreselectedResourcePath.d.ts +0 -20
- package/lib/Hooks/usePreselectedResourcePath.js +0 -31
- package/lib/Hooks/useRecentLocations.d.ts +0 -5
- package/lib/Hooks/useRecentLocations.js +0 -38
- package/lib/Hooks/useRecentResourcesPaths.d.ts +0 -20
- package/lib/Hooks/useRecentResourcesPaths.js +0 -30
- package/lib/Hooks/useResource.d.ts +0 -28
- package/lib/Hooks/useResource.js +0 -25
- package/lib/Hooks/useResourcePath.d.ts +0 -16
- package/lib/Hooks/useResourcePath.js +0 -64
- package/lib/Icons/HistoryIcon.d.ts +0 -4
- package/lib/Icons/HistoryIcon.js +0 -13
- package/lib/PreviewPanel/PreviewPanel.d.ts +0 -5
- package/lib/PreviewPanel/PreviewPanel.js +0 -8
- package/lib/PreviewPanel/details/MatrixResource.d.ts +0 -7
- package/lib/PreviewPanel/details/MatrixResource.js +0 -35
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.d.ts +0 -9
- package/lib/ResourceBreadcrumb/ResourceBreadcrumb.js +0 -54
- package/lib/ResourceList/ResourceList.d.ts +0 -18
- package/lib/ResourceList/ResourceList.js +0 -49
- package/lib/ResourcePickerContainer/ResourcePickerContainer.d.ts +0 -17
- package/lib/ResourcePickerContainer/ResourcePickerContainer.js +0 -166
- package/lib/StatusIndicator/StatusIndicator.d.ts +0 -8
- package/lib/StatusIndicator/StatusIndicator.js +0 -27
- package/lib/utils/findBestMatchLineage.d.ts +0 -2
- package/lib/utils/findBestMatchLineage.js +0 -28
- package/lib/utils/uuid.d.ts +0 -1
- package/lib/utils/uuid.js +0 -6
- package/src/Hooks/useCategorisedSources.spec.ts +0 -39
- package/src/Hooks/useCategorisedSources.ts +0 -46
- package/src/Hooks/useChildResources.spec.ts +0 -29
- package/src/Hooks/useChildResources.ts +0 -21
- package/src/Hooks/usePreselectedResourcePath.ts +0 -54
- package/src/Hooks/useRecentLocations.spec.ts +0 -81
- package/src/Hooks/useRecentLocations.ts +0 -44
- package/src/Hooks/useRecentResourcesPaths.ts +0 -54
- package/src/Hooks/useResource.spec.ts +0 -61
- package/src/Hooks/useResource.ts +0 -40
- package/src/Hooks/useResourcePath.spec.ts +0 -120
- package/src/Hooks/useResourcePath.ts +0 -76
- package/src/Icons/HistoryIcon.tsx +0 -17
- package/src/PreviewPanel/PreviewPanel.spec.tsx +0 -198
- package/src/PreviewPanel/PreviewPanel.stories.tsx +0 -76
- package/src/PreviewPanel/PreviewPanel.tsx +0 -6
- package/src/PreviewPanel/details/MatrixResource.tsx +0 -54
- package/src/PreviewPanel/details/matrix-resource.scss +0 -16
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.spec.tsx +0 -133
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.stories.tsx +0 -24
- package/src/ResourceBreadcrumb/ResourceBreadcrumb.tsx +0 -79
- package/src/ResourceBreadcrumb/resource-breadcrumb.scss +0 -28
- package/src/ResourceBreadcrumb/sample-hierarchy.json +0 -27
- package/src/ResourceList/ResourceList.spec.tsx +0 -202
- package/src/ResourceList/ResourceList.stories.tsx +0 -40
- package/src/ResourceList/ResourceList.tsx +0 -83
- package/src/ResourceList/sample-resources.json +0 -851
- package/src/ResourcePickerContainer/ResourcePickerContainer.spec.tsx +0 -780
- package/src/ResourcePickerContainer/ResourcePickerContainer.stories.tsx +0 -45
- package/src/ResourcePickerContainer/ResourcePickerContainer.tsx +0 -290
- package/src/SourceList/sample-sources.json +0 -251
- package/src/StatusIndicator/StatusIndicator.stories.tsx +0 -83
- package/src/StatusIndicator/StatusIndicator.tsx +0 -38
- package/src/__mocks__/JestHelpers.ts +0 -65
- package/src/__mocks__/StorybookHelpers.ts +0 -128
- package/src/__mocks__/jestHelpers.spec.ts +0 -38
- package/src/utils/findBestMatchLineage.spec.ts +0 -81
- package/src/utils/findBestMatchLineage.ts +0 -30
- package/src/utils/uuid.ts +0 -5
@@ -1,163 +1,36 @@
|
|
1
|
-
import React
|
2
|
-
import {
|
3
|
-
import {
|
4
|
-
import { SkeletonList, ResourceItem, ResourceState } from '@squiz/generic-browser-lib';
|
5
|
-
import clsx from 'clsx';
|
1
|
+
import React from 'react';
|
2
|
+
import { Icon, IconOptions } from '@squiz/generic-browser-lib';
|
3
|
+
import { ResourceBrowserSource } from '../types';
|
6
4
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
import { RecentResourcesPaths } from '../Hooks/useRecentResourcesPaths';
|
11
|
-
|
12
|
-
export interface SourceListProps {
|
13
|
-
sources: Source[];
|
14
|
-
selectedResource?: Resource | null;
|
15
|
-
previewModalState: OverlayTriggerState;
|
16
|
-
isLoading: boolean;
|
17
|
-
onSourceSelect: (node: ScopedSource, overlayProps: DOMAttributes<FocusableElement>) => void;
|
18
|
-
onSourceDrilldown: (source: ScopedSource) => void;
|
19
|
-
handleReload: () => void;
|
20
|
-
setSource: (source: ScopedSource | null, path?: Resource[]) => void;
|
21
|
-
recentSources: RecentResourcesPaths[];
|
22
|
-
error: Error | null;
|
5
|
+
interface SourceListProps {
|
6
|
+
sources: ResourceBrowserSource[];
|
7
|
+
onSourceSelect(source: ResourceBrowserSource): void;
|
23
8
|
}
|
24
9
|
|
25
|
-
|
26
|
-
sources,
|
27
|
-
selectedResource,
|
28
|
-
previewModalState,
|
29
|
-
isLoading,
|
30
|
-
onSourceSelect,
|
31
|
-
onSourceDrilldown,
|
32
|
-
handleReload,
|
33
|
-
setSource,
|
34
|
-
recentSources,
|
35
|
-
error,
|
36
|
-
}: SourceListProps) {
|
37
|
-
const categorisedSources = useCategorisedSources(sources);
|
38
|
-
const listRef = useRef<HTMLUListElement>(null);
|
39
|
-
const filteredRecentSources = recentSources.filter((item) => item.path?.length);
|
40
|
-
|
41
|
-
useEffect(() => {
|
42
|
-
if (listRef.current) {
|
43
|
-
listRef.current?.focus({
|
44
|
-
preventScroll: true,
|
45
|
-
});
|
46
|
-
}
|
47
|
-
}, []);
|
48
|
-
|
49
|
-
if (isLoading) {
|
10
|
+
function SourceList({ sources, onSourceSelect }: SourceListProps) {
|
50
11
|
return (
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
);
|
56
|
-
}
|
57
|
-
|
58
|
-
return (
|
59
|
-
<ul
|
60
|
-
ref={listRef}
|
61
|
-
tabIndex={-1}
|
62
|
-
aria-label={`Source list`}
|
63
|
-
className={clsx('flex flex-col bg-gray-100 min-h-full focus-visible:outline-0 px-7 py-4')}
|
64
|
-
>
|
65
|
-
{error && <ResourceState state="error" message={error.message} handleReload={handleReload} />}
|
66
|
-
|
67
|
-
{!error && filteredRecentSources.length > 0 && (
|
68
|
-
<li className={`flex flex-col text-sm font-semibold text-grey-800`}>
|
69
|
-
<div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
|
70
|
-
<span className="z-10 bg-gray-100 px-2.5 flex gap-1 items-center">
|
71
|
-
<HistoryIcon />
|
72
|
-
Recent locations
|
73
|
-
</span>
|
74
|
-
</div>
|
75
|
-
<ul aria-label={`recent location nodes`} className="flex flex-col">
|
76
|
-
{filteredRecentSources.map((item, index) => {
|
77
|
-
if (item.path) {
|
78
|
-
const lastResource = item.path[item.path.length - 1];
|
79
|
-
const [rootNode, ...path] = item.path;
|
80
|
-
return (
|
81
|
-
<ResourceItem
|
82
|
-
key={`${index}-${item.source?.id}-${lastResource?.id}`}
|
83
|
-
item={{ source: item.source, resource: lastResource }}
|
84
|
-
label={lastResource?.name || item.source?.name || ''}
|
85
|
-
type={lastResource?.type?.code || 'folder'}
|
86
|
-
previewModalState={previewModalState}
|
87
|
-
onSelect={() => {
|
88
|
-
setSource(
|
89
|
-
{
|
90
|
-
source: item.source as Source,
|
91
|
-
resource: rootNode,
|
92
|
-
},
|
93
|
-
path,
|
94
|
-
);
|
95
|
-
}}
|
96
|
-
className={clsx(
|
97
|
-
index === 0 && 'rounded-t-lg mt-3',
|
98
|
-
index === filteredRecentSources.length - 1 && 'rounded-b-lg',
|
99
|
-
)}
|
100
|
-
showChevron
|
101
|
-
/>
|
102
|
-
);
|
103
|
-
}
|
104
|
-
})}
|
105
|
-
</ul>
|
106
|
-
</li>
|
107
|
-
)}
|
108
|
-
|
109
|
-
{!error &&
|
110
|
-
categorisedSources.map(({ key, label, sources }, index) => {
|
111
|
-
return (
|
112
|
-
<li
|
113
|
-
key={key}
|
114
|
-
className={`flex flex-col text-sm font-semibold text-grey-800 ${
|
115
|
-
index > 0 || filteredRecentSources.length > 0 ? 'mt-3' : ''
|
116
|
-
}`}
|
117
|
-
>
|
118
|
-
<div className="relative flex justify-center before:w-full before:h-px before:bg-gray-300 before:absolute before:top-2/4 before:z-0">
|
119
|
-
<span className="z-10 bg-gray-100 px-2.5">{label}</span>
|
120
|
-
</div>
|
121
|
-
{sources.length > 0 && (
|
122
|
-
<ul aria-label={`${label} nodes`} className="flex flex-col">
|
123
|
-
{sources.map(({ source, resource }) => {
|
124
|
-
if (!resource || resource.childCount === 0) {
|
125
|
-
return (
|
126
|
-
<ResourceItem
|
127
|
-
key={`${source.id}-${resource?.id}`}
|
128
|
-
item={{ source, resource }}
|
129
|
-
label={resource?.name || source.name}
|
130
|
-
type={resource?.type.code || 'folder'}
|
131
|
-
previewModalState={previewModalState}
|
132
|
-
onSelect={onSourceDrilldown}
|
133
|
-
className="mt-3 rounded-lg"
|
134
|
-
showChevron
|
135
|
-
/>
|
136
|
-
);
|
137
|
-
}
|
12
|
+
<div className="overflow-y-scroll w-screen max-w-[400px] flex-1 grow-[3] border-r border-gray-300 bg-gray-100 pl-4.5 pr-4.5 pb-4.5 pt-3">
|
13
|
+
<div className="text-md font-semibold">Select an environment to use</div>
|
14
|
+
<ul tabIndex={-1} aria-label={`environment list`} className="flex flex-col bg-gray-100 min-h-full focus-visible:outline-0">
|
15
|
+
{sources.map((source, index) => {
|
138
16
|
return (
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
/>
|
17
|
+
<li key={index} className="flex items-stretch relative">
|
18
|
+
<button
|
19
|
+
onClick={() => {
|
20
|
+
onSourceSelect(source);
|
21
|
+
}}
|
22
|
+
className="w-full p-1 mt-3 bg-white border-1 border-grey-200 min-h-[64px] rounded-lg flex items-center text-md font-semibold"
|
23
|
+
>
|
24
|
+
<Icon icon={source.type as IconOptions} className="ml-4" />
|
25
|
+
<span className="line-clamp-2 text-left break-word ml-4">{source.name || source.id}</span>
|
26
|
+
<Icon icon={'arrow-right' as IconOptions} className="absolute ml-1 right-4" />
|
27
|
+
</button>
|
28
|
+
</li>
|
152
29
|
);
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
})}
|
159
|
-
</ul>
|
160
|
-
);
|
161
|
-
};
|
30
|
+
})}
|
31
|
+
</ul>
|
32
|
+
</div>
|
33
|
+
);
|
34
|
+
}
|
162
35
|
|
163
36
|
export default SourceList;
|
@@ -1,31 +1,62 @@
|
|
1
|
-
import {
|
1
|
+
import { ResourceBrowserSource, ResourceBrowserResource, ResourceBrowserPlugin } from '../types';
|
2
2
|
|
3
|
-
export
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
};
|
3
|
+
export type DeepPartial<T> = {
|
4
|
+
[P in keyof T]?: T[P] extends Array<infer U>
|
5
|
+
? Array<DeepPartial<U>>
|
6
|
+
: T[P] extends ReadonlyArray<infer U>
|
7
|
+
? ReadonlyArray<DeepPartial<U>>
|
8
|
+
: DeepPartial<T[P]>;
|
10
9
|
};
|
11
10
|
|
12
|
-
export const
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
urls: [],
|
20
|
-
childCount: 0,
|
21
|
-
lineages: [],
|
22
|
-
...properties,
|
23
|
-
};
|
11
|
+
export const mockSource = (properties: DeepPartial<ResourceBrowserSource> = {}): ResourceBrowserSource => {
|
12
|
+
return {
|
13
|
+
id: '1',
|
14
|
+
name: 'Test source',
|
15
|
+
type: 'dam',
|
16
|
+
...properties,
|
17
|
+
};
|
24
18
|
};
|
25
19
|
|
26
|
-
export const
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
export const mockPlugin = (properties: DeepPartial<ResourceBrowserPlugin> = {}): ResourceBrowserPlugin => {
|
21
|
+
return {
|
22
|
+
type: 'dam',
|
23
|
+
// @ts-ignore
|
24
|
+
sourceBrowserComponent: jest.fn(),
|
25
|
+
// @ts-ignore
|
26
|
+
renderSelectedResource: jest.fn(),
|
27
|
+
// @ts-ignore
|
28
|
+
resolveResource: jest.fn(),
|
29
|
+
...properties,
|
30
|
+
};
|
31
|
+
};
|
32
|
+
|
33
|
+
export const mockResource = (properties: Partial<ResourceBrowserResource> = {}): ResourceBrowserResource => {
|
34
|
+
return {
|
35
|
+
id: '1',
|
36
|
+
name: 'Resource 1',
|
37
|
+
url: '',
|
38
|
+
source: {
|
39
|
+
id: '1',
|
40
|
+
type: 'dam',
|
41
|
+
},
|
42
|
+
type: {
|
43
|
+
code: 'jpeg',
|
44
|
+
name: 'JPEG image',
|
45
|
+
},
|
46
|
+
squizImage: {
|
47
|
+
name: 'an-image-name',
|
48
|
+
imageVariations: {
|
49
|
+
original: {
|
50
|
+
width: 1024,
|
51
|
+
height: 900,
|
52
|
+
url: 'an-image-url',
|
53
|
+
mimeType: 'image/jpeg',
|
54
|
+
byteSize: 1068,
|
55
|
+
sha1Hash: '',
|
56
|
+
aspectRatio: '',
|
57
|
+
},
|
58
|
+
},
|
59
|
+
},
|
60
|
+
...properties,
|
61
|
+
};
|
31
62
|
};
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import {
|
3
|
+
ResourceBrowserPlugin,
|
4
|
+
ResourceBrowserUIProps,
|
5
|
+
ResourceBrowserResource,
|
6
|
+
ResourceBrowserSource,
|
7
|
+
ResourceBrowserSelectedState,
|
8
|
+
useResolveResourceResponse,
|
9
|
+
} from '../types';
|
10
|
+
|
11
|
+
// The plugin must conform to the Typescript interface ResourceBrowserPlugin
|
12
|
+
export default (): ResourceBrowserPlugin => {
|
13
|
+
return {
|
14
|
+
/**
|
15
|
+
* The type of source this plugin handles. Plugins can only handle a single ResourceBrowserSource type, export multiple
|
16
|
+
* plugins from your package with different types if you have a single codebase that can handle more than one type.
|
17
|
+
*
|
18
|
+
* This value needs to match the ResourceBrowserSource.type field that is passed to the ResourceBrowser via OnRequestSources.
|
19
|
+
*/
|
20
|
+
type: 'exampleType',
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Enables a div in the header of the ResourceBrowser modal for a plugin to insert content using React Portals.
|
24
|
+
* DOM element is passed into the Functional Component created using the sourceBrowserComponent function.
|
25
|
+
*/
|
26
|
+
headerPortal: false,
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Provide a React Functional Component for browsing the ResourceBrowserSource type; this will be rendered within the
|
30
|
+
* ResourceBrowers modal window when the source type is selected or if a pre-existing resource is being changed.
|
31
|
+
*
|
32
|
+
* The input props should be of the type ResourceBrowserUIProps so the ResourceBrowser can provide the following information:
|
33
|
+
* @param source will contain the source information provided to the ResourceBrowser.
|
34
|
+
* @param headerPortal (optional) may contain a DOM element for insertion of content using React Portals.
|
35
|
+
* @param preselectedResource (optional) may contain a resource that is being changed to start open in your UI (optional).
|
36
|
+
* @param onSelected should be called when a selection has been made and the ResourceBrowser should be closed.
|
37
|
+
*
|
38
|
+
* @returns ReactElement
|
39
|
+
*/
|
40
|
+
sourceBrowserComponent: () => {
|
41
|
+
// This should return a fucntion component, probably from another file.
|
42
|
+
return (props: ResourceBrowserUIProps) => {
|
43
|
+
return <></>;
|
44
|
+
};
|
45
|
+
},
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Provide params to assist Resource Browser in rendering a preview of the selected resource to the end user.
|
49
|
+
*
|
50
|
+
* @param resource The resource which is to be rendered to the frontend UI
|
51
|
+
*
|
52
|
+
* @returns { showThumbnail } if true will render a preview thumbnail using the SquizImage data or the resource data.
|
53
|
+
* @returns { icon } if showThumbnail evaluates to false this will be rendered to the left side of the resource label.
|
54
|
+
* @returns { label } Label for the selected resource.
|
55
|
+
* @returns { description } Description lines to provide additional information about the selected resource.
|
56
|
+
*/
|
57
|
+
renderSelectedResource: (resource: ResourceBrowserResource): Promise<ResourceBrowserSelectedState> => {
|
58
|
+
// ...Fetch information from source for resource here
|
59
|
+
return Promise.resolve({
|
60
|
+
showThumbnail: true, // If the resource is an image that can be previewed set this to true
|
61
|
+
icon: undefined, // (optionally) otherwise provide an icon for render
|
62
|
+
label: '', // Label or name of resource
|
63
|
+
description: [], // (optional) extra details on the resource e.g. size, mime type etc
|
64
|
+
});
|
65
|
+
},
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Provide a function to resolve a partial resource reference into a full data structure. Hooks may be used within.
|
69
|
+
*
|
70
|
+
* @param resourceId The id of the resource to resolve, this may be null if no resource is currently selected
|
71
|
+
* @param source The source structure of the resource to resolve, this may be null before the source retrieval has completed
|
72
|
+
*
|
73
|
+
* @returns { ResourceBrowserResource } Full data structure needed by the Resource Browser and its consumers.
|
74
|
+
*
|
75
|
+
*/
|
76
|
+
useResolveResource: (resourceId: string | null, source: ResourceBrowserSource | null): useResolveResourceResponse => {
|
77
|
+
// ...Fetch information from source for resource here
|
78
|
+
return {
|
79
|
+
data:
|
80
|
+
resourceId && source
|
81
|
+
? {
|
82
|
+
id: resourceId,
|
83
|
+
name: '',
|
84
|
+
url: '',
|
85
|
+
source: source,
|
86
|
+
type: {
|
87
|
+
code: '',
|
88
|
+
name: '',
|
89
|
+
},
|
90
|
+
squizImage: undefined,
|
91
|
+
}
|
92
|
+
: null,
|
93
|
+
error: null,
|
94
|
+
isLoading: false,
|
95
|
+
};
|
96
|
+
},
|
97
|
+
};
|
98
|
+
};
|
@@ -0,0 +1,141 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import { createPortal } from 'react-dom';
|
3
|
+
import { Icon } from '@squiz/generic-browser-lib';
|
4
|
+
|
5
|
+
import sampleSources from './sample-sources.json';
|
6
|
+
import { ResourceBrowserPlugin, ResourceBrowserResource } from '../types';
|
7
|
+
type CreateCallbacksProps = Partial<{
|
8
|
+
delay: number;
|
9
|
+
sourceIsLoading: boolean;
|
10
|
+
singleSource?: boolean;
|
11
|
+
error?: string;
|
12
|
+
}>;
|
13
|
+
|
14
|
+
export const createPlugins = (callbackWait: number, headerPortal = false): ResourceBrowserPlugin[] => {
|
15
|
+
const types = ['dam', 'matrix'];
|
16
|
+
return types.map((type): ResourceBrowserPlugin => {
|
17
|
+
return {
|
18
|
+
//@ts-ignore
|
19
|
+
type,
|
20
|
+
createHeaderPortal: headerPortal,
|
21
|
+
sourceBrowserComponent: () => {
|
22
|
+
return (props) => {
|
23
|
+
return (
|
24
|
+
<div className="h-screen lg:h-[calc(100vh-9rem)] w-screen max-w-[50rem]">
|
25
|
+
<div>THIS IS A {type} PLUGIN</div>
|
26
|
+
<button
|
27
|
+
onClick={() => {
|
28
|
+
props.onSelected({
|
29
|
+
id: '1f7a25b4-380f-4540-9555-8be2dcab4019',
|
30
|
+
source: props.source,
|
31
|
+
} as ResourceBrowserResource);
|
32
|
+
}}
|
33
|
+
>
|
34
|
+
Press to set resource
|
35
|
+
</button>
|
36
|
+
{headerPortal && props.headerPortal && createPortal(<div>HeadPortal</div>, props.headerPortal)}
|
37
|
+
</div>
|
38
|
+
);
|
39
|
+
};
|
40
|
+
},
|
41
|
+
renderSelectedResource: (resource) => {
|
42
|
+
return new Promise((resolve, reject) => {
|
43
|
+
const fileSize = resource.squizImage?.imageVariations?.original?.byteSize;
|
44
|
+
const fileWidth = resource.squizImage?.imageVariations?.original?.width;
|
45
|
+
const fileHeight = resource.squizImage?.imageVariations?.original?.height;
|
46
|
+
|
47
|
+
setTimeout(() => {
|
48
|
+
resolve({
|
49
|
+
showThumbnail: resource.squizImage ? true : false,
|
50
|
+
icon: <Icon icon={resource.type.code} resourceSource="matrix" className="w-4 h-4" />,
|
51
|
+
label: resource.name || resource.squizImage?.name || resource.id,
|
52
|
+
description: [
|
53
|
+
<dl
|
54
|
+
key="1"
|
55
|
+
className="col-start-2 col-end-2 flex flex-row gap-1 justify-self-start items-center font-normal text-sm"
|
56
|
+
>
|
57
|
+
<div>
|
58
|
+
<dt className="hidden">Asset size</dt>
|
59
|
+
<dd className="text-gray-600">
|
60
|
+
{resource.squizImage && (
|
61
|
+
<>
|
62
|
+
{fileSize} kB , {fileWidth} x {fileHeight}px
|
63
|
+
</>
|
64
|
+
)}
|
65
|
+
</dd>
|
66
|
+
</div>
|
67
|
+
</dl>,
|
68
|
+
],
|
69
|
+
});
|
70
|
+
}, callbackWait);
|
71
|
+
});
|
72
|
+
},
|
73
|
+
useResolveResource: (unresolvedResource) => {
|
74
|
+
const [resource, setResource] = useState<ResourceBrowserResource | null>(null);
|
75
|
+
const [error, setError] = useState<Error | null>(null);
|
76
|
+
const [isLoading, setIsLoading] = useState<boolean>(false);
|
77
|
+
|
78
|
+
useEffect(() => {
|
79
|
+
if (unresolvedResource !== null) {
|
80
|
+
setTimeout(() => {
|
81
|
+
setIsLoading(false);
|
82
|
+
setResource({
|
83
|
+
id: unresolvedResource.resource,
|
84
|
+
name: 'An image from Bynder',
|
85
|
+
url: 'https://cdn.mediavalet.com/aunsw/squiz/KfIjcjUJz0O6kWoInBys5A/XpBxg3Hj-0qOXFWnAAWvXQ/Large/RX-7.jpeg',
|
86
|
+
source: unresolvedResource.source,
|
87
|
+
type: {
|
88
|
+
code: 'jpeg',
|
89
|
+
name: 'JPEG image',
|
90
|
+
},
|
91
|
+
squizImage: {
|
92
|
+
name: 'An image from Bynder',
|
93
|
+
imageVariations: {
|
94
|
+
original: {
|
95
|
+
width: 1024,
|
96
|
+
height: 900,
|
97
|
+
url: 'https://cdn.mediavalet.com/aunsw/squiz/KfIjcjUJz0O6kWoInBys5A/XpBxg3Hj-0qOXFWnAAWvXQ/Large/RX-7.jpeg',
|
98
|
+
mimeType: 'image/jpeg',
|
99
|
+
byteSize: 1068,
|
100
|
+
sha1Hash: '',
|
101
|
+
aspectRatio: '',
|
102
|
+
},
|
103
|
+
},
|
104
|
+
},
|
105
|
+
});
|
106
|
+
}, callbackWait);
|
107
|
+
}
|
108
|
+
}, [unresolvedResource, setResource, setIsLoading]);
|
109
|
+
|
110
|
+
return {
|
111
|
+
data: resource,
|
112
|
+
error: error,
|
113
|
+
isLoading: isLoading,
|
114
|
+
};
|
115
|
+
},
|
116
|
+
};
|
117
|
+
});
|
118
|
+
};
|
119
|
+
|
120
|
+
export const createResourceBrowserCallbacks = ({
|
121
|
+
delay = 500,
|
122
|
+
sourceIsLoading = false,
|
123
|
+
error,
|
124
|
+
singleSource = false,
|
125
|
+
}: CreateCallbacksProps = {}) => {
|
126
|
+
return {
|
127
|
+
onRequestSources: () => {
|
128
|
+
return new Promise((resolve, reject) => {
|
129
|
+
if (!sourceIsLoading) {
|
130
|
+
setTimeout(() => {
|
131
|
+
if (error && Math.random() > 0.5) {
|
132
|
+
reject(new Error(error));
|
133
|
+
} else {
|
134
|
+
resolve(singleSource ? [sampleSources[0]] : sampleSources);
|
135
|
+
}
|
136
|
+
}, delay);
|
137
|
+
}
|
138
|
+
});
|
139
|
+
},
|
140
|
+
};
|
141
|
+
};
|
@@ -1,23 +1,19 @@
|
|
1
1
|
import React, { ReactElement, ReactNode } from 'react';
|
2
|
-
import {
|
3
|
-
ResourceBrowserContextProps,
|
4
|
-
ResourceBrowserContextProvider,
|
5
|
-
} from '../ResourceBrowserContext/ResourceBrowserContext';
|
2
|
+
import { ResourceBrowserContextProps, ResourceBrowserContextProvider } from '../ResourceBrowserContext/ResourceBrowserContext';
|
6
3
|
import { render } from '@testing-library/react';
|
7
4
|
|
8
5
|
export const renderWithContext = (ui: ReactElement, context: Partial<ResourceBrowserContextProps> = {}) => {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
)
|
22
|
-
});
|
6
|
+
return render(ui, {
|
7
|
+
wrapper: ({ children }: { children: ReactNode }): ReactElement => (
|
8
|
+
<ResourceBrowserContextProvider
|
9
|
+
value={{
|
10
|
+
onRequestSources: jest.fn(),
|
11
|
+
plugins: [],
|
12
|
+
...context,
|
13
|
+
}}
|
14
|
+
>
|
15
|
+
{children}
|
16
|
+
</ResourceBrowserContextProvider>
|
17
|
+
),
|
18
|
+
});
|
23
19
|
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"name": "Bynder #1",
|
4
|
+
"id": "c90feac1-55f3-4e1f-9b56-c22829e3f510",
|
5
|
+
"type": "dam",
|
6
|
+
"group": "DAM"
|
7
|
+
},
|
8
|
+
{
|
9
|
+
"name": "Bynder #2",
|
10
|
+
"id": "6a238a04-b5bd-44d5-940f-e235b3b352ab",
|
11
|
+
"type": "dam",
|
12
|
+
"group": "DAM"
|
13
|
+
},
|
14
|
+
{
|
15
|
+
"name": "MediaValet",
|
16
|
+
"id": "747b81ac-adce-455e-ac5a-bce7ac738115",
|
17
|
+
"type": "dam",
|
18
|
+
"group": "DAM"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"name": "Matrix site #1",
|
22
|
+
"id": "123",
|
23
|
+
"type": "matrix",
|
24
|
+
"group": "Matrix DAM UAT system"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"name": "Matrix site #2",
|
28
|
+
"id": "321",
|
29
|
+
"type": "matrix",
|
30
|
+
"group": "Matrix DAM UAT system"
|
31
|
+
}
|
32
|
+
]
|
package/src/index.scss
CHANGED
@@ -4,30 +4,40 @@
|
|
4
4
|
@import 'tailwindcss/utilities';
|
5
5
|
|
6
6
|
// Components
|
7
|
-
@import './ResourceBreadcrumb/resource-breadcrumb';
|
8
7
|
@import './ResourcePicker/resource-picker';
|
9
|
-
@import './PreviewPanel/details/matrix-resource';
|
10
8
|
@import '@squiz/generic-browser-lib/src/Spinner/spinner';
|
11
9
|
@import '@squiz/generic-browser-lib/src/Skeleton/skeleton';
|
12
10
|
|
13
11
|
*,
|
14
12
|
button {
|
15
|
-
|
16
|
-
|
13
|
+
@apply antialiased;
|
14
|
+
letter-spacing: -0.02em;
|
17
15
|
}
|
18
16
|
|
19
17
|
svg {
|
20
|
-
|
18
|
+
@apply text-gray-600;
|
21
19
|
}
|
22
20
|
|
23
21
|
.p-4\.5 {
|
24
|
-
|
22
|
+
padding: 18px;
|
23
|
+
}
|
24
|
+
|
25
|
+
.pl-4\.5 {
|
26
|
+
padding-left: 18px;
|
27
|
+
}
|
28
|
+
|
29
|
+
.pr-4\.5 {
|
30
|
+
padding-right: 18px;
|
31
|
+
}
|
32
|
+
|
33
|
+
.pb-4\.5 {
|
34
|
+
padding-bottom: 18px;
|
25
35
|
}
|
26
36
|
|
27
37
|
// In tailwind there is no break-word as it is deprecated, but break-words which is slightly different does not work here, so I have added the suggested combination here
|
28
38
|
// https://v1.tailwindcss.com/docs/word-break
|
29
39
|
// https://github.com/tailwindlabs/tailwindcss/discussions/2213
|
30
40
|
.break-word {
|
31
|
-
|
32
|
-
|
41
|
+
word-break: break-word;
|
42
|
+
overflow-wrap: anywhere;
|
33
43
|
}
|