Merge branch 'develop' into feature/oauth2-client
This commit is contained in:
commit
d78e24f4bf
@ -34,6 +34,7 @@ import FilesSelector from "@docspace/shared/selectors/Files";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { SettingsStore } from "@docspace/shared/store/SettingsStore";
|
||||
import {
|
||||
TFile,
|
||||
TFileSecurity,
|
||||
TFolder,
|
||||
TFolderSecurity,
|
||||
@ -41,7 +42,7 @@ import {
|
||||
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 { TRoom, TRoomSecurity } from "@docspace/shared/api/rooms/types";
|
||||
import { TTranslation } from "@docspace/shared/types";
|
||||
|
||||
import SelectedFolderStore from "SRC_DIR/store/SelectedFolderStore";
|
||||
@ -55,6 +56,8 @@ import InfoPanelStore from "SRC_DIR/store/InfoPanelStore";
|
||||
import { FilesSelectorProps } from "./FilesSelector.types";
|
||||
import { getAcceptButtonLabel, getHeaderLabel, getIsDisabled } from "./utils";
|
||||
|
||||
let disabledItems: (string | number)[] = [];
|
||||
|
||||
const FilesSelectorWrapper = ({
|
||||
isPanelVisible = false,
|
||||
// withoutImmediatelyClose = false,
|
||||
@ -87,7 +90,7 @@ const FilesSelectorWrapper = ({
|
||||
treeFolders,
|
||||
|
||||
selection,
|
||||
disabledItems,
|
||||
// disabledItems,
|
||||
setConflictDialogData,
|
||||
checkFileConflicts,
|
||||
itemOperationToFolder,
|
||||
@ -169,8 +172,16 @@ const FilesSelectorWrapper = ({
|
||||
onCloseAction();
|
||||
};
|
||||
|
||||
const getFilesArchiveError = (name: string) =>
|
||||
t("Common:ArchivedRoomAction", { name });
|
||||
const getFilesArchiveError = React.useCallback(
|
||||
(name: string) => t("Common:ArchivedRoomAction", { name }),
|
||||
[t],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
disabledItems = [];
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onAccept = async (
|
||||
selectedItemId: string | number | undefined,
|
||||
@ -375,6 +386,9 @@ const FilesSelectorWrapper = ({
|
||||
isMove || isCopy || isRestore ? "select-file-modal-cancel" : ""
|
||||
}
|
||||
getFilesArchiveError={getFilesArchiveError}
|
||||
withCreateFolder={
|
||||
(isMove || isCopy || isRestore || isRestoreAll) ?? false
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -471,10 +485,13 @@ export default inject(
|
||||
? selections
|
||||
: selections.filter((f) => f && !f?.isEditing);
|
||||
|
||||
const disabledItems: (string | number)[] = [];
|
||||
|
||||
selectionsWithoutEditing.forEach((item) => {
|
||||
if ((item?.isFolder || item?.parentId) && item?.id) {
|
||||
selectionsWithoutEditing.forEach((item: TFile | TFolder | TRoom) => {
|
||||
if (
|
||||
(("isFolder" in item && item?.isFolder) ||
|
||||
("parentId" in item && item?.parentId)) &&
|
||||
item?.id &&
|
||||
!disabledItems.includes(item.id)
|
||||
) {
|
||||
disabledItems.push(item.id);
|
||||
}
|
||||
});
|
||||
|
@ -70,10 +70,9 @@ const SessionsRowContent = ({
|
||||
)}
|
||||
<Text truncate>{convertTime(date)}</Text>
|
||||
{(country || city) && (
|
||||
<Text truncate>
|
||||
<Text fontSize="12px" fontWeight="600">
|
||||
{country}
|
||||
{country && city && ", "}
|
||||
{city}
|
||||
{country && city && ` ${city}`}
|
||||
</Text>
|
||||
)}
|
||||
<Text truncate containerWidth="160px">
|
||||
|
@ -255,7 +255,11 @@ class AccountsHotkeysStore {
|
||||
const scroll = document.getElementsByClassName(
|
||||
"section-scroll",
|
||||
) as HTMLCollectionOf<HTMLElement>;
|
||||
if (scroll && scroll[0]) scroll[0].focus();
|
||||
|
||||
if (scroll && scroll[0]) {
|
||||
const scrollElem = scroll[0]?.firstChild as HTMLElement;
|
||||
scrollElem?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.hotkeyCaret && selection.length) {
|
||||
|
@ -2051,6 +2051,7 @@ class ContextOptionsStore {
|
||||
],
|
||||
};
|
||||
|
||||
const showUploadFolder = !(isMobile || isTablet);
|
||||
const moreActions = {
|
||||
id: "personal_more-form",
|
||||
className: "main-button_drop-down",
|
||||
@ -2072,7 +2073,7 @@ class ContextOptionsStore {
|
||||
key: "personal_more-form__separator-2",
|
||||
},
|
||||
uploadFiles,
|
||||
uploadFolder,
|
||||
showUploadFolder ? uploadFolder : null,
|
||||
],
|
||||
};
|
||||
|
||||
@ -2321,6 +2322,7 @@ class ContextOptionsStore {
|
||||
]
|
||||
: [createTemplateForm, createTemplateNewFormFile, templateOformsGallery];
|
||||
|
||||
const showUploadFolder = !(isMobile || isTablet);
|
||||
const options = isRoomsFolder
|
||||
? [
|
||||
{
|
||||
@ -2338,7 +2340,7 @@ class ContextOptionsStore {
|
||||
createNewFolder,
|
||||
{ key: "separator", isSeparator: true },
|
||||
uploadFiles,
|
||||
uploadFolder,
|
||||
showUploadFolder ? uploadFolder : null,
|
||||
];
|
||||
|
||||
if (mainButtonItemsList && enablePlugins) {
|
||||
|
@ -135,7 +135,7 @@ class HotkeyStore {
|
||||
|
||||
if (!hotkeyCaret) {
|
||||
const scroll = document.getElementsByClassName("section-scroll");
|
||||
scroll && scroll[0] && scroll[0].focus();
|
||||
scroll && scroll[0] && scroll[0]?.firstChild.focus();
|
||||
}
|
||||
|
||||
if (!hotkeyCaret && selection.length) {
|
||||
|
@ -110,6 +110,7 @@ const SelectFileDialog = ({
|
||||
submitButtonId="select-file-modal-submit"
|
||||
cancelButtonId="select-file-modal-cancel"
|
||||
{...fileTypeDetection}
|
||||
withCreateFolder={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ const SelectFolderDialog = ({
|
||||
getFilesArchiveError={() => ""}
|
||||
parentId={0}
|
||||
getIsDisabled={getIsDisabled}
|
||||
withCreateFolder
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,16 @@ import PropTypes from "prop-types";
|
||||
import { ThemeProvider } from "../../components/theme-provider";
|
||||
|
||||
const ThemeWrapper = ({ theme, children }) => {
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={theme}
|
||||
currentColorScheme={{
|
||||
main: { accent: "#4781D1", buttons: "#5299E0" },
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
ThemeWrapper.propTypes = {
|
||||
|
@ -44,6 +44,16 @@ module.exports = {
|
||||
docs: {
|
||||
autodocs: true,
|
||||
},
|
||||
typescript: {
|
||||
check: false,
|
||||
checkOptions: {},
|
||||
reactDocgen: false,
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
propFilter: (prop) =>
|
||||
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function getAbsolutePath(value) {
|
||||
|
@ -337,7 +337,10 @@ export async function getTrashFolderList() {
|
||||
// return request(options);
|
||||
// }
|
||||
|
||||
export async function createFolder(parentFolderId: number, title: string) {
|
||||
export async function createFolder(
|
||||
parentFolderId: number | string,
|
||||
title: string,
|
||||
) {
|
||||
const data = { title };
|
||||
const options: AxiosRequestConfig = {
|
||||
method: "post",
|
||||
|
@ -40,7 +40,7 @@ export interface ArticleItemProps {
|
||||
/** Sets the catalog item to display text */
|
||||
showText?: boolean;
|
||||
/** Invokes a function upon clicking on a catalog item */
|
||||
onClick?: (id?: string) => void;
|
||||
onClick?: (e: React.MouseEvent, id?: string) => void;
|
||||
/** Invokes a function upon dragging and dropping a catalog item */
|
||||
onDrop?: (id?: string, text?: string) => void;
|
||||
/** Tells when the catalog item should display initial on icon, text should be hidden */
|
||||
|
@ -37,6 +37,7 @@ describe("<CampaignsBanner />", () => {
|
||||
render(
|
||||
<CampaignsBanner
|
||||
campaignBackground="" // TODO: add url on image
|
||||
campaignIcon=""
|
||||
campaignTranslate={translates}
|
||||
campaignConfig={config}
|
||||
onClose={() => null}
|
||||
|
@ -63,7 +63,7 @@ const DocspaceLogo = ({
|
||||
const logo = getLogoUrl(logoSize, !theme.isBase);
|
||||
|
||||
return (
|
||||
<StyledWrapper isMobile={isMobile} isResizable={isResizable} logo={!!logo}>
|
||||
<StyledWrapper isMobile={isMobile} isResizable={isResizable}>
|
||||
{logo && (
|
||||
<img
|
||||
src={logo}
|
||||
|
@ -25,13 +25,13 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
import { isIOS, isMobile } from "react-device-detect";
|
||||
|
||||
import { EmailSettings, parseAddress } from "../../utils";
|
||||
import { InputSize, InputType, TextInputProps } from "../text-input";
|
||||
|
||||
import StyledEmailInput from "./EmailInput.styled";
|
||||
import { EmailInputProps, TValidate } from "./EmailInput.types";
|
||||
import { isIOS, isMobile } from "react-device-detect";
|
||||
|
||||
const TextInputWrapper = ({
|
||||
onValidateInput,
|
||||
|
@ -25,7 +25,6 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import styled, { css } from "styled-components";
|
||||
import { isIOS, isMobile } from "react-device-detect";
|
||||
|
||||
import CrossIcon from "PUBLIC_DIR/images/cross.react.svg";
|
||||
|
||||
|
@ -25,7 +25,6 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
import { isTablet, isIOS } from "react-device-detect";
|
||||
|
||||
import { InputSize, TextInput } from "../text-input";
|
||||
import { IconButton } from "../icon-button";
|
||||
|
@ -28,5 +28,5 @@ export type DesktopDetailsProps = {
|
||||
title: string;
|
||||
onMaskClick: VoidFunction;
|
||||
className?: string;
|
||||
showCloseButton: boolean;
|
||||
showCloseButton?: boolean;
|
||||
};
|
||||
|
@ -24,8 +24,7 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React, { useEffect, useRef, useCallback } from "react";
|
||||
import { isMobileOnly, isIOS } from "react-device-detect";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
import { DeviceType } from "../../../enums";
|
||||
import { Portal } from "../../portal";
|
||||
|
@ -27,7 +27,7 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import { Base } from "../../themes";
|
||||
|
||||
const StyledButton = styled.div<{ isDisabled?: boolean }>`
|
||||
const StyledButton = styled.div<{ isDisabled?: boolean; isAction?: boolean }>`
|
||||
display: inline-block;
|
||||
background: ${(props) => props.theme.selectorAddButton.background};
|
||||
border: ${(props) => props.theme.selectorAddButton.border};
|
||||
@ -79,6 +79,56 @@ const StyledButton = styled.div<{ isDisabled?: boolean }>`
|
||||
}
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
${(props) =>
|
||||
props.isAction &&
|
||||
css`
|
||||
// convert into 0.1 opacity
|
||||
background-color: ${props.theme.currentColorScheme?.main.accent}1A;
|
||||
|
||||
svg {
|
||||
path {
|
||||
${!props.isDisabled &&
|
||||
css`
|
||||
fill: ${props.theme.currentColorScheme?.main.accent};
|
||||
`}
|
||||
}
|
||||
}
|
||||
|
||||
:hover {
|
||||
background-color: ${props.theme.currentColorScheme?.main.accent}1A;
|
||||
|
||||
svg {
|
||||
path {
|
||||
${!props.isDisabled &&
|
||||
css`
|
||||
fill: ${props.theme.currentColorScheme?.main.accent};
|
||||
opacity: 0.85;
|
||||
`}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:active {
|
||||
background-color: ${props.theme.currentColorScheme?.main.accent}1A;
|
||||
svg {
|
||||
path {
|
||||
${!props.isDisabled &&
|
||||
css`
|
||||
fill: ${props.theme.currentColorScheme?.main.accent};
|
||||
opacity: 1;
|
||||
filter: ${props.theme.isBase
|
||||
? "brightness(90%)"
|
||||
: "brightness(82%)"};
|
||||
`}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
opacity: 1;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
StyledButton.defaultProps = { theme: Base };
|
||||
|
@ -36,6 +36,7 @@ import { SelectorAddButtonProps } from "./SelectorAddButton.types";
|
||||
const SelectorAddButton = (props: SelectorAddButtonProps) => {
|
||||
const {
|
||||
isDisabled = false,
|
||||
isAction,
|
||||
title,
|
||||
className,
|
||||
id,
|
||||
@ -52,6 +53,7 @@ const SelectorAddButton = (props: SelectorAddButtonProps) => {
|
||||
<StyledButton
|
||||
{...props}
|
||||
isDisabled={isDisabled}
|
||||
isAction={isAction}
|
||||
title={title}
|
||||
onClick={onClickAction}
|
||||
className={className}
|
||||
|
@ -39,4 +39,6 @@ export interface SelectorAddButtonProps {
|
||||
style?: React.CSSProperties;
|
||||
/** Specifies the icon name */
|
||||
iconName?: string;
|
||||
/** Change colors to accent */
|
||||
isAction?: boolean;
|
||||
}
|
||||
|
@ -28,10 +28,12 @@ import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import CustomSvgUrl from "PUBLIC_DIR/images/icons/32/room/custom.svg?url";
|
||||
import ArchiveSvgUrl from "PUBLIC_DIR/images/room.archive.svg?url";
|
||||
import FolderSvgUrl from "PUBLIC_DIR/images/icons/32/folder.svg?url";
|
||||
import EmptyScreenFilter from "PUBLIC_DIR/images/empty_screen_filter.png";
|
||||
import { RoomsType } from "../../enums";
|
||||
|
||||
import { AvatarRole } from "../avatar";
|
||||
|
||||
import { Selector } from "./Selector";
|
||||
import { SelectorProps, TSelectorItem } from "./Selector.types";
|
||||
|
||||
@ -89,7 +91,26 @@ function makeName() {
|
||||
const getItems = (count: number) => {
|
||||
const items: TSelectorItem[] = [];
|
||||
|
||||
for (let i = 0; i < count / 2; i += 1) {
|
||||
items.push({
|
||||
key: "create_new",
|
||||
id: "create_new_item",
|
||||
label: "New folder",
|
||||
isCreateNewItem: true,
|
||||
onCreateClick: () => {},
|
||||
});
|
||||
|
||||
items.push({
|
||||
key: "input_item",
|
||||
id: "input_item",
|
||||
label: "",
|
||||
isInputItem: true,
|
||||
icon: FolderSvgUrl,
|
||||
defaultInputValue: "New folder",
|
||||
onAcceptInput: () => {},
|
||||
onCancelInput: () => {},
|
||||
});
|
||||
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const label = makeName();
|
||||
items.push({
|
||||
key: `${label} ${i}`,
|
||||
@ -100,21 +121,10 @@ const getItems = (count: number) => {
|
||||
isAdmin: false,
|
||||
isVisitor: false,
|
||||
isCollaborator: false,
|
||||
isRoomAdmin: false,
|
||||
avatar: "",
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < count / 2; i += 1) {
|
||||
const label = makeName();
|
||||
|
||||
items.push({
|
||||
key: `room_${i}`,
|
||||
id: `room_${i}`,
|
||||
label: `${label} ${i}`,
|
||||
icon: CustomSvgUrl,
|
||||
shared: false,
|
||||
isFolder: true,
|
||||
roomType: RoomsType.CustomRoom,
|
||||
role: AvatarRole.owner,
|
||||
hasAvatar: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -186,6 +186,7 @@ const StyledItem = styled.div<{
|
||||
isSelected: boolean | undefined;
|
||||
isDisabled?: boolean;
|
||||
isMultiSelect: boolean;
|
||||
noHover?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -221,6 +222,15 @@ const StyledItem = styled.div<{
|
||||
`}
|
||||
}
|
||||
|
||||
.clicked-label {
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-component {
|
||||
margin-inline-start: 8px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
svg {
|
||||
margin-inline-end: 0px;
|
||||
@ -245,12 +255,13 @@ const StyledItem = styled.div<{
|
||||
`
|
||||
: css`
|
||||
${props.isSelected && !props.isMultiSelect && selectedCss}
|
||||
@media (hover: hover) {
|
||||
${!props.noHover &&
|
||||
` @media (hover: hover) {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: ${props.theme.selector.item.hoverBackground};
|
||||
}
|
||||
}
|
||||
}`}
|
||||
`}
|
||||
`;
|
||||
|
||||
@ -266,6 +277,36 @@ const StyledEmptyScreen = styled.div<{ withSearch: boolean }>`
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.buttons {
|
||||
margin-top: 32px;
|
||||
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.empty-folder_container-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.empty-folder_link {
|
||||
color: ${(props) => props.theme.selector.emptyScreen.buttonColor};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.empty-folder_link {
|
||||
color: ${(props) =>
|
||||
props.theme.selector.emptyScreen.hoverButtonColor};
|
||||
}
|
||||
|
||||
svg path {
|
||||
fill: ${(props) => props.theme.selector.emptyScreen.hoverButtonColor};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
max-width: 72px;
|
||||
max-height: 72px;
|
||||
@ -325,7 +366,10 @@ const StyledBreadCrumbs = styled.div<{
|
||||
|
||||
StyledBreadCrumbs.defaultProps = { theme: Base };
|
||||
|
||||
const StyledItemText = styled(Text)<{ isCurrent: boolean; isLoading: boolean }>`
|
||||
const StyledItemText = styled(Text)<{
|
||||
isCurrent: boolean;
|
||||
isLoading?: boolean;
|
||||
}>`
|
||||
${(props) =>
|
||||
!props.isCurrent &&
|
||||
css`
|
||||
@ -440,6 +484,36 @@ const StyledInfo = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledInputWrapper = styled.div`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
margin-inline-start: 8px;
|
||||
|
||||
border: 1px solid ${(props) => props.theme.selector.item.inputButtonBorder};
|
||||
border-radius: 3px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
:hover {
|
||||
div {
|
||||
cursor: pointer;
|
||||
}
|
||||
cursor: pointer;
|
||||
|
||||
border-color: ${(props) =>
|
||||
props.theme.selector.item.inputButtonBorderHover};
|
||||
|
||||
path {
|
||||
fill: ${(props) => props.theme.selector.item.inputButtonBorderHover};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
StyledSelector.defaultProps = { theme: Base };
|
||||
StyledHeader.defaultProps = { theme: Base };
|
||||
StyledBody.defaultProps = { theme: Base };
|
||||
@ -449,6 +523,7 @@ StyledEmptyScreen.defaultProps = { theme: Base };
|
||||
StyledArrowRightSvg.defaultProps = { theme: Base };
|
||||
StyledComboBox.defaultProps = { theme: Base };
|
||||
StyledInfo.defaultProps = { theme: Base };
|
||||
StyledInputWrapper.defaultProps = { theme: Base };
|
||||
|
||||
export {
|
||||
StyledSelector,
|
||||
@ -468,4 +543,5 @@ export {
|
||||
StyledTabs,
|
||||
StyledInfo,
|
||||
StyledAccessSelector,
|
||||
StyledInputWrapper,
|
||||
};
|
||||
|
@ -158,6 +158,8 @@ const Selector = ({
|
||||
return null;
|
||||
});
|
||||
|
||||
const [inputItemVisible, setInputItemVisible] = React.useState(false);
|
||||
|
||||
const [requestRunning, setRequestRunning] = React.useState<boolean>(false);
|
||||
|
||||
const onSubmitAction = async (
|
||||
@ -386,6 +388,7 @@ const Selector = ({
|
||||
|
||||
React.useEffect(() => {
|
||||
const onKeyboardAction = (e: KeyboardEvent) => {
|
||||
if (inputItemVisible) return;
|
||||
if (e.key === ButtonKeys.esc) {
|
||||
onCancel?.();
|
||||
}
|
||||
@ -395,7 +398,7 @@ const Selector = ({
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyboardAction);
|
||||
};
|
||||
}, [onCancel]);
|
||||
}, [inputItemVisible, onCancel]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (items) {
|
||||
@ -596,6 +599,7 @@ const Selector = ({
|
||||
withFooterInput={withFooterInput}
|
||||
withFooterCheckbox={withFooterCheckbox}
|
||||
descriptionText={descriptionText}
|
||||
setInputItemVisible={setInputItemVisible}
|
||||
// bread crumbs
|
||||
{...breadCrumbsProps}
|
||||
// select all
|
||||
|
@ -26,10 +26,14 @@
|
||||
|
||||
import React from "react";
|
||||
import { RoomsType, ShareAccessRights } from "../../enums";
|
||||
import { AvatarRole } from "../avatar";
|
||||
import { MergeTypes } from "../../types";
|
||||
|
||||
import { TFileSecurity, TFolderSecurity } from "../../api/files/types";
|
||||
import { TRoomSecurity } from "../../api/rooms/types";
|
||||
|
||||
import { AvatarRole } from "../avatar";
|
||||
import { TSubmenuItem } from "../submenu";
|
||||
|
||||
import { SelectorAccessRightsMode } from "./Selector.enums";
|
||||
|
||||
// header
|
||||
@ -56,12 +60,12 @@ export type TSelectorHeader =
|
||||
| { withHeader?: undefined; headerProps?: undefined };
|
||||
|
||||
// bread crumbs
|
||||
|
||||
export type TBreadCrumb = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isRoom?: boolean;
|
||||
minWidth?: string;
|
||||
roomType?: RoomsType;
|
||||
onClick?: ({
|
||||
e,
|
||||
open,
|
||||
@ -71,49 +75,41 @@ export type TBreadCrumb = {
|
||||
open: boolean;
|
||||
item: TBreadCrumb;
|
||||
}) => void;
|
||||
roomType?: RoomsType;
|
||||
};
|
||||
|
||||
export interface BreadCrumbsProps {
|
||||
breadCrumbs: TBreadCrumb[];
|
||||
onSelectBreadCrumb: (item: TBreadCrumb) => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
export type TDisplayedItem = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isArrow: boolean;
|
||||
isList: boolean;
|
||||
isRoom?: boolean;
|
||||
listItems?: TBreadCrumb[];
|
||||
};
|
||||
|
||||
export type TSelectorBreadCrumbs =
|
||||
| {
|
||||
withBreadCrumbs: true;
|
||||
breadCrumbs: TBreadCrumb[];
|
||||
onSelectBreadCrumb: (item: TBreadCrumb) => void;
|
||||
breadCrumbsLoader: React.ReactNode;
|
||||
isBreadCrumbsLoading: boolean;
|
||||
breadCrumbs: TBreadCrumb[];
|
||||
breadCrumbsLoader: React.ReactNode;
|
||||
|
||||
onSelectBreadCrumb: (item: TBreadCrumb) => void;
|
||||
}
|
||||
| {
|
||||
withBreadCrumbs?: undefined;
|
||||
breadCrumbs?: undefined;
|
||||
onSelectBreadCrumb?: undefined;
|
||||
breadCrumbsLoader?: undefined;
|
||||
isBreadCrumbsLoading?: undefined;
|
||||
breadCrumbs?: undefined;
|
||||
breadCrumbsLoader?: undefined;
|
||||
|
||||
onSelectBreadCrumb?: undefined;
|
||||
};
|
||||
|
||||
// tabs
|
||||
|
||||
export type TWithTabs =
|
||||
| { withTabs: true; tabsData: TSubmenuItem[]; activeTabId: string }
|
||||
| { withTabs?: undefined; tabsData?: undefined; activeTabId?: undefined };
|
||||
|
||||
// select all
|
||||
|
||||
export interface SelectAllProps {
|
||||
label: string;
|
||||
icon: string;
|
||||
onSelectAll: () => void;
|
||||
isChecked: boolean;
|
||||
isIndeterminate: boolean;
|
||||
isLoading: boolean;
|
||||
rowLoader: React.ReactNode;
|
||||
}
|
||||
|
||||
export type TSelectorSelectAll = {
|
||||
isAllIndeterminate?: boolean;
|
||||
isAllChecked?: boolean;
|
||||
@ -175,6 +171,8 @@ export interface EmptyScreenProps {
|
||||
searchHeader: string;
|
||||
searchDescription: string;
|
||||
withSearch: boolean;
|
||||
|
||||
items: TSelectorItem[];
|
||||
}
|
||||
|
||||
type TSelectorEmptyScreen = {
|
||||
@ -358,6 +356,8 @@ export type BodyProps = TSelectorBreadCrumbs &
|
||||
|
||||
isMultiSelect: boolean;
|
||||
|
||||
setInputItemVisible: (value: boolean) => void;
|
||||
|
||||
items: TSelectorItem[];
|
||||
renderCustomItem?: (
|
||||
label: string,
|
||||
@ -389,146 +389,148 @@ export type FooterProps = TSelectorFooterSubmitButton &
|
||||
requestRunning?: boolean;
|
||||
};
|
||||
|
||||
type TSelectorItemLogo =
|
||||
| {
|
||||
avatar: string;
|
||||
color?: undefined;
|
||||
hasAvatar?: boolean;
|
||||
icon?: undefined;
|
||||
iconOriginal?: string;
|
||||
role?: AvatarRole;
|
||||
}
|
||||
| {
|
||||
avatar?: undefined;
|
||||
color: string;
|
||||
hasAvatar?: undefined;
|
||||
icon?: undefined;
|
||||
iconOriginal?: string;
|
||||
role?: undefined;
|
||||
}
|
||||
| {
|
||||
avatar?: undefined;
|
||||
color?: undefined;
|
||||
hasAvatar?: undefined;
|
||||
icon: string;
|
||||
iconOriginal: string;
|
||||
role?: undefined;
|
||||
};
|
||||
type TSelectorItemEmpty = {
|
||||
avatar?: undefined;
|
||||
color?: undefined;
|
||||
hasAvatar?: undefined;
|
||||
icon?: undefined;
|
||||
iconOriginal?: undefined;
|
||||
role?: undefined;
|
||||
email?: undefined;
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
isCollaborator?: undefined;
|
||||
isRoomAdmin?: undefined;
|
||||
access?: undefined;
|
||||
fileExst?: undefined;
|
||||
shared?: undefined;
|
||||
parentId?: undefined;
|
||||
rootFolderType?: undefined;
|
||||
security?: undefined;
|
||||
isFolder?: undefined;
|
||||
filesCount?: undefined;
|
||||
foldersCount?: undefined;
|
||||
roomType?: undefined;
|
||||
isGroup?: undefined;
|
||||
name?: undefined;
|
||||
isCreateNewItem?: undefined;
|
||||
onCreateClick?: undefined;
|
||||
onBackClick?: undefined;
|
||||
isInputItem?: undefined;
|
||||
defaultInputValue?: undefined;
|
||||
onAcceptInput?: undefined;
|
||||
onCancelInput?: undefined;
|
||||
};
|
||||
|
||||
export type TSelectorItemUser = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
email: string;
|
||||
isOwner: boolean;
|
||||
isAdmin: boolean;
|
||||
isVisitor: boolean;
|
||||
isCollaborator: boolean;
|
||||
isRoomAdmin: boolean;
|
||||
avatar: string;
|
||||
hasAvatar: boolean;
|
||||
role: AvatarRole;
|
||||
|
||||
access?: ShareAccessRights | string | number;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemFile = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
fileExst: string;
|
||||
parentId: string | number;
|
||||
rootFolderType: string | number;
|
||||
security: TFileSecurity;
|
||||
icon: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemFolder = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
isFolder: boolean;
|
||||
parentId: string | number;
|
||||
rootFolderType: string | number;
|
||||
filesCount: number;
|
||||
foldersCount: number;
|
||||
security: TFolderSecurity;
|
||||
icon?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemRoom = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
isFolder: boolean;
|
||||
roomType: RoomsType;
|
||||
shared: boolean;
|
||||
parentId: string | number;
|
||||
rootFolderType: string | number;
|
||||
filesCount: number;
|
||||
foldersCount: number;
|
||||
security: TRoomSecurity;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
iconOriginal?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemGroup = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
isGroup: boolean;
|
||||
name: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemNew = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
isCreateNewItem: boolean;
|
||||
onCreateClick: VoidFunction;
|
||||
onBackClick: VoidFunction;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TSelectorItemInput = MergeTypes<
|
||||
TSelectorItemEmpty,
|
||||
{
|
||||
isInputItem: boolean;
|
||||
defaultInputValue: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
|
||||
onAcceptInput: (value: string) => void;
|
||||
onCancelInput: VoidFunction;
|
||||
}
|
||||
>;
|
||||
|
||||
type TSelectorItemType =
|
||||
| {
|
||||
email: string;
|
||||
fileExst?: undefined;
|
||||
roomType?: undefined;
|
||||
shared?: undefined;
|
||||
isOwner: boolean;
|
||||
isAdmin: boolean;
|
||||
isVisitor: boolean;
|
||||
isCollaborator: boolean;
|
||||
isRoomAdmin: boolean;
|
||||
access?: ShareAccessRights | string | number;
|
||||
isFolder?: undefined;
|
||||
parentId?: undefined;
|
||||
rootFolderType?: undefined;
|
||||
filesCount?: undefined;
|
||||
foldersCount?: undefined;
|
||||
security?: undefined;
|
||||
isGroup?: undefined;
|
||||
name?: undefined;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst: string;
|
||||
roomType?: undefined;
|
||||
shared?: boolean;
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
isCollaborator?: undefined;
|
||||
isRoomAdmin?: undefined;
|
||||
access?: undefined;
|
||||
isFolder?: undefined;
|
||||
parentId?: string | number;
|
||||
rootFolderType?: string | number;
|
||||
filesCount?: undefined;
|
||||
foldersCount?: undefined;
|
||||
security?: TFileSecurity;
|
||||
isGroup?: undefined;
|
||||
name?: undefined;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst?: undefined;
|
||||
roomType: RoomsType;
|
||||
shared?: boolean;
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
isCollaborator?: undefined;
|
||||
isRoomAdmin?: undefined;
|
||||
access?: undefined;
|
||||
isFolder: boolean;
|
||||
parentId?: string | number;
|
||||
rootFolderType?: string | number;
|
||||
filesCount?: number;
|
||||
foldersCount?: number;
|
||||
security?: TRoomSecurity;
|
||||
isGroup?: undefined;
|
||||
name?: undefined;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst?: undefined;
|
||||
roomType?: undefined;
|
||||
shared?: boolean;
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
isCollaborator?: undefined;
|
||||
isRoomAdmin?: undefined;
|
||||
access?: undefined;
|
||||
isFolder: boolean;
|
||||
parentId?: string | number;
|
||||
rootFolderType?: string | number;
|
||||
filesCount?: number;
|
||||
foldersCount?: number;
|
||||
security?: TFolderSecurity;
|
||||
isGroup?: undefined;
|
||||
name?: undefined;
|
||||
}
|
||||
| {
|
||||
email?: undefined;
|
||||
fileExst?: undefined;
|
||||
roomType?: undefined;
|
||||
shared?: boolean;
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
isCollaborator?: undefined;
|
||||
isRoomAdmin?: undefined;
|
||||
access?: undefined;
|
||||
isFolder?: undefined;
|
||||
parentId?: string | number;
|
||||
rootFolderType?: string | number;
|
||||
filesCount?: number;
|
||||
foldersCount?: number;
|
||||
security?: TFolderSecurity;
|
||||
isGroup: true;
|
||||
name: string;
|
||||
};
|
||||
| TSelectorItemUser
|
||||
| TSelectorItemFile
|
||||
| TSelectorItemFolder
|
||||
| TSelectorItemRoom
|
||||
| TSelectorItemGroup
|
||||
| TSelectorItemNew
|
||||
| TSelectorItemInput;
|
||||
|
||||
export type TSelectorItem = TSelectorItemLogo &
|
||||
TSelectorItemType & {
|
||||
key?: string;
|
||||
id?: string | number;
|
||||
label: string;
|
||||
displayName?: string;
|
||||
export type TSelectorItem = TSelectorItemType & {
|
||||
label: string;
|
||||
|
||||
isSelected?: boolean;
|
||||
|
||||
isDisabled?: boolean;
|
||||
disabledText?: string;
|
||||
};
|
||||
key?: string;
|
||||
id?: string | number;
|
||||
displayName?: string;
|
||||
isSelected?: boolean;
|
||||
isDisabled?: boolean;
|
||||
disabledText?: string;
|
||||
};
|
||||
|
||||
export type Data = {
|
||||
items: TSelectorItem[];
|
||||
@ -542,6 +544,7 @@ export type Data = {
|
||||
email?: string,
|
||||
isGroup?: boolean,
|
||||
) => React.ReactNode | null;
|
||||
setInputItemVisible: (value: boolean) => void;
|
||||
};
|
||||
|
||||
export interface ItemProps {
|
||||
@ -549,12 +552,3 @@ export interface ItemProps {
|
||||
style: React.CSSProperties;
|
||||
data: Data;
|
||||
}
|
||||
|
||||
export type TDisplayedItem = {
|
||||
id: string | number;
|
||||
label: string;
|
||||
isArrow: boolean;
|
||||
isList: boolean;
|
||||
isRoom?: boolean;
|
||||
listItems?: TBreadCrumb[];
|
||||
};
|
||||
|
@ -102,13 +102,23 @@ const Body = ({
|
||||
|
||||
withInfo,
|
||||
infoText,
|
||||
setInputItemVisible,
|
||||
}: BodyProps) => {
|
||||
const [bodyHeight, setBodyHeight] = React.useState(0);
|
||||
|
||||
const bodyRef = React.useRef<HTMLDivElement>(null);
|
||||
const listOptionsRef = React.useRef<null | InfiniteLoader>(null);
|
||||
|
||||
const itemsCount = hasNextPage ? items.length + 1 : items.length;
|
||||
const isEmptyInput =
|
||||
items.length === 2 && items[1].isInputItem && items[0].isCreateNewItem;
|
||||
|
||||
const itemsCount = hasNextPage
|
||||
? items.length + 1
|
||||
: items.length === 1 && items[0].isCreateNewItem
|
||||
? 0
|
||||
: isEmptyInput
|
||||
? 1
|
||||
: items.length;
|
||||
|
||||
const resetCache = React.useCallback(() => {
|
||||
if (listOptionsRef && listOptionsRef.current) {
|
||||
@ -185,9 +195,11 @@ const Body = ({
|
||||
breadCrumbsLoader
|
||||
) : (
|
||||
<BreadCrumbs
|
||||
withBreadCrumbs
|
||||
isBreadCrumbsLoading={isLoading}
|
||||
breadCrumbs={breadCrumbs}
|
||||
breadCrumbsLoader={breadCrumbsLoader}
|
||||
onSelectBreadCrumb={onSelectBreadCrumb}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
@ -227,23 +239,27 @@ const Body = ({
|
||||
searchImage={searchEmptyScreenImage}
|
||||
searchHeader={searchEmptyScreenHeader}
|
||||
searchDescription={searchEmptyScreenDescription}
|
||||
items={items}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!!descriptionText && (
|
||||
<Text className="body-description-text">{descriptionText}</Text>
|
||||
)}
|
||||
{isMultiSelect && withSelectAll && !isSearch && (
|
||||
<SelectAll
|
||||
label={selectAllLabel}
|
||||
icon={selectAllIcon}
|
||||
isChecked={isAllChecked || false}
|
||||
isIndeterminate={isAllIndeterminate || false}
|
||||
onSelectAll={onSelectAll}
|
||||
isLoading={isLoading}
|
||||
rowLoader={rowLoader}
|
||||
/>
|
||||
)}
|
||||
{isMultiSelect && withSelectAll && !isSearch ? (
|
||||
isLoading ? (
|
||||
rowLoader
|
||||
) : (
|
||||
<SelectAll
|
||||
withSelectAll
|
||||
selectAllIcon={selectAllIcon}
|
||||
selectAllLabel={selectAllLabel}
|
||||
isAllChecked={isAllChecked}
|
||||
isAllIndeterminate={isAllIndeterminate}
|
||||
onSelectAll={onSelectAll}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{bodyHeight && (
|
||||
<InfiniteLoader
|
||||
@ -259,12 +275,13 @@ const Body = ({
|
||||
width="100%"
|
||||
itemCount={itemsCount}
|
||||
itemData={{
|
||||
items,
|
||||
items: isEmptyInput ? [items[1]] : items,
|
||||
onSelect,
|
||||
isMultiSelect: isMultiSelect || false,
|
||||
rowLoader,
|
||||
isItemLoaded,
|
||||
renderCustomItem,
|
||||
setInputItemVisible,
|
||||
}}
|
||||
itemSize={48}
|
||||
onItemsRendered={onItemsRendered}
|
||||
|
@ -34,8 +34,8 @@ import { ContextMenuModel } from "../../context-menu";
|
||||
|
||||
import {
|
||||
TBreadCrumb,
|
||||
BreadCrumbsProps,
|
||||
TDisplayedItem,
|
||||
TSelectorBreadCrumbs,
|
||||
} from "../Selector.types";
|
||||
import {
|
||||
StyledBreadCrumbs,
|
||||
@ -45,20 +45,20 @@ import {
|
||||
|
||||
const BreadCrumbs = ({
|
||||
breadCrumbs,
|
||||
isBreadCrumbsLoading,
|
||||
onSelectBreadCrumb,
|
||||
isLoading,
|
||||
}: BreadCrumbsProps) => {
|
||||
}: TSelectorBreadCrumbs) => {
|
||||
const [displayedItems, setDisplayedItems] = React.useState<TDisplayedItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const onClickItem = React.useCallback(
|
||||
({ item }: { item: TBreadCrumb }) => {
|
||||
if (isLoading) return;
|
||||
if (isBreadCrumbsLoading) return;
|
||||
|
||||
onSelectBreadCrumb(item);
|
||||
onSelectBreadCrumb?.(item);
|
||||
},
|
||||
[isLoading, onSelectBreadCrumb],
|
||||
[isBreadCrumbsLoading, onSelectBreadCrumb],
|
||||
);
|
||||
|
||||
const calculateDisplayedItems = React.useCallback(
|
||||
@ -214,11 +214,12 @@ const BreadCrumbs = ({
|
||||
noSelect
|
||||
truncate
|
||||
isCurrent={index === displayedItems.length - 1}
|
||||
isLoading={isLoading || false}
|
||||
isLoading={isBreadCrumbsLoading}
|
||||
onClick={() => {
|
||||
if (index === displayedItems.length - 1 || isLoading) return;
|
||||
if (index === displayedItems.length - 1 || isBreadCrumbsLoading)
|
||||
return;
|
||||
|
||||
onSelectBreadCrumb({
|
||||
onSelectBreadCrumb?.({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
isRoom: item.isRoom,
|
||||
|
@ -25,13 +25,27 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import PlusSvgUrl from "PUBLIC_DIR/images/plus.svg?url";
|
||||
import UpSvgUrl from "PUBLIC_DIR/images/up.svg?url";
|
||||
|
||||
import { Heading } from "../../heading";
|
||||
import { Text } from "../../text";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { Link, LinkType } from "../../link";
|
||||
|
||||
import { StyledEmptyScreen } from "../Selector.styled";
|
||||
import { EmptyScreenProps } from "../Selector.types";
|
||||
|
||||
const linkStyles = {
|
||||
isHovered: true,
|
||||
type: LinkType.action,
|
||||
fontWeight: "600",
|
||||
className: "empty-folder_link",
|
||||
display: "flex",
|
||||
};
|
||||
|
||||
const EmptyScreen = ({
|
||||
image,
|
||||
header,
|
||||
@ -40,11 +54,16 @@ const EmptyScreen = ({
|
||||
searchHeader,
|
||||
searchDescription,
|
||||
withSearch,
|
||||
items,
|
||||
}: EmptyScreenProps) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const currentImage = withSearch ? searchImage : image;
|
||||
const currentHeader = withSearch ? searchHeader : header;
|
||||
const currentDescription = withSearch ? searchDescription : description;
|
||||
|
||||
const createItem = items.length > 0 ? items[0] : null;
|
||||
|
||||
return (
|
||||
<StyledEmptyScreen withSearch={withSearch}>
|
||||
<img className="empty-image" src={currentImage} alt="empty-screen" />
|
||||
@ -56,6 +75,34 @@ const EmptyScreen = ({
|
||||
<Text className="empty-description" noSelect>
|
||||
{currentDescription}
|
||||
</Text>
|
||||
{createItem && (
|
||||
<div className="buttons">
|
||||
<div className="empty-folder_container-links">
|
||||
<IconButton
|
||||
className="empty-folder_container-icon"
|
||||
size={12}
|
||||
onClick={createItem.onCreateClick}
|
||||
iconName={PlusSvgUrl}
|
||||
isFill
|
||||
/>
|
||||
<Link {...linkStyles} onClick={createItem.onCreateClick}>
|
||||
{items[0].label}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="empty-folder_container-links">
|
||||
<IconButton
|
||||
className="empty-folder_container-icon"
|
||||
size={12}
|
||||
onClick={createItem.onBackClick}
|
||||
iconName={UpSvgUrl}
|
||||
isFill
|
||||
/>
|
||||
<Link {...linkStyles} onClick={createItem.onBackClick}>
|
||||
{t("Common:Back")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</StyledEmptyScreen>
|
||||
);
|
||||
};
|
||||
|
148
packages/shared/components/selector/sub-components/InputItem.tsx
Normal file
148
packages/shared/components/selector/sub-components/InputItem.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
|
||||
import AcceptIconSvgUrl from "PUBLIC_DIR/images/selector.input.accept.svg?url";
|
||||
import CancelIconSvgUrl from "PUBLIC_DIR/images/selector.input.cancel.svg?url";
|
||||
|
||||
import { InputSize, InputType, TextInput } from "../../text-input";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { RoomIcon } from "../../room-icon";
|
||||
|
||||
import { StyledInputWrapper, StyledItem } from "../Selector.styled";
|
||||
|
||||
const InputItem = ({
|
||||
defaultInputValue,
|
||||
onAcceptInput,
|
||||
onCancelInput,
|
||||
style,
|
||||
|
||||
color,
|
||||
icon,
|
||||
|
||||
setInputItemVisible,
|
||||
}: {
|
||||
defaultInputValue: string;
|
||||
onAcceptInput: (value: string) => void;
|
||||
onCancelInput: VoidFunction;
|
||||
style: React.CSSProperties;
|
||||
|
||||
color?: string;
|
||||
icon?: string;
|
||||
|
||||
setInputItemVisible: (value: boolean) => void;
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(defaultInputValue);
|
||||
|
||||
const requestRunning = React.useRef<boolean>(false);
|
||||
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const onAcceptInputAction = React.useCallback(async () => {
|
||||
if (requestRunning.current) return;
|
||||
requestRunning.current = true;
|
||||
await onAcceptInput(value);
|
||||
|
||||
requestRunning.current = false;
|
||||
}, [onAcceptInput, value]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setInputItemVisible(true);
|
||||
|
||||
return () => {
|
||||
setInputItemVisible(false);
|
||||
};
|
||||
}, [setInputItemVisible]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") onAcceptInputAction();
|
||||
else if (e.key === "Escape") onCancelInput();
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [onAcceptInputAction, onCancelInput]);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newVal = e.target.value;
|
||||
|
||||
setValue(newVal);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<StyledItem
|
||||
key="input-item"
|
||||
isSelected={false}
|
||||
isMultiSelect={false}
|
||||
isDisabled={false}
|
||||
noHover
|
||||
style={style}
|
||||
>
|
||||
{color ? (
|
||||
<RoomIcon
|
||||
color={color}
|
||||
title={value}
|
||||
showDefault
|
||||
className="item-logo"
|
||||
/>
|
||||
) : icon ? (
|
||||
<RoomIcon
|
||||
title={value}
|
||||
className="item-logo"
|
||||
imgClassName="room-logo"
|
||||
imgSrc={icon}
|
||||
showDefault={false}
|
||||
/>
|
||||
) : null}
|
||||
<TextInput
|
||||
value={value}
|
||||
size={InputSize.base}
|
||||
type={InputType.text}
|
||||
onChange={onChange}
|
||||
forwardedRef={inputRef}
|
||||
/>
|
||||
<StyledInputWrapper onClick={onAcceptInputAction}>
|
||||
<IconButton iconName={AcceptIconSvgUrl} size={16} />
|
||||
</StyledInputWrapper>
|
||||
<StyledInputWrapper onClick={onCancelInput}>
|
||||
<IconButton iconName={CancelIconSvgUrl} size={16} />
|
||||
</StyledInputWrapper>
|
||||
</StyledItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputItem;
|
@ -36,6 +36,8 @@ import { RoomIcon } from "../../room-icon";
|
||||
import { StyledItem } from "../Selector.styled";
|
||||
import { ItemProps, Data, TSelectorItem } from "../Selector.types";
|
||||
import { RoomsType } from "../../../enums";
|
||||
import NewItem from "./NewItem";
|
||||
import InputItem from "./InputItem";
|
||||
|
||||
const compareFunction = (prevProps: ItemProps, nextProps: ItemProps) => {
|
||||
const prevData = prevProps.data;
|
||||
@ -64,6 +66,7 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
isItemLoaded,
|
||||
rowLoader,
|
||||
renderCustomItem,
|
||||
setInputItemVisible,
|
||||
}: Data = data;
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
@ -81,6 +84,13 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
|
||||
const {
|
||||
label,
|
||||
isCreateNewItem,
|
||||
onCreateClick,
|
||||
|
||||
isInputItem,
|
||||
defaultInputValue,
|
||||
onAcceptInput,
|
||||
onCancelInput,
|
||||
avatar,
|
||||
icon,
|
||||
role,
|
||||
@ -92,6 +102,32 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
disabledText,
|
||||
} = item;
|
||||
|
||||
if (isInputItem) {
|
||||
return (
|
||||
<InputItem
|
||||
defaultInputValue={defaultInputValue}
|
||||
onAcceptInput={onAcceptInput}
|
||||
onCancelInput={onCancelInput}
|
||||
style={style}
|
||||
color={color}
|
||||
icon={icon}
|
||||
setInputItemVisible={setInputItemVisible}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isCreateNewItem &&
|
||||
(items.length > 2 || (items.length === 2 && !items[1].isInputItem))
|
||||
) {
|
||||
return (
|
||||
<NewItem label={label} onCreateClick={onCreateClick} style={style} />
|
||||
);
|
||||
}
|
||||
if (isCreateNewItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showPlanetIcon =
|
||||
(item.roomType === RoomsType.PublicRoom ||
|
||||
item.roomType === RoomsType.CustomRoom) &&
|
||||
@ -101,7 +137,10 @@ const Item = React.memo(({ index, style, data }: ItemProps) => {
|
||||
|
||||
const currentRole = role || AvatarRole.user;
|
||||
|
||||
const typeLabel = getUserTypeLabel(role, t);
|
||||
const typeLabel = getUserTypeLabel(
|
||||
role as "owner" | "admin" | "user" | "collaborator" | "manager",
|
||||
t,
|
||||
);
|
||||
|
||||
const onChangeAction = () => {
|
||||
onSelect?.(item, false);
|
||||
|
@ -0,0 +1,68 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Text } from "../../text";
|
||||
import { SelectorAddButton } from "../../selector-add-button";
|
||||
|
||||
import { StyledItem } from "../Selector.styled";
|
||||
|
||||
const NewItem = ({
|
||||
label,
|
||||
style,
|
||||
onCreateClick,
|
||||
}: {
|
||||
label: string;
|
||||
style: React.CSSProperties;
|
||||
onCreateClick: VoidFunction;
|
||||
}) => {
|
||||
return (
|
||||
<StyledItem
|
||||
key="create-new-item"
|
||||
style={style}
|
||||
isSelected={false}
|
||||
isMultiSelect={false}
|
||||
noHover
|
||||
>
|
||||
<SelectorAddButton onClick={onCreateClick} isAction />
|
||||
<Text
|
||||
className="label label-disabled clicked-label"
|
||||
fontWeight={600}
|
||||
fontSize="14px"
|
||||
noSelect
|
||||
truncate
|
||||
dir="auto"
|
||||
onClick={onCreateClick}
|
||||
title={label}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</StyledItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewItem;
|
@ -31,18 +31,22 @@ import { Text } from "../../text";
|
||||
import { Checkbox } from "../../checkbox";
|
||||
|
||||
import { StyledSelectAll } from "../Selector.styled";
|
||||
import { SelectAllProps } from "../Selector.types";
|
||||
import { TSelectorSelectAll } from "../Selector.types";
|
||||
|
||||
const SelectAll = React.memo(
|
||||
({
|
||||
label,
|
||||
icon,
|
||||
withSelectAll,
|
||||
|
||||
selectAllLabel,
|
||||
selectAllIcon,
|
||||
|
||||
isAllChecked,
|
||||
isAllIndeterminate,
|
||||
|
||||
onSelectAll,
|
||||
isChecked,
|
||||
isIndeterminate,
|
||||
isLoading,
|
||||
rowLoader,
|
||||
}: SelectAllProps) => {
|
||||
}: TSelectorSelectAll) => {
|
||||
if (!withSelectAll) return;
|
||||
|
||||
const onClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target instanceof HTMLElement && e.target.closest(".checkbox"))
|
||||
return;
|
||||
@ -52,35 +56,28 @@ const SelectAll = React.memo(
|
||||
|
||||
return (
|
||||
<StyledSelectAll onClick={onClick}>
|
||||
{isLoading ? (
|
||||
rowLoader
|
||||
) : (
|
||||
<>
|
||||
<Avatar
|
||||
className="select-all_avatar"
|
||||
source={icon}
|
||||
role={AvatarRole.user}
|
||||
size={AvatarSize.min}
|
||||
/>
|
||||
<Avatar
|
||||
className="select-all_avatar"
|
||||
source={selectAllIcon}
|
||||
role={AvatarRole.user}
|
||||
size={AvatarSize.min}
|
||||
/>
|
||||
|
||||
<Text
|
||||
className="label"
|
||||
fontWeight={600}
|
||||
fontSize="14px"
|
||||
noSelect
|
||||
truncate
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Text
|
||||
className="label"
|
||||
fontWeight={600}
|
||||
fontSize="14px"
|
||||
noSelect
|
||||
truncate
|
||||
>
|
||||
{selectAllLabel}
|
||||
</Text>
|
||||
|
||||
<Checkbox
|
||||
className="checkbox"
|
||||
isChecked={isChecked}
|
||||
isIndeterminate={isIndeterminate}
|
||||
// onChange={onSelectAll}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Checkbox
|
||||
className="checkbox"
|
||||
isChecked={isAllChecked}
|
||||
isIndeterminate={isAllIndeterminate}
|
||||
/>
|
||||
</StyledSelectAll>
|
||||
);
|
||||
},
|
||||
|
@ -40,13 +40,13 @@ import { ComboBox, ComboBoxSize, TOption } from "../../combobox";
|
||||
import { IconButton } from "../../icon-button";
|
||||
import { toastr } from "../../toast";
|
||||
import { Loader, LoaderTypes } from "../../loader";
|
||||
import { Text } from "../../text";
|
||||
|
||||
import { StyledLinkRow, StyledSquare } from "../Share.styled";
|
||||
import { getShareOptions, getAccessOptions } from "../Share.helpers";
|
||||
import { LinkRowProps } from "../Share.types";
|
||||
|
||||
import ExpiredComboBox from "./ExpiredComboBox";
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
|
||||
const LinkRow = ({
|
||||
onAddClick,
|
||||
@ -149,7 +149,7 @@ const LinkRow = ({
|
||||
scaledOptions={false}
|
||||
showDisabledItems
|
||||
size={ComboBoxSize.content}
|
||||
fillIcon={true}
|
||||
fillIcon
|
||||
modernView
|
||||
type="onlyIcon"
|
||||
isDisabled={isExpiredLink || isLoaded}
|
||||
|
@ -58,5 +58,8 @@ export interface SocialButtonProps extends Partial<StyledSocialButtonProps> {
|
||||
/** Button icon */
|
||||
iconName?: string;
|
||||
|
||||
"data-url"?: string;
|
||||
"data-providername"?: string;
|
||||
|
||||
IconComponent?: JSX.ElementType;
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ const ClearScrollbar = ({
|
||||
hasError?: boolean;
|
||||
heightTextAreaProp?: string;
|
||||
ref?: React.Ref<HTMLDivElement>;
|
||||
isFullHeight?: boolean;
|
||||
fullHeight?: number;
|
||||
// @ts-expect-error error from custom scrollbar
|
||||
} & ScrollbarProps) => <Scrollbar {...props} />;
|
||||
|
||||
|
@ -62,6 +62,7 @@ export type UseSocketHelperProps = {
|
||||
disabledItems: (string | number)[];
|
||||
filterParam?: string;
|
||||
getIcon: (fileExst: string) => string;
|
||||
withCreateFolder: boolean;
|
||||
};
|
||||
|
||||
export type UseRoomsHelperProps = {
|
||||
@ -85,7 +86,7 @@ export type UseRoomsHelperProps = {
|
||||
|
||||
export type UseFilesHelpersProps = {
|
||||
roomsFolderId?: number;
|
||||
setBreadCrumbs: (items: TBreadCrumb[]) => void;
|
||||
setBreadCrumbs: React.Dispatch<React.SetStateAction<TBreadCrumb[]>>;
|
||||
setIsBreadCrumbsLoading: (value: boolean) => void;
|
||||
setIsSelectedParentFolder: (value: boolean) => void;
|
||||
setIsNextPageLoading: (value: boolean) => void;
|
||||
@ -97,7 +98,7 @@ export type UseFilesHelpersProps = {
|
||||
setIsRoot: (value: boolean) => void;
|
||||
setIsInit: (value: boolean) => void;
|
||||
searchValue?: string;
|
||||
disabledItems: string[] | number[];
|
||||
disabledItems: (string | number)[];
|
||||
setSelectedItemSecurity: (value: TFileSecurity | TFolderSecurity) => void;
|
||||
isThirdParty: boolean;
|
||||
setSelectedTreeNode: (treeNode: TFolder) => void;
|
||||
@ -119,6 +120,8 @@ export type UseFilesHelpersProps = {
|
||||
getFilesArchiveError: (name: string) => string;
|
||||
isInit: boolean;
|
||||
setIsFirstLoad: (value: boolean) => void;
|
||||
withCreateFolder: boolean;
|
||||
setSelectedItemId: (value: number | string) => void;
|
||||
};
|
||||
|
||||
export type TSelectedFileInfo = {
|
||||
@ -138,7 +141,7 @@ export type FilesSelectorProps = (
|
||||
) & {
|
||||
socketHelper: SocketIOHelper;
|
||||
socketSubscribers: Set<string>;
|
||||
disabledItems: string[] | number[];
|
||||
disabledItems: (string | number)[];
|
||||
filterParam?: string;
|
||||
withoutBackButton: boolean;
|
||||
withBreadCrumbs: boolean;
|
||||
@ -199,4 +202,6 @@ export type FilesSelectorProps = (
|
||||
isPanelVisible: boolean;
|
||||
currentDeviceType: DeviceType;
|
||||
getFilesArchiveError: (name: string) => string;
|
||||
|
||||
withCreateFolder: boolean;
|
||||
};
|
||||
|
@ -25,8 +25,16 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { getFolder, getFolderInfo, getSettingsFiles } from "../../../api/files";
|
||||
import FolderSvgUrl from "PUBLIC_DIR/images/icons/32/folder.svg?url";
|
||||
|
||||
import {
|
||||
createFolder,
|
||||
getFolder,
|
||||
getFolderInfo,
|
||||
getSettingsFiles,
|
||||
} from "../../../api/files";
|
||||
import FilesFilter from "../../../api/files/filter";
|
||||
import {
|
||||
ApplyFilterOption,
|
||||
@ -75,7 +83,11 @@ const useFilesHelper = ({
|
||||
isInit,
|
||||
setIsInit,
|
||||
setIsFirstLoad,
|
||||
withCreateFolder,
|
||||
setSelectedItemId,
|
||||
}: UseFilesHelpersProps) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const requestRunning = React.useRef(false);
|
||||
const initRef = React.useRef(isInit);
|
||||
const firstLoadRef = React.useRef(isFirstLoad);
|
||||
@ -93,6 +105,65 @@ const useFilesHelper = ({
|
||||
initRef.current = isInit;
|
||||
}, [isInit]);
|
||||
|
||||
const onCancelInput = React.useCallback(() => {
|
||||
if (!withCreateFolder) return;
|
||||
|
||||
setItems((value) => {
|
||||
if (!value[1]?.isInputItem && !value[0]?.isInputItem) return value;
|
||||
|
||||
let idx = 1;
|
||||
|
||||
if (value[0].isInputItem) idx = 0;
|
||||
|
||||
const newValue = [...value];
|
||||
|
||||
newValue.splice(idx, 1);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
}, [setItems, withCreateFolder]);
|
||||
|
||||
const onAcceptInput = React.useCallback(
|
||||
async (value: string) => {
|
||||
if (!withCreateFolder || !selectedItemId) return;
|
||||
|
||||
await createFolder(selectedItemId, value);
|
||||
|
||||
onCancelInput();
|
||||
|
||||
// setBreadCrumbs((val) => {
|
||||
// return [...val, { id: folder.id, label: folder.title }];
|
||||
// });
|
||||
|
||||
// setSelectedItemId(folder.id);
|
||||
},
|
||||
[withCreateFolder, selectedItemId, onCancelInput],
|
||||
);
|
||||
|
||||
const addInputItem = React.useCallback(() => {
|
||||
if (!withCreateFolder) return;
|
||||
|
||||
const inputItem: TSelectorItem = {
|
||||
label: "",
|
||||
id: "new-folder-input",
|
||||
isInputItem: true,
|
||||
onAcceptInput,
|
||||
onCancelInput,
|
||||
defaultInputValue: t("NewFolder"),
|
||||
icon: FolderSvgUrl,
|
||||
};
|
||||
|
||||
setItems((value) => {
|
||||
if (value[1]?.isInputItem || value[0]?.isInputItem) return value;
|
||||
|
||||
const newValue = [...value];
|
||||
|
||||
newValue.splice(1, 0, inputItem);
|
||||
|
||||
return newValue;
|
||||
});
|
||||
}, [onAcceptInput, onCancelInput, setItems, t, withCreateFolder]);
|
||||
|
||||
const getFileList = React.useCallback(
|
||||
async (startIndex: number) => {
|
||||
if (requestRunning.current) return;
|
||||
@ -310,7 +381,28 @@ const useFilesHelper = ({
|
||||
}
|
||||
|
||||
if (firstLoadRef.current || startIndex === 0) {
|
||||
setTotal(total);
|
||||
if (withCreateFolder) {
|
||||
setTotal(total + 1);
|
||||
itemList.unshift({
|
||||
isCreateNewItem: true,
|
||||
label: "New folder",
|
||||
id: "create-folder-item",
|
||||
key: "create-folder-item",
|
||||
onCreateClick: addInputItem,
|
||||
onBackClick: () => {
|
||||
setSelectedItemId(current.parentId);
|
||||
setBreadCrumbs((val) => {
|
||||
const newVal = [...val];
|
||||
|
||||
newVal.pop();
|
||||
|
||||
return newVal;
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setTotal(total);
|
||||
}
|
||||
setItems(itemList);
|
||||
} else {
|
||||
setItems((prevState) => {
|
||||
@ -361,8 +453,8 @@ const useFilesHelper = ({
|
||||
setIsNextPageLoading,
|
||||
searchValue,
|
||||
filterParam,
|
||||
isUserOnly,
|
||||
selectedItemId,
|
||||
isUserOnly,
|
||||
getRootData,
|
||||
setSelectedItemSecurity,
|
||||
getIcon,
|
||||
@ -380,8 +472,11 @@ const useFilesHelper = ({
|
||||
setIsBreadCrumbsLoading,
|
||||
roomsFolderId,
|
||||
setIsSelectedParentFolder,
|
||||
setTotal,
|
||||
withCreateFolder,
|
||||
setItems,
|
||||
setTotal,
|
||||
addInputItem,
|
||||
setSelectedItemId,
|
||||
rootThirdPartyId,
|
||||
],
|
||||
);
|
||||
|
@ -43,6 +43,7 @@ const useSocketHelper = ({
|
||||
socketSubscribers,
|
||||
disabledItems,
|
||||
filterParam,
|
||||
withCreateFolder,
|
||||
setItems,
|
||||
setBreadCrumbs,
|
||||
setTotal,
|
||||
@ -109,9 +110,9 @@ const useSocketHelper = ({
|
||||
|
||||
if (opt?.type === "file" && "folderId" in data) {
|
||||
item = convertFilesToItems([data], getIcon, filterParam)[0];
|
||||
} else if (opt?.type === "folder" && "roomType" in data) {
|
||||
} else if (opt?.type === "folder" && !("folderId" in data)) {
|
||||
item =
|
||||
data.roomType && "tags" in data
|
||||
"roomType" in data && data.roomType && "tags" in data
|
||||
? convertRoomsToItems([data])[0]
|
||||
: convertFoldersToItems([data], disabledItems, filterParam)[0];
|
||||
}
|
||||
@ -122,6 +123,18 @@ const useSocketHelper = ({
|
||||
if (opt.type === "folder") {
|
||||
setTotal((v) => v + 1);
|
||||
|
||||
if (withCreateFolder) {
|
||||
const newValue = [...value];
|
||||
|
||||
let idx = 1;
|
||||
|
||||
if (value[1]?.isInputItem) idx = 2;
|
||||
|
||||
newValue.splice(idx, 0, item);
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
return [item, ...value];
|
||||
}
|
||||
|
||||
@ -129,7 +142,12 @@ const useSocketHelper = ({
|
||||
let idx = 0;
|
||||
|
||||
for (let i = 0; i < value.length - 1; i += 1) {
|
||||
if (!value[i].isFolder) break;
|
||||
if (
|
||||
!value[i]?.isFolder &&
|
||||
!value[i]?.isCreateNewItem &&
|
||||
!value[i]?.isInputItem
|
||||
)
|
||||
break;
|
||||
|
||||
idx = i + 1;
|
||||
}
|
||||
@ -146,7 +164,7 @@ const useSocketHelper = ({
|
||||
return value;
|
||||
});
|
||||
},
|
||||
[disabledItems, filterParam, getIcon, setItems, setTotal],
|
||||
[disabledItems, filterParam, getIcon, setItems, setTotal, withCreateFolder],
|
||||
);
|
||||
|
||||
const updateItem = React.useCallback(
|
||||
|
@ -107,6 +107,7 @@ const FilesSelector = ({
|
||||
withBreadCrumbs: withBreadCrumbsProp,
|
||||
filesSettings,
|
||||
cancelButtonLabel,
|
||||
withCreateFolder,
|
||||
}: FilesSelectorProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation(["Common"]);
|
||||
@ -148,6 +149,7 @@ const FilesSelector = ({
|
||||
socketSubscribers,
|
||||
disabledItems,
|
||||
filterParam,
|
||||
withCreateFolder,
|
||||
getIcon,
|
||||
setItems,
|
||||
setBreadCrumbs,
|
||||
@ -195,6 +197,72 @@ const FilesSelector = ({
|
||||
setIsFirstLoad,
|
||||
});
|
||||
|
||||
const onClickBreadCrumb = React.useCallback(
|
||||
(item: TBreadCrumb) => {
|
||||
if (!isFirstLoad) {
|
||||
afterSearch.current = false;
|
||||
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 { getFileList } = useFilesHelper({
|
||||
setIsBreadCrumbsLoading,
|
||||
setBreadCrumbs,
|
||||
@ -224,6 +292,8 @@ const FilesSelector = ({
|
||||
getFilesArchiveError,
|
||||
isInit,
|
||||
setIsInit,
|
||||
withCreateFolder,
|
||||
setSelectedItemId,
|
||||
});
|
||||
|
||||
const onSelectAction = React.useCallback(
|
||||
@ -323,72 +393,6 @@ const FilesSelector = ({
|
||||
setIsFirstLoad,
|
||||
]);
|
||||
|
||||
const onClickBreadCrumb = React.useCallback(
|
||||
(item: TBreadCrumb) => {
|
||||
if (!isFirstLoad) {
|
||||
afterSearch.current = false;
|
||||
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);
|
||||
|
@ -121,7 +121,6 @@ const GroupsSelector = (props: GroupsSelectorProps) => {
|
||||
label: group.name,
|
||||
name: group.name,
|
||||
isGroup: true,
|
||||
avatar: "",
|
||||
}));
|
||||
|
||||
if (isFirstLoad.current) {
|
||||
|
@ -86,7 +86,7 @@ const toListItem = (
|
||||
? t("Common:Disabled")
|
||||
: "";
|
||||
|
||||
const i = {
|
||||
const i: TSelectorItem = {
|
||||
id: userId,
|
||||
email,
|
||||
avatar: userAvatar,
|
||||
@ -100,7 +100,7 @@ const toListItem = (
|
||||
hasAvatar,
|
||||
isDisabled: isInvited || isDisabled,
|
||||
disabledText,
|
||||
} as TSelectorItem;
|
||||
};
|
||||
|
||||
return i;
|
||||
};
|
||||
|
@ -42,7 +42,7 @@ export type RoomSelectorProps = TSelectorHeader &
|
||||
|
||||
onSubmit: (items: TSelectorItem[]) => void | Promise<void>;
|
||||
roomType?: RoomsType;
|
||||
excludeItems?: number[];
|
||||
excludeItems?: (number | string | undefined)[];
|
||||
setIsDataReady?: (value: boolean) => void;
|
||||
submitButtonLabel?: string;
|
||||
withSearch?: boolean;
|
||||
|
@ -25,16 +25,42 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { TRoom } from "../../api/rooms/types";
|
||||
import { TSelectorItem } from "../../components/selector";
|
||||
|
||||
export const convertToItems = (folders: TRoom[]) => {
|
||||
const items = folders.map((folder) => {
|
||||
const { id, title, roomType, logo, shared } = folder;
|
||||
const items: TSelectorItem[] = folders.map((folder) => {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
roomType,
|
||||
logo,
|
||||
shared,
|
||||
parentId,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
rootFolderType,
|
||||
security,
|
||||
} = folder;
|
||||
|
||||
const icon = logo.medium;
|
||||
const iconOriginal = logo.original;
|
||||
const color = logo.color;
|
||||
|
||||
return { id, label: title, icon, iconOriginal, color, roomType, shared };
|
||||
return {
|
||||
id,
|
||||
label: title,
|
||||
icon,
|
||||
iconOriginal,
|
||||
color,
|
||||
roomType,
|
||||
shared,
|
||||
isFolder: true,
|
||||
parentId,
|
||||
filesCount,
|
||||
foldersCount,
|
||||
rootFolderType,
|
||||
security,
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
|
@ -159,10 +159,10 @@ const RoomSelector = ({
|
||||
if (isFirstLoad) {
|
||||
setTotal(totalCount);
|
||||
|
||||
setItems([...rooms] as TSelectorItem[]);
|
||||
setItems([...rooms]);
|
||||
} else {
|
||||
setItems((prevItems) => {
|
||||
const newItems = [...rooms] as TSelectorItem[];
|
||||
const newItems = [...rooms];
|
||||
|
||||
return [...prevItems, ...newItems];
|
||||
});
|
||||
|
@ -2391,10 +2391,17 @@ export const getBaseTheme = () => {
|
||||
item: {
|
||||
hoverBackground: grayLight,
|
||||
selectedBackground: lightHover,
|
||||
|
||||
inputButtonBorder: "#D0D5DA",
|
||||
inputButtonBorderHover: grayMain,
|
||||
},
|
||||
|
||||
emptyScreen: {
|
||||
descriptionColor: cyanBlueDarkShade,
|
||||
|
||||
buttonColor: "#657077",
|
||||
hoverButtonColor: "#333333",
|
||||
pressedButtonColor: "#555F65",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -2370,10 +2370,16 @@ const Dark: TTheme = {
|
||||
item: {
|
||||
hoverBackground: "#3d3d3d",
|
||||
selectedBackground: "#3d3d3d",
|
||||
|
||||
inputButtonBorder: "#474747",
|
||||
inputButtonBorderHover: grayMaxLight,
|
||||
},
|
||||
|
||||
emptyScreen: {
|
||||
descriptionColor: "#ADADAD",
|
||||
buttonColor: "#ADADAD",
|
||||
hoverButtonColor: "#FFFFFF",
|
||||
pressedButtonColor: "#CCCCCC",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -55,6 +55,8 @@ export type NonFunctionProperties<T, ExcludeTypes> = Pick<
|
||||
NonFunctionPropertyNames<T, ExcludeTypes>
|
||||
>;
|
||||
|
||||
export type MergeTypes<T, MergedType> = Omit<T, keyof MergedType> & MergedType;
|
||||
|
||||
export type TPathParts = {
|
||||
id: number;
|
||||
title: string;
|
||||
|
3
public/images/selector.input.accept.svg
Normal file
3
public/images/selector.input.accept.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.20712 14.2069L15.7071 4.70694L14.2929 3.29272L5.50001 12.0856L1.70712 8.29272L0.292908 9.70694L4.7929 14.2069C5.18343 14.5975 5.81659 14.5975 6.20712 14.2069Z" fill="#A3A9AE"/>
|
||||
</svg>
|
After Width: | Height: | Size: 332 B |
10
public/images/selector.input.cancel.svg
Normal file
10
public/images/selector.input.cancel.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_630_1430)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.41499 8.00016L13.7075 12.2927L12.2933 13.7069L8.00089 9.41449L3.7102 13.7059L2.29587 12.2918L6.58668 8.00028L2.29332 3.70692L3.70754 2.29271L8.00078 6.58595L12.2933 2.29276L13.7076 3.70686L9.41499 8.00016Z" fill="#A3A9AE"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_630_1430">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 523 B |
Loading…
Reference in New Issue
Block a user