diff --git a/packages/client/src/pages/Sdk/index.js b/packages/client/src/pages/Sdk/index.js index e81b42c7ad..07fb3a124b 100644 --- a/packages/client/src/pages/Sdk/index.js +++ b/packages/client/src/pages/Sdk/index.js @@ -4,7 +4,7 @@ import { useParams } from "react-router-dom"; import { Button } from "@docspace/shared/components/button"; import { ColorTheme, ThemeId } from "@docspace/shared/components/color-theme"; import AppLoader from "@docspace/common/components/AppLoader"; -import RoomSelector from "../../components/RoomSelector"; +import RoomSelector from "@docspace/shared/selectors/Room"; import FilesSelector from "../../components/FilesSelector"; import { frameCallEvent, diff --git a/packages/common/components/Loaders/SelectorSearchLoader/index.js b/packages/common/components/Loaders/SelectorSearchLoader/index.js deleted file mode 100644 index 0f7a745504..0000000000 --- a/packages/common/components/Loaders/SelectorSearchLoader/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { RectangleSkeleton } from "@docspace/shared/skeletons"; - -const SelectorSearchLoader = ({ - id, - className, - style, - - ...rest -}) => { - return ( - - ); -}; - -export default SelectorSearchLoader; diff --git a/packages/common/components/Loaders/index.js b/packages/common/components/Loaders/index.js index ead69ed05c..dcff41b516 100644 --- a/packages/common/components/Loaders/index.js +++ b/packages/common/components/Loaders/index.js @@ -1,4 +1,3 @@ - import Header from "./HeaderLoader"; import SectionHeader from "./SectionHeaderLoader"; import ArticleHeader from "./ArticleHeaderLoader"; @@ -38,8 +37,6 @@ import PaymentsLoader from "./PaymentsLoader"; import SelectorBreadCrumbsLoader from "./SelectorBreadCrumbsLoader"; import PaymentsStandaloneLoader from "./PaymentsStandaloneLoader"; -import SelectorSearchLoader from "./SelectorSearchLoader"; -import SelectorRowLoader from "./SelectorRowLoader"; import InfoPanelViewLoader from "./InfoPanelBodyLoader/InfoPanelViewLoader"; import InfoPanelHeaderLoader from "./InfoPanelHeaderLoader"; @@ -52,7 +49,6 @@ import SettingsDSConnect from "./SettingsLoader/SettingsDSConnectLoader"; import EmptyContainerLoader from "./EmptyContainerLoader/EmptyContainerLoader"; export default { - Header, SectionHeader, ArticleHeader, @@ -91,8 +87,6 @@ export default { SelectorBreadCrumbsLoader, PaymentsStandaloneLoader, - SelectorSearchLoader, - SelectorRowLoader, InfoPanelHeaderLoader, InfoPanelViewLoader, diff --git a/packages/shared/api/files/types.ts b/packages/shared/api/files/types.ts index 31dc78ab97..54efdf0b3c 100644 --- a/packages/shared/api/files/types.ts +++ b/packages/shared/api/files/types.ts @@ -1,4 +1,4 @@ -import { TUser } from "types"; +import { TCreatedBy, TPathParts, TUser } from "../../types"; import { EmployeeActivationStatus, EmployeeStatus, @@ -9,14 +9,6 @@ import { ShareAccessRights, } from "../../enums"; -export type TCreatedBy = { - avatarSmall: string; - displayName: string; - hasAvatar: boolean; - id: string; - profileUrl: string; -}; - export type TFileViewAccessibility = { CanConvert: boolean; CoAuhtoring: boolean; @@ -148,12 +140,6 @@ export type TFolder = { export type TGetFolderPath = TFolder[]; -export type TPathParts = { - id: number; - title: string; - roomType?: RoomsType; -}; - export type TGetFolder = { files: TFile[]; folders: TFolder[]; diff --git a/packages/shared/api/rooms/index.js b/packages/shared/api/rooms/index.ts similarity index 93% rename from packages/shared/api/rooms/index.js rename to packages/shared/api/rooms/index.ts index cd5c3bab7b..e0ac66b382 100644 --- a/packages/shared/api/rooms/index.js +++ b/packages/shared/api/rooms/index.ts @@ -1,3 +1,5 @@ +import { AxiosRequestConfig } from "axios"; + import { FolderType } from "../../enums"; import { request } from "../client"; import { @@ -6,8 +8,9 @@ import { toUrlParams, } from "../../utils/common"; import RoomsFilter from "./filter"; +import { TGetRooms } from "./types"; -export function getRooms(filter, signal) { +export async function getRooms(filter: RoomsFilter, signal?: AbortSignal) { let params; if (filter) { @@ -16,24 +19,24 @@ export function getRooms(filter, signal) { params = `?${filter.toApiUrlParams()}`; } - const options = { + const options: AxiosRequestConfig = { method: "get", url: `/files/rooms${params}`, signal, }; - return request(options).then((res) => { - res.files = decodeDisplayName(res.files); - res.folders = decodeDisplayName(res.folders); + const res = (await request(options)) as TGetRooms; - if (res.current.rootFolderType === FolderType.Archive) { - res.folders.forEach((room) => { - room.isArchive = true; - }); - } + res.files = decodeDisplayName(res.files); + res.folders = decodeDisplayName(res.folders); - return res; - }); + if (res.current.rootFolderType === FolderType.Archive) { + res.folders.forEach((room) => { + room.isArchive = true; + }); + } + + return res; } export function getRoomInfo(id) { diff --git a/packages/shared/api/rooms/types.ts b/packages/shared/api/rooms/types.ts new file mode 100644 index 0000000000..9b6c19304a --- /dev/null +++ b/packages/shared/api/rooms/types.ts @@ -0,0 +1,66 @@ +import { TFile, TFolder } from "../files/types"; +import { FolderType, RoomsType, ShareAccessRights } from "../../enums"; +import { TCreatedBy, TPathParts } from "../../types"; + +export type TLogo = { + original: string; + large: string; + medium: string; + small: string; + color?: string; +}; + +export type TRoomSecurity = { + Read: boolean; + Create: boolean; + Delete: boolean; + EditRoom: boolean; + Rename: boolean; + CopyTo: boolean; + Copy: boolean; + MoveTo: boolean; + Move: boolean; + Pin: boolean; + Mute: boolean; + EditAccess: boolean; + Duplicate: boolean; + Download: boolean; + CopySharedLink: boolean; +}; + +export type TRoom = { + parentId: number; + filesCount: number; + foldersCount: number; + new: number; + mute: boolean; + tags: string[]; + logo: TLogo; + pinned: boolean; + roomType: RoomsType; + private: boolean; + inRoom: boolean; + id: number; + rootFolderId: number; + canShare: boolean; + title: string; + access: ShareAccessRights; + shared: boolean; + created: Date; + createdBy: TCreatedBy; + updated: Date; + rootFolderType: FolderType; + updatedBy: TCreatedBy; + isArchive?: boolean; +}; + +export type TGetRooms = { + files: TFile[]; + folders: TRoom[]; + current: TFolder; + pathParts: TPathParts[]; + startIndex: number; + count: number; + total: number; + new: number; +}; diff --git a/packages/shared/selectors/Room/RoomSelector.types.ts b/packages/shared/selectors/Room/RoomSelector.types.ts new file mode 100644 index 0000000000..c8e2cee7bd --- /dev/null +++ b/packages/shared/selectors/Room/RoomSelector.types.ts @@ -0,0 +1,16 @@ +import { SelectorProps } from "../../components/selector"; +import { TLogo } from "../../api/rooms/types"; +import { RoomsType } from "../../enums"; + +export interface RoomSelectorProps extends SelectorProps { + excludeItems: number[]; +} + +export type TItem = { + id: number; + label: string; + icon: string; + color: string | undefined; + logo: TLogo; + roomType: RoomsType; +}; diff --git a/packages/shared/selectors/Room/RoomSelector.utils.ts b/packages/shared/selectors/Room/RoomSelector.utils.ts new file mode 100644 index 0000000000..4498ce181a --- /dev/null +++ b/packages/shared/selectors/Room/RoomSelector.utils.ts @@ -0,0 +1,14 @@ +import { TRoom } from "../../api/rooms/types"; + +export const convertToItems = (folders: TRoom[]) => { + const items = folders.map((folder) => { + const { id, title, roomType, logo } = folder; + + const icon = logo.medium; + const color = logo.color; + + return { id, label: title, icon, color, logo, roomType }; + }); + + return items; +}; diff --git a/packages/client/src/components/RoomSelector/index.js b/packages/shared/selectors/Room/index.tsx similarity index 56% rename from packages/client/src/components/RoomSelector/index.js rename to packages/shared/selectors/Room/index.tsx index ac4679bd04..d78131ca1d 100644 --- a/packages/client/src/components/RoomSelector/index.js +++ b/packages/shared/selectors/Room/index.tsx @@ -1,61 +1,26 @@ -import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url"; -import React from "react"; -import { withTranslation } from "react-i18next"; +import React from "react"; +import { useTranslation } from "react-i18next"; -import api from "@docspace/shared/api"; -import RoomsFilter from "@docspace/shared/api/rooms/filter"; -import { RoomsType } from "@docspace/shared/enums"; -import { iconSize32 } from "@docspace/shared/utils/image-helpers"; +import EmptyScreenCorporateSvgUrl from "PUBLIC_DIR/images/empty_screen_corporate.svg?url"; -import Loaders from "@docspace/common/components/Loaders"; +import { Selector } from "../../components/selector"; +import { RowLoader, SearchLoader } from "../../skeletons/selector"; +import api from "../../api"; +import RoomsFilter from "../../api/rooms/filter"; -import { Selector } from "@docspace/shared/components/selector"; +import { TTranslation } from "../../types"; -const pageCount = 100; +import { RoomSelectorProps, TItem } from "./RoomSelector.types"; +import { convertToItems } from "./RoomSelector.utils"; -const getRoomLogo = (roomType) => { - let path = ""; - switch (roomType) { - case RoomsType.CustomRoom: - path = "custom.svg"; - break; - case RoomsType.FillingFormsRoom: - path = "filling.form.svg"; - break; - case RoomsType.EditingRoom: - path = "editing.svg"; - break; - case RoomsType.ReadOnlyRoom: - path = "view.only.svg"; - break; - case RoomsType.ReviewRoom: - path = "review.svg"; - break; - } - - return iconSize32.get(path); -}; - -const convertToItems = (folders) => { - const items = folders.map((folder) => { - const { id, title, roomType, logo } = folder; - - const icon = logo.medium ? logo.medium : getRoomLogo(roomType); - const color = logo.color; - - return { id, label: title, icon, color, logo, roomType }; - }); - - return items; -}; +const PAGE_COUNT = 100; const RoomSelector = ({ - t, id, className, style, - excludeItems, + excludeItems = [], headerLabel, onBackClick, @@ -91,7 +56,9 @@ const RoomSelector = ({ searchEmptyScreenImage, searchEmptyScreenHeader, searchEmptyScreenDescription, -}) => { +}: RoomSelectorProps) => { + const { t }: { t: TTranslation } = useTranslation(["RoomSelector", "Common"]); + const [isFirstLoad, setIsFirstLoad] = React.useState(true); const [searchValue, setSearchValue] = React.useState(""); const [hasNextPage, setHasNextPage] = React.useState(false); @@ -99,22 +66,22 @@ const RoomSelector = ({ const [total, setTotal] = React.useState(0); - const [items, setItems] = React.useState([]); + const [items, setItems] = React.useState([]); const onSearchAction = React.useCallback( - (value) => { - onSearch && onSearch(value); + (value: string) => { + onSearch?.(value); setSearchValue(() => { setIsFirstLoad(true); return value; }); }, - [onSearch] + [onSearch], ); const onClearSearchAction = React.useCallback(() => { - onClearSearch && onClearSearch(); + onClearSearch?.(); setSearchValue(() => { setIsFirstLoad(true); @@ -123,50 +90,51 @@ const RoomSelector = ({ }, [onClearSearch]); const onLoadNextPage = React.useCallback( - (startIndex) => { + async (startIndex: number) => { setIsNextPageLoading(true); - const page = startIndex / pageCount; + const page = startIndex / PAGE_COUNT; const filter = RoomsFilter.getDefault(); filter.page = page; - filter.pageCount = pageCount; + filter.pageCount = PAGE_COUNT; - filter.filterValue = searchValue ? searchValue : null; + filter.filterValue = searchValue || null; - api.rooms - .getRooms(filter) - .then(({ folders, total, count }) => { - const rooms = convertToItems(folders); + const { + folders, + total: totalCount, + count, + } = await api.rooms.getRooms(filter); - const itemList = rooms.filter((x) => !excludeItems.includes(x.id)); + const rooms = convertToItems(folders); - setHasNextPage(count === pageCount); + const itemList = rooms.filter((x) => !excludeItems.includes(x.id)); - if (isFirstLoad) { - setTotal(total); - setItems(itemList); - } else { - setItems((value) => [...value, ...itemList]); - } - }) - .finally(() => { - if (isFirstLoad) { - setTimeout(() => { - setIsFirstLoad(false); - }, 500); - } + setHasNextPage(count === PAGE_COUNT); - setIsNextPageLoading(false); - }); + if (isFirstLoad) { + setTotal(totalCount); + setItems(itemList); + } else { + setItems((value) => [...value, ...itemList]); + } + + if (isFirstLoad) { + setTimeout(() => { + setIsFirstLoad(false); + }, 500); + } + + setIsNextPageLoading(false); }, - [isFirstLoad, excludeItems, searchValue] + [isFirstLoad, excludeItems, searchValue], ); React.useEffect(() => { onLoadNextPage(0); - }, [searchValue]); + }, [onLoadNextPage, searchValue]); return ( } + searchLoader={} rowLoader={ - ` width: 100%; height: 48px; min-height: 48px; @@ -52,7 +52,18 @@ const Divider = styled.div` border-bottom: ${(props) => props.theme.selector.border}; `; -const SelectorRowLoader = ({ +interface RowLoaderProps extends RectangleSkeletonProps { + id?: string; + className?: string; + style?: React.CSSProperties; + isMultiSelect?: boolean; + isContainer?: boolean; + isUser?: boolean; + withAllSelect?: boolean; + count?: number; +} + +const RowLoader = ({ id, className, style, @@ -62,30 +73,21 @@ const SelectorRowLoader = ({ withAllSelect, count = 5, ...rest -}) => { - const getRowItem = (key) => { +}: RowLoaderProps) => { + const getRowItem = (key: number) => { return ( - - + + {isMultiSelect && ( - + )} ); @@ -93,7 +95,7 @@ const SelectorRowLoader = ({ const getRowItems = () => { const rows = []; - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { rows.push(getRowItem(i)); } @@ -111,8 +113,8 @@ const SelectorRowLoader = ({ {getRowItems()} ) : ( - getRowItem() + getRowItem(0) ); }; -export default SelectorRowLoader; +export default RowLoader; diff --git a/packages/shared/skeletons/selector/Search.tsx b/packages/shared/skeletons/selector/Search.tsx new file mode 100644 index 0000000000..f7231ae2cf --- /dev/null +++ b/packages/shared/skeletons/selector/Search.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { RectangleSkeleton, RectangleSkeletonProps } from "../rectangle"; + +interface SearchLoaderProps extends RectangleSkeletonProps { + id?: string; + className?: string; + style?: React.CSSProperties; +} + +const SearchLoader = ({ + id, + className, + style, + + ...rest +}: SearchLoaderProps) => { + return ( + + ); +}; + +export default SearchLoader; diff --git a/packages/shared/skeletons/selector/index.ts b/packages/shared/skeletons/selector/index.ts new file mode 100644 index 0000000000..6c69ab3fe5 --- /dev/null +++ b/packages/shared/skeletons/selector/index.ts @@ -0,0 +1,4 @@ +import RowLoader from "./Row"; +import SearchLoader from "./Search"; + +export { SearchLoader, RowLoader }; diff --git a/packages/shared/tsconfig.eslint.json b/packages/shared/tsconfig.eslint.json index aca7a0a144..276328ff40 100644 --- a/packages/shared/tsconfig.eslint.json +++ b/packages/shared/tsconfig.eslint.json @@ -14,6 +14,7 @@ "hooks", "api", "sw", + "selectors", // add all files in which you see // the "parserOptions.project" error ".eslintrc.cjs", diff --git a/packages/shared/types/index.ts b/packages/shared/types/index.ts index 7b3aaaa4db..80ad8a7466 100644 --- a/packages/shared/types/index.ts +++ b/packages/shared/types/index.ts @@ -1,3 +1,4 @@ +import { RoomsType } from "../enums"; import { TTheme } from "../themes"; export type TDirectionX = "left" | "right"; @@ -9,6 +10,20 @@ export type TTranslation = (key: string) => string; export type { TUser } from "./user"; +export type TPathParts = { + id: number; + title: string; + roomType?: RoomsType; +}; + +export type TCreatedBy = { + avatarSmall: string; + displayName: string; + hasAvatar: boolean; + id: string; + profileUrl: string; +}; + export type TI18n = { language: string; changeLanguage: (l: string) => string; diff --git a/packages/shared/utils/common.ts b/packages/shared/utils/common.ts index e905b2e28b..535e8d8027 100644 --- a/packages/shared/utils/common.ts +++ b/packages/shared/utils/common.ts @@ -28,6 +28,7 @@ import { FolderType, RoomsType, ThemeKeys } from "../enums"; import { LANGUAGE, RTL_LANGUAGES } from "../constants"; import { TUser, TI18n } from "../types"; import { TFolder, TFile, TGetFolder } from "../api/files/types"; +import { TRoom } from "../api/rooms/types"; import TopLoaderService from "../components/top-loading-indicator"; import { Encoder } from "./encoder"; @@ -594,7 +595,9 @@ export const getFolderClassNameByType = (folderType: FolderType) => { } }; -export const decodeDisplayName = (items: T[]) => { +export const decodeDisplayName = ( + items: T[], +) => { return items.map((item) => { if (!item) return item;