Merge branch 'hotfix/v2.6.1' into feature/people-column

This commit is contained in:
Alexey Safronov 2024-08-21 11:43:56 +04:00
commit 311a9a1670
18 changed files with 283 additions and 103 deletions

View File

@ -160,7 +160,7 @@ export default function withFileActions(WrappedFileItem) {
if (
e.target?.tagName === "INPUT" ||
e.target?.tagName === "SPAN" ||
// e.target?.tagName === "SPAN" ||
e.target?.tagName === "A" ||
e.target.closest(".checkbox") ||
e.target.closest(".table-container_row-checkbox") ||

View File

@ -170,7 +170,7 @@ const PureConnectDialogContainer = (props) => {
provider_id,
)
.then(async () => {
await setThirdPartyAccountsInfo();
await setThirdPartyAccountsInfo(t);
})
.catch((err) => {
toastr.error(err);

View File

@ -24,18 +24,23 @@
// 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, useState, useRef } from "react";
import { useEffect, useState } from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import { isMobileOnly, isMobile } from "react-device-detect";
import { Button } from "@docspace/shared/components/button";
import { DropDownItem } from "@docspace/shared/components/drop-down-item";
import { Text } from "@docspace/shared/components/text";
import { Tooltip } from "@docspace/shared/components/tooltip";
import { connectedCloudsTypeTitleTranslation as ProviderKeyTranslation } from "@docspace/client/src/helpers/filesUtils";
import { Base } from "@docspace/shared/themes";
import { toastr } from "@docspace/shared/components/toast";
import { ComboBox } from "@docspace/shared/components/combobox";
import ExternalLinkReactSvgUrl from "PUBLIC_DIR/images/external.link.react.svg?url";
import { ThirdPartyServicesUrlName } from "../../../../../helpers/constants";
import { isDesktop } from "@docspace/shared/utils";
const StyledStorageLocation = styled.div`
display: flex;
@ -91,16 +96,29 @@ const StyledStorageLocation = styled.div`
StyledStorageLocation.defaultProps = { theme: Base };
const services = {
GoogleDrive: "google",
Box: "box",
Dropbox: "dropbox",
OneDrive: "skydrive",
Nextcloud: "nextcloud",
kDrive: "kdrive",
ownCloud: "owncloud",
WebDav: "webdav",
};
const StyledComboBoxItem = styled.div`
display: flex;
.drop-down-item_text {
color: ${({ theme, isDisabled }) =>
isDisabled ? theme.dropDownItem.disableColor : theme.dropDownItem.color};
}
.drop-down-item_icon {
display: flex;
align-items: center;
div {
display: flex;
}
margin-inline-start: auto;
svg {
min-height: 16px;
min-width: 16px;
}
}
`;
const ThirdPartyComboBox = ({
t,
@ -124,15 +142,16 @@ const ThirdPartyComboBox = ({
isDisabled,
setIsScrollLocked,
isAdmin,
}) => {
const deafultSelectedItem = {
const defaultSelectedItem = {
key: "length",
label:
storageLocation?.provider?.title ||
t("ThirdPartyStorageComboBoxPlaceholder"),
};
const [selectedItem, setSelectedItem] = useState(deafultSelectedItem);
const [selectedItem, setSelectedItem] = useState(defaultSelectedItem);
const thirdparties = connectItems.map((item) => ({
...item,
@ -142,7 +161,7 @@ const ThirdPartyComboBox = ({
const setStorageLocaiton = (thirparty, isConnected) => {
if (!isConnected) {
window.open(
`/portal-settings/integration/third-party-services?service=${services[thirparty.id]}`,
`/portal-settings/integration/third-party-services?service=${ThirdPartyServicesUrlName[thirparty.id]}`,
"_blank",
);
return;
@ -207,35 +226,79 @@ const ThirdPartyComboBox = ({
setSaveThirdpartyResponse(null);
}, [saveThirdpartyResponse]);
const options = thirdparties
.sort((storage) => (storage.isConnected ? -1 : 1))
.map((item) => ({
label:
item.title + (item.isConnected ? "" : ` (${t("ActivationRequired")})`),
title: item.title,
key: item.id,
icon: item.isConnected ? undefined : ExternalLinkReactSvgUrl,
className: item.isConnected ? "" : "storage-unavailable",
}));
const onSelect = (event) => {
const data = event.currentTarget.dataset;
const onSelect = (elem) => {
const thirdparty = thirdparties.find((t) => {
return elem.key === t.id;
return t.id === data.thirdPartyId;
});
thirdparty && setStorageLocaiton(thirdparty, thirdparty.isConnected);
thirdparty.isConnected
? setSelectedItem(elem)
: setSelectedItem({ ...deafultSelectedItem });
? setSelectedItem({ key: thirdparty.id, label: thirdparty.title })
: setSelectedItem({ ...defaultSelectedItem });
};
const getTextTooltip = () => {
return (
<Text fontSize="12px" noSelect>
{t("Common:EnableThirdPartyIntegration")}
</Text>
);
};
const advancedOptions = thirdparties
.sort((storage) => (storage.isConnected ? -1 : 1))
?.map((item) => {
const isDisabled = !item.isConnected && !isAdmin;
const itemLabel =
item.title + (item.isConnected ? "" : ` (${t("ActivationRequired")})`);
const disabledData = isDisabled
? { "data-tooltip-id": "file-links-tooltip", "data-tip": "tooltip" }
: {};
return (
<StyledComboBoxItem isDisabled={isDisabled} key={item.id}>
<DropDownItem
onClick={onSelect}
data-third-party-id={item.id}
disabled={isDisabled}
{...disabledData}
>
<Text className="drop-down-item_text" fontWeight={600}>
{itemLabel}
</Text>
{!isDisabled && !item.isConnected ? (
<ReactSVG
src={ExternalLinkReactSvgUrl}
className="drop-down-item_icon"
/>
) : (
<></>
)}
</DropDownItem>
{isDisabled && (
<Tooltip
float={isDesktop()}
id="file-links-tooltip"
getContent={getTextTooltip}
place="bottom"
/>
)}
</StyledComboBoxItem>
);
});
return (
<StyledStorageLocation>
<div className="set_room_params-thirdparty">
<ComboBox
className="thirdparty-combobox"
selectedOption={selectedItem}
options={options}
options={[]}
advancedOptions={advancedOptions}
scaled
withBackdrop={isMobile}
size="content"
@ -244,12 +307,12 @@ const ThirdPartyComboBox = ({
directionY="both"
displaySelectedOption
noBorder={false}
// fixedDirection
isDefaultMode={true}
hideMobileView={false}
forceCloseClickOutside
scaledOptions
onSelect={onSelect}
showDisabledItems
displayArrow
/>
<Button
id="shared_third-party-storage_connect"

View File

@ -71,6 +71,7 @@ const ThirdPartyStorage = ({
isDisabled,
currentColorScheme,
isRoomAdmin,
isAdmin,
createNewFolderIsChecked,
onCreateFolderChange,
@ -168,6 +169,7 @@ const ThirdPartyStorage = ({
setIsScrollLocked={setIsScrollLocked}
setIsOauthWindowOpen={setIsOauthWindowOpen}
isDisabled={isDisabled}
isAdmin={isAdmin}
/>
)}
@ -213,7 +215,7 @@ export default inject(
const connectItems = thirdPartyStore.connectingStorages;
const { isRoomAdmin } = authStore;
const { isRoomAdmin, isAdmin } = authStore;
return {
connectItems,
@ -232,6 +234,7 @@ export default inject(
getOAuthToken,
currentColorScheme,
isRoomAdmin,
isAdmin,
fetchConnectingStorages: thirdPartyStore.fetchConnectingStorages,
};
},

View File

@ -169,3 +169,14 @@ export const SortByFieldName = Object.freeze({
LastOpened: "LastOpened",
UsedSpace: "usedspace",
});
export const ThirdPartyServicesUrlName = Object.freeze({
GoogleDrive: "google",
Box: "box",
Dropbox: "dropbox",
OneDrive: "skydrive",
Nextcloud: "nextcloud",
kDrive: "kdrive",
ownCloud: "owncloud",
WebDav: "webdav",
});

View File

@ -625,6 +625,12 @@ const StyledBackup = styled.div`
}
.backup_third-party-context {
margin-top: 4px;
svg {
width: 16px;
height: 16px;
padding: 7px;
}
}
`;
const StyledBackupList = styled.div`
@ -764,6 +770,31 @@ const StyledSettingsHeader = styled.div`
margin: auto 0;
}
`;
const StyledComboBoxItem = styled.div`
display: flex;
.drop-down-item_text {
color: ${({ theme, isDisabled }) =>
isDisabled ? theme.dropDownItem.disableColor : theme.dropDownItem.color};
}
.drop-down-item_icon {
display: flex;
align-items: center;
div {
display: flex;
}
margin-inline-start: auto;
svg {
min-height: 16px;
min-width: 16px;
}
}
`;
export {
StyledModules,
StyledRestoreBackup,
@ -774,4 +805,5 @@ export {
StyledAutoBackup,
StyledStoragesModule,
StyledSettingsHeader,
StyledComboBoxItem,
};

View File

@ -27,10 +27,15 @@
import VerticalDotsReactSvgUrl from "PUBLIC_DIR/images/vertical-dots.react.svg?url";
import RefreshReactSvgUrl from "PUBLIC_DIR/images/refresh.react.svg?url";
import AccessNoneReactSvgUrl from "PUBLIC_DIR/images/access.none.react.svg?url";
import React, { useEffect, useReducer } from "react";
import ExternalLinkReactSvgUrl from "PUBLIC_DIR/images/external.link.react.svg?url";
import { useEffect, useReducer } from "react";
import { ReactSVG } from "react-svg";
import { Button } from "@docspace/shared/components/button";
import { DropDownItem } from "@docspace/shared/components/drop-down-item";
import { Text } from "@docspace/shared/components/text";
import { saveSettingsThirdParty } from "@docspace/shared/api/files";
import { StyledBackup } from "../StyledBackup";
import { StyledBackup, StyledComboBoxItem } from "../StyledBackup";
import { ComboBox } from "@docspace/shared/components/combobox";
import { toastr } from "@docspace/shared/components/toast";
import { inject, observer } from "mobx-react";
@ -39,6 +44,7 @@ import DeleteThirdPartyDialog from "../../../../../../components/dialogs/DeleteT
import { getOAuthToken } from "@docspace/shared/utils/common";
import FilesSelectorInput from "SRC_DIR/components/FilesSelectorInput";
import { useTranslation } from "react-i18next";
import { ThirdPartyServicesUrlName } from "../../../../../../helpers/constants";
const initialState = {
folderList: {},
@ -83,7 +89,7 @@ const DirectThirdPartyConnection = (props) => {
const onSetSettings = async () => {
try {
await setThirdPartyAccountsInfo();
await setThirdPartyAccountsInfo(t);
setState({
isLoading: false,
@ -157,7 +163,7 @@ const DirectThirdPartyConnection = (props) => {
provider_id,
);
await setThirdPartyAccountsInfo();
await setThirdPartyAccountsInfo(t);
} catch (e) {
toastr.error(e);
}
@ -165,9 +171,24 @@ const DirectThirdPartyConnection = (props) => {
setState({ isLoading: false, isUpdatingInfo: false });
};
const onSelectAccount = (options) => {
const key = options.key;
setSelectedThirdPartyAccount({ ...accounts[+key] });
const onSelectAccount = (event) => {
const data = event.currentTarget.dataset;
const account = accounts.find((t) => t.key === data.thirdPartyKey);
if (!account.connected) {
setSelectedThirdPartyAccount({
key: 0,
label: selectedThirdPartyAccount?.label,
});
return window.open(
`/portal-settings/integration/third-party-services?service=${ThirdPartyServicesUrlName[data.thirdPartyKey]}`,
"_blank",
);
}
setSelectedThirdPartyAccount(account);
};
const onDisconnect = () => {
@ -187,7 +208,7 @@ const DirectThirdPartyConnection = (props) => {
key: "Disconnect-settings",
label: t("Common:Disconnect"),
onClick: onDisconnect,
disabled: selectedThirdPartyAccount?.connected ? false : true,
disabled: selectedThirdPartyAccount?.storageIsConnected ? false : true,
icon: AccessNoneReactSvgUrl,
},
];
@ -201,6 +222,33 @@ const DirectThirdPartyConnection = (props) => {
const isDisabledSelector = isLoading || isDisabled;
const folderList = connectedThirdPartyAccount ?? {};
const advancedOptions = accounts?.map((item) => {
return (
<StyledComboBoxItem isDisabled={item.disabled} key={item.key}>
<DropDownItem
onClick={onSelectAccount}
className={item.className}
data-third-party-key={item.key}
disabled={item.disabled}
>
<Text className="drop-down-item_text" fontWeight={600}>
{item.title}
</Text>
{!item.disabled && !item.connected ? (
<ReactSVG
src={ExternalLinkReactSvgUrl}
className="drop-down-item_icon"
/>
) : (
<></>
)}
</DropDownItem>
</StyledComboBoxItem>
);
});
return (
<StyledBackup
isConnectedAccount={
@ -210,17 +258,25 @@ const DirectThirdPartyConnection = (props) => {
>
<div className="backup_connection">
<ComboBox
options={accounts}
className="thirdparty-combobox"
selectedOption={{
key: 0,
label: selectedThirdPartyAccount?.label,
}}
onSelect={onSelectAccount}
options={[]}
advancedOptions={advancedOptions}
scaled
size="content"
manualWidth={"auto"}
directionY="both"
displaySelectedOption
noBorder={false}
isDefaultMode={true}
hideMobileView={false}
forceCloseClickOutside
scaledOptions
dropDownMaxHeight={300}
tabIndex={1}
showDisabledItems
displayArrow
isDisabled={isDisabledComponent}
/>

View File

@ -37,13 +37,16 @@ import { combineUrl } from "@docspace/shared/utils/combineUrl";
import config from "PACKAGE_FILE";
import {
getSettingsThirdParty,
getThirdPartyCapabilities,
uploadBackup,
} from "@docspace/shared/api/files";
import i18n from "../i18n";
import { connectedCloudsTypeTitleTranslation } from "../helpers/filesUtils.js";
const { EveryDayType, EveryWeekType } = AutoBackupPeriod;
class BackupStore {
authStore = null;
thirdPartyStore = null;
restoreResource = null;
backupSchedule = {};
@ -100,11 +103,13 @@ class BackupStore {
selectedThirdPartyAccount = null;
connectedThirdPartyAccount = null;
accounts = [];
capabilities = [];
connectedAccount = [];
constructor() {
constructor(authStore, thirdPartyStore) {
makeAutoObservable(this);
this.authStore = authStore;
this.thirdPartyStore = thirdPartyStore;
}
setConnectedThirdPartyAccount = (account) => {
@ -195,37 +200,20 @@ class BackupStore {
return false;
}
setThirdPartyAccountsInfo = async () => {
const [connectedAccount, capabilities] = await Promise.all([
setThirdPartyAccountsInfo = async (t) => {
const [connectedAccount, providers] = await Promise.all([
getSettingsThirdParty(),
getThirdPartyCapabilities(),
this.thirdPartyStore.fetchConnectingStorages(),
]);
this.setCapabilities(capabilities);
this.setConnectedThirdPartyAccount(connectedAccount);
const providerNames = [
["GoogleDrive", i18n.t("Translations:TypeTitleGoogle")],
["Box", i18n.t("Translations:TypeTitleBoxNet")],
["DropboxV2", i18n.t("Translations:TypeTitleDropBox")],
["SharePoint", i18n.t("Translations:TypeTitleSharePoint")],
["OneDrive", i18n.t("Translations:TypeTitleSkyDrive")],
["WebDav", "Nextcloud"],
["WebDav", "ownCloud"],
["kDrive", i18n.t("Translations:TypeTitlekDrive")],
["Yandex", i18n.t("Translations:TypeTitleYandex")],
["WebDav", i18n.t("Translations:TypeTitleWebDav")],
];
let accounts = [],
selectedAccount = {};
let index = 0;
providerNames.map((item) => {
const { account, isConnected } = this.getThirdPartyAccount(
item[0],
item[1],
index,
);
providers.map((item) => {
const { account, isConnected } = this.getThirdPartyAccount(item, t);
if (!account) return;
@ -237,51 +225,53 @@ class BackupStore {
index++;
});
accounts = accounts.sort((storage) => (storage.connected ? -1 : 1));
this.setThirdPartyAccounts(accounts);
console.log(selectedAccount, accounts);
const connectedThirdPartyAccount = accounts.findLast((a) => a.connected);
this.setSelectedThirdPartyAccount(
Object.keys(selectedAccount).length !== 0
? selectedAccount
: { ...accounts[0] },
: connectedThirdPartyAccount,
);
};
getThirdPartyAccount = (providerKey, serviceTitle, index) => {
const accountIndex =
this.capabilities &&
this.capabilities.findIndex((x) => x[0] === providerKey);
if (accountIndex === -1) return { account: null, isConnected: false };
getThirdPartyAccount = (provider, t) => {
const serviceTitle = connectedCloudsTypeTitleTranslation(provider.name, t);
const serviceLabel = provider.connected
? serviceTitle
: `${serviceTitle} (${t("CreateEditRoomDialog:ActivationRequired")})`;
const isConnected =
this.connectedThirdPartyAccount?.providerKey === "WebDav"
? serviceTitle === this.connectedThirdPartyAccount?.title
: this.capabilities[accountIndex][0] ===
this.connectedThirdPartyAccount?.providerKey;
: provider.key === this.connectedThirdPartyAccount?.providerKey;
const isDisabled = !provider.connected && !this.authStore.isAdmin;
const account = {
key: index.toString(),
label: serviceTitle,
title: serviceTitle,
provider_key: this.capabilities[accountIndex][0],
...(this.capabilities[accountIndex][1] && {
provider_link: this.capabilities[accountIndex][1],
key: provider.name,
label: serviceLabel,
title: serviceLabel,
provider_key: provider.key,
...(provider.clientId && {
provider_link: provider.clientId,
}),
connected: isConnected,
storageIsConnected: isConnected,
connected: provider.connected,
...(isConnected && {
provider_id: this.connectedThirdPartyAccount?.providerId,
id: this.connectedThirdPartyAccount.id,
}),
disabled: isDisabled,
};
return { account, isConnected };
};
setCapabilities = (capabilities) => {
this.capabilities = capabilities;
};
setThirdPartyAccounts = (accounts) => {
this.accounts = accounts;
};

View File

@ -432,10 +432,10 @@ class LdapFormStore {
scrollToField = () => {
for (let key in this.errors) {
const element = document.getElementsByName(key)[0];
const element = document.getElementsByName(key)?.[0];
element.focus();
element.blur();
element?.focus();
element?.blur();
return;
}
};

View File

@ -978,9 +978,9 @@ class SsoFormStore {
for (let key in this) {
if (key.includes("HasError") && this[key] !== false) {
const name = key.replace("HasError", "");
const element = document.getElementsByName(name)[0];
element.focus();
element.blur();
const element = document.getElementsByName(name)?.[0];
element?.focus();
element?.blur();
return;
}
}

View File

@ -84,7 +84,10 @@ class ThirdPartyStore {
oauthHref: storage.redirectUrl,
category: storage.name,
requiredConnectionUrl: storage.requiredConnectionUrl,
clientId: storage.clientId,
}));
return res;
};
saveThirdParty = (

View File

@ -59,7 +59,7 @@ import {
getCategoryTypeByFolderType,
getCategoryUrl,
} from "SRC_DIR/helpers/utils";
import { Link } from "@docspace/shared/components/link";
import { Link, LinkTarget, LinkType } from "@docspace/shared/components/link";
import { globalColors } from "@docspace/shared/themes";
class UploadDataStore {
@ -1583,6 +1583,8 @@ class UploadDataStore {
<Link
isHovered
color={globalColors.link}
type={LinkType.action}
target={LinkTarget.self}
onClick={() => {
toastr.clear();
this.setUploadPanelVisible(true);

View File

@ -98,7 +98,7 @@ const paymentStore = new PaymentStore(
);
const wizardStore = new WizardStore();
const confirmStore = new ConfirmStore();
const backupStore = new BackupStore();
const backupStore = new BackupStore(authStore, thirdPartyStore);
const commonStore = new CommonStore(settingsStore);
const ssoStore = new SsoFormStore();

View File

@ -173,6 +173,7 @@ const ComboBoxPure = (props: ComboboxProps) => {
optionStyle,
style,
withLabel = true,
displayArrow,
} = props;
const { tabIndex, onClickSelectedItem } = props;
@ -297,6 +298,7 @@ const ComboBoxPure = (props: ComboboxProps) => {
isLoading={isLoading}
type={type}
plusBadgeValue={plusBadgeValue}
displayArrow={displayArrow}
/>
{displayType !== "toggle" && (

View File

@ -167,6 +167,7 @@ export interface ComboboxProps {
title?: string;
plusBadgeValue?: number;
withLabel?: boolean;
displayArrow?: boolean;
}
export interface ComboButtonProps {
@ -189,6 +190,7 @@ export interface ComboButtonProps {
isLoading?: boolean;
type?: TCombobox;
plusBadgeValue?: number;
displayArrow?: boolean;
}
export interface ComboButtonThemeProps extends ComboButtonProps {

View File

@ -72,11 +72,12 @@ const ComboButton = (props: ComboButtonProps) => {
modernView = false,
tabIndex = -1,
isLoading = false,
displayArrow: displayArrowProp,
} = props;
const defaultOption = selectedOption?.default;
// const isSelected = selectedOption?.key !== 0;
const displayArrow = withOptions || withAdvancedOptions;
const displayArrow = withOptions || withAdvancedOptions || displayArrowProp;
const comboButtonClassName = `combo-button combo-button_${isOpen ? "open" : "closed"}`;

View File

@ -37,6 +37,7 @@ import { Row } from "./sub-components/Row";
import { DropDownProps } from "./DropDown.types";
import { DEFAULT_PARENT_HEIGHT } from "./DropDown.constants";
import { isIOS, isMobile } from "react-device-detect";
const DropDown = ({
directionY = "bottom",
@ -284,12 +285,25 @@ const DropDown = ({
};
window.addEventListener("resize", documentResizeListener.current);
if (isIOS && isMobile)
window.visualViewport?.addEventListener(
"resize",
documentResizeListener.current,
);
}
}, [checkPosition, checkPositionPortal, isDefaultMode, open]);
const unbindDocumentResizeListener = React.useCallback(() => {
if (documentResizeListener.current) {
window.removeEventListener("resize", documentResizeListener.current);
if (isIOS && isMobile)
window.visualViewport?.removeEventListener(
"resize",
documentResizeListener.current,
);
documentResizeListener.current = null;
}
}, []);

View File

@ -513,5 +513,6 @@
"Website": "Website",
"Yes": "Yes",
"Yesterday": "Yesterday",
"You": "You"
"You": "You",
"EnableThirdPartyIntegration": "Please ask a DocSpace owner or administrator to enable the corresponding service in the Integration section of the DocSpace Settings."
}