@orangelogic/orange-dam-content-browser-sdk 1.0.1-test

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 (173) hide show
  1. package/.env +1 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc.json +82 -0
  4. package/.releaserc +17 -0
  5. package/.travis.yml +20 -0
  6. package/CBSDKdemo.html +315 -0
  7. package/GitVersion.yml +18 -0
  8. package/README.md +57 -0
  9. package/azure-pipeline.yaml +94 -0
  10. package/clientlib.config.js +36 -0
  11. package/config/env.js +105 -0
  12. package/config/getHttpsConfig.js +67 -0
  13. package/config/jest/babelTransform.js +30 -0
  14. package/config/jest/cssTransform.js +14 -0
  15. package/config/jest/fileTransform.js +41 -0
  16. package/config/modules.js +135 -0
  17. package/config/paths.js +79 -0
  18. package/config/webpack/persistentCache/createEnvironmentHash.js +10 -0
  19. package/config/webpack.config.js +762 -0
  20. package/config/webpackDevServer.config.js +128 -0
  21. package/config-overrides.js +8 -0
  22. package/gab_extension/GAB.html +85 -0
  23. package/gab_extension/GoogleChrome/manifest.json +28 -0
  24. package/gab_extension/GoogleChrome/src/assets/icon48.png +0 -0
  25. package/gab_extension/GoogleChrome/src/background/index.js +6 -0
  26. package/gab_extension/GoogleChrome/src/scripts/index.js +347 -0
  27. package/gab_extension/MozillaFirefox/manifest.json +20 -0
  28. package/gab_extension/MozillaFirefox/src/assets/icon.png +0 -0
  29. package/gab_extension/MozillaFirefox/src/background/index.js +5 -0
  30. package/gab_extension/MozillaFirefox/src/scripts/index.js +347 -0
  31. package/gab_extension/README.md +11 -0
  32. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.pbxproj +927 -0
  33. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  34. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  35. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/xcuserdata/oldevmac01.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  36. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/xcuserdata/oldevmac01.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
  37. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
  38. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +63 -0
  39. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/Contents.json +6 -0
  40. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +20 -0
  41. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Base.lproj/Main.html +23 -0
  42. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Icon.png +0 -0
  43. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Script.js +24 -0
  44. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Style.css +61 -0
  45. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/ViewController.swift +81 -0
  46. package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (Extension)/SafariWebExtensionHandler.swift +26 -0
  47. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/AppDelegate.swift +24 -0
  48. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Base.lproj/LaunchScreen.storyboard +36 -0
  49. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Base.lproj/Main.storyboard +38 -0
  50. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Info.plist +27 -0
  51. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/SceneDelegate.swift +18 -0
  52. package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (Extension)/Info.plist +13 -0
  53. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/AppDelegate.swift +21 -0
  54. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Base.lproj/Main.storyboard +125 -0
  55. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Info.plist +8 -0
  56. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Orange DAM Asset Browser Extension.entitlements +12 -0
  57. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (Extension)/Info.plist +13 -0
  58. package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (Extension)/Orange DAM Asset Browser Extension.entitlements +10 -0
  59. package/package.json +192 -0
  60. package/public/favicon.ico +0 -0
  61. package/public/index.html +92 -0
  62. package/public/logo192.png +0 -0
  63. package/public/logo512.png +0 -0
  64. package/public/manifest.json +25 -0
  65. package/public/robots.txt +3 -0
  66. package/scripts/build.js +218 -0
  67. package/scripts/start.js +154 -0
  68. package/scripts/test.js +53 -0
  69. package/src/App.tsx +98 -0
  70. package/src/AppContext.ts +18 -0
  71. package/src/GlobalConfigContext.ts +46 -0
  72. package/src/components/ArrayClamp/ArrayClamp.styled.ts +42 -0
  73. package/src/components/ArrayClamp/ArrayClamp.tsx +167 -0
  74. package/src/components/ArrayClamp/index.ts +1 -0
  75. package/src/components/Browser/Browser.styled.ts +82 -0
  76. package/src/components/Browser/Browser.tsx +284 -0
  77. package/src/components/Browser/BrowserItem.tsx +98 -0
  78. package/src/components/Browser/index.ts +1 -0
  79. package/src/components/ControlBar/ControlBar.constants.tsx +66 -0
  80. package/src/components/ControlBar/ControlBar.styled.ts +82 -0
  81. package/src/components/ControlBar/ControlBar.tsx +528 -0
  82. package/src/components/ControlBar/Facet/Facet.tsx +113 -0
  83. package/src/components/ControlBar/Facet/index.ts +1 -0
  84. package/src/components/ControlBar/index.ts +1 -0
  85. package/src/components/FormatDialog/CropPreviewer/CropPreviewer.tsx +224 -0
  86. package/src/components/FormatDialog/CropPreviewer/index.ts +3 -0
  87. package/src/components/FormatDialog/CustomRendition/CustomRendition.constants.ts +24 -0
  88. package/src/components/FormatDialog/CustomRendition/CustomRendition.styled.ts +57 -0
  89. package/src/components/FormatDialog/CustomRendition/CustomRendition.tsx +178 -0
  90. package/src/components/FormatDialog/CustomRendition/index.ts +1 -0
  91. package/src/components/FormatDialog/CustomRendition/transformations/Crop.tsx +249 -0
  92. package/src/components/FormatDialog/CustomRendition/transformations/Extension.tsx +54 -0
  93. package/src/components/FormatDialog/CustomRendition/transformations/Format.tsx +86 -0
  94. package/src/components/FormatDialog/CustomRendition/transformations/Resize.tsx +176 -0
  95. package/src/components/FormatDialog/CustomRendition/transformations/Rotate.tsx +101 -0
  96. package/src/components/FormatDialog/CustomRendition/transformations/index.ts +5 -0
  97. package/src/components/FormatDialog/FormatDialog.styled.ts +137 -0
  98. package/src/components/FormatDialog/FormatDialog.tsx +1533 -0
  99. package/src/components/FormatDialog/Previewer/Previewer.styled.ts +31 -0
  100. package/src/components/FormatDialog/Previewer/Previewer.tsx +143 -0
  101. package/src/components/FormatDialog/Previewer/index.ts +1 -0
  102. package/src/components/FormatDialog/ProxyMenu/ProxyMenu.styled.ts +88 -0
  103. package/src/components/FormatDialog/ProxyMenu/ProxyMenu.tsx +74 -0
  104. package/src/components/FormatDialog/ProxyMenu/index.ts +1 -0
  105. package/src/components/FormatDialog/TrackingParameters/TrackingParameters.tsx +59 -0
  106. package/src/components/FormatDialog/TrackingParameters/index.ts +1 -0
  107. package/src/components/FormatDialog/index.ts +1 -0
  108. package/src/components/Header/Header.styled.ts +51 -0
  109. package/src/components/Header/Header.tsx +118 -0
  110. package/src/components/Header/index.ts +1 -0
  111. package/src/components/Loader/Loader.tsx +37 -0
  112. package/src/components/Loader/index.ts +1 -0
  113. package/src/components/NoResult/NoResult.tsx +37 -0
  114. package/src/components/NoResult/index.tsx +1 -0
  115. package/src/components/Result/AssetCard/AssetCard.styled.ts +120 -0
  116. package/src/components/Result/AssetCard/AssetCard.tsx +192 -0
  117. package/src/components/Result/AssetCard/AssetCardWrapper.styled.ts +35 -0
  118. package/src/components/Result/AssetCard/AssetCardWrapper.tsx +165 -0
  119. package/src/components/Result/AssetCard/index.ts +1 -0
  120. package/src/components/Result/AssetPreview/AssetPreview.styled.ts +108 -0
  121. package/src/components/Result/AssetPreview/AssetPreview.tsx +78 -0
  122. package/src/components/Result/AssetPreview/ImagePreview/ImagePreview.tsx +42 -0
  123. package/src/components/Result/AssetPreview/ImagePreview/index.ts +1 -0
  124. package/src/components/Result/AssetPreview/OtherPreview/OtherPreview.styled.ts +23 -0
  125. package/src/components/Result/AssetPreview/OtherPreview/OtherPreview.tsx +28 -0
  126. package/src/components/Result/AssetPreview/OtherPreview/index.ts +1 -0
  127. package/src/components/Result/AssetPreview/VideoPreview/VideoPreview.tsx +132 -0
  128. package/src/components/Result/AssetPreview/VideoPreview/index.ts +1 -0
  129. package/src/components/Result/AssetPreview/index.ts +1 -0
  130. package/src/consts/asset.ts +16 -0
  131. package/src/consts/data.ts +17 -0
  132. package/src/index.tsx +305 -0
  133. package/src/page/Authenticate/Authenticate.tsx +232 -0
  134. package/src/page/Authenticate/ConnectingBackground.tsx +44 -0
  135. package/src/page/Authenticate/index.tsx +94 -0
  136. package/src/page/Home/Home.styled.ts +46 -0
  137. package/src/page/Home/Home.tsx +941 -0
  138. package/src/page/Home/index.ts +1 -0
  139. package/src/react-web-component.d.ts +4617 -0
  140. package/src/store/assets/assets.api.ts +167 -0
  141. package/src/store/assets/assets.service.ts +223 -0
  142. package/src/store/assets/assets.slice.ts +104 -0
  143. package/src/store/auth/auth.service.ts +71 -0
  144. package/src/store/auth/auth.slice.ts +295 -0
  145. package/src/store/index.ts +27 -0
  146. package/src/store/search/search.api.ts +319 -0
  147. package/src/store/search/search.slice.ts +28 -0
  148. package/src/store/user/user.api.ts +29 -0
  149. package/src/styles.css +42 -0
  150. package/src/types/assets.ts +71 -0
  151. package/src/types/auth.ts +42 -0
  152. package/src/types/common.ts +11 -0
  153. package/src/types/download.ts +8 -0
  154. package/src/types/navigation.ts +3 -0
  155. package/src/types/search.ts +116 -0
  156. package/src/types/storage.ts +1 -0
  157. package/src/types/user.ts +6 -0
  158. package/src/utils/api.ts +186 -0
  159. package/src/utils/array.ts +25 -0
  160. package/src/utils/constants.ts +12 -0
  161. package/src/utils/fetch.ts +116 -0
  162. package/src/utils/getRequestUrl.ts +15 -0
  163. package/src/utils/hooks.ts +36 -0
  164. package/src/utils/icon.ts +22 -0
  165. package/src/utils/image.ts +157 -0
  166. package/src/utils/number.ts +11 -0
  167. package/src/utils/rotate.ts +23 -0
  168. package/src/utils/storage.ts +184 -0
  169. package/src/utils/string.ts +24 -0
  170. package/src/view/AssetsPicker.tsx +24 -0
  171. package/src/web-component.d.ts +8151 -0
  172. package/tsconfig.eslint.json +10 -0
  173. package/tsconfig.json +37 -0
