Merge branch 'feature/ssr-confirm' into feature/ssr-confirm-test

This commit is contained in:
Darya Umrikhina 2024-08-16 12:52:48 +04:00
commit 8f6235424c
70 changed files with 1039 additions and 437 deletions

View File

@ -146,7 +146,7 @@ const AccessSelector: React.FC<AccessSelectorProps> = ({
directionX="right"
directionY="top"
fixedDirection
manualWidth="fit-content"
manualWidth="auto"
isDefaultMode
isAside={isMobileView}
setIsOpenItemAccess={setIsOpenItemAccess}

View File

@ -38,6 +38,7 @@ import { IconButton } from "@docspace/shared/components/icon-button";
import RoomsFilter from "@docspace/shared/api/rooms/filter";
import { RoomSearchArea } from "@docspace/shared/enums";
import { frameCallEvent } from "@docspace/shared/utils/common";
import { getCategoryUrl } from "SRC_DIR/helpers/utils";
import { CategoryType } from "SRC_DIR/helpers/constants";
@ -60,6 +61,8 @@ const RoomNoAccessContainer = (props) => {
const navigate = useNavigate();
React.useEffect(() => {
frameCallEvent({ event: "onNoAccess" });
const timer = setTimeout(onGoToShared, 5000);
return () => clearTimeout(timer);
}, []);

View File

@ -218,7 +218,7 @@ const QuotaForm = ({
size="content"
onSelect={onSelectComboBox}
showDisabledItems
manualWidth={"fit-content"}
manualWidth="auto"
directionY="both"
/>
</div>

View File

@ -196,7 +196,7 @@ const SpaceQuota = (props) => {
size="content"
modernView
isLoading={isLoading}
manualWidth="fit-content"
manualWidth="auto"
directionY="both"
/>
</StyledBody>

View File

@ -239,7 +239,7 @@ const ThirdPartyComboBox = ({
scaled
withBackdrop={isMobile}
size="content"
manualWidth={"fit-content"}
manualWidth={"auto"}
isMobileView={isMobileOnly}
directionY="both"
displaySelectedOption

View File

@ -174,7 +174,7 @@ const GroupMember = ({ member, infoPanelSelection }: GroupMemberProps) => {
size="content"
modernView
title={t("Common:Role")}
manualWidth={"fit-content"}
manualWidth="auto"
isMobileView={isMobileOnly}
directionY="both"
displaySelectedOption

View File

@ -155,7 +155,7 @@ const EmbeddingPanelComponent = (props: EmbeddingPanelProps) => {
);
const fileConfig = {
mode: "viewer",
mode: "editor",
width: `${widthValue}${dataDimensions[0].label}`,
height: `${heightValue}${dataDimensions[1].label}`,
frameId: "ds-frame",

View File

@ -219,8 +219,8 @@ const StyledProperties = styled.div`
grid-template-columns: 120px 1fr;
grid-column-gap: 24px;
-webkit-box-align: center;
align-items: center;
-webkit-box-align: baseline;
align-items: baseline;
.property-title {
font-size: 13px;

View File

@ -176,7 +176,7 @@ const Accounts = (props) => {
size="content"
displaySelectedOption
modernView
manualWidth={"fit-content"}
manualWidth="auto"
isLoading={isLoading}
/>
);

View File

@ -382,7 +382,7 @@ const User = ({
size="content"
modernView
title={t("Common:Role")}
manualWidth={"fit-content"}
manualWidth="auto"
isLoading={isLoading}
isMobileView={isMobileOnly}
directionY="both"

View File

@ -138,15 +138,8 @@ class InsideGroupTableHeader extends React.Component {
newFilter.sortBy = sortBy;
if (sortBy === "AZ") {
if (
newFilter.sortBy !== "lastname" &&
newFilter.sortBy !== "firstname"
) {
newFilter.sortBy = "firstname";
} else if (newFilter.sortBy === "lastname") {
newFilter.sortBy = "firstname";
} else {
newFilter.sortBy = "lastname";
if (newFilter.sortBy !== "displayname") {
newFilter.sortBy = "displayname";
}
newFilter.sortOrder =
newFilter.sortOrder === "ascending" ? "descending" : "ascending";
@ -176,10 +169,7 @@ class InsideGroupTableHeader extends React.Component {
} = this.props;
const { sortOrder } = filter;
const sortBy =
filter.sortBy === "firstname" || filter.sortBy === "lastname"
? "AZ"
: filter.sortBy;
const sortBy = filter.sortBy === "displayname" ? "AZ" : filter.sortBy;
return (
<TableHeader

View File

@ -399,7 +399,7 @@ const InsideGroupTableRow = (props) => {
directionY="both"
size="content"
modernView
manualWidth={"fit-content"}
manualWidth="auto"
isLoading={isLoading}
optionStyle={{ maxWidth: "400px" }}
textOverflow
@ -442,7 +442,7 @@ const InsideGroupTableRow = (props) => {
size="content"
displaySelectedOption
modernView
manualWidth={"fit-content"}
manualWidth="auto"
isLoading={isLoading}
/>
);

View File

@ -143,7 +143,7 @@ class PeopleTableHeader extends React.Component {
if (
newFilter.sortBy === sortBy ||
(sortBy === "AZ" && newFilter.sortBy === "firstname")
(sortBy === "AZ" && newFilter.sortBy === "displayname")
) {
newFilter.sortOrder =
newFilter.sortOrder === "ascending" ? "descending" : "ascending";
@ -151,7 +151,7 @@ class PeopleTableHeader extends React.Component {
newFilter.sortBy = sortBy;
if (sortBy === "AZ") {
newFilter.sortBy = "firstname";
newFilter.sortBy = "displayname";
}
}
@ -178,10 +178,7 @@ class PeopleTableHeader extends React.Component {
} = this.props;
const { sortOrder } = filter;
const sortBy =
filter.sortBy === "firstname" || filter.sortBy === "lastname"
? "AZ"
: filter.sortBy;
const sortBy = filter.sortBy === "displayname" ? "AZ" : filter.sortBy;
return (
<TableHeader

View File

@ -438,7 +438,7 @@ const PeopleTableRow = (props) => {
size="content"
displaySelectedOption
modernView
manualWidth={"fit-content"}
manualWidth={"auto"}
isLoading={isLoading}
/>
);

View File

@ -1961,16 +1961,9 @@ const SectionFilterContent = ({
const options = [];
const firstName = {
id: "sort-by_first-name",
key: "firstname",
label: t("Common:FirstName"),
default: true,
};
const lastName = {
id: "sort-by_last-name",
key: "lastname",
label: t("Common:LastName"),
id: "sort-by_displayname",
key: "displayname",
label: t("Common:Name"),
default: true,
};
@ -2012,7 +2005,7 @@ const SectionFilterContent = ({
hideableColumns.Storage = storage;
}
options.push(firstName, lastName, type, department, email);
options.push(firstName, type, department, email);
if (showStorageInfo) options.push(storage);
return options;

View File

@ -112,7 +112,7 @@ const Layout = ({
<DetailsNavigationHeader />
) : currentPath === oauthCreatePath ||
currentPath === oauthEditPath ? (
<OAuthSectionHeader />
<OAuthSectionHeader isEdit={currentPath === oauthEditPath} />
) : (
<SectionHeaderContent />
)}

View File

@ -24,6 +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 from "react";
import styled from "styled-components";
import { Text } from "@docspace/shared/components/text";
import { Box } from "@docspace/shared/components/box";

View File

@ -24,6 +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 from "react";
import { TableRow, TableCell } from "@docspace/shared/components/table";
import { Text } from "@docspace/shared/components/text";

View File

@ -136,7 +136,7 @@ const UsersRowContent = (props: TypeSelectRowContentProps) => {
size={ComboBoxSize.content}
displaySelectedOption
modernView
manualWidth="fit-content"
manualWidth="auto"
/>
</div>
</Box>,

View File

@ -149,7 +149,7 @@ const UsersTableRow = (props: TypeSelectTableRowProps) => {
size={ComboBoxSize.content}
displaySelectedOption
modernView
manualWidth="fit-content"
manualWidth="auto"
/>
</div>
</TableCell>

View File

@ -130,6 +130,7 @@ const Manager = (props) => {
);
const [selectedColumns, setSelectedColumns] = useState([
{ key: "Name", label: t("Common:Name") },
{ key: "Size", label: t("Common:Size") },
{ key: "Type", label: t("Common:Type") },
{ key: "Tags", label: t("Common:Tags") },
]);

View File

@ -18,6 +18,9 @@ export interface OAuthProps {
previewDialogVisible?: boolean;
disableDialogVisible?: boolean;
deleteDialogVisible?: boolean;
generateDeveloperTokenDialogVisible?: boolean;
revokeDeveloperTokenDialogVisible?: boolean;
isInit: boolean;
setIsInit: (value: boolean) => void;
}

View File

@ -28,17 +28,15 @@ const OAuthSectionHeader = ({ isEdit }: { isEdit: boolean }) => {
<HeaderContainer>
<Headline type="content" truncate>
<div className="settings-section_header">
<div className="header">
<IconButton
iconName={ArrowPathReactSvgUrl}
size={17}
isFill
onClick={onBack}
className="arrow-button"
/>
<IconButton
iconName={ArrowPathReactSvgUrl}
size={17}
isFill
onClick={onBack}
className="arrow-button"
/>
{isEdit ? t("EditApp") : t("NewApp")}
</div>
{isEdit ? t("EditApp") : t("NewApp")}
</div>
</Headline>
</HeaderContainer>

View File

@ -18,6 +18,8 @@ import DisableDialog from "./sub-components/DisableDialog";
import DeleteDialog from "./sub-components/DeleteDialog";
import OAuthEmptyScreen from "./sub-components/EmptyScreen";
import List from "./sub-components/List";
import GenerateDeveloperTokenDialog from "./sub-components/GenerateDeveloperTokenDialog";
import RevokeDeveloperTokenDialog from "./sub-components/RevokeDeveloperTokenDialog";
const MIN_LOADER_TIME = 500;
@ -35,6 +37,8 @@ const OAuth = ({
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
}: OAuthProps) => {
const { t } = useTranslation(["OAuth"]);
@ -102,6 +106,8 @@ const OAuth = ({
{disableDialogVisible && <DisableDialog />}
{previewDialogVisible && <PreviewDialog visible={previewDialogVisible} />}
{deleteDialogVisible && <DeleteDialog />}
{generateDeveloperTokenDialogVisible && <GenerateDeveloperTokenDialog />}
{revokeDeveloperTokenDialogVisible && <RevokeDeveloperTokenDialog />}
</OAuthContainer>
);
};
@ -128,6 +134,8 @@ export default inject(
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
} = oauthStore;
return {
viewAs,
@ -143,6 +151,8 @@ export default inject(
setIsInit,
disableDialogVisible,
deleteDialogVisible,
generateDeveloperTokenDialogVisible,
revokeDeveloperTokenDialogVisible,
};
},
)(observer(OAuth));

View File

@ -2,6 +2,7 @@ import React from "react";
import { Text } from "@docspace/shared/components/text";
import { SelectorAddButton } from "@docspace/shared/components/selector-add-button";
import { globalColors } from "@docspace/shared/themes";
import { StyledInputGroup } from "../ClientForm.styled";
@ -55,7 +56,8 @@ const SelectGroup = ({
color=""
textAlign=""
>
{label} *
{label}{" "}
<span style={{ color: globalColors.lightErrorStatus }}> *</span>
</Text>
</div>
<div className="select">

View File

@ -0,0 +1,211 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled, { useTheme } from "styled-components";
import { i18n } from "i18next";
import { useTranslation } from "react-i18next";
import copy from "copy-to-clipboard";
import moment from "moment-timezone";
import api from "@docspace/shared/api";
import { IClientProps } from "@docspace/shared/utils/oauth/types";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { Text } from "@docspace/shared/components/text";
import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { InputBlock } from "@docspace/shared/components/input-block";
import { InputSize, InputType } from "@docspace/shared/components/text-input";
import CopyReactSvgUrl from "PUBLIC_DIR/images/copy.react.svg?url";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { UserStore } from "@docspace/shared/store/UserStore";
import { globalColors } from "@docspace/shared/themes";
const StyledContainer = styled.div`
p {
margin-bottom: 16px;
}
.dates {
margin-top: 16px;
margin-bottom: 0;
}
`;
type GenerateDeveloperTokenDialogProps = {
client?: IClientProps;
email?: string;
setGenerateDeveloperTokenDialogVisible?: (value: boolean) => void;
};
const getDate = (date: Date, i18nArg: i18n) => {
return moment(date)
.locale(i18nArg.language)
.tz(window.timezone)
.format("MMM D, YYYY, h:mm:ss A");
};
const GenerateDeveloperTokenDialog = ({
client,
email,
setGenerateDeveloperTokenDialogVisible,
}: GenerateDeveloperTokenDialogProps) => {
const { i18n: i18nParam } = useTranslation(["OAuth", "Common"]);
const theme = useTheme();
const [token, setToken] = React.useState("");
const [dates, setDates] = React.useState({
created: getDate(new Date(), i18nParam),
expires: getDate(new Date(), i18nParam),
});
const [requestRunning, setRequestRunning] = React.useState(false);
const onGenerate = async () => {
if (token || !client || requestRunning) return;
try {
const { clientId, clientSecret, scopes } = client;
setRequestRunning(true);
const data = await api.oauth.generateDevelopToken(
clientId,
clientSecret,
scopes,
);
setRequestRunning(false);
if (!data) return;
const { access_token: accessToken, expires_in: expiresIn } = data;
const created = new Date();
// convert sec to ms
const expires = new Date(created.getTime() + expiresIn * 1000);
if (accessToken) {
setToken(accessToken);
copy(accessToken);
setDates({
created: getDate(created, i18nParam),
expires: getDate(expires, i18nParam),
});
toastr.success("Copied");
}
} catch (e) {
toastr.error(e as TData);
}
};
const onCopyClick = async () => {
copy(token);
toastr.success("Copied");
};
const onClose = () => {
if (requestRunning) return;
setGenerateDeveloperTokenDialogVisible?.(false);
};
return (
<ModalDialog
visible
onClose={onClose}
displayType={ModalDialogType.modal}
autoMaxHeight
scale
>
<ModalDialog.Header>Generate developer token</ModalDialog.Header>
<ModalDialog.Body>
<StyledContainer>
<Text>
By generating an developer access token, you will be able to make
API calls for your own account without going through the
authorization flow. To obtain access tokens for other users, use the
standard OAuth flow.
</Text>
<Text>
For scoped apps, the token will have the same scope as the app.
</Text>
{token ? (
<>
<Text
color={
theme.isBase
? globalColors.lightErrorStatus
: globalColors.darkErrorStatus
}
>
This access token can be used to access your account ({email})
via the API. Don`t share your access token with anyone.
</Text>
<InputBlock
value={token}
scale
isReadOnly
isDisabled
size={InputSize.base}
iconName={CopyReactSvgUrl}
onIconClick={onCopyClick}
type={InputType.text}
/>
<Text className="dates">
Created: {dates.created}
<br />
Expires: {dates.expires}{" "}
</Text>
</>
) : null}
</StyledContainer>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
label="Generate developer token"
primary
scale
onClick={onGenerate}
isDisabled={!!token}
isLoading={requestRunning}
size={ButtonSize.small}
/>
<Button
label="Cancel"
scale
onClick={onClose}
size={ButtonSize.small}
isDisabled={requestRunning}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(
({
oauthStore,
userStore,
}: {
oauthStore: OAuthStoreProps;
userStore: UserStore;
}) => {
const { setGenerateDeveloperTokenDialogVisible, bufferSelection } =
oauthStore;
const { user } = userStore;
return {
setGenerateDeveloperTokenDialogVisible,
client: bufferSelection,
email: user?.email,
};
},
)(observer(GenerateDeveloperTokenDialog));

View File

@ -0,0 +1,141 @@
import React from "react";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import api from "@docspace/shared/api";
import { IClientProps } from "@docspace/shared/utils/oauth/types";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { Text } from "@docspace/shared/components/text";
import { toastr } from "@docspace/shared/components/toast";
import { TData } from "@docspace/shared/components/toast/Toast.type";
import { InputBlock } from "@docspace/shared/components/input-block";
import { InputSize, InputType } from "@docspace/shared/components/text-input";
import { OAuthStoreProps } from "SRC_DIR/store/OAuthStore";
import { UserStore } from "@docspace/shared/store/UserStore";
const StyledContainer = styled.div`
p {
margin-bottom: 16px;
}
`;
type GenerateDeveloperTokenDialogProps = {
client?: IClientProps;
setRevokeDeveloperTokenDialogVisible?: (value: boolean) => void;
};
const GenerateDeveloperTokenDialog = ({
client,
setRevokeDeveloperTokenDialogVisible,
}: GenerateDeveloperTokenDialogProps) => {
// const {} = useTranslation(["OAuth", "Common"]);
const [token, setToken] = React.useState("");
const [requestRunning, setRequestRunning] = React.useState(false);
const onRevoke = async () => {
if (!token || !client || requestRunning) return;
try {
const { clientId, clientSecret } = client;
setRequestRunning(true);
await api.oauth.revokeDeveloperToken(token, clientId, clientSecret);
setRequestRunning(false);
setToken("");
setRevokeDeveloperTokenDialogVisible?.(false);
toastr.success("Revoked");
} catch (e) {
toastr.error(e as TData);
}
};
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setToken(value);
};
const onClose = () => {
if (requestRunning) return;
setRevokeDeveloperTokenDialogVisible?.(false);
};
return (
<ModalDialog
visible
onClose={onClose}
displayType={ModalDialogType.modal}
autoMaxHeight
scale
>
<ModalDialog.Header>Revoke developer token</ModalDialog.Header>
<ModalDialog.Body>
<StyledContainer>
<Text>Warning text</Text>
<InputBlock
value={token}
scale
placeholder="Enter developer token"
type={InputType.text}
size={InputSize.base}
onChange={onChange}
/>
</StyledContainer>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
label="Revoke"
primary
scale
onClick={onRevoke}
isDisabled={!token}
isLoading={requestRunning}
size={ButtonSize.small}
/>
<Button
label="Cancel"
scale
onClick={onClose}
size={ButtonSize.small}
isDisabled={requestRunning}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default inject(
({
oauthStore,
userStore,
}: {
oauthStore: OAuthStoreProps;
userStore: UserStore;
}) => {
const { setRevokeDeveloperTokenDialogVisible, bufferSelection } =
oauthStore;
const { user } = userStore;
return {
setRevokeDeveloperTokenDialogVisible,
client: bufferSelection,
email: user?.email,
};
},
)(observer(GenerateDeveloperTokenDialog));

View File

@ -1256,71 +1256,10 @@ class ContextOptionsStore {
!contextOptions.includes("finalize-version") &&
contextOptions.includes("show-version-history");
const versionActions = isDesktop()
? onlyShowVersionHistory
? [
{
id: "option_show-version-history",
key: "show-version-history",
label: t("ShowVersionHistory"),
icon: HistoryReactSvgUrl,
onClick: () =>
this.showVersionHistory(
item.id,
item.security,
item?.requestToken,
),
disabled: false,
},
]
: [
{
id: "option_version",
key: "version",
label: t("VersionHistory"),
icon: HistoryFinalizedReactSvgUrl,
items: [
{
id: "option_finalize-version",
key: "finalize-version",
label: t("FinalizeVersion"),
icon: HistoryFinalizedReactSvgUrl,
onClick: () =>
isEditing
? this.onShowEditingToast(t)
: this.finalizeVersion(item.id, item.security),
disabled: false,
},
{
id: "option_version-history",
key: "show-version-history",
label: t("ShowVersionHistory"),
icon: HistoryReactSvgUrl,
onClick: () =>
this.showVersionHistory(
item.id,
item.security,
item?.requestToken,
),
disabled: false,
},
],
},
]
: [
const versionActions = onlyShowVersionHistory
? [
{
id: "option_finalize-version",
key: "finalize-version",
label: t("FinalizeVersion"),
icon: HistoryFinalizedReactSvgUrl,
onClick: () =>
isEditing
? this.onShowEditingToast(t)
: this.finalizeVersion(item.id),
disabled: false,
},
{
id: "option_version-history",
id: "option_show-version-history",
key: "show-version-history",
label: t("ShowVersionHistory"),
icon: HistoryReactSvgUrl,
@ -1332,8 +1271,43 @@ class ContextOptionsStore {
),
disabled: false,
},
]
: [
{
id: "option_version",
key: "version",
label: t("VersionHistory"),
icon: HistoryFinalizedReactSvgUrl,
items: [
{
id: "option_finalize-version",
key: "finalize-version",
label: t("FinalizeVersion"),
icon: HistoryFinalizedReactSvgUrl,
onClick: () =>
isEditing
? this.onShowEditingToast(t)
: this.finalizeVersion(item.id, item.security),
disabled: false,
},
{
id: "option_version-history",
key: "show-version-history",
label: t("ShowVersionHistory"),
icon: HistoryReactSvgUrl,
onClick: () =>
this.showVersionHistory(
item.id,
item.security,
item?.requestToken,
),
disabled: false,
},
],
},
];
const moveActions = isDesktop()
const moveActions = !isInfoPanel
? [
{
id: "option_move-or-copy",
@ -1815,7 +1789,7 @@ class ContextOptionsStore {
const pluginItems = this.onLoadPlugins(item);
if (pluginItems.length > 0) {
if (!isDesktop() || pluginItems.length === 1) {
if (pluginItems.length === 1) {
pluginItems.forEach((plugin) => {
options.splice(1, 0, {
id: `option_${plugin.key}`,

View File

@ -125,6 +125,17 @@ class EditGroupStore {
const addedIds = Array.from(this.addedMembersMap.keys());
const removedIds = Array.from(this.removedMembersMap.keys());
const oldManager = this.group.manager;
const oldManagerRemovedButRemainsAsMember =
oldManager &&
oldManager.id !== this.manager?.id &&
!this.removedMembersMap.has(oldManager.id);
// Requires when new group is without manager and old manager moved to members. updateGroup api method doesn't provide possibility to do it without setting new manager
if (this.manager === null && oldManagerRemovedButRemainsAsMember) {
await api.groups.removeGroupMembers(this.group.id, [oldManager.id]);
addedIds.push(oldManager.id);
}
await updateGroup(
this.group?.id,

View File

@ -489,7 +489,7 @@ class LdapFormStore {
completed: true,
percents: 100,
certificateConfirmRequest: null,
error: "",
error: t("Common:UnexpectedError"),
};
}
@ -518,7 +518,6 @@ class LdapFormStore {
toastr.success(t("Common:SuccessfullyCompletedOperation"));
}
} catch (error) {
console.error(error);
toastr.error(error);
this.endProcess();
}

View File

@ -58,6 +58,12 @@ export interface OAuthStoreProps {
resetDialogVisible: boolean;
setResetDialogVisible: (value: boolean) => void;
generateDeveloperTokenDialogVisible: boolean;
setGenerateDeveloperTokenDialogVisible: (value: boolean) => void;
revokeDeveloperTokenDialogVisible: boolean;
setRevokeDeveloperTokenDialogVisible: (value: boolean) => void;
deleteDialogVisible: boolean;
setDeleteDialogVisible: (value: boolean) => void;
@ -157,6 +163,10 @@ class OAuthStore implements OAuthStoreProps {
resetDialogVisible: boolean = false;
generateDeveloperTokenDialogVisible: boolean = false;
revokeDeveloperTokenDialogVisible: boolean = false;
selection: string[] = [];
bufferSelection: IClientProps | null = null;
@ -216,6 +226,14 @@ class OAuthStore implements OAuthStoreProps {
this.resetDialogVisible = value;
};
setGenerateDeveloperTokenDialogVisible = (value: boolean) => {
this.generateDeveloperTokenDialogVisible = value;
};
setRevokeDeveloperTokenDialogVisible = (value: boolean) => {
this.revokeDeveloperTokenDialogVisible = value;
};
setClientSecret = (value: string) => {
this.clientSecret = value;
};
@ -526,6 +544,8 @@ class OAuthStore implements OAuthStoreProps {
this.setInfoDialogVisible(true);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onRevoke = () => {
@ -535,6 +555,8 @@ class OAuthStore implements OAuthStoreProps {
this.setRevokeDialogVisible(true);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onDisable = () => {
@ -544,6 +566,30 @@ class OAuthStore implements OAuthStoreProps {
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(true);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onGenerateDeveloperToken = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(false);
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(true);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onRevokeDeveloperToken = () => {
this.setBufferSelection(clientId);
this.setPreviewDialogVisible(false);
this.setInfoDialogVisible(false);
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(true);
};
const openOption = {
@ -598,6 +644,8 @@ class OAuthStore implements OAuthStoreProps {
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(true);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onShowPreview = () => {
@ -607,6 +655,8 @@ class OAuthStore implements OAuthStoreProps {
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
};
const onEnable = async (status: boolean) => {
@ -615,6 +665,8 @@ class OAuthStore implements OAuthStoreProps {
this.setRevokeDialogVisible(false);
this.setDisableDialogVisible(false);
this.setDeleteDialogVisible(false);
this.setGenerateDeveloperTokenDialogVisible(false);
this.setRevokeDeveloperTokenDialogVisible(false);
if (isGroupContext) {
try {
@ -673,6 +725,20 @@ class OAuthStore implements OAuthStoreProps {
onClick: onDisable,
};
const generateDeveloperTokenOption = {
key: "generate-token",
icon: EnableReactSvgUrl,
label: "Generate developer token",
onClick: onGenerateDeveloperToken,
};
const revokeDeveloperTokenOption = {
key: "revoke-token",
icon: EnableReactSvgUrl,
label: "Revoke developer token",
onClick: onRevokeDeveloperToken,
};
const contextOptions = [
{
key: "Separator dropdownItem",
@ -708,6 +774,9 @@ class OAuthStore implements OAuthStoreProps {
contextOptions.unshift(enableOption);
}
contextOptions.unshift(revokeDeveloperTokenOption);
contextOptions.unshift(generateDeveloperTokenOption);
if (!isInfo) contextOptions.unshift(infoOption);
contextOptions.unshift(authButtonOption);
contextOptions.unshift(editOption);

View File

@ -347,7 +347,7 @@ class ProfileActionsStore {
let bookTraining = null;
if (!isMobile && this.isTeamTrainingAlertAvailable) {
if (!isMobile && this.authStore.isTeamTrainingAlertAvailable) {
bookTraining = {
key: "user-menu-book-training",
icon: BookTrainingReactSvgUrl,

View File

@ -305,7 +305,7 @@ const Editor = ({
newConfig.events.onRequestClose = onSDKRequestClose;
}
if (config?.startFilling) {
if (config?.startFilling && !IS_ZOOM) {
newConfig.events.onRequestStartFilling = () =>
onSDKRequestStartFilling?.(t("Common:ShareAndCollect"));
}

View File

@ -25,7 +25,7 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { getSettings } from "@/utils/actions";
import ProfileRemoveForm from "@/components/ProfileRemoveForm ";
import ProfileRemoveForm from "@/components/ProfileRemoveForm";
async function Page() {
const settings = await getSettings();

View File

@ -26,15 +26,33 @@
import ConfirmRoute from "@/components/ConfirmRoute";
import { StyledBody, StyledPage } from "@/components/StyledConfirm.styled";
import { TConfirmLinkParams } from "@/types";
import { getSettings } from "@/utils/actions";
import { checkConfirmLink, getSettings } from "@/utils/actions";
import { headers } from "next/headers";
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const settings = await getSettings();
const hdrs = headers();
const searchParams = hdrs.get("x-confirm-query") ?? "";
const type = hdrs.get("x-confirm-type") ?? "";
const queryParams = Object.fromEntries(
new URLSearchParams(searchParams.toString()),
) as TConfirmLinkParams;
const confirmLinkParams: TConfirmLinkParams = Object.assign(
{ type },
queryParams,
);
const [settings, confirmLinkResult] = await Promise.all([
getSettings(),
checkConfirmLink(confirmLinkParams),
]);
return (
<StyledPage id="confirm-page">
@ -43,6 +61,9 @@ export default async function Layout({
<ConfirmRoute
defaultPage={settings?.defaultPage}
socketUrl={settings?.socketUrl}
confirmLinkResult={confirmLinkResult}
confirmLinkParams={confirmLinkParams}
confirmType={type}
>
{children}
</ConfirmRoute>

View File

@ -26,23 +26,16 @@
"use client";
import { notFound, usePathname, useSearchParams } from "next/navigation";
import React, { createContext, useCallback, useEffect, useState } from "react";
import { notFound, useSearchParams } from "next/navigation";
import React, { createContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { getCookie } from "@docspace/shared/utils";
import { LANGUAGE } from "@docspace/shared/constants";
import { checkConfirmLink, logout } from "@docspace/shared/api/user";
import AppLoader from "@docspace/shared/components/app-loader";
import { logout } from "@docspace/shared/api/user";
import { AuthenticatedAction, ValidationResult } from "@/utils/enums";
import {
TConfirmLinkResult,
TConfirmLinkParams,
ConfirmRouteProps,
TError,
TConfirmRouteContext,
} from "@/types";
import { ConfirmRouteProps, TConfirmRouteContext } from "@/types";
export const ConfirmRouteContext = createContext<TConfirmRouteContext>({
linkData: {},
@ -55,54 +48,19 @@ function ConfirmRoute(props: ConfirmRouteProps) {
defaultPage,
socketUrl,
children,
confirmLinkResult,
confirmLinkParams,
confirmType,
} = props;
const [stateData, setStateData] = useState<TConfirmRouteContext>({
linkData: {},
roomData: {},
});
const [confirmLinkResult, setConfirmLinkResult] =
useState<TConfirmLinkResult>();
const [error, setError] = useState<TError>();
const [loading, setLoading] = useState(true);
const [stateData, setStateData] = useState<TConfirmRouteContext | undefined>(
undefined,
);
const { i18n, t } = useTranslation(["Common"]);
const searchParams = useSearchParams();
const pathname = usePathname();
const isAuthenticated = !!socketUrl;
const getLinkParams = useCallback(() => {
const queryParams = Object.fromEntries(
new URLSearchParams(searchParams.toString()),
) as TConfirmLinkParams;
const posSeparator = pathname.lastIndexOf("/");
const type = !!posSeparator ? pathname?.slice(posSeparator + 1) : "";
const confirmLinkParams: TConfirmLinkParams = Object.assign(
{ type },
queryParams,
);
return { type, confirmLinkParams };
}, [pathname, searchParams]);
const getConfirmLinkResult = useCallback(async () => {
const { confirmLinkParams } = getLinkParams();
try {
const response = (await checkConfirmLink(
confirmLinkParams,
)) as TConfirmLinkResult;
setConfirmLinkResult(response);
} catch (error) {
const knownError = error as TError;
setError(knownError);
}
}, [getLinkParams]);
useEffect(() => {
if (location.search.includes("culture")) return;
const lng = getCookie(LANGUAGE);
@ -121,15 +79,7 @@ function ConfirmRoute(props: ConfirmRouteProps) {
}
}, [doAuthenticated, isAuthenticated, defaultPage, socketUrl]);
useEffect(() => {
getConfirmLinkResult();
}, [getConfirmLinkResult]);
useEffect(() => {
if (confirmLinkResult?.result == undefined) return;
const { confirmLinkParams } = getLinkParams();
if (!stateData) {
switch (confirmLinkResult.result) {
case ValidationResult.Ok:
const confirmHeader = searchParams.toString();
@ -143,7 +93,6 @@ function ConfirmRoute(props: ConfirmRouteProps) {
title: confirmLinkResult?.title,
};
setStateData((val) => ({ ...val, linkData, roomData }));
setLoading(false);
break;
case ValidationResult.Invalid:
case ValidationResult.Expired:
@ -165,27 +114,19 @@ function ConfirmRoute(props: ConfirmRouteProps) {
});
notFound();
}
}, [confirmLinkResult, getLinkParams, t, searchParams]);
}
const { type, confirmLinkParams } = getLinkParams();
if (!type && confirmLinkParams.type)
if (!confirmType && confirmLinkParams.type)
window.location.replace(
`/confirm/${confirmLinkParams.type}?${searchParams.toString()}`,
);
if (error) {
console.error("FAILED checkConfirmLink", { error, confirmLinkParams });
notFound();
}
if (loading) {
return <AppLoader />;
}
return (
<ConfirmRouteContext.Provider
value={{ linkData: stateData.linkData, roomData: stateData.roomData }}
value={{
linkData: stateData?.linkData ?? {},
roomData: stateData?.roomData ?? {},
}}
>
{children}
</ConfirmRouteContext.Provider>

View File

@ -33,7 +33,6 @@ import { mobile, tablet } from "@docspace/shared/utils";
export const RegisterContainer = styled.div<{
registrationForm?: boolean;
}>`
height: 100%;
width: 100%;

View File

@ -44,7 +44,10 @@ export const GreetingContainer = styled.div`
}
.tooltip {
p {
.invitation-text {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
@ -55,8 +58,10 @@ export const GreetingContainer = styled.div`
.portal-logo {
width: 100%;
max-width: 386px;
height: 44px;
margin: 0 auto;
padding-bottom: 16px;
height: 26.56px;
display: flex;
align-items: center;
justify-content: center;

View File

@ -72,7 +72,7 @@ export const GreetingCreateUserContainer = ({
<img src={logoUrl} className="portal-logo" alt="greeting-logo" />
{type === "LinkInvite" && (
<div className="tooltip">
<Text fontSize="16px">
<Text fontSize="16px" as="div" className="invitation-text">
{roomData.title ? (
<Trans
t={t}

View File

@ -94,7 +94,7 @@ const SimpleNav = ({
return (
<StyledSimpleNav id="login-header">
<img className="logo" src={logoUrl} alt="logo-url" />
{isLanguageComboboxVisible && <LanguageComboboxWrapper />}
{isComboboxVisible && <LanguageComboboxWrapper />}
</StyledSimpleNav>
);
};

View File

@ -79,6 +79,12 @@ export const StyledForm = styled(Box)`
.set-app-description {
width: 100%;
max-width: 500px;
.portal-logo {
margin: 0 auto;
max-width: 386px;
height: 44px;
}
}
.set-app-title {

View File

@ -28,7 +28,7 @@
"use client";
import { ChangeEvent, useContext, useState } from "react";
import { ChangeEvent, useContext, useEffect, useRef, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useTheme } from "styled-components";
@ -61,8 +61,6 @@ import { TError } from "@/types";
import { ConfirmRouteContext } from "../ConfirmRoute";
import { GreetingContainer } from "../GreetingContainer";
const PROXY_BASE_URL = combineUrl(window.ClientConfig?.proxy?.url, "/profile");
type TfaActivationFormProps = {
secretKey: string;
qrCode: string;
@ -86,6 +84,14 @@ const TfaActivationForm = ({
const { confirmHeader = null } = linkData;
const proxyBaseUrl = useRef("");
useEffect(() => {
proxyBaseUrl.current = combineUrl(
window.ClientConfig?.proxy?.url,
"/profile",
);
}, []);
const onSubmit = async () => {
try {
setIsLoading(true);
@ -97,7 +103,7 @@ const TfaActivationForm = ({
}
sessionStorage.setItem(OPEN_BACKUP_CODES_DIALOG, "true");
window.location.href = PROXY_BASE_URL;
window.location.href = proxyBaseUrl.current;
} catch (error) {
const knownError = error as TError;
let errorMessage: string;

View File

@ -49,9 +49,21 @@ export function middleware(request: NextRequest) {
}
if (request.nextUrl.pathname.includes("confirm")) {
const type = request.nextUrl.searchParams.get("type");
const queryType = request.nextUrl.searchParams.get("type");
const posSeparator = request.nextUrl.pathname.lastIndexOf("/");
const type = !!posSeparator
? request.nextUrl.pathname?.slice(posSeparator + 1)
: "";
requestHeaders.set("x-confirm-type", type);
requestHeaders.set(
"x-confirm-query",
request.nextUrl.searchParams.toString(),
);
return NextResponse.rewrite(
`${request.nextUrl.origin}/login/confirm/${type}${request.nextUrl.search}`,
`${request.nextUrl.origin}/login/confirm/${queryType}${request.nextUrl.search}`,
{ headers: requestHeaders },
);
}
@ -88,5 +100,12 @@ export function middleware(request: NextRequest) {
// See "Matching Paths" below to learn more
export const config = {
matcher: ["/health", "/", "/not-found", "/consent", "/login", "/confirm"],
matcher: [
"/health",
"/",
"/not-found",
"/consent",
"/login",
"/confirm/:path*",
],
};

View File

@ -156,6 +156,9 @@ export interface ConfirmRouteProps {
defaultPage?: string;
socketUrl?: string;
children: ReactNode;
confirmLinkResult: TConfirmLinkResult;
confirmLinkParams: TConfirmLinkParams;
confirmType: string;
}
export type GreetingContainersProps = {

View File

@ -46,7 +46,11 @@ import {
TVersionBuild,
} from "@docspace/shared/api/settings/types";
import { Encoder } from "@docspace/shared/utils/encoder";
import { TTfaSecretKeyAndQR } from "@/types";
import {
TConfirmLinkParams,
TConfirmLinkResult,
TTfaSecretKeyAndQR,
} from "@/types";
import { TScope } from "@docspace/shared/utils/oauth/types";
import { transformToClientProps } from "@docspace/shared/utils/oauth";
@ -369,3 +373,20 @@ export async function getTfaSecretKeyAndQR(confirmKey: string | null = null) {
return tfaSecretKeyAndQR.response as TTfaSecretKeyAndQR;
}
export async function checkConfirmLink(data: TConfirmLinkParams) {
const [checkConfirmLink] = createRequest(
[`/authentication/confirm`],
[["Content-Type", "application/json"]],
"POST",
JSON.stringify(data),
);
const response = await fetch(checkConfirmLink);
if (!response.ok) throw new Error(response.statusText);
const result = await response.json();
return result.response as TConfirmLinkResult;
}

View File

@ -33,12 +33,12 @@ export const initSSR = (headers: Record<string, string>) => {
client.initSSR(headers);
};
export const request = (
export const request = <T>(
options: TReqOption & AxiosRequestConfig,
skipRedirect = false,
isOAuth = false,
) => {
return client.request(options, skipRedirect, isOAuth);
): Promise<T> | undefined => {
return client.request<T>(options, skipRedirect, isOAuth);
};
export const setWithCredentialsStatus = (state: boolean) => {

View File

@ -146,6 +146,14 @@ export const addGroupMembers = (groupId: string, members: string) => {
});
};
export const removeGroupMembers = (groupId: string, membersIds: string[]) => {
return request({
method: "delete",
url: `/group/${groupId}/members`,
data: { id: groupId, members: membersIds },
}) as Promise<TGroup>;
};
// * Delete
export const deleteGroup = (groupId: string) => {

View File

@ -11,6 +11,7 @@ import {
IClientReqDTO,
TConsentData,
TConsentList,
TGenerateDeveloperToken,
} from "../../utils/oauth/types";
export const getClient = async (clientId: string): Promise<IClientProps> => {
@ -237,3 +238,38 @@ export const onOAuthCancel = (clientId: string, clientState: string) => {
true,
);
};
export const generateDevelopToken = (
client_id: string,
client_secret: string,
scopes: string[],
): Promise<TGenerateDeveloperToken> | undefined => {
const params = new URLSearchParams();
params.append("grant_type", "personal_access_token");
params.append("client_id", client_id);
params.append("client_secret", client_secret);
params.append("scope", scopes.join(" "));
return request<TGenerateDeveloperToken>(
{ method: "post", url: "/oauth2/token", data: params },
false,
true,
);
};
export const revokeDeveloperToken = (
token: string,
client_id: string,
client_secret: string,
) => {
const params = new URLSearchParams();
params.append("token", token);
params.append("client_id", client_id);
params.append("client_secret", client_secret);
return request(
{ method: "post", url: "/oauth2/revoke", data: params },
false,
true,
);
};

View File

@ -29,7 +29,7 @@ import { getObjectByLocation, toUrlParams } from "../../utils/common";
const DEFAULT_PAGE = 0;
const DEFAULT_PAGE_COUNT = 25;
const DEFAULT_TOTAL = 0;
const DEFAULT_SORT_BY = "firstname";
const DEFAULT_SORT_BY = "displayname";
const DEFAULT_SORT_ORDER = "ascending";
const DEFAULT_EMPLOYEE_STATUS = null;
const DEFAULT_ACTIVATION_STATUS = null;

View File

@ -29,7 +29,7 @@ import moment from "moment";
export const getCalendarYears = (observedDate: moment.Moment) => {
const years = [];
const selectedYear = observedDate.year();
const firstYear = selectedYear - (selectedYear % 10) - 1;
const firstYear = selectedYear - 1;
for (let i = firstYear; i <= firstYear + 15; i += 1) {
years.push(moment(i, "YYYY").format("YYYY"));

View File

@ -26,21 +26,7 @@
import styled, { css } from "styled-components";
import { Base, TTheme, globalColors } from "../../themes";
import { tablet, mobile, getCorrectFourValuesStyle } from "../../utils";
const styledTabletView = css<{ articleWidth: number }>`
position: fixed;
width: ${(props) => props.theme.newContextMenu.devices.tabletWidth};
max-width: ${(props) => props.theme.newContextMenu.devices.tabletWidth};
max-height: ${(props) => props.theme.newContextMenu.devices.maxHeight};
inset-inline-start: ${(props) =>
props.articleWidth
? `${props.articleWidth}px`
: props.theme.newContextMenu.devices.left};
inset-inline-end: ${(props) => props.theme.newContextMenu.devices.right};
bottom: ${(props) => props.theme.newContextMenu.devices.bottom};
margin: ${(props) => props.theme.newContextMenu.devices.margin};
`;
import { mobile, getCorrectFourValuesStyle } from "../../utils";
const styledMobileView = css`
position: fixed;
@ -97,14 +83,13 @@ const StyledContextMenu = styled.div<{
box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
-moz-box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
-webkit-box-shadow: ${(props) => props.theme.newContextMenu.boxShadow};
padding: ${(props) => props.theme.newContextMenu.padding};
@media ${tablet} {
${(props) => props.changeView && styledTabletView}
}
.scroll-body {
display: flex;
flex-direction: column;
justify-content: center;
@media ${mobile} {
${(props) => props.changeView && styledMobileView}
padding-inline-end: 0 !important;
}
}
@ -205,7 +190,6 @@ const StyledContextMenu = styled.div<{
box-shadow: ${(props) => props.theme.dropDown.boxShadow};
-moz-box-shadow: ${(props) => props.theme.dropDown.boxShadow};
-webkit-box-shadow: ${(props) => props.theme.dropDown.boxShadow};
padding: 4px 0px;
white-space: nowrap;
overflow: hidden;
@ -226,7 +210,7 @@ const StyledContextMenu = styled.div<{
position: relative;
border: ${(props) => props.theme.dropDownItem.border};
margin: ${(props) => props.theme.dropDownItem.margin};
padding: ${(props) => props.theme.dropDownItem.padding};
padding: 0 16px;
font-family: ${(props) => props.theme.fontFamily};
font-style: normal;
background: none;
@ -241,10 +225,6 @@ const StyledContextMenu = styled.div<{
-webkit-touch-callout: none;
@media ${tablet} {
padding: 0 16px;
}
&:hover {
background-color: ${(props) =>
props.noHover
@ -275,7 +255,7 @@ const StyledContextMenu = styled.div<{
cursor: default !important;
margin: ${(props) => props.theme.menuItem.separator.margin};
height: ${(props) => props.theme.menuItem.separator.height};
width: ${(props) => props.theme.menuItem.separator.width};
&:hover {
cursor: default !important;
}
@ -284,6 +264,14 @@ const StyledContextMenu = styled.div<{
.p-contextmenu .p-menuitem {
position: relative;
margin: ${(props) => props.theme.dropDownItem.margin};
max-width: calc(-32px + 100vw);
width: fit-content;
min-width: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.p-contextmenu .scroll-body .p-menuitem {
@ -361,10 +349,47 @@ const StyledContextMenu = styled.div<{
opacity: 1;
transition: opacity 250ms;
}
.p-contextmenu {
@media ${mobile} {
${(props) => props.changeView && styledMobileView}
}
@media not ${mobile} {
max-width: calc(100vw - 32px);
}
}
.p-contextmenu ul {
@media not ${mobile} {
max-width: calc(100vw - 32px);
}
}
`;
StyledContextMenu.defaultProps = {
theme: Base,
};
export const StyledList = styled.ul<{
listHeight: number;
widthSubMenu: null | number;
}>`
& > :first-child {
.scroll-body {
height: ${(props) => `${props.listHeight}px`};
}
}
& > :nth-child(1) {
${(props) =>
props.widthSubMenu &&
css`
.p-menuitem {
max-width: ${`${props.widthSubMenu}px`};
}
`}
}
`;
export { StyledContextMenu };

View File

@ -57,6 +57,8 @@ import {
ContextMenuRefType,
} from "./ContextMenu.types";
const marginBorder = 16; // Indentation from the border of the screen
const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
(props, ref) => {
const [visible, setVisible] = React.useState(false);
@ -65,9 +67,11 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
const [model, setModel] = React.useState<ContextMenuModel[] | null>(null);
const [changeView, setChangeView] = React.useState(false);
const [showMobileMenu, setShowMobileMenu] = React.useState(false);
const [onLoad, setOnLoad] = React.useState<
undefined | (() => Promise<ContextMenuModel[]>)
>(undefined);
const [mobileSubMenuItems, setMobileSubMenuItems] = React.useState<
ContextMenuModel[] | undefined
>([]);
const [mobileHeader, setMobileHeader] = React.useState<string>("");
const [articleWidth, setArticleWidth] = React.useState(0);
const prevReshow = React.useRef(false);
@ -211,7 +215,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
? rects.left - currentLeftOffset - currentRightOffset
: event.pageX + 1;
let top = rects ? rects.top : event.pageY + 1;
const width =
let width =
menuRef.current && menuRef.current.offsetParent
? menuRef.current.offsetWidth
: DomHelpers.getHiddenElementOuterWidth(menuRef.current);
@ -221,29 +225,30 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
: DomHelpers.getHiddenElementOuterHeight(menuRef.current);
const viewport = DomHelpers.getViewport();
const mobileView =
isMobileUtils() && (height > 210 || ignoreChangeView);
if (!mobileView) {
const options =
menuRef?.current?.getElementsByClassName("p-menuitem");
const optionsWidth: number[] = [];
if (options) {
Array.from(options).forEach((option) =>
optionsWidth.push(option.clientWidth),
);
const widthMaxContent = Math.max(...optionsWidth);
width = widthMaxContent;
}
}
if (theme.interfaceDirection === "rtl" && !rects && left > width) {
left = event.pageX - width + 1;
}
if (
isTabletUtils() &&
(height > 483 ||
(isMobileOnly && window.innerHeight < window.innerWidth))
) {
const article = document.getElementById("article-container");
let currentArticleWidth = 0;
if (article) {
currentArticleWidth = article.offsetWidth;
}
setChangeView(true);
setArticleWidth(currentArticleWidth);
return;
}
if (isMobileUtils() && (height > 210 || ignoreChangeView)) {
if (mobileView) {
setChangeView(true);
setArticleWidth(0);
@ -267,7 +272,8 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
// fit
if (top < document.body.scrollTop) {
top = document.body.scrollTop;
if (document.body.scrollTop === 0) top = marginBorder;
else top = document.body.scrollTop;
}
if (containerRef) {
@ -281,8 +287,10 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
}
}
if (menuRef.current) {
menuRef.current.style.left = `${left}px`;
menuRef.current.style.left = `${left || marginBorder}px`;
menuRef.current.style.top = `${top}px`;
if (!mobileView) menuRef.current.style.width = `${width}px`;
}
}
};
@ -410,14 +418,20 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
};
}, [documentResizeListener, onHide, visible]);
const onMobileItemClick = (
const onMobileItemClick = async (
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
label: string,
items?: ContextMenuModel[],
loadFunc?: () => Promise<ContextMenuModel[]>,
) => {
e.stopPropagation();
setShowMobileMenu(true);
if (loadFunc) setOnLoad(loadFunc);
const res = loadFunc ? await loadFunc() : items;
setMobileSubMenuItems(res);
setMobileHeader(label);
};
const onBackClick = (e: React.MouseEvent<HTMLDivElement>) => {
@ -517,7 +531,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
)}
<Text className="text" truncate dir="auto">
{header.title}
{showMobileMenu ? mobileHeader : header.title}
</Text>
</div>
)}
@ -527,7 +541,7 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
root
resetMenu={resetMenu}
onLeafClick={onLeafClick}
onLoad={onLoad}
mobileSubMenuItems={mobileSubMenuItems}
/>
) : (
<SubMenu
@ -535,8 +549,9 @@ const ContextMenu = React.forwardRef<ContextMenuRefType, ContextMenuProps>(
root
resetMenu={resetMenu}
onLeafClick={onLeafClick}
changeView={changeView}
onMobileItemClick={onMobileItemClick}
changeView={changeView}
withHeader={withHeader}
/>
)}
</div>

View File

@ -44,9 +44,9 @@ const MobileSubMenu = (props: {
onLeafClick: (e: React.MouseEvent) => void;
root?: boolean;
resetMenu: boolean;
onLoad?: () => Promise<ContextMenuModel[]>;
mobileSubMenuItems?: ContextMenuModel[];
}) => {
const { onLeafClick, root, resetMenu, onLoad } = props;
const { onLeafClick, root, resetMenu, mobileSubMenuItems } = props;
const [submenu, setSubmenu] = useState<null | ContextMenuModel[]>(null);
@ -91,16 +91,12 @@ const MobileSubMenu = (props: {
}
});
const fetchSubMenu = React.useCallback(async () => {
const res = await onLoad?.();
if (res) setSubmenu(res);
position();
}, [position, setSubmenu, onLoad]);
useEffect(() => {
if (onLoad) fetchSubMenu();
}, [onLoad, fetchSubMenu]);
if (!mobileSubMenuItems?.length) return;
setSubmenu(mobileSubMenuItems);
position();
}, [mobileSubMenuItems, mobileSubMenuItems?.length, position]);
const onItemClick = (e: React.MouseEvent, item: ContextMenuType) => {
const { disabled, url, onClick, items, action } = item;

View File

@ -33,20 +33,22 @@ import { useTheme } from "styled-components";
import ArrowIcon from "PUBLIC_DIR/images/arrow.right.react.svg";
import OutsdideIcon from "PUBLIC_DIR/images/arrow.outside.react.svg";
import { isMobile as isMobileDevice } from "react-device-detect";
import { classNames, ObjectUtils, DomHelpers } from "../../../utils";
import { classNames, ObjectUtils, DomHelpers, isMobile } from "../../../utils";
import { ContextMenuSkeleton } from "../../../skeletons/context-menu";
import { Scrollbar } from "../../scrollbar";
import { ToggleButton } from "../../toggle-button";
import { Scrollbar } from "../../scrollbar";
import { SubMenuItem } from "../ContextMenu.styled";
import { SubMenuItem, StyledList } from "../ContextMenu.styled";
import {
ContextMenuModel,
ContextMenuType,
SeparatorType,
} from "../ContextMenu.types";
const submenuListMargin = 4; // Indentation of the second level menu from the first level
const sectionPadding = 16; // Screen margin
const SubMenu = (props: {
model: ContextMenuModel[];
root?: boolean;
@ -57,23 +59,28 @@ const SubMenu = (props: {
) => void;
onMobileItemClick?: (
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
loadFunc: () => Promise<ContextMenuModel[]>,
label: string,
items?: ContextMenuModel[],
loadFunc?: () => Promise<ContextMenuModel[]>,
) => void;
changeView?: boolean;
onLoad?: () => Promise<ContextMenuModel[]>;
changeView?: boolean;
withHeader?: boolean;
}) => {
const {
onLeafClick,
root,
resetMenu,
changeView,
onMobileItemClick,
onLoad,
changeView,
withHeader,
} = props;
const [model, setModel] = useState(props?.model);
const [isLoading, setIsLoading] = useState(false);
const [activeItem, setActiveItem] = useState<ContextMenuType | null>(null);
const [widthSubMenu, setWidthSubMenu] = useState<null | number>(null);
const subMenuRef = useRef<HTMLUListElement>(null);
@ -92,15 +99,19 @@ const SubMenu = (props: {
e: React.MouseEvent | React.ChangeEvent<HTMLInputElement>,
item: ContextMenuType,
) => {
const { url, onClick, items, action } = item;
const { disabled, url, onClick, items, action, label } = item;
if (item.onLoad) {
if (isMobile() && label && (items || item.onLoad)) {
e.preventDefault();
if (!isMobileDevice) return;
onMobileItemClick?.(e, item.onLoad);
if (items) onMobileItemClick?.(e, label as string, items, undefined);
else if (item.onLoad)
onMobileItemClick?.(e, label as string, undefined, item.onLoad);
return;
}
if (disabled) {
e.preventDefault();
return;
}
@ -126,7 +137,9 @@ const SubMenu = (props: {
const containerOffset = DomHelpers.getOffset(parentItem);
const viewport = DomHelpers.getViewport();
const subListWidth = subMenuRef.current?.offsetParent
const options = subMenuRef.current?.getElementsByClassName("p-menuitem");
let subListWidth = subMenuRef.current?.offsetParent
? subMenuRef.current.offsetWidth
: DomHelpers.getHiddenElementOuterWidth(subMenuRef.current);
@ -136,42 +149,90 @@ const SubMenu = (props: {
const isRtl = theme.interfaceDirection === "rtl";
if (!isMobile() && options) {
const optionsWidth: number[] = [];
Array.from(options).forEach((option) =>
optionsWidth.push(Math.ceil(option.getBoundingClientRect().width)),
);
const widthMaxContent = Math.max(...optionsWidth);
if (root) subListWidth = subListWidth || widthMaxContent;
else if (!subMenuRef?.current?.style.width)
subListWidth = Math.max(subListWidth, widthMaxContent);
}
if (subMenuRef.current) {
subMenuRef.current.style.top = "0px";
let subMenuRefTop = null;
if (!isMobile()) {
if (root) subMenuRef.current.style.width = `${subListWidth}px`;
else if (!subMenuRef?.current?.style.width) {
subMenuRef.current.style.width = `${subListWidth}px`;
}
}
if (!isMobile() && !root) {
const firstList = parentItem?.firstChild as HTMLElement;
const menuItemActive = firstList.querySelector(
".p-menuitem-active",
) as HTMLElement;
const top = menuItemActive.offsetTop;
const scroller = firstList.querySelector(".scroller") as HTMLElement;
const scrollTop = scroller.scrollTop;
const positionActiveItem = top - scrollTop;
subMenuRefTop = positionActiveItem - 2;
subMenuRef.current.style.top = `${subMenuRefTop}px`;
}
const submenuRects = subMenuRef.current.getBoundingClientRect();
if (submenuRects.bottom > viewport.height) {
if (submenuRects.bottom > viewport.height && subMenuRefTop) {
const submenuMargin = 16;
const topOffset = submenuRects.bottom - viewport.height + submenuMargin;
subMenuRef.current.style.top = `${-1 * topOffset}px`;
const topOffset =
subMenuRefTop -
(submenuRects.bottom - viewport.height) -
submenuMargin;
subMenuRef.current.style.top = `${topOffset}px`;
}
const containerOffsetLeft = parseInt(`${containerOffset.left}`, 10);
const freeSpaceRight =
viewport.width - containerOffsetLeft - itemOuterWidth;
const freeSpaceLeft = containerOffsetLeft;
const submenuListMargin = 4;
const sectionPadding = 17;
if (isRtl) {
if (
!root &&
freeSpaceLeft > freeSpaceRight &&
subListWidth > containerOffsetLeft
) {
// If the menu extends beyond the screen
subMenuRef.current.style.width = `${containerOffsetLeft - submenuListMargin - sectionPadding}px`;
}
if (
subListWidth < containerOffsetLeft ||
(!root && freeSpaceLeft > freeSpaceRight)
) {
subMenuRef.current.style.left = `${-1 * subListWidth}px`;
if (!root && subListWidth > containerOffsetLeft) {
// If the menu extends beyond the screen
const newWidth =
containerOffsetLeft - submenuListMargin - sectionPadding;
subMenuRef.current.style.width = `${newWidth}px`;
setWidthSubMenu(newWidth);
}
} else {
subMenuRef.current.style.left = `${itemOuterWidth}px`;
if (!root) subMenuRef.current.style.marginLeft = `4px`;
if (!root && subListWidth > freeSpaceRight) {
// If the menu extends beyond the screen
const newWidth = freeSpaceRight - 3 * submenuListMargin;
subMenuRef.current.style.width = `${newWidth}px`;
setWidthSubMenu(newWidth);
}
}
}
@ -180,8 +241,22 @@ const SubMenu = (props: {
viewport.width - DomHelpers.calculateScrollbarWidth();
if (!isRtl) {
if (notEnoughWidthRight && containerOffsetLeft > subListWidth) {
if (notEnoughWidthRight && freeSpaceLeft > freeSpaceRight) {
subMenuRef.current.style.left = `${-1 * subListWidth}px`;
if (!root) subMenuRef.current.style.marginLeft = `-4px`;
if (
notEnoughWidthRight &&
!root &&
subListWidth > containerOffsetLeft
) {
// If the menu extends beyond the screen
const newWidth = containerOffsetLeft - 12;
subMenuRef.current.style.width = `${newWidth}px`;
setWidthSubMenu(newWidth);
}
} else {
subMenuRef.current.style.left = `${itemOuterWidth}px`;
@ -195,6 +270,7 @@ const SubMenu = (props: {
sectionPadding;
subMenuRef.current.style.width = `${newWidth}px`;
setWidthSubMenu(newWidth);
}
}
}
@ -231,29 +307,6 @@ const SubMenu = (props: {
/>
);
const renderSubMenu = (item: ContextMenuType) => {
const loaderItem = {
id: "link-loader-option",
key: "link-loader",
isLoader: true,
label: <ContextMenuSkeleton />,
};
if (item.items || item.onLoad) {
return (
<SubMenu
model={item.onLoad ? [loaderItem] : item.items || []}
resetMenu={item !== activeItem}
onLeafClick={onLeafClick}
// onEnter={onEnter}
onLoad={item.onLoad}
/>
);
}
return null;
};
const renderMenuitem = (
item: ContextMenuType,
index: number,
@ -301,7 +354,7 @@ const SubMenu = (props: {
const subMenuIcon = (item.items || item.onLoad) && (
<ArrowIcon className={subMenuIconClassName} />
);
const subMenu = renderSubMenu(item);
const dataKeys = Object.fromEntries(
Object.entries(item).filter((el) => el[0].indexOf("data-") === 0),
);
@ -367,7 +420,6 @@ const SubMenu = (props: {
onMouseEnter={(e) => onItemMouseEnter(e, item)}
>
{content}
{subMenu}
<ToggleButton
isChecked={item.checked || false}
onChange={onClick}
@ -389,7 +441,6 @@ const SubMenu = (props: {
onMouseEnter={(e) => onItemMouseEnter(e, item)}
>
{content}
{subMenu}
</li>
);
};
@ -428,78 +479,100 @@ const SubMenu = (props: {
};
const renderMenu = () => {
if (model) {
if (changeView) {
const newModel = model.filter(
(item: ContextMenuModel) => item && !item.disabled,
if (!model) return null;
return model.map((item: ContextMenuModel, index: number) => {
if (item?.disabled) return null;
return renderItem(item, index);
});
};
const renderSubMenuLower = () => {
if (!model) return null;
const submenu: JSX.Element[] = [];
const loaderItem = {
id: "link-loader-option",
key: "link-loader",
isLoader: true,
label: <ContextMenuSkeleton />,
};
model.forEach((item) => {
const contextMenuTypeItem = item as ContextMenuType;
if (contextMenuTypeItem?.items || contextMenuTypeItem?.onLoad) {
submenu.push(
<SubMenu
key={`sub-menu_${item.id}`}
model={
contextMenuTypeItem?.onLoad
? [loaderItem]
: contextMenuTypeItem?.items || []
}
resetMenu={item !== activeItem}
onLeafClick={onLeafClick}
onLoad={contextMenuTypeItem?.onLoad}
/>,
);
const rowHeights: number[] = newModel.map((item: ContextMenuModel) => {
if (!item) return 0;
if (item.isSeparator) return 13;
return 36;
});
// const getItemSize = (index) => rowHeights[index];
const height = rowHeights.reduce((a, b) => a + b);
const viewport = DomHelpers.getViewport();
const listHeight =
height + 61 > viewport.height - 64
? viewport.height - 125
: height + 5;
return (
<Scrollbar style={{ height: listHeight }}>
{model.map((item: ContextMenuModel, index: number) => {
if (!item || item?.disabled) return null;
return renderItem(item, index);
})}
</Scrollbar>
);
// return (
// <VariableSizeList
// height={listHeight}
// width={"auto"}
// itemCount={newModel.length}
// itemSize={getItemSize}
// itemData={newModel}
// outerElementType={CustomScrollbarsVirtualList}
// >
// {renderItem}
// </VariableSizeList>
// );
}
});
return model.map((item: ContextMenuModel, index: number) => {
if (item?.disabled) return null;
return renderItem(item, index);
});
}
return null;
return submenu;
};
const className = classNames({ "p-submenu-list": !root });
const submenu = renderMenu();
const active = isActive();
const submenuLower = renderSubMenuLower();
return (
<CSSTransition
nodeRef={subMenuRef}
classNames="p-contextmenusub"
in={active}
timeout={{ enter: 0, exit: 0 }}
unmountOnExit
onEnter={onEnter}
>
<ul ref={subMenuRef} className={`${className} not-selectable`}>
{submenu}
</ul>
</CSSTransition>
);
if (model.length) {
const newModel = model.filter(
(item: ContextMenuModel) => item && !item.disabled,
);
const rowHeights: number[] = newModel.map((item: ContextMenuModel) => {
if (!item) return 0;
if (item.isSeparator) return 13;
return 36;
});
const height = rowHeights.reduce((a, b) => a + b);
const viewport = DomHelpers.getViewport();
const paddingList = 12;
const marginsList = 32;
const backdrop = 64;
const header = 55;
const listHeight =
changeView && withHeader
? height + paddingList + header > viewport.height
? viewport.height - backdrop - header - paddingList
: height + paddingList
: height + paddingList + marginsList > viewport.height
? viewport.height - marginsList
: height + paddingList;
return (
<CSSTransition
nodeRef={subMenuRef}
classNames="p-contextmenusub"
in={active}
timeout={{ enter: 0, exit: 0 }}
unmountOnExit
onEnter={onEnter}
>
<StyledList
ref={subMenuRef}
className={`${className} not-selectable`}
listHeight={height + paddingList}
widthSubMenu={widthSubMenu}
>
<Scrollbar style={{ height: listHeight }}>{submenu}</Scrollbar>
{submenuLower}
</StyledList>
</CSSTransition>
);
}
};
export { SubMenu };

View File

@ -94,7 +94,7 @@ const StyledDropdown = styled.div<{
border: ${(props) => props.theme.dropDown.border};
border-radius: ${(props) => props.theme.dropDown.borderRadius};
-moz-border-radius: ${(props) => props.theme.dropDown.borderRadius};
-webkit-border-radius: ${(props) => props.theme.dropDown.borderRadius};dropDownMaxHeight
-webkit-border-radius: ${(props) => props.theme.dropDown.borderRadius};
box-shadow: ${(props) => props.theme.dropDown.boxShadow};
-moz-box-shadow: ${(props) => props.theme.dropDown.boxShadow};
-webkit-box-shadow: ${(props) => props.theme.dropDown.boxShadow};

View File

@ -42,7 +42,7 @@ const StyledErrorContainer = styled.div<{ isEditor: boolean }>`
css`
position: absolute;
`}
overflow-x: hidden;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;

View File

@ -996,6 +996,7 @@ export const ImageViewer = ({
onDoubleClick={handleDoubleTapOrClick}
onLoad={imageLoaded}
onError={onError}
onContextMenu={(event) => event.preventDefault()}
/>
</ImageWrapper>
</ImageViewerContainer>

View File

@ -36,7 +36,9 @@ export const PlayerBigPlayButton = ({
if (!visible) return;
return (
<WrapperPlayerBigPlayButton>
<WrapperPlayerBigPlayButton
onContextMenu={(event) => event.preventDefault()}
>
<BigIconPlay onClick={onClick} />
</WrapperPlayerBigPlayButton>
);

View File

@ -406,6 +406,10 @@ export const ViewerPlayer = ({
const percent = Number(event.target.value);
const newCurrentTime = (percent / 100) * videoRef.current.duration;
const videoCurrentTime = videoRef.current.currentTime;
if (Math.abs(newCurrentTime - videoCurrentTime) <= 0.1) return;
handleProgress();
setTimeline(percent);
setCurrentTime(newCurrentTime);
@ -653,6 +657,7 @@ export const ViewerPlayer = ({
onDurationChange={handleDurationChange}
onLoadedMetadata={handleLoadedMetaDataVideo}
onPlay={() => setIsPlaying(true)}
onContextMenu={(event) => event.preventDefault()}
/>
<PlayerBigPlayButton
onClick={handleBigPlayButtonClick}

View File

@ -79,7 +79,8 @@ const ControlButtons = ({
onClick={onNavigationButtonClick}
/>
) : null;
const children = tariffBar ? React.cloneElement(tariffBar, { title }) : null;
const children =
tariffBar && !isFrame ? React.cloneElement(tariffBar, { title }) : null;
const isTabletView = isTablet();
const contextOptionsFolder = getContextOptionsFolder();

View File

@ -63,7 +63,7 @@ const AccessSelector = (props: AccessSelectorProps) => {
options={accessRights as TOption[]}
size={ComboBoxSize.content}
scaled={false}
manualWidth="fit-content"
manualWidth="auto"
selectedOption={selectedAccessRight as TOption}
showDisabledItems
directionX="right"
@ -81,7 +81,7 @@ const AccessSelector = (props: AccessSelectorProps) => {
directionX="right"
directionY="top"
fixedDirection={isMobileView}
manualWidth={isMobileView ? "fit-content" : `${width}px`}
manualWidth={isMobileView ? "auto" : `${width}px`}
isAside={isMobileView}
manualY={isMobileView ? "0px" : undefined}
withoutBackground={isMobileView}

View File

@ -124,7 +124,9 @@ const Body = ({
const onBodyResize = React.useCallback(() => {
if (bodyRef && bodyRef.current) {
setBodyHeight(bodyRef.current.offsetHeight);
setTimeout(() => {
setBodyHeight(bodyRef.current!.offsetHeight);
}, 20);
}
}, []);

View File

@ -480,7 +480,7 @@ export const enum ParseErrorTypes {
}
export const enum ButtonKeys {
enter = "enter",
enter = "Enter",
numpadEnter = "NumpadEnter",
esc = "Escape",
tab = "Tab",

View File

@ -2511,9 +2511,8 @@ export const getBaseTheme = () => {
},
separator: {
borderBottom: `1px solid ${grayLightMid} !important`,
margin: "6px 16px 6px 16px !important",
margin: "6px 16px !important",
height: "1px !important",
width: "calc(100% - 32px) !important",
},
text: {
header: {
@ -2534,7 +2533,7 @@ export const getBaseTheme = () => {
background: "none",
svgFill: black,
header: {
height: "49px",
height: "55px",
borderBottom: `1px solid ${grayLightMid}`,
marginBottom: "6px",
},
@ -2544,7 +2543,7 @@ export const getBaseTheme = () => {
padding: "0 12px",
mobile: {
height: "36px",
padding: "0 16px 6px",
padding: "6px 16px",
},
},
newContextMenu: {
@ -2552,7 +2551,6 @@ export const getBaseTheme = () => {
borderRadius: "6px",
mobileBorderRadius: "6px 6px 0 0",
boxShadow: `0px 8px 16px 0px ${boxShadowColor}`,
padding: "6px 0px",
border: "none",
devices: {
maxHeight: "calc(100vh - 64px)",

View File

@ -663,7 +663,7 @@ const Dark: TTheme = {
input: {
color: white,
disableColor: grayDarkStrong,
disableColor: grayDarkText,
backgroundColor: black,
disableBackgroundColor: grayDarkStrong,
@ -2498,7 +2498,6 @@ const Dark: TTheme = {
borderBottom: `1px solid ${grayDarkStrong} !important`,
margin: "6px 16px 6px 16px !important",
height: "1px !important",
width: "calc(100% - 32px) !important",
},
text: {
header: {
@ -2519,7 +2518,7 @@ const Dark: TTheme = {
background: "none",
svgFill: white,
header: {
height: "49px",
height: "55px",
borderBottom: `1px solid ${grayDarkStrong}`,
marginBottom: "6px",
},
@ -2529,7 +2528,7 @@ const Dark: TTheme = {
padding: "0 12px",
mobile: {
height: "36px",
padding: "0 16px 6px",
padding: "6px 16px",
},
},
newContextMenu: {
@ -2537,7 +2536,6 @@ const Dark: TTheme = {
borderRadius: "6px",
mobileBorderRadius: "6px 6px 0 0",
boxShadow: `0px 8px 16px 0px ${boxShadowDarkColor}`,
padding: "6px 0px",
border: `1px solid ${grayDarkStrong}`,
devices: {
maxHeight: "calc(100vh - 64px)",

View File

@ -75,6 +75,7 @@ export type TFrameEvents = {
onAuthSuccess: null | ((e: Event) => void);
onSignOut: null | ((e: Event) => void);
onDownload: null | ((e: Event) => void);
onNoAccess: null | ((e: Event) => void);
};
export type TFrameConfig = {
@ -96,6 +97,7 @@ export type TFrameConfig = {
showSelectorCancel: boolean;
showSelectorHeader: boolean;
showHeader: boolean;
showHeaderBanner: string;
showTitle: boolean;
showMenu: boolean;
showFilter: boolean;

View File

@ -182,11 +182,11 @@ class AxiosClient {
}
};
request = (
request = <T>(
options: TReqOption & AxiosRequestConfig,
skipRedirect = false,
isOAuth = false,
) => {
): Promise<T> | undefined => {
const onSuccess = (response: TRes) => {
const error = this.getResponseError(response);
@ -237,10 +237,12 @@ class AxiosClient {
}
const loginURL = combineUrl(proxyURL, "/login");
if (!this.isSSR) {
switch (error.response?.status) {
case 401: {
if (options.skipUnauthorized) return Promise.resolve();
if (options.skipUnauthorized || window?.ClientConfig?.isFrame)
return Promise.resolve();
if (options.skipLogout) return Promise.reject(error);
const opt: AxiosRequestConfig = {
@ -261,14 +263,13 @@ class AxiosClient {
break;
case 403: {
const pathname = window.location.pathname;
const isFrame = window?.ClientConfig?.isFrame;
const isArchived = pathname.indexOf("/rooms/archived") !== -1;
const isRooms =
pathname.indexOf("/rooms/shared") !== -1 || isArchived;
if (isRooms && !skipRedirect && !isFrame) {
if (isRooms && !skipRedirect && !window?.ClientConfig?.isFrame) {
setTimeout(() => {
window.DocSpace.navigate(isArchived ? "/archived" : "/");
}, 1000);
@ -295,7 +296,9 @@ class AxiosClient {
return Promise.reject(error);
};
return this.client?.(options).then(onSuccess).catch(onError);
return this.client?.(options).then(onSuccess).catch(onError) as
| Promise<T>
| undefined;
};
}

View File

@ -150,3 +150,10 @@ export type IClientListProps = List<IClientProps>;
export type IClientListDTO = List<IClientResDTO>;
export type TConsentList = List<TConsentData>;
export type TGenerateDeveloperToken = {
access_token: string;
expires_in: number;
scope: string;
token_type: string;
};

View File

@ -53,7 +53,7 @@
showSignOut: true,
destroyText: "",
viewAs: "row", //TODO: ["row", "table", "tile"]
viewTableColumns: "Name,Type,Tags",
viewTableColumns: "Name,Size,Type,Tags",
checkCSP: true,
disableActionButton: false,
showSettings: false,
@ -97,6 +97,8 @@
onAuthSuccess: null,
onSignOut: null,
onDownload: null,
onNoAccess: null,
onContentReady: null,
},
};
@ -813,7 +815,10 @@
targetFrame.style.height = this.config.height;
targetFrame.parentNode.style.height = "inherit";
if (loader) loader.remove();
if (loader) {
loader.remove();
this.config.events.onContentReady();
}
}
}