@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.
- package/.env +1 -0
- package/.eslintignore +2 -0
- package/.eslintrc.json +82 -0
- package/.releaserc +17 -0
- package/.travis.yml +20 -0
- package/CBSDKdemo.html +315 -0
- package/GitVersion.yml +18 -0
- package/README.md +57 -0
- package/azure-pipeline.yaml +94 -0
- package/clientlib.config.js +36 -0
- package/config/env.js +105 -0
- package/config/getHttpsConfig.js +67 -0
- package/config/jest/babelTransform.js +30 -0
- package/config/jest/cssTransform.js +14 -0
- package/config/jest/fileTransform.js +41 -0
- package/config/modules.js +135 -0
- package/config/paths.js +79 -0
- package/config/webpack/persistentCache/createEnvironmentHash.js +10 -0
- package/config/webpack.config.js +762 -0
- package/config/webpackDevServer.config.js +128 -0
- package/config-overrides.js +8 -0
- package/gab_extension/GAB.html +85 -0
- package/gab_extension/GoogleChrome/manifest.json +28 -0
- package/gab_extension/GoogleChrome/src/assets/icon48.png +0 -0
- package/gab_extension/GoogleChrome/src/background/index.js +6 -0
- package/gab_extension/GoogleChrome/src/scripts/index.js +347 -0
- package/gab_extension/MozillaFirefox/manifest.json +20 -0
- package/gab_extension/MozillaFirefox/src/assets/icon.png +0 -0
- package/gab_extension/MozillaFirefox/src/background/index.js +5 -0
- package/gab_extension/MozillaFirefox/src/scripts/index.js +347 -0
- package/gab_extension/README.md +11 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.pbxproj +927 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/project.xcworkspace/xcuserdata/oldevmac01.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Orange DAM Asset Browser Extension.xcodeproj/xcuserdata/oldevmac01.xcuserdatad/xcschemes/xcschememanagement.plist +19 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +63 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/Contents.json +6 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +20 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Base.lproj/Main.html +23 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Icon.png +0 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Script.js +24 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/Resources/Style.css +61 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (App)/ViewController.swift +81 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/Shared (Extension)/SafariWebExtensionHandler.swift +26 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/AppDelegate.swift +24 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Base.lproj/LaunchScreen.storyboard +36 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Base.lproj/Main.storyboard +38 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/Info.plist +27 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (App)/SceneDelegate.swift +18 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/iOS (Extension)/Info.plist +13 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/AppDelegate.swift +21 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Base.lproj/Main.storyboard +125 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Info.plist +8 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (App)/Orange DAM Asset Browser Extension.entitlements +12 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (Extension)/Info.plist +13 -0
- package/gab_extension/Safari/Orange DAM Asset Browser Extension/macOS (Extension)/Orange DAM Asset Browser Extension.entitlements +10 -0
- package/package.json +192 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +92 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/scripts/build.js +218 -0
- package/scripts/start.js +154 -0
- package/scripts/test.js +53 -0
- package/src/App.tsx +98 -0
- package/src/AppContext.ts +18 -0
- package/src/GlobalConfigContext.ts +46 -0
- package/src/components/ArrayClamp/ArrayClamp.styled.ts +42 -0
- package/src/components/ArrayClamp/ArrayClamp.tsx +167 -0
- package/src/components/ArrayClamp/index.ts +1 -0
- package/src/components/Browser/Browser.styled.ts +82 -0
- package/src/components/Browser/Browser.tsx +284 -0
- package/src/components/Browser/BrowserItem.tsx +98 -0
- package/src/components/Browser/index.ts +1 -0
- package/src/components/ControlBar/ControlBar.constants.tsx +66 -0
- package/src/components/ControlBar/ControlBar.styled.ts +82 -0
- package/src/components/ControlBar/ControlBar.tsx +528 -0
- package/src/components/ControlBar/Facet/Facet.tsx +113 -0
- package/src/components/ControlBar/Facet/index.ts +1 -0
- package/src/components/ControlBar/index.ts +1 -0
- package/src/components/FormatDialog/CropPreviewer/CropPreviewer.tsx +224 -0
- package/src/components/FormatDialog/CropPreviewer/index.ts +3 -0
- package/src/components/FormatDialog/CustomRendition/CustomRendition.constants.ts +24 -0
- package/src/components/FormatDialog/CustomRendition/CustomRendition.styled.ts +57 -0
- package/src/components/FormatDialog/CustomRendition/CustomRendition.tsx +178 -0
- package/src/components/FormatDialog/CustomRendition/index.ts +1 -0
- package/src/components/FormatDialog/CustomRendition/transformations/Crop.tsx +249 -0
- package/src/components/FormatDialog/CustomRendition/transformations/Extension.tsx +54 -0
- package/src/components/FormatDialog/CustomRendition/transformations/Format.tsx +86 -0
- package/src/components/FormatDialog/CustomRendition/transformations/Resize.tsx +176 -0
- package/src/components/FormatDialog/CustomRendition/transformations/Rotate.tsx +101 -0
- package/src/components/FormatDialog/CustomRendition/transformations/index.ts +5 -0
- package/src/components/FormatDialog/FormatDialog.styled.ts +137 -0
- package/src/components/FormatDialog/FormatDialog.tsx +1533 -0
- package/src/components/FormatDialog/Previewer/Previewer.styled.ts +31 -0
- package/src/components/FormatDialog/Previewer/Previewer.tsx +143 -0
- package/src/components/FormatDialog/Previewer/index.ts +1 -0
- package/src/components/FormatDialog/ProxyMenu/ProxyMenu.styled.ts +88 -0
- package/src/components/FormatDialog/ProxyMenu/ProxyMenu.tsx +74 -0
- package/src/components/FormatDialog/ProxyMenu/index.ts +1 -0
- package/src/components/FormatDialog/TrackingParameters/TrackingParameters.tsx +59 -0
- package/src/components/FormatDialog/TrackingParameters/index.ts +1 -0
- package/src/components/FormatDialog/index.ts +1 -0
- package/src/components/Header/Header.styled.ts +51 -0
- package/src/components/Header/Header.tsx +118 -0
- package/src/components/Header/index.ts +1 -0
- package/src/components/Loader/Loader.tsx +37 -0
- package/src/components/Loader/index.ts +1 -0
- package/src/components/NoResult/NoResult.tsx +37 -0
- package/src/components/NoResult/index.tsx +1 -0
- package/src/components/Result/AssetCard/AssetCard.styled.ts +120 -0
- package/src/components/Result/AssetCard/AssetCard.tsx +192 -0
- package/src/components/Result/AssetCard/AssetCardWrapper.styled.ts +35 -0
- package/src/components/Result/AssetCard/AssetCardWrapper.tsx +165 -0
- package/src/components/Result/AssetCard/index.ts +1 -0
- package/src/components/Result/AssetPreview/AssetPreview.styled.ts +108 -0
- package/src/components/Result/AssetPreview/AssetPreview.tsx +78 -0
- package/src/components/Result/AssetPreview/ImagePreview/ImagePreview.tsx +42 -0
- package/src/components/Result/AssetPreview/ImagePreview/index.ts +1 -0
- package/src/components/Result/AssetPreview/OtherPreview/OtherPreview.styled.ts +23 -0
- package/src/components/Result/AssetPreview/OtherPreview/OtherPreview.tsx +28 -0
- package/src/components/Result/AssetPreview/OtherPreview/index.ts +1 -0
- package/src/components/Result/AssetPreview/VideoPreview/VideoPreview.tsx +132 -0
- package/src/components/Result/AssetPreview/VideoPreview/index.ts +1 -0
- package/src/components/Result/AssetPreview/index.ts +1 -0
- package/src/consts/asset.ts +16 -0
- package/src/consts/data.ts +17 -0
- package/src/index.tsx +305 -0
- package/src/page/Authenticate/Authenticate.tsx +232 -0
- package/src/page/Authenticate/ConnectingBackground.tsx +44 -0
- package/src/page/Authenticate/index.tsx +94 -0
- package/src/page/Home/Home.styled.ts +46 -0
- package/src/page/Home/Home.tsx +941 -0
- package/src/page/Home/index.ts +1 -0
- package/src/react-web-component.d.ts +4617 -0
- package/src/store/assets/assets.api.ts +167 -0
- package/src/store/assets/assets.service.ts +223 -0
- package/src/store/assets/assets.slice.ts +104 -0
- package/src/store/auth/auth.service.ts +71 -0
- package/src/store/auth/auth.slice.ts +295 -0
- package/src/store/index.ts +27 -0
- package/src/store/search/search.api.ts +319 -0
- package/src/store/search/search.slice.ts +28 -0
- package/src/store/user/user.api.ts +29 -0
- package/src/styles.css +42 -0
- package/src/types/assets.ts +71 -0
- package/src/types/auth.ts +42 -0
- package/src/types/common.ts +11 -0
- package/src/types/download.ts +8 -0
- package/src/types/navigation.ts +3 -0
- package/src/types/search.ts +116 -0
- package/src/types/storage.ts +1 -0
- package/src/types/user.ts +6 -0
- package/src/utils/api.ts +186 -0
- package/src/utils/array.ts +25 -0
- package/src/utils/constants.ts +12 -0
- package/src/utils/fetch.ts +116 -0
- package/src/utils/getRequestUrl.ts +15 -0
- package/src/utils/hooks.ts +36 -0
- package/src/utils/icon.ts +22 -0
- package/src/utils/image.ts +157 -0
- package/src/utils/number.ts +11 -0
- package/src/utils/rotate.ts +23 -0
- package/src/utils/storage.ts +184 -0
- package/src/utils/string.ts +24 -0
- package/src/view/AssetsPicker.tsx +24 -0
- package/src/web-component.d.ts +8151 -0
- package/tsconfig.eslint.json +10 -0
- 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
|
+
}];
|