Shared:Selectors:Files: init, fix usage, rewrite to typescript additional components

This commit is contained in:
Timofey Boyko 2024-02-09 12:28:10 +03:00
parent a32702c5a0
commit e46207cf8b
28 changed files with 2287 additions and 1510 deletions

View File

@ -1,131 +1,8 @@
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;
roomType: number;
fileExst?: string;
shared: boolean;
};
export type BreadCrumb = {
label: string;
id: number | string;
isRoom: boolean;
shared: boolean;
};
type setItems = (value: Item[] | null) => Item[];
export type useLoadersHelperProps = {
items: Item[] | null;
};
export type setItemsCallback = (value: Item[] | null) => Item[] | null;
export type setBreadCrumbsCallback = (
value: BreadCrumb[] | []
) => BreadCrumb[] | [];
export type setTotalCallback = (value: number) => number;
export type useSocketHelperProps = {
socketHelper: any;
socketSubscribers: Set<string>;
setItems: (callback: setItemsCallback) => void;
setBreadCrumbs: (callback: setBreadCrumbsCallback) => void;
setTotal: (callback: setTotalCallback) => void;
disabledItems: string[] | number[];
filterParam?: string;
getIcon: (size: number, fileExst: string) => string;
};
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;
onSetBaseFolderPath?: (
value: number | string | undefined | BreadCrumb[]
) => void;
isUserOnly?: boolean;
};
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;
isRoomsOnly: boolean;
onSetBaseFolderPath?: (
value: number | string | undefined | BreadCrumb[]
) => void;
};
export type useFilesHelpersProps = {
roomsFolderId?: number;
setBreadCrumbs: (items: BreadCrumb[]) => void;
setIsBreadCrumbsLoading: (value: boolean) => void;
setIsSelectedParentFolder: (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;
getRootData?: () => Promise<void>;
onSetBaseFolderPath?: (
value: number | string | undefined | BreadCrumb[]
) => void;
isRoomsOnly: boolean;
rootThirdPartyId?: string;
getRoomList?: (
startIndex: number,
isInit?: boolean,
search?: string | null,
isErrorPath?: boolean
) => void;
getIcon: (size: number, fileExst: string) => string;
t: any;
};
import { TFile, TFolder } from "@docspace/shared/api/files/types";
import { TBreadCrumb } from "@docspace/shared/components/selector/Selector.types";
import { DeviceType } from "@docspace/shared/enums";
import { TTheme } from "@docspace/shared/themes";
import SocketIOHelper from "@docspace/shared/utils/socket";
export type FilesSelectorProps = {
isPanelVisible: boolean;
@ -142,6 +19,7 @@ export type FilesSelectorProps = {
onClose?: () => void;
id?: string | number;
isMove?: boolean;
isCopy?: boolean;
isRestore: boolean;
@ -155,11 +33,11 @@ export type FilesSelectorProps = {
parentId?: number;
rootFolderType?: number;
treeFolders?: Item[];
treeFolders?: TFolder[];
theme: any;
theme: TTheme;
selection: any[];
selection: (TFolder | TFile)[];
disabledItems: string[] | number[];
setMoveToPanelVisible: (value: boolean) => void;
setRestorePanelVisible: (value: boolean) => void;
@ -168,32 +46,32 @@ export type FilesSelectorProps = {
setMovingInProgress: (value: boolean) => void;
setIsDataReady?: (value: boolean) => void;
setSelected: (selected: "close" | "none", clearBuffer?: boolean) => void;
setConflictDialogData: (conflicts: any, operationData: any) => void;
itemOperationToFolder: (operationData: any) => Promise<void>;
setConflictDialogData: (conflicts: unknown, operationData: unknown) => void;
itemOperationToFolder: (operationData: unknown) => Promise<void>;
clearActiveOperations: (
folderIds: string[] | number[],
fileIds: string[] | number[]
fileIds: string[] | number[],
) => void;
checkFileConflicts: (
selectedItemId: string | number | undefined,
folderIds: string[] | number[],
fileIds: string[] | number[]
) => Promise<any>;
fileIds: string[] | number[],
) => Promise<unknown>;
onSetBaseFolderPath?: (
value: number | string | undefined | BreadCrumb[]
value: number | string | undefined | TBreadCrumb[],
) => void;
onSetNewFolderPath?: (value: number | string | undefined) => void;
onSelectFolder?: (
value: number | string | undefined,
breadCrumbs: BreadCrumb[]
breadCrumbs: TBreadCrumb[],
) => void;
onSelectTreeNode?: (treeNode: any) => void;
onSelectTreeNode?: (treeNode: TFolder) => void;
onSave?: (
e: any,
e: unknown,
folderId: string | number,
fileTitle: string,
openNewTab: boolean
openNewTab: boolean,
) => void;
onSelectFile?: (
fileInfo: {
@ -203,7 +81,7 @@ export type FilesSelectorProps = {
fileExst?: string;
inPublic?: boolean;
},
breadCrumbs: BreadCrumb[]
breadCrumbs: TBreadCrumb[],
) => void;
setInfoPanelIsMobileHidden: (arg: boolean) => void;
@ -219,14 +97,14 @@ export type FilesSelectorProps = {
includeFolder?: boolean;
socketHelper: any;
socketHelper: SocketIOHelper;
socketSubscribers: Set<string>;
currentDeviceType: "mobile" | "tablet" | "desktop";
currentDeviceType: DeviceType;
embedded: boolean;
withHeader: boolean;
withCancelButton: boolean;
settings: any;
settings: unknown;
roomsFolderId?: number;
};

View File

@ -1,354 +0,0 @@
import React from "react";
// @ts-ignore
import { getFolder, getFolderInfo } from "@docspace/shared/api/files";
// @ts-ignore
import FilesFilter from "@docspace/shared/api/files/filter";
// @ts-ignore
import { iconSize32 } from "@docspace/shared/utils/image-helpers";
import { PAGE_COUNT, defaultBreadCrumb } from "../utils";
import {
useFilesHelpersProps,
Item,
BreadCrumb,
Security,
} from "../FilesSelector.types";
import {
ApplyFilterOption,
FilesSelectorFilterTypes,
FilterType,
FolderType,
} from "@docspace/shared/enums";
import {
getIconPathByFolderType,
FolderTypeValueOf,
} from "@docspace/shared/utils/common";
//@ts-ignore
import { toastr } from "@docspace/shared/components/toast";
type Room = {
id: number;
title: string;
roomType: number;
filesCount: number;
foldersCount: number;
security: Security;
parentId: number;
rootFolderType: number;
type: FolderTypeValueOf;
};
const DEFAULT_FILE_EXTS = "file";
export const convertFoldersToItems = (
folders: any,
disabledItems: any[],
filterParam?: string
) => {
const items = folders.map((room: Room) => {
const {
id,
title,
roomType,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
type,
} = room;
const folderIconPath = getIconPathByFolderType(type);
const icon = iconSize32.get(folderIconPath);
return {
id,
label: title,
title,
icon,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
isFolder: true,
roomType,
isDisabled: !!filterParam ? false : disabledItems.includes(id),
};
});
return items;
};
export const convertFilesToItems = (
files: any,
getIcon: (size: number, fileExst: string) => string,
filterParam?: string
) => {
const items = files.map((file: any) => {
const {
id,
title,
security,
parentId,
folderId,
rootFolderType,
fileExst,
} = file;
const icon = getIcon(32, fileExst || DEFAULT_FILE_EXTS);
const label = title.replace(fileExst, "") || fileExst;
return {
id,
label,
title,
icon,
security,
parentId: parentId || folderId,
rootFolderType,
isFolder: false,
isDisabled: !filterParam,
fileExst,
};
});
return items;
};
export const useFilesHelper = ({
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
setBreadCrumbs,
setIsBreadCrumbsLoading,
isFirstLoad,
selectedItemId,
setIsRoot,
searchValue,
disabledItems,
setSelectedItemSecurity,
isThirdParty,
onSelectTreeNode,
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
getIcon,
t,
setIsSelectedParentFolder,
roomsFolderId,
}: 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.extension = FilesSelectorFilterTypes.DOCX;
break;
case FilesSelectorFilterTypes.IMG:
filter.filterType = FilterType.ImagesOnly;
break;
case FilesSelectorFilterTypes.BackupOnly:
filter.extension = "gz,tar";
break;
case FilesSelectorFilterTypes.DOCXF:
filter.filterType = FilterType.OFormTemplateOnly;
break;
case FilesSelectorFilterTypes.XLSX:
filter.filterType = FilterType.SpreadsheetsOnly;
break;
case FilesSelectorFilterTypes.ALL:
filter.filterType = FilterType.FilesOnly;
break;
}
}
const id = itemId ? itemId : selectedItemId || "";
filter.folder = id.toString();
const setSettings = async (
folderId: string | number,
isErrorPath = false
) => {
if (isInit && getRootData) {
const folder = await getFolderInfo(folderId, true);
const isArchive = folder.rootFolderType === FolderType.Archive;
if (folder.rootFolderType === FolderType.TRASH || isArchive) {
if (isRoomsOnly && getRoomList) {
await getRoomList(0, true, null, true);
toastr.error(
t("Files:ArchivedRoomAction", { name: folder.title })
);
return;
}
await getRootData();
if (onSetBaseFolderPath && isArchive) {
onSetBaseFolderPath && onSetBaseFolderPath([]);
toastr.error(
t("Files:ArchivedRoomAction", { name: folder.title })
);
}
return;
}
}
const currentFolder = await getFolder(folderId, filter);
const { folders, files, total, count, pathParts, current } =
currentFolder;
setSelectedItemSecurity(current.security);
const foldersList: Item[] = convertFoldersToItems(
folders,
disabledItems,
filterParam
);
const filesList: Item[] = convertFilesToItems(
files,
getIcon,
filterParam
);
const itemList = [...foldersList, ...filesList];
setHasNextPage(count === PAGE_COUNT);
onSelectTreeNode &&
setSelectedTreeNode({ ...current, path: pathParts });
if (isInit) {
let foundParentId = false,
currentFolderIndex = -1;
const breadCrumbs: BreadCrumb[] = pathParts.map(
({
id,
title,
roomType,
}: {
id: number | string;
title: string;
roomType?: number;
}) => {
// const folderInfo: any = await getFolderInfo(folderId);
// const { title, id, parentId, rootFolderType, roomType } =
// folderInfo;
if (!foundParentId) {
currentFolderIndex = disabledItems.findIndex((x) => x === id);
}
if (!foundParentId && currentFolderIndex !== -1) {
foundParentId = true;
setIsSelectedParentFolder(true);
}
return {
label: title,
id: id,
isRoom: roomsFolderId === id,
roomType,
};
}
);
!isThirdParty &&
!isRoomsOnly &&
breadCrumbs.unshift({ ...defaultBreadCrumb });
onSetBaseFolderPath &&
onSetBaseFolderPath(isErrorPath ? [] : breadCrumbs);
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);
};
try {
await setSettings(id);
} catch (e) {
sessionStorage.removeItem("filesSelectorPath");
if (isThirdParty && rootThirdPartyId) {
await setSettings(rootThirdPartyId, true);
toastr.error(e);
return;
}
if (isRoomsOnly && getRoomList) {
await getRoomList(0, true, null, true);
toastr.error(e);
return;
}
getRootData && getRootData();
if (onSetBaseFolderPath) {
onSetBaseFolderPath([]);
toastr.error(e);
}
}
},
[selectedItemId, searchValue, isFirstLoad, disabledItems, roomsFolderId]
);
return { getFileList };
};
export default useFilesHelper;

View File

@ -1,151 +0,0 @@
import React from "react";
// @ts-ignore
import { getRooms } from "@docspace/shared/api/rooms";
// @ts-ignore
import RoomsFilter from "@docspace/shared/api/rooms/filter";
// @ts-ignore
import { RoomsType } from "@docspace/shared/enums";
// @ts-ignore
import { iconSize32 } from "@docspace/shared/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;
case RoomsType.PublicRoom:
path = "public.svg";
break;
case RoomsType.FormRoom:
path = "form.svg";
break;
}
return iconSize32.get(path);
};
export 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,
roomType,
color: logo.color,
};
});
return items;
};
const useRoomsHelper = ({
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
setBreadCrumbs,
setIsRoot,
isFirstLoad,
setIsBreadCrumbsLoading,
searchValue,
isRoomsOnly,
onSetBaseFolderPath,
}: useRoomsHelperProps) => {
const getRoomList = React.useCallback(
async (
startIndex: number,
isInit?: boolean,
search?: string | null,
isErrorPath?: boolean
) => {
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[] = [{ label: title, id, isRoom: true }];
!isRoomsOnly && breadCrumbs.unshift({ ...defaultBreadCrumb });
onSetBaseFolderPath &&
onSetBaseFolderPath(isErrorPath ? [] : breadCrumbs);
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

@ -1,236 +0,0 @@
import React from "react";
import { convertFilesToItems, convertFoldersToItems } from "./useFilesHelper";
import {
Item,
setItemsCallback,
useSocketHelperProps,
} from "../FilesSelector.types";
import { convertRoomsToItems } from "./useRoomsHelper";
const useSocketHelper = ({
socketHelper,
socketSubscribers,
setItems,
setBreadCrumbs,
setTotal,
disabledItems,
filterParam,
getIcon,
}: useSocketHelperProps) => {
const subscribedId = React.useRef<null | number>(null);
const subscribe = (id: number) => {
const roomParts = `DIR-${id}`;
if (socketSubscribers.has(roomParts)) return (subscribedId.current = id);
if (subscribedId.current && !socketSubscribers.has(roomParts)) {
unsubscribe(subscribedId.current, false);
}
socketHelper.emit({
command: "subscribe",
data: {
roomParts: `DIR-${id}`,
individual: true,
},
});
subscribedId.current = id;
};
const unsubscribe = (id: number, clear = true) => {
if (clear) {
subscribedId.current = null;
}
if (id && !socketSubscribers.has(`DIR-${id}`)) {
socketHelper.emit({
command: "unsubscribe",
data: {
roomParts: `DIR-${id}`,
individual: true,
},
});
}
};
const addItem = React.useCallback((opt: any) => {
if (!opt?.data) return;
const data = JSON.parse(opt.data);
if (
data.folderId
? data.folderId !== subscribedId.current
: data.parentId !== subscribedId.current
)
return;
let item: null | Item = null;
if (opt?.type === "file") {
item = convertFilesToItems([data], getIcon, filterParam)[0];
} else if (opt?.type === "folder") {
item = !!data.roomType
? convertRoomsToItems([data])[0]
: convertFoldersToItems([data], disabledItems, filterParam)[0];
}
const callback: setItemsCallback = (value: Item[] | null) => {
if (!item || !value) return value;
if (opt.type === "folder") {
setTotal((value) => value + 1);
return [item, ...value];
}
if (opt.type === "file") {
let idx = 0;
for (let i = 0; i < value.length - 1; i++) {
if (!value[i].isFolder) break;
idx = i + 1;
}
const newValue = [...value];
newValue.splice(idx, 0, item);
setTotal((value) => value + 1);
return newValue;
}
return value;
};
setItems(callback);
}, []);
const updateItem = React.useCallback((opt: any) => {
if (!opt?.data) return;
const data = JSON.parse(opt.data);
if (
((data.folderId && data.folderId !== subscribedId.current) ||
(data.parentId && data.parentId !== subscribedId.current)) &&
data.id !== subscribedId.current
)
return;
let item: null | Item = null;
if (opt?.type === "file") {
item = convertFilesToItems([data], getIcon, filterParam)[0];
} else if (opt?.type === "folder") {
item = !!data.roomType
? convertRoomsToItems([data])[0]
: convertFoldersToItems([data], disabledItems, filterParam)[0];
}
if (item?.id === subscribedId.current) {
return setBreadCrumbs((value) => {
if (!value) return value;
const newValue = [...value];
if (newValue[newValue.length - 1].id === item?.id) {
newValue[newValue.length - 1].label = item.label;
}
return newValue;
});
}
const callback: setItemsCallback = (value: Item[] | null) => {
if (!item || !value) return value;
if (opt.type === "folder") {
const idx = value.findIndex((v) => v.id === item?.id && v.isFolder);
if (idx > -1) {
const newValue = [...value];
newValue.splice(idx, 1, item);
return newValue;
}
setBreadCrumbs((breadCrumbsValue) => {
return breadCrumbsValue;
});
}
if (opt.type === "file") {
const idx = value.findIndex((v) => v.id === item?.id && !v.isFolder);
if (idx > -1) {
const newValue = [...value];
newValue.splice(idx, 1, item);
return [...newValue];
}
}
return value;
};
setItems(callback);
}, []);
const deleteItem = React.useCallback((opt: any) => {
const callback: setItemsCallback = (value: Item[] | null) => {
if (!value) return value;
if (opt.type === "folder") {
const newValue = value.filter((v) => +v.id !== +opt?.id || !v.isFolder);
if (newValue.length !== value.length) {
setTotal((value) => value - 1);
}
return newValue;
}
if (opt.type === "file") {
const newValue = value.filter((v) => +v.id !== +opt?.id || v.isFolder);
if (newValue.length !== value.length) {
setTotal((value) => value - 1);
}
return newValue;
}
return value;
};
setItems(callback);
}, []);
React.useEffect(() => {
socketHelper.on("s:modify-folder", async (opt: any) => {
switch (opt?.cmd) {
case "create":
addItem(opt);
break;
case "update":
updateItem(opt);
break;
case "delete":
deleteItem(opt);
break;
}
});
}, [addItem, updateItem, deleteItem]);
return { subscribe, unsubscribe };
};
export default useSocketHelper;

View File

@ -1,40 +1,35 @@
import React, { useEffect } from "react";
/* eslint-disable no-restricted-syntax */
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, RoomsType } from "@docspace/shared/enums";
import { DeviceType } from "@docspace/shared/enums";
import { Selector } from "@docspace/shared/components/selector";
import { Aside } from "@docspace/shared/components/aside";
import { Backdrop } from "@docspace/shared/components/backdrop";
import { Portal } from "@docspace/shared/components/portal";
import { FolderType } from "@docspace/shared/enums";
import FilesSelector from "@docspace/shared/selectors/Files";
import { toastr } from "@docspace/shared/components/toast";
import { RowLoader, SearchLoader } from "@docspace/shared/skeletons/selector";
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 { SettingsStore } from "@docspace/shared/store/SettingsStore";
import {
BreadCrumb,
FilesSelectorProps,
Item,
Security,
} from "./FilesSelector.types";
TFileSecurity,
TFolder,
TFolderSecurity,
} from "@docspace/shared/api/files/types";
import { TBreadCrumb } from "@docspace/shared/components/selector/Selector.types";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { TSelectedFileInfo } from "@docspace/shared/selectors/Files/FilesSelector.types";
import { TRoomSecurity } from "@docspace/shared/api/rooms/types";
import { TTranslation } from "@docspace/shared/types";
import useRootHelper from "./helpers/useRootHelper";
import useRoomsHelper from "./helpers/useRoomsHelper";
import useLoadersHelper from "./helpers/useLoadersHelper";
import useFilesHelper from "./helpers/useFilesHelper";
import SelectedFolderStore from "SRC_DIR/store/SelectedFolderStore";
import FilesActionStore from "SRC_DIR/store/FilesActionsStore";
import UploadDataStore from "SRC_DIR/store/UploadDataStore";
import TreeFoldersStore from "SRC_DIR/store/TreeFoldersStore";
import DialogsStore from "SRC_DIR/store/DialogsStore";
import FilesStore from "SRC_DIR/store/FilesStore";
import InfoPanelStore from "SRC_DIR/store/InfoPanelStore";
import { FilesSelectorProps } from "./FilesSelector.types";
import { getAcceptButtonLabel, getHeaderLabel, getIsDisabled } from "./utils";
import useSocketHelper from "./helpers/useSocketHelper";
const FilesSelector = ({
const FilesSelectorWrapper = ({
isPanelVisible = false,
// withoutImmediatelyClose = false,
isThirdParty = false,
@ -59,8 +54,6 @@ const FilesSelector = ({
treeFolders,
theme,
selection,
disabledItems,
setConflictDialogData,
@ -101,256 +94,22 @@ const FilesSelector = ({
currentDeviceType,
embedded,
withHeader,
withHeader = true,
withCancelButton = true,
getIcon,
isRoomBackup,
roomsFolderId,
}: 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[];
fileExst?: string;
inPublic?: boolean;
} | null>(null);
const [total, setTotal] = React.useState<number>(0);
const [hasNextPage, setHasNextPage] = React.useState<boolean>(false);
const [isSelectedParentFolder, setIsSelectedParentFolder] =
React.useState<boolean>(false);
const [searchValue, setSearchValue] = React.useState<string>("");
const { t }: { t: TTranslation } = useTranslation([
"Files",
"Common",
"Translations",
]);
const [isRequestRunning, setIsRequestRunning] =
React.useState<boolean>(false);
const { subscribe, unsubscribe } = useSocketHelper({
socketHelper,
socketSubscribers,
setItems,
setBreadCrumbs,
setTotal,
disabledItems,
filterParam,
getIcon,
});
const {
setIsBreadCrumbsLoading,
isNextPageLoading,
setIsNextPageLoading,
isFirstLoad,
setIsFirstLoad,
showBreadCrumbsLoader,
showLoader,
} = useLoadersHelper({ items });
useEffect(() => {
setIsDataReady?.(!showLoader);
}, [showLoader, setIsDataReady]);
const { isRoot, setIsRoot, getRootData } = useRootHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setTotal,
setItems,
treeFolders,
setHasNextPage,
setIsNextPageLoading,
onSetBaseFolderPath,
isUserOnly,
});
const { getRoomList } = useRoomsHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
isFirstLoad,
setIsRoot,
searchValue,
isRoomsOnly,
onSetBaseFolderPath,
});
const { getFileList } = useFilesHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
selectedItemId,
isFirstLoad,
setIsRoot,
searchValue,
disabledItems,
setSelectedItemSecurity,
isThirdParty,
onSelectTreeNode,
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
getIcon,
t,
setIsSelectedParentFolder,
roomsFolderId,
});
const onSelectAction = (item: Item) => {
const inPublic =
breadCrumbs.findIndex((f: any) => f.roomType === RoomsType.PublicRoom) >
-1;
if (item.isFolder) {
setIsFirstLoad(true);
setItems(null);
setBreadCrumbs((value) => [
...value,
{
label: item.label,
id: item.id,
isRoom:
item.parentId === 0 && item.rootFolderType === FolderType.Rooms,
roomType: item.roomType,
shared: item.shared,
},
]);
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,
fileExst: item.fileExst,
inPublic: inPublic,
});
}
};
React.useEffect(() => {
if (!selectedItemId) return;
if (selectedItemId && isRoot) return unsubscribe(+selectedItemId);
subscribe(+selectedItemId);
}, [selectedItemId, isRoot]);
React.useEffect(() => {
const getRoomSettings = () => {
setSelectedItemType("rooms");
getRoomList(0, true);
};
const needRoomList = isRoomsOnly && !currentFolderId;
if (needRoomList) {
getRoomSettings();
return;
}
if (!currentFolderId) {
getRootData();
return;
}
setSelectedItemId(currentFolderId);
if (
needRoomList ||
(!isThirdParty &&
parentId === roomsFolderId &&
rootFolderType === FolderType.Rooms)
) {
getRoomSettings();
return;
}
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 maxLength = breadCrumbs.length - 1;
let foundParentId = false,
currentFolderIndex = -1;
const newBreadCrumbs = breadCrumbs.map((item, index) => {
if (!foundParentId) {
currentFolderIndex = disabledItems.findIndex(
(id) => id === item?.id,
);
}
if (index !== maxLength && currentFolderIndex !== -1) {
foundParentId = true;
!isSelectedParentFolder && setIsSelectedParentFolder(true);
}
if (index === maxLength && !foundParentId && isSelectedParentFolder)
setIsSelectedParentFolder(false);
return { ...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 = () => {
setInfoPanelIsMobileHidden(false);
@ -375,50 +134,28 @@ const FilesSelector = ({
onCloseAction();
};
const onSearchAction = (value: string, callback?: Function) => {
setIsFirstLoad(true);
setItems(null);
if (selectedItemType === "rooms") {
getRoomList(0, false, value === "" ? null : value);
} else {
getFileList(0, selectedItemId, false, value === "" ? null : value);
}
const getFilesArchiveError = (name: string) =>
t("Files:ArchivedRoomAction", { name });
setSearchValue(value);
callback?.();
};
const onClearSearchAction = (callback?: Function) => {
setIsFirstLoad(true);
setItems(null);
if (selectedItemType === "rooms") {
getRoomList(0, false, null);
} else {
getFileList(0, selectedItemId, false, null);
}
setSearchValue("");
callback?.();
};
const onAcceptAction = (
items: any,
accessRights: any,
const onAccept = async (
selectedItemId: string | number | undefined,
folderTitle: string,
isPublic: boolean,
breadCrumbs: TBreadCrumb[],
fileName: string,
isChecked: boolean,
selectedTreeNode: TFolder,
selectedFileInfo: TSelectedFileInfo,
) => {
const isPublic =
breadCrumbs.findIndex((f: any) => f.roomType === RoomsType.PublicRoom) >
-1;
if ((isMove || isCopy || isRestore || isRestoreAll) && !isEditorDialog) {
const folderTitle = breadCrumbs[breadCrumbs.length - 1].label;
const fileIds: number[] = [];
const folderIds: number[] = [];
let fileIds: any[] = [];
let folderIds: any[] = [];
for (let item of selection) {
if (item.fileExst || item.contentLength) {
for (const item of selection) {
if (
("fileExst" in item && item.fileExst) ||
("contentLength" in item && item.contentLength)
) {
fileIds.push(item.id);
} else if (item.id === selectedItemId) {
toastr.error(t("MoveToFolderMessage"));
@ -446,27 +183,30 @@ const FilesSelector = ({
return;
}
setIsRequestRunning(true);
setSelectedItems();
checkFileConflicts(selectedItemId, folderIds, fileIds)
.then(async (conflicts: any) => {
if (conflicts.length) {
setConflictDialogData(conflicts, operationData);
setIsRequestRunning(false);
} else {
setIsRequestRunning(false);
onCloseAndDeselectAction();
const move = !isCopy;
if (move) setMovingInProgress(move);
sessionStorage.setItem("filesSelectorPath", `${selectedItemId}`);
await itemOperationToFolder(operationData);
}
})
.catch((e: any) => {
toastr.error(e);
try {
const conflicts = (await checkFileConflicts(
selectedItemId,
folderIds,
fileIds,
)) as [];
if (conflicts.length) {
setConflictDialogData(conflicts, operationData);
setIsRequestRunning(false);
clearActiveOperations(fileIds, folderIds);
});
} else {
setIsRequestRunning(false);
onCloseAndDeselectAction();
const move = !isCopy;
if (move) setMovingInProgress(move);
sessionStorage.setItem("filesSelectorPath", `${selectedItemId}`);
await itemOperationToFolder(operationData);
}
} catch (e: unknown) {
toastr.error(e as TData);
setIsRequestRunning(false);
clearActiveOperations(fileIds, folderIds);
}
} else {
toastr.error(t("Common:ErrorEmptyList"));
}
@ -481,16 +221,14 @@ const FilesSelector = ({
return;
}
//setIsRequestRunning(true);
//onSetNewFolderPath && onSetNewFolderPath(breadCrumbs);
onSelectFolder && onSelectFolder(selectedItemId, breadCrumbs);
onSave &&
selectedItemId &&
if (onSelectFolder) onSelectFolder(selectedItemId, breadCrumbs);
if (onSave && selectedItemId)
onSave(null, selectedItemId, fileName, isChecked);
onSelectTreeNode && onSelectTreeNode(selectedTreeNode);
onSelectFile && onSelectFile(selectedFileInfo!, breadCrumbs);
!embedded && onCloseAndDeselectAction();
//!withoutImmediatelyClose && onCloseAction();
if (onSelectTreeNode) onSelectTreeNode(selectedTreeNode);
if (onSelectFile && selectedFileInfo)
onSelectFile(selectedFileInfo, breadCrumbs);
if (!embedded) onCloseAndDeselectAction();
}
};
@ -516,120 +254,84 @@ const FilesSelector = ({
isRestore,
);
const isDisabled = getIsDisabled(
isFirstLoad,
isSelectedParentFolder,
fromFolderId == selectedItemId,
selectedItemType === "rooms",
isRoot,
isCopy,
isMove,
isRestoreAll,
isRequestRunning,
selectedItemSecurity,
filterParam,
!!selectedFileInfo,
includeFolder,
isRestore,
);
const getIsDisabledAction = (
isFirstLoad: boolean,
isSelectedParentFolder: boolean,
selectedItemId: string | number | undefined,
selectedItemType: "rooms" | "files" | undefined,
isRoot: boolean,
selectedItemSecurity:
| TFileSecurity
| TFolderSecurity
| TRoomSecurity
| undefined,
selectedFileInfo: TSelectedFileInfo,
) => {
return getIsDisabled(
isFirstLoad,
isSelectedParentFolder,
fromFolderId === selectedItemId,
selectedItemType === "rooms",
isRoot,
isCopy,
isMove,
isRestoreAll,
isRequestRunning,
selectedItemSecurity,
filterParam,
!!selectedFileInfo,
includeFolder,
isRestore,
);
};
const SelectorBody = (
<Selector
return (
<FilesSelector
socketHelper={socketHelper}
socketSubscribers={socketSubscribers}
disabledItems={disabledItems}
filterParam={filterParam}
getIcon={getIcon}
setIsDataReady={setIsDataReady}
treeFolders={treeFolders}
onSetBaseFolderPath={onSetBaseFolderPath}
isUserOnly={isUserOnly}
isRoomsOnly={isRoomsOnly}
isThirdParty={isThirdParty}
rootThirdPartyId={rootThirdPartyId}
roomsFolderId={roomsFolderId}
currentFolderId={currentFolderId || 0}
parentId={parentId}
rootFolderType={rootFolderType || FolderType.Rooms}
currentDeviceType={currentDeviceType}
onCancel={onCloseAction}
onSubmit={onAccept}
getIsDisabled={getIsDisabledAction}
withHeader={withHeader}
headerLabel={headerLabel}
withoutBackButton
searchPlaceholder={t("Common:Search")}
searchValue={searchValue}
onSearch={onSearchAction}
onClearSearch={onClearSearchAction}
items={items ? items : []}
onSelect={onSelectAction}
acceptButtonLabel={acceptButtonLabel}
onAccept={onAcceptAction}
submitButtonLabel={acceptButtonLabel}
withCancelButton={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={
<RowLoader
isMultiSelect={false}
isUser={isRoot}
isContainer={showLoader}
/>
}
searchLoader={<SearchLoader />}
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}
isPanelVisible={isPanelVisible}
embedded={embedded}
withFooterInput={withFooterInput || false}
withFooterCheckbox={withFooterCheckbox || false}
footerInputHeader={footerInputHeader || ""}
currentFooterInputValue={currentFooterInputValue || ""}
footerCheckboxLabel={footerCheckboxLabel || ""}
descriptionText={
!filterParam || filterParam === "ALL"
? ""
: descriptionText ?? t("Common:SelectDOCXFormat")
}
acceptButtonId={
submitButtonId={
isMove || isCopy || isRestore ? "select-file-modal-submit" : ""
}
cancelButtonId={
isMove || isCopy || isRestore ? "select-file-modal-cancel" : ""
}
getFilesArchiveError={getFilesArchiveError}
/>
);
const selectorComponent = embedded ? (
SelectorBody
) : (
<>
<Backdrop
visible={isPanelVisible}
isAside
withBackground
zIndex={309}
onClick={onCloseAction}
/>
<Aside
visible={isPanelVisible}
withoutBodyScroll
zIndex={310}
onClose={onCloseAction}
>
{SelectorBody}
</Aside>
</>
);
return currentDeviceType === DeviceType.mobile && !embedded ? (
<Portal visible={isPanelVisible} element={<div>{selectorComponent}</div>} />
) : (
selectorComponent
);
};
export default inject(
@ -643,8 +345,24 @@ export default inject(
dialogsStore,
filesStore,
infoPanelStore,
}: any,
{ isCopy, isRestoreAll, isMove, isRestore, isPanelVisible, id }: any,
}: {
settingsStore: SettingsStore;
selectedFolderStore: SelectedFolderStore;
filesActionsStore: FilesActionStore;
uploadDataStore: UploadDataStore;
treeFoldersStore: TreeFoldersStore;
dialogsStore: DialogsStore;
filesStore: FilesStore;
infoPanelStore: InfoPanelStore;
},
{
isCopy,
isRestoreAll,
isMove,
isRestore,
isPanelVisible,
id,
}: FilesSelectorProps,
) => {
const { id: selectedId, parentId, rootFolderType } = selectedFolderStore;
@ -672,7 +390,7 @@ export default inject(
const { setIsMobileHidden: setInfoPanelIsMobileHidden } = infoPanelStore;
const { theme, socketHelper, currentDeviceType } = settingsStore;
const { socketHelper, currentDeviceType } = settingsStore;
const socketSubscribesId = socketHelper.socketSubscribers;
@ -685,7 +403,7 @@ export default inject(
filesSettingsStore,
} = filesStore;
const { getIcon } = filesSettingsStore;
const { isVisible: infoPanelIsVisible, selection: infoPanelSelection } =
const { isVisible: infoPanelIsVisible, infoPanelSelection } =
infoPanelStore;
const selections =
@ -705,27 +423,27 @@ export default inject(
? filesList
: isCopy
? selections
: selections.filter((f: any) => f && !f?.isEditing);
: selections.filter((f) => f && !f?.isEditing);
const disabledItems: any[] = [];
const disabledItems: (string | number)[] = [];
selectionsWithoutEditing.forEach((item: any) => {
selectionsWithoutEditing.forEach((item) => {
if ((item?.isFolder || item?.parentId) && item?.id) {
disabledItems.push(item.id);
}
});
const includeFolder =
selectionsWithoutEditing.filter((i: any) => i.isFolder).length > 0;
selectionsWithoutEditing.filter((i) => i.isFolder).length > 0;
const fromFolderId = id
? id
: rootFolderType === FolderType.Archive ||
rootFolderType === FolderType.TRASH
const fromFolderId =
id ||
(rootFolderType === FolderType.Archive ||
rootFolderType === FolderType.TRASH
? undefined
: selectedId === selectionsWithoutEditing[0]?.id
? parentId
: selectedId;
: selectedId);
const currentFolderId =
sessionPath && (isMove || isCopy || isRestore || isRestoreAll)
@ -738,16 +456,16 @@ export default inject(
parentId,
rootFolderType,
treeFolders,
isPanelVisible: isPanelVisible
? isPanelVisible
: (moveToPanelVisible ||
copyPanelVisible ||
restorePanelVisible ||
restoreAllPanelVisible) &&
!conflictResolveDialogVisible,
isPanelVisible:
isPanelVisible ||
((moveToPanelVisible ||
copyPanelVisible ||
restorePanelVisible ||
restoreAllPanelVisible) &&
!conflictResolveDialogVisible),
setMoveToPanelVisible,
setRestorePanelVisible,
theme,
selection: selectionsWithoutEditing,
disabledItems,
setConflictDialogData,
@ -771,4 +489,4 @@ export default inject(
roomsFolderId,
};
},
)(observer(FilesSelector));
)(observer(FilesSelectorWrapper));

View File

@ -89,7 +89,10 @@ export async function getReferenceData(data: {
return res;
}
export async function getFolderInfo(folderId: number, skipRedirect = false) {
export async function getFolderInfo(
folderId: number | string,
skipRedirect = false,
) {
const options: AxiosRequestConfig = {
method: "get",
url: `/files/folder/${folderId}`,
@ -114,7 +117,7 @@ export async function getFolderPath(folderId: number) {
export async function getFolder(
folderId: string | number,
filter: FilesFilter,
signal: AbortSignal,
signal?: AbortSignal,
) {
let params = folderId;
@ -163,6 +166,7 @@ export async function getFoldersTree() {
const name = getFolderClassNameByType(type);
return {
...current,
id,
key: `0-${index}`,
parentId,
@ -175,7 +179,7 @@ export async function getFoldersTree() {
filesCount,
newItems,
security,
};
} as TFolder;
});
}
@ -1328,4 +1332,3 @@ export function deleteFilesFromRecent(fileIds: number[]) {
},
});
}

View File

@ -137,6 +137,7 @@ export type TFolder = {
rootFolderType: FolderType;
isArchive?: boolean;
roomType?: RoomsType;
path?: TPathParts[];
};
export type TGetFolderPath = TFolder[];
@ -345,4 +346,3 @@ export type TFileLink = {
title: string;
};
};

View File

@ -378,7 +378,10 @@ export function getUsersByQuery(query) {
});
}
export function getMembersList(roomId, filter = Filter.getDefault()) {
export async function getMembersList(
roomId: string | number,
filter = Filter.getDefault(),
) {
let params = "";
if (filter) {
@ -397,16 +400,16 @@ export function getMembersList(roomId, filter = Filter.getDefault()) {
params = `excludeShared=${excludeShared}`;
}
return request({
const res = (await request({
method: "get",
url: `people/room/${roomId}${params}`,
}).then((res) => {
res.items = res.items.map((user) => {
if (user && user.displayName) {
user.displayName = Encoder.htmlDecode(user.displayName);
}
return user;
});
return res;
})) as { items: TUser[]; total: number };
res.items = res.items.map((user) => {
if (user && user.displayName) {
user.displayName = Encoder.htmlDecode(user.displayName);
}
return user;
});
return res;
}

View File

@ -52,6 +52,7 @@ export type TRoom = {
rootFolderType: FolderType;
updatedBy: TCreatedBy;
isArchive?: boolean;
security: TRoomSecurity;
};
export type TGetRooms = {

View File

@ -26,7 +26,7 @@ const Selector = ({
withHeader,
headerProps,
isBreadCrumbsLoading,
isBreadCrumbsLoading = false,
breadCrumbsLoader,
withBreadCrumbs,
breadCrumbs,
@ -89,6 +89,7 @@ const Selector = ({
loadNextPage,
totalItems,
isLoading,
disableFirstFetch,
alwaysShowFooter,
@ -109,55 +110,19 @@ const Selector = ({
);
const [isFooterCheckboxChecked, setIsFooterCheckboxChecked] =
React.useState<boolean>(isChecked || false);
const [selectedAccess, setSelectedAccess] =
React.useState<TAccessRight | null>(null);
const compareSelectedItems = React.useCallback(
(newList: TSelectorItem[]) => {
let isEqual = true;
if (selectedItems?.length !== newList.length) {
return setFooterVisible(true);
}
if (newList.length === 0 && selectedItems?.length === 0) {
return setFooterVisible(false);
}
newList.forEach((item) => {
isEqual = selectedItems.some((x) => x.id === item.id);
});
return setFooterVisible(!isEqual);
},
[selectedItems],
);
const onSelectAction = (item: TSelectorItem) => {
onSelect?.({
...item,
});
if (isMultiSelect) {
setRenderedItems((value) => {
const idx = value.findIndex((x) => item.id === x.id);
const newValue = value.map((i: TSelectorItem) => ({ ...i }));
if (idx === -1) return newValue;
newValue[idx].isSelected = !value[idx].isSelected;
return newValue;
});
if (item.isSelected) {
setNewSelectedItems((value) => {
const newValue = value
.filter((x) => x.id !== item.id)
.map((x) => ({ ...x }));
compareSelectedItems(newValue);
const newValue = value.filter((x) => x.id !== item.id);
return newValue;
});
} else {
@ -166,11 +131,18 @@ const Selector = ({
...item,
});
compareSelectedItems(value);
return value;
return [...value];
});
}
setRenderedItems((value) => {
const idx = value.findIndex((x) => item.id === x.id);
if (idx === -1) return value;
value[idx] = { ...value[idx], isSelected: !value[idx].isSelected };
return value;
});
} else {
setRenderedItems((value) => {
const idx = value.findIndex((x) => item.id === x.id);
@ -184,19 +156,13 @@ const Selector = ({
newValue[idx].isSelected = !item.isSelected;
return newValue;
return [...newValue];
});
const newItem = {
...item,
};
if (item.isSelected) {
setNewSelectedItems([]);
compareSelectedItems([]);
} else {
setNewSelectedItems([newItem]);
compareSelectedItems([newItem]);
setNewSelectedItems([item]);
}
}
};
@ -212,22 +178,27 @@ const Selector = ({
) {
const cloneItems = items.map((x) => ({ ...x }));
const cloneRenderedItems = items.map((x) => ({ ...x, isSelected: true }));
setRenderedItems((i) => {
const cloneRenderedItems = i.map((x) => ({
...x,
isSelected: true,
}));
setRenderedItems(cloneRenderedItems);
return cloneRenderedItems;
});
setNewSelectedItems(cloneItems);
compareSelectedItems(cloneItems);
} else {
const cloneRenderedItems = items.map((x) => ({
...x,
isSelected: false,
}));
setRenderedItems((i) => {
const cloneRenderedItems = i.map((x) => ({
...x,
isSelected: false,
}));
setRenderedItems(cloneRenderedItems);
return cloneRenderedItems;
});
setNewSelectedItems([]);
compareSelectedItems([]);
}
}, [compareSelectedItems, items, newSelectedItems.length, onSelectAll]);
}, [items, newSelectedItems.length, onSelectAll]);
const onSubmitAction = () => {
onSubmit(
@ -248,19 +219,45 @@ const Selector = ({
const loadMoreItems = React.useCallback(
(startIndex: number) => {
if (startIndex === 1) return; // fix double fetch of the first page
if (!isNextPageLoading) loadNextPage?.(startIndex - 1);
if (!isNextPageLoading) loadNextPage(startIndex - 1);
},
[isNextPageLoading, loadNextPage],
);
React.useEffect(() => {
if (disableFirstFetch) return;
loadNextPage(0);
}, [disableFirstFetch, loadNextPage]);
React.useEffect(() => {
if (selectedAccessRight) setSelectedAccess({ ...selectedAccessRight });
}, [selectedAccessRight]);
React.useEffect(() => {
let isEqual = true;
if (selectedItems?.length !== newSelectedItems.length) {
return setFooterVisible(true);
}
if (newSelectedItems.length === 0 && selectedItems?.length === 0) {
return setFooterVisible(false);
}
newSelectedItems.forEach((item) => {
isEqual = selectedItems.some((x) => x.id === item.id);
});
setFooterVisible(!isEqual);
}, [selectedItems, newSelectedItems]);
React.useLayoutEffect(() => {
if (items && selectedItems) {
if (selectedItems.length === 0 || !isMultiSelect) {
if (items) {
if (
!selectedItems ||
(selectedItems && selectedItems.length === 0) ||
!isMultiSelect
) {
const cloneItems = items.map((x) => ({ ...x, isSelected: false }));
return setRenderedItems(cloneItems);
}
@ -279,9 +276,8 @@ const Selector = ({
setRenderedItems(newItems);
setNewSelectedItems(cloneSelectedItems);
compareSelectedItems(cloneSelectedItems);
}
}, [items, selectedItems, isMultiSelect, compareSelectedItems]);
}, [items, selectedItems, isMultiSelect]);
const breadCrumbsProps: TSelectorBreadCrumbs = withBreadCrumbs
? {
@ -382,7 +378,7 @@ const Selector = ({
<Body
withHeader={withHeader}
footerVisible={footerVisible || !!alwaysShowFooter}
items={renderedItems}
items={[...renderedItems]}
isMultiSelect={isMultiSelect}
onSelect={onSelectAction}
// empty screen
@ -432,20 +428,4 @@ const Selector = ({
);
};
Selector.defaultProps = {
isMultiSelect: false,
withSelectAll: false,
withAccessRights: false,
withCancelButton: false,
withoutBackButton: false,
isBreadCrumbsLoading: false,
withSearch: true,
withFooterInput: false,
alwaysShowFooter: false,
disableAcceptButton: false,
withHeader: true,
selectedItems: [],
};
export { Selector };

View File

@ -1,5 +1,7 @@
import React from "react";
import { RoomsType } from "../../enums";
import { TFileSecurity, TFolderSecurity } from "../../api/files/types";
import { TRoomSecurity } from "../../api/rooms/types";
import { RoomsType, ShareAccessRights } from "../../enums";
import { AvatarRole } from "../avatar";
// header
@ -18,7 +20,7 @@ export type HeaderProps = {
headerLabel: string;
} & (THeaderBackButton | THeaderNonBackButton);
type TSelectorHeader =
export type TSelectorHeader =
| {
withHeader: true;
headerProps: HeaderProps;
@ -33,6 +35,7 @@ export type TBreadCrumb = {
isRoom?: boolean;
minWidth?: string;
onClick?: (e: React.MouseEvent, open: boolean, item: TBreadCrumb) => void;
roomType?: RoomsType;
};
export interface BreadCrumbsProps {
@ -92,7 +95,7 @@ export interface SearchProps {
setIsSearch: React.Dispatch<React.SetStateAction<boolean>>;
}
type TSelectorSearch =
export type TSelectorSearch =
| {
withSearch: true;
searchLoader: React.ReactNode;
@ -141,7 +144,7 @@ type TSelectorEmptyScreen = {
};
// submit button
type TSelectorSubmitButton = {
export type TSelectorSubmitButton = {
submitButtonLabel: string;
disableSubmitButton: boolean;
onSubmit: (
@ -226,7 +229,7 @@ export type TSelectorCheckbox =
| {
withFooterCheckbox?: undefined;
footerCheckboxLabel?: undefined;
isChecked: boolean;
isChecked?: boolean;
setIsChecked?: undefined;
};
@ -259,13 +262,8 @@ export type SelectorProps = TSelectorHeader &
selectedAccessRight?: TAccessRight;
onAccessRightsChange?: (access: TAccessRight) => void;
loadNextPage:
| ((
startIndex: number,
search?: string,
...rest: unknown[]
) => Promise<void>)
| null;
disableFirstFetch?: boolean;
loadNextPage: (startIndex: number) => Promise<void>;
hasNextPage: boolean;
isNextPageLoading: boolean;
totalItems: number;
@ -273,7 +271,11 @@ export type SelectorProps = TSelectorHeader &
rowLoader: React.ReactNode;
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
renderCustomItem?: (
label: string,
role?: AvatarRole,
email?: string,
) => React.ReactNode | null;
alwaysShowFooter?: boolean;
descriptionText?: string;
@ -292,7 +294,11 @@ export type BodyProps = TSelectorBreadCrumbs &
isMultiSelect: boolean;
items: TSelectorItem[];
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
renderCustomItem?: (
label: string,
role?: AvatarRole,
email?: string,
) => React.ReactNode | null;
onSelect: (item: TSelectorItem) => void;
loadMoreItems: (startIndex: number) => void;
@ -323,9 +329,22 @@ type TSelectorItemLogo =
icon?: undefined;
avatar: string;
role?: AvatarRole;
hasAvatar?: boolean;
}
| { color: string; icon: undefined; avatar?: string; role?: undefined }
| { color?: undefined; icon: string; avatar?: undefined; role?: undefined };
| {
hasAvatar?: undefined;
color: string;
icon?: undefined;
avatar?: undefined;
role?: undefined;
}
| {
hasAvatar?: undefined;
color?: undefined;
icon: string;
avatar?: undefined;
role?: undefined;
};
type TSelectorItemType =
| {
@ -333,24 +352,68 @@ type TSelectorItemType =
fileExst?: undefined;
roomType?: undefined;
shared?: undefined;
isOwner: boolean;
isAdmin: boolean;
isVisitor: boolean;
isCollaborator: boolean;
access: ShareAccessRights | string | number;
isFolder?: undefined;
parentId?: undefined;
rootFolderType?: undefined;
filesCount?: undefined;
foldersCount?: undefined;
security?: undefined;
}
| {
email?: undefined;
fileExst: string;
roomType?: undefined;
shared?: boolean;
isOwner?: undefined;
isAdmin?: undefined;
isVisitor?: undefined;
isCollaborator?: undefined;
access?: undefined;
isFolder?: undefined;
parentId?: string | number;
rootFolderType?: string | number;
filesCount?: undefined;
foldersCount?: undefined;
security?: TFileSecurity;
}
| {
email?: undefined;
fileExst?: undefined;
roomType: RoomsType;
shared?: boolean;
isOwner?: undefined;
isAdmin?: undefined;
isVisitor?: undefined;
isCollaborator?: undefined;
access?: undefined;
isFolder: boolean;
parentId?: string | number;
rootFolderType?: string | number;
filesCount?: number;
foldersCount?: number;
security?: TRoomSecurity;
}
| {
email?: undefined;
fileExst?: undefined;
roomType?: undefined;
shared?: boolean;
isOwner?: undefined;
isAdmin?: undefined;
isVisitor?: undefined;
isCollaborator?: undefined;
access?: undefined;
isFolder: boolean;
parentId?: string | number;
rootFolderType?: string | number;
filesCount?: number;
foldersCount?: number;
security?: TFolderSecurity;
};
export type TSelectorItem = TSelectorItemLogo &
@ -358,9 +421,9 @@ export type TSelectorItem = TSelectorItemLogo &
key?: string;
id?: string | number;
label: string;
displayName?: string;
isSelected?: boolean;
isDisabled?: boolean;
};
@ -370,7 +433,11 @@ export type Data = {
isMultiSelect: boolean;
isItemLoaded: (index: number) => boolean;
rowLoader: React.ReactNode;
renderCustomItem?: (...args: unknown[]) => React.ReactNode | null;
renderCustomItem?: (
label: string,
role?: AvatarRole,
email?: string,
) => React.ReactNode | null;
};
export interface ItemProps {

View File

@ -12,7 +12,7 @@ const Header = React.memo(
({ onBackClick, withoutBackButton, headerLabel }: HeaderProps) => {
return (
<StyledHeader>
{!withoutBackButton && (
{!withoutBackButton && typeof withoutBackButton === "boolean" && (
<IconButton
className="arrow-button"
iconName={ArrowPathReactSvgUrl}

View File

@ -135,3 +135,5 @@ export const RTL_LANGUAGES = Object.freeze([
"ur",
"yi",
]);
export const HTML_EXST = [".htm", ".mht", ".html"];

View File

@ -0,0 +1,14 @@
import { TBreadCrumb } from "../../components/selector/Selector.types";
export const DEFAULT_BREAD_CRUMB: TBreadCrumb = {
label: "DocSpace",
id: 0,
isRoom: false,
};
export const SHOW_LOADER_TIMER = 500;
export const MIN_LOADER_TIMER = 500;
export const DEFAULT_FILE_EXTS = "file";
export const PAGE_COUNT = 100;

View File

@ -0,0 +1,158 @@
import { TSelectorItem } from "../../components/selector";
import { TBreadCrumb } from "../../components/selector/Selector.types";
import { TFileSecurity, TFolder, TFolderSecurity } from "../../api/files/types";
import SocketIOHelper from "../../utils/socket";
import { DeviceType, FolderType } from "../../enums";
import { TRoomSecurity } from "../../api/rooms/types";
export interface UseRootHelperProps {
setBreadCrumbs: React.Dispatch<React.SetStateAction<TBreadCrumb[]>>;
setIsBreadCrumbsLoading: React.Dispatch<React.SetStateAction<boolean>>;
setTotal: React.Dispatch<React.SetStateAction<number>>;
setItems: React.Dispatch<React.SetStateAction<TSelectorItem[]>>;
setIsNextPageLoading: React.Dispatch<React.SetStateAction<boolean>>;
setHasNextPage: React.Dispatch<React.SetStateAction<boolean>>;
setIsInit: (value: boolean) => void;
treeFolders?: TFolder[];
isUserOnly?: boolean;
}
export interface UseLoadersHelperProps {
items: TSelectorItem[];
}
export type UseSocketHelperProps = {
socketHelper: SocketIOHelper;
socketSubscribers: Set<string>;
setItems: React.Dispatch<React.SetStateAction<TSelectorItem[]>>;
setBreadCrumbs: React.Dispatch<React.SetStateAction<TBreadCrumb[]>>;
setTotal: React.Dispatch<React.SetStateAction<number>>;
disabledItems: (string | number)[];
filterParam?: string;
getIcon: (fileExst: string) => string;
};
export type UseRoomsHelperProps = {
setBreadCrumbs: (items: TBreadCrumb[]) => void;
setIsBreadCrumbsLoading: (value: boolean) => void;
setIsNextPageLoading: (value: boolean) => void;
setHasNextPage: (value: boolean) => void;
setTotal: (value: number) => void;
setItems: React.Dispatch<React.SetStateAction<TSelectorItem[]>>;
isFirstLoad: boolean;
setIsRoot: (value: boolean) => void;
searchValue?: string;
isRoomsOnly: boolean;
onSetBaseFolderPath?: (
value: number | string | undefined | TBreadCrumb[],
) => void;
isInit: boolean;
setIsInit: (value: boolean) => void;
};
export type UseFilesHelpersProps = {
roomsFolderId?: number;
setBreadCrumbs: (items: TBreadCrumb[]) => void;
setIsBreadCrumbsLoading: (value: boolean) => void;
setIsSelectedParentFolder: (value: boolean) => void;
setIsNextPageLoading: (value: boolean) => void;
setHasNextPage: (value: boolean) => void;
setTotal: (value: number) => void;
setItems: React.Dispatch<React.SetStateAction<TSelectorItem[]>>;
isFirstLoad: boolean;
selectedItemId: string | number | undefined;
setIsRoot: (value: boolean) => void;
setIsInit: (value: boolean) => void;
searchValue?: string;
disabledItems: string[] | number[];
setSelectedItemSecurity: (value: TFileSecurity | TFolderSecurity) => void;
isThirdParty: boolean;
setSelectedTreeNode: (treeNode: TFolder) => void;
filterParam?: string;
getRootData?: () => Promise<void>;
onSetBaseFolderPath?: (
value: number | string | undefined | TBreadCrumb[],
) => void;
isRoomsOnly: boolean;
rootThirdPartyId?: string;
getRoomList?: (
startIndex: number,
search?: string | null,
isInit?: boolean,
isErrorPath?: boolean,
) => Promise<void>;
getIcon: (fileExst: string) => string;
getFilesArchiveError: (name: string) => string;
isInit: boolean;
};
export type TSelectedFileInfo = {
id: number | string;
title: string;
path?: string[] | undefined;
fileExst?: string | undefined;
inPublic?: boolean | undefined;
} | null;
export interface FilesSelectorProps {
socketHelper: SocketIOHelper;
socketSubscribers: Set<string>;
disabledItems: string[] | number[];
filterParam?: string;
getIcon?: (size: number, fileExst: string) => string;
treeFolders?: TFolder[];
onSetBaseFolderPath?: (
value: number | string | undefined | TBreadCrumb[],
) => void;
isUserOnly?: boolean;
isRoomsOnly: boolean;
isThirdParty: boolean;
rootThirdPartyId?: string;
roomsFolderId?: number;
currentFolderId: number | string;
parentId?: number | string;
rootFolderType: FolderType;
onCancel: () => void;
onSubmit: (
selectedItemId: string | number | undefined,
folderTitle: string,
isPublic: boolean,
breadCrumbs: TBreadCrumb[],
fileName: string,
isChecked: boolean,
selectedTreeNode: TFolder,
selectedFileInfo: TSelectedFileInfo,
) => void;
getIsDisabled: (
isFirstLoad: boolean,
isSelectedParentFolder: boolean,
selectedItemId: string | number | undefined,
selectedItemType: "rooms" | "files" | undefined,
isRoot: boolean,
selectedItemSecurity:
| TFileSecurity
| TFolderSecurity
| TRoomSecurity
| undefined,
selectedFileInfo: TSelectedFileInfo,
) => boolean;
setIsDataReady?: (value: boolean) => void;
withHeader: boolean;
headerLabel: string;
submitButtonLabel: string;
withCancelButton: boolean;
withFooterInput: boolean;
withFooterCheckbox: boolean;
footerInputHeader: string;
currentFooterInputValue: string;
footerCheckboxLabel: string;
descriptionText: string;
submitButtonId?: string;
cancelButtonId?: string;
embedded?: boolean;
isPanelVisible: boolean;
currentDeviceType: DeviceType;
getFilesArchiveError: (name: string) => string;
}

View File

@ -0,0 +1,118 @@
import { TSelectorItem } from "../../components/selector";
import { TFile, TFolder } from "../../api/files/types";
import { TRoom } from "../../api/rooms/types";
import { getIconPathByFolderType } from "../../utils/common";
import { iconSize32 } from "../../utils/image-helpers";
import { DEFAULT_FILE_EXTS } from "./FilesSelector.constants";
export const convertFoldersToItems: (
folders: TFolder[],
disabledItems: (number | string)[],
filterParam?: string,
) => TSelectorItem[] = (
folders: TFolder[],
disabledItems: (number | string)[],
filterParam?: string,
) => {
const items = folders.map((folder: TFolder) => {
const {
id,
title,
// roomType,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
} = folder;
const folderIconPath = getIconPathByFolderType(rootFolderType);
const icon = iconSize32.get(folderIconPath) as string;
return {
id,
label: title,
title,
icon,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
isFolder: true,
// roomType,
isDisabled: filterParam ? false : disabledItems?.includes(id),
};
});
return items;
};
export const convertFilesToItems: (
files: TFile[],
getIcon: (fileExst: string) => string,
filterParam?: string,
) => TSelectorItem[] = (
files: TFile[],
getIcon: (fileExst: string) => string,
filterParam?: string,
) => {
const items = files.map((file) => {
const { id, title, security, folderId, rootFolderType, fileExst } = file;
const icon = getIcon(fileExst || DEFAULT_FILE_EXTS);
const label = title.replace(fileExst, "") || fileExst;
return {
id,
label,
title,
icon,
security,
parentId: folderId,
rootFolderType,
isDisabled: !filterParam,
fileExst,
};
});
return items;
};
export const convertRoomsToItems: (rooms: TRoom[]) => TSelectorItem[] = (
rooms: TRoom[],
) => {
const items = rooms.map((room) => {
const {
id,
title,
roomType,
logo,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
} = room;
const icon = logo.medium || "";
const iconProp = icon ? { icon } : { color: logo.color as string };
return {
id,
label: title,
title,
filesCount,
foldersCount,
security,
parentId,
rootFolderType,
isFolder: true,
roomType,
...iconProp,
};
});
return items;
};

View File

@ -0,0 +1,304 @@
import React from "react";
import { getFolder, getFolderInfo } from "../../../api/files";
import FilesFilter from "../../../api/files/filter";
import {
ApplyFilterOption,
FilesSelectorFilterTypes,
FilterType,
FolderType,
} from "../../../enums";
import { toastr } from "../../../components/toast";
import { TSelectorItem } from "../../../components/selector";
import { TData } from "../../../components/toast/Toast.type";
import { TBreadCrumb } from "../../../components/selector/Selector.types";
import { PAGE_COUNT, DEFAULT_BREAD_CRUMB } from "../FilesSelector.constants";
import { UseFilesHelpersProps } from "../FilesSelector.types";
import {
convertFilesToItems,
convertFoldersToItems,
} from "../FilesSelector.utils";
const useFilesHelper = ({
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
setBreadCrumbs,
setIsBreadCrumbsLoading,
isFirstLoad,
selectedItemId,
setIsRoot,
searchValue,
disabledItems,
setSelectedItemSecurity,
isThirdParty,
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
getIcon,
setIsSelectedParentFolder,
roomsFolderId,
getFilesArchiveError,
isInit,
setIsInit,
}: UseFilesHelpersProps) => {
const requestRunning = React.useRef(false);
const initRef = React.useRef(isInit);
const firstLoadRef = React.useRef(isFirstLoad);
const disabledItemsRef = React.useRef(disabledItems);
React.useEffect(() => {
disabledItemsRef.current = disabledItems;
}, [disabledItems]);
React.useEffect(() => {
firstLoadRef.current = isFirstLoad;
}, [isFirstLoad]);
React.useEffect(() => {
initRef.current = isInit;
}, [isInit]);
const getFileList = React.useCallback(
async (startIndex: number) => {
if (requestRunning.current) return;
requestRunning.current = true;
setIsNextPageLoading(true);
const currentSearch = 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.extension = FilesSelectorFilterTypes.DOCX;
break;
case FilesSelectorFilterTypes.IMG:
filter.filterType = FilterType.ImagesOnly;
break;
case FilesSelectorFilterTypes.BackupOnly:
filter.extension = "gz,tar";
break;
case FilesSelectorFilterTypes.DOCXF:
filter.filterType = FilterType.OFormTemplateOnly;
break;
case FilesSelectorFilterTypes.XLSX:
filter.filterType = FilterType.SpreadsheetsOnly;
break;
case FilesSelectorFilterTypes.ALL:
filter.filterType = FilterType.FilesOnly;
break;
default:
}
}
const id = selectedItemId || "";
filter.folder = id.toString();
const setSettings = async (
folderId: string | number,
isErrorPath = false,
) => {
if (initRef.current && getRootData) {
const folder = await getFolderInfo(folderId, true);
const isArchive = folder.rootFolderType === FolderType.Archive;
if (folder.rootFolderType === FolderType.TRASH || isArchive) {
if (isRoomsOnly && getRoomList) {
await getRoomList(0);
onSetBaseFolderPath?.([]);
const error = getFilesArchiveError(folder.title);
toastr.error(error);
requestRunning.current = false;
return;
}
await getRootData();
if (onSetBaseFolderPath && isArchive) {
onSetBaseFolderPath?.([]);
const error = getFilesArchiveError(folder.title);
toastr.error(error);
}
requestRunning.current = false;
return;
}
}
const currentFolder = await getFolder(folderId, filter);
const { folders, files, total, count, pathParts, current } =
currentFolder;
setSelectedItemSecurity(current.security);
const foldersList: TSelectorItem[] = convertFoldersToItems(
folders,
disabledItemsRef.current,
filterParam,
);
const filesList: TSelectorItem[] = convertFilesToItems(
files,
getIcon,
filterParam,
);
const itemList = [...foldersList, ...filesList];
setHasNextPage(count === PAGE_COUNT);
setSelectedTreeNode?.({ ...current, path: pathParts });
if (initRef.current) {
let foundParentId = false;
let currentFolderIndex = -1;
const breadCrumbs: TBreadCrumb[] = pathParts.map(
(
{
id: breadCrumbId,
title,
roomType,
}: {
id: number | string;
title: string;
roomType?: number;
},
index,
) => {
if (!foundParentId && disabledItemsRef.current) {
currentFolderIndex = disabledItemsRef.current.findIndex(
(x) => x === id,
);
}
if (!foundParentId && currentFolderIndex !== -1) {
foundParentId = true;
setIsSelectedParentFolder(true);
}
return {
label: title,
id: breadCrumbId,
isRoom: roomsFolderId === id || index === 0,
roomType,
};
},
);
breadCrumbs.forEach((item, idx) => {
if (item.roomType) breadCrumbs[idx].isRoom = true;
});
if (!isThirdParty && !isRoomsOnly)
breadCrumbs.unshift({ ...DEFAULT_BREAD_CRUMB });
onSetBaseFolderPath?.(isErrorPath ? [] : breadCrumbs);
setBreadCrumbs(breadCrumbs);
setIsBreadCrumbsLoading(false);
}
if (firstLoadRef.current || startIndex === 0) {
setTotal(total);
setItems(itemList);
} else {
setItems((prevState) => {
if (prevState) return [...prevState, ...itemList];
return [...itemList];
});
}
setIsRoot(false);
setIsInit(false);
setIsNextPageLoading(false);
};
try {
await setSettings(id);
requestRunning.current = false;
} catch (e) {
sessionStorage.removeItem("filesSelectorPath");
if (isThirdParty && rootThirdPartyId) {
await setSettings(rootThirdPartyId, true);
toastr.error(e as TData);
requestRunning.current = false;
return;
}
if (isRoomsOnly && getRoomList) {
await getRoomList(0, null, true, true);
toastr.error(e as TData);
requestRunning.current = false;
return;
}
requestRunning.current = false;
getRootData?.();
if (onSetBaseFolderPath) {
onSetBaseFolderPath([]);
toastr.error(e as TData);
}
}
},
[
setIsNextPageLoading,
searchValue,
filterParam,
selectedItemId,
getRootData,
setSelectedItemSecurity,
getIcon,
setHasNextPage,
setSelectedTreeNode,
setIsRoot,
setIsInit,
isRoomsOnly,
getRoomList,
onSetBaseFolderPath,
getFilesArchiveError,
isThirdParty,
setBreadCrumbs,
setIsBreadCrumbsLoading,
roomsFolderId,
setIsSelectedParentFolder,
setTotal,
setItems,
rootThirdPartyId,
],
);
return { getFileList };
};
export default useFilesHelper;

View File

@ -0,0 +1,253 @@
import React from "react";
import { TFilesSettings } from "../../../api/files/types";
import { getSettingsFiles } from "../../../api/files";
import { presentInArray } from "../../../utils";
import { iconSize32 } from "../../../utils/image-helpers";
import { HTML_EXST } from "../../../constants";
const useFilesSettings = (
getIconProp?: (size: number, fileExst: string) => string,
) => {
const [settings, setSettings] = React.useState({} as TFilesSettings);
const requestRunning = React.useRef(false);
const initSettings = React.useCallback(async () => {
if (requestRunning.current) return;
requestRunning.current = true;
if (getIconProp) return;
const res = await getSettingsFiles();
setSettings(res);
requestRunning.current = false;
}, [getIconProp]);
React.useEffect(() => {
if (settings.extsArchive) return;
initSettings();
}, [initSettings, settings.extsArchive]);
const isArchive = React.useCallback(
(extension: string) => presentInArray(settings.extsArchive, extension),
[settings.extsArchive],
);
const isImage = React.useCallback(
(extension: string) => presentInArray(settings.extsImage, extension),
[settings.extsImage],
);
const isSound = React.useCallback(
(extension: string) => presentInArray(settings.extsAudio, extension),
[settings.extsAudio],
);
const isHtml = React.useCallback(
(extension: string) => presentInArray(HTML_EXST, extension),
[],
);
const getIcon = React.useCallback(
(fileExst: string) => {
if (getIconProp) return getIconProp(32, fileExst);
const isArchiveItem = isArchive(fileExst);
const isImageItem = isImage(fileExst);
const isSoundItem = isSound(fileExst);
const isHtmlItem = isHtml(fileExst);
let path = "";
if (isArchiveItem) path = "file_archive.svg";
if (isImageItem) path = "image.svg";
if (isSoundItem) path = "sound.svg";
if (isHtmlItem) path = "html.svg";
if (path) return iconSize32.get(path) ?? "";
switch (fileExst) {
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;
case ".sxc":
path = "sxc.svg";
break;
case ".et":
path = "et.svg";
break;
case ".ett":
path = "ett.svg";
break;
case ".sxw":
path = "sxw.svg";
break;
case ".stw":
path = "stw.svg";
break;
case ".wps":
path = "wps.svg";
break;
case ".wpt":
path = "wpt.svg";
break;
case ".mhtml":
path = "mhtml.svg";
break;
case ".dps":
path = "dps.svg";
break;
case ".dpt":
path = "dpt.svg";
break;
case ".sxi":
path = "sxi.svg";
break;
default:
path = "file.svg";
break;
}
return iconSize32.get(path) ?? "";
},
[getIconProp, isArchive, isHtml, isImage, isSound],
);
return { getIcon };
};
export default useFilesSettings;

View File

@ -1,20 +1,23 @@
import React from "react";
import { useLoadersHelperProps } from "../FilesSelector.types";
import { MIN_LOADER_TIMER, SHOW_LOADER_TIMER } from "../utils";
import { UseLoadersHelperProps } from "../FilesSelector.types";
import {
MIN_LOADER_TIMER,
SHOW_LOADER_TIMER,
} from "../FilesSelector.constants";
const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
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 [isFirstLoad, setIsFirstLoad] = React.useState(true);
const startLoader = React.useRef<Date | null>(new Date());
const breadCrumbsLoaderTimeout = React.useRef<NodeJS.Timeout | null>(null);
@ -23,6 +26,7 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
const isMount = React.useRef<boolean>(true);
React.useEffect(() => {
isMount.current = true;
return () => {
isMount.current = false;
};
@ -33,26 +37,24 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
setShowLoader(true);
startLoader.current = new Date();
} else {
if (startLoader.current) {
const currentDate = new Date();
} else if (startLoader.current) {
const currentDate = new Date();
const ms = Math.abs(
startLoader.current.getTime() - currentDate.getTime()
);
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);
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]);
@ -63,7 +65,7 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
}
breadCrumbsStartLoader.current = new Date();
breadCrumbsLoaderTimeout.current = setTimeout(() => {
isMount.current && setShowBreadCrumbsLoader(true);
if (isMount.current) setShowBreadCrumbsLoader(true);
}, SHOW_LOADER_TIMER);
} else {
if (breadCrumbsLoaderTimeout.current) {
@ -77,7 +79,7 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
const currentDate = new Date();
const ms = Math.abs(
breadCrumbsStartLoader.current.getTime() - currentDate.getTime()
breadCrumbsStartLoader.current.getTime() - currentDate.getTime(),
);
if (ms >= MIN_LOADER_TIMER) {
@ -96,14 +98,14 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
}, [isBreadCrumbsLoading]);
React.useEffect(() => {
if (isFirstLoad && items) {
if (items.length && isFirstLoad) {
setIsFirstLoad(false);
}
}, [isFirstLoad, items]);
React.useEffect(() => {
calculateLoader();
}, [isFirstLoad, calculateLoader]);
}, [calculateLoader]);
React.useEffect(() => {
calculateBreadCrumbsLoader();
@ -114,8 +116,10 @@ const useLoadersHelper = ({ items }: useLoadersHelperProps) => {
setIsBreadCrumbsLoading,
isNextPageLoading,
setIsNextPageLoading,
isFirstLoad,
setIsFirstLoad,
showBreadCrumbsLoader,
showLoader,
};

View File

@ -0,0 +1,110 @@
import React from "react";
import { getRooms } from "../../../api/rooms";
import RoomsFilter from "../../../api/rooms/filter";
import { TSelectorItem } from "../../../components/selector";
import { TBreadCrumb } from "../../../components/selector/Selector.types";
import { PAGE_COUNT, DEFAULT_BREAD_CRUMB } from "../FilesSelector.constants";
import { UseRoomsHelperProps } from "../FilesSelector.types";
import { convertRoomsToItems } from "../FilesSelector.utils";
const useRoomsHelper = ({
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
setBreadCrumbs,
setIsRoot,
onSetBaseFolderPath,
setIsBreadCrumbsLoading,
searchValue,
isRoomsOnly,
isFirstLoad,
isInit,
setIsInit,
}: UseRoomsHelperProps) => {
const requestRunning = React.useRef(false);
const initRef = React.useRef(isInit);
const firstLoadRef = React.useRef(isFirstLoad);
React.useEffect(() => {
firstLoadRef.current = isFirstLoad;
}, [isFirstLoad]);
React.useEffect(() => {
initRef.current = isInit;
}, [isInit]);
const getRoomList = React.useCallback(
async (startIndex: number) => {
if (requestRunning.current) return;
requestRunning.current = true;
setIsNextPageLoading(true);
const filterValue = 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;
if (initRef.current) {
const { title, id } = current;
const breadCrumbs: TBreadCrumb[] = [{ label: title, id, isRoom: true }];
if (!isRoomsOnly) breadCrumbs.unshift({ ...DEFAULT_BREAD_CRUMB });
onSetBaseFolderPath?.(breadCrumbs);
setBreadCrumbs(breadCrumbs);
setIsBreadCrumbsLoading(false);
}
const itemList: TSelectorItem[] = convertRoomsToItems(folders);
setHasNextPage(count === PAGE_COUNT);
if (firstLoadRef.current || startIndex === 0) {
setTotal(total);
setItems(itemList);
} else {
setItems((prevState) => {
if (prevState) return [...prevState, ...itemList];
return [...itemList];
});
}
requestRunning.current = false;
setIsNextPageLoading(false);
setIsRoot(false);
setIsInit(false);
},
[
setIsNextPageLoading,
searchValue,
setHasNextPage,
setIsRoot,
setIsInit,
isRoomsOnly,
onSetBaseFolderPath,
setBreadCrumbs,
setIsBreadCrumbsLoading,
setTotal,
setItems,
],
);
return { getRoomList };
};
export default useRoomsHelper;

View File

@ -1,16 +1,13 @@
import React from "react";
import { FolderType } from "@docspace/shared/enums";
// @ts-ignore
import { getFoldersTree } from "@docspace/shared/api/files";
import { FolderType } from "../../../enums";
import { getFoldersTree } from "../../../api/files";
import { TFolder } from "../../../api/files/types";
import { getCatalogIconUrlByType } from "../../../utils/catalogIconHelper";
import { TSelectorItem } from "../../../components/selector";
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";
import { getCatalogIconUrlByType } from "@docspace/shared/utils/catalogIconHelper";
import { UseRootHelperProps } from "../FilesSelector.types";
import { DEFAULT_BREAD_CRUMB } from "../FilesSelector.constants";
const useRootHelper = ({
setBreadCrumbs,
@ -21,25 +18,34 @@ const useRootHelper = ({
setTotal,
setHasNextPage,
isUserOnly,
}: useRootHelperProps) => {
setIsInit,
}: UseRootHelperProps) => {
const [isRoot, setIsRoot] = React.useState<boolean>(false);
const requestRunning = React.useRef(false);
const getRootData = React.useCallback(async () => {
setBreadCrumbs([defaultBreadCrumb]);
setIsRoot(true);
setIsBreadCrumbsLoading(false);
const newItems: Item[] = [];
if (requestRunning.current) return;
let currentTree: Item[] | null = null;
requestRunning.current = true;
setBreadCrumbs([DEFAULT_BREAD_CRUMB]);
setIsRoot(true);
setIsNextPageLoading(true);
setIsBreadCrumbsLoading(false);
const newItems: TSelectorItem[] = [];
let currentTree: TFolder[] | null = null;
if (treeFolders && treeFolders?.length > 0) {
currentTree = treeFolders;
} else {
currentTree = await getFoldersTree();
const folders = await getFoldersTree();
currentTree = folders;
}
currentTree?.forEach((folder) => {
const avatar = getCatalogIconUrlByType(folder.rootFolderType);
let avatar = "";
if (folder.rootFolderType)
avatar = getCatalogIconUrlByType(folder.rootFolderType);
if (
(!isUserOnly && folder.rootFolderType === FolderType.Rooms) ||
@ -47,7 +53,6 @@ const useRootHelper = ({
) {
newItems.push({
label: folder.title,
title: folder.title,
id: folder.id,
parentId: folder.parentId,
rootFolderType: folder.rootFolderType,
@ -64,7 +69,19 @@ const useRootHelper = ({
setTotal(newItems.length);
setHasNextPage(false);
setIsNextPageLoading(false);
}, [treeFolders]);
setIsInit(false);
requestRunning.current = false;
}, [
isUserOnly,
setBreadCrumbs,
setHasNextPage,
setIsBreadCrumbsLoading,
setIsInit,
setIsNextPageLoading,
setItems,
setTotal,
treeFolders,
]);
return { isRoot, setIsRoot, getRootData };
};

View File

@ -0,0 +1,257 @@
import React from "react";
import { TSelectorItem } from "../../../components/selector";
import { TFile, TFolder } from "../../../api/files/types";
import { TRoom } from "../../../api/rooms/types";
import { TOptSocket } from "../../../utils/socket";
import {
convertFilesToItems,
convertFoldersToItems,
convertRoomsToItems,
} from "../FilesSelector.utils";
import { UseSocketHelperProps } from "../FilesSelector.types";
const useSocketHelper = ({
socketHelper,
socketSubscribers,
disabledItems,
filterParam,
setItems,
setBreadCrumbs,
setTotal,
getIcon,
}: UseSocketHelperProps) => {
const subscribedId = React.useRef<null | number>(null);
const unsubscribe = React.useCallback(
(id: number, clear = true) => {
if (clear) {
subscribedId.current = null;
}
if (id && !socketSubscribers.has(`DIR-${id}`)) {
socketHelper.emit({
command: "unsubscribe",
data: {
roomParts: `DIR-${id}`,
individual: true,
},
});
}
},
[socketHelper, socketSubscribers],
);
const subscribe = React.useCallback(
(id: number) => {
const roomParts = `DIR-${id}`;
if (socketSubscribers.has(roomParts)) return (subscribedId.current = id);
if (subscribedId.current && !socketSubscribers.has(roomParts)) {
unsubscribe(subscribedId.current, false);
}
socketHelper.emit({
command: "subscribe",
data: {
roomParts: `DIR-${id}`,
individual: true,
},
});
subscribedId.current = id;
},
[socketHelper, socketSubscribers, unsubscribe],
);
const addItem = React.useCallback(
(opt: TOptSocket) => {
if (!opt?.data) return;
const data: TFile | TFolder | TRoom = JSON.parse(opt.data);
if (
"folderId" in data && data.folderId
? data.folderId !== subscribedId.current
: "parentId" in data && data.parentId !== subscribedId.current
)
return;
let item: TSelectorItem = {} as TSelectorItem;
if (opt?.type === "file" && "folderId" in data) {
item = convertFilesToItems([data], getIcon, filterParam)[0];
} else if (opt?.type === "folder" && "roomType" in data) {
item =
data.roomType && "tags" in data
? convertRoomsToItems([data])[0]
: convertFoldersToItems([data], disabledItems, filterParam)[0];
}
setItems((value) => {
if (!item || !value) return value;
if (opt.type === "folder") {
setTotal((v) => v + 1);
return [item, ...value];
}
if (opt.type === "file") {
let idx = 0;
for (let i = 0; i < value.length - 1; i += 1) {
if (!value[i].isFolder) break;
idx = i + 1;
}
const newValue = [...value];
newValue.splice(idx, 0, item);
setTotal((v) => v + 1);
return newValue;
}
return value;
});
},
[disabledItems, filterParam, getIcon, setItems, setTotal],
);
const updateItem = React.useCallback(
(opt: TOptSocket) => {
if (!opt?.data) return;
const data: TFile | TFolder | TRoom = JSON.parse(opt.data);
if (
(("folderId" in data &&
data.folderId &&
data.folderId !== subscribedId.current) ||
("parentId" in data &&
data.parentId &&
data.parentId !== subscribedId.current)) &&
data.id !== subscribedId.current
)
return;
let item: TSelectorItem = {} as TSelectorItem;
if (opt?.type === "file" && "folderId" in data) {
item = convertFilesToItems([data], getIcon, filterParam)[0];
} else if (opt?.type === "folder" && "roomType" in data) {
item =
data.roomType && "tags" in data
? convertRoomsToItems([data])[0]
: convertFoldersToItems([data], disabledItems, filterParam)[0];
}
if (item?.id === subscribedId.current) {
return setBreadCrumbs((value) => {
if (!value) return value;
const newValue = [...value];
if (newValue[newValue.length - 1].id === item?.id) {
newValue[newValue.length - 1].label = item.label;
}
return newValue;
});
}
setItems((value) => {
if (!item || !value) return value;
if (opt.type === "folder") {
const idx = value.findIndex((v) => v.id === item?.id && v.isFolder);
if (idx > -1) {
const newValue = [...value];
newValue.splice(idx, 1, item);
return newValue;
}
setBreadCrumbs((breadCrumbsValue) => {
return breadCrumbsValue;
});
}
if (opt.type === "file") {
const idx = value.findIndex((v) => v.id === item?.id && !v.isFolder);
if (idx > -1) {
const newValue = [...value];
newValue.splice(idx, 1, item);
return [...newValue];
}
}
return value;
});
},
[disabledItems, filterParam, getIcon, setBreadCrumbs, setItems],
);
const deleteItem = React.useCallback(
(opt: TOptSocket) => {
setItems((value) => {
if (!value) return value;
if (opt.type === "folder") {
const newValue = value.filter(
(v) => v?.id !== opt?.id || !v.isFolder,
);
if (newValue.length !== value.length) {
setTotal((v) => v - 1);
}
return newValue;
}
if (opt.type === "file") {
const newValue = value.filter((v) => v?.id !== opt?.id || v.isFolder);
if (newValue.length !== value.length) {
setTotal((v) => v - 1);
}
return newValue;
}
return value;
});
},
[setItems, setTotal],
);
React.useEffect(() => {
socketHelper.on("s:modify-folder", (opt?: TOptSocket) => {
switch (opt?.cmd) {
case "create":
addItem(opt);
break;
case "update":
updateItem(opt);
break;
case "delete":
deleteItem(opt);
break;
default:
}
});
}, [addItem, updateItem, deleteItem, socketHelper]);
return { subscribe, unsubscribe };
};
export default useSocketHelper;

View File

@ -0,0 +1,544 @@
import React from "react";
import { useTheme } from "styled-components";
import { useTranslation } from "react-i18next";
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 { TRoomSecurity } from "../../api/rooms/types";
import { TFileSecurity, TFolder, TFolderSecurity } from "../../api/files/types";
import { FolderType, RoomsType, DeviceType } from "../../enums";
import { Selector, TSelectorItem } from "../../components/selector";
import { Aside } from "../../components/aside";
import { Backdrop } from "../../components/backdrop";
import { Portal } from "../../components/portal";
import {
RowLoader,
SearchLoader,
BreadCrumbsLoader,
} from "../../skeletons/selector";
import {
TBreadCrumb,
TSelectorBreadCrumbs,
TSelectorCancelButton,
TSelectorCheckbox,
TSelectorHeader,
TSelectorInput,
TSelectorSearch,
TSelectorSubmitButton,
} from "../../components/selector/Selector.types";
import useFilesHelper from "./hooks/useFilesHelper";
import useLoadersHelper from "./hooks/useLoadersHelper";
import useRoomsHelper from "./hooks/useRoomsHelper";
import useRootHelper from "./hooks/useRootHelper";
import useSocketHelper from "./hooks/useSocketHelper";
import { FilesSelectorProps } from "./FilesSelector.types";
import useFilesSettings from "./hooks/useFilesSettings";
const FilesSelector = ({
socketHelper,
socketSubscribers,
disabledItems,
filterParam,
getIcon: getIconProp,
treeFolders,
onSetBaseFolderPath,
isUserOnly,
isRoomsOnly,
isThirdParty,
rootThirdPartyId,
roomsFolderId,
currentFolderId,
parentId,
rootFolderType,
onSubmit,
onCancel,
getIsDisabled,
withHeader,
headerLabel,
submitButtonLabel,
withCancelButton,
withFooterInput,
withFooterCheckbox,
footerInputHeader,
currentFooterInputValue,
footerCheckboxLabel,
descriptionText,
submitButtonId,
cancelButtonId,
embedded,
isPanelVisible,
currentDeviceType,
getFilesArchiveError,
setIsDataReady,
}: FilesSelectorProps) => {
const theme = useTheme();
const { t } = useTranslation(["Common"]);
const [breadCrumbs, setBreadCrumbs] = React.useState<TBreadCrumb[]>([]);
const [items, setItems] = React.useState<TSelectorItem[]>([]);
const [selectedItemType, setSelectedItemType] = React.useState<
"rooms" | "files" | undefined
>(undefined);
const [selectedItemId, setSelectedItemId] = React.useState<
number | string | undefined
>(undefined);
const [selectedItemSecurity, setSelectedItemSecurity] = React.useState<
TRoomSecurity | TFolderSecurity | TFileSecurity | undefined
>(undefined);
const [selectedTreeNode, setSelectedTreeNode] = React.useState({} as TFolder);
const [selectedFileInfo, setSelectedFileInfo] = React.useState<{
id: number | string;
title: string;
path?: string[];
fileExst?: string;
inPublic?: boolean;
} | null>(null);
const [total, setTotal] = React.useState<number>(0);
const [hasNextPage, setHasNextPage] = React.useState<boolean>(false);
const [isSelectedParentFolder, setIsSelectedParentFolder] =
React.useState<boolean>(false);
const [searchValue, setSearchValue] = React.useState<string>("");
const [isInit, setIsInit] = React.useState(true);
const { getIcon } = useFilesSettings(getIconProp);
const { subscribe, unsubscribe } = useSocketHelper({
socketHelper,
socketSubscribers,
disabledItems,
filterParam,
getIcon,
setItems,
setBreadCrumbs,
setTotal,
});
const {
setIsBreadCrumbsLoading,
isNextPageLoading,
setIsNextPageLoading,
isFirstLoad,
setIsFirstLoad,
showBreadCrumbsLoader,
showLoader,
} = useLoadersHelper({ items });
const { isRoot, setIsRoot, getRootData } = useRootHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setTotal,
setItems,
treeFolders,
setHasNextPage,
setIsNextPageLoading,
isUserOnly,
setIsInit,
});
const { getRoomList } = useRoomsHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
isFirstLoad,
setIsRoot,
searchValue,
isRoomsOnly,
onSetBaseFolderPath,
isInit,
setIsInit,
});
const { getFileList } = useFilesHelper({
setIsBreadCrumbsLoading,
setBreadCrumbs,
setIsNextPageLoading,
setHasNextPage,
setTotal,
setItems,
selectedItemId,
isFirstLoad,
setIsRoot,
searchValue,
disabledItems,
setSelectedItemSecurity,
isThirdParty,
setSelectedTreeNode,
filterParam,
getRootData,
onSetBaseFolderPath,
isRoomsOnly,
rootThirdPartyId,
getRoomList,
getIcon,
setIsSelectedParentFolder,
roomsFolderId,
getFilesArchiveError,
isInit,
setIsInit,
});
const onSelectAction = React.useCallback(
(item: TSelectorItem) => {
if (item.isFolder) {
setIsFirstLoad(true);
setItems([]);
setBreadCrumbs((value) => [
...value,
{
label: item.label,
id: item.id,
isRoom:
item.parentId === 0 && item.rootFolderType === FolderType.Rooms,
roomType: item.roomType,
shared: item.shared,
} as TBreadCrumb,
]);
setSelectedItemId(item.id);
setSearchValue("");
if (item.parentId === 0 && item.rootFolderType === FolderType.Rooms) {
setSelectedItemType("rooms");
} else {
setSelectedItemType("files");
}
} else if (item.id && item.label) {
const inPublic =
breadCrumbs.findIndex((f) => f.roomType === RoomsType.PublicRoom) >
-1;
setSelectedFileInfo({
id: item.id,
title: item.label,
fileExst: item.fileExst,
inPublic,
});
}
},
[breadCrumbs, setIsFirstLoad],
);
React.useEffect(() => {
if (!selectedItemId) return;
if (selectedItemId && isRoot) return unsubscribe(+selectedItemId);
subscribe(+selectedItemId);
}, [selectedItemId, isRoot, unsubscribe, subscribe]);
React.useEffect(() => {
setIsFirstLoad(true);
const needRoomList = isRoomsOnly && !currentFolderId;
if (needRoomList) {
setSelectedItemType("rooms");
return;
}
if (!currentFolderId) {
setSelectedItemType("rooms");
return;
}
setSelectedItemId(currentFolderId);
if (
needRoomList ||
(!isThirdParty &&
parentId === roomsFolderId &&
rootFolderType === FolderType.Rooms)
) {
setSelectedItemType("rooms");
return;
}
setSelectedItemType("files");
}, [
currentFolderId,
isRoomsOnly,
isThirdParty,
parentId,
roomsFolderId,
rootFolderType,
setIsFirstLoad,
]);
const onClickBreadCrumb = React.useCallback(
(item: TBreadCrumb) => {
if (!isFirstLoad) {
setSearchValue("");
setIsFirstLoad(true);
if (+item.id === 0) {
setSelectedItemSecurity(undefined);
setSelectedItemType(undefined);
getRootData();
} else {
setItems([]);
setBreadCrumbs((bc) => {
const idx = bc.findIndex(
(value) => value.id.toString() === item.id.toString(),
);
const maxLength = bc.length - 1;
let foundParentId = false;
let currentFolderIndex = -1;
const newBreadCrumbs = bc.map((i, index) => {
if (!foundParentId) {
currentFolderIndex = disabledItems.findIndex(
(id) => id === i?.id,
);
}
if (index !== maxLength && currentFolderIndex !== -1) {
foundParentId = true;
if (!isSelectedParentFolder) setIsSelectedParentFolder(true);
}
if (
index === maxLength &&
!foundParentId &&
isSelectedParentFolder
)
setIsSelectedParentFolder(false);
return { ...i };
});
newBreadCrumbs.splice(idx + 1, newBreadCrumbs.length - idx - 1);
return newBreadCrumbs;
});
setSelectedItemId(item.id);
if (item.isRoom) {
setSelectedItemType("rooms");
} else {
setSelectedItemType("files");
}
}
}
},
[
disabledItems,
getRootData,
isFirstLoad,
isSelectedParentFolder,
setIsFirstLoad,
],
);
const onSearchAction = React.useCallback(
(value: string, callback?: Function) => {
setIsFirstLoad(true);
setItems([]);
setSearchValue(value);
callback?.();
},
[setIsFirstLoad],
);
const onClearSearchAction = React.useCallback(
(callback?: Function) => {
setIsFirstLoad(true);
setItems([]);
setSearchValue("");
callback?.();
},
[setIsFirstLoad],
);
React.useEffect(() => {
if (setIsDataReady) setIsDataReady(!showLoader);
}, [setIsDataReady, showLoader]);
const onSubmitAction = React.useCallback(
async (
i: unknown,
accessRights: unknown,
fileName: string,
isChecked: boolean,
) => {
const isPublic =
breadCrumbs.findIndex((f) => f.roomType === RoomsType.PublicRoom) > -1;
const folderTitle = breadCrumbs[breadCrumbs.length - 1].label;
onSubmit(
selectedItemId,
folderTitle,
isPublic,
breadCrumbs,
fileName,
isChecked,
selectedTreeNode,
selectedFileInfo,
);
},
[breadCrumbs, selectedFileInfo, selectedItemId, selectedTreeNode, onSubmit],
);
React.useEffect(() => {
if (selectedItemType === "rooms") getRoomList(0);
if (selectedItemType === "files" && typeof selectedItemId !== "undefined")
getFileList(0);
}, [getFileList, getRoomList, selectedItemType, selectedItemId]);
const headerProps: TSelectorHeader = withHeader
? { withHeader, headerProps: { headerLabel } }
: {};
const withSearch =
!isRoot && items.length ? items.length > 0 : !isRoot && isFirstLoad;
const searchProps: TSelectorSearch = withSearch
? {
withSearch,
searchLoader: <SearchLoader />,
searchPlaceholder: t("Common:Search"),
searchValue,
isSearchLoading: showBreadCrumbsLoader,
onSearch: onSearchAction,
onClearSearch: onClearSearchAction,
}
: {};
const submitButtonProps: TSelectorSubmitButton = {
onSubmit: onSubmitAction,
submitButtonLabel,
submitButtonId,
disableSubmitButton: getIsDisabled(
isFirstLoad,
isSelectedParentFolder,
selectedItemId,
selectedItemType,
isRoot,
selectedItemSecurity,
selectedFileInfo,
),
};
const cancelButtonProps: TSelectorCancelButton = withCancelButton
? {
withCancelButton,
cancelButtonLabel: t("Common:CancelButton"),
cancelButtonId,
onCancel,
}
: {};
const footerInputProps: TSelectorInput = withFooterInput
? {
withFooterInput,
footerInputHeader,
currentFooterInputValue,
}
: {};
const footerCheckboxProps: TSelectorCheckbox = withFooterCheckbox
? {
withFooterCheckbox,
footerCheckboxLabel,
isChecked: false,
setIsChecked: () => {},
}
: {};
const breadCrumbsProps: TSelectorBreadCrumbs = {
breadCrumbs,
breadCrumbsLoader: <BreadCrumbsLoader />,
isBreadCrumbsLoading: showBreadCrumbsLoader,
withBreadCrumbs: true,
onSelectBreadCrumb: onClickBreadCrumb,
};
const SelectorBody = (
<Selector
{...headerProps}
{...searchProps}
{...submitButtonProps}
{...cancelButtonProps}
{...footerInputProps}
{...footerCheckboxProps}
{...breadCrumbsProps}
isMultiSelect={false}
items={items}
onSelect={onSelectAction}
emptyScreenImage={
theme?.isBase ? EmptyScreenAltSvgUrl : EmptyScreenAltSvgDarkUrl
}
emptyScreenHeader={t("SelectorEmptyScreenHeader")}
emptyScreenDescription=""
searchEmptyScreenImage={
theme?.isBase
? EmptyScreenFilterAltSvgUrl
: EmptyScreenFilterAltDarkSvgUrl
}
searchEmptyScreenHeader={t("Common:NotFoundTitle")}
searchEmptyScreenDescription={t("EmptyFilterDescriptionText")}
isLoading={showLoader}
rowLoader={
<RowLoader
isMultiSelect={false}
isUser={isRoot}
isContainer={showLoader}
/>
}
alwaysShowFooter
isNextPageLoading={isNextPageLoading}
hasNextPage={hasNextPage}
totalItems={total}
loadNextPage={
isRoot
? async () => {}
: selectedItemType === "rooms"
? getRoomList
: getFileList
}
descriptionText={descriptionText}
disableFirstFetch
/>
);
const selectorComponent = embedded ? (
SelectorBody
) : (
<>
<Backdrop
visible={isPanelVisible}
isAside
withBackground
zIndex={309}
onClick={onCancel}
/>
<Aside
visible={isPanelVisible}
withoutBodyScroll
zIndex={310}
onClose={onCancel}
>
{SelectorBody}
</Aside>
</>
);
return currentDeviceType === DeviceType.mobile && !embedded ? (
<Portal visible={isPanelVisible} element={<div>{selectorComponent}</div>} />
) : (
selectorComponent
);
};
export default FilesSelector;

View File

@ -0,0 +1,67 @@
import React from "react";
import styled from "styled-components";
import { RectangleSkeleton, RectangleSkeletonProps } from "../rectangle";
const StyledContainer = styled.div`
width: 100%;
display: flex;
align-items: center;
padding: 0 16px;
margin-bottom: 16px;
gap: 8px;
`;
interface BreadCrumbsProps extends RectangleSkeletonProps {
id?: string;
className?: string;
style?: React.CSSProperties;
}
const BreadCrumbsLoader = ({
id,
className,
style,
...rest
}: BreadCrumbsProps) => {
return (
<StyledContainer>
<RectangleSkeleton
width="80px"
height="22px"
style={{ ...style }}
{...rest}
/>
<RectangleSkeleton
width="12px"
height="12px"
style={{ ...style }}
{...rest}
/>
<RectangleSkeleton
width="80px"
height="22px"
style={{ ...style }}
{...rest}
/>
<RectangleSkeleton
width="12px"
height="12px"
style={{ ...style }}
{...rest}
/>
<RectangleSkeleton
width="80px"
height="22px"
style={{ ...style }}
{...rest}
/>
</StyledContainer>
);
};
export default BreadCrumbsLoader;

View File

@ -1,4 +1,5 @@
import RowLoader from "./Row";
import SearchLoader from "./Search";
import BreadCrumbsLoader from "./BreadCrumbs";
export { SearchLoader, RowLoader };
export { SearchLoader, RowLoader, BreadCrumbsLoader };

View File

@ -13,7 +13,7 @@ export type TDirectionY = "bottom" | "top" | "both";
export type TViewAs = "tile" | "table" | "row" | "settings" | "profile";
export type TTranslation = (key: string) => string;
export type TTranslation = (key: string, prop?: unknown) => string;
export type NonFunctionPropertyNames<T, ExcludeTypes> = {
[K in keyof T]: T[K] extends ExcludeTypes ? never : K;

View File

@ -104,3 +104,13 @@ export const getModalType = () => {
export const isValidDate = (date: Date) => {
return moment(date).tz(window.timezone).year() !== 9999;
};
export const presentInArray = (
array: string[],
search: string,
caseInsensitive = false,
) => {
const pattern = caseInsensitive ? search.toLowerCase() : search;
const result = array?.findIndex((item) => item === pattern);
return result !== -1;
};

View File

@ -11,7 +11,16 @@ let client: Socket<DefaultEventsMap, DefaultEventsMap> | null = null;
let callbacks: { eventName: string; callback: (value: TOnCallback) => void }[] =
[];
const subscribers = new Set();
const subscribers = new Set<string>();
export type TOptSocket = {
featureId: string;
value: number;
data?: string;
type?: "folder" | "file";
id?: string;
cmd?: "create" | "update" | "delete";
};
export type TEmit = {
command: string;
@ -126,7 +135,7 @@ class SocketIOHelper {
}
};
on = (eventName: string, callback: (value: TOnCallback) => void) => {
on = (eventName: string, callback: (value: TOptSocket) => void) => {
if (!this.isEnabled) {
callbacks.push({ eventName, callback });
return;