Merge pull request #1492 from ONLYOFFICE/feature/move-to-selector
Feature/files selector
This commit is contained in:
commit
aaacece34f
@ -1,8 +1,8 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-typescript"
|
||||
"@babel/preset-typescript",
|
||||
["@babel/preset-react", { "runtime": "automatic" }]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-runtime",
|
||||
|
8
packages/client/global.d.ts
vendored
Normal file
8
packages/client/global.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
declare module "*.svg" {
|
||||
const SVG: React.VFC<React.SVGProps<SVGSVGElement>>;
|
||||
export default SVG;
|
||||
}
|
||||
declare module "*.svg?url" {
|
||||
const SVGUrl: string;
|
||||
export default SVGUrl;
|
||||
}
|
@ -95,6 +95,7 @@
|
||||
"RoomsRemoved": "Rooms removed",
|
||||
"RoomUnpinned": "Room unpinned",
|
||||
"SearchByContent": "Search by file contents",
|
||||
"SelectorEmptyScreenHeader": "No files and folders here yet",
|
||||
"SendByEmail": "Send by email",
|
||||
"Share": "Share",
|
||||
"ShowVersionHistory": "Show version history",
|
||||
|
@ -4,11 +4,9 @@ import { inject, observer } from "mobx-react";
|
||||
import {
|
||||
SharingPanel,
|
||||
UploadPanel,
|
||||
OperationsPanel,
|
||||
VersionHistoryPanel,
|
||||
ChangeOwnerPanel,
|
||||
NewFilesPanel,
|
||||
SelectFileDialog,
|
||||
HotkeyPanel,
|
||||
InvitePanel,
|
||||
} from "../panels";
|
||||
@ -29,6 +27,8 @@ import ConvertPasswordDialog from "../dialogs/ConvertPasswordDialog";
|
||||
import ArchiveDialog from "../dialogs/ArchiveDialog";
|
||||
import RestoreRoomDialog from "../dialogs/RestoreRoomDialog";
|
||||
import PreparationPortalDialog from "../dialogs/PreparationPortalDialog";
|
||||
import FilesSelector from "../FilesSelector";
|
||||
import { FilesSelectorFilterTypes } from "@docspace/common/constants";
|
||||
|
||||
const Panels = (props) => {
|
||||
const {
|
||||
@ -80,10 +80,11 @@ const Panels = (props) => {
|
||||
),
|
||||
ownerPanelVisible && <ChangeOwnerPanel key="change-owner-panel" />,
|
||||
(moveToPanelVisible || copyPanelVisible || restoreAllPanelVisible) && (
|
||||
<OperationsPanel
|
||||
key="operation-panel"
|
||||
<FilesSelector
|
||||
key="files-selector"
|
||||
isMove={moveToPanelVisible}
|
||||
isCopy={copyPanelVisible}
|
||||
isRestore={restoreAllPanelVisible}
|
||||
isRestoreAll={restoreAllPanelVisible}
|
||||
/>
|
||||
),
|
||||
connectDialogVisible && <ConnectDialog key="connect-dialog" />,
|
||||
@ -110,21 +111,15 @@ const Panels = (props) => {
|
||||
<CreateRoomConfirmDialog key="create-room-confirm-dialog" />
|
||||
),
|
||||
selectFileDialogVisible && (
|
||||
<SelectFileDialog
|
||||
<FilesSelector
|
||||
key="select-file-dialog"
|
||||
//resetTreeFolders
|
||||
onSelectFile={createMasterForm}
|
||||
filterParam={FilesSelectorFilterTypes.DOCX}
|
||||
isPanelVisible={selectFileDialogVisible}
|
||||
onSelectFile={createMasterForm}
|
||||
onClose={onClose}
|
||||
filteredType="exceptPrivacyTrashArchiveFolders"
|
||||
ByExtension
|
||||
searchParam={".docx"}
|
||||
dialogName={t("Translations:CreateMasterFormFromFile")}
|
||||
filesListTitle={t("Common:SelectDOCXFormat")}
|
||||
creationButtonPrimary
|
||||
withSubfolders={false}
|
||||
/>
|
||||
),
|
||||
|
||||
hotkeyPanelVisible && <HotkeyPanel key="hotkey-panel" />,
|
||||
invitePanelVisible && <InvitePanel key="invite-panel" />,
|
||||
convertPasswordDialogVisible && (
|
||||
|
@ -0,0 +1,152 @@
|
||||
export type Security = {
|
||||
Copy: boolean;
|
||||
CopyTo: boolean;
|
||||
Create: boolean;
|
||||
Delete: boolean;
|
||||
Duplicate: boolean;
|
||||
EditAccess: boolean;
|
||||
EditRoom: boolean;
|
||||
Move: boolean;
|
||||
MoveTo: boolean;
|
||||
Mute: boolean;
|
||||
Pin: boolean;
|
||||
Read: boolean;
|
||||
Rename: boolean;
|
||||
};
|
||||
|
||||
export type Item = {
|
||||
id: number | string;
|
||||
parentId: number | string;
|
||||
rootFolderType: number | string;
|
||||
title: string;
|
||||
label: string;
|
||||
filesCount?: number;
|
||||
foldersCount?: number;
|
||||
avatar?: string;
|
||||
icon?: string;
|
||||
isFolder: boolean;
|
||||
isDisabled?: boolean;
|
||||
security: Security;
|
||||
};
|
||||
|
||||
export type BreadCrumb = {
|
||||
label: string;
|
||||
id: number | string;
|
||||
isRoom: boolean;
|
||||
};
|
||||
|
||||
type setItems = (value: Item[] | null) => Item[];
|
||||
|
||||
export type useLoadersHelperProps = {
|
||||
items: Item[] | null;
|
||||
};
|
||||
|
||||
export type useRootHelperProps = {
|
||||
setBreadCrumbs: (items: BreadCrumb[]) => void;
|
||||
setIsBreadCrumbsLoading: (value: boolean) => void;
|
||||
setTotal: (value: number) => void;
|
||||
setItems: (items: Item[] | setItems) => void;
|
||||
treeFolders?: Item[];
|
||||
setIsNextPageLoading: (value: boolean) => void;
|
||||
setHasNextPage: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export type useRoomsHelperProps = {
|
||||
setBreadCrumbs: (items: BreadCrumb[]) => void;
|
||||
setIsBreadCrumbsLoading: (value: boolean) => void;
|
||||
setIsNextPageLoading: (value: boolean) => void;
|
||||
setHasNextPage: (value: boolean) => void;
|
||||
setTotal: (value: number) => void;
|
||||
setItems: (items: Item[] | setItems) => void;
|
||||
isFirstLoad: boolean;
|
||||
setIsRoot: (value: boolean) => void;
|
||||
searchValue?: string;
|
||||
};
|
||||
|
||||
export type useFilesHelpersProps = {
|
||||
setBreadCrumbs: (items: BreadCrumb[]) => void;
|
||||
setIsBreadCrumbsLoading: (value: boolean) => void;
|
||||
setIsNextPageLoading: (value: boolean) => void;
|
||||
setHasNextPage: (value: boolean) => void;
|
||||
setTotal: (value: number) => void;
|
||||
setItems: (items: Item[] | setItems) => void;
|
||||
isFirstLoad: boolean;
|
||||
selectedItemId: string | number | undefined;
|
||||
setIsRoot: (value: boolean) => void;
|
||||
searchValue?: string;
|
||||
disabledItems: string[] | number[];
|
||||
setSelectedItemSecurity: (value: Security) => void;
|
||||
isThirdParty: boolean;
|
||||
onSelectTreeNode?: (treeNode: any) => void;
|
||||
setSelectedTreeNode: (treeNode: any) => void;
|
||||
filterParam?: string;
|
||||
};
|
||||
|
||||
export type FilesSelectorProps = {
|
||||
isPanelVisible: boolean;
|
||||
withoutBasicSelection: boolean;
|
||||
withoutImmediatelyClose: boolean;
|
||||
isThirdParty: boolean;
|
||||
isEditorDialog: boolean;
|
||||
|
||||
onClose?: () => void;
|
||||
|
||||
isMove?: boolean;
|
||||
isCopy?: boolean;
|
||||
isRestoreAll?: boolean;
|
||||
|
||||
filterParam?: string;
|
||||
|
||||
currentFolderId?: number;
|
||||
fromFolderId?: number;
|
||||
parentId?: number;
|
||||
rootFolderType?: number;
|
||||
|
||||
treeFolders?: Item[];
|
||||
|
||||
theme: any;
|
||||
|
||||
selection: any[];
|
||||
disabledItems: string[] | number[];
|
||||
isFolderActions?: boolean;
|
||||
setMoveToPanelVisible: (value: boolean) => void;
|
||||
setCopyPanelVisible: (value: boolean) => void;
|
||||
setRestoreAllPanelVisible: (value: boolean) => void;
|
||||
setIsFolderActions: (value: boolean) => void;
|
||||
setMovingInProgress: (value: boolean) => void;
|
||||
setConflictDialogData: (conflicts: any, operationData: any) => void;
|
||||
itemOperationToFolder: (operationData: any) => Promise<void>;
|
||||
clearActiveOperations: (
|
||||
folderIds: string[] | number[],
|
||||
fileIds: string[] | number[]
|
||||
) => void;
|
||||
checkFileConflicts: (
|
||||
selectedItemId: string | number | undefined,
|
||||
folderIds: string[] | number[],
|
||||
fileIds: string[] | number[]
|
||||
) => Promise<any>;
|
||||
|
||||
onSetBaseFolderPath?: (value: number | string | undefined) => void;
|
||||
onSetNewFolderPath?: (value: number | string | undefined) => void;
|
||||
onSelectFolder?: (value: number | string | undefined) => void;
|
||||
onSelectTreeNode?: (treeNode: any) => void;
|
||||
onSave?: (
|
||||
e: any,
|
||||
folderId: string | number,
|
||||
fileTitle: string,
|
||||
openNewTab: boolean
|
||||
) => void;
|
||||
onSelectFile?: (fileInfo: {
|
||||
id: string | number;
|
||||
title: string;
|
||||
path?: string[];
|
||||
}) => void;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
footerInputHeader?: string;
|
||||
currentFooterInputValue?: string;
|
||||
footerCheckboxLabel?: string;
|
||||
|
||||
descriptionText?: string;
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider, inject, observer } from "mobx-react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
// @ts-ignore
|
||||
import store from "client/store";
|
||||
import FilesSelector from "./";
|
||||
import i18n from "./i18n";
|
||||
import { FilesSelectorProps } from "./FilesSelector.types";
|
||||
const { auth: authStore } = store;
|
||||
|
||||
const FilesSelectorWrapper = (props: FilesSelectorProps) => {
|
||||
React.useEffect(() => {
|
||||
authStore.init(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MobxProvider {...store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<FilesSelector {...props} />
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilesSelectorWrapper;
|
@ -0,0 +1,377 @@
|
||||
import React from "react";
|
||||
|
||||
// @ts-ignore
|
||||
import { getFolder, getFolderInfo } from "@docspace/common/api/files";
|
||||
// @ts-ignore
|
||||
import FilesFilter from "@docspace/common/api/files/filter";
|
||||
// @ts-ignore
|
||||
import { iconSize32 } from "@docspace/common/utils/image-helpers";
|
||||
|
||||
import { PAGE_COUNT, defaultBreadCrumb } from "../utils";
|
||||
|
||||
import {
|
||||
useFilesHelpersProps,
|
||||
Item,
|
||||
BreadCrumb,
|
||||
Security,
|
||||
} from "../FilesSelector.types";
|
||||
import {
|
||||
ApplyFilterOption,
|
||||
FilesSelectorFilterTypes,
|
||||
FilterType,
|
||||
FolderType,
|
||||
} from "@docspace/common/constants";
|
||||
|
||||
const getIconUrl = (extension: string, isImage: boolean, isMedia: boolean) => {
|
||||
// if (extension !== iconPath) return iconSize32.get(iconPath);
|
||||
let path = "";
|
||||
|
||||
switch (extension) {
|
||||
case ".avi":
|
||||
path = "avi.svg";
|
||||
break;
|
||||
case ".csv":
|
||||
path = "csv.svg";
|
||||
break;
|
||||
case ".djvu":
|
||||
path = "djvu.svg";
|
||||
break;
|
||||
case ".doc":
|
||||
path = "doc.svg";
|
||||
break;
|
||||
case ".docm":
|
||||
path = "docm.svg";
|
||||
break;
|
||||
case ".docx":
|
||||
path = "docx.svg";
|
||||
break;
|
||||
case ".dotx":
|
||||
path = "dotx.svg";
|
||||
break;
|
||||
case ".dvd":
|
||||
path = "dvd.svg";
|
||||
break;
|
||||
case ".epub":
|
||||
path = "epub.svg";
|
||||
break;
|
||||
case ".pb2":
|
||||
case ".fb2":
|
||||
path = "fb2.svg";
|
||||
break;
|
||||
case ".flv":
|
||||
path = "flv.svg";
|
||||
break;
|
||||
case ".fodt":
|
||||
path = "fodt.svg";
|
||||
break;
|
||||
case ".iaf":
|
||||
path = "iaf.svg";
|
||||
break;
|
||||
case ".ics":
|
||||
path = "ics.svg";
|
||||
break;
|
||||
case ".m2ts":
|
||||
path = "m2ts.svg";
|
||||
break;
|
||||
case ".mht":
|
||||
path = "mht.svg";
|
||||
break;
|
||||
case ".mkv":
|
||||
path = "mkv.svg";
|
||||
break;
|
||||
case ".mov":
|
||||
path = "mov.svg";
|
||||
break;
|
||||
case ".mp4":
|
||||
path = "mp4.svg";
|
||||
break;
|
||||
case ".mpg":
|
||||
path = "mpg.svg";
|
||||
break;
|
||||
case ".odp":
|
||||
path = "odp.svg";
|
||||
break;
|
||||
case ".ods":
|
||||
path = "ods.svg";
|
||||
break;
|
||||
case ".odt":
|
||||
path = "odt.svg";
|
||||
break;
|
||||
case ".otp":
|
||||
path = "otp.svg";
|
||||
break;
|
||||
case ".ots":
|
||||
path = "ots.svg";
|
||||
break;
|
||||
case ".ott":
|
||||
path = "ott.svg";
|
||||
break;
|
||||
case ".pdf":
|
||||
path = "pdf.svg";
|
||||
break;
|
||||
case ".pot":
|
||||
path = "pot.svg";
|
||||
break;
|
||||
case ".pps":
|
||||
path = "pps.svg";
|
||||
break;
|
||||
case ".ppsx":
|
||||
path = "ppsx.svg";
|
||||
break;
|
||||
case ".ppt":
|
||||
path = "ppt.svg";
|
||||
break;
|
||||
case ".pptm":
|
||||
path = "pptm.svg";
|
||||
break;
|
||||
case ".pptx":
|
||||
path = "pptx.svg";
|
||||
break;
|
||||
case ".rtf":
|
||||
path = "rtf.svg";
|
||||
break;
|
||||
case ".svg":
|
||||
path = "svg.svg";
|
||||
break;
|
||||
case ".txt":
|
||||
path = "txt.svg";
|
||||
break;
|
||||
case ".webm":
|
||||
path = "webm.svg";
|
||||
break;
|
||||
case ".xls":
|
||||
path = "xls.svg";
|
||||
break;
|
||||
case ".xlsm":
|
||||
path = "xlsm.svg";
|
||||
break;
|
||||
case ".xlsx":
|
||||
path = "xlsx.svg";
|
||||
break;
|
||||
case ".xps":
|
||||
path = "xps.svg";
|
||||
break;
|
||||
case ".xml":
|
||||
path = "xml.svg";
|
||||
break;
|
||||
case ".oform":
|
||||
path = "oform.svg";
|
||||
break;
|
||||
case ".docxf":
|
||||
path = "docxf.svg";
|
||||
break;
|
||||
default:
|
||||
path = "file.svg";
|
||||
break;
|
||||
}
|
||||
|
||||
if (isMedia) path = "sound.svg";
|
||||
if (isImage) path = "image.svg";
|
||||
|
||||
return iconSize32.get(path);
|
||||
};
|
||||
|
||||
const convertFoldersToItems = (
|
||||
folders: any,
|
||||
disabledItems: any[],
|
||||
filterParam?: string
|
||||
) => {
|
||||
const items = folders.map((room: any) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
security,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
}: {
|
||||
id: number;
|
||||
title: string;
|
||||
filesCount: number;
|
||||
foldersCount: number;
|
||||
security: Security;
|
||||
parentId: number;
|
||||
rootFolderType: number;
|
||||
} = room;
|
||||
|
||||
const icon = iconSize32.get("folder.svg");
|
||||
|
||||
return {
|
||||
id,
|
||||
label: title,
|
||||
title,
|
||||
icon,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
security,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
isFolder: true,
|
||||
isDisabled: !!filterParam ? false : disabledItems.includes(id),
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const convertFilesToItems = (files: any, filterParam?: string) => {
|
||||
const items = files.map((file: any) => {
|
||||
const { id, title, security, parentId, rootFolderType, fileExst } = file;
|
||||
|
||||
const isImage = file.viewAccessability.ImageView;
|
||||
const isMedia = file.viewAccessability.MediaView;
|
||||
|
||||
let icon = getIconUrl(fileExst, isImage, isMedia);
|
||||
|
||||
// if(filterParam)
|
||||
|
||||
return {
|
||||
id,
|
||||
label: title.replace(fileExst, ""),
|
||||
title,
|
||||
icon,
|
||||
|
||||
security,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
isFolder: false,
|
||||
isDisabled: !filterParam,
|
||||
};
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
export const useFilesHelper = ({
|
||||
setIsNextPageLoading,
|
||||
setHasNextPage,
|
||||
setTotal,
|
||||
setItems,
|
||||
setBreadCrumbs,
|
||||
setIsBreadCrumbsLoading,
|
||||
isFirstLoad,
|
||||
selectedItemId,
|
||||
setIsRoot,
|
||||
searchValue,
|
||||
disabledItems,
|
||||
setSelectedItemSecurity,
|
||||
isThirdParty,
|
||||
onSelectTreeNode,
|
||||
setSelectedTreeNode,
|
||||
filterParam,
|
||||
}: useFilesHelpersProps) => {
|
||||
const getFileList = React.useCallback(
|
||||
async (
|
||||
startIndex: number,
|
||||
itemId: number | string | undefined,
|
||||
isInit?: boolean,
|
||||
search?: string | null
|
||||
) => {
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
const currentSearch = search
|
||||
? search
|
||||
: search === null
|
||||
? ""
|
||||
: searchValue || "";
|
||||
|
||||
const page = startIndex / PAGE_COUNT;
|
||||
|
||||
const filter = FilesFilter.getDefault();
|
||||
|
||||
filter.page = page;
|
||||
filter.pageCount = PAGE_COUNT;
|
||||
filter.search = currentSearch;
|
||||
filter.applyFilterOption = null;
|
||||
filter.withSubfolders = false;
|
||||
if (filterParam) {
|
||||
filter.applyFilterOption = ApplyFilterOption.Files;
|
||||
switch (filterParam) {
|
||||
case FilesSelectorFilterTypes.DOCX:
|
||||
filter.filterType = FilterType.DocumentsOnly;
|
||||
break;
|
||||
|
||||
case FilesSelectorFilterTypes.IMG:
|
||||
filter.filterType = FilterType.ImagesOnly;
|
||||
break;
|
||||
|
||||
case FilesSelectorFilterTypes.GZ:
|
||||
filter.filterType = FilterType.ArchiveOnly;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const id = itemId ? itemId : selectedItemId || "";
|
||||
|
||||
filter.folder = id.toString();
|
||||
|
||||
const currentFolder = await getFolder(id, filter);
|
||||
|
||||
const { folders, files, total, count, pathParts, current } =
|
||||
currentFolder;
|
||||
|
||||
setSelectedItemSecurity(current.security);
|
||||
|
||||
const foldersList: Item[] = convertFoldersToItems(
|
||||
folders,
|
||||
disabledItems,
|
||||
filterParam
|
||||
);
|
||||
|
||||
const filesList: Item[] = convertFilesToItems(files, filterParam);
|
||||
|
||||
const itemList = [...foldersList, ...filesList];
|
||||
|
||||
setHasNextPage(count === PAGE_COUNT);
|
||||
|
||||
onSelectTreeNode && setSelectedTreeNode({ ...current, path: pathParts });
|
||||
|
||||
if (isInit) {
|
||||
if (isThirdParty) {
|
||||
const breadCrumbs: BreadCrumb[] = [
|
||||
{ label: current.title, isRoom: false, id: current.id },
|
||||
];
|
||||
|
||||
setBreadCrumbs(breadCrumbs);
|
||||
setIsBreadCrumbsLoading(false);
|
||||
} else {
|
||||
const breadCrumbs: BreadCrumb[] = await Promise.all(
|
||||
pathParts.map(async (folderId: number | string) => {
|
||||
const folderInfo: any = await getFolderInfo(folderId);
|
||||
|
||||
const { title, id, parentId, rootFolderType } = folderInfo;
|
||||
|
||||
return {
|
||||
label: title,
|
||||
id: id,
|
||||
isRoom: parentId === 0 && rootFolderType === FolderType.Rooms,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
breadCrumbs.unshift({ ...defaultBreadCrumb });
|
||||
|
||||
setBreadCrumbs(breadCrumbs);
|
||||
setIsBreadCrumbsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirstLoad || startIndex === 0) {
|
||||
setTotal(total);
|
||||
setItems(itemList);
|
||||
} else {
|
||||
setItems((prevState: Item[] | null) => {
|
||||
if (prevState) return [...prevState, ...itemList];
|
||||
return [...itemList];
|
||||
});
|
||||
}
|
||||
setIsRoot(false);
|
||||
setIsNextPageLoading(false);
|
||||
},
|
||||
[selectedItemId, searchValue, isFirstLoad, disabledItems]
|
||||
);
|
||||
|
||||
return { getFileList };
|
||||
};
|
||||
|
||||
export default useFilesHelper;
|
@ -0,0 +1,124 @@
|
||||
import React from "react";
|
||||
|
||||
import { useLoadersHelperProps } from "../FilesSelector.types";
|
||||
import { MIN_LOADER_TIMER, SHOW_LOADER_TIMER } from "../utils";
|
||||
|
||||
const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
|
||||
const [isBreadCrumbsLoading, setIsBreadCrumbsLoading] =
|
||||
React.useState<boolean>(true);
|
||||
const [isNextPageLoading, setIsNextPageLoading] =
|
||||
React.useState<boolean>(false);
|
||||
const [isFirstLoad, setIsFirstLoad] = React.useState<boolean>(true);
|
||||
|
||||
const [showBreadCrumbsLoader, setShowBreadCrumbsLoader] =
|
||||
React.useState<boolean>(true);
|
||||
const [showLoader, setShowLoader] = React.useState<boolean>(true);
|
||||
|
||||
const loaderTimeout = React.useRef<NodeJS.Timeout | null>(null);
|
||||
const startLoader = React.useRef<Date | null>(new Date());
|
||||
|
||||
const breadCrumbsLoaderTimeout = React.useRef<NodeJS.Timeout | null>(null);
|
||||
const breadCrumbsStartLoader = React.useRef<Date | null>(new Date());
|
||||
|
||||
const isMount = React.useRef<boolean>(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
isMount.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const calculateLoader = React.useCallback(() => {
|
||||
if (isFirstLoad) {
|
||||
setShowLoader(true);
|
||||
|
||||
startLoader.current = new Date();
|
||||
} else {
|
||||
if (startLoader.current) {
|
||||
const currentDate = new Date();
|
||||
|
||||
const ms = Math.abs(
|
||||
startLoader.current.getTime() - currentDate.getTime()
|
||||
);
|
||||
|
||||
if (ms >= MIN_LOADER_TIMER) {
|
||||
startLoader.current = null;
|
||||
return setShowLoader(false);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (isMount.current) {
|
||||
startLoader.current = null;
|
||||
setShowLoader(false);
|
||||
}
|
||||
}, MIN_LOADER_TIMER - ms);
|
||||
}
|
||||
}
|
||||
}, [isFirstLoad]);
|
||||
|
||||
const calculateBreadCrumbsLoader = React.useCallback(() => {
|
||||
if (isBreadCrumbsLoading) {
|
||||
if (breadCrumbsLoaderTimeout.current) {
|
||||
return;
|
||||
}
|
||||
breadCrumbsStartLoader.current = new Date();
|
||||
breadCrumbsLoaderTimeout.current = setTimeout(() => {
|
||||
isMount.current && setShowBreadCrumbsLoader(true);
|
||||
}, SHOW_LOADER_TIMER);
|
||||
} else {
|
||||
if (breadCrumbsLoaderTimeout.current) {
|
||||
clearTimeout(breadCrumbsLoaderTimeout.current);
|
||||
breadCrumbsLoaderTimeout.current = null;
|
||||
breadCrumbsStartLoader.current = null;
|
||||
return setShowBreadCrumbsLoader(false);
|
||||
}
|
||||
|
||||
if (breadCrumbsStartLoader.current) {
|
||||
const currentDate = new Date();
|
||||
|
||||
const ms = Math.abs(
|
||||
breadCrumbsStartLoader.current.getTime() - currentDate.getTime()
|
||||
);
|
||||
|
||||
if (ms >= MIN_LOADER_TIMER) {
|
||||
breadCrumbsStartLoader.current = null;
|
||||
return setShowBreadCrumbsLoader(false);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (isMount.current) {
|
||||
breadCrumbsStartLoader.current = null;
|
||||
setShowBreadCrumbsLoader(false);
|
||||
}
|
||||
}, MIN_LOADER_TIMER - ms);
|
||||
}
|
||||
}
|
||||
}, [isBreadCrumbsLoading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFirstLoad && items) {
|
||||
setIsFirstLoad(false);
|
||||
}
|
||||
}, [isFirstLoad, items]);
|
||||
|
||||
React.useEffect(() => {
|
||||
calculateLoader();
|
||||
}, [isFirstLoad, calculateLoader]);
|
||||
|
||||
React.useEffect(() => {
|
||||
calculateBreadCrumbsLoader();
|
||||
}, [isBreadCrumbsLoading, calculateBreadCrumbsLoader]);
|
||||
|
||||
return {
|
||||
isBreadCrumbsLoading,
|
||||
setIsBreadCrumbsLoading,
|
||||
isNextPageLoading,
|
||||
setIsNextPageLoading,
|
||||
isFirstLoad,
|
||||
setIsFirstLoad,
|
||||
showBreadCrumbsLoader,
|
||||
showLoader,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLoadersHelper;
|
@ -0,0 +1,133 @@
|
||||
import React from "react";
|
||||
|
||||
// @ts-ignore
|
||||
import { getRooms } from "@docspace/common/api/rooms";
|
||||
// @ts-ignore
|
||||
import RoomsFilter from "@docspace/common/api/rooms/filter";
|
||||
// @ts-ignore
|
||||
import { RoomsType } from "@docspace/common/constants";
|
||||
// @ts-ignore
|
||||
import { iconSize32 } from "@docspace/common/utils/image-helpers";
|
||||
|
||||
import { PAGE_COUNT, defaultBreadCrumb } from "../utils";
|
||||
|
||||
import { BreadCrumb, Item, useRoomsHelperProps } from "../FilesSelector.types";
|
||||
|
||||
const getRoomLogo = (roomType: number) => {
|
||||
let path = "";
|
||||
switch (roomType) {
|
||||
case RoomsType.CustomRoom:
|
||||
path = "custom.svg";
|
||||
break;
|
||||
|
||||
case RoomsType.EditingRoom:
|
||||
path = "editing.svg";
|
||||
break;
|
||||
}
|
||||
|
||||
return iconSize32.get(path);
|
||||
};
|
||||
|
||||
const convertRoomsToItems = (rooms: any) => {
|
||||
const items = rooms.map((room: any) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
roomType,
|
||||
logo,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
security,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
} = room;
|
||||
|
||||
const icon = logo.medium ? logo.medium : getRoomLogo(roomType);
|
||||
|
||||
return {
|
||||
id,
|
||||
label: title,
|
||||
title,
|
||||
icon,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
security,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
isFolder: true,
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const useRoomsHelper = ({
|
||||
setIsNextPageLoading,
|
||||
setHasNextPage,
|
||||
setTotal,
|
||||
setItems,
|
||||
setBreadCrumbs,
|
||||
setIsRoot,
|
||||
isFirstLoad,
|
||||
setIsBreadCrumbsLoading,
|
||||
searchValue,
|
||||
}: useRoomsHelperProps) => {
|
||||
const getRoomList = React.useCallback(
|
||||
async (startIndex: number, isInit?: boolean, search?: string | null) => {
|
||||
setIsNextPageLoading(true);
|
||||
|
||||
const filterValue = search
|
||||
? search
|
||||
: search === null
|
||||
? ""
|
||||
: searchValue || "";
|
||||
|
||||
const page = startIndex / PAGE_COUNT;
|
||||
|
||||
const filter = RoomsFilter.getDefault();
|
||||
|
||||
filter.page = page;
|
||||
filter.pageCount = PAGE_COUNT;
|
||||
|
||||
filter.filterValue = filterValue;
|
||||
|
||||
const rooms = await getRooms(filter);
|
||||
|
||||
const { folders, total, count, current } = rooms;
|
||||
|
||||
const { title, id } = current;
|
||||
|
||||
if (isInit) {
|
||||
const breadCrumbs: BreadCrumb[] = [
|
||||
{ ...defaultBreadCrumb },
|
||||
{ label: title, id, isRoom: true },
|
||||
];
|
||||
|
||||
setBreadCrumbs(breadCrumbs);
|
||||
|
||||
setIsBreadCrumbsLoading(false);
|
||||
}
|
||||
|
||||
const itemList: Item[] = convertRoomsToItems(folders);
|
||||
|
||||
setHasNextPage(count === PAGE_COUNT);
|
||||
|
||||
if (isFirstLoad || startIndex === 0) {
|
||||
setTotal(total);
|
||||
setItems(itemList);
|
||||
} else {
|
||||
setItems((prevState: Item[] | null) => {
|
||||
if (prevState) return [...prevState, ...itemList];
|
||||
return [...itemList];
|
||||
});
|
||||
}
|
||||
|
||||
setIsNextPageLoading(false);
|
||||
setIsRoot(false);
|
||||
},
|
||||
[isFirstLoad, searchValue]
|
||||
);
|
||||
return { getRoomList };
|
||||
};
|
||||
|
||||
export default useRoomsHelper;
|
@ -0,0 +1,72 @@
|
||||
import React from "react";
|
||||
|
||||
import { FolderType } from "@docspace/common/constants";
|
||||
// @ts-ignore
|
||||
import { getFoldersTree } from "@docspace/common/api/files";
|
||||
|
||||
import CatalogFolderReactSvgUrl from "PUBLIC_DIR/images/catalog.folder.react.svg?url";
|
||||
import CatalogUserReactSvgUrl from "PUBLIC_DIR/images/catalog.user.react.svg?url";
|
||||
|
||||
import { useRootHelperProps, Item } from "../FilesSelector.types";
|
||||
|
||||
import { defaultBreadCrumb } from "../utils";
|
||||
|
||||
const useRootHelper = ({
|
||||
setBreadCrumbs,
|
||||
setIsBreadCrumbsLoading,
|
||||
setItems,
|
||||
treeFolders,
|
||||
setIsNextPageLoading,
|
||||
setTotal,
|
||||
setHasNextPage,
|
||||
}: useRootHelperProps) => {
|
||||
const [isRoot, setIsRoot] = React.useState<boolean>(false);
|
||||
|
||||
const getRootData = React.useCallback(async () => {
|
||||
setBreadCrumbs([defaultBreadCrumb]);
|
||||
setIsRoot(true);
|
||||
setIsBreadCrumbsLoading(false);
|
||||
const newItems: Item[] = [];
|
||||
|
||||
let currentTree: Item[] | null = null;
|
||||
|
||||
if (treeFolders && treeFolders?.length > 0) {
|
||||
currentTree = treeFolders;
|
||||
} else {
|
||||
currentTree = await getFoldersTree();
|
||||
}
|
||||
|
||||
currentTree?.forEach((folder) => {
|
||||
if (
|
||||
folder.rootFolderType === FolderType.Rooms ||
|
||||
folder.rootFolderType === FolderType.USER
|
||||
) {
|
||||
newItems.push({
|
||||
label: folder.title,
|
||||
title: folder.title,
|
||||
id: folder.id,
|
||||
parentId: folder.parentId,
|
||||
rootFolderType: folder.rootFolderType,
|
||||
filesCount: folder.filesCount,
|
||||
foldersCount: folder.foldersCount,
|
||||
security: folder.security,
|
||||
isFolder: true,
|
||||
|
||||
avatar:
|
||||
folder.rootFolderType === FolderType.Rooms
|
||||
? CatalogFolderReactSvgUrl
|
||||
: CatalogUserReactSvgUrl,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setItems(newItems);
|
||||
setTotal(newItems.length);
|
||||
setHasNextPage(false);
|
||||
setIsNextPageLoading(false);
|
||||
}, [treeFolders]);
|
||||
|
||||
return { isRoot, setIsRoot, getRootData };
|
||||
};
|
||||
|
||||
export default useRootHelper;
|
@ -28,8 +28,8 @@ newInstance
|
||||
loadPath: loadLanguagePath(config.homepage),
|
||||
},
|
||||
|
||||
ns: ["SelectFolder"],
|
||||
defaultNS: "SelectFolder",
|
||||
ns: ["Files", "Common", "Translations"],
|
||||
defaultNS: "Files",
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
598
packages/client/src/components/FilesSelector/index.tsx
Normal file
598
packages/client/src/components/FilesSelector/index.tsx
Normal file
@ -0,0 +1,598 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// @ts-ignore
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { FolderType } from "@docspace/common/constants";
|
||||
|
||||
import Aside from "@docspace/components/aside";
|
||||
import Backdrop from "@docspace/components/backdrop";
|
||||
import Selector from "@docspace/components/selector";
|
||||
// @ts-ignore
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
|
||||
import EmptyScreenFilterAltSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt.svg?url";
|
||||
import EmptyScreenFilterAltDarkSvgUrl from "PUBLIC_DIR/images/empty_screen_filter_alt_dark.svg?url";
|
||||
import EmptyScreenAltSvgUrl from "PUBLIC_DIR/images/empty_screen_alt.svg?url";
|
||||
import EmptyScreenAltSvgDarkUrl from "PUBLIC_DIR/images/empty_screen_alt_dark.svg?url";
|
||||
|
||||
import {
|
||||
BreadCrumb,
|
||||
FilesSelectorProps,
|
||||
Item,
|
||||
Security,
|
||||
} from "./FilesSelector.types";
|
||||
|
||||
import useRootHelper from "./helpers/useRootHelper";
|
||||
import useRoomsHelper from "./helpers/useRoomsHelper";
|
||||
import useLoadersHelper from "./helpers/useLoadersHelper";
|
||||
import useFilesHelper from "./helpers/useFilesHelper";
|
||||
import { getAcceptButtonLabel, getHeaderLabel, getIsDisabled } from "./utils";
|
||||
|
||||
const FilesSelector = ({
|
||||
isPanelVisible = false,
|
||||
withoutBasicSelection = false,
|
||||
withoutImmediatelyClose = false,
|
||||
isThirdParty = false,
|
||||
isEditorDialog = false,
|
||||
|
||||
filterParam,
|
||||
|
||||
onClose,
|
||||
|
||||
isMove,
|
||||
isCopy,
|
||||
isRestoreAll,
|
||||
|
||||
currentFolderId,
|
||||
fromFolderId,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
|
||||
treeFolders,
|
||||
|
||||
theme,
|
||||
|
||||
selection,
|
||||
disabledItems,
|
||||
isFolderActions,
|
||||
setIsFolderActions,
|
||||
setConflictDialogData,
|
||||
checkFileConflicts,
|
||||
itemOperationToFolder,
|
||||
clearActiveOperations,
|
||||
setMovingInProgress,
|
||||
setMoveToPanelVisible,
|
||||
setCopyPanelVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
|
||||
onSelectFolder,
|
||||
onSetBaseFolderPath,
|
||||
onSetNewFolderPath,
|
||||
onSelectTreeNode,
|
||||
onSave,
|
||||
onSelectFile,
|
||||
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
footerInputHeader,
|
||||
currentFooterInputValue,
|
||||
footerCheckboxLabel,
|
||||
|
||||
descriptionText,
|
||||
}: FilesSelectorProps) => {
|
||||
const { t } = useTranslation(["Files", "Common", "Translations"]);
|
||||
|
||||
const [breadCrumbs, setBreadCrumbs] = React.useState<BreadCrumb[]>([]);
|
||||
const [items, setItems] = React.useState<Item[] | null>(null);
|
||||
|
||||
const [selectedItemType, setSelectedItemType] = React.useState<
|
||||
"rooms" | "files" | undefined
|
||||
>(undefined);
|
||||
const [selectedItemId, setSelectedItemId] = React.useState<
|
||||
number | string | undefined
|
||||
>(undefined);
|
||||
const [selectedItemSecurity, setSelectedItemSecurity] = React.useState<
|
||||
Security | undefined
|
||||
>(undefined);
|
||||
const [selectedTreeNode, setSelectedTreeNode] = React.useState(null);
|
||||
const [selectedFileInfo, setSelectedFileInfo] = React.useState<{
|
||||
id: number | string;
|
||||
title: string;
|
||||
path?: string[];
|
||||
} | null>(null);
|
||||
|
||||
const [total, setTotal] = React.useState<number>(0);
|
||||
const [hasNextPage, setHasNextPage] = React.useState<boolean>(false);
|
||||
|
||||
const [searchValue, setSearchValue] = React.useState<string>("");
|
||||
|
||||
const [isRequestRunning, setIsRequestRunning] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const {
|
||||
setIsBreadCrumbsLoading,
|
||||
isNextPageLoading,
|
||||
setIsNextPageLoading,
|
||||
isFirstLoad,
|
||||
setIsFirstLoad,
|
||||
showBreadCrumbsLoader,
|
||||
showLoader,
|
||||
} = useLoadersHelper({ items });
|
||||
|
||||
const { isRoot, setIsRoot, getRootData } = useRootHelper({
|
||||
setIsBreadCrumbsLoading,
|
||||
setBreadCrumbs,
|
||||
setTotal,
|
||||
setItems,
|
||||
treeFolders,
|
||||
setHasNextPage,
|
||||
setIsNextPageLoading,
|
||||
});
|
||||
|
||||
const { getRoomList } = useRoomsHelper({
|
||||
setIsBreadCrumbsLoading,
|
||||
setBreadCrumbs,
|
||||
setIsNextPageLoading,
|
||||
setHasNextPage,
|
||||
setTotal,
|
||||
setItems,
|
||||
isFirstLoad,
|
||||
setIsRoot,
|
||||
searchValue,
|
||||
});
|
||||
|
||||
const { getFileList } = useFilesHelper({
|
||||
setIsBreadCrumbsLoading,
|
||||
setBreadCrumbs,
|
||||
setIsNextPageLoading,
|
||||
setHasNextPage,
|
||||
setTotal,
|
||||
setItems,
|
||||
selectedItemId,
|
||||
isFirstLoad,
|
||||
setIsRoot,
|
||||
searchValue,
|
||||
disabledItems,
|
||||
setSelectedItemSecurity,
|
||||
isThirdParty,
|
||||
onSelectTreeNode,
|
||||
setSelectedTreeNode,
|
||||
filterParam,
|
||||
});
|
||||
|
||||
const onSelectAction = (item: Item) => {
|
||||
if (item.isFolder) {
|
||||
setIsFirstLoad(true);
|
||||
setItems(null);
|
||||
setBreadCrumbs((value) => [
|
||||
...value,
|
||||
{
|
||||
label: item.label,
|
||||
id: item.id,
|
||||
isRoom:
|
||||
item.parentId === 0 && item.rootFolderType === FolderType.Rooms,
|
||||
},
|
||||
]);
|
||||
setSelectedItemId(item.id);
|
||||
setSearchValue("");
|
||||
|
||||
if (item.parentId === 0 && item.rootFolderType === FolderType.Rooms) {
|
||||
setSelectedItemType("rooms");
|
||||
getRoomList(0, false, null);
|
||||
} else {
|
||||
setSelectedItemType("files");
|
||||
getFileList(0, item.id, false, null);
|
||||
}
|
||||
} else {
|
||||
setSelectedFileInfo({ id: item.id, title: item.title });
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!withoutBasicSelection) {
|
||||
onSelectFolder && onSelectFolder(currentFolderId);
|
||||
onSetBaseFolderPath && onSetBaseFolderPath(currentFolderId);
|
||||
}
|
||||
if (!currentFolderId) {
|
||||
getRootData();
|
||||
} else {
|
||||
setSelectedItemId(currentFolderId);
|
||||
if (
|
||||
parentId === 0 &&
|
||||
rootFolderType === FolderType.Rooms &&
|
||||
!isThirdParty
|
||||
) {
|
||||
setSelectedItemType("rooms");
|
||||
getRoomList(0, true);
|
||||
} else {
|
||||
setSelectedItemType("files");
|
||||
getFileList(0, currentFolderId, true);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onClickBreadCrumb = (item: BreadCrumb) => {
|
||||
if (!isFirstLoad) {
|
||||
setSearchValue("");
|
||||
setIsFirstLoad(true);
|
||||
|
||||
if (+item.id === 0) {
|
||||
setSelectedItemSecurity(undefined);
|
||||
setSelectedItemType(undefined);
|
||||
getRootData();
|
||||
} else {
|
||||
setItems(null);
|
||||
|
||||
const idx = breadCrumbs.findIndex(
|
||||
(value) => value.id.toString() === item.id.toString()
|
||||
);
|
||||
|
||||
const newBreadCrumbs = breadCrumbs.map((item) => ({ ...item }));
|
||||
|
||||
newBreadCrumbs.splice(idx + 1, newBreadCrumbs.length - idx - 1);
|
||||
|
||||
setBreadCrumbs(newBreadCrumbs);
|
||||
setSelectedItemId(item.id);
|
||||
if (item.isRoom) {
|
||||
setSelectedItemType("rooms");
|
||||
getRoomList(0, false, null);
|
||||
} else {
|
||||
setSelectedItemType("files");
|
||||
getFileList(0, item.id, false, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onCloseAction = () => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
} else {
|
||||
if (isCopy) {
|
||||
setCopyPanelVisible(false);
|
||||
setIsFolderActions(false);
|
||||
} else if (isRestoreAll) {
|
||||
setRestoreAllPanelVisible(false);
|
||||
} else {
|
||||
setMoveToPanelVisible(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSearchAction = (value: string) => {
|
||||
setIsFirstLoad(true);
|
||||
setItems(null);
|
||||
if (selectedItemType === "rooms") {
|
||||
getRoomList(0, false, value === "" ? null : value);
|
||||
} else {
|
||||
getFileList(0, selectedItemId, false, value === "" ? null : value);
|
||||
}
|
||||
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
const onClearSearchAction = () => {
|
||||
setIsFirstLoad(true);
|
||||
setItems(null);
|
||||
if (selectedItemType === "rooms") {
|
||||
getRoomList(0, false, null);
|
||||
} else {
|
||||
getFileList(0, selectedItemId, false, null);
|
||||
}
|
||||
|
||||
setSearchValue("");
|
||||
};
|
||||
|
||||
const onAcceptAction = (
|
||||
items: any,
|
||||
accessRights: any,
|
||||
fileName: string,
|
||||
isChecked: boolean
|
||||
) => {
|
||||
if ((isMove || isCopy || isRestoreAll) && !isEditorDialog) {
|
||||
const folderTitle = breadCrumbs[breadCrumbs.length - 1].label;
|
||||
|
||||
let fileIds: any[] = [];
|
||||
let folderIds: any[] = [];
|
||||
|
||||
for (let item of selection) {
|
||||
if (item.fileExst || item.contentLength) {
|
||||
fileIds.push(item.id);
|
||||
} else if (item.id === selectedItemId) {
|
||||
toastr.error(t("Translations:MoveToFolderMessage"));
|
||||
} else {
|
||||
folderIds.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFolderActions) {
|
||||
fileIds = [];
|
||||
folderIds = [];
|
||||
|
||||
folderIds.push(currentFolderId);
|
||||
}
|
||||
|
||||
if (folderIds.length || fileIds.length) {
|
||||
const operationData = {
|
||||
destFolderId: selectedItemId,
|
||||
folderIds,
|
||||
fileIds,
|
||||
deleteAfter: false,
|
||||
isCopy,
|
||||
folderTitle,
|
||||
translations: {
|
||||
copy: t("Common:CopyOperation"),
|
||||
move: t("Translations:MoveToOperation"),
|
||||
},
|
||||
};
|
||||
|
||||
setIsRequestRunning(true);
|
||||
|
||||
checkFileConflicts(selectedItemId, folderIds, fileIds)
|
||||
.then(async (conflicts: any) => {
|
||||
if (conflicts.length) {
|
||||
setConflictDialogData(conflicts, operationData);
|
||||
setIsRequestRunning(false);
|
||||
} else {
|
||||
setIsRequestRunning(false);
|
||||
onCloseAction();
|
||||
const move = !isCopy;
|
||||
if (move) setMovingInProgress(move);
|
||||
sessionStorage.setItem("filesSelectorPath", `${selectedItemId}`);
|
||||
await itemOperationToFolder(operationData);
|
||||
}
|
||||
})
|
||||
.catch((e: any) => {
|
||||
toastr.error(e);
|
||||
setIsRequestRunning(false);
|
||||
clearActiveOperations(fileIds, folderIds);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsRequestRunning(true);
|
||||
onSetNewFolderPath && onSetNewFolderPath(selectedItemId);
|
||||
onSelectFolder && onSelectFolder(selectedItemId);
|
||||
onSave &&
|
||||
selectedItemId &&
|
||||
onSave(null, selectedItemId, fileName, isChecked);
|
||||
onSelectTreeNode && onSelectTreeNode(selectedTreeNode);
|
||||
|
||||
const info: {
|
||||
id: string | number;
|
||||
title: string;
|
||||
path?: string[];
|
||||
} = {
|
||||
id: selectedFileInfo?.id || "",
|
||||
title: selectedFileInfo?.title || "",
|
||||
path: [],
|
||||
};
|
||||
|
||||
breadCrumbs.forEach((item, index) => {
|
||||
if (index !== 0 && info.path) info.path.push(item.label);
|
||||
});
|
||||
|
||||
onSelectFile && selectedFileInfo && onSelectFile(info);
|
||||
!withoutImmediatelyClose && onCloseAction();
|
||||
}
|
||||
};
|
||||
|
||||
const headerLabel = getHeaderLabel(
|
||||
t,
|
||||
isCopy,
|
||||
isRestoreAll,
|
||||
isMove,
|
||||
filterParam
|
||||
);
|
||||
|
||||
const acceptButtonLabel = getAcceptButtonLabel(
|
||||
t,
|
||||
isCopy,
|
||||
isRestoreAll,
|
||||
isMove,
|
||||
filterParam
|
||||
);
|
||||
|
||||
const isDisabled = getIsDisabled(
|
||||
isFirstLoad,
|
||||
fromFolderId === selectedItemId,
|
||||
selectedItemType === "rooms",
|
||||
isRoot,
|
||||
isCopy,
|
||||
isMove,
|
||||
isRestoreAll,
|
||||
isRequestRunning,
|
||||
selectedItemSecurity,
|
||||
filterParam,
|
||||
!!selectedFileInfo
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
visible={isPanelVisible}
|
||||
isAside
|
||||
withBackground
|
||||
zIndex={210}
|
||||
onClick={onCloseAction}
|
||||
/>
|
||||
<Aside
|
||||
visible={isPanelVisible}
|
||||
withoutBodyScroll
|
||||
zIndex={310}
|
||||
onClose={onCloseAction}
|
||||
>
|
||||
<Selector
|
||||
headerLabel={headerLabel}
|
||||
withoutBackButton
|
||||
searchPlaceholder={t("Common:Search")}
|
||||
searchValue={searchValue}
|
||||
onSearch={onSearchAction}
|
||||
onClearSearch={onClearSearchAction}
|
||||
items={items ? items : []}
|
||||
onSelect={onSelectAction}
|
||||
acceptButtonLabel={acceptButtonLabel}
|
||||
onAccept={onAcceptAction}
|
||||
withCancelButton
|
||||
cancelButtonLabel={t("Common:CancelButton")}
|
||||
onCancel={onCloseAction}
|
||||
emptyScreenImage={
|
||||
theme.isBase ? EmptyScreenAltSvgUrl : EmptyScreenAltSvgDarkUrl
|
||||
}
|
||||
emptyScreenHeader={t("SelectorEmptyScreenHeader")}
|
||||
emptyScreenDescription=""
|
||||
searchEmptyScreenImage={
|
||||
theme.isBase
|
||||
? EmptyScreenFilterAltSvgUrl
|
||||
: EmptyScreenFilterAltDarkSvgUrl
|
||||
}
|
||||
searchEmptyScreenHeader={t("Common:NotFoundTitle")}
|
||||
searchEmptyScreenDescription={t("EmptyFilterDescriptionText")}
|
||||
withBreadCrumbs
|
||||
breadCrumbs={breadCrumbs}
|
||||
onSelectBreadCrumb={onClickBreadCrumb}
|
||||
isLoading={showLoader}
|
||||
isBreadCrumbsLoading={showBreadCrumbsLoader}
|
||||
withSearch={
|
||||
!isRoot && items ? items.length > 0 : !isRoot && isFirstLoad
|
||||
}
|
||||
rowLoader={
|
||||
<Loaders.SelectorRowLoader
|
||||
isMultiSelect={false}
|
||||
isUser={isRoot}
|
||||
isContainer={showLoader}
|
||||
/>
|
||||
}
|
||||
searchLoader={<Loaders.SelectorSearchLoader />}
|
||||
breadCrumbsLoader={<Loaders.SelectorBreadCrumbsLoader />}
|
||||
alwaysShowFooter={true}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
hasNextPage={hasNextPage}
|
||||
totalItems={total}
|
||||
loadNextPage={
|
||||
isRoot
|
||||
? null
|
||||
: selectedItemType === "rooms"
|
||||
? getRoomList
|
||||
: getFileList
|
||||
}
|
||||
disableAcceptButton={isDisabled}
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
footerInputHeader={footerInputHeader}
|
||||
currentFooterInputValue={currentFooterInputValue}
|
||||
footerCheckboxLabel={footerCheckboxLabel}
|
||||
descriptionText={
|
||||
!filterParam ? "" : descriptionText ?? t("Common:SelectDOCXFormat")
|
||||
}
|
||||
acceptButtonId={isMove || isCopy ? "select-file-modal-submit" : ""}
|
||||
cancelButtonId={isMove || isCopy ? "select-file-modal-cancel" : ""}
|
||||
/>
|
||||
</Aside>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(
|
||||
(
|
||||
{
|
||||
auth,
|
||||
selectedFolderStore,
|
||||
filesActionsStore,
|
||||
uploadDataStore,
|
||||
treeFoldersStore,
|
||||
dialogsStore,
|
||||
filesStore,
|
||||
}: any,
|
||||
{ isCopy, isRestoreAll, isMove, isPanelVisible, id, passedFoldersTree }: any
|
||||
) => {
|
||||
const { id: selectedId, parentId, rootFolderType } = selectedFolderStore;
|
||||
|
||||
const { setConflictDialogData, checkFileConflicts } = filesActionsStore;
|
||||
const { itemOperationToFolder, clearActiveOperations } = uploadDataStore;
|
||||
|
||||
const sessionPath = window.sessionStorage.getItem("filesSelectorPath");
|
||||
|
||||
const fromFolderId = id
|
||||
? id
|
||||
: passedFoldersTree?.length > 0
|
||||
? passedFoldersTree[0].id
|
||||
: rootFolderType === FolderType.Archive ||
|
||||
rootFolderType === FolderType.TRASH
|
||||
? undefined
|
||||
: selectedId;
|
||||
|
||||
const currentFolderId =
|
||||
sessionPath && (isMove || isCopy || isRestoreAll)
|
||||
? +sessionPath
|
||||
: fromFolderId;
|
||||
|
||||
const { treeFolders } = treeFoldersStore;
|
||||
|
||||
const {
|
||||
moveToPanelVisible,
|
||||
setMoveToPanelVisible,
|
||||
copyPanelVisible,
|
||||
setCopyPanelVisible,
|
||||
restoreAllPanelVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
conflictResolveDialogVisible,
|
||||
isFolderActions,
|
||||
setIsFolderActions,
|
||||
} = dialogsStore;
|
||||
|
||||
const { theme } = auth.settingsStore;
|
||||
|
||||
const { selection, bufferSelection, filesList, setMovingInProgress } =
|
||||
filesStore;
|
||||
|
||||
const selections =
|
||||
isMove || isCopy || isRestoreAll
|
||||
? isRestoreAll
|
||||
? filesList
|
||||
: selection.length
|
||||
? selection
|
||||
: [bufferSelection]
|
||||
: [];
|
||||
|
||||
const selectionsWithoutEditing = isRestoreAll
|
||||
? filesList
|
||||
: isCopy
|
||||
? selections
|
||||
: selections.filter((f: any) => f && !f?.isEditing);
|
||||
|
||||
const disabledItems: any[] = [];
|
||||
|
||||
selectionsWithoutEditing.forEach((item: any) => {
|
||||
if (item?.isFolder && item?.id) {
|
||||
disabledItems.push(item.id);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
currentFolderId,
|
||||
fromFolderId,
|
||||
parentId,
|
||||
rootFolderType,
|
||||
treeFolders,
|
||||
isPanelVisible: isPanelVisible
|
||||
? isPanelVisible
|
||||
: (moveToPanelVisible || copyPanelVisible || restoreAllPanelVisible) &&
|
||||
!conflictResolveDialogVisible,
|
||||
setMoveToPanelVisible,
|
||||
theme,
|
||||
selection: selectionsWithoutEditing,
|
||||
disabledItems,
|
||||
isFolderActions,
|
||||
setConflictDialogData,
|
||||
checkFileConflicts,
|
||||
itemOperationToFolder,
|
||||
clearActiveOperations,
|
||||
setMovingInProgress,
|
||||
setCopyPanelVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
setIsFolderActions,
|
||||
};
|
||||
}
|
||||
)(observer(FilesSelector));
|
71
packages/client/src/components/FilesSelector/utils.ts
Normal file
71
packages/client/src/components/FilesSelector/utils.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { FilesSelectorFilterTypes } from "@docspace/common/constants";
|
||||
import { BreadCrumb, Security } from "./FilesSelector.types";
|
||||
|
||||
export const PAGE_COUNT = 100;
|
||||
|
||||
export const defaultBreadCrumb: BreadCrumb = {
|
||||
label: "DocSpace",
|
||||
id: 0,
|
||||
isRoom: false,
|
||||
};
|
||||
|
||||
export const SHOW_LOADER_TIMER = 500;
|
||||
export const MIN_LOADER_TIMER = 500;
|
||||
|
||||
export const getHeaderLabel = (
|
||||
t: any,
|
||||
isCopy?: boolean,
|
||||
isRestoreAll?: boolean,
|
||||
isMove?: boolean,
|
||||
filterParam?: string
|
||||
) => {
|
||||
if (isMove) return t("Common:MoveTo");
|
||||
if (isCopy) return t("Common:Copy");
|
||||
if (isRestoreAll) return t("Common:Restore");
|
||||
if (filterParam === FilesSelectorFilterTypes.DOCX)
|
||||
return t("Translations:CreateMasterFormFromFile");
|
||||
if (!!filterParam) return t("Common:SelectFile");
|
||||
|
||||
return t("Common:Save");
|
||||
};
|
||||
|
||||
export const getAcceptButtonLabel = (
|
||||
t: any,
|
||||
isCopy?: boolean,
|
||||
isRestoreAll?: boolean,
|
||||
isMove?: boolean,
|
||||
filterParam?: string
|
||||
) => {
|
||||
if (isMove) return t("Translations:MoveHere");
|
||||
if (isCopy) return t("Translations:CopyHere");
|
||||
if (isRestoreAll) return t("Common:RestoreHere");
|
||||
if (filterParam === FilesSelectorFilterTypes.DOCX) return t("Common:Create");
|
||||
if (!!filterParam) return t("Common:SaveButton");
|
||||
|
||||
return t("Common:SaveHereButton");
|
||||
};
|
||||
|
||||
export const getIsDisabled = (
|
||||
isFirstLoad: boolean,
|
||||
sameId?: boolean,
|
||||
isRooms?: boolean,
|
||||
isRoot?: boolean,
|
||||
isCopy?: boolean,
|
||||
isMove?: boolean,
|
||||
isRestoreAll?: boolean,
|
||||
isRequestRunning?: boolean,
|
||||
security?: Security,
|
||||
filterParam?: string,
|
||||
isFileSelected?: boolean
|
||||
) => {
|
||||
if (isFirstLoad) return true;
|
||||
if (isRequestRunning) return true;
|
||||
if (!!filterParam) return !isFileSelected;
|
||||
if (sameId && !isCopy) return true;
|
||||
if (isRooms) return true;
|
||||
if (isRoot) return true;
|
||||
if (isCopy) return !security?.CopyTo;
|
||||
if (isMove || isRestoreAll) return !security?.MoveTo;
|
||||
|
||||
return false;
|
||||
};
|
@ -1,658 +0,0 @@
|
||||
import CatalogFolderReactSvgUrl from "PUBLIC_DIR/images/catalog.folder.react.svg?url";
|
||||
import CatalogUserReactSvgUrl from "PUBLIC_DIR/images/catalog.user.react.svg?url";
|
||||
import CatalogSharedReactSvgUrl from "PUBLIC_DIR/images/catalog.shared.react.svg?url";
|
||||
import CatalogPortfolioReactSvgUrl from "PUBLIC_DIR/images/catalog.portfolio.react.svg?url";
|
||||
import CatalogFavoritesReactSvgUrl from "PUBLIC_DIR/images/catalog.favorites.react.svg?url";
|
||||
import CatalogRecentReactSvgUrl from "PUBLIC_DIR/images/catalog.recent.react.svg?url";
|
||||
import CatalogPrivateReactSvgUrl from "PUBLIC_DIR/images/catalog.private.react.svg?url";
|
||||
import CatalogTrashReactSvgUrl from "PUBLIC_DIR/images/catalog.trash.react.svg?url";
|
||||
import CloudServicesGoogleDriveReactSvgUrl from "PUBLIC_DIR/images/cloud.services.google.drive.react.svg?url";
|
||||
import CloudServicesBoxReactSvgUrl from "PUBLIC_DIR/images/cloud.services.box.react.svg?url";
|
||||
import CloudServicesDropboxReactSvgUrl from "PUBLIC_DIR/images/cloud.services.dropbox.react.svg?url";
|
||||
import CloudServicesOnedriveReactSvgUrl from "PUBLIC_DIR/images/cloud.services.onedrive.react.svg?url";
|
||||
import CloudServicesKdriveReactSvgUrl from "PUBLIC_DIR/images/cloud.services.kdrive.react.svg?url";
|
||||
import CloudServicesYandexReactSvgUrl from "PUBLIC_DIR/images/cloud.services.yandex.react.svg?url";
|
||||
import CloudServicesNextcloudReactSvgUrl from "PUBLIC_DIR/images/cloud.services.nextcloud.react.svg?url";
|
||||
import CloudServicesWebdavReactSvgUrl from "PUBLIC_DIR/images/cloud.services.webdav.react.svg?url";
|
||||
import React from "react";
|
||||
import TreeMenu from "@docspace/components/tree-menu";
|
||||
import TreeNode from "@docspace/components/tree-menu/sub-components/tree-node";
|
||||
import styled from "styled-components";
|
||||
import { FolderType, ShareAccessRights } from "@docspace/common/constants";
|
||||
import { onConvertFiles } from "../../helpers/files-converter";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import ExpanderDownIcon from "PUBLIC_DIR/images/expander-down.react.svg";
|
||||
import ExpanderRightIcon from "PUBLIC_DIR/images/expander-right.react.svg";
|
||||
import commonIconsStyles from "@docspace/components/utils/common-icons-style";
|
||||
import { observer, inject } from "mobx-react";
|
||||
import { runInAction } from "mobx";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import Base from "@docspace/components/themes/base";
|
||||
|
||||
const backgroundDragColor = "#EFEFB2";
|
||||
const backgroundDragEnterColor = "#F8F7BF";
|
||||
|
||||
const StyledTreeMenu = styled(TreeMenu)`
|
||||
width: 100%;
|
||||
.rc-tree-node-content-wrapper {
|
||||
background: ${(props) => !props.dragging && "none !important"};
|
||||
}
|
||||
|
||||
/* .rc-tree-node-selected {
|
||||
background: ${(props) =>
|
||||
!props.isPanel
|
||||
? props.theme.filesArticleBody.background
|
||||
: props.theme.filesArticleBody.panelBackground} !important;
|
||||
} */
|
||||
|
||||
.rc-tree-treenode-disabled > span:not(.rc-tree-switcher),
|
||||
.rc-tree-treenode-disabled > a,
|
||||
.rc-tree-treenode-disabled > a span {
|
||||
cursor: wait;
|
||||
}
|
||||
/*
|
||||
span.rc-tree-iconEle {
|
||||
margin-left: 4px;
|
||||
}*/
|
||||
`;
|
||||
|
||||
StyledTreeMenu.defaultProps = { theme: Base };
|
||||
|
||||
const StyledFolderSVG = styled.div`
|
||||
svg {
|
||||
width: 100%;
|
||||
|
||||
path {
|
||||
fill: ${(props) => props.theme.filesArticleBody.fill};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledFolderSVG.defaultProps = { theme: Base };
|
||||
|
||||
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
|
||||
${commonIconsStyles}
|
||||
path {
|
||||
fill: ${(props) => props.theme.filesArticleBody.expanderColor};
|
||||
}
|
||||
`;
|
||||
StyledExpanderDownIcon.defaultProps = { theme: Base };
|
||||
|
||||
const StyledExpanderRightIcon = styled(ExpanderRightIcon)`
|
||||
${commonIconsStyles}
|
||||
path {
|
||||
fill: ${(props) => props.theme.filesArticleBody.expanderColor};
|
||||
}
|
||||
`;
|
||||
StyledExpanderRightIcon.defaultProps = { theme: Base };
|
||||
|
||||
class TreeFolders extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isExpand: false, isLoading: false };
|
||||
this.selectionFoldersId = [];
|
||||
}
|
||||
|
||||
setItemSecurityRights = (data) => {
|
||||
const { selectedKeys, setItemSecurity } = this.props;
|
||||
const selectedFolder = data.find(
|
||||
(x) => x.id.toString() === selectedKeys[0]
|
||||
);
|
||||
|
||||
selectedFolder && setItemSecurity(selectedFolder.security);
|
||||
};
|
||||
componentDidMount() {
|
||||
const { selectionFiles, expandedPanelKeys = [], data } = this.props;
|
||||
this.props.isLoadingNodes && this.props.setIsLoadingNodes(false);
|
||||
|
||||
const isRootFolder = expandedPanelKeys.length === 0;
|
||||
|
||||
isRootFolder && this.setItemSecurityRights(data);
|
||||
|
||||
if (selectionFiles) {
|
||||
for (let item of selectionFiles) {
|
||||
if (!item.fileExst) {
|
||||
this.selectionFoldersId.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBadgeClick = (e) => {
|
||||
e.stopPropagation();
|
||||
const id = e.currentTarget.dataset.id;
|
||||
this.props.onBadgeClick && this.props.onBadgeClick(id);
|
||||
};
|
||||
|
||||
getFolderIcon = (item) => {
|
||||
let iconUrl = CatalogFolderReactSvgUrl;
|
||||
|
||||
switch (item.rootFolderType) {
|
||||
case FolderType.USER:
|
||||
iconUrl = CatalogUserReactSvgUrl;
|
||||
break;
|
||||
case FolderType.SHARE:
|
||||
iconUrl = CatalogSharedReactSvgUrl;
|
||||
break;
|
||||
case FolderType.COMMON:
|
||||
iconUrl = CatalogPortfolioReactSvgUrl;
|
||||
break;
|
||||
case FolderType.Favorites:
|
||||
iconUrl = CatalogFavoritesReactSvgUrl;
|
||||
break;
|
||||
case FolderType.Recent:
|
||||
iconUrl = CatalogRecentReactSvgUrl;
|
||||
break;
|
||||
case FolderType.Privacy:
|
||||
iconUrl = CatalogPrivateReactSvgUrl;
|
||||
break;
|
||||
case FolderType.TRASH:
|
||||
iconUrl = CatalogTrashReactSvgUrl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (item.parentId !== 0) iconUrl = CatalogFolderReactSvgUrl;
|
||||
|
||||
switch (item.providerKey) {
|
||||
case "GoogleDrive":
|
||||
iconUrl = CloudServicesGoogleDriveReactSvgUrl;
|
||||
break;
|
||||
case "Box":
|
||||
iconUrl = CloudServicesBoxReactSvgUrl;
|
||||
break;
|
||||
case "DropboxV2":
|
||||
iconUrl = CloudServicesDropboxReactSvgUrl;
|
||||
break;
|
||||
case "OneDrive":
|
||||
iconUrl = CloudServicesOnedriveReactSvgUrl;
|
||||
break;
|
||||
case "SharePoint":
|
||||
iconUrl = CloudServicesOnedriveReactSvgUrl;
|
||||
break;
|
||||
case "kDrive":
|
||||
iconUrl = CloudServicesKdriveReactSvgUrl;
|
||||
break;
|
||||
case "Yandex":
|
||||
iconUrl = CloudServicesYandexReactSvgUrl;
|
||||
break;
|
||||
case "NextCloud":
|
||||
iconUrl = CloudServicesNextcloudReactSvgUrl;
|
||||
break;
|
||||
case "OwnCloud":
|
||||
iconUrl = CatalogFolderReactSvgUrl;
|
||||
break;
|
||||
case "WebDav":
|
||||
iconUrl = CloudServicesWebdavReactSvgUrl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledFolderSVG>
|
||||
<ReactSVG src={iconUrl} />
|
||||
</StyledFolderSVG>
|
||||
);
|
||||
};
|
||||
|
||||
showDragItems = (item) => {
|
||||
const { isAdmin, myId, commonId, currentId, draggableItems } = this.props;
|
||||
if (item.id === currentId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!draggableItems || draggableItems.find((x) => x.id === item.id))
|
||||
return false;
|
||||
|
||||
if (
|
||||
item.rootFolderType === FolderType.SHARE &&
|
||||
item.access === ShareAccessRights.FullAccess
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
if (
|
||||
(item.pathParts &&
|
||||
(item.pathParts[0] === myId || item.pathParts[0] === commonId)) ||
|
||||
item.rootFolderType === FolderType.USER ||
|
||||
item.rootFolderType === FolderType.COMMON
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(item.pathParts && item.pathParts[0] === myId) ||
|
||||
item.rootFolderType === FolderType.USER
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
getItems = (props) => {
|
||||
const { data, path } = props;
|
||||
|
||||
return data.map((item) => {
|
||||
const dragging = this.props.dragging ? this.showDragItems(item) : false;
|
||||
const showBadge = false;
|
||||
const provider = item.providerKey;
|
||||
const serviceFolder = !!item.providerKey;
|
||||
|
||||
let value = "",
|
||||
disableNodeValue = "";
|
||||
if (dragging) value = `${item.id} dragging ${provider}`;
|
||||
|
||||
const { roomsFolderId, expandedPanelKeys } = this.props;
|
||||
|
||||
let isDisabledNode = false;
|
||||
if (item.id == roomsFolderId) {
|
||||
isDisabledNode = expandedPanelKeys?.includes(roomsFolderId + "");
|
||||
}
|
||||
|
||||
if (this.selectionFoldersId && this.selectionFoldersId.includes(item.id))
|
||||
disableNodeValue = "disable-node";
|
||||
|
||||
if (isDisabledNode) disableNodeValue += " disable-folder ";
|
||||
|
||||
if ((item.folders && item.folders.length > 0) || serviceFolder) {
|
||||
return (
|
||||
<TreeNode
|
||||
id={item.id}
|
||||
key={item.id}
|
||||
className={`tree-drag ${item.folderClassName} ${disableNodeValue}`}
|
||||
data-value={value}
|
||||
title={item.title}
|
||||
icon={this.getFolderIcon(item)}
|
||||
dragging={dragging}
|
||||
isLeaf={
|
||||
item.rootFolderType === FolderType.Privacy &&
|
||||
!this.props.isDesktop
|
||||
? true
|
||||
: null
|
||||
}
|
||||
newItems={
|
||||
!this.props.isDesktop &&
|
||||
item.rootFolderType === FolderType.Privacy
|
||||
? null
|
||||
: item.newItems
|
||||
}
|
||||
providerKey={item.providerKey}
|
||||
onBadgeClick={this.onBadgeClick}
|
||||
showBadge={showBadge}
|
||||
path={path}
|
||||
security={item.security}
|
||||
>
|
||||
{item.rootFolderType === FolderType.Privacy && !this.props.isDesktop
|
||||
? null
|
||||
: this.getItems({
|
||||
data: item.folders ? item.folders : [],
|
||||
path: [...path, { id: item.id, title: item.title }],
|
||||
})}
|
||||
</TreeNode>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TreeNode
|
||||
id={item.id}
|
||||
key={item.id}
|
||||
className={`tree-drag ${item.folderClassName} ${disableNodeValue}`}
|
||||
data-value={value}
|
||||
title={item.title}
|
||||
needTopMargin={item.rootFolderType === FolderType.TRASH}
|
||||
dragging={dragging}
|
||||
isLeaf={
|
||||
(item.rootFolderType === FolderType.Privacy &&
|
||||
!this.props.isDesktop) ||
|
||||
!item.foldersCount
|
||||
? true
|
||||
: false
|
||||
}
|
||||
icon={this.getFolderIcon(item)}
|
||||
newItems={
|
||||
!this.props.isDesktop && item.rootFolderType === FolderType.Privacy
|
||||
? null
|
||||
: item.newItems
|
||||
}
|
||||
providerKey={item.providerKey}
|
||||
onBadgeClick={this.onBadgeClick}
|
||||
showBadge={showBadge}
|
||||
security={item.security}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
switcherIcon = (obj) => {
|
||||
if (obj.isLeaf) {
|
||||
return null;
|
||||
}
|
||||
if (obj.expanded) {
|
||||
return <StyledExpanderDownIcon theme={this.props.theme} size="scale" />;
|
||||
} else {
|
||||
return <StyledExpanderRightIcon theme={this.props.theme} size="scale" />;
|
||||
}
|
||||
};
|
||||
|
||||
loop = (data, child, pos) => {
|
||||
let newPos = pos.split("-");
|
||||
let newData = data;
|
||||
|
||||
newPos.shift();
|
||||
while (newPos.length !== 1) {
|
||||
const index = +newPos[0];
|
||||
newData = newData[index].folders;
|
||||
newPos.shift();
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
newData[newPos].folders = child;
|
||||
});
|
||||
};
|
||||
|
||||
getNewTreeData(treeData, curId, child, pos) {
|
||||
const { selectedNodeParentId, setIsLoadingNodes } = this.props;
|
||||
!this.expand && selectedNodeParentId && setIsLoadingNodes(true);
|
||||
|
||||
this.loop(treeData, child, pos);
|
||||
this.setLeaf(treeData, curId, 10);
|
||||
}
|
||||
|
||||
setLeaf(treeData, curKey, level) {
|
||||
const loopLeaf = (data, lev) => {
|
||||
const l = lev - 1;
|
||||
data.forEach((item) => {
|
||||
if (
|
||||
item.key?.length > curKey.length
|
||||
? item.key.indexOf(curKey) !== 0
|
||||
: curKey.indexOf(item.key) !== 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (item.folders) {
|
||||
loopLeaf(item.folders, l);
|
||||
} else if (l < 1) {
|
||||
item.isLeaf = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
loopLeaf(treeData, level + 1);
|
||||
}
|
||||
|
||||
generateTreeNodes = (treeNode) => {
|
||||
const { withoutProvider } = this.props;
|
||||
const folderId = treeNode.id;
|
||||
const level = treeNode.pos;
|
||||
|
||||
let arrayFolders, proverIndex;
|
||||
return this.props.getSubfolders(folderId).then((data) => {
|
||||
arrayFolders = data;
|
||||
|
||||
const folderIndex = treeNode.pos;
|
||||
let i = 0;
|
||||
|
||||
for (let item of arrayFolders) {
|
||||
item["key"] = `${folderIndex}-${i}`;
|
||||
|
||||
if (withoutProvider && item.providerKey) {
|
||||
proverIndex = i;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (proverIndex) {
|
||||
arrayFolders.splice(proverIndex, 1);
|
||||
}
|
||||
|
||||
return { folders: arrayFolders, listIds: [], level };
|
||||
});
|
||||
};
|
||||
|
||||
onLoadData = (treeNode, isExpand) => {
|
||||
const {
|
||||
data: incomingDate,
|
||||
certainFolders,
|
||||
roomsFolderId,
|
||||
expandedPanelKeys = [],
|
||||
} = this.props;
|
||||
isExpand && this.setState({ isExpand: true });
|
||||
//console.log("load data...", treeNode);
|
||||
|
||||
this.setState({
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
if (this.state.isExpand && !isExpand) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.generateTreeNodes(treeNode)
|
||||
.then((data) => {
|
||||
const itemId = treeNode.id.toString();
|
||||
const listIds = data.listIds;
|
||||
listIds.push(itemId);
|
||||
|
||||
const treeData = certainFolders
|
||||
? [...incomingDate]
|
||||
: [...this.props.treeFolders];
|
||||
|
||||
this.getNewTreeData(treeData, listIds, data.folders, data.level);
|
||||
!certainFolders && this.props.setTreeFolders(treeData);
|
||||
|
||||
const isLastFoldersLevel =
|
||||
expandedPanelKeys[expandedPanelKeys.length - 1] === data.listIds[0];
|
||||
|
||||
isLastFoldersLevel && this.setItemSecurityRights(data.folders);
|
||||
|
||||
if (
|
||||
data.listIds[0] == roomsFolderId &&
|
||||
this.props.onSelect &&
|
||||
this.selectedRootRoom
|
||||
) {
|
||||
const roomsIndex = treeData.findIndex((f) => f.id == roomsFolderId);
|
||||
const firstRoomsNodeId = treeData[roomsIndex]?.folders[0]?.id;
|
||||
|
||||
this.selectedRootRoom = false;
|
||||
|
||||
treeData[roomsIndex]?.folders[0] &&
|
||||
this.props.onSelect(
|
||||
[firstRoomsNodeId],
|
||||
treeData[roomsIndex]?.folders[0]
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log("err", err))
|
||||
.finally(() => {
|
||||
this.setState({ isExpand: false, isLoading: false });
|
||||
});
|
||||
};
|
||||
|
||||
onExpand = (expandedKeys, treeNode, isRoom = false) => {
|
||||
this.expand = true;
|
||||
if (treeNode.node && !treeNode.node.children) {
|
||||
if (treeNode.expanded) {
|
||||
this.onLoadData(treeNode.node, true);
|
||||
}
|
||||
} else if (isRoom) {
|
||||
this.props.onSelect(
|
||||
[treeNode.node.children[0].id],
|
||||
treeNode.node.children[0]
|
||||
);
|
||||
}
|
||||
|
||||
this.props.setExpandedPanelKeys(expandedKeys);
|
||||
};
|
||||
|
||||
onSelect = (folder, treeNode) => {
|
||||
const { onSelect, expandedPanelKeys = [], roomsFolderId } = this.props;
|
||||
|
||||
const newExpandedPanelKeys = JSON.parse(JSON.stringify(expandedPanelKeys));
|
||||
newExpandedPanelKeys.push(folder[0]);
|
||||
|
||||
if (folder[0] == roomsFolderId) {
|
||||
this.onExpand(newExpandedPanelKeys, treeNode, true);
|
||||
this.selectedRootRoom = true;
|
||||
return;
|
||||
}
|
||||
|
||||
onSelect && onSelect(folder, treeNode.node);
|
||||
};
|
||||
|
||||
onDragOver = (data) => {
|
||||
const parentElement = data.event.target.parentElement;
|
||||
const existElement = parentElement.classList.contains(
|
||||
"rc-tree-node-content-wrapper"
|
||||
);
|
||||
|
||||
if (existElement) {
|
||||
if (data.node.props.dragging) {
|
||||
parentElement.style.background = backgroundDragColor;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDragLeave = (data) => {
|
||||
const parentElement = data.event.target.parentElement;
|
||||
const existElement = parentElement.classList.contains(
|
||||
"rc-tree-node-content-wrapper"
|
||||
);
|
||||
|
||||
if (existElement) {
|
||||
if (data.node.props.dragging) {
|
||||
parentElement.style.background = backgroundDragEnterColor;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onDrop = (data) => {
|
||||
const { setDragging, onTreeDrop } = this.props;
|
||||
const { dragging, id } = data.node.props;
|
||||
//if (dragging) {
|
||||
setDragging(false);
|
||||
const promise = new Promise((resolve) =>
|
||||
onConvertFiles(data.event, resolve)
|
||||
);
|
||||
promise.then((files) => onTreeDrop(files, id));
|
||||
//}
|
||||
};
|
||||
onLoad = (loadedKeys, options) => {
|
||||
const { firstLoadScroll, selectedNodeParentId } = this.props;
|
||||
//console.log("onLoad tree nodes", "loadedKeys", treeNode, "options", options);
|
||||
|
||||
if (
|
||||
!this.expand &&
|
||||
selectedNodeParentId &&
|
||||
loadedKeys.includes(selectedNodeParentId.toString())
|
||||
) {
|
||||
firstLoadScroll();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
selectedKeys,
|
||||
dragging,
|
||||
expandedPanelKeys,
|
||||
treeFolders,
|
||||
data,
|
||||
disabled,
|
||||
isPanel,
|
||||
isLoadingNodes,
|
||||
} = this.props;
|
||||
|
||||
const { isLoading } = this.state;
|
||||
|
||||
return (
|
||||
<StyledTreeMenu
|
||||
isPanel={isPanel}
|
||||
className="files-tree-menu"
|
||||
checkable={false}
|
||||
draggable={dragging}
|
||||
disabled={isLoadingNodes || isLoading || disabled}
|
||||
multiple={false}
|
||||
showIcon
|
||||
switcherIcon={this.switcherIcon}
|
||||
onSelect={this.onSelect}
|
||||
selectedKeys={selectedKeys}
|
||||
loadData={this.onLoadData}
|
||||
expandedKeys={expandedPanelKeys}
|
||||
onExpand={this.onExpand}
|
||||
onDragOver={this.onDragOver}
|
||||
onDragLeave={this.onDragLeave}
|
||||
onDrop={this.onDrop}
|
||||
dragging={dragging}
|
||||
gapBetweenNodes="22"
|
||||
gapBetweenNodesTablet="26"
|
||||
isFullFillSelection={false}
|
||||
childrenCount={expandedPanelKeys?.length}
|
||||
onLoad={this.onLoad}
|
||||
>
|
||||
{this.getItems(
|
||||
{ data: data, path: [] } || { data: treeFolders, path: [] }
|
||||
)}
|
||||
</StyledTreeMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TreeFolders.defaultProps = {
|
||||
selectedKeys: [],
|
||||
};
|
||||
|
||||
export default inject(
|
||||
(
|
||||
{
|
||||
auth,
|
||||
filesStore,
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
selectFolderDialogStore,
|
||||
},
|
||||
{ useDefaultSelectedKeys, selectedKeys }
|
||||
) => {
|
||||
const { selection, dragging, setDragging } = filesStore;
|
||||
|
||||
const {
|
||||
treeFolders,
|
||||
setTreeFolders,
|
||||
myFolderId,
|
||||
commonFolderId,
|
||||
setExpandedPanelKeys,
|
||||
getSubfolders,
|
||||
setIsLoadingNodes,
|
||||
isLoadingNodes,
|
||||
roomsFolderId,
|
||||
} = treeFoldersStore;
|
||||
const { id, parentId: selectedNodeParentId } = selectedFolderStore;
|
||||
const { setItemSecurity } = selectFolderDialogStore;
|
||||
return {
|
||||
setItemSecurity,
|
||||
isAdmin: auth.isAdmin,
|
||||
isDesktop: auth.settingsStore.isDesktopClient,
|
||||
dragging,
|
||||
currentId: id,
|
||||
myId: myFolderId,
|
||||
commonId: commonFolderId,
|
||||
draggableItems: dragging ? selection : null,
|
||||
treeFolders,
|
||||
selectedKeys: useDefaultSelectedKeys
|
||||
? treeFoldersStore.selectedKeys
|
||||
: selectedKeys,
|
||||
|
||||
setDragging,
|
||||
setTreeFolders,
|
||||
setExpandedPanelKeys,
|
||||
getSubfolders,
|
||||
setIsLoadingNodes,
|
||||
isLoadingNodes,
|
||||
selectedNodeParentId,
|
||||
roomsFolderId,
|
||||
};
|
||||
}
|
||||
)(withTranslation(["Files", "Common"])(observer(TreeFolders)));
|
@ -1,113 +0,0 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Text from "@docspace/components/text";
|
||||
import Scrollbar from "@docspace/components/scrollbar";
|
||||
import { StyledTree } from "../panels/SelectionPanel/StyledSelectionPanel";
|
||||
import TreeFolders from "./TreeFolders";
|
||||
const FolderTreeBody = ({
|
||||
expandedKeys,
|
||||
folderTree,
|
||||
onSelect,
|
||||
withoutProvider,
|
||||
certainFolders,
|
||||
isAvailable,
|
||||
filter,
|
||||
selectedKeys,
|
||||
theme,
|
||||
isDisableTree,
|
||||
parentId,
|
||||
isLoadingNodes,
|
||||
setIsLoadingNodes,
|
||||
selectionFiles,
|
||||
}) => {
|
||||
const { t } = useTranslation(["SelectFolder", "Common"]);
|
||||
|
||||
const scrollToSelectedNode = () => {
|
||||
const selectedNode = document.getElementsByClassName(
|
||||
"rc-tree-treenode-selected"
|
||||
)[0];
|
||||
if (selectedNode) {
|
||||
document
|
||||
.querySelector("#folder-tree-scroll-bar > .scroll-body")
|
||||
.scrollTo(0, selectedNode.offsetTop);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
scrollToSelectedNode();
|
||||
}, []);
|
||||
|
||||
const firstLoadScroll = () => {
|
||||
setIsLoadingNodes(false);
|
||||
|
||||
scrollToSelectedNode();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAvailable ? (
|
||||
<StyledTree theme={theme} isLoadingNodes={isLoadingNodes}>
|
||||
<div className="selection-panel_tree-folder">
|
||||
<Scrollbar id="folder-tree-scroll-bar" stype="mediumBlack">
|
||||
<TreeFolders
|
||||
isPanel={true}
|
||||
expandedPanelKeys={expandedKeys}
|
||||
data={folderTree}
|
||||
filter={filter}
|
||||
onSelect={onSelect}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders={certainFolders}
|
||||
selectedKeys={selectedKeys}
|
||||
disabled={isDisableTree}
|
||||
needUpdate={false}
|
||||
defaultExpandAll
|
||||
parentId={parentId}
|
||||
firstLoadScroll={firstLoadScroll}
|
||||
selectionFiles={selectionFiles}
|
||||
/>
|
||||
</Scrollbar>
|
||||
</div>
|
||||
</StyledTree>
|
||||
) : (
|
||||
<StyledTree>
|
||||
<div className="selection-panel_empty-folder">
|
||||
<Text as="span">{t("NotAvailableFolder")}</Text>
|
||||
</div>
|
||||
</StyledTree>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FolderTreeBody.defaultProps = {
|
||||
isAvailable: true,
|
||||
isLoadingData: false,
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({
|
||||
filesStore,
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
clientLoadingStore,
|
||||
}) => {
|
||||
const { filter } = filesStore;
|
||||
const { isLoading } = clientLoadingStore;
|
||||
const { expandedPanelKeys, isLoadingNodes, setIsLoadingNodes } =
|
||||
treeFoldersStore;
|
||||
const expandedKeysProp = expandedPanelKeys
|
||||
? expandedPanelKeys
|
||||
: selectedFolderStore.pathParts;
|
||||
const expandedKeys = expandedKeysProp?.map((item) => item.toString());
|
||||
!expandedPanelKeys && expandedKeys?.pop();
|
||||
return {
|
||||
expandedKeys: expandedKeys,
|
||||
|
||||
filter,
|
||||
isLoading,
|
||||
isLoadingNodes,
|
||||
setIsLoadingNodes,
|
||||
};
|
||||
}
|
||||
)(observer(FolderTreeBody));
|
@ -139,6 +139,7 @@ const ConflictResolveDialog = (props) => {
|
||||
|
||||
onClosePanels();
|
||||
try {
|
||||
sessionStorage.setItem("filesSelectorPath", `${destFolderId}`);
|
||||
await itemOperationToFolder(data);
|
||||
} catch (error) {
|
||||
toastr.error(error.message ? error.message : error);
|
||||
@ -189,6 +190,8 @@ const ConflictResolveDialog = (props) => {
|
||||
const singleFile = filesCount === 1;
|
||||
const file = items[0].title;
|
||||
|
||||
const obj = { file, folder: folderTitle };
|
||||
|
||||
return (
|
||||
<StyledModalDialog
|
||||
isLoading={!tReady}
|
||||
@ -205,17 +208,15 @@ const ConflictResolveDialog = (props) => {
|
||||
t={t}
|
||||
i18nKey="ConflictResolveDescription"
|
||||
ns="ConflictResolveDialog"
|
||||
>
|
||||
{{ file, folder: folderTitle }}
|
||||
</Trans>
|
||||
values={obj}
|
||||
></Trans>
|
||||
) : (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey="ConflictResolveDescriptionFiles"
|
||||
ns="ConflictResolveDialog"
|
||||
>
|
||||
{{ filesCount, folder: folderTitle }}
|
||||
</Trans>
|
||||
values={{ filesCount, folder: folderTitle }}
|
||||
></Trans>
|
||||
)}
|
||||
</Text>
|
||||
<Text className="select-action">
|
||||
|
@ -4,8 +4,8 @@ import styled from "styled-components";
|
||||
|
||||
import { IconButton } from "@docspace/components";
|
||||
import { Base } from "@docspace/components/themes";
|
||||
import SelectFolderDialog from "@docspace/client/src/components/panels/SelectFolderDialog";
|
||||
import FilesFilter from "@docspace/common/api/files/filter";
|
||||
|
||||
import FilesSelector from "SRC_DIR/components/FilesSelector";
|
||||
|
||||
const StyledFolderInput = styled.div`
|
||||
box-sizing: border-box;
|
||||
@ -146,21 +146,13 @@ const FolderInput = ({
|
||||
</div>
|
||||
</StyledFolderInput>
|
||||
|
||||
<SelectFolderDialog
|
||||
t={t}
|
||||
<FilesSelector
|
||||
isPanelVisible={isDialogOpen}
|
||||
onClose={onClose}
|
||||
treeNode={treeNode}
|
||||
isThirdParty={true}
|
||||
onSelectTreeNode={setTreeNode}
|
||||
filter={FilesFilter.getDefault()}
|
||||
selectedId={thirdpartyAccount.id}
|
||||
selectFolderInputExist={true}
|
||||
passedFoldersTree={[thirdpartyAccount]}
|
||||
filteredType={""}
|
||||
displayType={"modal"}
|
||||
withoutProvider={false}
|
||||
withoutImmediatelyClose={false}
|
||||
isDisableTree={false}
|
||||
id={thirdpartyAccount.id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,259 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import SelectFolderDialog from "../SelectFolderDialog";
|
||||
|
||||
let timerId;
|
||||
const OperationsPanelComponent = (props) => {
|
||||
const {
|
||||
t,
|
||||
tReady,
|
||||
filter,
|
||||
isCopy,
|
||||
isRestore,
|
||||
visible,
|
||||
provider,
|
||||
selection,
|
||||
isFolderActions,
|
||||
isRecycleBin,
|
||||
setDestFolderId,
|
||||
setIsFolderActions,
|
||||
currentFolderId,
|
||||
setCopyPanelVisible,
|
||||
setExpandedPanelKeys,
|
||||
setMoveToPanelVisible,
|
||||
setConflictDialogData,
|
||||
itemOperationToFolder,
|
||||
checkFileConflicts,
|
||||
conflictResolveDialogVisible,
|
||||
clearActiveOperations,
|
||||
thirdPartyMoveDialogVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
setMovingInProgress,
|
||||
} = props;
|
||||
|
||||
const deleteAfter = false; // TODO: get from settings
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
};
|
||||
});
|
||||
const onClose = () => {
|
||||
if (isCopy) {
|
||||
setCopyPanelVisible(false);
|
||||
setIsFolderActions(false);
|
||||
} else if (isRestore) {
|
||||
setRestoreAllPanelVisible(false);
|
||||
} else {
|
||||
setMoveToPanelVisible(false);
|
||||
}
|
||||
setExpandedPanelKeys(null);
|
||||
};
|
||||
|
||||
const onSubmit = (selectedFolder, folderTitle, providerKey) => {
|
||||
if (!isCopy && currentFolderId === selectedFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCopy) {
|
||||
startOperation(isCopy, selectedFolder, folderTitle);
|
||||
} else {
|
||||
startOperation(isCopy, selectedFolder, folderTitle);
|
||||
}
|
||||
};
|
||||
|
||||
const startOperation = async (isCopy, destFolderId, folderTitle) => {
|
||||
const isProviderFolder = selection.find((x) => !x.providerKey);
|
||||
const items =
|
||||
isProviderFolder && !isCopy
|
||||
? selection.filter((x) => !x.providerKey)
|
||||
: selection;
|
||||
|
||||
let fileIds = [];
|
||||
let folderIds = [];
|
||||
|
||||
for (let item of items) {
|
||||
if (item.fileExst || item.contentLength) {
|
||||
fileIds.push(item.id);
|
||||
} else if (item.id === destFolderId) {
|
||||
toastr.error(t("MoveToFolderMessage"));
|
||||
} else {
|
||||
folderIds.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFolderActions) {
|
||||
fileIds = [];
|
||||
folderIds = [];
|
||||
|
||||
folderIds.push(currentFolderId);
|
||||
}
|
||||
|
||||
if (!folderIds.length && !fileIds.length) return;
|
||||
|
||||
const operationData = {
|
||||
destFolderId,
|
||||
folderIds,
|
||||
fileIds,
|
||||
deleteAfter,
|
||||
isCopy,
|
||||
folderTitle,
|
||||
translations: {
|
||||
copy: t("Common:CopyOperation"),
|
||||
move: t("Translations:MoveToOperation"),
|
||||
},
|
||||
};
|
||||
|
||||
if (!timerId)
|
||||
timerId = setTimeout(() => {
|
||||
setIsLoading(true);
|
||||
}, 500);
|
||||
|
||||
checkFileConflicts(destFolderId, folderIds, fileIds)
|
||||
.then(async (conflicts) => {
|
||||
if (conflicts.length) {
|
||||
setConflictDialogData(conflicts, operationData);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
onClose();
|
||||
const move = !isCopy;
|
||||
if (move) setMovingInProgress(move);
|
||||
await itemOperationToFolder(operationData);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
toastr.error(e);
|
||||
setIsLoading(false);
|
||||
clearActiveOperations(fileIds, folderIds);
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
});
|
||||
};
|
||||
|
||||
// console.log("Operations panel render", expandedKeys);
|
||||
const isVisible =
|
||||
conflictResolveDialogVisible || thirdPartyMoveDialogVisible
|
||||
? false
|
||||
: visible;
|
||||
|
||||
return (
|
||||
<SelectFolderDialog
|
||||
selectionFiles={selection}
|
||||
isDisableTree={isLoading}
|
||||
filteredType="exceptSortedByTags"
|
||||
isPanelVisible={isVisible}
|
||||
onSubmit={onSubmit}
|
||||
onClose={onClose}
|
||||
id={isRecycleBin ? null : currentFolderId}
|
||||
withoutImmediatelyClose
|
||||
dialogName={
|
||||
isRecycleBin
|
||||
? t("Common:Restore")
|
||||
: isCopy
|
||||
? t("Common:Copy")
|
||||
: t("Common:MoveTo")
|
||||
}
|
||||
buttonName={
|
||||
isRecycleBin
|
||||
? t("Common:RestoreHere")
|
||||
: isCopy
|
||||
? t("Translations:CopyHere")
|
||||
: t("Translations:MoveHere")
|
||||
}
|
||||
operationsType={!isCopy || isRecycleBin ? "move" : "copy"}
|
||||
isRecycleBin={isRecycleBin}
|
||||
currentFolderId={currentFolderId}
|
||||
></SelectFolderDialog>
|
||||
);
|
||||
};
|
||||
|
||||
const OperationsPanel = withTranslation(["Translations", "Common", "Files"])(
|
||||
OperationsPanelComponent
|
||||
);
|
||||
|
||||
export default inject(
|
||||
(
|
||||
{
|
||||
filesStore,
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
dialogsStore,
|
||||
filesActionsStore,
|
||||
uploadDataStore,
|
||||
},
|
||||
{ isCopy, isRestore }
|
||||
) => {
|
||||
const {
|
||||
filter,
|
||||
selection,
|
||||
filesList,
|
||||
bufferSelection,
|
||||
setMovingInProgress,
|
||||
} = filesStore;
|
||||
const { isRecycleBinFolder, setExpandedPanelKeys } = treeFoldersStore;
|
||||
const { setConflictDialogData, checkFileConflicts } = filesActionsStore;
|
||||
const { itemOperationToFolder, clearActiveOperations } = uploadDataStore;
|
||||
|
||||
const {
|
||||
moveToPanelVisible,
|
||||
copyPanelVisible,
|
||||
isFolderActions,
|
||||
setCopyPanelVisible,
|
||||
setMoveToPanelVisible,
|
||||
setDestFolderId,
|
||||
setIsFolderActions,
|
||||
conflictResolveDialogVisible,
|
||||
thirdPartyMoveDialogVisible,
|
||||
restoreAllPanelVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
} = dialogsStore;
|
||||
|
||||
const selections = isRestore
|
||||
? filesList
|
||||
: selection.length
|
||||
? selection
|
||||
: [bufferSelection];
|
||||
|
||||
const selectionsWithoutEditing = isRestore
|
||||
? filesList
|
||||
: isCopy
|
||||
? selections
|
||||
: selections.filter((f) => f && !f?.isEditing);
|
||||
|
||||
const provider = selections?.find((x) => x?.providerKey);
|
||||
|
||||
return {
|
||||
currentFolderId: selectedFolderStore.id,
|
||||
parentFolderId: selectedFolderStore.parentId,
|
||||
isRecycleBin: isRecycleBinFolder,
|
||||
filter,
|
||||
visible: copyPanelVisible || moveToPanelVisible || restoreAllPanelVisible,
|
||||
provider,
|
||||
selection: selectionsWithoutEditing,
|
||||
isFolderActions,
|
||||
|
||||
setCopyPanelVisible,
|
||||
setMoveToPanelVisible,
|
||||
setRestoreAllPanelVisible,
|
||||
setDestFolderId,
|
||||
setIsFolderActions,
|
||||
setConflictDialogData,
|
||||
setExpandedPanelKeys,
|
||||
itemOperationToFolder,
|
||||
checkFileConflicts,
|
||||
conflictResolveDialogVisible,
|
||||
clearActiveOperations,
|
||||
thirdPartyMoveDialogVisible,
|
||||
setMovingInProgress,
|
||||
};
|
||||
}
|
||||
)(observer(OperationsPanel));
|
@ -1,116 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyledAsideBody } from "../SelectionPanel/StyledSelectionPanel";
|
||||
import Text from "@docspace/components/text";
|
||||
import SelectFolderInput from "../SelectFolderInput";
|
||||
import Button from "@docspace/components/button";
|
||||
import ModalDialog from "@docspace/components/modal-dialog";
|
||||
import FilesListWrapper from "../SelectionPanel/FilesListWrapper";
|
||||
|
||||
const SelectFileDialogAsideView = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
onSelectFile,
|
||||
files,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
filesListTitle,
|
||||
dialogName,
|
||||
primaryButtonName,
|
||||
theme,
|
||||
onButtonClick,
|
||||
folderId,
|
||||
onSelectFolder,
|
||||
resultingFolderTree,
|
||||
footer,
|
||||
fileId,
|
||||
filteredType,
|
||||
onCloseSelectFolderDialog,
|
||||
onClickInput,
|
||||
isFolderPanelVisible,
|
||||
maxInputWidth,
|
||||
newFilter,
|
||||
}) => {
|
||||
return (
|
||||
<ModalDialog
|
||||
visible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
displayType="aside"
|
||||
withoutBodyScroll
|
||||
withFooterBorder
|
||||
>
|
||||
<ModalDialog.Header>{dialogName}</ModalDialog.Header>
|
||||
<ModalDialog.Body className="select-file_body-modal-dialog">
|
||||
<StyledAsideBody theme={theme}>
|
||||
<div className="selection-panel_aside-body">
|
||||
<div className="selection-panel_folder-info">
|
||||
<Text
|
||||
className="selection-panel_folder-selection-title"
|
||||
fontWeight={600}
|
||||
>
|
||||
{t("Translations:FolderSelection")}
|
||||
</Text>
|
||||
|
||||
<SelectFolderInput
|
||||
theme={theme}
|
||||
onClickInput={onClickInput}
|
||||
onClose={onCloseSelectFolderDialog}
|
||||
onSelectFolder={onSelectFolder}
|
||||
isPanelVisible={isFolderPanelVisible}
|
||||
filteredType={filteredType}
|
||||
withoutProvider={withoutProvider}
|
||||
id={folderId}
|
||||
onSelectFile={onSelectFile}
|
||||
displayType="aside"
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={loadNextPage}
|
||||
files={files}
|
||||
folderTree={resultingFolderTree}
|
||||
isFolderTreeLoading={!!!resultingFolderTree}
|
||||
withFileSelectDialog
|
||||
maxInputWidth={maxInputWidth ? maxInputWidth : "446px"}
|
||||
/>
|
||||
|
||||
<Text color="#A3A9AE" className="selection-panel_aside-title">
|
||||
{filesListTitle}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="selection-panel_files">
|
||||
<FilesListWrapper
|
||||
theme={theme}
|
||||
onSelectFile={onSelectFile}
|
||||
folderId={folderId}
|
||||
displayType="aside"
|
||||
folderSelection={false}
|
||||
fileId={fileId}
|
||||
newFilter={newFilter}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</StyledAsideBody>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<Button
|
||||
theme={theme}
|
||||
primary
|
||||
scale
|
||||
size="normal"
|
||||
label={primaryButtonName}
|
||||
onClick={onButtonClick}
|
||||
isDisabled={!fileId}
|
||||
/>
|
||||
<Button
|
||||
theme={theme}
|
||||
scale
|
||||
size="normal"
|
||||
label={t("Common:CancelButton")}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
export default SelectFileDialogAsideView;
|
@ -1,43 +0,0 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider, inject, observer } from "mobx-react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import SelectFileDialog from "./index";
|
||||
import store from "client/store";
|
||||
import i18n from "./i18n";
|
||||
const { auth: authStore } = store;
|
||||
|
||||
class SelectFileDialogBody extends React.Component {
|
||||
componentDidMount() {
|
||||
const { settings, setFilesSettings } = this.props;
|
||||
settings && setFilesSettings(settings); // Remove after initialization settings in Editor
|
||||
}
|
||||
|
||||
render() {
|
||||
return <SelectFileDialog {...this.props} />;
|
||||
}
|
||||
}
|
||||
const SelectFileWrapper = inject(({ settingsStore }) => {
|
||||
const { setFilesSettings } = settingsStore;
|
||||
|
||||
return {
|
||||
setFilesSettings,
|
||||
};
|
||||
})(observer(SelectFileDialogBody));
|
||||
|
||||
class SelectFileDialogWrapper extends React.Component {
|
||||
componentDidMount() {
|
||||
authStore.init(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider {...store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<SelectFileWrapper {...this.props} />
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFileDialogWrapper;
|
@ -1,39 +0,0 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Backend from "@docspace/common/utils/i18next-http-backend";
|
||||
import { LANGUAGE } from "@docspace/common/constants";
|
||||
import config from "PACKAGE_FILE";
|
||||
import { getCookie } from "@docspace/common/utils";
|
||||
import { loadLanguagePath } from "SRC_DIR/helpers/utils";
|
||||
const newInstance = i18n.createInstance();
|
||||
|
||||
newInstance
|
||||
.use(Backend)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: getCookie(LANGUAGE) || "en",
|
||||
fallbackLng: "en",
|
||||
load: "currentOnly",
|
||||
//debug: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
format: function (value, format) {
|
||||
if (format === "lowercase") return value.toLowerCase();
|
||||
return value;
|
||||
},
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: loadLanguagePath(config.homepage),
|
||||
},
|
||||
|
||||
ns: ["SelectFile", "Common", "Files", "Translations"],
|
||||
defaultNS: "SelectFile",
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default newInstance;
|
@ -1,353 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import throttle from "lodash/throttle";
|
||||
import SelectFileDialogAsideView from "./AsideView";
|
||||
import utils from "@docspace/components/utils";
|
||||
import { FilterType, FolderType } from "@docspace/common/constants";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import SelectionPanel from "../SelectionPanel/SelectionPanelBody";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
|
||||
const { desktop } = utils.device;
|
||||
class SelectFileDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { filter, folderId, fileInfo } = props;
|
||||
|
||||
this.state = {
|
||||
isVisible: false,
|
||||
selectedFileInfo: {},
|
||||
displayType: this.getDisplayType(),
|
||||
isAvailableFolderList: true,
|
||||
selectedFolderId: folderId,
|
||||
selectedFileInfo: fileInfo,
|
||||
};
|
||||
this.throttledResize = throttle(this.setDisplayType, 300);
|
||||
this.newFilter = filter.clone();
|
||||
}
|
||||
|
||||
getFilterParameters = () => {
|
||||
const {
|
||||
isImageOnly,
|
||||
isDocumentsOnly,
|
||||
isArchiveOnly,
|
||||
isPresentationOnly,
|
||||
isTablesOnly,
|
||||
isMediaOnly,
|
||||
searchParam = "",
|
||||
ByExtension,
|
||||
} = this.props;
|
||||
|
||||
if (isImageOnly) {
|
||||
return { filterType: FilterType.ImagesOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isDocumentsOnly) {
|
||||
return { filterType: FilterType.DocumentsOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isArchiveOnly) {
|
||||
return { filterType: FilterType.ArchiveOnly, filterValue: searchParam };
|
||||
}
|
||||
if (isPresentationOnly) {
|
||||
return {
|
||||
filterType: FilterType.PresentationsOnly,
|
||||
filterValue: searchParam,
|
||||
};
|
||||
}
|
||||
if (isTablesOnly) {
|
||||
return {
|
||||
filterType: FilterType.SpreadsheetsOnly,
|
||||
filterValue: searchParam,
|
||||
};
|
||||
}
|
||||
if (isMediaOnly) {
|
||||
return { filterType: FilterType.MediaOnly, filterValue: searchParam };
|
||||
}
|
||||
|
||||
if (ByExtension) {
|
||||
return { filterType: FilterType.ByExtension, filterValue: searchParam };
|
||||
}
|
||||
|
||||
return { filterType: FilterType.FilesOnly, filterValue: "" };
|
||||
};
|
||||
|
||||
setFilter = () => {
|
||||
const { withSubfolders = true } = this.props;
|
||||
|
||||
const filterParams = this.getFilterParameters();
|
||||
this.newFilter.filterType = filterParams.filterType;
|
||||
this.newFilter.search = filterParams.filterValue;
|
||||
this.newFilter.withSubfolders = withSubfolders;
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
const {
|
||||
filteredType,
|
||||
onSelectFolder,
|
||||
passedFoldersTree,
|
||||
displayType,
|
||||
folderId,
|
||||
withoutBasicSelection,
|
||||
} = this.props;
|
||||
|
||||
!displayType && window.addEventListener("resize", this.throttledResize);
|
||||
|
||||
this.setFilter();
|
||||
|
||||
let resultingFolderTree, resultingId;
|
||||
|
||||
const treeFolders = await this.props.fetchTreeFolders();
|
||||
const roomsFolder = treeFolders.find(
|
||||
(f) => f.rootFolderType == FolderType.Rooms
|
||||
);
|
||||
const hasSharedFolder =
|
||||
roomsFolder && roomsFolder.foldersCount ? true : false;
|
||||
|
||||
try {
|
||||
[
|
||||
resultingFolderTree,
|
||||
resultingId,
|
||||
] = await SelectionPanel.getBasicFolderInfo(
|
||||
treeFolders,
|
||||
filteredType,
|
||||
folderId,
|
||||
passedFoldersTree,
|
||||
hasSharedFolder
|
||||
);
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tree = resultingFolderTree;
|
||||
|
||||
if (tree.length === 0) {
|
||||
this.setState({ isAvailable: false });
|
||||
onSelectFolder && onSelectFolder(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!withoutBasicSelection) {
|
||||
onSelectFolder && onSelectFolder(resultingId);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
resultingFolderTree: tree,
|
||||
selectedFolderId: resultingId,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!isEqual(prevProps, this.props)) {
|
||||
this.setFilter();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.throttledResize && this.throttledResize.cancel();
|
||||
window.removeEventListener("resize", this.throttledResize);
|
||||
}
|
||||
|
||||
getDisplayType = () => {
|
||||
const displayType =
|
||||
window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "modal";
|
||||
|
||||
return displayType;
|
||||
};
|
||||
|
||||
setDisplayType = () => {
|
||||
const displayType = this.getDisplayType();
|
||||
|
||||
this.setState({ displayType: displayType });
|
||||
};
|
||||
|
||||
onClickInput = () => {
|
||||
this.setState({
|
||||
isVisible: true,
|
||||
});
|
||||
};
|
||||
|
||||
onCloseSelectFolderDialog = () => {
|
||||
this.setState({
|
||||
isVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
onSelectFolder = (folder) => {
|
||||
const { displayType } = this.state;
|
||||
const { folderId } = this.props;
|
||||
const id = displayType === "aside" ? folder : folder[0];
|
||||
|
||||
if (id !== folderId) {
|
||||
this.setState({
|
||||
selectedFolderId: id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onSelectFile = (item, index) => {
|
||||
this.setState({
|
||||
selectedFileInfo: item,
|
||||
});
|
||||
};
|
||||
|
||||
onClickSave = () => {
|
||||
const { onClose, onSelectFile, setFile, setFolderId } = this.props;
|
||||
const { selectedFileInfo, selectedFolderId } = this.state;
|
||||
|
||||
setFile(selectedFileInfo);
|
||||
|
||||
if (selectedFileInfo.folderId.toString() === selectedFolderId.toString()) {
|
||||
setFolderId(selectedFolderId);
|
||||
}
|
||||
|
||||
onSelectFile && onSelectFile(selectedFileInfo);
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
filteredType,
|
||||
withoutProvider,
|
||||
filesListTitle,
|
||||
theme,
|
||||
header,
|
||||
footer,
|
||||
dialogName,
|
||||
creationButtonPrimary,
|
||||
maxInputWidth,
|
||||
} = this.props;
|
||||
const {
|
||||
isVisible,
|
||||
displayType,
|
||||
isAvailableFolderList,
|
||||
resultingFolderTree,
|
||||
isLoadingData,
|
||||
selectedFileInfo,
|
||||
selectedFolderId,
|
||||
} = this.state;
|
||||
|
||||
const buttonName = creationButtonPrimary
|
||||
? t("Common:Create")
|
||||
: t("Common:SaveButton");
|
||||
const name = dialogName ? dialogName : t("Common:SelectFile");
|
||||
|
||||
// console.log("Render file-component");
|
||||
return displayType === "aside" ? (
|
||||
<SelectFileDialogAsideView
|
||||
t={t}
|
||||
theme={theme}
|
||||
isPanelVisible={isPanelVisible}
|
||||
isFolderPanelVisible={isVisible}
|
||||
onClose={onClose}
|
||||
withoutProvider={withoutProvider}
|
||||
folderId={selectedFolderId}
|
||||
resultingFolderTree={resultingFolderTree}
|
||||
onButtonClick={this.onClickSave}
|
||||
header={header}
|
||||
dialogName={name}
|
||||
footer={footer}
|
||||
isLoadingData={isLoadingData}
|
||||
primaryButtonName={buttonName}
|
||||
isAvailable={isAvailableFolderList}
|
||||
onSelectFolder={this.onSelectFolder}
|
||||
onSelectFile={this.onSelectFile}
|
||||
filesListTitle={filesListTitle}
|
||||
fileId={selectedFileInfo.id}
|
||||
newFilter={this.newFilter}
|
||||
filteredType={filteredType}
|
||||
onClickInput={this.onClickInput}
|
||||
onCloseSelectFolderDialog={this.onCloseSelectFolderDialog}
|
||||
maxInputWidth={maxInputWidth}
|
||||
/>
|
||||
) : (
|
||||
<SelectionPanel
|
||||
t={t}
|
||||
theme={theme}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
withoutProvider={withoutProvider}
|
||||
folderId={selectedFolderId}
|
||||
resultingFolderTree={resultingFolderTree}
|
||||
onButtonClick={this.onClickSave}
|
||||
header={header}
|
||||
dialogName={name}
|
||||
footer={footer}
|
||||
isLoadingData={isLoadingData}
|
||||
primaryButtonName={buttonName}
|
||||
isAvailable={isAvailableFolderList}
|
||||
onSelectFolder={this.onSelectFolder}
|
||||
onSelectFile={this.onSelectFile}
|
||||
filesListTitle={filesListTitle}
|
||||
fileId={selectedFileInfo.id}
|
||||
newFilter={this.newFilter}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
SelectFileDialog.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isPanelVisible: PropTypes.bool.isRequired,
|
||||
onSelectFile: PropTypes.func.isRequired,
|
||||
filteredType: PropTypes.oneOf([
|
||||
"exceptSortedByTags",
|
||||
"exceptPrivacyTrashArchiveFolders",
|
||||
]),
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
withoutProvider: PropTypes.bool,
|
||||
headerName: PropTypes.string,
|
||||
filesListTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
};
|
||||
|
||||
SelectFileDialog.defaultProps = {
|
||||
id: "",
|
||||
filesListTitle: "",
|
||||
withoutProvider: false,
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({
|
||||
auth,
|
||||
filesStore,
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
selectFileDialogStore,
|
||||
}) => {
|
||||
const {
|
||||
folderId: id,
|
||||
fileInfo,
|
||||
setFolderId,
|
||||
setFile,
|
||||
} = selectFileDialogStore;
|
||||
|
||||
const { setExpandedPanelKeys, fetchTreeFolders } = treeFoldersStore;
|
||||
const { filter } = filesStore;
|
||||
const { id: storeFolderId } = selectedFolderStore;
|
||||
|
||||
const { settingsStore } = auth;
|
||||
const { theme } = settingsStore;
|
||||
const folderId = id ? id : storeFolderId;
|
||||
|
||||
return {
|
||||
fileInfo,
|
||||
setFile,
|
||||
setFolderId,
|
||||
filter,
|
||||
storeFolderId,
|
||||
|
||||
folderId,
|
||||
theme: theme,
|
||||
setExpandedPanelKeys,
|
||||
fetchTreeFolders,
|
||||
};
|
||||
}
|
||||
)(
|
||||
observer(
|
||||
withTranslation(["SelectFile", "Common", "Translations"])(SelectFileDialog)
|
||||
)
|
||||
);
|
@ -1,9 +1,10 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import SelectFileDialog from "../SelectFileDialog";
|
||||
|
||||
import StyledComponent from "./StyledSelectFileInput";
|
||||
import SimpleFileInput from "../../SimpleFileInput";
|
||||
import FilesSelector from "SRC_DIR/components/FilesSelector";
|
||||
|
||||
class SelectFileInput extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
@ -46,7 +47,7 @@ class SelectFileInput extends React.PureComponent {
|
||||
/>
|
||||
|
||||
{isPanelVisible && (
|
||||
<SelectFileDialog
|
||||
<FilesSelector
|
||||
{...rest}
|
||||
id={folderId}
|
||||
isPanelVisible={isPanelVisible}
|
||||
@ -71,10 +72,13 @@ SelectFileInput.defaultProps = {
|
||||
};
|
||||
|
||||
export default inject(
|
||||
({ clientLoadingStore, treeFoldersStore, selectFileDialogStore }) => {
|
||||
(
|
||||
{ clientLoadingStore, treeFoldersStore, selectFileDialogStore },
|
||||
{ fileName: fileNameProps }
|
||||
) => {
|
||||
const { setFirstLoad } = clientLoadingStore;
|
||||
const { folderId, setFolderId, setFile, fileInfo } = selectFileDialogStore;
|
||||
const fileName = fileInfo?.title;
|
||||
const fileName = fileInfo?.title || fileNameProps;
|
||||
const { setExpandedPanelKeys } = treeFoldersStore;
|
||||
return {
|
||||
setFirstLoad,
|
||||
|
@ -1,129 +0,0 @@
|
||||
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
|
||||
import React from "react";
|
||||
import IconButton from "@docspace/components/icon-button";
|
||||
import FolderTreeBody from "../../FolderTreeBody";
|
||||
import Button from "@docspace/components/button";
|
||||
import ModalDialog from "@docspace/components/modal-dialog";
|
||||
import {
|
||||
StyledAsideBody,
|
||||
StyledAsideHeader,
|
||||
} from "../SelectionPanel/StyledSelectionPanel";
|
||||
import Text from "@docspace/components/text";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
const StyledModalDialog = styled(ModalDialog)`
|
||||
.modal-dialog-aside-body {
|
||||
padding-top: 12px;
|
||||
}
|
||||
`;
|
||||
const SelectFolderDialogAsideView = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
withFileSelectDialog,
|
||||
isAvailable,
|
||||
folderId,
|
||||
isLoadingData,
|
||||
resultingFolderTree,
|
||||
onSelectFolder,
|
||||
footer,
|
||||
onButtonClick,
|
||||
dialogName,
|
||||
header,
|
||||
primaryButtonName,
|
||||
isDisableTree,
|
||||
isDisableButton,
|
||||
parentId,
|
||||
selectionFiles,
|
||||
}) => {
|
||||
const isLoaded = folderId && resultingFolderTree;
|
||||
return (
|
||||
<StyledModalDialog
|
||||
visible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
withoutBodyScroll
|
||||
withFooterBorder
|
||||
displayType="aside"
|
||||
isDoubleFooterLine
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
<StyledAsideHeader>
|
||||
{withFileSelectDialog && (
|
||||
<IconButton
|
||||
className="selection-panel_aside-header-icon"
|
||||
size="16"
|
||||
iconName={ArrowPathReactSvgUrl}
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
{dialogName}
|
||||
</StyledAsideHeader>
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body>
|
||||
<StyledAsideBody header={!!header} footer={!!footer}>
|
||||
<div className="selection-panel_aside-body">
|
||||
<div className="selection-panel_aside-header">
|
||||
<div>{header}</div>
|
||||
{isLoaded ? (
|
||||
<Text fontWeight="700" fontSize="18px">
|
||||
{t("Common:Rooms")}
|
||||
</Text>
|
||||
) : (
|
||||
<Loaders.Rectangle
|
||||
className="selection-panel_header-loader"
|
||||
width="83px"
|
||||
height="24px"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="selection-panel_aside-tree">
|
||||
{isLoaded ? (
|
||||
<FolderTreeBody
|
||||
selectionFiles={selectionFiles}
|
||||
parentId={parentId}
|
||||
folderTree={resultingFolderTree}
|
||||
onSelect={onSelectFolder}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders
|
||||
isAvailable={isAvailable}
|
||||
selectedKeys={[`${folderId}`]}
|
||||
isDisableTree={isDisableTree}
|
||||
displayType="aside"
|
||||
/>
|
||||
) : (
|
||||
<div className="selection-panel_aside-loader">
|
||||
<Loaders.NewTreeFolders />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</StyledAsideBody>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
{footer}
|
||||
<div>
|
||||
<Button
|
||||
className="select-folder-dialog-buttons-save"
|
||||
primary
|
||||
scale
|
||||
size="normal"
|
||||
label={primaryButtonName}
|
||||
onClick={onButtonClick}
|
||||
isDisabled={isDisableButton}
|
||||
/>
|
||||
<Button
|
||||
size="normal"
|
||||
scale
|
||||
label={t("Common:CancelButton")}
|
||||
onClick={onClose}
|
||||
isDisabled={isLoadingData}
|
||||
/>
|
||||
</div>
|
||||
</ModalDialog.Footer>
|
||||
</StyledModalDialog>
|
||||
);
|
||||
};
|
||||
export default SelectFolderDialogAsideView;
|
@ -1,87 +0,0 @@
|
||||
import React from "react";
|
||||
import { Provider as MobxProvider, inject, observer } from "mobx-react";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import store from "client/store";
|
||||
import SelectFolderDialog from "./index";
|
||||
import i18n from "./i18n";
|
||||
import { getFolder } from "@docspace/common/api/files";
|
||||
const { auth: authStore } = store;
|
||||
|
||||
class SelectFolderDialogBody extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
pathParts: [],
|
||||
parentId: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { setExpandedPanelKeys, setParentId, folderId } = this.props;
|
||||
|
||||
if (folderId) {
|
||||
folderId &&
|
||||
getFolder(folderId)
|
||||
.then((data) => {
|
||||
const pathParts = data.pathParts.map((item) => item.toString());
|
||||
pathParts?.pop();
|
||||
|
||||
setExpandedPanelKeys(pathParts);
|
||||
setParentId(data.current.parentId);
|
||||
|
||||
this.setState({
|
||||
pathParts: pathParts,
|
||||
parentId: data.current.parentId,
|
||||
});
|
||||
})
|
||||
.catch((e) => toastr.error(e));
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isPanelVisible, setParentId, setExpandedPanelKeys } = this.props;
|
||||
const { pathParts, parentId } = this.state;
|
||||
if (isPanelVisible && isPanelVisible !== prevProps.isPanelVisible) {
|
||||
setExpandedPanelKeys(pathParts);
|
||||
setParentId(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { folderId, isPanelVisible } = this.props;
|
||||
const { pathParts, parentId } = this.state;
|
||||
return (
|
||||
isPanelVisible && (
|
||||
<SelectFolderDialog {...this.props} selectedId={folderId} />
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
const SelectFolderModalWrapper = inject(
|
||||
({ treeFoldersStore, selectedFolderStore }) => {
|
||||
const { setExpandedPanelKeys } = treeFoldersStore;
|
||||
const { setParentId } = selectedFolderStore;
|
||||
return {
|
||||
setExpandedPanelKeys,
|
||||
setParentId,
|
||||
};
|
||||
}
|
||||
)(observer(SelectFolderDialogBody));
|
||||
|
||||
class SelectFolderModal extends React.Component {
|
||||
componentDidMount() {
|
||||
authStore.init(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MobxProvider {...store}>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<SelectFolderModalWrapper {...this.props} />
|
||||
</I18nextProvider>
|
||||
</MobxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectFolderModal;
|
@ -1,374 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import throttle from "lodash/throttle";
|
||||
import SelectFolderDialogAsideView from "./AsideView";
|
||||
import utils from "@docspace/components/utils";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import SelectionPanel from "../SelectionPanel/SelectionPanelBody";
|
||||
import { FilterType, FolderType } from "@docspace/common/constants";
|
||||
|
||||
const { desktop } = utils.device;
|
||||
|
||||
let treeFolders = [];
|
||||
let selectedTreeNode = null;
|
||||
class SelectFolderDialog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { id, displayType, filter } = this.props;
|
||||
this.newFilter = filter.clone();
|
||||
this.newFilter.filterType = FilterType.FilesOnly;
|
||||
this.newFilter.withSubfolders = false;
|
||||
this.state = {
|
||||
isLoadingData: false,
|
||||
displayType: displayType || this.getDisplayType(),
|
||||
isAvailable: true,
|
||||
};
|
||||
this.throttledResize = throttle(this.setDisplayType, 300);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const {
|
||||
filteredType,
|
||||
onSetBaseFolderPath,
|
||||
onSelectFolder,
|
||||
passedFoldersTree,
|
||||
displayType,
|
||||
withFileSelectDialog = false,
|
||||
folderTree,
|
||||
setResultingFolderId,
|
||||
selectFolderInputExist,
|
||||
id,
|
||||
storeFolderId,
|
||||
withoutBasicSelection,
|
||||
setResultingFoldersTree,
|
||||
} = this.props;
|
||||
|
||||
!displayType && window.addEventListener("resize", this.throttledResize);
|
||||
|
||||
const initialFolderId = selectFolderInputExist ? id : storeFolderId;
|
||||
|
||||
let resultingFolderTree, resultingId;
|
||||
|
||||
if (!withFileSelectDialog) {
|
||||
treeFolders = await this.props.fetchTreeFolders();
|
||||
|
||||
const roomsFolder = treeFolders.find(
|
||||
(f) => f.rootFolderType == FolderType.Rooms
|
||||
);
|
||||
|
||||
const hasSharedFolder =
|
||||
roomsFolder && roomsFolder.foldersCount ? true : false;
|
||||
|
||||
try {
|
||||
[
|
||||
resultingFolderTree,
|
||||
resultingId,
|
||||
] = await SelectionPanel.getBasicFolderInfo(
|
||||
treeFolders,
|
||||
filteredType,
|
||||
initialFolderId,
|
||||
passedFoldersTree,
|
||||
hasSharedFolder
|
||||
);
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const tree = withFileSelectDialog ? folderTree : resultingFolderTree;
|
||||
|
||||
if (tree.length === 0) {
|
||||
this.setState({ isAvailable: false });
|
||||
onSelectFolder && onSelectFolder(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setResultingFoldersTree(tree);
|
||||
|
||||
const resId = withFileSelectDialog ? id : resultingId;
|
||||
|
||||
if (!withoutBasicSelection) {
|
||||
onSelectFolder && onSelectFolder(resId);
|
||||
onSetBaseFolderPath && onSetBaseFolderPath(resId);
|
||||
}
|
||||
|
||||
setResultingFolderId(resId);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { toDefault } = this.props;
|
||||
|
||||
if (this.throttledResize) {
|
||||
this.throttledResize && this.throttledResize.cancel();
|
||||
window.removeEventListener("resize", this.throttledResize);
|
||||
}
|
||||
|
||||
toDefault();
|
||||
}
|
||||
getDisplayType = () => {
|
||||
const displayType =
|
||||
window.innerWidth < desktop.match(/\d+/)[0] ? "aside" : "modal";
|
||||
|
||||
return displayType;
|
||||
};
|
||||
|
||||
setDisplayType = () => {
|
||||
const displayType = this.getDisplayType();
|
||||
|
||||
this.setState({ displayType: displayType });
|
||||
};
|
||||
|
||||
onSelect = async (folder, treeNode) => {
|
||||
const {
|
||||
setResultingFolderId,
|
||||
resultingFolderId,
|
||||
onSelectTreeNode,
|
||||
setItemSecurity,
|
||||
} = this.props;
|
||||
|
||||
if (+resultingFolderId === +folder[0]) return;
|
||||
if (!!onSelectTreeNode) selectedTreeNode = treeNode;
|
||||
|
||||
setResultingFolderId(folder[0]);
|
||||
|
||||
setItemSecurity(treeNode.security);
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
const {
|
||||
setExpandedPanelKeys,
|
||||
onClose,
|
||||
|
||||
selectFolderInputExist,
|
||||
withFileSelectDialog,
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
!treeFolders.length &&
|
||||
!selectFolderInputExist &&
|
||||
!withFileSelectDialog
|
||||
) {
|
||||
setExpandedPanelKeys(null);
|
||||
}
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
onCloseAside = () => {
|
||||
const { onClose } = this.props;
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
onButtonClick = (e) => {
|
||||
const {
|
||||
onSave,
|
||||
onSetNewFolderPath,
|
||||
onSelectFolder,
|
||||
onSelectTreeNode,
|
||||
withoutImmediatelyClose,
|
||||
onSubmit,
|
||||
|
||||
providerKey,
|
||||
folderTitle,
|
||||
resultingFolderId,
|
||||
setSelectedItems,
|
||||
} = this.props;
|
||||
|
||||
setSelectedItems();
|
||||
|
||||
onSubmit && onSubmit(resultingFolderId, folderTitle, providerKey);
|
||||
onSave && onSave(e, resultingFolderId);
|
||||
onSetNewFolderPath && onSetNewFolderPath(resultingFolderId);
|
||||
onSelectFolder && onSelectFolder(resultingFolderId);
|
||||
onSelectTreeNode && onSelectTreeNode(selectedTreeNode);
|
||||
//setResultingFolderId(resultingFolderId);
|
||||
!withoutImmediatelyClose && this.onClose();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
isPanelVisible,
|
||||
zIndex,
|
||||
withoutProvider,
|
||||
withFileSelectDialog,
|
||||
header,
|
||||
dialogName,
|
||||
footer,
|
||||
buttonName,
|
||||
isDisableTree,
|
||||
resultingFolderId,
|
||||
folderTitle,
|
||||
expandedKeys,
|
||||
isDisableButton,
|
||||
currentFolderId,
|
||||
selectionFiles,
|
||||
resultingFolderTree,
|
||||
operationsType,
|
||||
securityItem,
|
||||
} = this.props;
|
||||
const { displayType, isLoadingData, isAvailable } = this.state;
|
||||
|
||||
const primaryButtonName = buttonName
|
||||
? buttonName
|
||||
: t("Common:SaveHereButton");
|
||||
const name = dialogName ? dialogName : t("Common:SaveButton");
|
||||
|
||||
const isUnavailableTree = isDisableTree || !isAvailable;
|
||||
const isDisableMoving =
|
||||
currentFolderId?.toString() === resultingFolderId?.toString() ||
|
||||
!securityItem.MoveTo;
|
||||
|
||||
const isDisabledOperations =
|
||||
operationsType === "move" ? isDisableMoving : !securityItem.CopyTo;
|
||||
|
||||
const buttonIsDisabled =
|
||||
isDisabledOperations ||
|
||||
isLoadingData ||
|
||||
isUnavailableTree ||
|
||||
isDisableButton ||
|
||||
(selectionFiles && selectionFiles[0])?.parentId === +resultingFolderId;
|
||||
|
||||
return displayType === "aside" ? (
|
||||
<SelectFolderDialogAsideView
|
||||
selectionFiles={selectionFiles}
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
zIndex={zIndex}
|
||||
onClose={this.onCloseAside}
|
||||
withoutProvider={withoutProvider}
|
||||
withFileSelectDialog={withFileSelectDialog}
|
||||
certainFolders={true}
|
||||
folderId={resultingFolderId}
|
||||
resultingFolderTree={resultingFolderTree}
|
||||
onSelectFolder={this.onSelect}
|
||||
onButtonClick={this.onButtonClick}
|
||||
header={header}
|
||||
dialogName={
|
||||
withFileSelectDialog ? t("Translations:FolderSelection") : name
|
||||
}
|
||||
footer={footer}
|
||||
isLoadingData={isLoadingData}
|
||||
primaryButtonName={
|
||||
withFileSelectDialog ? t("Common:SelectAction") : primaryButtonName
|
||||
}
|
||||
isAvailable={isAvailable}
|
||||
isDisableTree={isDisableTree}
|
||||
isDisableButton={buttonIsDisabled}
|
||||
/>
|
||||
) : (
|
||||
<SelectionPanel
|
||||
selectionFiles={selectionFiles}
|
||||
t={t}
|
||||
isPanelVisible={isPanelVisible}
|
||||
onClose={this.onClose}
|
||||
withoutProvider={withoutProvider}
|
||||
folderId={resultingFolderId}
|
||||
resultingFolderTree={resultingFolderTree}
|
||||
onButtonClick={this.onButtonClick}
|
||||
header={header}
|
||||
dialogName={name}
|
||||
footer={footer}
|
||||
isLoadingData={isLoadingData}
|
||||
primaryButtonName={primaryButtonName}
|
||||
isAvailable={isAvailable}
|
||||
onSelectFolder={this.onSelect}
|
||||
folderTitle={folderTitle}
|
||||
expandedKeys={expandedKeys}
|
||||
isDisableTree={isDisableTree}
|
||||
folderSelection
|
||||
newFilter={this.newFilter}
|
||||
isDisableButton={buttonIsDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SelectFolderDialog.propTypes = {
|
||||
onSelectFolder: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
isPanelVisible: PropTypes.bool.isRequired,
|
||||
filteredType: PropTypes.oneOf([
|
||||
"exceptSortedByTags",
|
||||
"exceptPrivacyTrashArchiveFolders",
|
||||
"",
|
||||
]),
|
||||
displayType: PropTypes.oneOf(["aside", "modal"]),
|
||||
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
withoutProvider: PropTypes.bool,
|
||||
withoutImmediatelyClose: PropTypes.bool,
|
||||
isDisableTree: PropTypes.bool,
|
||||
operationsType: PropTypes.oneOf(["copy", "move"]),
|
||||
};
|
||||
SelectFolderDialog.defaultProps = {
|
||||
id: "",
|
||||
withoutProvider: false,
|
||||
withoutImmediatelyClose: false,
|
||||
isDisableTree: false,
|
||||
filteredType: "",
|
||||
};
|
||||
|
||||
export default inject(
|
||||
(
|
||||
{
|
||||
treeFoldersStore,
|
||||
selectedFolderStore,
|
||||
selectFolderDialogStore,
|
||||
filesStore,
|
||||
filesActionsStore,
|
||||
},
|
||||
{ selectedId }
|
||||
) => {
|
||||
const { setExpandedPanelKeys, fetchTreeFolders } = treeFoldersStore;
|
||||
|
||||
const { filter } = filesStore;
|
||||
const { setSelectedItems } = filesActionsStore;
|
||||
|
||||
const { id } = selectedFolderStore;
|
||||
const {
|
||||
setResultingFolderId,
|
||||
setFolderTitle,
|
||||
setProviderKey,
|
||||
providerKey,
|
||||
folderTitle,
|
||||
resultingFolderId,
|
||||
setIsLoading,
|
||||
resultingFolderTree,
|
||||
setResultingFoldersTree,
|
||||
toDefault,
|
||||
setItemSecurity,
|
||||
securityItem,
|
||||
} = selectFolderDialogStore;
|
||||
|
||||
const selectedFolderId = selectedId ? selectedId : id;
|
||||
|
||||
return {
|
||||
storeFolderId: selectedFolderId,
|
||||
providerKey,
|
||||
folderTitle,
|
||||
resultingFolderId,
|
||||
setExpandedPanelKeys,
|
||||
setResultingFolderId,
|
||||
setFolderTitle,
|
||||
setProviderKey,
|
||||
filter,
|
||||
setSelectedItems,
|
||||
fetchTreeFolders,
|
||||
setIsLoading,
|
||||
resultingFolderTree,
|
||||
toDefault,
|
||||
setResultingFoldersTree,
|
||||
setItemSecurity,
|
||||
securityItem,
|
||||
};
|
||||
}
|
||||
)(
|
||||
observer(
|
||||
withTranslation(["SelectFolder", "Common", "Translations"])(
|
||||
SelectFolderDialog
|
||||
)
|
||||
)
|
||||
);
|
@ -4,10 +4,11 @@ import PropTypes from "prop-types";
|
||||
import StyledComponent from "./StyledSelectFolderInput";
|
||||
import { getFolder, getFolderPath } from "@docspace/common/api/files";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import SelectFolderDialog from "../SelectFolderDialog";
|
||||
|
||||
import SimpleFileInput from "../../SimpleFileInput";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { FolderType } from "@docspace/common/constants";
|
||||
import FilesSelector from "SRC_DIR/components/FilesSelector";
|
||||
class SelectFolderInput extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@ -162,8 +163,8 @@ class SelectFolderInput extends React.PureComponent {
|
||||
placeholder={placeholder}
|
||||
isDisabled={isFolderTreeLoading || isDisabled || isLoading}
|
||||
/>
|
||||
{isReady && (
|
||||
<SelectFolderDialog
|
||||
{isReady && isPanelVisible && (
|
||||
<FilesSelector
|
||||
{...rest}
|
||||
selectFolderInputExist
|
||||
isPanelVisible={isPanelVisible}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { FolderType } from "@docspace/common/constants";
|
||||
|
||||
export const exceptSortedByTagsFolders = [
|
||||
FolderType.Recent,
|
||||
FolderType.TRASH,
|
||||
FolderType.Favorites,
|
||||
FolderType.Privacy,
|
||||
FolderType.Archive,
|
||||
];
|
||||
|
||||
export const exceptPrivacyTrashArchiveFolders = [
|
||||
FolderType.Privacy,
|
||||
FolderType.TRASH,
|
||||
FolderType.Archive,
|
||||
];
|
@ -1,211 +0,0 @@
|
||||
import EmptyScreenReactSvgUrl from "PUBLIC_DIR/images/empty.screen.react.svg?url";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import Loader from "@docspace/components/loader";
|
||||
import Text from "@docspace/components/text";
|
||||
import { useTranslation, withTranslation } from "react-i18next";
|
||||
import CustomScrollbarsVirtualList from "@docspace/components/scrollbar/custom-scrollbars-virtual-list";
|
||||
import InfiniteLoader from "react-window-infinite-loader";
|
||||
import AutoSizer from "react-virtualized-auto-sizer";
|
||||
import { FixedSizeList as List } from "react-window";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import FilesListRow from "./FilesListRow";
|
||||
import EmptyContainer from "../../EmptyContainer/EmptyContainer";
|
||||
import { StyledItemsLoader } from "../SelectionPanel/StyledSelectionPanel";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
|
||||
let countLoad, timerId;
|
||||
const FilesListBody = ({
|
||||
files,
|
||||
onSelectFile,
|
||||
loadNextPage,
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
displayType,
|
||||
folderId,
|
||||
fileId,
|
||||
page,
|
||||
folderSelection,
|
||||
getIcon,
|
||||
maxHeight = 384,
|
||||
}) => {
|
||||
const { t } = useTranslation(["SelectFile", "Common"]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const filesListRef = useRef(null);
|
||||
if (page === 0) {
|
||||
countLoad = 0;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (filesListRef && filesListRef.current) {
|
||||
filesListRef.current.resetloadMoreItemsCache(true);
|
||||
}
|
||||
}, [folderId, page, displayType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNextPageLoading) {
|
||||
timerId = setTimeout(() => {
|
||||
setIsLoading(true);
|
||||
}, 500);
|
||||
} else {
|
||||
isLoading && setIsLoading(false);
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
}, [isNextPageLoading]);
|
||||
|
||||
// If there are more items to be loaded then add an extra row to hold a loading indicator.
|
||||
const itemCount = hasNextPage ? files.length + 1 : files.length;
|
||||
|
||||
// Every row is loaded except for our loading indicator row.
|
||||
const isItemLoaded = useCallback(
|
||||
(index) => {
|
||||
const isLoaded = !hasNextPage || index < files.length;
|
||||
if (isLoaded) {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
return isLoaded;
|
||||
},
|
||||
[files, hasNextPage]
|
||||
);
|
||||
|
||||
const loadMoreItems = useCallback(() => {
|
||||
if (folderId && page == 0 && isNextPageLoading) {
|
||||
loadNextPage && loadNextPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNextPageLoading) return;
|
||||
countLoad++;
|
||||
|
||||
folderId && loadNextPage && loadNextPage();
|
||||
}, [isNextPageLoading, files, displayType, folderId]);
|
||||
|
||||
const renderPageLoader = (style) => {
|
||||
return (
|
||||
<div style={style}>
|
||||
<StyledItemsLoader key="loader">
|
||||
<Loader type="oval" size="16px" className="panel-loader" />
|
||||
<Text as="span">
|
||||
{t("Common:LoadingProcessing")} {t("Common:LoadingDescription")}
|
||||
</Text>
|
||||
</StyledItemsLoader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderFirstLoader = useCallback(
|
||||
(style) => {
|
||||
return (
|
||||
<div style={style}>
|
||||
<div className="selection-panel_loader" key="loader">
|
||||
{isLoading ? <Loaders.ListLoader withoutFirstRectangle /> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[isLoading]
|
||||
);
|
||||
|
||||
const isFileChecked = useCallback(
|
||||
(id) => {
|
||||
const checked = fileId ? id === fileId : false;
|
||||
return checked;
|
||||
},
|
||||
[fileId]
|
||||
);
|
||||
|
||||
const Item = useCallback(
|
||||
({ index, style }) => {
|
||||
const isLoaded = isItemLoaded(index);
|
||||
|
||||
if (!isLoaded || !folderId) {
|
||||
if (countLoad >= 1) {
|
||||
return renderPageLoader(style);
|
||||
}
|
||||
|
||||
return renderFirstLoader(style);
|
||||
}
|
||||
|
||||
const item = files[index];
|
||||
const isChecked = folderSelection ? false : isFileChecked(item.id);
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<FilesListRow
|
||||
displayType={displayType}
|
||||
index={index}
|
||||
onSelectFile={onSelectFile}
|
||||
item={item}
|
||||
isChecked={isChecked}
|
||||
folderSelection={folderSelection}
|
||||
icon={getIcon(
|
||||
32,
|
||||
item.fileExst,
|
||||
item.providerKey,
|
||||
item.contentLength
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[files, fileId, displayType, renderFirstLoader, renderPageLoader]
|
||||
);
|
||||
return (
|
||||
<div className="selection-panel_files-list-body">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<InfiniteLoader
|
||||
ref={filesListRef}
|
||||
isItemLoaded={isItemLoaded}
|
||||
itemCount={itemCount}
|
||||
loadMoreItems={loadMoreItems}
|
||||
>
|
||||
{({ onItemsRendered, ref }) => (
|
||||
<List
|
||||
height={maxHeight}
|
||||
itemCount={itemCount}
|
||||
itemSize={48}
|
||||
onItemsRendered={onItemsRendered}
|
||||
ref={ref}
|
||||
width={width + 8}
|
||||
outerElementType={CustomScrollbarsVirtualList}
|
||||
>
|
||||
{Item}
|
||||
</List>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
{!hasNextPage && itemCount === 0 && (
|
||||
<div className="select-file-dialog_empty-container">
|
||||
<EmptyContainer
|
||||
headerText={t("Files:EmptyFolderHeader")}
|
||||
imageSrc={EmptyScreenReactSvgUrl}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
FilesListBody.defaultProps = {
|
||||
listHeight: 300,
|
||||
isMultiSelect: false,
|
||||
};
|
||||
|
||||
export default inject(({ auth, settingsStore }) => {
|
||||
const { user } = auth.userStore;
|
||||
const { getIcon } = settingsStore;
|
||||
return {
|
||||
viewer: user,
|
||||
getIcon,
|
||||
};
|
||||
})(observer(withTranslation(["Common", "Files"])(FilesListBody)));
|
@ -1,51 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyledRow } from "./StyledSelectionPanel";
|
||||
import Text from "@docspace/components/text";
|
||||
import RadioButton from "@docspace/components/radio-button";
|
||||
import ItemIcon from "../../ItemIcon";
|
||||
const FilesListRow = ({
|
||||
displayType,
|
||||
index,
|
||||
onSelectFile,
|
||||
isChecked,
|
||||
folderSelection,
|
||||
icon,
|
||||
item,
|
||||
}) => {
|
||||
const { id, fileExst, title } = item;
|
||||
const element = <ItemIcon id={id} icon={icon} fileExst={fileExst} />;
|
||||
|
||||
const onFileClick = () => {
|
||||
onSelectFile && onSelectFile(item, index);
|
||||
};
|
||||
return (
|
||||
<StyledRow
|
||||
displayType={displayType}
|
||||
isChecked={isChecked}
|
||||
folderSelection={folderSelection}
|
||||
onClick={onFileClick}
|
||||
>
|
||||
<div className="selection-panel_icon">{element}</div>
|
||||
<div className="selection-panel_text">
|
||||
<Text fontSize="14px" fontWeight={600} noSelect>
|
||||
{title}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="selection-panel_checkbox">
|
||||
{!folderSelection && (
|
||||
<RadioButton
|
||||
fontSize="13px"
|
||||
fontWeight="400"
|
||||
name={`${index}`}
|
||||
isChecked={isChecked}
|
||||
onClick={onFileClick}
|
||||
value=""
|
||||
className="select-file-dialog_checked"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</StyledRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilesListRow;
|
@ -1,158 +0,0 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import FilesListBody from "./FilesListBody";
|
||||
import axios from "axios";
|
||||
import { getFolder } from "@docspace/common/api/files";
|
||||
|
||||
class FilesListWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { newFilter } = this.props;
|
||||
this.newFilter = newFilter;
|
||||
|
||||
this.state = {
|
||||
isNextPageLoading: false,
|
||||
page: 0,
|
||||
hasNextPage: true,
|
||||
files: [],
|
||||
};
|
||||
this._isMount = false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMount = true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { folderId } = this.props;
|
||||
const { isNextPageLoading } = this.state;
|
||||
|
||||
if (folderId !== prevProps.folderId) {
|
||||
if (isNextPageLoading) {
|
||||
this.abortController.abort();
|
||||
|
||||
this._isLoadNextPage = false;
|
||||
this.setState({
|
||||
isNextPageLoading: false,
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
page: 0,
|
||||
files: [],
|
||||
hasNextPage: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMount = false;
|
||||
}
|
||||
_loadNextPage = () => {
|
||||
const { files, page } = this.state;
|
||||
const {
|
||||
folderId,
|
||||
setFolderTitle,
|
||||
setProviderKey,
|
||||
setResultingFolderId,
|
||||
folderSelection,
|
||||
} = this.props;
|
||||
|
||||
if (this._isLoadNextPage) return;
|
||||
|
||||
const pageCount = 30;
|
||||
this.newFilter.page = page;
|
||||
this.newFilter.pageCount = pageCount;
|
||||
this._isLoadNextPage = true;
|
||||
this.setState({ isNextPageLoading: true }, async () => {
|
||||
try {
|
||||
this.abortController = new AbortController();
|
||||
|
||||
const data = await getFolder(
|
||||
folderId,
|
||||
this.newFilter,
|
||||
this.abortController.signal
|
||||
).catch((err) => {
|
||||
if (axios.isCancel(err)) {
|
||||
console.log("Request canceled", err.message);
|
||||
} else {
|
||||
const errorBody = err.response;
|
||||
|
||||
if (errorBody.data && errorBody.data.error) {
|
||||
toastr.error(errorBody.data.error.message);
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
if (!data) return;
|
||||
|
||||
if (page === 0 && folderSelection) {
|
||||
setFolderTitle(data.current.title);
|
||||
setProviderKey(data.current.providerKey);
|
||||
setResultingFolderId(folderId);
|
||||
}
|
||||
|
||||
const finalData = [...data.files];
|
||||
const newFilesList = [...files].concat(finalData);
|
||||
const hasNextPage = newFilesList.length < data.total - 1;
|
||||
this._isLoadNextPage = false;
|
||||
|
||||
this._isMount &&
|
||||
this.setState((state) => ({
|
||||
hasNextPage: hasNextPage,
|
||||
isNextPageLoading: false,
|
||||
page: state.page + 1,
|
||||
files: newFilesList,
|
||||
}));
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
onSelectFile,
|
||||
folderSelection = false,
|
||||
fileId,
|
||||
folderId,
|
||||
maxHeight,
|
||||
} = this.props;
|
||||
const { hasNextPage, isNextPageLoading, files, page } = this.state;
|
||||
|
||||
return (
|
||||
<FilesListBody
|
||||
files={files}
|
||||
onSelectFile={onSelectFile}
|
||||
hasNextPage={hasNextPage}
|
||||
isNextPageLoading={isNextPageLoading}
|
||||
loadNextPage={this._loadNextPage}
|
||||
folderId={folderId}
|
||||
displayType={"modal"}
|
||||
folderSelection={folderSelection}
|
||||
fileId={fileId}
|
||||
page={page}
|
||||
maxHeight={maxHeight}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default inject(
|
||||
({ selectedFolderStore, selectFolderDialogStore, auth }) => {
|
||||
const { id } = selectedFolderStore;
|
||||
const {
|
||||
setResultingFolderId,
|
||||
setFolderTitle,
|
||||
setProviderKey,
|
||||
} = selectFolderDialogStore;
|
||||
|
||||
return {
|
||||
storeFolderId: id,
|
||||
setResultingFolderId,
|
||||
setFolderTitle,
|
||||
setProviderKey,
|
||||
};
|
||||
}
|
||||
)(observer(FilesListWrapper));
|
@ -1,222 +0,0 @@
|
||||
import React from "react";
|
||||
import ModalDialog from "@docspace/components/modal-dialog";
|
||||
import FolderTreeBody from "../../FolderTreeBody";
|
||||
import Button from "@docspace/components/button";
|
||||
import FilesListWrapper from "./FilesListWrapper";
|
||||
import {
|
||||
getCommonFoldersTree,
|
||||
getFolder,
|
||||
getFoldersTree,
|
||||
getSharedRoomsTree,
|
||||
getThirdPartyCommonFolderTree,
|
||||
} from "@docspace/common/api/files";
|
||||
import toastr from "@docspace/components/toast/toastr";
|
||||
import {
|
||||
exceptSortedByTagsFolders,
|
||||
exceptPrivacyTrashArchiveFolders,
|
||||
} from "./ExceptionFoldersConstants";
|
||||
import { StyledBody, StyledModalDialog } from "./StyledSelectionPanel";
|
||||
import Text from "@docspace/components/text";
|
||||
import Loaders from "@docspace/common/components/Loaders";
|
||||
import { FolderType } from "@docspace/common/constants";
|
||||
|
||||
const SelectionPanelBody = ({
|
||||
t,
|
||||
isPanelVisible,
|
||||
onClose,
|
||||
withoutProvider,
|
||||
onSelectFile,
|
||||
filesListTitle,
|
||||
dialogName,
|
||||
primaryButtonName,
|
||||
isLoading,
|
||||
onButtonClick,
|
||||
folderId,
|
||||
onSelectFolder,
|
||||
resultingFolderTree,
|
||||
isAvailable,
|
||||
footer,
|
||||
header,
|
||||
folderSelection = false,
|
||||
folderTitle,
|
||||
fileId,
|
||||
isLoadingData,
|
||||
expandedKeys,
|
||||
isDisableTree,
|
||||
page,
|
||||
newFilter,
|
||||
isDisableButton,
|
||||
parentId,
|
||||
selectionFiles,
|
||||
}) => {
|
||||
const isLoaded = folderId && resultingFolderTree;
|
||||
return (
|
||||
<StyledModalDialog
|
||||
visible={isPanelVisible}
|
||||
onClose={onClose}
|
||||
displayType="modal"
|
||||
isLoading={isLoading}
|
||||
withFooterBorder
|
||||
isDoubleFooterLine
|
||||
autoMaxWidth
|
||||
>
|
||||
<ModalDialog.Header className={"select-panel-modal-header"}>
|
||||
{dialogName}
|
||||
</ModalDialog.Header>
|
||||
<ModalDialog.Body className="select-file_body-modal-dialog">
|
||||
<StyledBody header={!!header} footer={!!footer}>
|
||||
<div className="selection-panel_body">
|
||||
<div className="selection-panel_tree-body">
|
||||
{isLoaded ? (
|
||||
<FolderTreeBody
|
||||
selectionFiles={selectionFiles}
|
||||
folderTree={resultingFolderTree}
|
||||
onSelect={onSelectFolder}
|
||||
withoutProvider={withoutProvider}
|
||||
certainFolders
|
||||
isAvailable={isAvailable}
|
||||
selectedKeys={[`${folderId}`]}
|
||||
parentId={parentId}
|
||||
expandedKeys={expandedKeys}
|
||||
isDisableTree={isDisableTree}
|
||||
displayType="modal"
|
||||
/>
|
||||
) : (
|
||||
<Loaders.NewTreeFolders />
|
||||
)}
|
||||
</div>
|
||||
<div className="selection-panel_files-body">
|
||||
<>
|
||||
<div className="selection-panel_files-header">
|
||||
{header}
|
||||
|
||||
<Text
|
||||
color="#A3A9AE"
|
||||
className="selection-panel_title"
|
||||
fontSize="12px"
|
||||
fontWeight={600}
|
||||
noSelect
|
||||
>
|
||||
{folderSelection
|
||||
? t("FolderContents", { folderTitle })
|
||||
: filesListTitle}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<FilesListWrapper
|
||||
onSelectFile={onSelectFile}
|
||||
folderId={folderId}
|
||||
displayType={"modal"}
|
||||
folderSelection={folderSelection}
|
||||
newFilter={newFilter}
|
||||
fileId={fileId}
|
||||
maxHeight={!header ? 384 : 269}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</StyledBody>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
{footer}
|
||||
|
||||
<div>
|
||||
<Button
|
||||
id="select-file-modal-submit"
|
||||
className="select-file-modal-dialog-buttons-save"
|
||||
primary
|
||||
size="normal"
|
||||
label={primaryButtonName}
|
||||
onClick={onButtonClick}
|
||||
isDisabled={
|
||||
isDisableButton ||
|
||||
(!fileId && !folderSelection) ||
|
||||
!(folderId && resultingFolderTree)
|
||||
}
|
||||
isLoading={isDisableTree}
|
||||
/>
|
||||
<Button
|
||||
id="select-file-modal-cancel"
|
||||
className="modal-dialog-button"
|
||||
size="normal"
|
||||
label={t("Common:CancelButton")}
|
||||
onClick={onClose}
|
||||
isDisabled={isLoadingData}
|
||||
/>
|
||||
</div>
|
||||
</ModalDialog.Footer>
|
||||
</StyledModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
class SelectionPanel extends React.Component {
|
||||
static getFolderPath = async (id) => {
|
||||
try {
|
||||
const data = await getFolder(id);
|
||||
const newPathParts = data.pathParts.map((item) => item.toString());
|
||||
|
||||
+newPathParts[newPathParts.length - 1] === +id && newPathParts.pop();
|
||||
|
||||
return newPathParts;
|
||||
} catch (e) {
|
||||
toastr.error(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
static getBasicFolderInfo = async (
|
||||
treeFolders,
|
||||
filteredType,
|
||||
id,
|
||||
passedFoldersTree = [],
|
||||
hasSharedFolder
|
||||
) => {
|
||||
const filterFoldersTree = (folders, arrayOfExceptions) => {
|
||||
const arr = !hasSharedFolder
|
||||
? [...arrayOfExceptions, FolderType.Rooms]
|
||||
: arrayOfExceptions;
|
||||
|
||||
let newArray = [];
|
||||
|
||||
for (let i = 0; i < folders.length; i++) {
|
||||
if (!arr.includes(folders[i].rootFolderType)) {
|
||||
newArray.push(folders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return newArray;
|
||||
};
|
||||
|
||||
const getExceptionsFolders = (treeFolders) => {
|
||||
switch (filteredType) {
|
||||
case "exceptSortedByTags":
|
||||
return filterFoldersTree(treeFolders, exceptSortedByTagsFolders);
|
||||
case "exceptPrivacyTrashArchiveFolders":
|
||||
return filterFoldersTree(
|
||||
treeFolders,
|
||||
exceptPrivacyTrashArchiveFolders
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let filteredTreeFolders;
|
||||
|
||||
const foldersTree =
|
||||
passedFoldersTree.length > 0 ? passedFoldersTree : treeFolders;
|
||||
|
||||
const passedId = id ? id : foldersTree[0].id;
|
||||
|
||||
if (
|
||||
filteredType === "exceptSortedByTags" ||
|
||||
filteredType === "exceptPrivacyTrashArchiveFolders"
|
||||
) {
|
||||
filteredTreeFolders = getExceptionsFolders(foldersTree);
|
||||
}
|
||||
|
||||
return [filteredTreeFolders || foldersTree, passedId];
|
||||
};
|
||||
render() {
|
||||
return <SelectionPanelBody {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectionPanel;
|
@ -1,329 +0,0 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import ModalDialog from "@docspace/components/modal-dialog";
|
||||
const commonStyles = css`
|
||||
.empty-folder_container {
|
||||
grid-template-areas:
|
||||
"img img"
|
||||
"headerText headerText";
|
||||
grid-template-rows: 72px 1fr;
|
||||
|
||||
padding-bottom: 0;
|
||||
|
||||
.ec-image {
|
||||
margin: auto;
|
||||
}
|
||||
.ec-header {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.select-file-dialog_empty-container {
|
||||
.empty-folder_container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledModalDialog = styled(ModalDialog)`
|
||||
#modal-dialog {
|
||||
max-height: 560px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.select-panel-modal-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
line-height: 52px;
|
||||
font-size: 21px;
|
||||
}
|
||||
.modal-dialog-aside-header {
|
||||
height: 53px;
|
||||
}
|
||||
|
||||
.select-file_body-modal-dialog {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledBody = styled.div`
|
||||
.selection-panel_body {
|
||||
height: ${(props) => (props.footer ? "395px" : "434px")};
|
||||
display: grid;
|
||||
grid-template-columns: 245px 1fr;
|
||||
grid-template-areas: "tree files" "footer footer";
|
||||
grid-template-rows: auto max-content;
|
||||
margin-right: -4px;
|
||||
padding-bottom: 0;
|
||||
|
||||
.selection-panel_files-body {
|
||||
width: 500px;
|
||||
grid-area: files;
|
||||
display: grid;
|
||||
grid-template-rows: max-content auto;
|
||||
}
|
||||
/* .selection-panel_files-list-body {
|
||||
height: 384px;
|
||||
} */
|
||||
|
||||
.selection-panel_tree-body {
|
||||
grid-area: tree;
|
||||
height: 100%;
|
||||
border-right: 1px solid ${(props) => props.theme.row.borderBottom};
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: max-content auto;
|
||||
|
||||
.selection-panel_folder-title {
|
||||
padding: 12px 20px 14px 0px;
|
||||
}
|
||||
|
||||
.selection-panel_header-loader {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.selection-panel_tree-folder {
|
||||
height: ${(props) => (props.footer ? "343px" : "387px")};
|
||||
max-height: 384px;
|
||||
margin-left: -17px;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.span.rc-tree-switcher {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-panel_files-header {
|
||||
padding: 16px;
|
||||
word-break: break-word;
|
||||
.selection-panel_title {
|
||||
${(props) => props.header && "padding-top: 16px"};
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
.selection-panel_footer {
|
||||
grid-area: footer;
|
||||
border-top: 1px solid ${(props) => props.theme.row.borderBottom};
|
||||
margin-left: -13px;
|
||||
margin-right: -7px;
|
||||
padding-left: 16px;
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
.selection-panel_buttons {
|
||||
${(props) => props.footer && "margin-top:16px"};
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${commonStyles}
|
||||
`;
|
||||
|
||||
const StyledRow = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 32px auto 32px;
|
||||
grid-gap: 8px;
|
||||
position: relative;
|
||||
height: 48px;
|
||||
width: calc(100% - 16px);
|
||||
|
||||
padding-left: 16px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
${(props) =>
|
||||
props.isChecked && `background: ${props.theme.row.backgroundColor}`};
|
||||
|
||||
.selection-panel_clicked-area {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.selection-panel_text {
|
||||
margin-top: auto;
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
p {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.selection-panel_icon,
|
||||
.selection-panel_checkbox {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.selection-panel_icon {
|
||||
svg {
|
||||
path {
|
||||
fill: #a3a9ae;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.folderSelection &&
|
||||
css`
|
||||
.selection-panel_icon {
|
||||
::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background-color: ${(props) =>
|
||||
props.theme.modalDialog.colorDisabledFileIcons};
|
||||
|
||||
border-top-right-radius: 45%;
|
||||
left: 18px;
|
||||
top: 6px;
|
||||
width: 27px;
|
||||
height: 32px;
|
||||
content: "";
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
.selection-panel_text p {
|
||||
color: ${(props) => props.theme.text.disableColor};
|
||||
}
|
||||
cursor: default;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledAsideBody = styled.div`
|
||||
height: 100%;
|
||||
|
||||
.selection-panel_aside-body {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: max-content auto max-content;
|
||||
}
|
||||
.selection-panel_files-list-body {
|
||||
height: 100%;
|
||||
margin-left: -16px;
|
||||
margin-right: -6px;
|
||||
}
|
||||
.selection-panel_files {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.selection-panel_aside-header {
|
||||
margin-bottom: 12px;
|
||||
div:first-child {
|
||||
${(props) => props.header && " margin-bottom: 12px;"}
|
||||
}
|
||||
|
||||
.selection-panel_header-loader {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
}
|
||||
.selection-panel_aside-folder-title {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.selection-panel_folder-selection-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.selection-panel_aside-tree {
|
||||
margin-left: -16px;
|
||||
margin-right: -16px;
|
||||
max-width: 477px;
|
||||
overflow: hidden;
|
||||
.selection-panel_aside-loader {
|
||||
overflow: auto;
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-panel_aside-title {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.selection-panel_aside-footer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: 1px solid ${(props) => props.theme.row.borderBottom};
|
||||
|
||||
.selection-panel_aside-buttons {
|
||||
${(props) => props.footer && "margin-top:16px"};
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
button:first-child {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${commonStyles}
|
||||
`;
|
||||
|
||||
const StyledAsideHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.selection-panel_aside-header-icon {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTree = styled.div`
|
||||
height: 100%;
|
||||
|
||||
.files-tree-menu {
|
||||
margin-bottom: 22px;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
.selection-panel_tree-folder {
|
||||
height: 100%;
|
||||
}
|
||||
#folder-tree-scroll-bar {
|
||||
.nav-thumb-horizontal {
|
||||
height: 0px !important;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.displayType === "modal" &&
|
||||
css`
|
||||
.nav-thumb-vertical {
|
||||
margin-left: 4px !important;
|
||||
width: 4px !important;
|
||||
}
|
||||
`}
|
||||
|
||||
.scroll-body {
|
||||
${(props) => props.isLoadingNodes && "overflow-y: hidden !important"};
|
||||
overflow-x: hidden !important;
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-panel_empty-folder {
|
||||
margin-top: 12px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledItemsLoader = styled.div`
|
||||
display: flex;
|
||||
.panel-loader {
|
||||
margin-left: 16px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
export {
|
||||
StyledBody,
|
||||
StyledRow,
|
||||
StyledAsideBody,
|
||||
StyledAsideHeader,
|
||||
StyledTree,
|
||||
StyledModalDialog,
|
||||
StyledItemsLoader,
|
||||
};
|
@ -1,12 +1,10 @@
|
||||
import SharingPanel from "./SharingPanel";
|
||||
import AddUsersPanel from "./AddUsersPanel";
|
||||
import EmbeddingPanel from "./EmbeddingPanel";
|
||||
import OperationsPanel from "./OperationsPanel";
|
||||
import NewFilesPanel from "./NewFilesPanel";
|
||||
import VersionHistoryPanel from "./VersionHistoryPanel";
|
||||
import ChangeOwnerPanel from "./ChangeOwnerPanel";
|
||||
import UploadPanel from "./UploadPanel";
|
||||
import SelectFileDialog from "./SelectFileDialog";
|
||||
import HotkeyPanel from "./HotkeysPanel";
|
||||
import InvitePanel from "./InvitePanel";
|
||||
|
||||
@ -14,12 +12,10 @@ export {
|
||||
SharingPanel,
|
||||
AddUsersPanel,
|
||||
EmbeddingPanel,
|
||||
OperationsPanel,
|
||||
NewFilesPanel,
|
||||
VersionHistoryPanel,
|
||||
ChangeOwnerPanel,
|
||||
UploadPanel,
|
||||
SelectFileDialog,
|
||||
HotkeyPanel,
|
||||
InvitePanel,
|
||||
};
|
||||
|
@ -18,6 +18,7 @@ import { ContextMenuButton } from "@docspace/components";
|
||||
import DeleteThirdPartyDialog from "../../../../../../components/dialogs/DeleteThirdPartyDialog";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import { getOAuthToken } from "@docspace/common/utils";
|
||||
import { FilesSelectorFilterTypes } from "@docspace/common/constants";
|
||||
|
||||
let accounts = [],
|
||||
capabilities;
|
||||
@ -187,10 +188,8 @@ const DirectThirdPartyConnection = (props) => {
|
||||
const onConnect = () => {
|
||||
clearLocalStorage();
|
||||
|
||||
const {
|
||||
provider_key,
|
||||
provider_link: directConnection,
|
||||
} = selectedThirdPartyAccount;
|
||||
const { provider_key, provider_link: directConnection } =
|
||||
selectedThirdPartyAccount;
|
||||
|
||||
if (directConnection) {
|
||||
let authModal = window.open(
|
||||
@ -282,8 +281,8 @@ const DirectThirdPartyConnection = (props) => {
|
||||
onSelectFile={onSelectFile}
|
||||
onClickInput={onClickInput}
|
||||
isPanelVisible={isPanelVisible}
|
||||
searchParam=".gz"
|
||||
filesListTitle={t("Settings:SelectFileInGZFormat")}
|
||||
filterParam={FilesSelectorFilterTypes.GZ}
|
||||
descriptionText={t("Settings:SelectFileInGZFormat")}
|
||||
withoutResetFolderTree
|
||||
isArchiveOnly
|
||||
isDisabled={
|
||||
@ -299,6 +298,7 @@ const DirectThirdPartyConnection = (props) => {
|
||||
id={id}
|
||||
onSelectFolder={onSelectFolder}
|
||||
name={"thirdParty"}
|
||||
isThirdParty={true}
|
||||
onClose={onClose}
|
||||
onClickInput={onClickInput}
|
||||
onSetLoadingData={onSetLoadingData}
|
||||
|
@ -59,6 +59,7 @@ const RestoreBackup = (props) => {
|
||||
useState(false);
|
||||
const [isVisibleSelectFileDialog, setIsVisibleSelectFileDialog] =
|
||||
useState(false);
|
||||
const [path, setPath] = useState("");
|
||||
|
||||
const startRestoreBackup = useCallback(async () => {
|
||||
try {
|
||||
@ -162,10 +163,17 @@ const RestoreBackup = (props) => {
|
||||
<RoomsModule
|
||||
isDisabled={!isEnableRestore}
|
||||
t={t}
|
||||
fileName={path}
|
||||
isPanelVisible={isVisibleSelectFileDialog}
|
||||
onClose={onModalClose}
|
||||
onClickInput={onClickInput}
|
||||
onSelectFile={(file) => setRestoreResource(file.id)}
|
||||
onSelectFile={(file) => {
|
||||
if (file && file.path) {
|
||||
const newPath = file.path.join("/");
|
||||
setPath(`${newPath}/${file.title}`);
|
||||
}
|
||||
setRestoreResource(file.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{radioButtonState === DISK_SPACE && (
|
||||
|
@ -1,17 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
import SelectFileInputWrapper from "client/SelectFileInput";
|
||||
import { FilesSelectorFilterTypes } from "@docspace/common/constants";
|
||||
class RoomsModule extends React.Component {
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<SelectFileInputWrapper
|
||||
{...this.props}
|
||||
filteredType="exceptSortedByTags"
|
||||
withoutProvider
|
||||
isArchiveOnly
|
||||
searchParam=".gz"
|
||||
filesListTitle={t("SelectFileInGZFormat")}
|
||||
filterParam={FilesSelectorFilterTypes.GZ}
|
||||
descriptionText={t("SelectFileInGZFormat")}
|
||||
withSubfolders={false}
|
||||
maxFolderInputWidth="446px"
|
||||
withoutBasicSelection
|
||||
|
@ -114,10 +114,12 @@ class ProfileActionsStore {
|
||||
};
|
||||
|
||||
onSettingsClick = (settingsUrl) => {
|
||||
this.selectedFolderStore.setSelectedFolder(null);
|
||||
window.DocSpace.navigate(settingsUrl);
|
||||
};
|
||||
|
||||
onPaymentsClick = () => {
|
||||
this.selectedFolderStore.setSelectedFolder(null);
|
||||
window.DocSpace.navigate(PAYMENTS_URL);
|
||||
};
|
||||
|
||||
|
20
packages/client/tsconfig.json
Normal file
20
packages/client/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"target": "es2016",
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"paths": {
|
||||
"PUBLIC_DIR": ["../../public"],
|
||||
"SRC_DIR": ["./src"]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const ModuleFederationPlugin = require("webpack").container
|
||||
.ModuleFederationPlugin;
|
||||
const ModuleFederationPlugin =
|
||||
require("webpack").container.ModuleFederationPlugin;
|
||||
const DefinePlugin = require("webpack").DefinePlugin;
|
||||
|
||||
const ExternalTemplateRemotesPlugin = require("external-remotes-plugin");
|
||||
@ -222,7 +222,7 @@ const config = {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [
|
||||
"@babel/preset-react",
|
||||
["@babel/preset-react", { runtime: "automatic" }],
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
@ -299,11 +299,11 @@ module.exports = (env, argv) => {
|
||||
"./SharingDialog": "./src/components/panels/SharingDialog",
|
||||
"./utils": "./src/helpers/filesUtils.js",
|
||||
"./SelectFileDialog":
|
||||
"./src/components/panels/SelectFileDialog/SelectFileDialogWrapper",
|
||||
"./src/components/FilesSelector/FilesSelectorWrapper",
|
||||
"./SelectFileInput":
|
||||
"./src/components/panels/SelectFileInput/SelectFileInputWrapper",
|
||||
"./SelectFolderDialog":
|
||||
"./src/components/panels/SelectFolderDialog/SelectFolderDialogWrapper",
|
||||
"./src/components/FilesSelector/FilesSelectorWrapper",
|
||||
"./SelectFolderInput":
|
||||
"./src/components/panels/SelectFolderInput/SelectFolderInputWrapper",
|
||||
"./PeopleSelector": "./src/components/PeopleSelector",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ApplyFilterOption } from "../../constants";
|
||||
import { getObjectByLocation, toUrlParams } from "../../utils";
|
||||
import queryString from "query-string";
|
||||
|
||||
@ -16,6 +17,7 @@ const DEFAULT_SELECTED_ITEM = {};
|
||||
const DEFAULT_FOLDER = "@my";
|
||||
const DEFAULT_SEARCH_IN_CONTENT = null;
|
||||
const DEFAULT_EXCLUDE_SUBJECT = null;
|
||||
const DEFAULT_APPLY_FILTER_OPTION = null;
|
||||
|
||||
const SEARCH_TYPE = "withSubfolders";
|
||||
const AUTHOR_TYPE = "authorType";
|
||||
@ -31,6 +33,7 @@ const FOLDER = "folder";
|
||||
const PREVIEW = "preview";
|
||||
const SEARCH_IN_CONTENT = "searchInContent";
|
||||
const EXCLUDE_SUBJECT = "excludeSubject";
|
||||
const APPLY_FILTER_OPTION = "applyFilterOption";
|
||||
|
||||
// TODO: add next params
|
||||
// subjectGroup bool
|
||||
@ -77,6 +80,8 @@ class FilesFilter {
|
||||
urlFilter[SEARCH_IN_CONTENT] || defaultFilter.searchInContent;
|
||||
const excludeSubject =
|
||||
urlFilter[EXCLUDE_SUBJECT] || defaultFilter.excludeSubject;
|
||||
const applyFilterOption =
|
||||
urlFilter[APPLY_FILTER_OPTION] || defaultFilter.applyFilterOption;
|
||||
|
||||
const newFilter = new FilesFilter(
|
||||
page,
|
||||
@ -93,7 +98,8 @@ class FilesFilter {
|
||||
defaultFilter.selectedItem,
|
||||
folder,
|
||||
searchInContent,
|
||||
excludeSubject
|
||||
excludeSubject,
|
||||
applyFilterOption
|
||||
);
|
||||
|
||||
return newFilter;
|
||||
@ -114,7 +120,8 @@ class FilesFilter {
|
||||
selectedItem = DEFAULT_SELECTED_ITEM,
|
||||
folder = DEFAULT_FOLDER,
|
||||
searchInContent = DEFAULT_SEARCH_IN_CONTENT,
|
||||
excludeSubject = DEFAULT_EXCLUDE_SUBJECT
|
||||
excludeSubject = DEFAULT_EXCLUDE_SUBJECT,
|
||||
applyFilterOption = DEFAULT_APPLY_FILTER_OPTION
|
||||
) {
|
||||
this.page = page;
|
||||
this.pageCount = pageCount;
|
||||
@ -131,6 +138,7 @@ class FilesFilter {
|
||||
this.folder = folder;
|
||||
this.searchInContent = searchInContent;
|
||||
this.excludeSubject = excludeSubject;
|
||||
this.applyFilterOption = applyFilterOption;
|
||||
}
|
||||
|
||||
getStartIndex = () => {
|
||||
@ -159,10 +167,14 @@ class FilesFilter {
|
||||
startIndex,
|
||||
searchInContent,
|
||||
excludeSubject,
|
||||
applyFilterOption,
|
||||
} = this;
|
||||
|
||||
const isFilterSet =
|
||||
filterType || (search ?? "").trim() || authorType
|
||||
filterType ||
|
||||
(search ?? "").trim() ||
|
||||
authorType ||
|
||||
applyFilterOption !== ApplyFilterOption.All
|
||||
? withSubfolders
|
||||
: false;
|
||||
|
||||
@ -184,6 +196,7 @@ class FilesFilter {
|
||||
userIdOrGroupId,
|
||||
searchInContent,
|
||||
excludeSubject,
|
||||
applyFilterOption,
|
||||
};
|
||||
|
||||
const str = toUrlParams(dtoFilter, true);
|
||||
@ -204,6 +217,7 @@ class FilesFilter {
|
||||
roomId,
|
||||
searchInContent,
|
||||
excludeSubject,
|
||||
applyFilterOption,
|
||||
} = this;
|
||||
|
||||
const dtoFilter = {};
|
||||
@ -220,6 +234,7 @@ class FilesFilter {
|
||||
if (URLParams.preview) dtoFilter[PREVIEW] = URLParams.preview;
|
||||
if (searchInContent) dtoFilter[SEARCH_IN_CONTENT] = searchInContent;
|
||||
if (excludeSubject) dtoFilter[EXCLUDE_SUBJECT] = excludeSubject;
|
||||
if (applyFilterOption) dtoFilter[APPLY_FILTER_OPTION] = applyFilterOption;
|
||||
|
||||
dtoFilter[PAGE] = page + 1;
|
||||
dtoFilter[SORT_BY] = sortBy;
|
||||
@ -249,7 +264,8 @@ class FilesFilter {
|
||||
this.selectedItem,
|
||||
this.folder,
|
||||
this.searchInContent,
|
||||
this.excludeSubject
|
||||
this.excludeSubject,
|
||||
this.applyFilterOption
|
||||
);
|
||||
}
|
||||
|
||||
@ -268,7 +284,8 @@ class FilesFilter {
|
||||
this.folder === filter.folder &&
|
||||
this.pageCount === filter.pageCount &&
|
||||
this.searchInContent === filter.searchInContent &&
|
||||
this.excludeSubject === filter.excludeSubject;
|
||||
this.excludeSubject === filter.excludeSubject &&
|
||||
this.applyFilterOption === filter.applyFilterOption;
|
||||
|
||||
return equals;
|
||||
}
|
||||
|
@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import RectangleLoader from "../RectangleLoader/RectangleLoader";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
margin-bottom: 16px;
|
||||
|
||||
gap: 8px;
|
||||
`;
|
||||
|
||||
const SelectorBreadCrumbsLoader = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<RectangleLoader
|
||||
width={"80px"}
|
||||
height={"22px"}
|
||||
style={{ ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
<RectangleLoader
|
||||
width={"12px"}
|
||||
height={"12px"}
|
||||
style={{ ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
<RectangleLoader
|
||||
width={"80px"}
|
||||
height={"22px"}
|
||||
style={{ ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
<RectangleLoader
|
||||
width={"12px"}
|
||||
height={"12px"}
|
||||
style={{ ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
<RectangleLoader
|
||||
width={"80px"}
|
||||
height={"22px"}
|
||||
style={{ ...style }}
|
||||
{...rest}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectorBreadCrumbsLoader;
|
@ -61,7 +61,7 @@ const SelectorRowLoader = ({
|
||||
{...rest}
|
||||
>
|
||||
<RectangleLoader className={"avatar"} width={"32px"} height={"32px"} />
|
||||
<RectangleLoader className={"text"} width={"212px"} height={"16px"} />
|
||||
<RectangleLoader width={"212px"} height={"16px"} />
|
||||
{isMultiSelect && (
|
||||
<RectangleLoader
|
||||
className={"checkbox"}
|
||||
@ -75,7 +75,7 @@ const SelectorRowLoader = ({
|
||||
|
||||
const getRowItems = () => {
|
||||
const rows = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
rows.push(getRowItem(i));
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@ import DataBackupLoader from "./DataBackupLoader";
|
||||
import AutoBackupLoader from "./AutoBackupLoader";
|
||||
import RestoreBackupLoader from "./RestoreBackupLoader";
|
||||
import PaymentsLoader from "./PaymentsLoader";
|
||||
|
||||
import SelectorBreadCrumbsLoader from "./SelectorBreadCrumbsLoader";
|
||||
import SelectorSearchLoader from "./SelectorSearchLoader";
|
||||
import SelectorRowLoader from "./SelectorRowLoader";
|
||||
|
||||
@ -86,6 +88,8 @@ export default {
|
||||
AutoBackupLoader,
|
||||
RestoreBackupLoader,
|
||||
PaymentsLoader,
|
||||
|
||||
SelectorBreadCrumbsLoader,
|
||||
SelectorSearchLoader,
|
||||
SelectorRowLoader,
|
||||
|
||||
|
@ -59,6 +59,24 @@ export const AccountLoginType = Object.freeze({
|
||||
LDAP: "1",
|
||||
STANDART: "2",
|
||||
});
|
||||
/**
|
||||
* Enum for files selector filter.
|
||||
* @readonly
|
||||
*/
|
||||
export const ApplyFilterOption = Object.freeze({
|
||||
All: "All",
|
||||
Files: "Files",
|
||||
Folder: "Folder",
|
||||
});
|
||||
/**
|
||||
* Enum for files selector filter.
|
||||
* @readonly
|
||||
*/
|
||||
export const FilesSelectorFilterTypes = Object.freeze({
|
||||
DOCX: "DOCX",
|
||||
IMG: "IMG",
|
||||
GZ: "GZ",
|
||||
});
|
||||
/**
|
||||
* Enum for filter subject.
|
||||
* @readonly
|
||||
|
@ -1,17 +1,19 @@
|
||||
module.exports = {
|
||||
parser: "babel-eslint",
|
||||
extends: ["eslint:recommended", "plugin:react/recommended", "plugin:storybook/recommended"],
|
||||
export default {
|
||||
parser: "@babel/eslint-parser",
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true
|
||||
node: true,
|
||||
"jest/globals": true,
|
||||
},
|
||||
plugins: ["jest"],
|
||||
env: {
|
||||
"jest/globals": true
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -114,7 +114,7 @@ class ContextMenuButton extends React.Component {
|
||||
|
||||
onDropDownItemClick = (item, e) => {
|
||||
const open = this.state.displayType === "dropdown";
|
||||
item.onClick && item.onClick(e, open);
|
||||
item.onClick && item.onClick(e, open, item);
|
||||
this.toggle(!this.state.isOpen);
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,11 @@ const StyledDropdownItem = styled.div`
|
||||
display: ${(props) => (props.textOverflow ? "block" : "flex")};
|
||||
width: ${(props) => props.theme.dropDownItem.width};
|
||||
max-width: ${(props) => props.theme.dropDownItem.maxWidth};
|
||||
${(props) =>
|
||||
props.minWidth &&
|
||||
css`
|
||||
min-width: ${props.minWidth};
|
||||
`};
|
||||
border: ${(props) => props.theme.dropDownItem.border};
|
||||
cursor: pointer;
|
||||
margin: ${(props) => props.theme.dropDownItem.margin};
|
||||
|
@ -48,6 +48,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.21.0",
|
||||
"@babel/core": "^7.21.3",
|
||||
"@babel/eslint-parser": "^7.21.8",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.18.10",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
|
||||
|
@ -10,6 +10,7 @@ import Selector from "@docspace/components/selector";
|
||||
<Selector
|
||||
acceptButtonLabel="Add"
|
||||
accessRights={[]}
|
||||
withoutBackButton={false}
|
||||
cancelButtonLabel="Cancel"
|
||||
emptyScreenDescription="The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time."
|
||||
emptyScreenHeader="No other accounts here yet"
|
||||
@ -39,6 +40,18 @@ import Selector from "@docspace/components/selector";
|
||||
selectedAccessRight={{}}
|
||||
selectedItems={[]}
|
||||
totalItems={0}
|
||||
withBreadCrumbs={false}
|
||||
breadCrumbs={[]}
|
||||
onSelectBreadCrumb={function noRefCheck() {}}
|
||||
breadCrumbsLoader={<></>}
|
||||
withSearch={true}
|
||||
isBreadCrumbsLoading={false}
|
||||
withFooterInput={false}
|
||||
footerInputHeader={""}
|
||||
footerCheckboxLabel={""}
|
||||
currentFooterInputValue={""}
|
||||
alwaysShowFooter={false}
|
||||
descriptionText={""}
|
||||
/>
|
||||
```
|
||||
|
||||
@ -50,8 +63,10 @@ import Selector from "@docspace/components/selector";
|
||||
| `className` | `string` | - | - | - | Accepts class |
|
||||
| `style` | `obj`, `array` | - | - | - | Accepts css style |
|
||||
| `headerLabel` | `string` | - | - | - | Selector header text |
|
||||
| `withoutBackButton` | `bool` | - | - | - | Hide header back button |
|
||||
| `onBackClick` | `func` | - | - | - | What the header arrow will trigger when clicked |
|
||||
| `searchPlaceholder` | `string` | - | - | - | Placeholder for search input |
|
||||
| `withSearch` | `bool` | - | - | true | Show search input |
|
||||
| `onSearch` | `func` | - | - | - | What the search input will trigger when user stopped typing |
|
||||
| `onClearSearch` | `func` | - | - | - | What the clear icon of search input will trigger when clicked |
|
||||
| `items` | `array` | - | - | - | Displaying items |
|
||||
@ -84,3 +99,15 @@ import Selector from "@docspace/components/selector";
|
||||
| `isLoading` | `bool` | - | - | - | Set loading state for select |
|
||||
| `searchLoader` | `node` | - | - | - | Loader element for search block |
|
||||
| `rowLoader` | `node` | - | - | - | Loader element for item |
|
||||
| `withBreadCrumbs` | `bool` | - | - | - | Add displaying bread crumbs |
|
||||
| `breadCrumbs` | `array` | - | - | - | Bread crumbs items |
|
||||
| `onSelectBreadCrumb` | `func` | - | - | - | Function for select bread crumb item |
|
||||
| `breadCrumbsLoader` | `node` | - | - | - | Loader element for bread crumbs |
|
||||
| `isBreadCrumbsLoading` | `bool` | - | - | false | Set loading state for bread crumbs |
|
||||
| `currentFooterInputValue` | `string` | - | - | - | Current file name |
|
||||
| `footerCheckboxLabel` | `string` | - | - | - | Title of checkbox |
|
||||
| `footerInputHeader` | `string` | - | - | - | Header of new name block |
|
||||
| `withFooterInput` | `bool` | - | - | false | Show name change input |
|
||||
| `alwaysShowFooter` | `bool` | - | - | false | Always show buttons |
|
||||
| `disableAcceptButton` | `bool` | - | - | false | Disable click at accept button |
|
||||
| `descriptionText` | `string` | - | - | - | Description body text |
|
||||
|
@ -1,190 +0,0 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Selector from "./";
|
||||
import CustomSvgUrl from "PUBLIC_DIR/images/icons/32/room/custom.svg?url";
|
||||
import ArchiveSvgUrl from "PUBLIC_DIR/images/room.archive.svg?url";
|
||||
import EmptyScreenFilter from "PUBLIC_DIR/images/empty_screen_filter.png";
|
||||
|
||||
const StyledRowLoader = styled.div`
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
`;
|
||||
|
||||
const StyledSearchLoader = styled.div`
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
background: black;
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: "Components/Selector",
|
||||
component: Selector,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Selector for displaying items list of people or room selector",
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
height: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function makeName() {
|
||||
var result = "";
|
||||
var characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var charactersLength = characters.length;
|
||||
for (var i = 0; i < 15; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const getItems = (count) => {
|
||||
const items = [];
|
||||
|
||||
for (let i = 0; i < count / 2; i++) {
|
||||
items.push({
|
||||
key: `user_${i}`,
|
||||
id: `user_${i}`,
|
||||
label: makeName() + " " + i,
|
||||
icon: CustomSvgUrl,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < count / 2; i++) {
|
||||
items.push({
|
||||
key: `room_${i}`,
|
||||
id: `room_${i}`,
|
||||
label: makeName() + " " + i + " room",
|
||||
icon: CustomSvgUrl,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const getAccessRights = () => {
|
||||
const accesses = [
|
||||
{
|
||||
key: "roomManager",
|
||||
label: "Room manager",
|
||||
access: 0,
|
||||
},
|
||||
{
|
||||
key: "editor",
|
||||
label: "Editor",
|
||||
access: 1,
|
||||
},
|
||||
{
|
||||
key: "formFiller",
|
||||
label: "Form filler",
|
||||
access: 2,
|
||||
},
|
||||
{
|
||||
key: "reviewer",
|
||||
label: "Reviewer",
|
||||
access: 3,
|
||||
},
|
||||
{
|
||||
key: "commentator",
|
||||
label: "Commentator",
|
||||
access: 4,
|
||||
},
|
||||
{
|
||||
key: "viewer",
|
||||
label: "Viewer",
|
||||
access: 5,
|
||||
},
|
||||
];
|
||||
|
||||
return accesses;
|
||||
};
|
||||
|
||||
const items = getItems(100000);
|
||||
|
||||
const selectedItems = [items[0], items[3], items[7]];
|
||||
|
||||
const accessRights = getAccessRights();
|
||||
|
||||
const selectedAccessRight = accessRights[1];
|
||||
|
||||
const renderedItems = items.slice(0, 100);
|
||||
const totalItems = items.length;
|
||||
|
||||
const Template = (args) => {
|
||||
const [rendItems, setRendItems] = React.useState(renderedItems);
|
||||
|
||||
const loadNextPage = (index) => {
|
||||
setRendItems((val) => [...val, ...items.slice(index, index + 100)]);
|
||||
};
|
||||
|
||||
const rowLoader = <StyledRowLoader />;
|
||||
const searchLoader = <StyledSearchLoader className="search-loader" />;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "480px",
|
||||
height: args.height,
|
||||
border: "1px solid #eee",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<Selector
|
||||
{...args}
|
||||
items={rendItems}
|
||||
loadNextPage={loadNextPage}
|
||||
searchLoader={searchLoader}
|
||||
rowLoader={rowLoader}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
height: "485px", // container height
|
||||
headerLabel: "Room list",
|
||||
onBackClick: () => {},
|
||||
searchPlaceholder: "Search",
|
||||
searchValue: "",
|
||||
items: renderedItems,
|
||||
onSelect: (item) => {},
|
||||
isMultiSelect: false,
|
||||
selectedItems: selectedItems,
|
||||
acceptButtonLabel: "Add",
|
||||
onAccept: (items, access) => {},
|
||||
withSelectAll: false,
|
||||
selectAllLabel: "All accounts",
|
||||
selectAllIcon: ArchiveSvgUrl,
|
||||
onSelectAll: () => {},
|
||||
withAccessRights: false,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange: (access) => {},
|
||||
withCancelButton: false,
|
||||
cancelButtonLabel: "Cancel",
|
||||
onCancel: () => {},
|
||||
emptyScreenImage: EmptyScreenFilter,
|
||||
emptyScreenHeader: "No other accounts here yet",
|
||||
emptyScreenDescription:
|
||||
"The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
searchEmptyScreenImage: EmptyScreenFilter,
|
||||
searchEmptyScreenHeader: "No other accounts here yet search",
|
||||
searchEmptyScreenDescription:
|
||||
" SEARCH !!! The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
totalItems,
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
isLoading: false,
|
||||
};
|
323
packages/components/selector/Selector.stories.tsx
Normal file
323
packages/components/selector/Selector.stories.tsx
Normal file
@ -0,0 +1,323 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Selector from ".";
|
||||
import CustomSvgUrl from "PUBLIC_DIR/images/icons/32/room/custom.svg?url";
|
||||
import ArchiveSvgUrl from "PUBLIC_DIR/images/room.archive.svg?url";
|
||||
import EmptyScreenFilter from "PUBLIC_DIR/images/empty_screen_filter.png";
|
||||
import { Item } from "./sub-components/Item/Item.types";
|
||||
|
||||
const StyledRowLoader = styled.div`
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
`;
|
||||
|
||||
const StyledSearchLoader = styled.div`
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
background: black;
|
||||
`;
|
||||
|
||||
const StyledBreadCrumbsLoader = styled.div`
|
||||
width: 100%;
|
||||
height: 54px;
|
||||
background: black;
|
||||
`;
|
||||
|
||||
export default {
|
||||
title: "Components/Selector",
|
||||
component: Selector,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Selector for displaying items list of people or room selector",
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
height: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function makeName() {
|
||||
var result = "";
|
||||
var characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var charactersLength = characters.length;
|
||||
for (var i = 0; i < 15; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const getItems = (count: number) => {
|
||||
const items: Item[] = [];
|
||||
|
||||
for (let i = 0; i < count / 2; i++) {
|
||||
items.push({
|
||||
key: `user_${i}`,
|
||||
id: `user_${i}`,
|
||||
label: makeName() + " " + i,
|
||||
icon: CustomSvgUrl,
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < count / 2; i++) {
|
||||
items.push({
|
||||
key: `room_${i}`,
|
||||
id: `room_${i}`,
|
||||
label: makeName() + " " + i + " room",
|
||||
icon: CustomSvgUrl,
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
const getAccessRights = () => {
|
||||
const accesses = [
|
||||
{
|
||||
key: "roomManager",
|
||||
label: "Room manager",
|
||||
access: 0,
|
||||
},
|
||||
{
|
||||
key: "editor",
|
||||
label: "Editor",
|
||||
access: 1,
|
||||
},
|
||||
{
|
||||
key: "formFiller",
|
||||
label: "Form filler",
|
||||
access: 2,
|
||||
},
|
||||
{
|
||||
key: "reviewer",
|
||||
label: "Reviewer",
|
||||
access: 3,
|
||||
},
|
||||
{
|
||||
key: "commentator",
|
||||
label: "Commentator",
|
||||
access: 4,
|
||||
},
|
||||
{
|
||||
key: "viewer",
|
||||
label: "Viewer",
|
||||
access: 5,
|
||||
},
|
||||
];
|
||||
|
||||
return accesses;
|
||||
};
|
||||
|
||||
const items = getItems(100000);
|
||||
|
||||
const selectedItems = [items[0], items[3], items[7]];
|
||||
|
||||
const accessRights = getAccessRights();
|
||||
|
||||
const selectedAccessRight = accessRights[1];
|
||||
|
||||
const renderedItems = items.slice(0, 100);
|
||||
const totalItems = items.length;
|
||||
|
||||
const Template = (args) => {
|
||||
const [rendItems, setRendItems] = React.useState(renderedItems);
|
||||
|
||||
const loadNextPage = (index: number) => {
|
||||
setRendItems((val) => [...val, ...items.slice(index, index + 100)]);
|
||||
};
|
||||
|
||||
const rowLoader = <StyledRowLoader />;
|
||||
const searchLoader = <StyledSearchLoader className="search-loader" />;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "480px",
|
||||
height: args.height,
|
||||
border: "1px solid #eee",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<Selector
|
||||
{...args}
|
||||
items={rendItems}
|
||||
loadNextPage={loadNextPage}
|
||||
searchLoader={searchLoader}
|
||||
rowLoader={rowLoader}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
export const BreadCrumbs = Template.bind({});
|
||||
|
||||
export const NewName = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
height: "485px", // container height
|
||||
headerLabel: "Room list",
|
||||
onBackClick: () => {},
|
||||
searchPlaceholder: "Search",
|
||||
searchValue: "",
|
||||
items: renderedItems,
|
||||
onSelect: (item) => {},
|
||||
isMultiSelect: false,
|
||||
selectedItems: selectedItems,
|
||||
acceptButtonLabel: "Add",
|
||||
onAccept: (items, access) => {},
|
||||
withSelectAll: false,
|
||||
selectAllLabel: "All accounts",
|
||||
selectAllIcon: ArchiveSvgUrl,
|
||||
onSelectAll: () => {},
|
||||
withAccessRights: false,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange: (access) => {},
|
||||
withCancelButton: false,
|
||||
cancelButtonLabel: "Cancel",
|
||||
onCancel: () => {},
|
||||
emptyScreenImage: EmptyScreenFilter,
|
||||
emptyScreenHeader: "No other accounts here yet",
|
||||
emptyScreenDescription:
|
||||
"The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
searchEmptyScreenImage: EmptyScreenFilter,
|
||||
searchEmptyScreenHeader: "No other accounts here yet search",
|
||||
searchEmptyScreenDescription:
|
||||
" SEARCH !!! The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
totalItems,
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
isLoading: false,
|
||||
withBreadCrumbs: false,
|
||||
breadCrumbs: [],
|
||||
onSelectBreadCrumb: (item) => {},
|
||||
breadCrumbsLoader: <StyledBreadCrumbsLoader />,
|
||||
withoutBackButton: false,
|
||||
withSearch: false,
|
||||
isBreadCrumbsLoading: false,
|
||||
alwaysShowFooter: false,
|
||||
disableAcceptButton: false,
|
||||
descriptionText: "",
|
||||
};
|
||||
|
||||
BreadCrumbs.args = {
|
||||
height: "485px", // container height
|
||||
headerLabel: "Room list",
|
||||
onBackClick: () => {},
|
||||
searchPlaceholder: "Search",
|
||||
searchValue: "",
|
||||
items: renderedItems,
|
||||
onSelect: (item) => {},
|
||||
isMultiSelect: false,
|
||||
selectedItems: selectedItems,
|
||||
acceptButtonLabel: "Add",
|
||||
onAccept: (items, access) => {},
|
||||
withSelectAll: false,
|
||||
selectAllLabel: "All accounts",
|
||||
selectAllIcon: ArchiveSvgUrl,
|
||||
onSelectAll: () => {},
|
||||
withAccessRights: false,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange: (access) => {},
|
||||
withCancelButton: false,
|
||||
cancelButtonLabel: "Cancel",
|
||||
onCancel: () => {},
|
||||
emptyScreenImage: EmptyScreenFilter,
|
||||
emptyScreenHeader: "No other accounts here yet",
|
||||
emptyScreenDescription:
|
||||
"The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
searchEmptyScreenImage: EmptyScreenFilter,
|
||||
searchEmptyScreenHeader: "No other accounts here yet search",
|
||||
searchEmptyScreenDescription:
|
||||
" SEARCH !!! The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
totalItems,
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
isLoading: false,
|
||||
withBreadCrumbs: true,
|
||||
breadCrumbs: [
|
||||
{ id: 1, label: "DocSpace" },
|
||||
{ id: 2, label: "1111111" },
|
||||
{ id: 3, label: "21222222222" },
|
||||
{ id: 4, label: "32222222222222222222222222222222222222" },
|
||||
{ id: 5, label: "4222222222222222222222222222222222222" },
|
||||
],
|
||||
onSelectBreadCrumb: (item) => {},
|
||||
breadCrumbsLoader: <StyledBreadCrumbsLoader />,
|
||||
withoutBackButton: false,
|
||||
withSearch: false,
|
||||
isBreadCrumbsLoading: false,
|
||||
withFooterInput: false,
|
||||
footerInputHeader: "",
|
||||
footerCheckboxLabel: "",
|
||||
currentFooterInputValue: "",
|
||||
alwaysShowFooter: false,
|
||||
disableAcceptButton: false,
|
||||
descriptionText: "",
|
||||
};
|
||||
|
||||
NewName.args = {
|
||||
height: "485px", // container height
|
||||
headerLabel: "Room list",
|
||||
onBackClick: () => {},
|
||||
searchPlaceholder: "Search",
|
||||
searchValue: "",
|
||||
items: renderedItems,
|
||||
onSelect: (item) => {},
|
||||
isMultiSelect: false,
|
||||
selectedItems: selectedItems,
|
||||
acceptButtonLabel: "Add",
|
||||
onAccept: (items, access) => {},
|
||||
withSelectAll: false,
|
||||
selectAllLabel: "All accounts",
|
||||
selectAllIcon: ArchiveSvgUrl,
|
||||
onSelectAll: () => {},
|
||||
withAccessRights: false,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange: (access) => {},
|
||||
withCancelButton: false,
|
||||
cancelButtonLabel: "Cancel",
|
||||
onCancel: () => {},
|
||||
emptyScreenImage: EmptyScreenFilter,
|
||||
emptyScreenHeader: "No other accounts here yet",
|
||||
emptyScreenDescription:
|
||||
"The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
searchEmptyScreenImage: EmptyScreenFilter,
|
||||
searchEmptyScreenHeader: "No other accounts here yet search",
|
||||
searchEmptyScreenDescription:
|
||||
" SEARCH !!! The list of users previously invited to DocSpace or separate rooms will appear here. You will be able to invite these users for collaboration at any time.",
|
||||
totalItems,
|
||||
hasNextPage: true,
|
||||
isNextPageLoading: false,
|
||||
isLoading: false,
|
||||
withBreadCrumbs: true,
|
||||
breadCrumbs: [
|
||||
{ id: 1, label: "DocSpace" },
|
||||
{ id: 2, label: "1111111" },
|
||||
{ id: 3, label: "21222222222" },
|
||||
],
|
||||
onSelectBreadCrumb: (item) => {},
|
||||
breadCrumbsLoader: <StyledBreadCrumbsLoader />,
|
||||
withoutBackButton: false,
|
||||
withSearch: false,
|
||||
isBreadCrumbsLoading: false,
|
||||
withFooterInput: true,
|
||||
footerInputHeader: "File name",
|
||||
footerCheckboxLabel: "Open saved document in new tab",
|
||||
currentFooterInputValue: "OldFIleName.docx",
|
||||
alwaysShowFooter: false,
|
||||
disableAcceptButton: false,
|
||||
descriptionText: "",
|
||||
};
|
81
packages/components/selector/Selector.types.ts
Normal file
81
packages/components/selector/Selector.types.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
|
||||
import { Item } from "./sub-components/Item/Item.types";
|
||||
import { BreadCrumb } from "./sub-components/BreadCrumbs/BreadCrumbs.types";
|
||||
|
||||
export type AccessRight = {
|
||||
string: {
|
||||
key: string;
|
||||
label: string;
|
||||
description: string;
|
||||
|
||||
access: string | number;
|
||||
};
|
||||
};
|
||||
|
||||
export type SelectorProps = {
|
||||
id?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
headerLabel: string;
|
||||
withoutBackButton?: boolean;
|
||||
onBackClick?: () => void;
|
||||
withSearch?: boolean;
|
||||
searchPlaceholder?: string;
|
||||
searchValue?: string;
|
||||
onSearch?: (value: string) => void;
|
||||
onClearSearch?: () => void;
|
||||
items: Item[];
|
||||
onSelect: (item: any) => void;
|
||||
isMultiSelect?: boolean;
|
||||
selectedItems?: Item[];
|
||||
acceptButtonLabel: string;
|
||||
onAccept: (
|
||||
selectedItems: Item[],
|
||||
access: AccessRight | null,
|
||||
fileName: string,
|
||||
isFooterCheckboxChecked: boolean
|
||||
) => void;
|
||||
withSelectAll?: boolean;
|
||||
selectAllLabel?: string;
|
||||
selectAllIcon?: string;
|
||||
onSelectAll?: () => void;
|
||||
withAccessRights?: boolean;
|
||||
accessRights?: AccessRight[];
|
||||
selectedAccessRight?: AccessRight;
|
||||
onAccessRightsChange?: (access: AccessRight) => void;
|
||||
withCancelButton?: boolean;
|
||||
cancelButtonLabel?: string;
|
||||
onCancel?: () => void;
|
||||
emptyScreenImage?: string;
|
||||
emptyScreenHeader?: string;
|
||||
emptyScreenDescription?: string;
|
||||
searchEmptyScreenImage?: string;
|
||||
searchEmptyScreenHeader?: string;
|
||||
searchEmptyScreenDescription?: string;
|
||||
hasNextPage?: boolean;
|
||||
isNextPageLoading?: boolean;
|
||||
loadNextPage?: ((startIndex: number, ...rest: any) => Promise<void>) | null;
|
||||
totalItems: number;
|
||||
isLoading?: boolean;
|
||||
searchLoader?: any;
|
||||
rowLoader?: any;
|
||||
withBreadCrumbs?: boolean;
|
||||
breadCrumbs?: BreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: any) => void;
|
||||
breadCrumbsLoader?: any;
|
||||
isBreadCrumbsLoading?: boolean;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
footerInputHeader?: string;
|
||||
currentFooterInputValue?: string;
|
||||
footerCheckboxLabel?: string;
|
||||
alwaysShowFooter?: boolean;
|
||||
disableAcceptButton?: boolean;
|
||||
|
||||
descriptionText?: string;
|
||||
|
||||
acceptButtonId?: string;
|
||||
cancelButtonId?: string;
|
||||
};
|
@ -1,94 +0,0 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import Base from "../themes/base";
|
||||
|
||||
const StyledSelector = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
StyledSelector.defaultProps = { theme: Base };
|
||||
|
||||
const StyledSelectorHeader = styled.div`
|
||||
width: calc(100% - 32px);
|
||||
min-height: 53px;
|
||||
height: 53px;
|
||||
max-height: 53px;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
border-bottom: ${(props) => props.theme.selector.border};
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow-button {
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.heading-text {
|
||||
font-weight: 700;
|
||||
font-size: 21px;
|
||||
line-height: 28px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelectorHeader.defaultProps = { theme: Base };
|
||||
|
||||
const StyledSelectorBody = styled.div`
|
||||
width: 100%;
|
||||
|
||||
height: ${(props) =>
|
||||
props.footerVisible
|
||||
? `calc(100% - 16px - ${props.footerHeight}px - ${props.headerHeight}px)`
|
||||
: `calc(100% - 16px - ${props.headerHeight}px)`};
|
||||
|
||||
padding: 16px 0 0 0;
|
||||
|
||||
.search-input,
|
||||
.search-loader {
|
||||
padding: 0 16px;
|
||||
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelectorBody.defaultProps = { theme: Base };
|
||||
|
||||
const StyledSelectorFooter = styled.div`
|
||||
width: calc(100% - 32px);
|
||||
max-height: 73px;
|
||||
height: 73px;
|
||||
min-height: 73px;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
gap: 8px;
|
||||
|
||||
border-top: ${(props) => props.theme.selector.border};
|
||||
|
||||
.button {
|
||||
min-height: 40px;
|
||||
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelectorFooter.defaultProps = { theme: Base };
|
||||
|
||||
export {
|
||||
StyledSelector,
|
||||
StyledSelectorHeader,
|
||||
StyledSelectorBody,
|
||||
StyledSelectorFooter,
|
||||
};
|
17
packages/components/selector/StyledSelector.ts
Normal file
17
packages/components/selector/StyledSelector.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import Base from "../themes/base";
|
||||
|
||||
const StyledSelector = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
StyledSelector.defaultProps = { theme: Base };
|
||||
|
||||
export { StyledSelector };
|
@ -7,62 +7,101 @@ import Footer from "./sub-components/Footer";
|
||||
|
||||
import { StyledSelector } from "./StyledSelector";
|
||||
|
||||
import { AccessRight, SelectorProps } from "./Selector.types";
|
||||
import { Item } from "./sub-components/Item/Item.types";
|
||||
|
||||
const Selector = ({
|
||||
id,
|
||||
className,
|
||||
style,
|
||||
|
||||
headerLabel,
|
||||
withoutBackButton,
|
||||
onBackClick,
|
||||
|
||||
isBreadCrumbsLoading,
|
||||
breadCrumbsLoader,
|
||||
withBreadCrumbs,
|
||||
breadCrumbs,
|
||||
onSelectBreadCrumb,
|
||||
|
||||
withSearch,
|
||||
searchLoader,
|
||||
searchPlaceholder,
|
||||
searchValue,
|
||||
onSearch,
|
||||
onClearSearch,
|
||||
items,
|
||||
onSelect,
|
||||
isMultiSelect,
|
||||
selectedItems,
|
||||
acceptButtonLabel,
|
||||
onAccept,
|
||||
|
||||
withSelectAll,
|
||||
selectAllLabel,
|
||||
selectAllIcon,
|
||||
onSelectAll,
|
||||
|
||||
items,
|
||||
isMultiSelect,
|
||||
selectedItems,
|
||||
acceptButtonLabel,
|
||||
onSelect,
|
||||
onAccept,
|
||||
rowLoader,
|
||||
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccessRightsChange,
|
||||
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
onCancel,
|
||||
|
||||
emptyScreenImage,
|
||||
emptyScreenHeader,
|
||||
emptyScreenDescription,
|
||||
|
||||
searchEmptyScreenImage,
|
||||
searchEmptyScreenHeader,
|
||||
searchEmptyScreenDescription,
|
||||
|
||||
hasNextPage,
|
||||
isNextPageLoading,
|
||||
loadNextPage,
|
||||
totalItems,
|
||||
isLoading,
|
||||
searchLoader,
|
||||
rowLoader,
|
||||
}) => {
|
||||
const [footerVisible, setFooterVisible] = React.useState(false);
|
||||
|
||||
const [isSearch, setIsSearch] = React.useState(false);
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
footerInputHeader,
|
||||
footerCheckboxLabel,
|
||||
currentFooterInputValue,
|
||||
|
||||
const [renderedItems, setRenderedItems] = React.useState([]);
|
||||
const [newSelectedItems, setNewSelectedItems] = React.useState([]);
|
||||
alwaysShowFooter,
|
||||
disableAcceptButton,
|
||||
|
||||
const [selectedAccess, setSelectedAccess] = React.useState({});
|
||||
descriptionText,
|
||||
acceptButtonId,
|
||||
cancelButtonId,
|
||||
}: SelectorProps) => {
|
||||
const [footerVisible, setFooterVisible] = React.useState<boolean>(false);
|
||||
const [isSearch, setIsSearch] = React.useState<boolean>(false);
|
||||
|
||||
const [renderedItems, setRenderedItems] = React.useState<Item[]>([]);
|
||||
const [newSelectedItems, setNewSelectedItems] = React.useState<Item[]>([]);
|
||||
|
||||
const [newFooterInputValue, setNewFooterInputValue] = React.useState<string>(
|
||||
currentFooterInputValue || ""
|
||||
);
|
||||
const [isFooterCheckboxChecked, setIsFooterCheckboxChecked] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const [selectedAccess, setSelectedAccess] =
|
||||
React.useState<AccessRight | null>(null);
|
||||
|
||||
const onBackClickAction = React.useCallback(() => {
|
||||
onBackClick && onBackClick();
|
||||
}, [onBackClick]);
|
||||
|
||||
const onSearchAction = React.useCallback(
|
||||
(value) => {
|
||||
(value: string) => {
|
||||
onSearch && onSearch(value);
|
||||
|
||||
setIsSearch(true);
|
||||
@ -75,11 +114,12 @@ const Selector = ({
|
||||
setIsSearch(false);
|
||||
}, [onClearSearch]);
|
||||
|
||||
const onSelectAction = (item) => {
|
||||
const onSelectAction = (item: Item) => {
|
||||
onSelect &&
|
||||
onSelect({
|
||||
...item,
|
||||
id: item.id,
|
||||
email: item.email,
|
||||
email: item.email || "",
|
||||
avatar: item.avatar,
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
@ -111,9 +151,6 @@ const Selector = ({
|
||||
value.push({
|
||||
id: item.id,
|
||||
email: item.email,
|
||||
avatar: item.avatar,
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
...item,
|
||||
});
|
||||
|
||||
@ -138,9 +175,7 @@ const Selector = ({
|
||||
const newItem = {
|
||||
id: item.id,
|
||||
email: item.email,
|
||||
avatar: item.avatar,
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
|
||||
...item,
|
||||
};
|
||||
|
||||
@ -176,16 +211,22 @@ const Selector = ({
|
||||
}
|
||||
}, [items, newSelectedItems]);
|
||||
|
||||
const onAcceptAction = React.useCallback(() => {
|
||||
onAccept && onAccept(newSelectedItems, selectedAccess);
|
||||
}, [newSelectedItems, selectedAccess]);
|
||||
const onAcceptAction = () => {
|
||||
onAccept &&
|
||||
onAccept(
|
||||
newSelectedItems,
|
||||
selectedAccess,
|
||||
newFooterInputValue,
|
||||
isFooterCheckboxChecked
|
||||
);
|
||||
};
|
||||
|
||||
const onCancelAction = React.useCallback(() => {
|
||||
onCancel && onCancel();
|
||||
}, [onCancel]);
|
||||
|
||||
const onChangeAccessRightsAction = React.useCallback(
|
||||
(access) => {
|
||||
(access: AccessRight) => {
|
||||
setSelectedAccess({ ...access });
|
||||
onAccessRightsChange && onAccessRightsChange(access);
|
||||
},
|
||||
@ -193,14 +234,14 @@ const Selector = ({
|
||||
);
|
||||
|
||||
const compareSelectedItems = React.useCallback(
|
||||
(newList) => {
|
||||
(newList: Item[]) => {
|
||||
let isEqual = true;
|
||||
|
||||
if (selectedItems.length !== newList.length) {
|
||||
if (selectedItems?.length !== newList.length) {
|
||||
return setFooterVisible(true);
|
||||
}
|
||||
|
||||
if (newList.length === 0 && selectedItems.length === 0) {
|
||||
if (newList.length === 0 && selectedItems?.length === 0) {
|
||||
return setFooterVisible(false);
|
||||
}
|
||||
|
||||
@ -214,14 +255,14 @@ const Selector = ({
|
||||
);
|
||||
|
||||
const loadMoreItems = React.useCallback(
|
||||
(startIndex) => {
|
||||
(startIndex: number) => {
|
||||
!isNextPageLoading && loadNextPage && loadNextPage(startIndex - 1);
|
||||
},
|
||||
[isNextPageLoading, loadNextPage]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedAccess({ ...selectedAccessRight });
|
||||
if (!!selectedAccessRight) setSelectedAccess({ ...selectedAccessRight });
|
||||
}, [selectedAccessRight]);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -251,10 +292,14 @@ const Selector = ({
|
||||
|
||||
return (
|
||||
<StyledSelector id={id} className={className} style={style}>
|
||||
<Header onBackClickAction={onBackClickAction} headerLabel={headerLabel} />
|
||||
<Header
|
||||
onBackClickAction={onBackClickAction}
|
||||
headerLabel={headerLabel}
|
||||
withoutBackButton={withoutBackButton}
|
||||
/>
|
||||
|
||||
<Body
|
||||
footerVisible={footerVisible}
|
||||
footerVisible={footerVisible || !!alwaysShowFooter}
|
||||
isSearch={isSearch}
|
||||
isAllIndeterminate={
|
||||
newSelectedItems.length !== renderedItems.length &&
|
||||
@ -288,9 +333,18 @@ const Selector = ({
|
||||
isLoading={isLoading}
|
||||
searchLoader={searchLoader}
|
||||
rowLoader={rowLoader}
|
||||
withBreadCrumbs={withBreadCrumbs}
|
||||
breadCrumbs={breadCrumbs}
|
||||
onSelectBreadCrumb={onSelectBreadCrumb}
|
||||
breadCrumbsLoader={breadCrumbsLoader}
|
||||
isBreadCrumbsLoading={isBreadCrumbsLoading}
|
||||
withSearch={withSearch}
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
descriptionText={descriptionText}
|
||||
/>
|
||||
|
||||
{footerVisible && (
|
||||
{(footerVisible || alwaysShowFooter) && (
|
||||
<Footer
|
||||
isMultiSelect={isMultiSelect}
|
||||
acceptButtonLabel={acceptButtonLabel}
|
||||
@ -303,109 +357,132 @@ const Selector = ({
|
||||
onAccept={onAcceptAction}
|
||||
onCancel={onCancelAction}
|
||||
onChangeAccessRights={onChangeAccessRightsAction}
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
footerInputHeader={footerInputHeader}
|
||||
footerCheckboxLabel={footerCheckboxLabel}
|
||||
currentFooterInputValue={newFooterInputValue}
|
||||
setNewFooterInputValue={setNewFooterInputValue}
|
||||
isFooterCheckboxChecked={isFooterCheckboxChecked}
|
||||
setIsFooterCheckboxChecked={setIsFooterCheckboxChecked}
|
||||
disableAcceptButton={
|
||||
withFooterInput
|
||||
? disableAcceptButton
|
||||
: disableAcceptButton && !newFooterInputValue.trim()
|
||||
}
|
||||
acceptButtonId={acceptButtonId}
|
||||
cancelButtonId={cancelButtonId}
|
||||
/>
|
||||
)}
|
||||
</StyledSelector>
|
||||
);
|
||||
};
|
||||
|
||||
Selector.propTypes = {
|
||||
/** Accepts id */
|
||||
id: PropTypes.string,
|
||||
/** Accepts class */
|
||||
className: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
/** Accepts css style */
|
||||
style: PropTypes.object,
|
||||
// Selector.propTypes = {
|
||||
// /** Accepts id */
|
||||
// id: PropTypes.string,
|
||||
// /** Accepts class */
|
||||
// className: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
// /** Accepts css style */
|
||||
// style: PropTypes.object,
|
||||
|
||||
/** Selector header text */
|
||||
headerLabel: PropTypes.string,
|
||||
/** Sets a callback function that is triggered when the header arrow is clicked */
|
||||
onBackClick: PropTypes.func,
|
||||
// /** Selector header text */
|
||||
// headerLabel: PropTypes.string,
|
||||
// /** Hide header back button */
|
||||
// withoutBackButton: PropTypes.bool,
|
||||
// /** Sets a callback function that is triggered when the header arrow is clicked */
|
||||
// onBackClick: PropTypes.func,
|
||||
|
||||
/** Placeholder for search input */
|
||||
searchPlaceholder: PropTypes.string,
|
||||
/** Start value for search input */
|
||||
searchValue: PropTypes.string,
|
||||
/** Sets a callback function that is triggered when the user stops typing */
|
||||
onSearch: PropTypes.func,
|
||||
/** Sets a callback function that is triggered when the clear icon of the search input is clicked */
|
||||
onClearSearch: PropTypes.func,
|
||||
// /** Placeholder for search input */
|
||||
// searchPlaceholder: PropTypes.string,
|
||||
// /** Start value for search input */
|
||||
// searchValue: PropTypes.string,
|
||||
// /** Sets a callback function that is triggered when the user stops typing */
|
||||
// onSearch: PropTypes.func,
|
||||
// /** Sets a callback function that is triggered when the clear icon of the search input is clicked */
|
||||
// onClearSearch: PropTypes.func,
|
||||
|
||||
/** Displays items */
|
||||
items: PropTypes.array,
|
||||
/** Sets a callback function that is triggered when the item is clicked */
|
||||
onSelect: PropTypes.func,
|
||||
// /** Displays items */
|
||||
// items: PropTypes.array,
|
||||
// /** Sets a callback function that is triggered when the item is clicked */
|
||||
// onSelect: PropTypes.func,
|
||||
|
||||
/** Allows selecting multiple objects */
|
||||
isMultiSelect: PropTypes.bool,
|
||||
/** Sets the items to present a checked state */
|
||||
selectedItems: PropTypes.array,
|
||||
/** Accepts button text */
|
||||
acceptButtonLabel: PropTypes.string,
|
||||
/** Sets a callback function that is triggered when the accept button is clicked */
|
||||
onAccept: PropTypes.func,
|
||||
// /** Allows selecting multiple objects */
|
||||
// isMultiSelect: PropTypes.bool,
|
||||
// /** Sets the items to present a checked state */
|
||||
// selectedItems: PropTypes.array,
|
||||
// /** Accepts button text */
|
||||
// acceptButtonLabel: PropTypes.string,
|
||||
// /** Sets a callback function that is triggered when the accept button is clicked */
|
||||
// onAccept: PropTypes.func,
|
||||
|
||||
/** Adds an option for selecting all items */
|
||||
withSelectAll: PropTypes.bool,
|
||||
/** Text for selecting all components */
|
||||
selectAllLabel: PropTypes.string,
|
||||
/** Icon for selecting all components */
|
||||
selectAllIcon: PropTypes.string,
|
||||
/** Sets a callback function that is triggered when SelectAll is clicked */
|
||||
onSelectAll: PropTypes.func,
|
||||
// /** Adds an option for selecting all items */
|
||||
// withSelectAll: PropTypes.bool,
|
||||
// /** Text for selecting all components */
|
||||
// selectAllLabel: PropTypes.string,
|
||||
// /** Icon for selecting all components */
|
||||
// selectAllIcon: PropTypes.string,
|
||||
// /** Sets a callback function that is triggered when SelectAll is clicked */
|
||||
// onSelectAll: PropTypes.func,
|
||||
|
||||
/** Adds combobox for displaying access rights at the footer */
|
||||
withAccessRights: PropTypes.bool,
|
||||
/** Access rights items */
|
||||
accessRights: PropTypes.array,
|
||||
/** Selected access rights items */
|
||||
selectedAccessRight: PropTypes.object,
|
||||
/** Sets a callback function that is triggered when the access rights are changed */
|
||||
onAccessRightsChange: PropTypes.func,
|
||||
// /** Adds combobox for displaying access rights at the footer */
|
||||
// withAccessRights: PropTypes.bool,
|
||||
// /** Access rights items */
|
||||
// accessRights: PropTypes.array,
|
||||
// /** Selected access rights items */
|
||||
// selectedAccessRight: PropTypes.object,
|
||||
// /** Sets a callback function that is triggered when the access rights are changed */
|
||||
// onAccessRightsChange: PropTypes.func,
|
||||
|
||||
/** Adds a cancel button at the footer */
|
||||
withCancelButton: PropTypes.bool,
|
||||
/** Displays text in the cancel button */
|
||||
cancelButtonLabel: PropTypes.string,
|
||||
/** Sets a callback function that is triggered when the cancel button is clicked */
|
||||
onCancel: PropTypes.func,
|
||||
// /** Adds a cancel button at the footer */
|
||||
// withCancelButton: PropTypes.bool,
|
||||
// /** Displays text in the cancel button */
|
||||
// cancelButtonLabel: PropTypes.string,
|
||||
// /** Sets a callback function that is triggered when the cancel button is clicked */
|
||||
// onCancel: PropTypes.func,
|
||||
|
||||
/** Image for default empty screen */
|
||||
emptyScreenImage: PropTypes.string,
|
||||
/** Header for default empty screen */
|
||||
emptyScreenHeader: PropTypes.string,
|
||||
/** Description for default empty screen */
|
||||
emptyScreenDescription: PropTypes.string,
|
||||
// /** Image for default empty screen */
|
||||
// emptyScreenImage: PropTypes.string,
|
||||
// /** Header for default empty screen */
|
||||
// emptyScreenHeader: PropTypes.string,
|
||||
// /** Description for default empty screen */
|
||||
// emptyScreenDescription: PropTypes.string,
|
||||
|
||||
/** Image for search empty screen */
|
||||
searchEmptyScreenImage: PropTypes.string,
|
||||
/** Header for search empty screen */
|
||||
searchEmptyScreenHeader: PropTypes.string,
|
||||
/** Description for search empty screen */
|
||||
searchEmptyScreenDescription: PropTypes.string,
|
||||
// /** Image for search empty screen */
|
||||
// searchEmptyScreenImage: PropTypes.string,
|
||||
// /** Header for search empty screen */
|
||||
// searchEmptyScreenHeader: PropTypes.string,
|
||||
// /** Description for search empty screen */
|
||||
// searchEmptyScreenDescription: PropTypes.string,
|
||||
|
||||
/** Counts items for infinity scroll */
|
||||
totalItems: PropTypes.number,
|
||||
/** Sets the next page for the infinity scroll */
|
||||
hasNextPage: PropTypes.bool,
|
||||
/** Notifies that the next page is loading */
|
||||
isNextPageLoading: PropTypes.bool,
|
||||
/** Sets a callback function that is invoked to load the next page */
|
||||
loadNextPage: PropTypes.func,
|
||||
// /** Counts items for infinity scroll */
|
||||
// totalItems: PropTypes.number,
|
||||
// /** Sets the next page for the infinity scroll */
|
||||
// hasNextPage: PropTypes.bool,
|
||||
// /** Notifies that the next page is loading */
|
||||
// isNextPageLoading: PropTypes.bool,
|
||||
// /** Sets a callback function that is invoked to load the next page */
|
||||
// loadNextPage: PropTypes.func,
|
||||
|
||||
/** Sets loading state for select */
|
||||
isLoading: PropTypes.bool,
|
||||
/** Loader element for search block */
|
||||
searchLoader: PropTypes.node,
|
||||
/** Loader element for item */
|
||||
rowLoader: PropTypes.node,
|
||||
};
|
||||
// /** Sets loading state for select */
|
||||
// isLoading: PropTypes.bool,
|
||||
// /** Loader element for search block */
|
||||
// searchLoader: PropTypes.node,
|
||||
// /** Loader element for item */
|
||||
// rowLoader: PropTypes.node,
|
||||
// };
|
||||
|
||||
Selector.defaultProps = {
|
||||
isMultiSelect: false,
|
||||
withSelectAll: false,
|
||||
withAccessRights: false,
|
||||
withCancelButton: false,
|
||||
withoutBackButton: false,
|
||||
isBreadCrumbsLoading: false,
|
||||
withSearch: true,
|
||||
withFooterInput: false,
|
||||
alwaysShowFooter: false,
|
||||
disableAcceptButton: false,
|
||||
|
||||
selectedItems: [],
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { BreadCrumb } from "../BreadCrumbs/BreadCrumbs.types";
|
||||
import { Item } from "./../Item/Item.types";
|
||||
|
||||
export type BodyProps = {
|
||||
footerVisible: boolean;
|
||||
isSearch: boolean;
|
||||
isAllIndeterminate?: boolean;
|
||||
isAllChecked?: boolean;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
withSearch?: boolean;
|
||||
onSearch: (value: string) => void;
|
||||
onClearSearch: () => void;
|
||||
items: Item[];
|
||||
onSelect: (item: Item) => void;
|
||||
isMultiSelect?: boolean;
|
||||
withSelectAll?: boolean;
|
||||
selectAllLabel?: string;
|
||||
selectAllIcon?: string;
|
||||
onSelectAll?: () => void;
|
||||
emptyScreenImage?: string;
|
||||
emptyScreenHeader?: string;
|
||||
emptyScreenDescription?: string;
|
||||
searchEmptyScreenImage?: string;
|
||||
searchEmptyScreenHeader?: string;
|
||||
searchEmptyScreenDescription?: string;
|
||||
loadMoreItems: (startIndex: number) => void;
|
||||
hasNextPage?: boolean;
|
||||
isNextPageLoading?: boolean;
|
||||
totalItems: number;
|
||||
isLoading?: boolean;
|
||||
searchLoader: any;
|
||||
rowLoader: any;
|
||||
withBreadCrumbs?: boolean;
|
||||
breadCrumbs?: BreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: BreadCrumb) => void;
|
||||
breadCrumbsLoader?: any;
|
||||
isBreadCrumbsLoading?: boolean;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
|
||||
descriptionText?: string;
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import Base from "../../../themes/base";
|
||||
|
||||
const StyledBody = styled.div<{
|
||||
footerVisible: boolean;
|
||||
footerHeight: number;
|
||||
headerHeight: number;
|
||||
}>`
|
||||
width: 100%;
|
||||
|
||||
height: ${(props) =>
|
||||
props.footerVisible
|
||||
? `calc(100% - 16px - ${props.footerHeight}px - ${props.headerHeight}px)`
|
||||
: `calc(100% - 16px - ${props.headerHeight}px)`};
|
||||
|
||||
padding: 16px 0 0 0;
|
||||
|
||||
.search-input,
|
||||
.search-loader {
|
||||
padding: 0 16px;
|
||||
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.body-description-text {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
color: ${(props) => props.theme.selector.bodyDescriptionText};
|
||||
}
|
||||
`;
|
||||
|
||||
StyledBody.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledBody;
|
@ -4,18 +4,26 @@ import { FixedSizeList as List } from "react-window";
|
||||
|
||||
import CustomScrollbarsVirtualList from "../../../scrollbar/custom-scrollbars-virtual-list";
|
||||
|
||||
import Text from "../../../text";
|
||||
|
||||
import Search from "../Search";
|
||||
import SelectAll from "../SelectAll";
|
||||
import Item from "../Item";
|
||||
import EmptyScreen from "../EmptyScreen";
|
||||
|
||||
import { StyledSelectorBody } from "../../StyledSelector";
|
||||
import StyledBody from "./StyledBody";
|
||||
import { BodyProps } from "./Body.types";
|
||||
import BreadCrumbs from "../BreadCrumbs";
|
||||
|
||||
const CONTAINER_PADDING = 16;
|
||||
const HEADER_HEIGHT = 54;
|
||||
const BREAD_CRUMBS_HEIGHT = 38;
|
||||
const SEARCH_HEIGHT = 44;
|
||||
const BODY_DESCRIPTION_TEXT_HEIGHT = 32;
|
||||
const SELECT_ALL_HEIGHT = 73;
|
||||
const FOOTER_HEIGHT = 73;
|
||||
const FOOTER_WITH_NEW_NAME_HEIGHT = 145;
|
||||
const FOOTER_WITH_CHECKBOX_HEIGHT = 181;
|
||||
|
||||
const Body = ({
|
||||
footerVisible,
|
||||
@ -45,14 +53,22 @@ const Body = ({
|
||||
isLoading,
|
||||
searchLoader,
|
||||
rowLoader,
|
||||
}) => {
|
||||
const [bodyHeight, setBodyHeight] = React.useState(null);
|
||||
withBreadCrumbs,
|
||||
breadCrumbs,
|
||||
onSelectBreadCrumb,
|
||||
breadCrumbsLoader,
|
||||
withSearch,
|
||||
isBreadCrumbsLoading,
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
descriptionText,
|
||||
}: BodyProps) => {
|
||||
const [bodyHeight, setBodyHeight] = React.useState(0);
|
||||
|
||||
const bodyRef = React.useRef(null);
|
||||
const listOptionsRef = React.useRef(null);
|
||||
const bodyRef = React.useRef<HTMLDivElement>(null);
|
||||
const listOptionsRef = React.useRef<any>(null);
|
||||
|
||||
const itemsCount = hasNextPage ? items.length + 1 : items.length;
|
||||
const withSearch = isSearch || itemsCount > 0;
|
||||
|
||||
const resetCache = React.useCallback(() => {
|
||||
if (listOptionsRef && listOptionsRef.current) {
|
||||
@ -60,23 +76,14 @@ const Body = ({
|
||||
}
|
||||
}, [listOptionsRef.current]);
|
||||
|
||||
// const onBodyRef = React.useCallback(
|
||||
// (node) => {
|
||||
// if (node) {
|
||||
// node.addEventListener("resize", onBodyResize);
|
||||
// bodyRef.current = node;
|
||||
// setBodyHeight(node.offsetHeight);
|
||||
// }
|
||||
// },
|
||||
// [onBodyResize]
|
||||
// );
|
||||
|
||||
const onBodyResize = React.useCallback(() => {
|
||||
setBodyHeight(bodyRef.current.offsetHeight);
|
||||
if (bodyRef && bodyRef.current) {
|
||||
setBodyHeight(bodyRef.current.offsetHeight);
|
||||
}
|
||||
}, [bodyRef?.current?.offsetHeight]);
|
||||
|
||||
const isItemLoaded = React.useCallback(
|
||||
(index) => {
|
||||
(index: number) => {
|
||||
return !hasNextPage || index < itemsCount;
|
||||
},
|
||||
[hasNextPage, itemsCount]
|
||||
@ -99,21 +106,43 @@ const Body = ({
|
||||
|
||||
let listHeight = bodyHeight - CONTAINER_PADDING;
|
||||
|
||||
if (withSearch) listHeight -= SEARCH_HEIGHT;
|
||||
if (withSearch || isSearch || itemsCount > 0) listHeight -= SEARCH_HEIGHT;
|
||||
|
||||
if (withBreadCrumbs) listHeight -= BREAD_CRUMBS_HEIGHT;
|
||||
|
||||
if (isMultiSelect && withSelectAll && !isSearch)
|
||||
listHeight -= SELECT_ALL_HEIGHT;
|
||||
|
||||
if (!!descriptionText) listHeight -= BODY_DESCRIPTION_TEXT_HEIGHT;
|
||||
|
||||
return (
|
||||
<StyledSelectorBody
|
||||
<StyledBody
|
||||
ref={bodyRef}
|
||||
footerHeight={FOOTER_HEIGHT}
|
||||
footerHeight={
|
||||
withFooterCheckbox
|
||||
? FOOTER_WITH_CHECKBOX_HEIGHT
|
||||
: withFooterInput
|
||||
? FOOTER_WITH_NEW_NAME_HEIGHT
|
||||
: FOOTER_HEIGHT
|
||||
}
|
||||
headerHeight={HEADER_HEIGHT}
|
||||
footerVisible={footerVisible}
|
||||
>
|
||||
{isLoading && !isSearch ? (
|
||||
{withBreadCrumbs ? (
|
||||
isBreadCrumbsLoading ? (
|
||||
breadCrumbsLoader
|
||||
) : (
|
||||
<BreadCrumbs
|
||||
breadCrumbs={breadCrumbs}
|
||||
onSelectBreadCrumb={onSelectBreadCrumb}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{isBreadCrumbsLoading ? (
|
||||
searchLoader
|
||||
) : withSearch ? (
|
||||
) : withSearch || isSearch || (itemsCount > 0 && withSearch) ? (
|
||||
<Search
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
@ -126,7 +155,7 @@ const Body = ({
|
||||
rowLoader
|
||||
) : itemsCount === 0 ? (
|
||||
<EmptyScreen
|
||||
withSearch={isSearch && value}
|
||||
withSearch={isSearch && !!value}
|
||||
image={emptyScreenImage}
|
||||
header={emptyScreenHeader}
|
||||
description={emptyScreenDescription}
|
||||
@ -136,6 +165,9 @@ const Body = ({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!!descriptionText && (
|
||||
<Text className="body-description-text">{descriptionText}</Text>
|
||||
)}
|
||||
{isMultiSelect && withSelectAll && !isSearch && (
|
||||
<SelectAll
|
||||
label={selectAllLabel}
|
||||
@ -180,7 +212,7 @@ const Body = ({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledSelectorBody>
|
||||
</StyledBody>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,23 @@
|
||||
export type BreadCrumb = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isRoom?: boolean;
|
||||
minWidth?: string;
|
||||
onClick?: (e: any, open: any, item: BreadCrumb) => void;
|
||||
};
|
||||
|
||||
export type DisplayedItem = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isArrow: boolean;
|
||||
isList: boolean;
|
||||
isRoom?: boolean;
|
||||
|
||||
listItems?: BreadCrumb[];
|
||||
};
|
||||
|
||||
export type BreadCrumbsProps = {
|
||||
breadCrumbs?: BreadCrumb[];
|
||||
onSelectBreadCrumb?: (item: BreadCrumb) => void;
|
||||
isLoading?: boolean;
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import ArrowRightSvg from "PUBLIC_DIR/images/arrow.right.react.svg";
|
||||
|
||||
import Text from "../../../text";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledBreadCrumbs = styled.div<{
|
||||
itemsCount: number;
|
||||
gridTemplateColumns: string;
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
|
||||
padding: 0 16px 16px 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: ${(props) => props.gridTemplateColumns};
|
||||
|
||||
grid-column-gap: 8px;
|
||||
|
||||
align-items: center;
|
||||
|
||||
.context-menu-button {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledItemText = styled(Text)<{ isCurrent: boolean; isLoading: boolean }>`
|
||||
${(props) =>
|
||||
!props.isCurrent &&
|
||||
css`
|
||||
color: ${props.theme.selector.breadCrumbs.prevItemColor};
|
||||
|
||||
${!props.isLoading && `cursor: pointer`};
|
||||
`}
|
||||
`;
|
||||
|
||||
StyledItemText.defaultProps = { theme: Base };
|
||||
|
||||
const StyledArrowRightSvg = styled(ArrowRightSvg)`
|
||||
path {
|
||||
fill: ${(props) => props.theme.selector.breadCrumbs.arrowRightColor};
|
||||
}
|
||||
`;
|
||||
|
||||
StyledArrowRightSvg.defaultProps = { theme: Base };
|
||||
|
||||
export { StyledBreadCrumbs, StyledItemText, StyledArrowRightSvg };
|
@ -0,0 +1,201 @@
|
||||
import React from "react";
|
||||
|
||||
import ContextMenuButton from "../../../context-menu-button";
|
||||
|
||||
import {
|
||||
BreadCrumb,
|
||||
BreadCrumbsProps,
|
||||
DisplayedItem,
|
||||
} from "./BreadCrumbs.types";
|
||||
|
||||
import {
|
||||
StyledBreadCrumbs,
|
||||
StyledItemText,
|
||||
StyledArrowRightSvg,
|
||||
} from "./StyledBreadCrumbs";
|
||||
|
||||
const BreadCrumbs = ({
|
||||
breadCrumbs,
|
||||
onSelectBreadCrumb,
|
||||
isLoading,
|
||||
}: BreadCrumbsProps) => {
|
||||
const [displayedItems, setDisplayedItems] = React.useState<DisplayedItem[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const onClickItem = React.useCallback(
|
||||
(e, open, item: BreadCrumb) => {
|
||||
if (isLoading) return;
|
||||
onSelectBreadCrumb && onSelectBreadCrumb(item);
|
||||
},
|
||||
[breadCrumbs, isLoading]
|
||||
);
|
||||
|
||||
const calculateDisplayedItems = React.useCallback(
|
||||
(items: BreadCrumb[]) => {
|
||||
const itemsLength = items.length;
|
||||
const oldItems: BreadCrumb[] = [];
|
||||
|
||||
items.forEach((item) =>
|
||||
oldItems.push({
|
||||
...item,
|
||||
id: item.id.toString(),
|
||||
})
|
||||
);
|
||||
if (itemsLength > 0) {
|
||||
const newItems: DisplayedItem[] = [];
|
||||
|
||||
if (itemsLength <= 3) {
|
||||
oldItems.forEach((item, index) => {
|
||||
newItems.push({
|
||||
...item,
|
||||
isArrow: false,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
if (index !== oldItems.length - 1) {
|
||||
newItems.push({
|
||||
id: `arrow-${index}`,
|
||||
label: "",
|
||||
isArrow: true,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
newItems.push({
|
||||
...oldItems[0],
|
||||
isArrow: false,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
id: "arrow-1",
|
||||
label: "",
|
||||
isArrow: true,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
id: "drop-down-item",
|
||||
label: "",
|
||||
isArrow: false,
|
||||
isList: true,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
id: "arrow-2",
|
||||
label: "",
|
||||
isArrow: true,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
...oldItems[itemsLength - 2],
|
||||
isArrow: false,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
id: "arrow-3",
|
||||
label: "",
|
||||
isArrow: true,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
newItems.push({
|
||||
...oldItems[itemsLength - 1],
|
||||
isArrow: false,
|
||||
isList: false,
|
||||
listItems: [],
|
||||
});
|
||||
|
||||
oldItems.splice(0, 1);
|
||||
oldItems.splice(oldItems.length - 2, 2);
|
||||
|
||||
oldItems.forEach((item) => {
|
||||
newItems[2].listItems?.push({
|
||||
...item,
|
||||
minWidth: "150px",
|
||||
onClick: onClickItem,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return setDisplayedItems(newItems);
|
||||
}
|
||||
},
|
||||
[onClickItem]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (breadCrumbs && breadCrumbs.length > 0) {
|
||||
calculateDisplayedItems(breadCrumbs);
|
||||
}
|
||||
}, [breadCrumbs, calculateDisplayedItems]);
|
||||
|
||||
let gridTemplateColumns = "minmax(1px, max-content)";
|
||||
|
||||
if (displayedItems.length > 5) {
|
||||
gridTemplateColumns =
|
||||
"minmax(1px, max-content) 12px 16px 12px minmax(1px, max-content) 12px minmax(1px, max-content)";
|
||||
} else if (displayedItems.length === 5) {
|
||||
gridTemplateColumns =
|
||||
"minmax(1px, max-content) 12px minmax(1px, max-content) 12px minmax(1px, max-content)";
|
||||
} else if (displayedItems.length === 3) {
|
||||
gridTemplateColumns =
|
||||
"minmax(1px, max-content) 12px minmax(1px, max-content)";
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledBreadCrumbs
|
||||
itemsCount={displayedItems.length}
|
||||
gridTemplateColumns={gridTemplateColumns}
|
||||
>
|
||||
{displayedItems.map((item, index) =>
|
||||
item.isList ? (
|
||||
<ContextMenuButton
|
||||
key={`bread-crumb-item-${item.id}-${index}`}
|
||||
className="context-menu-button"
|
||||
getData={() => item.listItems}
|
||||
/>
|
||||
) : item.isArrow ? (
|
||||
<StyledArrowRightSvg key={`bread-crumb-item-${item.id}-${index}`} />
|
||||
) : (
|
||||
<StyledItemText
|
||||
key={`bread-crumb-item-${item.id}-${index}`}
|
||||
fontSize={"16px"}
|
||||
fontWeight={600}
|
||||
lineHeight={"22px"}
|
||||
noSelect
|
||||
truncate
|
||||
isCurrent={index === displayedItems.length - 1}
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
if (index === displayedItems.length - 1 || isLoading) return;
|
||||
|
||||
onSelectBreadCrumb &&
|
||||
onSelectBreadCrumb({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
isRoom: item.isRoom,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</StyledItemText>
|
||||
)
|
||||
)}
|
||||
</StyledBreadCrumbs>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreadCrumbs;
|
@ -0,0 +1,9 @@
|
||||
export type EmptyScreenProps = {
|
||||
image?: string;
|
||||
header?: string;
|
||||
description?: string;
|
||||
searchImage?: string;
|
||||
searchHeader?: string;
|
||||
searchDescription?: string;
|
||||
withSearch: boolean;
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledEmptyScreen = styled.div<{ withSearch: boolean }>`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
margin-top: ${(props) => (props.withSearch ? "80px" : "64px")};
|
||||
padding: 0 28px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.empty-image {
|
||||
max-width: 72px;
|
||||
max-height: 72px;
|
||||
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.empty-header {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-description {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
color: ${(props) => props.theme.selector.emptyScreen.descriptionColor};
|
||||
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledEmptyScreen.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledEmptyScreen;
|
@ -1,79 +0,0 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Heading from "../../../heading";
|
||||
import Text from "../../../text";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
margin-top: ${(props) => (props.withSearch ? "80px" : "64px")};
|
||||
padding: 0 28px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.empty-image {
|
||||
max-width: 72px;
|
||||
max-height: 72px;
|
||||
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.empty-header {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-description {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
color: ${(props) => props.theme.selector.emptyScreen.descriptionColor};
|
||||
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledContainer.defaultProps = { theme: Base };
|
||||
|
||||
const EmptyScreen = ({
|
||||
image,
|
||||
header,
|
||||
description,
|
||||
searchImage,
|
||||
searchHeader,
|
||||
searchDescription,
|
||||
withSearch,
|
||||
}) => {
|
||||
const currentImage = withSearch ? searchImage : image;
|
||||
const currentHeader = withSearch ? searchHeader : header;
|
||||
const currentDescription = withSearch ? searchDescription : description;
|
||||
|
||||
return (
|
||||
<StyledContainer withSearch={withSearch}>
|
||||
<img
|
||||
className="empty-image"
|
||||
src={currentImage}
|
||||
alt="empty-screen-image"
|
||||
/>
|
||||
<Heading level={3} className="empty-header">
|
||||
{currentHeader}
|
||||
</Heading>
|
||||
<Text className="empty-description" noSelect>
|
||||
{currentDescription}
|
||||
</Text>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyScreen;
|
@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
|
||||
import Heading from "../../../heading";
|
||||
import Text from "../../../text";
|
||||
|
||||
import StyledEmptyScreen from "./StyledEmptyScreen";
|
||||
import { EmptyScreenProps } from "./EmptyScreen.type";
|
||||
|
||||
const EmptyScreen = ({
|
||||
image,
|
||||
header,
|
||||
description,
|
||||
searchImage,
|
||||
searchHeader,
|
||||
searchDescription,
|
||||
withSearch,
|
||||
}: EmptyScreenProps) => {
|
||||
const currentImage = withSearch ? searchImage : image;
|
||||
const currentHeader = withSearch ? searchHeader : header;
|
||||
const currentDescription = withSearch ? searchDescription : description;
|
||||
|
||||
return (
|
||||
<StyledEmptyScreen withSearch={withSearch}>
|
||||
<img
|
||||
className="empty-image"
|
||||
src={currentImage}
|
||||
alt="empty-screen-image"
|
||||
/>
|
||||
<Heading level={3} className="empty-header">
|
||||
{currentHeader}
|
||||
</Heading>
|
||||
<Text className="empty-description" noSelect>
|
||||
{currentDescription}
|
||||
</Text>
|
||||
</StyledEmptyScreen>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyScreen;
|
@ -0,0 +1,28 @@
|
||||
import { AccessRight } from "../../Selector.types";
|
||||
|
||||
export type FooterProps = {
|
||||
isMultiSelect?: boolean;
|
||||
acceptButtonLabel: string;
|
||||
selectedItemsCount: number;
|
||||
withCancelButton?: boolean;
|
||||
cancelButtonLabel?: string;
|
||||
withAccessRights?: boolean;
|
||||
accessRights?: AccessRight[];
|
||||
selectedAccessRight?: AccessRight | null;
|
||||
disableAcceptButton?: boolean;
|
||||
onAccept?: () => void;
|
||||
onCancel?: () => void;
|
||||
onChangeAccessRights?: (access: AccessRight) => void;
|
||||
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
footerInputHeader?: string;
|
||||
currentFooterInputValue?: string;
|
||||
footerCheckboxLabel?: string;
|
||||
setNewFooterInputValue?: (value: string) => void;
|
||||
isFooterCheckboxChecked?: boolean;
|
||||
setIsFooterCheckboxChecked?: any;
|
||||
|
||||
acceptButtonId?: string;
|
||||
cancelButtonId?: string;
|
||||
};
|
@ -0,0 +1,87 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import Combobox from "../../../combobox";
|
||||
import Text from "../../../text";
|
||||
import Base from "../../../themes/base";
|
||||
|
||||
const StyledFooter = styled.div<{
|
||||
withFooterInput?: boolean;
|
||||
withFooterCheckbox?: boolean;
|
||||
}>`
|
||||
width: calc(100% - 32px);
|
||||
max-height: ${(props) =>
|
||||
props.withFooterCheckbox
|
||||
? "181px"
|
||||
: props.withFooterInput
|
||||
? "145px"
|
||||
: "73px"};
|
||||
height: ${(props) =>
|
||||
props.withFooterCheckbox
|
||||
? "181px"
|
||||
: props.withFooterInput
|
||||
? "145px"
|
||||
: "73px"};
|
||||
min-height: ${(props) =>
|
||||
props.withFooterCheckbox
|
||||
? "181px"
|
||||
: props.withFooterInput
|
||||
? "145px"
|
||||
: "73px"};
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
border-top: ${(props) => props.theme.selector.border};
|
||||
|
||||
.button {
|
||||
min-height: 40px;
|
||||
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledFooter.defaultProps = { theme: Base };
|
||||
|
||||
const StyledNewNameContainer = styled.div`
|
||||
margin-top: 16px;
|
||||
|
||||
.new-file-input {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledNewNameHeader = styled(Text)`
|
||||
margin-bottom: 4px;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
gap: 8px;
|
||||
|
||||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
const StyledComboBox = styled(Combobox)`
|
||||
margin-bottom: 2px;
|
||||
max-height: 50px;
|
||||
|
||||
.combo-button {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.combo-button-label,
|
||||
.combo-button-label:hover {
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export {
|
||||
StyledFooter,
|
||||
StyledNewNameContainer,
|
||||
StyledNewNameHeader,
|
||||
StyledButtonContainer,
|
||||
StyledComboBox,
|
||||
};
|
@ -1,83 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import styled from "styled-components";
|
||||
|
||||
import Button from "../../../button";
|
||||
import Combobox from "../../../combobox";
|
||||
|
||||
import { StyledSelectorFooter } from "../../StyledSelector";
|
||||
|
||||
const StyledComboBox = styled(Combobox)`
|
||||
margin-bottom: 2px;
|
||||
max-height: 50px;
|
||||
|
||||
.combo-button {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.combo-button-label,
|
||||
.combo-button-label:hover {
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Footer = React.memo(
|
||||
({
|
||||
isMultiSelect,
|
||||
acceptButtonLabel,
|
||||
selectedItemsCount,
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccept,
|
||||
onCancel,
|
||||
onChangeAccessRights,
|
||||
}) => {
|
||||
const label =
|
||||
selectedItemsCount && isMultiSelect
|
||||
? `${acceptButtonLabel} (${selectedItemsCount})`
|
||||
: acceptButtonLabel;
|
||||
|
||||
return (
|
||||
<StyledSelectorFooter>
|
||||
<Button
|
||||
className={"button accept-button"}
|
||||
label={label}
|
||||
primary
|
||||
scale
|
||||
size={"normal"}
|
||||
onClick={onAccept}
|
||||
/>
|
||||
|
||||
{withAccessRights && (
|
||||
<StyledComboBox
|
||||
onSelect={onChangeAccessRights}
|
||||
options={accessRights}
|
||||
size="content"
|
||||
scaled={false}
|
||||
manualWidth="fit-content"
|
||||
selectedOption={selectedAccessRight}
|
||||
showDisabledItems
|
||||
directionX={"right"}
|
||||
directionY={"top"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{withCancelButton && (
|
||||
<Button
|
||||
className={"button cancel-button"}
|
||||
label={cancelButtonLabel}
|
||||
scale
|
||||
size={"normal"}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
)}
|
||||
</StyledSelectorFooter>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Footer;
|
129
packages/components/selector/sub-components/Footer/index.tsx
Normal file
129
packages/components/selector/sub-components/Footer/index.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import React from "react";
|
||||
|
||||
import Button from "../../../button";
|
||||
import TextInput from "../../../text-input";
|
||||
import Checkbox from "../../../checkbox";
|
||||
|
||||
import {
|
||||
StyledFooter,
|
||||
StyledComboBox,
|
||||
StyledButtonContainer,
|
||||
StyledNewNameContainer,
|
||||
StyledNewNameHeader,
|
||||
} from "./StyledFooter";
|
||||
import { FooterProps } from "./Footer.types";
|
||||
|
||||
const Footer = React.memo(
|
||||
({
|
||||
isMultiSelect,
|
||||
acceptButtonLabel,
|
||||
selectedItemsCount,
|
||||
withCancelButton,
|
||||
cancelButtonLabel,
|
||||
withAccessRights,
|
||||
accessRights,
|
||||
selectedAccessRight,
|
||||
onAccept,
|
||||
disableAcceptButton,
|
||||
onCancel,
|
||||
onChangeAccessRights,
|
||||
|
||||
withFooterInput,
|
||||
withFooterCheckbox,
|
||||
footerInputHeader,
|
||||
footerCheckboxLabel,
|
||||
currentFooterInputValue,
|
||||
setNewFooterInputValue,
|
||||
isFooterCheckboxChecked,
|
||||
setIsFooterCheckboxChecked,
|
||||
acceptButtonId,
|
||||
cancelButtonId,
|
||||
}: FooterProps) => {
|
||||
const label =
|
||||
selectedItemsCount && isMultiSelect
|
||||
? `${acceptButtonLabel} (${selectedItemsCount})`
|
||||
: acceptButtonLabel;
|
||||
|
||||
const onChangeFileName = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setNewFooterInputValue && setNewFooterInputValue(value);
|
||||
};
|
||||
|
||||
const onChangeCheckbox = () => {
|
||||
setIsFooterCheckboxChecked &&
|
||||
setIsFooterCheckboxChecked((value: boolean) => !value);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFooter
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
>
|
||||
{withFooterInput && (
|
||||
<StyledNewNameContainer>
|
||||
<StyledNewNameHeader
|
||||
lineHeight={"20px"}
|
||||
fontWeight={600}
|
||||
fontSize={"13px"}
|
||||
>
|
||||
{footerInputHeader}
|
||||
</StyledNewNameHeader>
|
||||
<TextInput
|
||||
className={"new-file-input"}
|
||||
value={currentFooterInputValue}
|
||||
scale
|
||||
onChange={onChangeFileName}
|
||||
/>
|
||||
{withFooterCheckbox && (
|
||||
<Checkbox
|
||||
label={footerCheckboxLabel}
|
||||
isChecked={isFooterCheckboxChecked}
|
||||
onChange={onChangeCheckbox}
|
||||
/>
|
||||
)}
|
||||
</StyledNewNameContainer>
|
||||
)}
|
||||
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
id={acceptButtonId}
|
||||
className={"button accept-button"}
|
||||
label={label}
|
||||
primary
|
||||
scale
|
||||
size={"normal"}
|
||||
isDisabled={disableAcceptButton}
|
||||
onClick={onAccept}
|
||||
/>
|
||||
|
||||
{withAccessRights && (
|
||||
<StyledComboBox
|
||||
onSelect={onChangeAccessRights}
|
||||
options={accessRights}
|
||||
size="content"
|
||||
scaled={false}
|
||||
manualWidth="fit-content"
|
||||
selectedOption={selectedAccessRight}
|
||||
showDisabledItems
|
||||
directionX={"right"}
|
||||
directionY={"top"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{withCancelButton && (
|
||||
<Button
|
||||
id={cancelButtonId}
|
||||
className={"button cancel-button"}
|
||||
label={cancelButtonLabel}
|
||||
scale
|
||||
size={"normal"}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
)}
|
||||
</StyledButtonContainer>
|
||||
</StyledFooter>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Footer;
|
@ -0,0 +1,5 @@
|
||||
export type HeaderProps = {
|
||||
onBackClickAction?: () => void;
|
||||
withoutBackButton?: boolean;
|
||||
headerLabel: string;
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import Base from "../../../themes/base";
|
||||
|
||||
const StyledHeader = styled.div`
|
||||
width: calc(100% - 32px);
|
||||
min-height: 53px;
|
||||
height: 53px;
|
||||
max-height: 53px;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
border-bottom: ${(props) => props.theme.selector.border};
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow-button {
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.heading-text {
|
||||
font-weight: 700;
|
||||
font-size: 21px;
|
||||
line-height: 28px;
|
||||
}
|
||||
`;
|
||||
|
||||
StyledHeader.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledHeader;
|
@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import IconButton from "../../../icon-button";
|
||||
import Heading from "../../../heading";
|
||||
|
||||
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
|
||||
|
||||
import { StyledSelectorHeader } from "../../StyledSelector";
|
||||
|
||||
const Header = React.memo(({ onBackClickAction, headerLabel }) => {
|
||||
return (
|
||||
<StyledSelectorHeader>
|
||||
<IconButton
|
||||
className="arrow-button"
|
||||
iconName={ArrowPathReactSvgUrl}
|
||||
size={17}
|
||||
onClick={onBackClickAction}
|
||||
/>
|
||||
<Heading className={"heading-text"}>{headerLabel}</Heading>
|
||||
</StyledSelectorHeader>
|
||||
);
|
||||
});
|
||||
|
||||
export default Header;
|
29
packages/components/selector/sub-components/Header/index.tsx
Normal file
29
packages/components/selector/sub-components/Header/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import IconButton from "../../../icon-button";
|
||||
import Heading from "../../../heading";
|
||||
|
||||
import ArrowPathReactSvgUrl from "PUBLIC_DIR/images/arrow.path.react.svg?url";
|
||||
|
||||
import StyledHeader from "./StyledHeader";
|
||||
import { HeaderProps } from "./Header.types";
|
||||
|
||||
const Header = React.memo(
|
||||
({ onBackClickAction, withoutBackButton, headerLabel }: HeaderProps) => {
|
||||
return (
|
||||
<StyledHeader>
|
||||
{!withoutBackButton && (
|
||||
<IconButton
|
||||
className="arrow-button"
|
||||
iconName={ArrowPathReactSvgUrl}
|
||||
size={17}
|
||||
onClick={onBackClickAction}
|
||||
/>
|
||||
)}
|
||||
<Heading className={"heading-text"}>{headerLabel}</Heading>
|
||||
</StyledHeader>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Header;
|
@ -0,0 +1,25 @@
|
||||
export type Item = {
|
||||
key?: string;
|
||||
id?: number | string;
|
||||
label: string;
|
||||
avatar?: string;
|
||||
icon?: string;
|
||||
role?: string;
|
||||
isSelected?: boolean;
|
||||
email?: string;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export type Data = {
|
||||
items: Item[];
|
||||
onSelect: (item: Item) => void;
|
||||
isMultiSelect: boolean;
|
||||
isItemLoaded: (index: number) => boolean;
|
||||
rowLoader: any;
|
||||
};
|
||||
|
||||
export type ItemProps = {
|
||||
index: number;
|
||||
style: object;
|
||||
data: Data;
|
||||
};
|
@ -0,0 +1,66 @@
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const selectedCss = css`
|
||||
background: ${(props) =>
|
||||
props.theme.selector.item.selectedBackground} !important;
|
||||
`;
|
||||
|
||||
const StyledItem = styled.div<{
|
||||
isSelected: boolean | undefined;
|
||||
isDisabled?: boolean;
|
||||
isMultiSelect: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.room-logo,
|
||||
.user-avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.room-logo {
|
||||
height: 32px;
|
||||
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
line-height: 18px;
|
||||
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
svg {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isDisabled
|
||||
? css`
|
||||
opacity: 0.5;
|
||||
`
|
||||
: css`
|
||||
${props.isSelected && !props.isMultiSelect && selectedCss}
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: ${props.theme.selector.item.hoverBackground};
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
StyledItem.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledItem;
|
@ -1,64 +1,14 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { ReactSVG } from "react-svg";
|
||||
|
||||
import Avatar from "../../../avatar";
|
||||
import Text from "../../../text";
|
||||
import Checkbox from "../../../checkbox";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const selectedCss = css`
|
||||
background: ${(props) =>
|
||||
props.theme.selector.item.selectedBackground} !important;
|
||||
`;
|
||||
import StyledItem from "./StyledItem";
|
||||
|
||||
const StyledItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
import { ItemProps, Data, Item as ItemType } from "./Item.types";
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
${(props) => props.isSelected && !props.isMultiSelect && selectedCss}
|
||||
|
||||
.room-logo,
|
||||
.user-avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.room-logo {
|
||||
height: 32px;
|
||||
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
line-height: 16px;
|
||||
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
svg {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: ${(props) => props.theme.selector.item.hoverBackground};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledItem.defaultProps = { theme: Base };
|
||||
|
||||
const compareFunction = (prevProps, nextProps) => {
|
||||
const compareFunction = (prevProps: ItemProps, nextProps: ItemProps) => {
|
||||
const prevData = prevProps.data;
|
||||
const prevItems = prevData.items;
|
||||
const prevIndex = prevProps.index;
|
||||
@ -76,17 +26,19 @@ const compareFunction = (prevProps, nextProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Item = React.memo(({ index, style, data }) => {
|
||||
const { items, onSelect, isMultiSelect, isItemLoaded, rowLoader } = data;
|
||||
const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
const { items, onSelect, isMultiSelect, isItemLoaded, rowLoader }: Data =
|
||||
data;
|
||||
|
||||
const isLoaded = isItemLoaded(index);
|
||||
|
||||
const renderItem = () => {
|
||||
const item = items[index];
|
||||
const item: ItemType = items[index];
|
||||
|
||||
if (!item && !item?.id) return <div style={style}>{rowLoader}</div>;
|
||||
if (!item || (item && !item.id))
|
||||
return <div style={style}>{rowLoader}</div>;
|
||||
|
||||
const { label, avatar, icon, role, isSelected } = item;
|
||||
const { label, avatar, icon, role, isSelected, isDisabled } = item;
|
||||
|
||||
const currentRole = role ? role : "user";
|
||||
|
||||
@ -96,8 +48,13 @@ const Item = React.memo(({ index, style, data }) => {
|
||||
onSelect && onSelect(item);
|
||||
};
|
||||
|
||||
const onClick = (e) => {
|
||||
if (e.target.closest(".checkbox")) return;
|
||||
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (
|
||||
((e.target instanceof HTMLElement || e.target instanceof SVGElement) &&
|
||||
!!e.target.closest(".checkbox")) ||
|
||||
isDisabled
|
||||
)
|
||||
return;
|
||||
|
||||
onSelect && onSelect(item);
|
||||
};
|
||||
@ -109,6 +66,7 @@ const Item = React.memo(({ index, style, data }) => {
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
className="test-22"
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{!isLogo ? (
|
||||
<Avatar
|
@ -0,0 +1,6 @@
|
||||
export type SearchProps = {
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
onSearch: (value: string) => void;
|
||||
onClearSearch: () => void;
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import SearchInput from "../../../search-input";
|
||||
|
||||
const Search = React.memo(({ placeholder, value, onSearch, onClearSearch }) => {
|
||||
return (
|
||||
<SearchInput
|
||||
className="search-input"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Search;
|
21
packages/components/selector/sub-components/Search/index.tsx
Normal file
21
packages/components/selector/sub-components/Search/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
import SearchInput from "../../../search-input";
|
||||
|
||||
import { SearchProps } from "./Search.types";
|
||||
|
||||
const Search = React.memo(
|
||||
({ placeholder, value, onSearch, onClearSearch }: SearchProps) => {
|
||||
return (
|
||||
<SearchInput
|
||||
className="search-input"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onSearch}
|
||||
onClearSearch={onClearSearch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default Search;
|
@ -0,0 +1,9 @@
|
||||
export type SelectAllProps = {
|
||||
label?: string;
|
||||
icon?: string;
|
||||
onSelectAll?: () => void;
|
||||
isChecked?: boolean;
|
||||
isIndeterminate?: boolean;
|
||||
isLoading?: boolean;
|
||||
rowLoader: any;
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
import styled from "styled-components";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledSelectAll = styled.div`
|
||||
width: 100%;
|
||||
max-height: 61px;
|
||||
height: 61px;
|
||||
min-height: 61px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
border-bottom: ${(props) => props.theme.selector.border};
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 8px 16px 20px;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
.select-all_avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
line-height: 16px;
|
||||
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
svg {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelectAll.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledSelectAll;
|
@ -1,51 +1,11 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Avatar from "../../../avatar";
|
||||
import Text from "../../../text";
|
||||
import Checkbox from "../../../checkbox";
|
||||
import { Base } from "../../../themes";
|
||||
|
||||
const StyledSelectAll = styled.div`
|
||||
width: 100%;
|
||||
max-height: 61px;
|
||||
height: 61px;
|
||||
min-height: 61px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
border-bottom: ${(props) => props.theme.selector.border};
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 8px 16px 20px;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
.select-all_avatar {
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
line-height: 16px;
|
||||
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
svg {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelectAll.defaultProps = { theme: Base };
|
||||
import StyledSelectAll from "./StyledSelectAll";
|
||||
import { SelectAllProps } from "./SelectAll.types";
|
||||
|
||||
const SelectAll = React.memo(
|
||||
({
|
||||
@ -56,9 +16,10 @@ const SelectAll = React.memo(
|
||||
isIndeterminate,
|
||||
isLoading,
|
||||
rowLoader,
|
||||
}) => {
|
||||
const onClick = (e) => {
|
||||
if (e.target.closest(".checkbox")) return;
|
||||
}: SelectAllProps) => {
|
||||
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target instanceof HTMLElement && e.target.closest(".checkbox"))
|
||||
return;
|
||||
|
||||
onSelectAll && onSelectAll();
|
||||
};
|
@ -1119,8 +1119,8 @@ const Base = {
|
||||
},
|
||||
|
||||
icon: {
|
||||
background: lightHover,
|
||||
color: grayMain,
|
||||
background: "#ECEEF1",
|
||||
color: "#A3A9AE",
|
||||
},
|
||||
|
||||
width: {
|
||||
@ -2285,6 +2285,13 @@ const Base = {
|
||||
selector: {
|
||||
border: `1px solid ${grayLightMid}`,
|
||||
|
||||
breadCrumbs: {
|
||||
prevItemColor: "#A3A9AE",
|
||||
arrowRightColor: "#A3A9AE",
|
||||
},
|
||||
|
||||
bodyDescriptionText: "#A3A9AE",
|
||||
|
||||
item: {
|
||||
hoverBackground: grayLight,
|
||||
selectedBackground: lightHover,
|
||||
|
@ -1110,8 +1110,8 @@ const Dark = {
|
||||
},
|
||||
|
||||
icon: {
|
||||
background: grayMain,
|
||||
color: globalColors.lightHover,
|
||||
background: "#242424",
|
||||
color: "#ADADAD",
|
||||
},
|
||||
|
||||
width: {
|
||||
@ -2289,6 +2289,13 @@ const Dark = {
|
||||
selector: {
|
||||
border: `1px solid #474747`,
|
||||
|
||||
breadCrumbs: {
|
||||
prevItemColor: "#858585",
|
||||
arrowRightColor: "#858585",
|
||||
},
|
||||
|
||||
bodyDescriptionText: "#858585",
|
||||
|
||||
item: {
|
||||
hoverBackground: "#3d3d3d",
|
||||
selectedBackground: "#3d3d3d",
|
||||
|
@ -21,12 +21,10 @@ const SelectFileDialog = ({
|
||||
url: CLIENT_REMOTE_ENTRY_URL,
|
||||
module: "./SelectFileDialog",
|
||||
}}
|
||||
resetTreeFolders
|
||||
filteredType="exceptPrivacyTrashArchiveFolders"
|
||||
isPanelVisible={isVisible}
|
||||
onClose={onCloseFileDialog}
|
||||
onSelectFile={onSelectFile}
|
||||
filesListTitle={filesListTitle}
|
||||
descriptionText={filesListTitle}
|
||||
settings={settings}
|
||||
/>
|
||||
)) ||
|
||||
|
@ -1,10 +1,7 @@
|
||||
import React from "react";
|
||||
import DynamicComponent from "./DynamicComponent";
|
||||
import { CLIENT_REMOTE_ENTRY_URL, CLIENT_SCOPE } from "../helpers/constants";
|
||||
import Text from "@docspace/components/text";
|
||||
import TextInput from "@docspace/components/text-input";
|
||||
import Checkbox from "@docspace/components/checkbox";
|
||||
import { StyledSelectFolder } from "../components/StyledEditor";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
const SelectFolderDialog = ({
|
||||
successAuth,
|
||||
@ -13,44 +10,13 @@ const SelectFolderDialog = ({
|
||||
onCloseFolderDialog,
|
||||
onClickSaveSelectFolder,
|
||||
titleSelectorFolder,
|
||||
onChangeInput,
|
||||
|
||||
extension,
|
||||
onClickCheckbox,
|
||||
|
||||
mfReady,
|
||||
openNewTab,
|
||||
}) => {
|
||||
const { t } = useTranslation(["Editor", "Common"]);
|
||||
|
||||
const headerProps = {
|
||||
header: (
|
||||
<StyledSelectFolder>
|
||||
<Text className="editor-select-folder_text">{t("FileName")}</Text>
|
||||
<TextInput
|
||||
className="editor-select-folder_text-input"
|
||||
scale
|
||||
onChange={onChangeInput}
|
||||
value={titleSelectorFolder}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
),
|
||||
};
|
||||
|
||||
const footerProps =
|
||||
extension !== "fb2"
|
||||
? {
|
||||
footer: (
|
||||
<StyledSelectFolder>
|
||||
<Checkbox
|
||||
className="editor-select-folder_checkbox"
|
||||
label={t("OpenSavedDocument")}
|
||||
onChange={onClickCheckbox}
|
||||
isChecked={openNewTab}
|
||||
/>
|
||||
</StyledSelectFolder>
|
||||
),
|
||||
}
|
||||
: {};
|
||||
|
||||
return (
|
||||
(mfReady && isVisible && successAuth && (
|
||||
<DynamicComponent
|
||||
@ -60,14 +26,17 @@ const SelectFolderDialog = ({
|
||||
module: "./SelectFolderDialog",
|
||||
}}
|
||||
needProxy
|
||||
folderId={folderId}
|
||||
id={folderId}
|
||||
isPanelVisible={isVisible}
|
||||
onClose={onCloseFolderDialog}
|
||||
filteredType="exceptSortedByTags"
|
||||
onSave={onClickSaveSelectFolder}
|
||||
isDisableButton={!titleSelectorFolder.trim()}
|
||||
{...headerProps}
|
||||
{...footerProps}
|
||||
isCopy={true}
|
||||
isEditorDialog={true}
|
||||
withFooterInput={true}
|
||||
withFooterCheckbox={extension !== "fb2"}
|
||||
footerInputHeader={t("FileName")}
|
||||
currentFooterInputValue={titleSelectorFolder}
|
||||
footerCheckboxLabel={t("OpenSavedDocument")}
|
||||
/>
|
||||
)) ||
|
||||
null
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { getPresignedUri } from "@docspace/common/api/files";
|
||||
import { EDITOR_ID } from "@docspace/common/constants";
|
||||
import {
|
||||
EDITOR_ID,
|
||||
FilesSelectorFilterTypes,
|
||||
FilterType,
|
||||
} from "@docspace/common/constants";
|
||||
import { useTranslation } from "react-i18next";
|
||||
//import SharingDialog from "../components/SharingDialog";
|
||||
import SelectFileDialog from "../components/SelectFileDialog";
|
||||
@ -20,7 +24,6 @@ const withDialogs = (WrappedComponent) => {
|
||||
const [titleSelectorFolder, setTitleSelectorFolder] = useState("");
|
||||
const [urlSelectorFolder, setUrlSelectorFolder] = useState("");
|
||||
const [extension, setExtension] = useState();
|
||||
const [openNewTab, setNewOpenTab] = useState(false);
|
||||
|
||||
const { t } = useTranslation(["Editor", "Common"]);
|
||||
|
||||
@ -135,7 +138,7 @@ const withDialogs = (WrappedComponent) => {
|
||||
};
|
||||
|
||||
const insertImageActionProps = {
|
||||
isImageOnly: true,
|
||||
filterParam: FilesSelectorFilterTypes.IMG,
|
||||
};
|
||||
|
||||
const mailMergeActionProps = {
|
||||
@ -198,10 +201,9 @@ const withDialogs = (WrappedComponent) => {
|
||||
|
||||
const onCloseFolderDialog = () => {
|
||||
setIsFolderDialogVisible(false);
|
||||
setNewOpenTab(false);
|
||||
};
|
||||
|
||||
const getSavingInfo = async (title, folderId) => {
|
||||
const getSavingInfo = async (title, folderId, openNewTab) => {
|
||||
const savingInfo = await window.filesUtils.SaveAs(
|
||||
title,
|
||||
urlSelectorFolder,
|
||||
@ -220,13 +222,13 @@ const withDialogs = (WrappedComponent) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onClickSaveSelectFolder = (e, folderId) => {
|
||||
const currentExst = titleSelectorFolder.split(".").pop();
|
||||
const onClickSaveSelectFolder = (e, folderId, fileTitle, openNewTab) => {
|
||||
const currentExst = fileTitle.split(".").pop();
|
||||
|
||||
const title =
|
||||
currentExst !== extension
|
||||
? titleSelectorFolder.concat(`.${extension}`)
|
||||
: titleSelectorFolder;
|
||||
? fileTitle.concat(`.${extension}`)
|
||||
: fileTitle;
|
||||
|
||||
if (openNewTab) {
|
||||
window.filesUtils.SaveAs(
|
||||
@ -236,18 +238,10 @@ const withDialogs = (WrappedComponent) => {
|
||||
openNewTab
|
||||
);
|
||||
} else {
|
||||
getSavingInfo(title, folderId);
|
||||
getSavingInfo(title, folderId, openNewTab);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickCheckbox = () => {
|
||||
setNewOpenTab(!openNewTab);
|
||||
};
|
||||
|
||||
const onChangeInput = (e) => {
|
||||
setTitleSelectorFolder(e.target.value);
|
||||
};
|
||||
|
||||
// const sharingDialog = (
|
||||
// <SharingDialog
|
||||
// mfReady={mfReady}
|
||||
@ -280,9 +274,6 @@ const withDialogs = (WrappedComponent) => {
|
||||
onCloseFolderDialog={onCloseFolderDialog}
|
||||
onClickSaveSelectFolder={onClickSaveSelectFolder}
|
||||
titleSelectorFolder={titleSelectorFolder}
|
||||
onChangeInput={onChangeInput}
|
||||
onClickCheckbox={onClickCheckbox}
|
||||
openNewTab={openNewTab}
|
||||
mfReady={mfReady}
|
||||
/>
|
||||
);
|
||||
|
34
products/ASC.Files/Core/Core/ApplyFilterOption.cs
Normal file
34
products/ASC.Files/Core/Core/ApplyFilterOption.cs
Normal file
@ -0,0 +1,34 @@
|
||||
// (c) Copyright Ascensio System SIA 2010-2022
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
namespace ASC.Files.Core.Core;
|
||||
|
||||
public enum ApplyFilterOption
|
||||
{
|
||||
All,
|
||||
Files,
|
||||
Folders
|
||||
}
|
@ -286,7 +286,8 @@ public class FileStorageService //: IFileStorageService
|
||||
IEnumerable<string> tagNames = null,
|
||||
bool excludeSubject = false,
|
||||
ProviderFilter provider = ProviderFilter.None,
|
||||
SubjectFilter subjectFilter = SubjectFilter.Owner)
|
||||
SubjectFilter subjectFilter = SubjectFilter.Owner,
|
||||
ApplyFilterOption applyFilterOption = ApplyFilterOption.All)
|
||||
{
|
||||
var subjectId = string.IsNullOrEmpty(subject) ? Guid.Empty : new Guid(subject);
|
||||
|
||||
@ -336,7 +337,7 @@ public class FileStorageService //: IFileStorageService
|
||||
try
|
||||
{
|
||||
(entries, total) = await _entryManager.GetEntriesAsync(parent, from, count, filterType, subjectGroup, subjectId, searchText, searchInContent, withSubfolders, orderBy, roomId, searchArea,
|
||||
withoutTags, tagNames, excludeSubject, provider, subjectFilter);
|
||||
withoutTags, tagNames, excludeSubject, provider, subjectFilter, applyFilterOption);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -43,8 +43,8 @@ public enum FilterType
|
||||
[EnumMember] FillingFormsRooms = 13,
|
||||
[EnumMember] EditingRooms = 14,
|
||||
[EnumMember] ReviewRooms = 15,
|
||||
[EnumMember] ReadOnlyRooms = 16,
|
||||
[EnumMember] ReadOnlyRooms = 16,
|
||||
[EnumMember] CustomRooms = 17,
|
||||
[EnumMember] OFormTemplateOnly = 18,
|
||||
[EnumMember] OFormOnly = 19,
|
||||
[EnumMember] OFormTemplateOnly = 18,
|
||||
[EnumMember] OFormOnly = 19
|
||||
}
|
@ -372,7 +372,7 @@ public class EntryManager
|
||||
|
||||
public async Task<(IEnumerable<FileEntry> Entries, int Total)> GetEntriesAsync<T>(Folder<T> parent, int from, int count, FilterType filterType, bool subjectGroup, Guid subjectId,
|
||||
string searchText, bool searchInContent, bool withSubfolders, OrderBy orderBy, T roomId = default, SearchArea searchArea = SearchArea.Active, bool withoutTags = false, IEnumerable<string> tagNames = null,
|
||||
bool excludeSubject = false, ProviderFilter provider = ProviderFilter.None, SubjectFilter subjectFilter = SubjectFilter.Owner)
|
||||
bool excludeSubject = false, ProviderFilter provider = ProviderFilter.None, SubjectFilter subjectFilter = SubjectFilter.Owner, ApplyFilterOption applyFilterOption = ApplyFilterOption.All)
|
||||
{
|
||||
var total = 0;
|
||||
|
||||
@ -395,6 +395,9 @@ public class EntryManager
|
||||
|
||||
searchInContent = searchInContent && filterType != FilterType.ByExtension && !Equals(parent.Id, await _globalFolderHelper.FolderTrashAsync);
|
||||
|
||||
var (foldersFilterType, foldersSearchText) = applyFilterOption != ApplyFilterOption.Files ? (filterType, searchText) : (FilterType.None, string.Empty);
|
||||
var (filesFilterType, filesSearchText) = applyFilterOption != ApplyFilterOption.Folders ? (filterType, searchText) : (FilterType.None, string.Empty);
|
||||
|
||||
if (parent.FolderType == FolderType.Projects && parent.Id.Equals(await _globalFolderHelper.FolderProjectsAsync))
|
||||
{
|
||||
|
||||
@ -466,8 +469,8 @@ public class EntryManager
|
||||
withSubfolders = false;
|
||||
}
|
||||
|
||||
var folders = _daoFactory.GetFolderDao<T>().GetFoldersAsync(parent.Id, orderBy, filterType, subjectGroup, subjectId, searchText, withSubfolders, excludeSubject);
|
||||
var files = _daoFactory.GetFileDao<T>().GetFilesAsync(parent.Id, orderBy, filterType, subjectGroup, subjectId, searchText, searchInContent, withSubfolders, excludeSubject);
|
||||
var folders = _daoFactory.GetFolderDao<T>().GetFoldersAsync(parent.Id, orderBy, foldersFilterType, subjectGroup, subjectId, foldersSearchText, withSubfolders, excludeSubject);
|
||||
var files = _daoFactory.GetFileDao<T>().GetFilesAsync(parent.Id, orderBy, filesFilterType, subjectGroup, subjectId, filesSearchText, searchInContent, withSubfolders, excludeSubject);
|
||||
|
||||
var task1 = _fileSecurity.FilterReadAsync(folders).ToListAsync();
|
||||
var task2 = _fileSecurity.FilterReadAsync(files).ToListAsync();
|
||||
|
@ -122,9 +122,10 @@ public abstract class FoldersController<T> : ApiControllerBase
|
||||
/// <param name="filterType" optional="true" remark="Allowed values: None (0), FilesOnly (1), FoldersOnly (2), DocumentsOnly (3), PresentationsOnly (4), SpreadsheetsOnly (5) or ImagesOnly (7)">Filter type</param>
|
||||
/// <returns>Folder contents</returns>
|
||||
[HttpGet("{folderId}", Order = 1)]
|
||||
public async Task<FolderContentDto<T>> GetFolderAsync(T folderId, Guid? userIdOrGroupId, FilterType? filterType, T roomId, bool? searchInContent, bool? withsubfolders, bool? excludeSubject)
|
||||
public async Task<FolderContentDto<T>> GetFolderAsync(T folderId, Guid? userIdOrGroupId, FilterType? filterType, T roomId, bool? searchInContent, bool? withsubfolders, bool? excludeSubject,
|
||||
ApplyFilterOption? applyFilterOption)
|
||||
{
|
||||
var folder = await _foldersControllerHelper.GetFolderAsync(folderId, userIdOrGroupId, filterType, roomId, searchInContent, withsubfolders, excludeSubject);
|
||||
var folder = await _foldersControllerHelper.GetFolderAsync(folderId, userIdOrGroupId, filterType, roomId, searchInContent, withsubfolders, excludeSubject, applyFilterOption);
|
||||
|
||||
return folder.NotFoundIfNull();
|
||||
}
|
||||
@ -222,7 +223,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@common")]
|
||||
public async Task<FolderContentDto<int>> GetCommonFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderCommonAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderCommonAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -234,7 +236,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@favorites")]
|
||||
public async Task<FolderContentDto<int>> GetFavoritesFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderFavoritesAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderFavoritesAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -246,9 +249,10 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
/// <category>Folders</category>
|
||||
/// <returns>My folder contents</returns>
|
||||
[HttpGet("@my")]
|
||||
public async Task<FolderContentDto<int>> GetMyFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
public async Task<FolderContentDto<int>> GetMyFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders, ApplyFilterOption? applyFilterOption)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderMyAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderMyAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, applyFilterOption);
|
||||
}
|
||||
|
||||
[HttpGet("@privacy")]
|
||||
@ -259,7 +263,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
throw new SecurityException();
|
||||
}
|
||||
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderPrivacyAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderPrivacyAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -273,7 +278,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@projects")]
|
||||
public async Task<FolderContentDto<string>> GetProjectsFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.GetFolderProjectsAsync<string>(), userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.GetFolderProjectsAsync<string>(), userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -285,7 +291,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@recent")]
|
||||
public async Task<FolderContentDto<int>> GetRecentFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderRecentAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderRecentAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
[HttpGet("@root")]
|
||||
@ -295,7 +302,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
|
||||
await foreach (var folder in foldersIds)
|
||||
{
|
||||
yield return await _foldersControllerHelper.GetFolderAsync(folder, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
yield return await _foldersControllerHelper.GetFolderAsync(folder, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false,
|
||||
ApplyFilterOption.All);
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,7 +318,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@share")]
|
||||
public async Task<FolderContentDto<int>> GetShareFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderShareAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderShareAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -322,7 +331,8 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
[HttpGet("@templates")]
|
||||
public async Task<FolderContentDto<int>> GetTemplatesFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderTemplatesAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(await _globalFolderHelper.FolderTemplatesAsync, userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, ApplyFilterOption.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -334,8 +344,9 @@ public class FoldersControllerCommon : ApiControllerBase
|
||||
/// <category>Folders</category>
|
||||
/// <returns>Trash folder contents</returns>
|
||||
[HttpGet("@trash")]
|
||||
public async Task<FolderContentDto<int>> GetTrashFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders)
|
||||
public async Task<FolderContentDto<int>> GetTrashFolderAsync(Guid? userIdOrGroupId, FilterType? filterType, bool? searchInContent, bool? withsubfolders, ApplyFilterOption? applyFilterOption)
|
||||
{
|
||||
return await _foldersControllerHelper.GetFolderAsync(Convert.ToInt32(await _globalFolderHelper.FolderTrashAsync), userIdOrGroupId, filterType, default, searchInContent, withsubfolders, false);
|
||||
return await _foldersControllerHelper.GetFolderAsync(Convert.ToInt32(await _globalFolderHelper.FolderTrashAsync), userIdOrGroupId, filterType, default, searchInContent, withsubfolders,
|
||||
false, applyFilterOption);
|
||||
}
|
||||
}
|
@ -74,9 +74,9 @@ public class FoldersControllerHelper : FilesHelperBase
|
||||
return await _folderDtoHelper.GetAsync(folder);
|
||||
}
|
||||
|
||||
public async Task<FolderContentDto<T>> GetFolderAsync<T>(T folderId, Guid? userIdOrGroupId, FilterType? filterType, T roomId, bool? searchInContent, bool? withSubFolders, bool? excludeSubject)
|
||||
public async Task<FolderContentDto<T>> GetFolderAsync<T>(T folderId, Guid? userIdOrGroupId, FilterType? filterType, T roomId, bool? searchInContent, bool? withSubFolders, bool? excludeSubject, ApplyFilterOption? applyFilterOption)
|
||||
{
|
||||
var folderContentWrapper = await ToFolderContentWrapperAsync(folderId, userIdOrGroupId ?? Guid.Empty, filterType ?? FilterType.None, roomId, searchInContent ?? false, withSubFolders ?? false, excludeSubject ?? false);
|
||||
var folderContentWrapper = await ToFolderContentWrapperAsync(folderId, userIdOrGroupId ?? Guid.Empty, filterType ?? FilterType.None, roomId, searchInContent ?? false, withSubFolders ?? false, excludeSubject ?? false, applyFilterOption ?? ApplyFilterOption.All);
|
||||
|
||||
return folderContentWrapper.NotFoundIfNull();
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class FoldersControllerHelper : FilesHelperBase
|
||||
return await _folderDtoHelper.GetAsync(folder);
|
||||
}
|
||||
|
||||
private async Task<FolderContentDto<T>> ToFolderContentWrapperAsync<T>(T folderId, Guid userIdOrGroupId, FilterType filterType, T roomId, bool searchInContent, bool withSubFolders, bool excludeSubject)
|
||||
private async Task<FolderContentDto<T>> ToFolderContentWrapperAsync<T>(T folderId, Guid userIdOrGroupId, FilterType filterType, T roomId, bool searchInContent, bool withSubFolders, bool excludeSubject, ApplyFilterOption applyFilterOption)
|
||||
{
|
||||
OrderBy orderBy = null;
|
||||
if (SortedByTypeExtensions.TryParse(_apiContext.SortBy, true, out var sortBy))
|
||||
@ -176,7 +176,7 @@ public class FoldersControllerHelper : FilesHelperBase
|
||||
|
||||
var startIndex = Convert.ToInt32(_apiContext.StartIndex);
|
||||
var items = await _fileStorageService.GetFolderItemsAsync(folderId, startIndex, Convert.ToInt32(_apiContext.Count), filterType, filterType == FilterType.ByUser, userIdOrGroupId.ToString(), _apiContext.FilterValue, searchInContent, withSubFolders, orderBy, excludeSubject: excludeSubject,
|
||||
roomId: roomId);
|
||||
roomId: roomId, applyFilterOption: applyFilterOption);
|
||||
|
||||
return await _folderContentDtoHelper.GetAsync(items, startIndex);
|
||||
}
|
||||
|
33
yarn.lock
33
yarn.lock
@ -1003,8 +1003,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/eslint-parser@npm:^7.21.8":
|
||||
version: 7.21.8
|
||||
resolution: "@babel/eslint-parser@npm:7.21.8"
|
||||
dependencies:
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals": 5.1.1-v1
|
||||
eslint-visitor-keys: ^2.1.0
|
||||
semver: ^6.3.0
|
||||
peerDependencies:
|
||||
"@babel/core": ">=7.11.0"
|
||||
eslint: ^7.5.0 || ^8.0.0
|
||||
checksum: 6d870f53808682b9d7e3c2a69a832b2095963103bb2d686daee3fcf1df49a0b3dfe58e95c773cab8cf59f2657ec432dfd5e47b9f1835c264eb84d2ec5ab2ad35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.21.5, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.4.0, @babel/generator@npm:^7.9.0":
|
||||
version: 7.22.5
|
||||
version: 7.22.3
|
||||
resolution: "@babel/generator@npm:7.22.5"
|
||||
dependencies:
|
||||
"@babel/types": ^7.22.5
|
||||
@ -3222,6 +3236,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@babel/cli": ^7.21.0
|
||||
"@babel/core": ^7.21.3
|
||||
"@babel/eslint-parser": ^7.21.8
|
||||
"@babel/plugin-proposal-class-properties": ^7.18.6
|
||||
"@babel/plugin-proposal-export-default-from": ^7.18.10
|
||||
"@babel/plugin-proposal-export-namespace-from": ^7.18.9
|
||||
@ -4690,6 +4705,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1":
|
||||
version: 5.1.1-v1
|
||||
resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1"
|
||||
dependencies:
|
||||
eslint-scope: 5.1.1
|
||||
checksum: f2e3b2d6a6e2d9f163ca22105910c9f850dc4897af0aea3ef0a5886b63d8e1ba6505b71c99cb78a3bba24a09557d601eb21c8dede3f3213753fcfef364eb0e57
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@nodelib/fs.scandir@npm:2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "@nodelib/fs.scandir@npm:2.1.5"
|
||||
@ -12123,6 +12147,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "eslint-visitor-keys@npm:2.1.0"
|
||||
checksum: e3081d7dd2611a35f0388bbdc2f5da60b3a3c5b8b6e928daffff7391146b434d691577aa95064c8b7faad0b8a680266bcda0a42439c18c717b80e6718d7e267d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1":
|
||||
version: 3.4.1
|
||||
resolution: "eslint-visitor-keys@npm:3.4.1"
|
||||
|
Loading…
Reference in New Issue
Block a user