Merge branch 'hotfix/v2.6.1' into develop

# Conflicts:
#	packages/client/src/pages/PortalSettings/Layout/Section/Header/index.js
#	packages/client/src/pages/PortalSettings/categories/data-import/NextCloudWorkspace/Stepper/SelectUsersStep/index.js
#	packages/client/src/pages/PortalSettings/categories/data-import/components/AddEmailsStep/AccountsTable/RowView/UsersRowContent.tsx
#	packages/client/src/pages/PortalSettings/utils/settingsTree.js
This commit is contained in:
Alexey Safronov 2024-07-29 16:13:17 +04:00
commit 602ce53411
74 changed files with 932 additions and 445 deletions

View File

@ -10,13 +10,16 @@
"ExpectUsers": "Expect users",
"FeedLinkWasDeleted": "Link was deleted",
"FeedLocationLabel": "Folder «{{folderTitle}}»",
"FeedLocationLabelFrom": "from «{{folderTitle}}»",
"FeedLocationRoomLabel": "Room «{{folderTitle}}»",
"FileConverted": "File converted.",
"FileCopied": "Files copied.",
"FileCopiedTo": "Files copied to «{{folderTitle}}»",
"FileCreated": "File created.",
"FileDeleted": "Files removed.",
"FileExtension": "File extension",
"FileMoved": "Files moved.",
"FileMovedTo": "File moved to «{{folderTitle}}»",
"FileRenamed": "File renamed.",
"FilesEmptyScreenText": "See file and folder details here",
"FileUploaded": "Files added.",

View File

@ -424,6 +424,7 @@ export default inject(
isRestore,
isPanelVisible,
id,
currentFolderId: currentFolderIdProp,
}: FilesSelectorProps,
) => {
const { id: selectedId, parentId, rootFolderType } = selectedFolderStore;
@ -551,7 +552,7 @@ export default inject(
getIcon,
roomsFolderId,
currentFolderId: folderId,
currentFolderId: folderId || currentFolderIdProp,
filesSettings,
};
},

View File

@ -29,4 +29,8 @@ import { ModalDialog } from "@docspace/shared/components/modal-dialog";
export const StyledModal = styled(ModalDialog)`
user-select: none;
.modal-body {
padding-top: 20px;
}
`;

View File

@ -24,8 +24,15 @@
// 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
export const DEFAULT_MIN_COLUMN_SIZE = 110;
export const SETTINGS_SIZE = 24;
export const CONTAINER_MARGIN = 25;
export const MIN_SIZE_FIRST_COLUMN = 210;
export const TABLE_HEADER_HEIGHT = 40;
import styled from "styled-components";
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
export const StyledModalDialog = styled(ModalDialog)`
.modal-body {
padding: 0;
}
.search-input {
margin: 16px 16px 12px;
}
`;

View File

