Merge pull request #1492 from ONLYOFFICE/feature/move-to-selector

Feature/files selector
This commit is contained in:
Alexey Safronov 2023-06-29 18:26:58 +04:00 committed by GitHub
commit aaacece34f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 3580 additions and 4068 deletions

View File

@ -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
View 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;
}

View File

@ -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",

View File

@ -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 && (

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -28,8 +28,8 @@ newInstance
loadPath: loadLanguagePath(config.homepage),
},
ns: ["SelectFolder"],
defaultNS: "SelectFolder",
ns: ["Files", "Common", "Translations"],
defaultNS: "Files",
react: {
useSuspense: false,

View 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));

View 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;
};

View File

@ -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)));

View File

@ -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));

View File

@ -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">

View File

@ -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}
/>
</>
);

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)
)
);

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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
)
)
);

View File

@ -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}

View File

@ -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,
];

View File

@ -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)));

View File

@ -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;

View File

@ -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));

View File

@ -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;

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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}

View File

@ -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 && (

View File

@ -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

View File

@ -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);
};

View 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"]
}
}
}

View File

@ -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",

View File

@ -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;
}

View File

@ -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;

View File

@ -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));
}

View File

@ -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,

View File

@ -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

View File

@ -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
}
};
};

View File

@ -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);
};

View File

@ -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};

View File

@ -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",

View File

@ -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 |

View File

@ -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,
};

View 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: "",
};

View 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;
};

View File

@ -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,
};

View 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 };

View File

@ -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: [],
};

View File

@ -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;
};

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;
};

View File

@ -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 };

View File

@ -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;

View File

@ -0,0 +1,9 @@
export type EmptyScreenProps = {
image?: string;
header?: string;
description?: string;
searchImage?: string;
searchHeader?: string;
searchDescription?: string;
withSearch: boolean;
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
};

View File

@ -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,
};

View File

@ -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;

View 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;

View File

@ -0,0 +1,5 @@
export type HeaderProps = {
onBackClickAction?: () => void;
withoutBackButton?: boolean;
headerLabel: string;
};

View File

@ -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;

View File

@ -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;

View 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;

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1,6 @@
export type SearchProps = {
placeholder?: string;
value?: string;
onSearch: (value: string) => void;
onClearSearch: () => void;
};

View File

@ -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;

View 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;

View File

@ -0,0 +1,9 @@
export type SelectAllProps = {
label?: string;
icon?: string;
onSelectAll?: () => void;
isChecked?: boolean;
isIndeterminate?: boolean;
isLoading?: boolean;
rowLoader: any;
};

View File

@ -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;

View File

@ -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();
};

View File

@ -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,

View File

@ -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",

View File

@ -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}
/>
)) ||

View File

@ -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

View File

@ -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}
/>
);

View 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
}

View File

@ -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)
{

View File

@ -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
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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"