@@ -0,0 +1,167 @@
1
+ import React, {
2
+ FC,
3
+ isValidElement,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+
11
+ import { CxResizeObserver } from '@/web-component';
12
+ import { Container } from './ArrayClamp.styled';
13
+
14
+ type ArrayClampProps = {
15
+ children: React.ReactNode;
16
+ className?: string;
17
+ separator?: string;
18
+ tooltipSeparator?: string;
19
+ getChildString?: (index: number) => string;
20
+ };
21
+ type ArrayChildrenProps = {
22
+ children: React.ReactNode;
23
+ isClamped: (index: number) => boolean | undefined;
24
+ separator?: string;
25
+ totalVisible: number;
26
+ };
27
+
28
+ const ArrayChildren: FC<ArrayChildrenProps> = ({
29
+ children,
30
+ isClamped,
31
+ separator,
32
+ totalVisible,
33
+ }) => {
34
+ const renderLastItem = (child: React.ReactNode, index: number) => {
35
+ return (
36
+ <cx-line-clamp
37
+ class={`array-clamp__item ${isClamped(index) ? 'clamped' : ''}`}
38
+ >
39
+ {child}
40
+ </cx-line-clamp>
41
+ );
42
+ };
43
+
44
+ const renderItem = (child: React.ReactNode, index: number) => {
45
+ if (index === totalVisible - 1) {
46
+ return renderLastItem(child, index);
47
+ }
48
+
49
+ return (
50
+ <span
51
+ className={`array-clamp__item ${isClamped(index) ? 'clamped' : ''}`}
52
+ >
53
+ {child}
54
+ <span className="array-clamp__separator">{separator}</span>
55
+ </span>
56
+ );
57
+ };
58
+
59
+ return React.Children.map(children, (child, index) =>
60
+ renderItem(child, index),
61
+ );
62
+ };
63
+
64
+ const ArrayClamp: FC<ArrayClampProps> = ({ children, className, separator = ', ', tooltipSeparator, getChildString }) => {
65
+ const containerRef = useRef<HTMLDivElement>(null);
66
+ const resizeObserverRef = useRef<CxResizeObserver>(null);
67
+ const [clampedMap, setClampedMap] = useState(new Map<number, boolean>());
68
+
69
+ const totalClamped = Array.from(clampedMap.values()).filter(Boolean).length;
70
+ const totalVisible = React.Children.count(children) - totalClamped;
71
+
72
+ const hiddenChildrenStr = useMemo(() => {
73
+ return React.Children.toArray(children)
74
+ .map((child, index) => {
75
+ if (!clampedMap.get(index)) {
76
+ return '';
77
+ }
78
+ if (getChildString) {
79
+ return getChildString(index);
80
+ }
81
+ if (typeof child === 'string') {
82
+ return child;
83
+ } else if (isValidElement(child)) {
84
+ return (child.props as { children: string }).children;
85
+ } else {
86
+ return '';
87
+ }
88
+ })
89
+ .filter(Boolean)
90
+ .join(tooltipSeparator || separator);
91
+ }, [children, clampedMap, tooltipSeparator, separator, getChildString]);
92
+
93
+ useEffect(() => {
94
+ const container = containerRef.current;
95
+ const resizeObserver = resizeObserverRef.current;
96
+
97
+ if (!container || !resizeObserver) {
98
+ return;
99
+ }
100
+
101
+ const handleResize = () => {
102
+ const items = container.querySelectorAll('.array-clamp__item');
103
+ const containerWidth = container.clientWidth;
104
+ let currentWidth = 0;
105
+ let hasClamped = false;
106
+
107
+ items.forEach((item, index) => {
108
+ if (hasClamped) {
109
+ setClampedMap((prev) => new Map(prev.set(index, true)));
110
+ } else {
111
+ currentWidth += item.clientWidth;
112
+
113
+ if (currentWidth > containerWidth) {
114
+ hasClamped = true;
115
+ setClampedMap((prev) => new Map(prev.set(index, true)));
116
+ }
117
+ }
118
+ });
119
+ };
120
+
121
+ const onResize = (e: CustomEvent) => {
122
+ if (e.target !== resizeObserver) {
123
+ return;
124
+ }
125
+ handleResize();
126
+ };
127
+
128
+ resizeObserver.addEventListener('cx-resize', onResize);
129
+ handleResize();
130
+
131
+ return () => {
132
+ resizeObserver.removeEventListener('cx-resize', onResize);
133
+ };
134
+ }, [children]);
135
+
136
+ const isClamped = useCallback(
137
+ (index: number) => {
138
+ return clampedMap.get(index);
139
+ },
140
+ [clampedMap],
141
+ );
142
+
143
+ return (
144
+ <Container className={`array-clamp ${className}`}>
145
+ <cx-resize-observer ref={resizeObserverRef}>
146
+ <div ref={containerRef} className={'array-clamp__items-container'}>
147
+ <ArrayChildren
148
+ isClamped={isClamped}
149
+ separator={separator}
150
+ totalVisible={totalVisible}
151
+ >
152
+ {children}
153
+ </ArrayChildren>
154
+ </div>
155
+ {totalClamped > 0 && (
156
+ <cx-tag variant="neutral" size="small" pill class="array-clamp__indicator">
157
+ <cx-line-clamp tooltip={hiddenChildrenStr} lines={1}>
158
+ +{totalClamped}
159
+ </cx-line-clamp>
160
+ </cx-tag>
161
+ )}
162
+ </cx-resize-observer>
163
+ </Container>
164
+ );
165
+ };
166
+
167
+ export default ArrayClamp;
@@ -0,0 +1 @@
1
+ export { default } from './ArrayClamp';
@@ -0,0 +1,82 @@
1
+ import { CxDrawerProps } from '@/react-web-component';
2
+ import styled from 'styled-components';
3
+
4
+ export const Drawer = styled('cx-drawer')<CxDrawerProps>`
5
+ &::part(base) {
6
+ z-index: var(--cx-z-index-dialog);
7
+ }
8
+
9
+ &::part(body) {
10
+ padding: 0;
11
+ }
12
+
13
+ cx-input {
14
+ width: 100%;
15
+ }
16
+
17
+ cx-space {
18
+ height: 100%;
19
+ width: 100%;
20
+ }
21
+
22
+ .browser__folders {
23
+ width: 100%;
24
+ flex: 1;
25
+ overflow-y: auto;
26
+ position: relative;
27
+
28
+ cx-tree {
29
+ width: 100%;
30
+ position: absolute;
31
+ top: 0;
32
+ left: 0;
33
+ }
34
+ }
35
+
36
+ .browser__folders {
37
+ cx-tree-item {
38
+ &::part(item) {
39
+ padding: 0;
40
+ }
41
+ }
42
+
43
+ cx-skeleton {
44
+ --border-radius: var(--cx-border-radius-medium);
45
+ width: 100%;
46
+ height: 32px;
47
+ margin-bottom: var(--cx-spacing-3x-small);
48
+ }
49
+ }
50
+
51
+ .browser__collections {
52
+ border: none;
53
+ padding: none;
54
+ width: 100%;
55
+
56
+ cx-menu-item {
57
+ &::part(base) {
58
+ border-radius: var(--cx-border-radius-large);
59
+ font-size: var(--cx-font-size-medium);
60
+ padding: var(--cx-spacing-2x-small) var(--cx-spacing-small);
61
+ }
62
+ &::part(checked-icon) {
63
+ display: none;
64
+ }
65
+
66
+ &.selected::part(base) {
67
+ background-color: var(--cx-color-primary-50);
68
+ }
69
+
70
+ &.selected::part(label),
71
+ &.selected::part(prefix),
72
+ &.selected::part(suffix) {
73
+ color: var(--cx-color-primary-600);
74
+ }
75
+ }
76
+ }
77
+
78
+ .browser__collections__menu {
79
+ border: none;
80
+ max-height: 200px;
81
+ }
82
+ `;
@@ -0,0 +1,284 @@
1
+ import _debounce from 'lodash-es/debounce';
2
+ import { FC, useCallback, useEffect, useRef, useState } from 'react';
3
+
4
+ import { LIBRARY_NAME } from '@/consts/data';
5
+ import { useGetCollectionsQuery, useGetFoldersQuery } from '@/store/search/search.api';
6
+ import { RootFolder } from '@/store/search/search.slice';
7
+ import { Folder } from '@/types/search';
8
+ import { getData } from '@/utils/storage';
9
+ import {
10
+ CxChangeEvent, CxDrawer, CxInput, CxMenu, CxSelectEvent, CxSelectionChangeEvent, CxTree,
11
+ } from '@/web-component';
12
+ import { skipToken } from '@reduxjs/toolkit/query';
13
+
14
+ import { Drawer } from './Browser.styled';
15
+ import BrowserItem from './BrowserItem';
16
+
17
+ type Props = {
18
+ collectionPath?: string;
19
+ currentFolder: Folder;
20
+ focusInput?: boolean;
21
+ lastLocationMode?: boolean;
22
+ open: boolean;
23
+ showCollections?: boolean;
24
+ useSession?: string;
25
+ onFolderSelect: (selectedFolder: Folder) => void;
26
+ onClose: () => void;
27
+ };
28
+
29
+ const Browser: FC<Props> = ({
30
+ collectionPath,
31
+ currentFolder,
32
+ focusInput,
33
+ lastLocationMode,
34
+ open,
35
+ showCollections,
36
+ useSession,
37
+ onFolderSelect,
38
+ onClose,
39
+ }) => {
40
+ const [searchText, setSearchText] = useState('');
41
+ const [isDefined, setIsDefined] = useState(false);
42
+
43
+ const collectionRef = useRef<CxMenu>(null);
44
+ const drawerRef = useRef<CxDrawer>(null);
45
+ const searchRef = useRef<CxInput>(null);
46
+ const treeRef = useRef<CxTree>(null);
47
+ const firstRender = useRef(true);
48
+
49
+ useEffect(() => {
50
+ Promise.all([
51
+ customElements.whenDefined('cx-drawer'),
52
+ customElements.whenDefined('cx-input'),
53
+ customElements.whenDefined('cx-tree'),
54
+ ]).then(() => {
55
+ setIsDefined(true);
56
+ });
57
+ }, []);
58
+
59
+ useEffect(() => {
60
+ const searchInput = searchRef.current;
61
+ if (!searchInput) return;
62
+ const onSearchInput = _debounce((e: CxChangeEvent) => {
63
+ const value = (e.target as CxInput).value;
64
+ if (searchText !== value && (value.length > 2 || value.length === 0)) {
65
+ setSearchText(value);
66
+ }
67
+ }, 500);
68
+ searchInput.addEventListener('cx-input', onSearchInput);
69
+
70
+ return () => {
71
+ searchInput.removeEventListener('cx-input', onSearchInput);
72
+ };
73
+ }, [isDefined, searchText]);
74
+
75
+ useEffect(() => {
76
+ const drawer = drawerRef.current;
77
+ if (!drawer) return;
78
+ const onDrawerClose = () => {
79
+ onClose();
80
+ };
81
+ drawer.addEventListener('cx-request-close', onDrawerClose);
82
+
83
+ return () => {
84
+ drawer.removeEventListener('cx-request-close', onDrawerClose);
85
+ };
86
+ }, [isDefined, onClose]);
87
+
88
+ const {
89
+ data: folders,
90
+ isLoading: isLoadingFolders,
91
+ isFetching: isFetchingFolders,
92
+ isError: isErrorFolders,
93
+ } = useGetFoldersQuery({ folder: RootFolder, searchText, useSession });
94
+
95
+ useEffect(() => {
96
+ const handleDefaultFolder = () => {
97
+ if (!folders) return;
98
+ const libraryFolder = folders.find((folder) => folder.title === LIBRARY_NAME);
99
+
100
+ if (libraryFolder) {
101
+ onFolderSelect(libraryFolder);
102
+ } else {
103
+ onFolderSelect(RootFolder);
104
+ }
105
+ };
106
+
107
+ if (firstRender.current && folders) {
108
+ firstRender.current = false;
109
+ if (lastLocationMode) {
110
+ getData('lastLocation').then((lastLocation) => {
111
+ if (typeof lastLocation === 'string') {
112
+ try {
113
+ const folder = JSON.parse(lastLocation) as Folder;
114
+ onFolderSelect(folder);
115
+ } catch (error) {
116
+ handleDefaultFolder();
117
+ }
118
+ } else {
119
+ handleDefaultFolder();
120
+ }
121
+ }).catch(() => {
122
+ handleDefaultFolder();
123
+ });
124
+ } else {
125
+ handleDefaultFolder();
126
+ }
127
+ }
128
+ }, [folders, lastLocationMode, onFolderSelect]);
129
+
130
+ const {
131
+ data: collections,
132
+ isLoading: isLoadingCollections,
133
+ isFetching: isFetchingCollections,
134
+ isError: isErrorCollections,
135
+ } = useGetCollectionsQuery(
136
+ collectionPath
137
+ ? {
138
+ folder: collectionPath,
139
+ useSession,
140
+ }
141
+ : skipToken,
142
+ );
143
+
144
+ useEffect(() => {
145
+ const tree = treeRef.current;
146
+ if (!tree) return;
147
+ const onTreeSelect = (e: CxSelectionChangeEvent) => {
148
+ const folder = JSON.parse(
149
+ e.detail.selection[0].dataset.value ?? '{}',
150
+ ) as Folder;
151
+ onFolderSelect?.(folder);
152
+ };
153
+ tree.addEventListener('cx-selection-change', onTreeSelect);
154
+ }, [isDefined, onFolderSelect]);
155
+
156
+ useEffect(() => {
157
+ const collection = collectionRef.current;
158
+ if (!collection) return;
159
+ const onCollectionSelect = (e: CxSelectEvent) => {
160
+ const folder = JSON.parse(e.detail.item.value ?? '{}') as Folder;
161
+ onFolderSelect?.(folder);
162
+ };
163
+ collection.addEventListener('cx-select', onCollectionSelect);
164
+
165
+ return () => {
166
+ collection.removeEventListener('cx-select', onCollectionSelect);
167
+ };
168
+ }, [isDefined, collections, onFolderSelect]);
169
+
170
+ const renderFolders = useCallback(() => {
171
+ if (isLoadingFolders || isFetchingFolders) {
172
+ return Array.from({ length: 5 }).map((_, index) => (
173
+ <cx-skeleton key={index}></cx-skeleton>
174
+ ));
175
+ } else if (folders && folders.length > 0) {
176
+ return folders?.map((folder) => (
177
+ <BrowserItem
178
+ key={folder.id}
179
+ folder={folder}
180
+ currentFolderID={currentFolder.id}
181
+ searchText={searchText}
182
+ useSession={useSession}
183
+ />
184
+ ));
185
+ } else if (isErrorFolders) {
186
+ return (
187
+ <cx-typography variant="body3">Failed to load folders</cx-typography>
188
+ );
189
+ }
190
+
191
+ return <cx-typography variant="body3">No folders found</cx-typography>;
192
+ }, [
193
+ isLoadingFolders,
194
+ isFetchingFolders,
195
+ isErrorFolders,
196
+ folders,
197
+ currentFolder.id,
198
+ searchText,
199
+ useSession,
200
+ ]);
201
+
202
+ const renderCollections = useCallback(() => {
203
+ if (isLoadingCollections || isFetchingCollections) {
204
+ return Array.from({ length: 5 }).map((_, index) => (
205
+ <cx-skeleton key={index}></cx-skeleton>
206
+ ));
207
+ } else if (collections && collections.length > 0) {
208
+ return collections?.map((collection) => {
209
+ const isSelected = currentFolder.id === collection.id;
210
+
211
+ return (
212
+ <cx-menu-item
213
+ key={collection.id}
214
+ value={JSON.stringify(collection)}
215
+ className={`${isSelected ? 'selected' : ''}`}
216
+ >
217
+ <cx-icon slot="prefix" name="collections"></cx-icon>
218
+ {collection.title}
219
+ </cx-menu-item>
220
+ );
221
+ });
222
+ } else if (isErrorCollections) {
223
+ return (
224
+ <cx-typography variant="body3">
225
+ Failed to load collections
226
+ </cx-typography>
227
+ );
228
+ }
229
+
230
+ return <cx-typography variant="body3">No collections found</cx-typography>;
231
+ }, [isLoadingCollections, isFetchingCollections, collections, isErrorCollections, currentFolder.id]);
232
+
233
+ return (
234
+ <Drawer
235
+ ref={drawerRef}
236
+ label="Browser"
237
+ placement="start"
238
+ contained
239
+ open={open}
240
+ >
241
+ <cx-space direction="vertical" spacing="small" wrap="nowrap">
242
+ <cx-space
243
+ direction="vertical"
244
+ spacing="small"
245
+ style={{
246
+ padding: 'var(--body-spacing) var(--body-spacing) 0',
247
+ }}
248
+ >
249
+ <cx-typography variant="body3">Folders</cx-typography>
250
+ <cx-input
251
+ ref={searchRef}
252
+ value={searchText}
253
+ placeholder="Search..."
254
+ clearable
255
+ autoFocus={focusInput}
256
+ className='search-input'
257
+ >
258
+ <cx-icon name="search" slot="prefix" className="icon--large"></cx-icon>
259
+ </cx-input>
260
+ <div className="browser__folders">
261
+ <cx-tree ref={treeRef}>{renderFolders()}</cx-tree>
262
+ </div>
263
+ </cx-space>
264
+ {showCollections && collections && collections.length > 0 && (
265
+ <div className="browser__collections">
266
+ <cx-details>
267
+ <cx-typography slot="summary" variant="body3">
268
+ Collections
269
+ </cx-typography>
270
+ <cx-menu
271
+ ref={collectionRef}
272
+ className="browser__collections__menu"
273
+ >
274
+ {renderCollections()}
275
+ </cx-menu>
276
+ </cx-details>
277
+ </div>
278
+ )}
279
+ </cx-space>
280
+ </Drawer>
281
+ );
282
+ };
283
+
284
+ export default Browser;
@@ -0,0 +1,98 @@
1
+ import { FC, useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import { useGetFoldersQuery } from '@/store/search/search.api';
4
+ import { Folder } from '@/types/search';
5
+
6
+ import { CxCollapseEvent, CxTreeItem } from '@/web-component';
7
+
8
+ type Props = {
9
+ folder: Folder;
10
+ currentFolderID: string;
11
+ searchText?: string;
12
+ useSession?: string;
13
+ };
14
+
15
+ export const BrowserItem: FC<Props> = ({
16
+ folder,
17
+ currentFolderID,
18
+ searchText,
19
+ useSession,
20
+ }) => {
21
+ const [isDefined, setIsDefined] = useState(false);
22
+ const [isExpanded, setIsExpanded] = useState(false);
23
+ const treeItemRef = useRef<CxTreeItem>(null);
24
+ const isSelected = currentFolderID === folder.id;
25
+
26
+ const {
27
+ data: folders,
28
+ isFetching,
29
+ } = useGetFoldersQuery({ folder, searchText: '', useSession }, { skip: !isExpanded });
30
+
31
+ const highlightedTitle = useMemo(() => {
32
+ if (!searchText) return folder.title;
33
+ const searchWords = searchText.toLowerCase().split(' ').filter(Boolean);
34
+ const regex = new RegExp(`(${searchWords.join('|')})`, 'gi');
35
+ const parts = folder.title.split(regex);
36
+
37
+ return parts.map((part, index) =>
38
+ searchWords.includes(part.toLowerCase()) ? <strong key={index}>{part}</strong> : part,
39
+ );
40
+ }, [folder.title, searchText]);
41
+
42
+ useEffect(() => {
43
+ Promise.all([
44
+ customElements.whenDefined('cx-tree-item'),
45
+ ]).then(() => {
46
+ setIsDefined(true);
47
+ });
48
+ }, [isDefined]);
49
+
50
+ useEffect(() => {
51
+ const treeItem = treeItemRef.current;
52
+ if (!treeItem) return;
53
+ const onExpand = () => {
54
+ setIsExpanded(true);
55
+ };
56
+ const onCollapse = (e: CxCollapseEvent) => {
57
+ if (e.detail.target === treeItemRef.current) {
58
+ setIsExpanded(false);
59
+ }
60
+ };
61
+ treeItem.addEventListener('cx-lazy-load', onExpand);
62
+ treeItem.addEventListener('cx-expand', onExpand);
63
+ treeItem.addEventListener('cx-collapse', onCollapse);
64
+
65
+ return () => {
66
+ treeItem.removeEventListener('cx-lazy-load', onExpand);
67
+ treeItem.removeEventListener('cx-expand', onExpand);
68
+ treeItem.removeEventListener('cx-collapse', onCollapse);
69
+ };
70
+ }, [isDefined]);
71
+
72
+ // Lazy load if folder has children
73
+ // and (folders are not fetched yet or are fetching)
74
+ const isLazy = folder.hasChildren && (folders === undefined || isFetching);
75
+
76
+ return (
77
+ <cx-tree-item
78
+ ref={treeItemRef}
79
+ data-value={JSON.stringify(folder)}
80
+ expanded={isExpanded}
81
+ selected={isSelected}
82
+ lazy={isLazy}
83
+ >
84
+ <cx-icon name="folder"></cx-icon>
85
+ <cx-line-clamp lines={1}>{highlightedTitle}</cx-line-clamp>
86
+ {folders?.map((item) => (
87
+ <BrowserItem
88
+ key={item.id}
89
+ folder={item}
90
+ searchText={searchText}
91
+ currentFolderID={currentFolderID}
92
+ />
93
+ ))}
94
+ </cx-tree-item>
95
+ );
96
+ };
97
+
98
+ export default BrowserItem;
@@ -0,0 +1 @@
1
+ export { default as Browser } from './Browser';
@@ -0,0 +1,66 @@
1
+ import { GridView } from '@/types/search';
2
+
3
+ const AscendingIcon = () => (
4
+ <svg
5
+ // @ts-expect-error
6
+ slot="prefix"
7
+ width="20"
8
+ height="20"
9
+ viewBox="0 0 20 20"
10
+ fill="none"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ >
13
+ <path
14
+ d="M3.33333 4.7619L4.25984 5.68841L6.60714 3.33929V11.3095H7.61905V3.33929L9.96627 5.68841L10.8929 4.7619L6.96429 0.833333L3.33333 4.7619Z"
15
+ fill="currentColor"
16
+ />
17
+ <path
18
+ opacity="0.3"
19
+ d="M17.0833 15.2381L16.1568 14.3116L13.8095 16.6607V8.69048H12.7976V16.6607L10.4504 14.3116L9.52381 15.2381L13.4524 19.1667L17.0833 15.2381Z"
20
+ fill="currentColor"
21
+ />
22
+ </svg>
23
+ );
24
+
25
+ const DescendingIcon = () => (
26
+ <svg
27
+ // @ts-expect-error
28
+ slot="prefix"
29
+ width="20"
30
+ height="20"
31
+ viewBox="0 0 20 20"
32
+ fill="none"
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ >
35
+ <path
36
+ opacity="0.3"
37
+ d="M3.33333 4.7619L4.25984 5.68841L6.60714 3.33929V11.3095H7.61905V3.33929L9.96627 5.68841L10.8929 4.7619L6.96429 0.833333L3.33333 4.7619Z"
38
+ fill="currentColor"
39
+ />
40
+ <path
41
+ d="M17.0833 15.2381L16.1568 14.3116L13.8095 16.6607V8.69048H12.7976V16.6607L10.4504 14.3116L9.52381 15.2381L13.4524 19.1667L17.0833 15.2381Z"
42
+ fill="currentColor"
43
+ />
44
+ </svg>
45
+ );
46
+
47
+ export const sortDirections = [{
48
+ label: 'Ascending',
49
+ value: 'ascending',
50
+ icon: AscendingIcon,
51
+ }, {
52
+ label: 'Descending',
53
+ value: 'descending',
54
+ icon: DescendingIcon,
55
+ }];
56
+
57
+ export const views = [{
58
+ value: GridView.Small,
59
+ label: 'Small labeled',
60
+ }, {
61
+ value: GridView.Medium,
62
+ label: 'Medium labeled',
63
+ }, {
64
+ value: GridView.Large,
65
+ label: 'Large labeled',
66
+ }];