@ -24,23 +24,32 @@
// 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 { inject, observer } from "mobx-react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ModalDialog,
ModalDialogType,
} from "@docspace/shared/components/modal-dialog";
import { observer, inject } from "mobx-react";
import { useState, useEffect, useTransition } from "react";
import { getGroupMembersInRoom } from "@docspace/shared/api/groups";
import { useTranslation } from "react-i18next";
import { InputSize } from "@docspace/shared/components/text-input";
import { SearchInput } from "@docspace/shared/components/search-input";
import GroupMember from "./GroupMember";
import {
TGroup,
TGroupMemberInvitedInRoom,
} from "@docspace/shared/api/groups/types";
import EmptyContainer from "./EmptyContainer";
import GroupMembersList from "./sub-components/GroupMembersList/GroupMembersList";
import { StyledModalDialog } from "./EditGroupMembersDialog.styled";
import { ModalBodyLoader } from "./sub-components/ModalBodyLoader/ModalBodyLoader";
import { MIN_LOADER_TIMER } from "@docspace/shared/selectors/Files/FilesSelector.constants";
interface EditGroupMembersProps {
visible: boolean;
setVisible: (visible: boolean) => void;
group: any;
group: TGroup;
infoPanelSelection: any;
}
@ -53,33 +62,63 @@ const EditGroupMembers = ({
const { t } = useTranslation(["Common"]);
const [searchValue, setSearchValue] = useState<string>("");
const onChangeSearchValue = (newValue: string) => {
setSearchValue(newValue);
};
const onClearSearch = () => onChangeSearchValue("");
const [total, setTotal] = useState(0);
const [groupMembers, setGroupMembers] = useState<
TGroupMemberInvitedInRoom[] | null
>(null);
const [isSearchResultLoading, setIsSearchResultLoading] = useState(false);
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
const [groupMembers, setGroupMembers] = useState<any[] | null>(null);
const filteredGroupMembers = groupMembers?.filter((groupMember) =>
groupMember.user.displayName.includes(searchValue),
);
const [, startTransition] = useTransition();
const onChangeSearchValue = (value: string) => {
setIsSearchResultLoading(true);
setSearchValue(value.trim());
};
const onClearSearch = () => onChangeSearchValue("");
const onClose = () => setVisible(false);
const isSearchListEmpty =
filteredGroupMembers && !filteredGroupMembers.length;
const hasMembers = filteredGroupMembers && filteredGroupMembers.length !== 0;
const loadNextPage = async (startIndex: number) => {
const startLoadingTime = new Date();
try {
setIsNextPageLoading(true);
const filter = { startIndex, count: 100, filterValue: searchValue };
const data = await getGroupMembersInRoom(
infoPanelSelection.id,
group.id,
filter,
);
setTotal(data.total);
if (startIndex === 0 || !groupMembers) {
setGroupMembers(data.items);
} else {
setGroupMembers([...groupMembers, ...data.items]);
}
} catch (e) {
console.error(e);
} finally {
const nowDate = new Date();
const diff = Math.abs(nowDate.getTime() - startLoadingTime.getTime());
if (diff < MIN_LOADER_TIMER) {
setTimeout(() => {
setIsSearchResultLoading(false);
}, MIN_LOADER_TIMER - diff);
} else {
setIsSearchResultLoading(false);
}
setIsNextPageLoading(false);
// setIsSearchResultLoading(false);
}
};
useEffect(() => {
const fetchGroup = async () => {
if (!group) return;
getGroupMembersInRoom(infoPanelSelection.id, group.id)!
.then((data: any) => startTransition(() => setGroupMembers(data.items)))
.catch((err: any) => console.error(err));
};
fetchGroup();
}, [group, infoPanelSelection.id]);
loadNextPage(0);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchValue]);
if (!infoPanelSelection?.isRoom) {
onClose();
@ -87,7 +126,7 @@ const EditGroupMembers = ({
}
return (
<ModalDialog
<StyledModalDialog
visible={visible}
onClose={onClose}
displayType={ModalDialogType.aside}
@ -95,29 +134,40 @@ const EditGroupMembers = ({
<ModalDialog.Header>{group.name}</ModalDialog.Header>
<ModalDialog.Body>
<SearchInput
className="search-input"
placeholder={t("PeopleTranslations:SearchByGroupMembers")}
value={searchValue}
onChange={onChangeSearchValue}
onClearSearch={onClearSearch}
size={InputSize.base}
/>
{!groupMembers ? (
<ModalBodyLoader withSearch />
) : (
<>
<SearchInput
className="search-input"
placeholder={t("PeopleTranslations:SearchByGroupMembers")}
value={searchValue}
onChange={onChangeSearchValue}
onClearSearch={onClearSearch}
size={InputSize.base}
/>
<div style={{ height: "12px", width: "100%" }} />
{isSearchListEmpty && <EmptyContainer />}
{hasMembers &&
filteredGroupMembers.map(({ user, ...rest }) => (
<GroupMember t={t} key={user.id} user={{ ...user, ...rest }} />
))}
{isSearchResultLoading ? (
<ModalBodyLoader withSearch={false} />
) : !groupMembers.length ? (
<EmptyContainer />
) : (
<GroupMembersList
members={groupMembers}
loadNextPage={loadNextPage}
hasNextPage={groupMembers.length < total}
total={total}
isNextPageLoading={isNextPageLoading}
/>
)}
</>
)}
</ModalDialog.Body>
</ModalDialog>
</StyledModalDialog>
);
};
export default inject(({ infoPanelStore, userStore, dialogsStore }) => ({
export default inject(({ infoPanelStore, userStore, dialogsStore }: any) => ({
infoPanelSelection: infoPanelStore.infoPanelSelection,
selfId: userStore.user.id,
group: dialogsStore.editMembersGroup,

View File

@ -31,7 +31,7 @@ export const GroupMember = styled.div<{ isExpect: boolean }>`
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
padding: 8px 16px;
.avatar {
min-width: 32px;

View File

@ -24,39 +24,47 @@
// 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 { useState } from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { isMobile, isMobileOnly } from "react-device-detect";
import AtReactSvgUrl from "PUBLIC_DIR/images/@.react.svg?url";
// import { StyledUser } from "../../styles/members";
import { Avatar } from "@docspace/shared/components/avatar";
import { ComboBox } from "@docspace/shared/components/combobox";
import DefaultUserPhotoUrl from "PUBLIC_DIR/images/default_user_photo_size_82-82.png";
import { isMobileOnly, isMobile } from "react-device-detect";
import { decode } from "he";
import { filterUserRoleOptions } from "SRC_DIR/helpers";
import { Text } from "@docspace/shared/components/text";
import * as Styled from "./index.styled";
import { getUserRoleOptionsByUserAccess } from "@docspace/shared/utils/room-members/getUserRoleOptionsByUserAccess";
import { getUserRoleOptionsByRoomType } from "@docspace/shared/utils/room-members/getUserRoleOptionsByRoomType";
import { updateRoomMemberRole } from "@docspace/shared/api/rooms";
import { toastr } from "@docspace/shared/components/toast";
import { useState } from "react";
import { HelpButton } from "@docspace/shared/components/help-button";
import { getUserRoleOptions } from "@docspace/shared/utils/room-members/getUserRoleOptions";
import { ShareAccessRights } from "@docspace/shared/enums";
import { getUserRole, getUserTypeLabel } from "@docspace/shared/utils/common";
import { getUserRole } from "@docspace/shared/utils/common";
import { TGroupMemberInvitedInRoom } from "@docspace/shared/api/groups/types";
import * as Styled from "./index.styled";
interface GroupMemberProps {
t: any;
user: any;
member: TGroupMemberInvitedInRoom;
infoPanelSelection: any;
}
const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
const GroupMember = ({ member, infoPanelSelection }: GroupMemberProps) => {
const { user } = member;
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation("Common");
const userRole = user.isOwner
? getUserRoleOptions(t).portalAdmin
: getUserRoleOptionsByUserAccess(t, user.userAccess || user.groupAccess);
: getUserRoleOptionsByUserAccess(
t,
member.userAccess || member.groupAccess,
);
const fullRoomRoleOptions = getUserRoleOptionsByRoomType(
t,
@ -86,7 +94,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
else
selectedUserRoleCBOption = getUserRoleOptionsByUserAccess(
t,
user.userAccess || user.groupAccess,
member.userAccess || member.groupAccess,
);
const availableUserRoleCBOptions = filterUserRoleOptions(
@ -101,7 +109,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
notify: false,
sharingMessage: "",
})
.then(() => (user.userAccess = userRoleOption.access))
.then(() => (member.userAccess = userRoleOption.access))
.catch((err) => toastr.error(err))
.finally(() => setIsLoading(false));
};
@ -134,8 +142,8 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
</div>
<div className="individual-rights-tooltip">
{user.userAccess &&
user.userAccess !== user.groupAccess &&
{member.userAccess &&
member.userAccess !== member.groupAccess &&
!user.isOwner && (
<HelpButton
place="left"
@ -152,7 +160,7 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
{userRole && userRoleOptions && (
<div className="role-wrapper">
{user.canEditAccess && !user.isOwner ? (
{member.canEditAccess && !user.isOwner ? (
<ComboBox
className="role-combobox"
selectedOption={userRole}
@ -180,6 +188,6 @@ const GroupMember = ({ t, user, infoPanelSelection }: GroupMemberProps) => {
);
};
export default inject(({ infoPanelStore }) => ({
export default inject(({ infoPanelStore }: any) => ({
infoPanelSelection: infoPanelStore.infoPanelSelection,
}))(observer(GroupMember));

View File

@ -0,0 +1,126 @@
// (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, { memo, useCallback } from "react";
import { useTheme } from "styled-components";
import { areEqual, FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import InfiniteLoader from "react-window-infinite-loader";
import { Scrollbar } from "@docspace/shared/components/scrollbar";
import { RowLoader } from "@docspace/shared/skeletons/selector";
import { TGroupMemberInvitedInRoom } from "@docspace/shared/api/groups/types";
import GroupMember from "../GroupMember";
const ROW_HEIGHT = 48;
const SEARCH_WITH_PADDING_HEIGHT = 60;
const Row = memo(
({
data,
index,
style,
}: {
data: TGroupMemberInvitedInRoom[];
index: number;
style: React.CSSProperties;
}) => {
const member = data[index];
return (
<div style={style}>
{member ? (
<GroupMember member={member} />
) : (
<RowLoader isMultiSelect={false} isContainer isUser />
)}
</div>
);
},
areEqual,
);
Row.displayName = "Row";
interface GroupMembersListProps {
members: TGroupMemberInvitedInRoom[];
loadNextPage: (startIndex: number) => void;
hasNextPage: boolean;
total: number;
isNextPageLoading: boolean;
}
const GroupMembersList = ({
members,
loadNextPage,
hasNextPage,
total,
isNextPageLoading,
}: GroupMembersListProps) => {
const { interfaceDirection } = useTheme();
const itemCount = hasNextPage ? members.length + 1 : members.length;
const isItemLoaded = useCallback(
(index: number) => {
return !hasNextPage || index < itemCount - 1;
},
[hasNextPage, itemCount],
);
const loadMoreItems = isNextPageLoading ? () => {} : loadNextPage;
return (
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={isItemLoaded}
itemCount={total}
loadMoreItems={loadMoreItems}
>
{({ onItemsRendered, ref }) => (
<List
ref={ref}
direction={interfaceDirection}
height={height - SEARCH_WITH_PADDING_HEIGHT}
width={width}
itemCount={itemCount}
itemSize={ROW_HEIGHT}
itemData={members}
outerElementType={Scrollbar}
onItemsRendered={onItemsRendered}
>
{Row}
</List>
)}
</InfiniteLoader>
)}
</AutoSizer>
);
};
export default GroupMembersList;

View File

@ -0,0 +1,44 @@
// (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 { SearchLoader } from "@docspace/shared/skeletons/selector";
import React from "react";
import { MemberLoader } from "@docspace/shared/skeletons/info-panel/body/views/MembersLoader";
interface ModalBodyLoaderProps {
withSearch: boolean;
}
export const ModalBodyLoader = ({ withSearch }: ModalBodyLoaderProps) => {
return (
<div style={{ paddingTop: withSearch ? "16px" : "0" }}>
{withSearch && <SearchLoader />}
<div style={{ paddingInline: "16px" }}>
<MemberLoader count={25} />
</div>
</div>
);
};

View File

@ -24,7 +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 { useState, useEffect } from "react";
import { useState } from "react";
import { inject, observer } from "mobx-react";
import styled, { css } from "styled-components";
import { Aside } from "@docspace/shared/components/aside";
@ -83,20 +83,12 @@ const ChangeRoomOwner = (props) => {
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
document.addEventListener("keyup", onKeyUp, false);
return () => {
document.removeEventListener("keyup", onKeyUp, false);
};
}, []);
const onKeyUp = (e) => {
if (e.keyCode === 27) onClose();
if (e.keyCode === 13 || e.which === 13) onChangeRoomOwner();
};
const onChangeRoomOwner = async (user, isChecked) => {
const onChangeRoomOwner = async (
user,
selectedAccess,
newFooterInputValue,
isChecked,
) => {
if (showBackButton) {
setRoomParams && setRoomParams(user[0]);
} else {

View File

@ -96,14 +96,8 @@ const StyledFileRow = styled(Row)`
.password-input {
position: absolute;
top: 48px;
${(props) =>
props.theme.interfaceDirection === "rtl"
? css`
right: 16px;
`
: css`
left: 16px;
`}
left: 0px;
right: 0px;
max-width: 470px;
width: calc(100% - 16px);
display: flex;

View File

@ -33,6 +33,9 @@ import { useTheme } from "styled-components";
const StyledLoadErrorIcon = styled(LoadErrorIcon)`
outline: none !important;
path {
fill: ${(props) => props.theme.filesPanels.upload.iconColor};
}
`;
const ErrorFileUpload = ({

View File

@ -0,0 +1,27 @@
import { getLanguage } from "@docspace/shared/utils";
import { translations } from "./autoGeneratedTranslations";
export function loadLanguagePath(homepage, fixedNS = null) {
return (lng, ns) => {
const language = getLanguage(lng instanceof Array ? lng[0] : lng);
const lngCollection = translations?.get(language);
const data = lngCollection?.get(`${fixedNS || ns}`);
if (!data) return `/locales/${language}/${fixedNS || ns}.json`;
let path = data?.split("/");
const length = path?.length;
const isCommonPath = path[length - 1].indexOf("Common") > -1;
path = `/${path[length - 3]}/${path[length - 2]}/${path[length - 1]}`;
if (ns.length > 0 && ns[0] === "Common" && isCommonPath) {
return `/static${path}`;
}
return path;
};
}

View File

@ -25,12 +25,10 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { authStore } from "@docspace/shared/store";
import { getLanguage } from "@docspace/shared/utils";
import { toCommunityHostname } from "@docspace/shared/utils/common";
import { CategoryType } from "./constants";
import { FolderType, ShareAccessRights } from "@docspace/shared/enums";
import { translations } from "./autoGeneratedTranslations";
import { FolderType } from "@docspace/shared/enums";
// import router from "SRC_DIR/router";
import i18n from "../i18n";
@ -54,31 +52,6 @@ export const setDocumentTitle = (subTitle = "") => {
document.title = title;
};
export function loadLanguagePath(homepage, fixedNS = null) {
return (lng, ns) => {
const language = getLanguage(lng instanceof Array ? lng[0] : lng);
const lngCollection = translations?.get(language);
const data = lngCollection?.get(`${fixedNS || ns}`);
if (!data) return `/locales/${language}/${fixedNS || ns}.json`;
let path = data?.split("/");
const length = path?.length;
const isCommonPath = path[length - 1].indexOf("Common") > -1;
path = `/${path[length - 3]}/${path[length - 2]}/${path[length - 1]}`;
if (ns.length > 0 && ns[0] === "Common" && isCommonPath) {
return `/static${path}`;
}
return path;
};
}
export const checkIfModuleOld = (link) => {
if (
!link ||

View File

@ -31,7 +31,7 @@ import config from "PACKAGE_FILE";
import { LANGUAGE } from "@docspace/shared/constants";
import { getCookie } from "@docspace/shared/utils";
import { loadLanguagePath } from "./helpers/utils";
import { loadLanguagePath } from "./helpers/language-helpers";
const newInstance = i18n.createInstance();

View File

@ -32,7 +32,7 @@ import FilesFilter from "@docspace/shared/api/files/filter";
import RoomsFilter from "@docspace/shared/api/rooms/filter";
import { getGroup } from "@docspace/shared/api/groups";
import { getUserById } from "@docspace/shared/api/people";
import { MEDIA_VIEW_URL } from "@docspace/shared/constants";
import { CREATED_FORM_KEY, MEDIA_VIEW_URL } from "@docspace/shared/constants";
import {
Events,
@ -78,6 +78,7 @@ const useFiles = ({
scrollToTop,
selectedFolderStore,
wsCreatedPDFForm,
}) => {
const navigate = useNavigate();
const { id } = useParams();
@ -292,7 +293,15 @@ const useFiles = ({
);
} else {
const folderId = filter.folder;
return fetchFiles(folderId, filter);
return fetchFiles(folderId, filter)?.finally(() => {
const data = sessionStorage.getItem(CREATED_FORM_KEY);
if (data) {
wsCreatedPDFForm({
data,
});
sessionStorage.removeItem(CREATED_FORM_KEY);
}
});
}
}
@ -306,7 +315,7 @@ const useFiles = ({
const isFormRoom =
selectedFolderStore.roomType === RoomsType.FormRoom ||
selectedFolderStore.type === FolderType.FormRoom;
selectedFolderStore.parentRoomType === FolderType.FormRoom;
const payload = {
extension: "pdf",

View File

@ -122,7 +122,8 @@ const StyledHistoryBlockMessage = styled.div`
overflow: hidden;
}
.folder-label {
.folder-label,
.source-folder-label {
max-width: 100%;
color: ${(props) => props.theme.infoPanel.history.locationIconColor};
text-overflow: ellipsis;
@ -130,6 +131,10 @@ const StyledHistoryBlockMessage = styled.div`
overflow: hidden;
}
.source-folder-label {
color: ${(props) => props.theme.infoPanel.history.messageColor};
}
.old-role {
color: ${(props) => props.theme.infoPanel.history.oldRoleColor};
font-weight: 600;

View File

@ -40,24 +40,38 @@ const HistoryMainTextFolderInfo = ({
feed,
selectedFolderId,
}: HistoryMainTextFolderInfoProps) => {
if (
feed.data.parentId === selectedFolderId ||
feed.data.toFolderId === selectedFolderId
)
const {
parentId,
toFolderId,
parentTitle,
parentType,
fromParentType,
fromParentTitle,
} = feed.data;
if (parentId === selectedFolderId || toFolderId === selectedFolderId)
return null;
const destination =
feed.data.parentType === 0 || feed.data.toParentType === 0
? t("FeedLocationLabel", {
folderTitle: feed.data.parentTitle || feed.data.toFolderTitle,
})
: t("FeedLocationRoomLabel", {
folderTitle: feed.data.parentTitle || feed.data.toFolderTitle,
});
if (!parentTitle) return null;
const isFolder = parentType === 0;
const isFromFolder = fromParentType === 0;
const destination = isFolder
? t("FeedLocationLabel", { folderTitle: parentTitle })
: t("FeedLocationRoomLabel", { folderTitle: parentTitle });
const sourceDestination = isFromFolder
? t("FeedLocationLabelFrom", { folderTitle: fromParentTitle })
: t("FeedLocationRoomLabel", { folderTitle: parentTitle });
const className = isFromFolder ? "source-folder-label" : "folder-label";
return (
<StyledHistoryBlockMessage className="message">
<span className="folder-label">{destination}</span>
<span className={className}>
{isFromFolder ? sourceDestination : destination}
</span>
</StyledHistoryBlockMessage>
);
};

View File

@ -114,7 +114,7 @@ export default inject<TStore>(({ dialogsStore, infoPanelStore }) => {
const { infoPanelSelection } = infoPanelStore;
const { setLinkParams, setEditLinkPanelIsVisible } = dialogsStore;
const { id, roomType, security } = infoPanelSelection!;
const { EditRoom } = security!;
const { EditRoom } = security || {};
const isFormRoom = roomType === RoomsType.FormRoom;

View File

@ -18,8 +18,18 @@ export const useFeedTranslation = (
case "FileRenamed":
return t("InfoPanel:FileRenamed");
case "FileMoved":
if (feed.data.fromParentTitle) {
return t("InfoPanel:FileMovedTo", {
folderTitle: feed.data.parentTitle,
});
}
return t("InfoPanel:FileMoved");
case "FileCopied":
if (feed.data.fromParentTitle) {
return t("InfoPanel:FileCopiedTo", {
folderTitle: feed.data.parentTitle,
});
}
return t("InfoPanel:FileCopied");
case "FileDeleted":
return t("InfoPanel:FileDeleted");

View File

@ -61,6 +61,8 @@ class GroupsTableHeader extends React.Component {
];
const columns = props.getColumns(defaultColumns);
const tableColumns = columns.map((c) => c.enable && c.key);
this.setTableColumns(tableColumns);
this.state = { columns };
}
@ -77,7 +79,7 @@ class GroupsTableHeader extends React.Component {
this.setState({ columns });
const tableColumns = columns.map((c) => c.enable && c.key);
localStorage.setItem(`${TABLE_COLUMNS}=${this.props.userId}`, tableColumns);
this.setTableColumns(tableColumns);
const event = new Event(Events.CHANGE_COLUMN);
window.dispatchEvent(event);
@ -103,6 +105,10 @@ class GroupsTableHeader extends React.Component {
navigate(`${location.pathname}?${newFilter.toUrlParams()}`);
};
setTableColumns = (tableColumns) => {
localStorage.setItem(`${TABLE_COLUMNS}=${this.props.userId}`, tableColumns);
};
render() {
const { columns } = this.state;
const {

View File

@ -102,6 +102,8 @@ class InsideGroupTableHeader extends React.Component {
});
const columns = props.getColumns(defaultColumns);
const tableColumns = columns.map((c) => c.enable && c.key);
this.setTableColumns(tableColumns);
this.state = { columns };
}
@ -118,7 +120,7 @@ class InsideGroupTableHeader extends React.Component {
this.setState({ columns });
const tableColumns = columns.map((c) => c.enable && c.key);
localStorage.setItem(`${TABLE_COLUMNS}=${this.props.userId}`, tableColumns);
this.setTableColumns(tableColumns);
const event = new Event(Events.CHANGE_COLUMN);
@ -156,6 +158,10 @@ class InsideGroupTableHeader extends React.Component {
navigate(`${location.pathname}?${newFilter.toUrlParams()}`);
};
setTableColumns = (tableColumns) => {
localStorage.setItem(`${TABLE_COLUMNS}=${this.props.userId}`, tableColumns);
};
render() {
const { columns } = this.state;
const {

View File

@ -160,6 +160,7 @@ const PureHome = (props) => {
getFolderModel,
scrollToTop,
isEmptyGroups,
wsCreatedPDFForm,
} = props;
//console.log(t("ComingSoon"))
@ -206,6 +207,7 @@ const PureHome = (props) => {
scrollToTop,
selectedFolderStore,
wsCreatedPDFForm,
});
const { showUploadPanel } = useOperations({
@ -500,6 +502,7 @@ export default inject(
removeTagsFromRoom,
getRooms,
scrollToTop,
wsCreatedPDFForm,
} = filesStore;
const { updateProfileCulture } = peopleStore.targetUserStore;
@ -564,8 +567,15 @@ export default inject(
const { usersStore, groupsStore, viewAs: accountsViewAs } = peopleStore;
const { getUsersList: fetchPeople } = usersStore;
const { getGroups: fetchGroups, fetchGroup, groups } = groupsStore;
const isEmptyGroups = (groups && groups.length === 0) || !Boolean(groups);
const {
getGroups: fetchGroups,
fetchGroup,
groups,
groupsIsFiltered,
} = groupsStore;
const isEmptyGroups =
!groupsIsFiltered &&
((groups && groups.length === 0) || !Boolean(groups));
if (!firstLoad) {
if (isLoading) {
@ -679,6 +689,7 @@ export default inject(
getFolderModel,
scrollToTop,
isEmptyGroups,
wsCreatedPDFForm,
};
},
)(observer(Home));

View File

@ -73,8 +73,6 @@ export const HeaderContainer = styled.div`
white-space: nowrap;
overflow: hidden;
color: ${(props) => props.theme.client.settings.headerTitleColor};
display: flex;
align-items: center;
}
}
.action-wrapper {
@ -371,17 +369,10 @@ const SectionHeaderContent = (props) => {
<LoaderSectionHeader />
) : (
<HeaderContainer>
{!isCategoryOrHeader && arrayOfParams[0] && (
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBackToParent}
className="arrow-button"
/>
)}
<Headline type="content" truncate={true}>
{isMobile() && isServicePage && (
{!isCategoryOrHeader &&
arrayOfParams[0] &&
(isMobile() ||
window.location.href.indexOf("/javascript-sdk/") > -1) && (
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
@ -390,6 +381,7 @@ const SectionHeaderContent = (props) => {
className="arrow-button"
/>
)}
<Headline type="content" truncate={true}>
<div className="settings-section_header">
<div className="header">{translatedHeader}</div>
{isNeedPaidIcon ? (

View File

@ -62,6 +62,7 @@ const UsersRow = (props: AddEmailUsersRowProps) => {
onRowClick={handleAccountToggle}
onSelect={toggleAccount}
isDisabled={!isPrevEmailValid}
contextButtonSpacerWidth="0"
>
<UsersRowContent
id={data.key}

View File

@ -60,6 +60,9 @@ const DecisionButton = styled(Button)`
width: 32px;
height: 32px;
padding: 0;
path {
fill: ${(props) => props.theme.client.settings.migration.tableHeaderText};
}
`;
DecisionButton.defaultProps = { theme: Base };
@ -82,13 +85,19 @@ const StyledRowContent = styled(RowContent)`
.user-email {
margin-inline-end: 5px;
font-size: 12px;
font-weight: 600;
color: ${(props) =>
props.theme.client.settings.migration.tableRowTextColor};
path {
fill: #a3a9ae;
fill: ${(props) => props.theme.client.settings.migration.tableHeaderText};
}
}
.row-main-container-wrapper {
margin: 0;
width: 100%;
}
.mainIcons {
@ -179,7 +188,7 @@ const UsersRowContent = (props: AddEmailRowContentProps) => {
<Text fontWeight={600} fontSize="14px">
{displayName}
</Text>
<Text fontWeight={600} fontSize="12px" color="#A3A9AE">
<Text className="user-email">
{prevEmail === "" ? t("Settings:NoEmail") : prevEmail}
</Text>
</div>

View File

@ -37,13 +37,7 @@ import UsersRow from "./UsersRow";
import { AddEmailRowProps, RowViewProps } from "../../../../types";
const StyledRowContainer = styled(RowContainer)`
margin-bottom: 20px;
.row-main-container-wrapper {
@media ${tablet} {
margin: 0;
}
}
margin: 0 0 20px;
`;
const StyledRow = styled(Row)`
@ -51,6 +45,12 @@ const StyledRow = styled(Row)`
height: 40px;
min-height: 40px;
.row-header-title {
color: ${(props) => props.theme.client.settings.migration.tableHeaderText};
font-weight: 600;
font-size: 12px;
}
@media ${tablet} {
.row_content {
height: auto;
@ -101,9 +101,7 @@ const RowView = (props: RowViewProps) => {
indeterminate={isIndeterminate}
isDisabled={usersWithFilledEmails.length === 0}
>
<Text color="#a3a9ae" fontWeight={600} fontSize="12px">
{t("Common:Name")}
</Text>
<Text className="row-header-title">{t("Common:Name")}</Text>
</StyledRow>
{accountsData.map((data) => (
<UsersRow

View File

@ -64,25 +64,27 @@ const StyledTableRow = styled(TableRow)`
display: flex;
gap: 8px;
overflow: hidden;
font-size: 12px;
font-weight: 600;
color: ${(props) =>
props.theme.client.settings.migration.tableRowTextColor};
path {
fill: #a3a9ae;
fill: ${(props) => props.theme.client.settings.migration.tableHeaderText};
}
}
.import-email-input {
width: 357.67px;
}
.textOverflow {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
`;
const DecisionButton = styled(Button)`
width: 32px;
height: 32px;
path {
fill: ${(props) => props.theme.client.settings.migration.tableHeaderText};
}
`;
DecisionButton.defaultProps = { theme: Base };
@ -199,7 +201,7 @@ const UsersTableRow = (props: AddEmailTableRowProps) => {
isChecked={isChecked}
isDisabled={!isPrevEmailValid}
/>
<Text fontWeight={600} className="textOverflow">
<Text fontWeight={600} truncate>
{displayName}
</Text>
</TableCell>
@ -230,7 +232,7 @@ const UsersTableRow = (props: AddEmailTableRowProps) => {
) : (
<span onClick={openEmail} className="user-email" ref={emailTextRef}>
<EditSvg />
<Text fontWeight={600} color="#A3A9AE" className="textOverflow">
<Text className="user-email" truncate>
{prevEmail !== "" ? prevEmail : t("Settings:NoEmail")}
</Text>
</span>

View File

@ -75,10 +75,7 @@ const LDAP = ({
if (!isLoaded && isLdapAvailable) return <LdapLoader />;
return (
<StyledLdapPage
isSmallWindow={isSmallWindow}
isSettingPaid={isLdapAvailable}
>
<StyledLdapPage isSmallWindow={isSmallWindow}>
<Text className="intro-text settings_unavailable">{t("LdapIntro")}</Text>
<Box marginProp="8px 0 24px 0">
<Link

View File

@ -27,7 +27,6 @@
import styled, { css } from "styled-components";
import { Box } from "@docspace/shared/components/box";
import { mobile } from "@docspace/shared/utils";
import { UnavailableStyles } from "../../../../utils/commonSettingsStyles";
const StyledLdapPage = styled(Box)`
max-width: ${(props) => (props.isSmallWindow ? "100%" : "700px")};
@ -222,8 +221,6 @@ const StyledLdapPage = styled(Box)`
margin-top: 24px;
}
}
${(props) => !props.isSettingPaid && UnavailableStyles}
`;
export default StyledLdapPage;

View File

@ -33,22 +33,14 @@ import { Text } from "@docspace/shared/components/text";
const HideButton = (props) => {
const { t } = useTranslation("SingleSignOn");
const {
text,
label,
isAdditionalParameters,
value,
setIsSettingsShown,
isDisabled,
} = props;
const { text, label, isAdditionalParameters, value, setIsSettingsShown } =
props;
const marginProp = isAdditionalParameters ? null : "24px 0";
const onClick = () => {
setIsSettingsShown(!value);
};
const onClickProp = isDisabled ? {} : { onClick: onClick };
return (
<Box
alignItems="center"
@ -71,7 +63,7 @@ const HideButton = (props) => {
<Link
className="hide-button settings_unavailable"
isHovered
{...onClickProp}
onClick={onClick}
type="action"
>
{value

View File

@ -83,11 +83,7 @@ const SettingsContainer = ({
const renderBody = () => (
<>
{!isMobileView && (
<HideButton
text={t("Settings:LDAP")}
value={isSettingsShown}
isDisabled={!isLdapAvailable}
/>
<HideButton text={t("Settings:LDAP")} value={isSettingsShown} />
)}
{isMobileView && <ToggleLDAP />}

View File

@ -45,6 +45,7 @@ const SubmitResetButtons = (props) => {
hasChanges,
isLoadingXml,
enableSso,
isSSOAvailable,
} = props;
return (
@ -62,7 +63,9 @@ const SubmitResetButtons = (props) => {
saveButtonDisabled={
!enableSso || hasErrors || !hasChanges || isLoadingXml
}
cancelEnable={!(isSubmitLoading || isLoadingXml)}
disableRestoreToDefault={
isSubmitLoading || isLoadingXml || !isSSOAvailable
}
additionalClassSaveButton="save-button"
additionalClassCancelButton="restore-button"
/>
@ -71,7 +74,7 @@ const SubmitResetButtons = (props) => {
);
};
export default inject(({ ssoStore }) => {
export default inject(({ ssoStore, currentQuotaStore }) => {
const {
saveSsoSettings,
isSsoEnabled,
@ -84,6 +87,7 @@ export default inject(({ ssoStore }) => {
isLoadingXml,
enableSso,
} = ssoStore;
const { isSSOAvailable } = currentQuotaStore;
return {
saveSsoSettings,
@ -96,5 +100,6 @@ export default inject(({ ssoStore }) => {
hasChanges,
isLoadingXml,
enableSso,
isSSOAvailable,
};
})(observer(SubmitResetButtons));

View File

@ -69,7 +69,6 @@ const SingleSignOn = (props) => {
<StyledSsoPage
hideSettings={serviceProviderSettings}
hideMetadata={spMetadata}
isSettingPaid={isSSOAvailable}
>
<Text className="intro-text settings_unavailable" noSelect>
{t("SsoIntro")}
@ -88,7 +87,7 @@ const SingleSignOn = (props) => {
})}
label={SERVICE_PROVIDER_SETTINGS}
value={serviceProviderSettings}
isDisabled={!isSSOAvailable}
//isDisabled={!isSSOAvailable}
/>
<SPSettings />
@ -101,7 +100,7 @@ const SingleSignOn = (props) => {
})}
label={SP_METADATA}
value={spMetadata}
isDisabled={!isSSOAvailable}
//isDisabled={!isSSOAvailable}
/>
<Box className="sp-metadata">

View File

@ -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 { UnavailableStyles } from "../../../../utils/commonSettingsStyles";
const StyledSsoPage = styled.div`
max-width: 100%;
@ -147,8 +146,6 @@ const StyledSsoPage = styled.div`
`}
}
}
${(props) => !props.isSettingPaid && UnavailableStyles}
`;
export default StyledSsoPage;

View File

@ -52,22 +52,13 @@ const StyledWrapper = styled.div`
const HideButton = (props) => {
const { t } = useTranslation("SingleSignOn");
const {
text,
label,
isAdditionalParameters,
value,
setHideLabel,
isDisabled,
id,
} = props;
const { text, label, isAdditionalParameters, value, setHideLabel, id } =
props;
const onClick = () => {
setHideLabel(label);
};
const onClickProp = isDisabled ? {} : { onClick: onClick };
return (
<StyledWrapper isAdditionalParameters={isAdditionalParameters}>
{!isAdditionalParameters && (
@ -86,7 +77,7 @@ const HideButton = (props) => {
id={id}
className="hide-button settings_unavailable"
isHovered
{...onClickProp}
onClick={onClick}
type="action"
>
{value

View File

@ -32,6 +32,7 @@ import { Text } from "@docspace/shared/components/text";
import { ToggleButton } from "@docspace/shared/components/toggle-button";
import { Badge } from "@docspace/shared/components/badge";
import { mobile } from "@docspace/shared/utils";
import { UnavailableStyles } from "../../../../utils/commonSettingsStyles";
const StyledWrapper = styled.div`
display: flex;
@ -69,6 +70,8 @@ const StyledWrapper = styled.div`
}
}
}
${(props) => !props.isSSOAvailable && UnavailableStyles}
`;
const ToggleSSO = ({ enableSso, ssoToggle, isSSOAvailable }) => {
@ -76,7 +79,7 @@ const ToggleSSO = ({ enableSso, ssoToggle, isSSOAvailable }) => {
const theme = useTheme();
return (
<StyledWrapper>
<StyledWrapper isSSOAvailable={isSSOAvailable}>
<ToggleButton
className="enable-sso toggle"
isChecked={enableSso}

View File

@ -279,7 +279,7 @@ class ConsumerModalDialog extends React.Component {
>
<ModalDialog.Header>{selectedConsumer.title}</ModalDialog.Header>
<ModalDialog.Body>
<Box paddingProp="0 0 16px">{consumerInstruction}</Box>
<Box paddingProp="16px 0 16px">{consumerInstruction}</Box>
<React.Fragment>
{selectedConsumer.props.map((prop, i) =>
this.inputsRender(prop, i),

View File

@ -70,7 +70,7 @@ export const StyledAvatarWrapper = styled.div`
@media ${mobile} {
display: flex;
position: fixed;
position: absolute;
right: 16px;
${(props) =>
props.theme.interfaceDirection === "rtl"

View File

@ -70,6 +70,7 @@ export const StyledPage = styled.div`
.public-room-text {
margin: 8px 0;
white-space: wrap;
}
.public-room-name {
@ -83,12 +84,6 @@ export const StyledPage = styled.div`
min-width: 32px;
min-height: 32px;
}
.public-room-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
`;

View File

@ -294,6 +294,7 @@ const Sdk = ({
acceptButtonLabel={frameConfig?.acceptButtonLabel}
cancelButtonLabel={frameConfig?.cancelButtonLabel}
currentFolderId={frameConfig?.id}
openRoot={!frameConfig?.id}
descriptionText={formatsDescription[frameConfig?.filterParam] || ""}
/>
);

View File

@ -28,7 +28,7 @@ import { makeAutoObservable } from "mobx";
import { isMobile } from "@docspace/shared/utils";
import { checkDialogsOpen } from "@docspace/shared/utils/checkDialogsOpen";
import { TUser, TUserGroup } from "@docspace/shared/api/people/types";
import { TABLE_HEADER_HEIGHT } from "@docspace/shared/components/table/Table.constants";
import { TABLE_HEADER_HEIGHT } from "@docspace/shared/utils/device";
type AccountsType = TUser | TUserGroup;

View File

@ -116,6 +116,13 @@ const LOADER_TIMER = 500;
let loadingTime;
let timer;
const systemFolders = [
FolderType.InProgress,
FolderType.Done,
FolderType.SubFolderDone,
FolderType.SubFolderInProgress,
];
class ContextOptionsStore {
settingsStore;
dialogsStore;
@ -409,7 +416,9 @@ class ContextOptionsStore {
const isShared = shared || sharedItem;
if (isShared && !isArchive) {
const isSystemFolder = systemFolders.includes(item.type);
if (isShared && !isArchive && !isSystemFolder) {
try {
const itemLink = item.isFolder
? await getFolderLink(item.id)
@ -965,7 +974,7 @@ class ContextOptionsStore {
const filterUrlParams = filesFilter.toUrlParams();
const url = getCategoryUrl(
this.filesStore.categoryType,
filterUrlParams.folder,
filesFilter.folder,
);
navigate(

View File

@ -1723,6 +1723,8 @@ class FilesStore {
].includes(err?.response?.status);
if (isUserError && !isThirdPartyError) {
if (isPublicRoom()) return Promise.reject(err);
this.setIsErrorRoomNotAvailable(true);
} else {
if (axios.isCancel(err)) {
@ -3961,19 +3963,6 @@ class FilesStore {
openDocEditor = (id, preview = false, shareKey = null, editForm = false) => {
const { openOnNewPage } = this.filesSettingsStore;
const foundIndex = this.files.findIndex((x) => x.id === id);
const file = foundIndex !== -1 ? this.files[foundIndex] : undefined;
if (
file &&
!preview &&
file.rootFolderType !== FolderType.Archive &&
file.fileExst !== ".oform"
) {
const newStatus = file.fileStatus | FileStatus.IsEditing;
this.updateSelectionStatus(id, newStatus, true);
this.updateFileStatus(foundIndex, newStatus);
}
const share = shareKey ? shareKey : this.publicRoomStore.publicRoomKey;

View File

@ -37,7 +37,7 @@ import getFilesFromEvent from "@docspace/shared/components/drag-and-drop/get-fil
import config from "PACKAGE_FILE";
import { getCategoryUrl } from "SRC_DIR/helpers/utils";
import { encryptionUploadDialog } from "../helpers/encryptionUploadDialog";
import { TABLE_HEADER_HEIGHT } from "@docspace/shared/components/table/Table.constants";
import { TABLE_HEADER_HEIGHT } from "@docspace/shared/utils/device";
class HotkeyStore {
filesStore;

View File

@ -29,6 +29,7 @@ import { makeAutoObservable } from "mobx";
import api from "@docspace/shared/api";
import FilesFilter from "@docspace/shared/api/files/filter";
import { combineUrl } from "@docspace/shared/utils/combineUrl";
import {
frameCallCommand,
isPublicRoom as isPublicRoomUtil,
@ -124,7 +125,13 @@ class PublicRoomStore {
if (filter) {
const folderId = filter.folder;
return fetchFiles(folderId, filter);
return fetchFiles(folderId, filter).catch((error) => {
if (error?.response?.status === 403) {
window.location.replace(
combineUrl(window.ClientConfig?.proxy?.url, "/login"),
);
}
});
}
return Promise.resolve();

View File

@ -1,12 +1,12 @@
{
"BackToRoom": "Back to room",
"CheckReadyForm": "Check ready form",
"CheckReadyForms": "Check ready forms",
"Description": "Your form completed and saved",
"DescriptionForAnonymous": "You submitted the filled PDF form which was assigned a unique number. To check the status, contact the form owner providing the assigned number.",
"DescriptionForRegisteredUser": "The filled PDF form is saved and available to you in the Complete folder. To check the status, contact the form owner providing the assigned number.",
"FillItOutAgain": "Fill it out again",
"FormCompletedSuccessfully": "Form completed successfully",
"FormNumber": "Form number:",
"Manager": "Manager:",
"FormOwner": "Form owner",
"Title": "The form is completed"
}

View File

@ -24,15 +24,18 @@
// 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 { redirect } from "next/navigation";
import { headers } from "next/headers";
import { headers, cookies } from "next/headers";
import { ThemeKeys } from "@docspace/shared/enums";
import { getBaseUrl } from "@docspace/shared/utils/next-ssr-helper";
import { SYSTEM_THEME_KEY } from "@docspace/shared/constants";
import Providers from "@/providers";
import Scripts from "@/components/Scripts";
import StyledComponentsRegistry from "@/utils/registry";
import { getColorTheme, getSettings, getUser } from "@/utils/actions";
import "@/styles/globals.scss";
import Providers from "@/providers";
import { getSettings, getUser } from "@/utils/actions";
export default async function RootLayout({
children,
@ -41,13 +44,23 @@ export default async function RootLayout({
}) {
const hdrs = headers();
const cookieStore = cookies();
const systemTheme = cookieStore.get(SYSTEM_THEME_KEY)?.value as
| ThemeKeys
| undefined;
if (hdrs.get("x-health-check") || hdrs.get("referer")?.includes("/health")) {
console.log("is health check");
return <></>;
}
const startDate = new Date();
const [user, settings] = await Promise.all([getUser(), getSettings()]);
const [user, settings, colorTheme] = await Promise.all([
getUser(),
getSettings(),
getColorTheme(),
]);
const timer = new Date().getTime() - startDate.getTime();
if (settings === "access-restricted") redirect(`${getBaseUrl()}/${settings}`);
@ -70,7 +83,7 @@ export default async function RootLayout({
<body>
<StyledComponentsRegistry>
<Providers
contextData={{ user, settings }}
contextData={{ user, settings, systemTheme, colorTheme }}
api_host={api_host}
timer={timer}
>

View File

@ -169,6 +169,7 @@ const Editor = ({
goBack = {
requestClose: true,
text: openFileLocationText,
blank: openOnNewPage,
};
} else {
goBack = {

View File

@ -43,11 +43,9 @@ export const CompletedFormLayout = styled.section<CompletedFormLayoutProps>`
}
width: 100%;
height: 100dvh;
min-height: 100dvh;
padding: 100px 16px 16px;
overflow-y: auto;
background-image: ${(props) => props.bgPattern};
background-repeat: no-repeat;
background-attachment: fixed;
@ -85,7 +83,7 @@ export const CompletedFormLayout = styled.section<CompletedFormLayoutProps>`
align-self: center;
justify-content: center;
height: 48px;
width: 100vw;
width: calc(100% + 32px);
margin: 0 -16px;
margin-bottom: 32px;
@ -163,7 +161,7 @@ export const MainContent = styled.main`
width: 100%;
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-columns: 45fr 101fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"form-file form-file form-file"
@ -175,6 +173,10 @@ export const MainContent = styled.main`
.completed-form__file {
grid-area: form-file;
svg {
flex-shrink: 0;
}
}
.completed-form__filename {
@ -182,11 +184,16 @@ export const MainContent = styled.main`
line-height: 16px;
margin: 0px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.completed-form__download {
cursor: pointer;
margin-inline-start: auto;
flex-shrink: 0;
}
.label {
@ -204,6 +211,15 @@ export const MainContent = styled.main`
font-weight: 600;
color: ${(props) => props.theme.completedForm.descriptionColor};
}
@media ${mobile} {
grid-template-columns: 100%;
grid-template-areas:
"form-file"
"form-number"
"manager";
}
`;
export const FormNumberWrapper = styled.div`
@ -249,14 +265,49 @@ export const ManagerWrapper = styled.div`
font-size: 16px;
line-height: 22px;
font-weight: 700;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* max-width: 300px; */
}
.manager__mail {
grid-area: mail;
display: flex;
gap: 8px;
align-items: center;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
svg {
flex: 0 0 auto;
}
}
@media ${mobile} {
grid-template-columns: 100%;
grid-template-areas:
"avatar"
"user-name"
"mail";
.manager__avatar {
justify-self: center;
}
.manager__user-name {
text-align: center;
}
.manager__mail {
justify-content: center;
}
}
}
`;

View File

@ -114,10 +114,17 @@ export const CompletedForm = ({
);
const {
response: { completedForm, formNumber, manager, originalForm, roomId },
response: {
completedForm,
formNumber,
manager,
originalForm,
roomId,
isRoomMember,
},
} = session;
const isAnonim = Boolean(share);
const isAnonim = Boolean(share) && !isRoomMember;
const getFolderUrl = (folderId: number, isAnonim: boolean): string => {
if (isNullOrUndefined(folderId)) return "";
@ -135,10 +142,6 @@ export const CompletedForm = ({
return `${origin}${path}${filter.toUrlParams()}`;
};
const setHistory = (url: string) => {
history.pushState({}, "", url);
};
const copyLinkFile = async () => {
const origin = window.location.origin;
@ -154,14 +157,12 @@ export const CompletedForm = ({
const gotoCompleteFolder = () => {
const url = getFolderUrl(completedForm.folderId, false);
setHistory(url);
window.location.replace(url);
window.location.assign(url);
};
const handleBackToRoom = () => {
const url = getFolderUrl(roomId, isAnonim);
setHistory(url);
window.location.replace(url);
window.location.assign(url);
};
const fillAgainSearchParams = new URLSearchParams({
@ -207,7 +208,7 @@ export const CompletedForm = ({
</Box>
</FormNumberWrapper>
<ManagerWrapper>
<span className="label">{t("CompletedForm:Manager")}</span>
<span className="label">{t("CompletedForm:FormOwner")}</span>
<Box>
<Avatar
className="manager__avatar"
@ -223,22 +224,22 @@ export const CompletedForm = ({
href={`mailto:${manager.email}`}
>
<MailIcon />
{manager.email}
<span>{manager.email}</span>
</Link>
</Box>
</ManagerWrapper>
</MainContent>
<ButtonWrapper isShreFile={isShreFile}>
<ButtonWrapper isShreFile={isShreFile && !isRoomMember}>
<Button
scale
primary
size={ButtonSize.medium}
label={
isAnonim ? t("Common:Download") : t("CompletedForm:CheckReadyForm")
isAnonim ? t("Common:Download") : t("CompletedForm:CheckReadyForms")
}
onClick={isAnonim ? handleDownload : gotoCompleteFolder}
/>
{!isShreFile && (
{(!isShreFile || isRoomMember) && (
<Button
scale
size={ButtonSize.medium}

View File

@ -39,6 +39,7 @@ export type CompletedFormProps = {
originalForm: TFile;
formNumber: number;
roomId: number;
isRoomMember: boolean;
};
};
isShreFile: boolean;

View File

@ -38,8 +38,11 @@ interface UseI18NProps {
const useI18N = ({ settings, user }: UseI18NProps) => {
const [i18n, setI18N] = React.useState<i18n>(
getI18NInstance(user?.cultureName ?? "en", settings?.culture ?? "en") ??
({} as i18n),
() =>
getI18NInstance(
user?.cultureName ?? settings?.culture ?? "en",
settings?.culture ?? "en",
) ?? ({} as i18n),
);
const isInit = React.useRef(false);
@ -53,7 +56,7 @@ const useI18N = ({ settings, user }: UseI18NProps) => {
isInit.current = true;
const instance = getI18NInstance(
user?.cultureName ?? "en",
user?.cultureName ?? settings?.culture ?? "en",
settings?.culture ?? "en",
);

View File

@ -34,7 +34,7 @@ import {
} from "@docspace/shared/api/files";
// import { getOperationProgress } from "@docspace/shared/utils/getOperationProgress";
import { toastr } from "@docspace/shared/components/toast";
import { EDITOR_ID } from "@docspace/shared/constants";
import { CREATED_FORM_KEY, EDITOR_ID } from "@docspace/shared/constants";
import type {
TFile,
@ -52,6 +52,13 @@ import type { TData } from "@docspace/shared/components/toast/Toast.type";
import { saveAs } from "@/utils";
import type { ConflictStateType } from "@/types";
type SuccessResponseType = {
form: TFile;
message: string;
};
type FaildResponseType = string;
type ResponseType = SuccessResponseType | FaildResponseType;
const DefaultConflictDataDialogState: ConflictStateType = {
visible: false,
resolve: () => {},
@ -70,6 +77,12 @@ const hasFileUrl = (arg: object): arg is { data: { url: string } } => {
);
};
const isSuccessResponse = (
res: ResponseType | undefined,
): res is SuccessResponseType => {
return Boolean(res) && typeof res === "object" && "form" in res;
};
const useStartFillingSelectDialog = (fileInfo: TFile | undefined) => {
// const { t } = useTranslation(["Common"]);
const resolveRef = useRef<(value: string | PromiseLike<string>) => void>();
@ -179,14 +192,22 @@ const useStartFillingSelectDialog = (fileInfo: TFile | undefined) => {
const fileUrl = await getFileUrl();
const response = await saveAs(
const response = await saveAs<ResponseType>(
fileInfo.title,
fileUrl,
selectedItemId,
false,
"createForm",
);
const [key, value] = response?.split(":") ?? [];
if (isSuccessResponse(response)) {
const { form } = response;
sessionStorage.setItem(CREATED_FORM_KEY, JSON.stringify(form));
}
const [key, value] =
typeof response === "string" ? response.split(":") : [];
// await copyToFolder(
// Number(selectedItemId),

View File

@ -26,34 +26,64 @@
import React from "react";
import { i18n } from "i18next";
import { match, P } from "ts-pattern";
import { Base, Dark, TColorScheme, TTheme } from "@docspace/shared/themes";
import { getSystemTheme } from "@docspace/shared/utils";
import { setCookie } from "@docspace/shared/utils/cookie";
import { ThemeKeys } from "@docspace/shared/enums";
import { getAppearanceTheme } from "@docspace/shared/api/settings";
import { TUser } from "@docspace/shared/api/people/types";
import { getFontFamilyDependingOnLanguage } from "@docspace/shared/utils/rtlUtils";
import { SYSTEM_THEME_KEY } from "@docspace/shared/constants";
const SYSTEM_THEME = getSystemTheme();
import type { TUser } from "@docspace/shared/api/people/types";
import type { TGetColorTheme } from "@docspace/shared/api/settings/types";
type MatchType = [ThemeKeys | undefined, ThemeKeys | undefined];
export interface UseThemeProps {
user?: TUser;
i18n?: i18n;
systemTheme?: ThemeKeys;
colorTheme?: TGetColorTheme;
lang?: string;
}
const useTheme = ({ user, i18n }: UseThemeProps) => {
const useTheme = ({
user,
i18n,
systemTheme,
colorTheme,
lang,
}: UseThemeProps) => {
const [currentColorTheme, setCurrentColorTheme] =
React.useState<TColorScheme>({} as TColorScheme);
React.useState<TColorScheme>(() => {
if (!colorTheme) return {} as TColorScheme;
return (
colorTheme.themes.find((theme) => theme.id === colorTheme.selected) ??
({} as TColorScheme)
);
});
const [theme, setTheme] = React.useState<TTheme>(() => {
if (user?.theme === ThemeKeys.DarkStr)
return {
...Dark,
currentColorScheme: currentColorTheme,
};
const interfaceDirection = i18n?.dir ? i18n.dir(lang) : "ltr";
const newTheme = match<MatchType>([user?.theme, systemTheme])
.returnType<TTheme>()
.with([ThemeKeys.DarkStr, P._], () => Dark)
.with([ThemeKeys.BaseStr, P._], () => Base)
.with([ThemeKeys.SystemStr, ThemeKeys.BaseStr], () => Base)
.with([ThemeKeys.SystemStr, ThemeKeys.DarkStr], () => Dark)
.with([undefined, ThemeKeys.DarkStr], () => Dark)
.with([undefined, ThemeKeys.BaseStr], () => Base)
.otherwise(() => Base);
return {
...Base,
...newTheme,
currentColorScheme: currentColorTheme,
interfaceDirection,
fontFamily: getFontFamilyDependingOnLanguage(i18n?.language ?? "en"),
};
});
@ -73,29 +103,27 @@ const useTheme = ({ user, i18n }: UseThemeProps) => {
}, []);
const getUserTheme = React.useCallback(() => {
if (!user?.theme) return;
let theme = user.theme;
const interfaceDirection = i18n?.dir ? i18n.dir() : "ltr";
const SYSTEM_THEME = getSystemTheme();
if (user.theme === ThemeKeys.SystemStr) theme = SYSTEM_THEME;
let theme = user?.theme ?? SYSTEM_THEME;
const interfaceDirection = i18n?.dir ? i18n.dir(lang) : "ltr";
if (theme === ThemeKeys.BaseStr) {
setTheme({
...Base,
currentColorScheme: currentColorTheme,
interfaceDirection,
fontFamily: getFontFamilyDependingOnLanguage(i18n?.language),
});
if (user?.theme === ThemeKeys.SystemStr) theme = SYSTEM_THEME;
return;
}
const fontFamily = getFontFamilyDependingOnLanguage(i18n?.language ?? "en");
const isBaseTheme = theme === ThemeKeys.BaseStr;
const themeCookie = isBaseTheme ? ThemeKeys.BaseStr : ThemeKeys.DarkStr;
setTheme({
...Dark,
...(isBaseTheme ? Base : Dark),
currentColorScheme: currentColorTheme,
interfaceDirection,
fontFamily,
});
}, [user?.theme, i18n, currentColorTheme]);
setCookie(SYSTEM_THEME_KEY, themeCookie);
}, [user?.theme, i18n, currentColorTheme, lang]);
React.useEffect(() => {
getCurrentColorTheme();
@ -105,6 +133,16 @@ const useTheme = ({ user, i18n }: UseThemeProps) => {
getUserTheme();
}, [currentColorTheme, getUserTheme]);
React.useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
mediaQuery.addEventListener("change", getUserTheme);
return () => {
mediaQuery.removeEventListener("change", getUserTheme);
};
}, [getUserTheme]);
return { theme, currentColorTheme };
};

View File

@ -30,7 +30,11 @@ import React from "react";
import { ThemeProvider as ComponentThemeProvider } from "@docspace/shared/components/theme-provider";
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import type {
TGetColorTheme,
TSettings,
} from "@docspace/shared/api/settings/types";
import type { ThemeKeys } from "@docspace/shared/enums";
import useTheme from "@/hooks/useTheme";
import useI18N from "@/hooks/useI18N";
@ -39,12 +43,28 @@ type TThemeProvider = {
children: React.ReactNode;
settings: TSettings | undefined;
user: TUser | undefined;
systemTheme: ThemeKeys | undefined;
colorTheme: TGetColorTheme | undefined;
};
const ThemeProvider = ({ children, user, settings }: TThemeProvider) => {
const ThemeProvider = ({
children,
user,
settings,
systemTheme,
colorTheme,
}: TThemeProvider) => {
const { i18n } = useI18N({ settings, user });
const { theme, currentColorTheme } = useTheme({ user, i18n });
const lang = user?.cultureName ?? settings?.culture;
const { theme, currentColorTheme } = useTheme({
user,
i18n,
systemTheme,
colorTheme,
lang,
});
return (
<ComponentThemeProvider

View File

@ -24,9 +24,13 @@
// 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 { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import { Toast } from "@docspace/shared/components/toast/Toast";
import type { TUser } from "@docspace/shared/api/people/types";
import type {
TGetColorTheme,
TSettings,
} from "@docspace/shared/api/settings/types";
import type { ThemeKeys } from "@docspace/shared/enums";
import ThemeProvider from "./ThemeProvider";
import TranslationProvider from "./TranslationProvider";
@ -35,6 +39,8 @@ import ErrorProvider from "./ErrorProvider";
export type TContextData = {
user: TUser | undefined;
settings: TSettings | undefined;
systemTheme: ThemeKeys | undefined;
colorTheme: TGetColorTheme | undefined;
};
export type TProviders = {

View File

@ -36,7 +36,10 @@ import type {
TFile,
} from "@docspace/shared/api/files/types";
import { TUser } from "@docspace/shared/api/people/types";
import { TSettings } from "@docspace/shared/api/settings/types";
import type {
TGetColorTheme,
TSettings,
} from "@docspace/shared/api/settings/types";
import type {
ActionType,
@ -500,3 +503,19 @@ export async function getEditorUrl(
return editorUrl.response as TDocServiceLocation;
}
export async function getColorTheme() {
const [getSettings] = createRequest(
[`/settings/colortheme`],
[["", ""]],
"GET",
);
const res = await fetch(getSettings);
if (!res.ok) return;
const colorTheme = await res.json();
return colorTheme.response as TGetColorTheme;
}

View File

@ -81,30 +81,31 @@ export const onSDKRequestEditRights = async (
const url = window.location.href;
const isPDF = documentType === "pdf";
if (isPDF) {
const newURL = new URL(url);
let newURL = new URL(url);
if (newURL.searchParams.has("action")) {
newURL.searchParams.delete("action");
if (
!isPDF &&
fileInfo?.viewAccessibility?.MustConvert &&
fileInfo?.security?.Convert
) {
try {
const response = await convertDocumentUrl(fileInfo.id);
if (response && response.webUrl) {
newURL = new URL(response.webUrl);
} else {
throw new Error("Invalid response data");
}
} catch (error) {
console.error("Error converting document", { error });
return;
}
} else {
if (newURL.searchParams.has("action")) newURL.searchParams.delete("action");
newURL.searchParams.append("action", "edit");
history.pushState({}, "", newURL.toString());
document.location.reload();
return;
}
let convertUrl = url;
if (fileInfo?.viewAccessibility?.MustConvert && fileInfo?.security?.Convert) {
const newUrl = await convertDocumentUrl(fileInfo.id);
if (newUrl) {
convertUrl = newUrl.webUrl;
}
}
history.pushState({}, "", convertUrl);
history.pushState({}, "", newURL.toString());
document.location.reload();
};

View File

@ -48,6 +48,8 @@ export const getBackUrl = (
} else {
backUrl = `/rooms/shared/${folderId}/filter?folder=${folderId}`;
}
} else if (rootFolderType === FolderType.Archive) {
backUrl = `/rooms/archived/${folderId}/filter?folder=${folderId}`;
} else {
if (
rootFolderType === FolderType.SHARE ||
@ -99,14 +101,15 @@ export const getDataSaveAs = async (params: string) => {
}
};
export const saveAs = (
export const saveAs = <T = string>(
title: string,
url: string,
folderId: string | number,
openNewTab: boolean,
action = "create",
) => {
const options = {
action: "create",
action,
fileuri: url,
title: title,
folderid: folderId,
@ -115,7 +118,7 @@ export const saveAs = (
const params = toUrlParams(options, true);
if (!openNewTab) {
return getDataSaveAs(params);
return getDataSaveAs(params) as Promise<T>;
} else {
const handlerUrl = combineUrl(
window.ClientConfig?.proxy?.url,

View File

@ -42,7 +42,10 @@ import { useSearchParams, useRouter } from "next/navigation";
import { Text } from "@docspace/shared/components/text";
import { Button, ButtonSize } from "@docspace/shared/components/button";
import { createPasswordHash } from "@docspace/shared/utils/common";
import {
createPasswordHash,
frameCallCommand,
} from "@docspace/shared/utils/common";
import { checkPwd } from "@docspace/shared/utils/desktop";
import { login } from "@docspace/shared/utils/loginUtils";
import { toastr } from "@docspace/shared/components/toast";
@ -129,6 +132,7 @@ const LoginForm = ({
setIdentifier(email);
setEmailFromInvitation(email);
frameCallCommand("setIsLoaded");
}, [loginData]);
const authCallback = useCallback(

View File

@ -27,8 +27,12 @@
import Filter from "./filter";
import { request } from "../client";
import { checkFilterInstance } from "../../utils/common";
import { TGroup } from "./types";
import { checkFilterInstance, toUrlParams } from "../../utils/common";
import {
TGetGroupMembersInRoom,
TGetGroupMembersInRoomFilter,
TGroup,
} from "./types";
// * Create
@ -104,11 +108,14 @@ export const getGroupsByUserId = (userId: string) => {
export const getGroupMembersInRoom = (
folderId: string | number,
groupId: string,
filter: TGetGroupMembersInRoomFilter,
) => {
const url = `/files/folder/${folderId}/group/${groupId}/share?${toUrlParams(filter, false)}`;
return request({
method: "get",
url: `/files/folder/${folderId}/group/${groupId}/share`,
});
url,
}) as Promise<TGetGroupMembersInRoom>;
};
// * Update

View File

@ -25,6 +25,7 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
import { TUser } from "../people/types";
import { ShareAccessRights } from "../../enums";
export type TGroup = {
category: string;
@ -37,3 +38,23 @@ export type TGroup = {
shared?: boolean;
isLDAP: boolean;
};
export type TGroupMemberInvitedInRoom = {
user: TUser;
canEditAccess: boolean;
overridden: boolean;
owner: boolean;
groupAccess: number;
userAccess: ShareAccessRights;
};
export type TGetGroupMembersInRoom = {
items: TGroupMemberInvitedInRoom[];
total: number;
};
export type TGetGroupMembersInRoomFilter = {
startIndex?: number;
count?: number;
filterValue?: string;
};

View File

@ -112,11 +112,11 @@ const StyledBody = styled.div<{
height: ${(props) =>
props.footerVisible
? props.withHeader
? `calc(100% - 16px - ${props.footerHeight}px - ${props.headerHeight}px)`
: `calc(100% - 16px - ${props.footerHeight}px)`
? `calc(100% - ${props.footerHeight}px - ${props.headerHeight}px)`
: `calc(100% - ${props.footerHeight}px)`
: props.withHeader
? `calc(100% - 16px - ${props.headerHeight}px)`
: `calc(100% - 16px)`};
? `calc(100% - ${props.headerHeight}px)`
: "100%"};
padding: ${({ withTabs }) => (withTabs ? "0" : "16px 0 0 0")};

View File

@ -189,7 +189,7 @@ const Body = ({
if (withInfoBar) {
const infoEl = document.querySelector(".selector_info-bar");
if (infoEl) {
const height = infoEl.getClientRects()[0].height;
const height = infoEl.getClientRects()[0].height + CONTAINER_PADDING;
listHeight -= height;
}
}

View File

@ -24,7 +24,10 @@
// 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 { SETTINGS_SIZE } from "./Table.constants";
export const MIN_SIZE_FIRST_COLUMN = 210;
export const DEFAULT_MIN_COLUMN_SIZE = 110;
export const SETTINGS_SIZE = 24;
export const HANDLE_OFFSET = 8;
export const getSubstring = (str: string) => +str.substring(0, str.length - 2);

View File

@ -38,16 +38,17 @@ import {
import { TTableColumn, TableHeaderProps } from "./Table.types";
import { TableSettings } from "./sub-components/TableSettings";
import { TableHeaderCell } from "./sub-components/TableHeaderCell";
import { checkingForUnfixedSize, getSubstring } from "./Table.utils";
import {
DEFAULT_MIN_COLUMN_SIZE,
MIN_SIZE_FIRST_COLUMN,
SETTINGS_SIZE,
HANDLE_OFFSET,
checkingForUnfixedSize,
getSubstring,
} from "./Table.utils";
import { isDesktop } from "../../utils";
const defaultMinColumnSize = 110;
const settingsSize = 24;
const minSizeFirstColumn = 210;
const handleOffset = 8;
class TableHeader extends React.Component<
class TableHeaderComponent extends React.Component<
TableHeaderProps,
{ hideColumns: boolean; columnIndex: null | number }
> {
@ -137,7 +138,7 @@ class TableHeader extends React.Component<
if (leftColumn) {
const minSize = leftColumn.dataset.minWidth
? leftColumn.dataset.minWidth
: defaultMinColumnSize;
: DEFAULT_MIN_COLUMN_SIZE;
if (leftColumn.getBoundingClientRect().width <= +minSize) {
if (colIndex < 0) return false;
@ -185,17 +186,17 @@ class TableHeader extends React.Component<
const defaultColumn = document.getElementById(`column_${colIndex}`);
if (!defaultColumn || defaultColumn.dataset.defaultSize) return;
if (column2Width + offset - handleOffset >= defaultMinColumnSize) {
widths[+columnIndex] = `${newWidth + handleOffset}px`;
widths[colIndex] = `${column2Width + offset - handleOffset}px`;
} else if (column2Width !== defaultMinColumnSize) {
if (column2Width + offset - HANDLE_OFFSET >= DEFAULT_MIN_COLUMN_SIZE) {
widths[+columnIndex] = `${newWidth + HANDLE_OFFSET}px`;
widths[colIndex] = `${column2Width + offset - HANDLE_OFFSET}px`;
} else if (column2Width !== DEFAULT_MIN_COLUMN_SIZE) {
const width =
getSubstring(widths[+columnIndex]) +
getSubstring(widths[+colIndex]) -
defaultMinColumnSize;
DEFAULT_MIN_COLUMN_SIZE;
widths[+columnIndex] = `${width}px`;
widths[colIndex] = `${defaultMinColumnSize}px`;
widths[colIndex] = `${DEFAULT_MIN_COLUMN_SIZE}px`;
} else {
if (colIndex === columns.length) return false;
this.moveToRight(widths, newWidth, colIndex + 1);
@ -229,7 +230,7 @@ class TableHeader extends React.Component<
)?.defaultSize;
const widthColumns =
containerWidth - settingsSize - (defaultSizeColumn || 0);
containerWidth - SETTINGS_SIZE - (defaultSizeColumn || 0);
const newColumnSize = defaultSize || widthColumns / allColumnsLength;
@ -247,9 +248,9 @@ class TableHeader extends React.Component<
};
if (
(indexOfMaxSize === 0 && newSizeMaxColumn < minSizeFirstColumn) ||
(indexOfMaxSize !== 0 && newSizeMaxColumn < defaultMinColumnSize) ||
newColumnSize < defaultMinColumnSize ||
(indexOfMaxSize === 0 && newSizeMaxColumn < MIN_SIZE_FIRST_COLUMN) ||
(indexOfMaxSize !== 0 && newSizeMaxColumn < DEFAULT_MIN_COLUMN_SIZE) ||
newColumnSize < DEFAULT_MIN_COLUMN_SIZE ||
enableColumnsLength === 1
)
return ResetColumnsSize();
@ -277,14 +278,14 @@ class TableHeader extends React.Component<
const minSize = column.dataset.minWidth
? column.dataset.minWidth
: defaultMinColumnSize;
: DEFAULT_MIN_COLUMN_SIZE;
if (newWidth <= +minSize - handleOffset) {
if (newWidth <= +minSize - HANDLE_OFFSET) {
const currentWidth = getSubstring(widths[+columnIndex]);
// Move left
if (currentWidth !== +minSize) {
newWidth = +minSize - handleOffset;
newWidth = +minSize - HANDLE_OFFSET;
this.moveToRight(widths, newWidth);
} else this.moveToLeft(widths, newWidth);
} else {
@ -369,7 +370,7 @@ class TableHeader extends React.Component<
if (storageSize) {
const splitStorage = storageSize.split(" ");
if (getSubstring(splitStorage[0]) <= defaultMinColumnSize) {
if (getSubstring(splitStorage[0]) <= DEFAULT_MIN_COLUMN_SIZE) {
localStorage.removeItem(columnStorageName);
this.onResize();
return;
@ -407,7 +408,7 @@ class TableHeader extends React.Component<
.map((column) => getSubstring(column))
.reduce((x, y) => x + y);
const oldWidth = defaultWidth - defaultSize - settingsSize;
const oldWidth = defaultWidth - defaultSize - SETTINGS_SIZE;
let str = "";
let gridTemplateColumnsWithoutOverfilling: string[] = [];
@ -424,7 +425,7 @@ class TableHeader extends React.Component<
? storageInfoPanelSize.split(" ")
: tableContainer;
let containerMinWidth = containerWidth - defaultSize - settingsSize;
let containerMinWidth = containerWidth - defaultSize - SETTINGS_SIZE;
tableInfoPanelContainer.forEach((item, index) => {
const column = document.getElementById(`column_${index}`);
@ -436,12 +437,12 @@ class TableHeader extends React.Component<
if (
enable &&
(item !== `${defaultSize}px` || `${defaultSize}px` === `0px`) &&
item !== `${settingsSize}px`
item !== `${SETTINGS_SIZE}px`
) {
if (column?.dataset?.minWidth) {
containerMinWidth -= +column.dataset.minWidth;
} else {
containerMinWidth -= defaultMinColumnSize;
containerMinWidth -= DEFAULT_MIN_COLUMN_SIZE;
}
}
});
@ -461,11 +462,11 @@ class TableHeader extends React.Component<
if (column?.dataset?.minWidth && column?.dataset?.default) {
gridTemplateColumns.push(
`${containerWidth - defaultSize - settingsSize}px`,
`${containerWidth - defaultSize - SETTINGS_SIZE}px`,
);
} else if (
item === `${defaultSize}px` ||
item === `${settingsSize}px`
item === `${SETTINGS_SIZE}px`
) {
gridTemplateColumns.push(item);
} else {
@ -477,7 +478,7 @@ class TableHeader extends React.Component<
let hasGridTemplateColumnsWithoutOverfilling = false;
if (infoPanelVisible) {
if (!hideColumns) {
const contentWidth = containerWidth - defaultSize - settingsSize;
const contentWidth = containerWidth - defaultSize - SETTINGS_SIZE;
let enabledColumnsCount = 0;
@ -486,7 +487,7 @@ class TableHeader extends React.Component<
index !== 0 &&
item !== "0px" &&
item !== `${defaultSize}px` &&
item !== `${settingsSize}px`
item !== `${SETTINGS_SIZE}px`
) {
enabledColumnsCount += 1;
}
@ -497,10 +498,10 @@ class TableHeader extends React.Component<
.map((column) => getSubstring(column))
.reduce((x, y) => x + y) -
defaultSize -
settingsSize;
SETTINGS_SIZE;
if (
contentWidth - enabledColumnsCount * defaultMinColumnSize >
contentWidth - enabledColumnsCount * DEFAULT_MIN_COLUMN_SIZE >
getSubstring(tableInfoPanelContainer[0])
) {
const currentContentWidth =
@ -524,7 +525,7 @@ class TableHeader extends React.Component<
if (!enable) {
gridTemplateColumns.push("0px");
} else if (item !== `${settingsSize}px`) {
} else if (item !== `${SETTINGS_SIZE}px`) {
const percent =
enabledColumnsCount === 0
? 100
@ -536,17 +537,17 @@ class TableHeader extends React.Component<
const newItemWidth = defaultColumnSize
? `${defaultColumnSize}px`
: (currentContentWidth * percent) / 100 >
defaultMinColumnSize
DEFAULT_MIN_COLUMN_SIZE
? `${(currentContentWidth * percent) / 100}px`
: `${defaultMinColumnSize}px`;
: `${DEFAULT_MIN_COLUMN_SIZE}px`;
if (
(currentContentWidth * percent) / 100 <
defaultMinColumnSize &&
DEFAULT_MIN_COLUMN_SIZE &&
!defaultColumnSize
) {
overWidth +=
defaultMinColumnSize -
DEFAULT_MIN_COLUMN_SIZE -
(currentContentWidth * percent) / 100;
}
@ -565,10 +566,10 @@ class TableHeader extends React.Component<
index !== 0 &&
column !== "0px" &&
column !== `${defaultSize}px` &&
column !== `${settingsSize}px` &&
columnWidth > defaultMinColumnSize
column !== `${SETTINGS_SIZE}px` &&
columnWidth > DEFAULT_MIN_COLUMN_SIZE
) {
const availableWidth = columnWidth - defaultMinColumnSize;
const availableWidth = columnWidth - DEFAULT_MIN_COLUMN_SIZE;
if (availableWidth < Math.abs(overWidth)) {
overWidth = Math.abs(overWidth) - availableWidth;
@ -598,15 +599,15 @@ class TableHeader extends React.Component<
if (!enable) {
gridTemplateColumns.push("0px");
} else if (item !== `${settingsSize}px`) {
} else if (item !== `${SETTINGS_SIZE}px`) {
const newItemWidth = defaultColumnSize
? `${defaultColumnSize}px`
: index === 0
? `${
contentWidth -
enabledColumnsCount * defaultMinColumnSize
enabledColumnsCount * DEFAULT_MIN_COLUMN_SIZE
}px`
: `${defaultMinColumnSize}px`;
: `${DEFAULT_MIN_COLUMN_SIZE}px`;
gridTemplateColumns.push(newItemWidth);
} else {
@ -617,7 +618,7 @@ class TableHeader extends React.Component<
}
} else {
let overWidth = 0;
if (!hideColumns) {
if (!hideColumns && !hideColumnsConst) {
// eslint-disable-next-line guard-for-in, no-restricted-syntax
for (const index in tableContainer) {
const item = tableContainer[index];
@ -646,7 +647,7 @@ class TableHeader extends React.Component<
getSubstring(gridTemplateColumns[+index - colIndex]) +
getSubstring(item)
}px`;
} else if (item !== `${settingsSize}px`) {
} else if (item !== `${SETTINGS_SIZE}px`) {
const percent = (getSubstring(item) / oldWidth) * 100;
if (percent === 100) {
@ -663,30 +664,31 @@ class TableHeader extends React.Component<
let newItemWidth = defaultColumnSize
? `${defaultColumnSize}px`
: percent === 0
? `${defaultMinColumnSize}px`
? `${DEFAULT_MIN_COLUMN_SIZE}px`
: `${
((containerWidth - defaultSize - settingsSize) *
((containerWidth - defaultSize - SETTINGS_SIZE) *
percent) /
100
}px`;
const minWidth = column?.dataset?.minWidth;
const minSize = minWidth ? +minWidth : minSizeFirstColumn;
const minSize = minWidth ? +minWidth : MIN_SIZE_FIRST_COLUMN;
// Checking whether the first column is less than the minimum width
if (+index === 0 && getSubstring(newItemWidth) < minSize) {
overWidth += minSizeFirstColumn - getSubstring(newItemWidth);
newItemWidth = `${minSizeFirstColumn}px`;
overWidth += MIN_SIZE_FIRST_COLUMN - getSubstring(newItemWidth);
newItemWidth = `${MIN_SIZE_FIRST_COLUMN}px`;
}
// Checking whether columns are smaller than the minimum width
if (
+index !== 0 &&
!defaultColumnSize &&
getSubstring(newItemWidth) < defaultMinColumnSize
getSubstring(newItemWidth) < DEFAULT_MIN_COLUMN_SIZE
) {
overWidth += defaultMinColumnSize - getSubstring(newItemWidth);
newItemWidth = `${defaultMinColumnSize}px`;
overWidth +=
DEFAULT_MIN_COLUMN_SIZE - getSubstring(newItemWidth);
newItemWidth = `${DEFAULT_MIN_COLUMN_SIZE}px`;
}
gridTemplateColumns.push(newItemWidth);
@ -783,9 +785,11 @@ class TableHeader extends React.Component<
const column = document.getElementById(`column_${index}`);
const minWidth = column?.dataset?.minWidth;
const minSize = minWidth ? +minWidth : minSizeFirstColumn;
const minSize = minWidth ? +minWidth : MIN_SIZE_FIRST_COLUMN;
if ((index === 0 ? minSize : defaultMinColumnSize) !== getSubstring(item))
if (
(index === 0 ? minSize : DEFAULT_MIN_COLUMN_SIZE) !== getSubstring(item)
)
countColumns += 1;
});
@ -797,21 +801,21 @@ class TableHeader extends React.Component<
const column = document.getElementById(`column_${index}`);
const minWidth = column?.dataset?.minWidth;
const minSize = minWidth ? +minWidth : minSizeFirstColumn;
const minSize = minWidth ? +minWidth : MIN_SIZE_FIRST_COLUMN;
const itemSubstring = getSubstring(item);
if ((index === 0 ? minSize : defaultMinColumnSize) === itemSubstring)
if ((index === 0 ? minSize : DEFAULT_MIN_COLUMN_SIZE) === itemSubstring)
return;
const differenceWithMinimum =
itemSubstring - (index === 0 ? minSize : defaultMinColumnSize);
itemSubstring - (index === 0 ? minSize : DEFAULT_MIN_COLUMN_SIZE);
if (differenceWithMinimum >= addWidth) {
newGridTemplateColumns[index] = `${itemSubstring - addWidth}px`;
} else {
newGridTemplateColumns[index] = `${
index === 0 ? minSize : defaultMinColumnSize
index === 0 ? minSize : DEFAULT_MIN_COLUMN_SIZE
}px`;
}
});
@ -862,7 +866,7 @@ class TableHeader extends React.Component<
columns.find((col) => col.defaultSize && col.enable)?.defaultSize || 0;
const containerWidth =
container.clientWidth - defaultColumnSize - settingsSize;
container.clientWidth - defaultColumnSize - SETTINGS_SIZE;
const firstColumnPercent = enableColumns.length > 0 ? 40 : 100;
const percent = enableColumns.length > 0 ? 60 / enableColumns.length : 0;
@ -882,7 +886,7 @@ class TableHeader extends React.Component<
}
}
str += `${settingsSize}px`;
str += `${SETTINGS_SIZE}px`;
if (container) container.style.gridTemplateColumns = str;
if (this.headerRef && this.headerRef.current) {
@ -970,4 +974,6 @@ class TableHeader extends React.Component<
}
}
export default withTheme(TableHeader);
const TableHeader = withTheme(TableHeaderComponent);
export { TableHeader };

View File

@ -24,13 +24,10 @@
// 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 TableHeader from "./TableHeader";
export { TableHeader };
export { TableContainer } from "./TableContainer";
export { TableBody } from "./TableBody";
export { TableRow } from "./TableRow";
// export { TableHeader } from "./TableHeader";
export { TableHeader } from "./TableHeader";
export { TableGroupMenu } from "./TableGroupMenu";
export { TableCell } from "./sub-components/TableCell";

View File

@ -53,6 +53,7 @@ export const ROOM = "room";
export const USERS = "users";
export const USERS_IN_ROOM = "usersInRoom";
export const PDF_FORM_DIALOG_KEY = "pdf_form_dialog";
export const CREATED_FORM_KEY = "created_form_key";
export const COUNT_FOR_SHOWING_BAR = 2;
export const PERCENTAGE_FOR_SHOWING_BAR = 90;

View File

@ -33,6 +33,30 @@ import {
StyledMembersLoader,
} from "../body.styled";
export const MemberLoader = ({ count = 1 }: { count?: number }) => {
return (
<>
{[...Array(count).keys()].map((i) => (
<StyledMemberLoader key={i}>
<RectangleSkeleton
className="avatar"
width="32px"
height="32px"
borderRadius="50%"
/>
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
<RectangleSkeleton
className="role-selector"
width="64px"
height="20px"
borderRadius="3px"
/>
</StyledMemberLoader>
))}
</>
);
};
const MembersLoader = () => {
return (
<StyledMembersLoader>
@ -41,46 +65,14 @@ const MembersLoader = () => {
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
</StyledMemberSubtitleLoader>
{[...Array(4).keys()].map((i) => (
<StyledMemberLoader key={i}>
<RectangleSkeleton
className="avatar"
width="32px"
height="32px"
borderRadius="50%"
/>
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
<RectangleSkeleton
className="role-selector"
width="64px"
height="20px"
borderRadius="3px"
/>
</StyledMemberLoader>
))}
<MemberLoader count={4} />
<StyledMemberSubtitleLoader className="pending_users">
<RectangleSkeleton width="111px" height="16px" borderRadius="3px" />
<RectangleSkeleton width="16px" height="16px" borderRadius="3px" />
</StyledMemberSubtitleLoader>
{[...Array(4).keys()].map((i) => (
<StyledMemberLoader key={i}>
<RectangleSkeleton
className="avatar"
width="32px"
height="32px"
borderRadius="50%"
/>
<RectangleSkeleton width="212px" height="16px" borderRadius="3px" />
<RectangleSkeleton
className="role-selector"
width="64px"
height="20px"
borderRadius="3px"
/>
</StyledMemberLoader>
))}
<MemberLoader count={4} />
</StyledMembersLoader>
);
};

View File

@ -369,7 +369,7 @@ class AuthStore {
login = async (user: TUser, hash: string, session = true) => {
try {
const response = (await api.user.login(user, hash, session)) as {
const response = (await api.user.login(user, hash, "", session)) as {
token: string;
tfa: string;
error: { message: unknown };

View File

@ -2114,6 +2114,7 @@ export const getBaseTheme = () => {
folderLabelColor: "#A3A9AE",
renamedItemColor: "#A3A9AE",
oldRoleColor: "#657077",
messageColor: "#333333",
},
details: {
@ -2720,6 +2721,7 @@ export const getBaseTheme = () => {
upload: {
color: gray,
tooltipColor: lightCumulus,
iconColor: lightErrorStatus,
shareButton: {
color: gray,

View File

@ -2086,6 +2086,7 @@ const Dark: TTheme = {
folderLabelColor: "#A3A9AE",
renamedItemColor: "#A3A9AE",
oldRoleColor: "#A3A9AE",
messageColor: "#FFFFFF",
},
details: {
@ -2698,6 +2699,7 @@ const Dark: TTheme = {
upload: {
color: black,
tooltipColor: "#F5E9BA",
iconColor: darkErrorStatus,
shareButton: {
color: gray,

View File

@ -25,6 +25,7 @@
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
export const INFO_PANEL_WIDTH = 400;
export const TABLE_HEADER_HEIGHT = 40;
export function checkIsSSR() {
return typeof window === "undefined";