Shared:Selectors:Room: rewrite to typescript and move from client

This commit is contained in:
Timofey Boyko 2024-01-17 10:13:30 +03:00
parent 82ad593fcc
commit a9a8de4722
16 changed files with 238 additions and 161 deletions

View File

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

View File

@ -1,21 +0,0 @@
import React from "react";
import { RectangleSkeleton } from "@docspace/shared/skeletons";
const SelectorSearchLoader = ({
id,
className,
style,
...rest
}) => {
return (
<RectangleSkeleton
width={"calc(100% - 16px)"}
height={"32px"}
style={{ padding: "0 0 0 16px", marginBottom: "8px", ...style }}
{...rest}
/>
);
};
export default SelectorSearchLoader;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TItem[]>([]);
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 (
<Selector
@ -216,9 +184,9 @@ const RoomSelector = ({
isNextPageLoading={isNextPageLoading}
loadNextPage={onLoadNextPage}
isLoading={isFirstLoad}
searchLoader={<Loaders.SelectorSearchLoader />}
searchLoader={<SearchLoader />}
rowLoader={
<Loaders.SelectorRowLoader
<RowLoader
isMultiSelect={isMultiSelect}
isContainer={isFirstLoad}
isUser={false}
@ -228,6 +196,4 @@ const RoomSelector = ({
);
};
RoomSelector.defaultProps = { excludeItems: [] };
export default withTranslation(["RoomSelector", "Common"])(RoomSelector);
export default RoomSelector;

View File

@ -12,4 +12,5 @@ export interface RectangleSkeletonProps {
foregroundOpacity?: number;
speed?: number;
animate?: boolean;
style?: React.CSSProperties;
}

View File

@ -1,7 +1,7 @@
import React from "react";
import styled, { css } from "styled-components";
import { RectangleSkeleton } from "@docspace/shared/skeletons";
import { RectangleSkeleton, RectangleSkeletonProps } from "../rectangle";
const StyledContainer = styled.div`
width: 100%;
@ -13,7 +13,7 @@ const StyledContainer = styled.div`
flex-direction: column;
`;
const StyledItem = styled.div`
const StyledItem = styled.div<{ isUser?: boolean }>`
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 (
<StyledItem
id={id}
className={className}
style={style}
isMultiSelect={isMultiSelect}
isUser={isUser}
key={key}
{...rest}
>
<RectangleSkeleton
className={"avatar"}
width={"32px"}
height={"32px"}
/>
<RectangleSkeleton width={"212px"} height={"16px"} />
<RectangleSkeleton className="avatar" width="32px" height="32px" />
<RectangleSkeleton width="212px" height="16px" />
{isMultiSelect && (
<RectangleSkeleton
className={"checkbox"}
width={"16px"}
height={"16px"}
/>
<RectangleSkeleton className="checkbox" width="16px" height="16px" />
)}
</StyledItem>
);
@ -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()}
</StyledContainer>
) : (
getRowItem()
getRowItem(0)
);
};
export default SelectorRowLoader;
export default RowLoader;

View File

@ -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 (
<RectangleSkeleton
width="calc(100% - 16px)"
height="32px"
style={{ padding: "0 0 0 16px", marginBottom: "8px", ...style }}
{...rest}
/>
);
};
export default SearchLoader;

View File

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

View File

@ -14,6 +14,7 @@
"hooks",
"api",
"sw",
"selectors",
// add all files in which you see
// the "parserOptions.project" error
".eslintrc.cjs",

View File

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

View File

@ -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 = <T extends TFile | TFolder>(items: T[]) => {
export const decodeDisplayName = <T extends TFile | TFolder | TRoom>(
items: T[],
) => {
return items.map((item) => {
if (!item) return item;