Merge branch 'develop' into bugfix/backup

This commit is contained in:
Tatiana Lopaeva 2022-08-29 16:42:57 +03:00
commit 64893f12f9
55 changed files with 1438 additions and 518 deletions

View File

@ -24,9 +24,9 @@
// 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
using Amazon.S3.Internal;
using Amazon.Extensions.S3.Encryption;
using Amazon.Extensions.S3.Encryption.Primitives;
using Amazon.S3.Internal;
namespace ASC.Data.Storage.S3;
@ -47,7 +47,7 @@ public class S3Storage : BaseStorage
private string _serviceurl;
private bool _forcepathstyle;
private string _secretAccessKeyId = string.Empty;
private ServerSideEncryptionMethod _sse = ServerSideEncryptionMethod.AES256;
private readonly ServerSideEncryptionMethod _sse = ServerSideEncryptionMethod.AES256;
private bool _useHttp = true;
private bool _lowerCasing = true;
private bool _revalidateCloudFront;
@ -55,7 +55,7 @@ public class S3Storage : BaseStorage
private string _subDir = "";
private EncryptionMethod _encryptionMethod = EncryptionMethod.None;
private string _encryptionKey = null;
private string _encryptionKey;
public S3Storage(
TempStream tempStream,
@ -184,17 +184,17 @@ public class S3Storage : BaseStorage
return SaveAsync(domain, path, stream, contentType, contentDisposition, ACL.Auto);
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5)
{
var buffered = _tempStream.GetBuffered(stream);
if (EnableQuotaCheck(domain))
if (EnableQuotaCheck(domain))
{
QuotaController.QuotaUsedCheck(buffered.Length);
}

View File

@ -45,7 +45,11 @@ public class ProtobufSerializer : IIntegrationEventSerializer
private void BuildTypeModelFromAssembly(Assembly assembly)
{
if (!assembly.GetName().Name.StartsWith("ASC.")) return;
var name = assembly.GetName().Name;
if (name == null || !name.StartsWith("ASC."))
{
return;
}
var types = assembly.GetExportedTypes()
.Where(t => t.GetCustomAttributes<ProtoContractAttribute>().Any());
@ -64,7 +68,7 @@ public class ProtobufSerializer : IIntegrationEventSerializer
{
return Array.Empty<byte>();
}
using var ms = new MemoryStream();
Serializer.Serialize(ms, item);
@ -74,7 +78,7 @@ public class ProtobufSerializer : IIntegrationEventSerializer
/// <inheritdoc/>
public T Deserialize<T>(byte[] serializedObject)
{
{
using var ms = new MemoryStream(serializedObject);
return Serializer.Deserialize<T>(ms);
@ -107,7 +111,7 @@ public class ProtobufSerializer : IIntegrationEventSerializer
if (!baseType.GetSubtypes().Any(s => s.DerivedType == itemType))
{
baseType.AddSubType(_baseFieldNumber, protoType);
_baseFieldNumber++;
_processedProtoTypes.Add(protoType.FullName);

View File

@ -120,7 +120,6 @@ export default function withContent(WrappedContent) {
},
{ item }
) => {
const { editCompleteAction } = filesActionsStore;
const {
createFile,
createFolder,
@ -164,7 +163,6 @@ export default function withContent(WrappedContent) {
createFile,
createFolder,
culture,
editCompleteAction,
folderFormValidation,
homepage: config.homepage,

View File

@ -331,7 +331,8 @@ export default function withFileActions(WrappedFileItem) {
)
isActive = true;
const showHotkeyBorder = hotkeyCaret?.id === item.id;
const showHotkeyBorder =
hotkeyCaret?.id === item.id && hotkeyCaret?.isFolder === item.isFolder;
return {
t,

View File

@ -1,9 +1,9 @@
import React, { useEffect } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { observer, inject } from "mobx-react";
import { FileAction } from "@docspace/common/constants";
import { Events } from "@docspace/client/src/helpers/filesConstants";
import toastr from "client/toastr";
import throttle from "lodash/throttle";
const withHotkeys = (Component) => {
const WithHotkeys = (props) => {
@ -51,6 +51,7 @@ const withHotkeys = (Component) => {
selection,
setFavoriteAction,
filesIsLoading,
} = props;
const hotkeysFilter = {
@ -58,7 +59,11 @@ const withHotkeys = (Component) => {
ev.target?.type === "checkbox" || ev.target?.tagName !== "INPUT",
filterPreventDefault: false,
enableOnTags: ["INPUT"],
enabled: !someDialogIsOpen && enabledHotkeys && !mediaViewerIsVisible,
enabled:
!someDialogIsOpen &&
enabledHotkeys &&
!mediaViewerIsVisible &&
!filesIsLoading,
// keyup: true,
// keydown: false,
};
@ -87,9 +92,12 @@ const withHotkeys = (Component) => {
};
useEffect(() => {
window.addEventListener("keydown", onKeyDown);
const throttledKeyDownEvent = throttle(onKeyDown, 300);
return () => window.removeEventListener("keypress", onKeyDown);
window.addEventListener("keydown", throttledKeyDownEvent);
return () =>
window.removeEventListener("keypress", throttledKeyDownEvent);
});
//Select/deselect item
@ -322,9 +330,9 @@ const withHotkeys = (Component) => {
setSelected,
viewAs,
setViewAs,
fileActionStore,
enabledHotkeys,
selection,
filesIsLoading,
} = filesStore;
const {
@ -413,6 +421,7 @@ const withHotkeys = (Component) => {
selection,
setFavoriteAction,
filesIsLoading,
};
}
)(observer(WithHotkeys));

View File

@ -45,6 +45,7 @@ const ArticleBodyContent = (props) => {
theme,
toggleArticleOpen,
categoryType,
filesIsLoading,
} = props;
const campaigns = (localStorage.getItem("campaigns") || "")
@ -67,6 +68,7 @@ const ArticleBodyContent = (props) => {
archiveFolderId,
} = props;
if (filesIsLoading) return;
const filesSection = window.location.pathname.indexOf("/filter") > 0;
if (filesSection) {
@ -191,6 +193,7 @@ export default inject(
isLoading,
isLoaded,
categoryType,
filesIsLoading,
} = filesStore;
const {
@ -253,6 +256,7 @@ export default inject(
archiveFolderId,
categoryType,
filesIsLoading,
};
}
)(

View File

@ -103,7 +103,7 @@ const CreateEvent = ({
addActiveItems(null, [folder.id]);
setCreatedItem({ id: createdFolderId, type: "folder" });
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type, true))
.catch((e) => toastr.error(e))
.finally(() => {
const folderIds = [+id];
@ -123,7 +123,7 @@ const CreateEvent = ({
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((err) => {
if (err.indexOf("password") == -1) {
toastr.error(err, t("Common:Warning"));
@ -173,7 +173,7 @@ const CreateEvent = ({
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];
@ -209,7 +209,7 @@ const CreateEvent = ({
return open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => editCompleteAction(id, item, false, type))
.then(() => editCompleteAction(item, type))
.catch((e) => toastr.error(e))
.finally(() => {
const fileIds = [+id];

View File

@ -48,7 +48,7 @@ const RenameEvent = ({
if (isSameTitle) {
setStartValue(originalTitle);
return editCompleteAction(item.id, item, isSameTitle, type);
return editCompleteAction(item, type);
} else {
timerId = setTimeout(() => {
isFile ? addActiveItems([item.id]) : addActiveItems(null, [item.id]);
@ -57,7 +57,7 @@ const RenameEvent = ({
isFile
? updateFile(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() => editCompleteAction(item, type))
.then(() =>
toastr.success(
t("FileRenamed", {
@ -68,7 +68,7 @@ const RenameEvent = ({
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
editCompleteAction(item, type);
})
.finally(() => {
clearTimeout(timerId);
@ -79,7 +79,7 @@ const RenameEvent = ({
onClose();
})
: renameFolder(item.id, value)
.then(() => editCompleteAction(item.id, item, false, type))
.then(() => editCompleteAction(item, type))
.then(() =>
toastr.success(
t("FolderRenamed", {
@ -90,7 +90,7 @@ const RenameEvent = ({
)
.catch((err) => {
toastr.error(err);
editCompleteAction(item.id, item, false, type);
editCompleteAction(item, type);
})
.finally(() => {
clearTimeout(timerId);

View File

@ -86,7 +86,7 @@ const Dialog = ({
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="GlobalSendBtn"
label={t("Common:SaveButton")}
size="normal"
scale

View File

@ -153,7 +153,7 @@ class ChangeEmailDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangeEmailSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -82,7 +82,7 @@ class ChangePasswordDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangePasswordSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -37,7 +37,7 @@ class ChangePhoneDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ChangePhoneSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -114,7 +114,7 @@ const ConvertPasswordDialogComponent = (props) => {
open && openDocEditor(file.id, file.providerKey, tab);
})
.then(() => {
editCompleteAction(actionId, fileInfo, false);
editCompleteAction(fileInfo);
})
.catch((err) => {
if (err.indexOf("password") == -1) {

View File

@ -67,7 +67,7 @@ class DeleteSelfProfileDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="DeleteSelfSendBtn"
label={t("Common:SendButton")}
size="normal"
scale

View File

@ -40,7 +40,7 @@ class ResetApplicationDialogComponent extends React.Component {
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
key="SendBtn"
key="ResetSendBtn"
label={t("Common:ResetApplication")}
size="normal"
scale

View File

@ -43,7 +43,7 @@ const SectionBodyContent = ({
descriptionText={t("EmptyScreenDescription")}
/>
) : (
<TileContainer useReactWindow={false} className="tile-container">
<TileContainer className="tile-container">
{oformFiles.map((item, index) => (
<FileTile key={`${item.id}_${index}`} item={item} />
))}

View File

@ -1,71 +1,25 @@
import React, { memo } from "react";
import React from "react";
import { withTranslation } from "react-i18next";
import PropTypes from "prop-types";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import CustomScrollbarsVirtualList from "@docspace/components/scrollbar";
import { StyledGridWrapper, StyledTileContainer } from "../StyledTileView";
class TileContainer extends React.PureComponent {
renderTile = memo(({ data, index, style }) => {
return <div style={style}>{data[index]}</div>;
}, areEqual);
render() {
const {
itemHeight,
children,
useReactWindow,
id,
className,
style,
} = this.props;
const renderList = ({ height, width }) => (
<List
className="list"
height={height}
width={width}
itemSize={itemHeight}
itemCount={children.length}
itemData={children}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderTile}
</List>
);
const { children, id, className, style } = this.props;
return (
<StyledTileContainer
id={id}
className={className}
style={style}
useReactWindow={useReactWindow}
>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
<StyledGridWrapper>{children}</StyledGridWrapper>
)}
<StyledTileContainer id={id} className={className} style={style}>
<StyledGridWrapper>{children}</StyledGridWrapper>
</StyledTileContainer>
);
}
}
TileContainer.propTypes = {
itemHeight: PropTypes.number,
manualHeight: PropTypes.string,
children: PropTypes.any.isRequired,
useReactWindow: PropTypes.bool,
className: PropTypes.string,
id: PropTypes.string,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
TileContainer.defaultProps = {
itemHeight: 50,
useReactWindow: true,
id: "rowContainer",
};
export default withTranslation(["Files", "Common"])(TileContainer);

View File

@ -64,7 +64,12 @@ const FilesRowContainer = ({
viewAs,
setViewAs,
infoPanelVisible,
filterTotal,
fetchMoreFiles,
hasMoreFiles,
isRooms,
selectedFolderId,
withPaging,
}) => {
useEffect(() => {
if ((viewAs !== "table" && viewAs !== "row") || !sectionWidth) return;
@ -84,8 +89,14 @@ const FilesRowContainer = ({
return (
<StyledRowContainer
className="files-row-container"
filesLength={filesList.length}
itemCount={filterTotal}
fetchMoreFiles={fetchMoreFiles}
hasMoreFiles={hasMoreFiles}
draggable
useReactWindow={false}
useReactWindow={!withPaging}
selectedFolderId={selectedFolderId}
itemHeight={58}
>
{filesList.map((item, index) => (
<SimpleFilesRow
@ -100,18 +111,34 @@ const FilesRowContainer = ({
);
};
export default inject(({ filesStore, auth, treeFoldersStore }) => {
const { filesList, viewAs, setViewAs } = filesStore;
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
export default inject(
({ filesStore, auth, treeFoldersStore, selectedFolderStore }) => {
const {
filesList,
viewAs,
setViewAs,
filterTotal,
fetchMoreFiles,
hasMoreFiles,
withPaging,
roomsFilterTotal,
} = filesStore;
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
const isRooms = isRoomsFolder || isArchiveFolder;
return {
filesList,
viewAs,
setViewAs,
infoPanelVisible,
isRooms,
};
})(observer(FilesRowContainer));
return {
filesList,
viewAs,
setViewAs,
infoPanelVisible,
filterTotal: isRooms ? roomsFilterTotal : filterTotal,
fetchMoreFiles,
hasMoreFiles,
isRooms,
selectedFolderId: selectedFolderStore.id,
withPaging,
};
}
)(observer(FilesRowContainer));

View File

@ -7,7 +7,6 @@ import TableHeader from "./TableHeader";
import TableBody from "@docspace/components/table-container/TableBody";
import { isMobile } from "react-device-detect";
import styled, { css } from "styled-components";
import { isTablet } from "@docspace/components/utils/device";
import { Base } from "@docspace/components/themes";
const marginCss = css`
@ -119,7 +118,12 @@ const Table = ({
theme,
infoPanelVisible,
userId,
fetchMoreFiles,
hasMoreFiles,
filterTotal,
isRooms,
selectedFolderId,
withPaging,
}) => {
const [tagCount, setTagCount] = React.useState(null);
@ -181,7 +185,7 @@ const Table = ({
: `${COLUMNS_SIZE_INFO_PANEL}=${userId}`;
return (
<StyledTableContainer forwardedRef={ref}>
<StyledTableContainer useReactWindow={!withPaging} forwardedRef={ref}>
<TableHeader
sectionWidth={sectionWidth}
containerRef={ref}
@ -194,7 +198,19 @@ const Table = ({
roomsColumnInfoPanelStorageName={`${COLUMNS_ROOMS_SIZE_INFO_PANEL}=${userId}`}
isRooms={isRooms}
/>
<TableBody>
<TableBody
fetchMoreFiles={fetchMoreFiles}
columnStorageName={columnStorageName}
filesLength={filesList.length}
hasMoreFiles={hasMoreFiles}
itemCount={filterTotal}
useReactWindow={!withPaging}
infoPanelVisible={infoPanelVisible}
columnInfoPanelStorageName={columnInfoPanelStorageName}
selectedFolderId={selectedFolderId}
itemHeight={isRooms ? 49 : 41}
>
{filesList.map((item, index) => {
return index === 0 && item.isRoom ? (
<TableRow
@ -234,34 +250,45 @@ const Table = ({
);
};
export default inject(({ filesStore, treeFoldersStore, auth }) => {
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
export default inject(
({ filesStore, treeFoldersStore, auth, selectedFolderStore }) => {
const { isVisible: infoPanelVisible } = auth.infoPanelStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const isRooms =
isRoomsFolder ||
isArchiveFolder ||
window.location.href.includes("/rooms?");
const isRooms =
isRoomsFolder ||
isArchiveFolder ||
window.location.href.includes("/rooms?");
const {
filesList,
viewAs,
setViewAs,
setFirsElemChecked,
setHeaderBorder,
} = filesStore;
const {
filesList,
viewAs,
setViewAs,
setFirsElemChecked,
setHeaderBorder,
fetchMoreFiles,
hasMoreFiles,
filterTotal,
withPaging,
roomsFilterTotal,
} = filesStore;
return {
filesList,
viewAs,
setViewAs,
setFirsElemChecked,
setHeaderBorder,
theme: auth.settingsStore.theme,
userId: auth.userStore.user.id,
infoPanelVisible,
isRooms,
};
})(observer(Table));
return {
filesList,
viewAs,
setViewAs,
setFirsElemChecked,
setHeaderBorder,
theme: auth.settingsStore.theme,
userId: auth.userStore.user.id,
infoPanelVisible,
fetchMoreFiles,
hasMoreFiles,
filterTotal: isRooms ? roomsFilterTotal : filterTotal,
isRooms,
selectedFolderId: selectedFolderStore.id,
withPaging,
};
}
)(observer(Table));

View File

@ -167,7 +167,6 @@ class FilesTableHeader extends React.Component {
componentDidMount() {
this.customScrollElm = document.getElementsByClassName("section-scroll")[0];
this.customScrollElm.addEventListener("scroll", this.onBeginScroll);
}
@ -301,6 +300,7 @@ class FilesTableHeader extends React.Component {
columnInfoPanelStorageName,
filesColumnInfoPanelStorageName,
roomsColumnInfoPanelStorageName,
withPaging,
} = this.props;
// const { sortBy, sortOrder } = filter;
@ -340,6 +340,7 @@ class FilesTableHeader extends React.Component {
resetColumnsSize={resetColumnsSize || needReset}
sortingVisible={sortingVisible}
infoPanelVisible={infoPanelVisible}
useReactWindow={!withPaging}
/>
);
}
@ -357,7 +358,7 @@ export default inject(
canShare,
firstElemChecked,
headerBorder,
withPaging,
roomsFilter,
fetchRooms,
} = filesStore;
@ -385,6 +386,7 @@ export default inject(
headerBorder,
infoPanelVisible,
withPaging,
};
}
)(

View File

@ -28,7 +28,7 @@ const getThumbSize = (width) => {
return `${imgWidth}x300`;
};
const FilesTileContainer = ({ filesList, t, sectionWidth }) => {
const FilesTileContainer = ({ filesList, t, sectionWidth, withPaging }) => {
const firstRef = useRef();
const [thumbSize, setThumbSize] = useState("");
const [columnCount, setColumnCount] = useState(null);
@ -77,7 +77,7 @@ const FilesTileContainer = ({ filesList, t, sectionWidth }) => {
<TileContainer
className="tile-container"
draggable
useReactWindow={false}
useReactWindow={!withPaging}
headingFolders={t("Folders")}
headingFiles={t("Files")}
>
@ -108,9 +108,10 @@ const FilesTileContainer = ({ filesList, t, sectionWidth }) => {
};
export default inject(({ filesStore }) => {
const { filesList } = filesStore;
const { filesList, withPaging } = filesStore;
return {
filesList,
withPaging,
};
})(observer(FilesTileContainer));

View File

@ -0,0 +1,207 @@
import React from "react";
import { inject, observer } from "mobx-react";
import InfiniteLoaderComponent from "@docspace/components/infinite-loader";
import { StyledCard, StyledItem, StyledHeaderItem } from "./StyledInfiniteGrid";
import Loaders from "@docspace/common/components/Loaders";
import uniqueid from "lodash/uniqueId";
const HeaderItem = ({ children, className, ...rest }) => {
return (
<StyledHeaderItem className={`${className} header-item`} {...rest}>
{children}
</StyledHeaderItem>
);
};
const Card = ({ children, ...rest }) => {
const getItemSize = (child) => {
const isFile = child?.props?.className?.includes("file");
const isFolder = child?.props?.className?.includes("folder");
const isRoom = child?.props?.className?.includes("room");
const horizontalGap = 16;
const verticalGap = 14;
const headerMargin = 15;
const folderHeight = 64 + verticalGap;
const roomHeight = 122 + verticalGap;
const fileHeight = 220 + horizontalGap;
const titleHeight = 20 + headerMargin;
if (isRoom) return roomHeight;
if (isFolder) return folderHeight;
if (isFile) return fileHeight;
return titleHeight;
};
const cardHeight = getItemSize(children);
return (
<StyledCard className="Card" cardHeight={cardHeight} {...rest}>
{children}
</StyledCard>
);
};
const Item = ({ children, className, ...rest }) => {
return (
<StyledItem className={`Item ${className}`} {...rest}>
{children}
</StyledItem>
);
};
const InfiniteGrid = (props) => {
const {
children,
hasMoreFiles,
filterTotal,
fetchMoreFiles,
filesLength,
className,
getCountTilesInRow,
selectedFolderId,
...rest
} = props;
const countTilesInRow = getCountTilesInRow();
let cards = [];
const list = [];
const addItemToList = (key, className, clear) => {
list.push(
<Item key={key} className={className}>
{cards}
</Item>
);
if (clear) cards = [];
};
const checkType = (useTempList = true) => {
const isFile = useTempList
? cards.at(-1).props.children.props.className.includes("file")
: list.at(-1).props.className.includes("isFile");
if (isFile) return "isFile";
const isFolder = useTempList
? cards.at(-1).props.children.props.className.includes("folder")
: list.at(-1).props.className.includes("isFolder");
if (isFolder) return "isFolder";
return "isRoom";
};
React.Children.map(children.props.children, (child) => {
if (child) {
if (child.props.className === "tile-items-heading") {
// If cards is not empty then put the cards into the list
if (cards.length) {
const type = checkType();
addItemToList(`last-item-of_${type}`, type, true);
}
list.push(
<HeaderItem
className={list.length ? "files_header" : "folder_header"}
key="header_item"
>
{child}
</HeaderItem>
);
} else {
const isFile = child?.props?.className?.includes("file");
const isRoom = child?.props?.className?.includes("room");
const className = isFile ? "isFile" : isRoom ? "isRoom" : "isFolder";
if (cards.length && cards.length === countTilesInRow) {
const listKey = uniqueid("list-item_");
addItemToList(listKey, className, true);
}
const cardKey = uniqueid("card-item_");
cards.push(<Card key={cardKey}>{child}</Card>);
}
}
});
const type = checkType(!!cards.length);
const otherClassName = type;
if (hasMoreFiles) {
// If cards elements are full, it will add the full line of loaders
if (cards.length === countTilesInRow) {
addItemToList("loaded-row", otherClassName, true);
}
// Added line of loaders
while (cards.length !== countTilesInRow) {
const key = `tiles-loader_${countTilesInRow - cards.length}`;
cards.push(
<Loaders.Tile
key={key}
className={`tiles-loader ${otherClassName}`}
isFolder={type === "isFolder"}
/>
);
}
addItemToList("loaded-row", otherClassName);
} else if (cards.length) {
// Adds loaders until the row is full
const listKey = uniqueid("list-item_");
addItemToList(listKey, otherClassName);
}
// console.log("InfiniteGrid render", list);
return (
<InfiniteLoaderComponent
viewAs="tile"
countTilesInRow={countTilesInRow}
filesLength={filesLength}
hasMoreFiles={hasMoreFiles}
itemCount={hasMoreFiles ? list.length + 1 : list.length}
loadMoreItems={fetchMoreFiles}
className={`TileList ${className}`}
selectedFolderId={selectedFolderId}
{...rest}
>
{list}
</InfiniteLoaderComponent>
);
};
export default inject(
({ filesStore, selectedFolderStore, treeFoldersStore }) => {
const {
filesList,
hasMoreFiles,
filterTotal,
fetchMoreFiles,
getCountTilesInRow,
roomsFilterTotal,
} = filesStore;
const { isRoomsFolder, isArchiveFolder } = treeFoldersStore;
const filesLength = filesList.length;
const isRooms =
isRoomsFolder ||
isArchiveFolder ||
window.location.href.includes("/rooms?");
return {
filesList,
hasMoreFiles,
filterTotal: isRooms ? roomsFilterTotal : filterTotal,
fetchMoreFiles,
filesLength,
getCountTilesInRow,
selectedFolderId: selectedFolderStore.id,
};
}
)(observer(InfiniteGrid));

View File

@ -0,0 +1,39 @@
import styled, { css } from "styled-components";
import { desktop, tablet } from "@docspace/components/utils/device";
const paddingCss = css`
@media ${desktop} {
margin-left: 1px;
padding-right: 0px;
}
@media ${tablet} {
margin-left: -1px;
}
`;
const StyledCard = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
height: ${({ cardHeight }) => `${cardHeight}px`};
`;
const StyledItem = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
gap: 14px 16px;
width: 100%;
@media ${tablet} {
gap: 14px;
}
${paddingCss};
`;
const StyledHeaderItem = styled.div`
height: 20px;
grid-column: -1 / 1;
`;
export { StyledCard, StyledItem, StyledHeaderItem };

View File

@ -1,11 +1,8 @@
/* eslint-disable react/display-name */
import React, { memo } from "react";
import React from "react";
import { inject, observer } from "mobx-react";
import { withTranslation } from "react-i18next";
import styled, { css } from "styled-components";
import PropTypes from "prop-types";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import Heading from "@docspace/components/heading";
import ContextMenu from "@docspace/components/context-menu";
import CustomScrollbarsVirtualList from "@docspace/components/scrollbar";
@ -18,6 +15,7 @@ import {
} from "@docspace/components/utils/device";
import { Base } from "@docspace/components/themes";
import InfiniteGrid from "./InfiniteGrid";
const paddingCss = css`
@media ${desktop} {
@ -46,10 +44,19 @@ const StyledGridWrapper = styled.div`
@media ${tablet} {
grid-gap: 14px;
}
.tiles-loader {
padding-top: 14px;
&:nth-of-type(n + 3) {
display: block;
}
}
`;
const StyledTileContainer = styled.div`
position: relative;
height: 100%;
.tile-item-wrapper {
position: relative;
@ -165,8 +172,6 @@ class TileContainer extends React.PureComponent {
super(props);
this.state = {
contextOptions: [],
isOpen: false,
selectedFilterData: {
sortId: props.filter.sortBy,
sortDirection: props.filter.sortOrder,
@ -174,54 +179,8 @@ class TileContainer extends React.PureComponent {
};
}
onRowContextClick = (options) => {
if (Array.isArray(options)) {
this.setState({
contextOptions: options,
});
}
};
toggleDropdown = () => {
this.setState((prev) => ({
isOpen: !prev.isOpen,
}));
};
componentDidMount() {
window.addEventListener("contextmenu", this.onRowContextClick);
}
componentWillUnmount() {
window.removeEventListener("contextmenu", this.onRowContextClick);
}
renderFolders = () => {
return <div />;
};
renderFiles = () => {
return <div />;
};
// eslint-disable-next-line react/prop-types
renderTile = memo(({ data, index, style }) => {
// eslint-disable-next-line react/prop-types
const options = data[index].props.contextOptions;
return (
<div
onContextMenu={this.onRowContextClick.bind(this, options)}
style={style}
>
{data[index]}
</div>
);
}, areEqual);
render() {
const {
itemHeight,
children,
useReactWindow,
id,
@ -231,66 +190,85 @@ class TileContainer extends React.PureComponent {
headingFiles,
} = this.props;
const { selectedFilterData } = this.state;
const Rooms = [];
const Folders = [];
const Files = [];
React.Children.map(children, (item, index) => {
React.Children.map(children, (item) => {
const { isFolder, isRoom, fileExst, id } = item.props.item;
if ((isFolder || id === -1) && !fileExst && !isRoom) {
Folders.push(
<div
className="tile-item-wrapper folder"
key={index}
onContextMenu={this.onRowContextClick.bind(
this,
item.props.contextOptions
)}
>
<div className="tile-item-wrapper folder" key={id}>
{item}
</div>
);
} else if (isRoom) {
Rooms.push(
<div
className="tile-item-wrapper folder"
key={index}
onContextMenu={this.onRowContextClick.bind(
this,
item.props.contextOptions
)}
>
<div className="tile-item-wrapper room" key={id}>
{item}
</div>
);
} else {
Files.push(
<div
className="tile-item-wrapper file"
key={index}
onContextMenu={this.onRowContextClick.bind(
this,
item.props.contextOptions
)}
>
<div className="tile-item-wrapper file" key={id}>
{item}
</div>
);
}
});
const renderList = ({ height, width }) => (
<List
className="list"
height={height}
width={width}
itemSize={itemHeight}
itemCount={children.length}
itemData={children}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderTile}
</List>
const renderTile = (
<>
{Rooms.length > 0 && (
<Heading
size="xsmall"
id={"room-tile-heading"}
className="tile-items-heading"
>
{"Rooms"}
</Heading>
)}
{Rooms.length > 0 ? (
useReactWindow ? (
Rooms
) : (
<StyledGridWrapper isRooms>{Rooms}</StyledGridWrapper>
)
) : null}
{Folders.length > 0 && (
<Heading
size="xsmall"
id={"folder-tile-heading"}
className="tile-items-heading"
>
{headingFolders}
</Heading>
)}
{Folders.length > 0 ? (
useReactWindow ? (
Folders
) : (
<StyledGridWrapper isFolders>{Folders}</StyledGridWrapper>
)
) : null}
{Files.length > 0 && (
<Heading size="xsmall" className="tile-items-heading">
{headingFiles}
</Heading>
)}
{Files.length > 0 ? (
useReactWindow ? (
Files
) : (
<StyledGridWrapper>{Files}</StyledGridWrapper>
)
) : null}
</>
);
return (
@ -299,64 +277,19 @@ class TileContainer extends React.PureComponent {
className={className}
style={style}
useReactWindow={useReactWindow}
isDesc={this.state.selectedFilterData.sortDirection === "desc"}
isDesc={selectedFilterData.sortDirection === "desc"}
>
{Rooms.length > 0 && (
<>
<Heading
size="xsmall"
id={"room-tile-heading"}
className="tile-items-heading"
>
{"Rooms"}
</Heading>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
<StyledGridWrapper isRooms>{Rooms}</StyledGridWrapper>
)}
</>
{useReactWindow ? (
<InfiniteGrid>{renderTile}</InfiniteGrid>
) : (
renderTile
)}
{Folders.length > 0 && (
<>
<Heading
size="xsmall"
id={"folder-tile-heading"}
className="tile-items-heading"
>
{headingFolders}
</Heading>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
<StyledGridWrapper isFolders>{Folders}</StyledGridWrapper>
)}
</>
)}
{Files.length > 0 && (
<>
<Heading size="xsmall" className="tile-items-heading">
{headingFiles}
</Heading>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
) : (
<StyledGridWrapper>{Files}</StyledGridWrapper>
)}
</>
)}
<ContextMenu targetAreaId={id} options={this.state.contextOptions} />
</StyledTileContainer>
);
}
}
TileContainer.propTypes = {
itemHeight: PropTypes.number,
manualHeight: PropTypes.string,
children: PropTypes.any.isRequired,
useReactWindow: PropTypes.bool,
className: PropTypes.string,
@ -365,54 +298,23 @@ TileContainer.propTypes = {
};
TileContainer.defaultProps = {
itemHeight: 50,
useReactWindow: true,
id: "rowContainer",
id: "tileContainer",
};
export default inject(
({ auth, filesStore, treeFoldersStore, selectedFolderStore }) => {
const {
fetchFiles,
filter,
setIsLoading,
setViewAs,
viewAs,
files,
folders,
createThumbnails,
} = filesStore;
const { user } = auth.userStore;
const { customNames, personal } = auth.settingsStore;
const { personal } = auth.settingsStore;
const { fetchFiles, filter, setIsLoading } = filesStore;
const { isFavoritesFolder, isRecentFolder } = treeFoldersStore;
const { search, filterType, authorType } = filter;
const isFiltered =
(!!files.length ||
!!folders.length ||
search ||
filterType ||
authorType) &&
!(treeFoldersStore.isPrivacyFolder && isMobile);
return {
customNames,
user,
selectedFolderId: selectedFolderStore.id,
selectedItem: filter.selectedItem,
personal,
fetchFiles,
filter,
viewAs,
isFiltered,
setIsLoading,
isFavoritesFolder,
isRecentFolder,
setIsLoading,
fetchFiles,
setViewAs,
createThumbnails,
personal,
selectedFolderId: selectedFolderStore.id,
};
}
)(observer(withTranslation(["Files", "Common"])(TileContainer)));

View File

@ -502,6 +502,7 @@ class PureHome extends React.Component {
showTitle,
showFilter,
frameConfig,
withPaging,
} = this.props;
if (window.parent && !frameConfig) {
@ -513,6 +514,7 @@ class PureHome extends React.Component {
<MediaViewer />
<DragTooltip />
<Section
withPaging={withPaging}
dragging={dragging}
withBodyScroll
withBodyAutoFocus={!isMobile}
@ -581,9 +583,11 @@ class PureHome extends React.Component {
<InfoPanelBodyContent />
</Section.InfoPanelBody>
<Section.SectionPaging>
<SectionPagingContent tReady={tReady} />
</Section.SectionPaging>
{withPaging && (
<Section.SectionPaging>
<SectionPagingContent tReady={tReady} />
</Section.SectionPaging>
)}
</Section>
</>
);
@ -634,6 +638,7 @@ export default inject(
createRoom,
refreshFiles,
setViewAs,
withPaging,
} = filesStore;
const {
@ -770,6 +775,7 @@ export default inject(
createRoom,
refreshFiles,
setViewAs,
withPaging,
};
}
)(withRouter(observer(Home)));

View File

@ -199,6 +199,18 @@ class FilesActionStore {
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
};
updateFilesAfterDelete = () => {
const { setSelected } = this.filesStore;
const {
clearSecondaryProgressData,
} = this.uploadDataStore.secondaryProgressDataStore;
setSelected("close");
this.dialogsStore.setIsFolderActions(false);
setTimeout(() => clearSecondaryProgressData(), TIMEOUT);
};
deleteAction = async (
translations,
newSelection = null,
@ -268,7 +280,28 @@ class FilesActionStore {
label: translations.deleteOperation,
};
await this.uploadDataStore.loopFilesOperations(data, pbData);
this.updateCurrentFolder(fileIds, folderIds, false);
const showToast = () => {
if (isRecycleBinFolder) {
return toastr.success(translations.deleteFromTrash);
}
if (selection.length > 1 || isThirdPartyFile) {
return toastr.success(translations.deleteSelectedElem);
}
if (selection[0].fileExst) {
return toastr.success(translations.FileRemoved);
}
return toastr.success(translations.FolderRemoved);
};
if (this.filesStore.withPaging) {
this.updateCurrentFolder(fileIds, folderIds, false);
showToast();
} else {
this.updateFilesAfterDelete(folderIds);
this.filesStore.removeFiles(fileIds, folderIds, showToast);
}
if (currentFolderId) {
const { socketHelper } = this.authStore.settingsStore;
@ -278,18 +311,6 @@ class FilesActionStore {
data: currentFolderId,
});
}
if (isRecycleBinFolder) {
return toastr.success(translations.deleteFromTrash);
}
if (selection.length > 1 || isThirdPartyFile) {
return toastr.success(translations.deleteSelectedElem);
}
if (selection[0].fileExst) {
return toastr.success(translations.FileRemoved);
}
return toastr.success(translations.FolderRemoved);
})
.finally(() => {
clearActiveOperations(fileIds, folderIds);
@ -469,35 +490,12 @@ class FilesActionStore {
return this.downloadFiles(fileIds, folderIds, label);
};
editCompleteAction = async (id, selectedItem, isCancelled = false, type) => {
const {
filter,
folders,
files,
editCompleteAction = async (selectedItem, type, isFolder = false) => {
if (type === FileAction.Create) {
this.filesStore.addFile(selectedItem, isFolder);
}
fetchFiles,
setIsLoading,
} = this.filesStore;
const { treeFolders, setTreeFolders } = this.treeFoldersStore;
const items = [...folders, ...files];
const item = items.find((o) => o.id === id && !o.fileExst); //TODO: maybe need files find and folders find, not at one function?
if (type === FileAction.Create || type === FileAction.Rename) {
setIsLoading(true);
if (!isCancelled) {
const data = await fetchFiles(this.selectedFolderStore.id, filter);
const newItem = (item && item.id) === -1 ? null : item; //TODO: not add new folders?
if (!selectedItem.fileExst && !selectedItem.contentLength) {
const path = data.selectedFolder.pathParts;
const folders = await getSubfolders(this.selectedFolderStore.id);
loopTreeFolders(path, treeFolders, folders, null, newItem);
setTreeFolders(treeFolders);
}
}
setIsLoading(false);
type === FileAction.Rename &&
this.onSelectItem(
{
@ -619,14 +617,21 @@ class FilesActionStore {
if (isFile) {
addActiveItems([itemId]);
this.isMediaOpen();
return deleteFile(itemId)
.then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
const data = res[0] ? res[0] : null;
await this.uploadDataStore.loopFilesOperations(data, pbData);
return deleteFile(itemId).then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
const data = res[0] ? res[0] : null;
await this.uploadDataStore.loopFilesOperations(data, pbData);
if (this.filesStore.withPaging) {
this.updateCurrentFolder([itemId]);
})
.then(() => toastr.success(translations.successRemoveFile));
toastr.success(translations.successRemoveFile);
} else {
this.updateFilesAfterDelete();
this.filesStore.removeFiles([itemId], null, () =>
toastr.success(translations.successRemoveFile)
);
}
});
} else if (isRoom) {
const items = Array.isArray(itemId) ? itemId : [itemId];
addActiveItems(null, items);
@ -643,15 +648,23 @@ class FilesActionStore {
.then(() => toastr.success(translations?.successRemoveRoom));
} else {
addActiveItems(null, [itemId]);
return deleteFolder(itemId)
.then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
const data = res[0] ? res[0] : null;
await this.uploadDataStore.loopFilesOperations(data, pbData);
return deleteFolder(itemId).then(async (res) => {
if (res[0]?.error) return Promise.reject(res[0].error);
const data = res[0] ? res[0] : null;
await this.uploadDataStore.loopFilesOperations(data, pbData);
if (this.filesStore.withPaging) {
this.updateCurrentFolder(null, [itemId]);
getIsEmptyTrash();
})
.then(() => toastr.success(translations.successRemoveFolder));
toastr.success(translations.successRemoveFolder);
} else {
this.updateFilesAfterDelete([itemId]);
this.filesStore.removeFiles([itemId], null, () =>
toastr.success(translations.successRemoveFolder)
);
}
getIsEmptyTrash();
});
}
};

View File

@ -12,7 +12,7 @@ import {
import history from "@docspace/common/history";
import { combineUrl } from "@docspace/common/utils";
import { updateTempContent } from "@docspace/common/utils";
import { isMobile } from "react-device-detect";
import { isMobile, isMobileOnly } from "react-device-detect";
import toastr from "client/toastr";
import config from "PACKAGE_FILE";
@ -25,6 +25,7 @@ import {
getCategoryType,
getCategoryTypeByFolderType,
} from "SRC_DIR/helpers/utils";
import { isDesktop } from "@docspace/components/utils/device";
import { getContextMenuKeysByType } from "SRC_DIR/helpers/plugins";
import { PluginContextMenuItemType } from "SRC_DIR/helpers/plugins/constants";
@ -93,6 +94,8 @@ class FilesStore {
pageItemsLength = null;
isHidePagination = false;
trashIsEmpty = false;
filesIsLoading = false;
withPaging = false;
constructor(
authStore,
@ -132,7 +135,7 @@ class FilesStore {
const newFiles = [file, ...this.files];
if (newFiles.length > this.filter.pageCount) {
if (newFiles.length > this.filter.pageCount && this.withPaging) {
newFiles.pop(); // Remove last
}
@ -181,6 +184,10 @@ class FilesStore {
})
);
const newFilter = this.filter.clone();
newFilter.total -= 1;
this.setFilter(newFilter);
// Hide pagination when deleting files
runInAction(() => {
this.isHidePagination = true;
@ -524,9 +531,7 @@ class FilesStore {
};
setHotkeyCaret = (hotkeyCaret) => {
if (hotkeyCaret) {
this.hotkeyCaret = hotkeyCaret;
} else if (this.hotkeyCaret) {
if (hotkeyCaret || this.hotkeyCaret) {
this.hotkeyCaret = hotkeyCaret;
}
};
@ -570,6 +575,8 @@ class FilesStore {
const value = `${filter.sortBy},${filter.pageCount},${filter.sortOrder}`;
localStorage.setItem(key, value);
if (!this.withPaging) filter.pageCount = 100;
this.setFilterUrl(filter, true);
this.roomsFilter = filter;
@ -587,6 +594,7 @@ class FilesStore {
};
setFilter = (filter) => {
if (!this.withPaging) filter.pageCount = 100;
this.filter = filter;
};
@ -659,9 +667,9 @@ class FilesStore {
treeFolders,
setSelectedNode,
getSubfolders,
selectedTreeNode,
} = this.treeFoldersStore;
const { id } = this.selectedFolderStore;
this.scrollToTop();
const filterData = filter ? filter.clone() : FilesFilter.getDefault();
filterData.folder = folderId;
@ -678,6 +686,11 @@ class FilesStore {
filterData.sortOrder = splitFilter[2];
}
if (!this.withPaging) {
filterData.page = 0;
filterData.pageCount = 100;
}
setSelectedNode([folderId + ""]);
//TODO: fix @my
@ -841,6 +854,11 @@ class FilesStore {
filterData.sortOrder = splitFilter[2];
}
if (!this.withPaging) {
filterData.page = 0;
filterData.pageCount = 100;
}
if (folderId) setSelectedNode([folderId + ""]);
const searchArea = folderId
@ -1655,6 +1673,63 @@ class FilesStore {
this.folders[idx].pinned = !this.folders[idx].pinned;
};
scrollToTop = () => {
if (this.withPaging) return;
const scrollElm = isMobileOnly
? document.querySelector("#customScrollBar > .scroll-body")
: document.querySelector("#sectionScroll > .scroll-body");
scrollElm && scrollElm.scrollTo(0, 0);
};
addFile = (item, isFolder) => {
const filter = this.filter.clone();
filter.total += 1;
this.setFilter(filter);
isFolder ? this.folders.unshift(item) : this.files.unshift(item);
this.scrollToTop();
};
removeFiles = (fileIds, folderIds, showToast) => {
const newFilter = this.filter.clone();
const deleteCount = fileIds.length + folderIds.length;
newFilter.startIndex =
(newFilter.page + 1) * newFilter.pageCount - deleteCount;
newFilter.pageCount = deleteCount;
api.files
.getFolder(newFilter.folder, newFilter)
.then((res) => {
const files = fileIds
? this.files.filter((x) => !fileIds.includes(x.id))
: [];
const folders = folderIds
? this.folders.filter((x) => !folderIds.includes(x.id))
: [];
const newFiles = [...files, ...res.files];
const newFolders = [...folders, ...res.folders];
const filter = this.filter.clone();
filter.total = res.total;
runInAction(() => {
this.setFilter(filter);
this.setFiles(newFiles);
this.setFolders(newFolders);
});
showToast && showToast();
})
.catch(() => {
toastr.error(err);
console.log("Need page reload");
});
};
updateFile = (fileId, title) => {
return api.files
.updateFile(fileId, title)
@ -2497,6 +2572,73 @@ class FilesStore {
setTrashIsEmpty = (isEmpty) => {
this.trashIsEmpty = isEmpty;
};
get roomsFilterTotal() {
return this.roomsFilter.total;
}
get filterTotal() {
return this.filter.total;
}
get hasMoreFiles() {
const { Shared, Archive } = CategoryType;
const isRoom = this.categoryType == Shared || this.categoryType == Archive;
const filterTotal = isRoom ? this.roomsFilter.total : this.filter.total;
console.log("hasMoreFiles isRoom", isRoom);
console.log("hasMoreFiles filesList", this.filesList.length);
console.log("hasMoreFiles this.filter.total", this.filter.total);
console.log("hasMoreFiles this.roomsFilter.total", this.roomsFilter.total);
console.log("hasMoreFiles filterTotal", filterTotal);
console.log("hasMoreFiles", this.filesList.length < filterTotal);
console.log("----------------------------");
return this.filesList.length < filterTotal;
}
setFilesIsLoading = (filesIsLoading) => {
this.filesIsLoading = filesIsLoading;
};
fetchMoreFiles = async () => {
if (!this.hasMoreFiles || this.filesIsLoading || this.isLoading) return;
const { Shared, Archive } = CategoryType;
const isRoom = this.categoryType == Shared || this.categoryType == Archive;
this.setFilesIsLoading(true);
// console.log("fetchMoreFiles");
const newFilter = isRoom ? this.roomsFilter.clone() : this.filter.clone();
newFilter.page += 1;
if (isRoom) this.setRoomsFilter(newFilter);
else this.setFilter(newFilter);
const newFiles = isRoom
? await api.rooms.getRooms(newFilter)
: await api.files.getFolder(newFilter.folder, newFilter);
runInAction(() => {
this.setFiles([...this.files, ...newFiles.files]);
this.setFolders([...this.folders, ...newFiles.folders]);
this.setFilesIsLoading(false);
});
};
//Duplicate of countTilesInRow, used to update the number of tiles in a row after the window is resized.
getCountTilesInRow = () => {
const isDesktopView = isDesktop();
const tileGap = isDesktopView ? 16 : 14;
const minTileWidth = 216 + tileGap;
const sectionPadding = isDesktopView ? 24 : 16;
const body = document.getElementById("section");
const sectionWidth = body ? body.offsetWidth - sectionPadding : 0;
return Math.floor(sectionWidth / minTileWidth);
};
}
export default FilesStore;

View File

@ -14,6 +14,8 @@ class HotkeyStore {
treeFoldersStore;
uploadDataStore;
elemOffset = 0;
constructor(
filesStore,
dialogsStore,
@ -31,6 +33,29 @@ class HotkeyStore {
this.uploadDataStore = uploadDataStore;
}
scrollToCaret = () => {
const { offsetTop, item } = this.getItemOffset();
const scroll = document.getElementsByClassName("section-scroll")[0];
const scrollRect = scroll.getBoundingClientRect();
if (item && item[0]) {
const el = item[0];
const rect = el.getBoundingClientRect();
if (
scrollRect.top + scrollRect.height - rect.height > rect.top &&
scrollRect.top < rect.top + el.offsetHeight
) {
//console.log("element is visible");
} else {
scroll.scrollTo(0, offsetTop - scrollRect.height / 2);
//console.log("element is not visible");
}
} else {
scroll.scrollTo(0, this.elemOffset - scrollRect.height / 2);
}
};
activateHotkeys = (e) => {
if (
this.dialogsStore.someDialogIsOpen ||
@ -50,7 +75,7 @@ class HotkeyStore {
e.preventDefault();
}
const { selection: s, hotkeyCaret, viewAs, filesList } = this.filesStore;
const { selection: s, hotkeyCaret, filesList } = this.filesStore;
const selection = s.length ? s : filesList;
if (!hotkeyCaret) {
@ -59,11 +84,28 @@ class HotkeyStore {
}
if (!hotkeyCaret && selection.length) {
this.filesStore.setHotkeyCaret(selection[0]);
this.setCaret(selection[0]);
this.filesStore.setHotkeyCaretStart(selection[0]);
}
if (!hotkeyCaret || isDefaultKeys) return;
if (!hotkeyCaret || isDefaultKeys) return e;
};
setCaret = (caret) => {
//TODO: inf-scroll
// const id = caret.isFolder ? `folder_${caret.id}` : `file_${caret.id}`;
// const elem = document.getElementById(id);
// if (!elem) return;
this.filesStore.setHotkeyCaret(caret);
this.scrollToCaret();
const { offsetTop } = this.getItemOffset();
if (offsetTop) this.elemOffset = offsetTop;
};
getItemOffset = () => {
const { hotkeyCaret, viewAs } = this.filesStore;
let item = document.getElementsByClassName(
`${hotkeyCaret.id}_${hotkeyCaret.fileExst}`
@ -75,35 +117,38 @@ class HotkeyStore {
if (item && item[0]) {
const el = item[0];
const rect = el.getBoundingClientRect();
const scroll = document.getElementsByClassName("section-scroll")[0];
const scrollRect = scroll.getBoundingClientRect();
if (
scrollRect.top + scrollRect.height - rect.height > rect.top &&
scrollRect.top < rect.top + el.offsetHeight
) {
//console.log("element is visible");
} else {
scroll.scrollTo(0, el.offsetTop - scrollRect.height / 2);
//console.log("element is not visible");
}
const offset = el.closest(".window-item")?.offsetTop;
const offsetTop = offset
? offset
: viewAs === "tile"
? el.parentElement.parentElement.offsetTop
: el.offsetTop;
return { offsetTop, item };
}
return { offsetTop: null, item: null };
};
selectFirstFile = () => {
const { filesList } = this.filesStore;
if (filesList.length) {
// scroll to first element
const scroll = document.querySelector("#sectionScroll > .scroll-body");
scroll.scrollTo(0, 0);
this.filesStore.setSelection([filesList[0]]);
this.filesStore.setHotkeyCaret(filesList[0]);
this.setCaret(filesList[0]);
this.filesStore.setHotkeyCaretStart(filesList[0]);
}
};
setSelectionWithCaret = (selection) => {
this.filesStore.setSelection(selection);
this.filesStore.setHotkeyCaret(selection[0]);
this.setCaret(selection[0]);
this.filesStore.setHotkeyCaretStart(selection[0]);
};
@ -112,7 +157,6 @@ class HotkeyStore {
selection,
setSelection,
hotkeyCaret,
setHotkeyCaret,
setHotkeyCaretStart,
} = this.filesStore;
@ -128,7 +172,7 @@ class HotkeyStore {
setHotkeyCaretStart(hotkeyCaret);
} else {
if (selection.length) {
setHotkeyCaret(selection[0]);
this.setCaret(selection[0]);
setHotkeyCaretStart(selection[0]);
} else this.selectFirstFile();
}
@ -197,7 +241,6 @@ class HotkeyStore {
setHotkeyCaretStart,
hotkeyCaret,
viewAs,
setHotkeyCaret,
deselectFile,
} = this.filesStore;
@ -213,14 +256,14 @@ class HotkeyStore {
...this.selectionsDown,
...[hotkeyCaretStart ? hotkeyCaretStart : hotkeyCaret],
]);
setHotkeyCaret(this.nextForTileDown);
this.setCaret(this.nextForTileDown);
} else if (this.nextFile) {
if (selection.findIndex((f) => f.id === this.nextFile.id) !== -1) {
deselectFile(hotkeyCaret);
} else {
setSelection([...selection, ...[this.nextFile]]);
}
setHotkeyCaret(this.nextFile);
this.setCaret(this.nextFile);
}
};
@ -232,7 +275,6 @@ class HotkeyStore {
setHotkeyCaretStart,
hotkeyCaret,
viewAs,
setHotkeyCaret,
deselectFile,
} = this.filesStore;
@ -248,7 +290,7 @@ class HotkeyStore {
...this.selectionsUp,
...[hotkeyCaretStart ? hotkeyCaretStart : hotkeyCaret],
]);
setHotkeyCaret(this.prevForTileUp);
this.setCaret(this.prevForTileUp);
} else if (this.prevFile) {
if (selection.findIndex((f) => f.id === this.prevFile.id) !== -1) {
deselectFile(hotkeyCaret);
@ -256,7 +298,7 @@ class HotkeyStore {
setSelection([...[this.prevFile], ...selection]);
}
setHotkeyCaret(this.prevFile);
this.setCaret(this.prevFile);
}
};
@ -266,7 +308,6 @@ class HotkeyStore {
setSelection,
hotkeyCaret,
viewAs,
setHotkeyCaret,
deselectFile,
hotkeyCaretStart,
filesList,
@ -307,7 +348,7 @@ class HotkeyStore {
if (viewAs === "tile") {
setSelection(nextForTileRight);
setHotkeyCaret(nextFile);
this.setCaret(nextFile);
} else if (nextFile) {
if (selection.findIndex((f) => f.id === nextFile.id) !== -1) {
deselectFile(hotkeyCaret);
@ -315,7 +356,7 @@ class HotkeyStore {
setSelection([...selection, ...[nextFile]]);
}
setHotkeyCaret(nextFile);
this.setCaret(nextFile);
}
};
@ -325,7 +366,6 @@ class HotkeyStore {
setSelection,
hotkeyCaret,
viewAs,
setHotkeyCaret,
deselectFile,
filesList,
hotkeyCaretStart,
@ -366,7 +406,7 @@ class HotkeyStore {
if (viewAs === "tile") {
setSelection(prevForTileLeft);
setHotkeyCaret(prevFile);
this.setCaret(prevFile);
} else if (prevFile) {
if (selection.findIndex((f) => f.id === prevFile.id) !== -1) {
deselectFile(hotkeyCaret);
@ -374,30 +414,30 @@ class HotkeyStore {
setSelection([...[prevFile], ...selection]);
}
setHotkeyCaret(prevFile);
this.setCaret(prevFile);
}
};
moveCaretBottom = () => {
const { viewAs, setHotkeyCaret } = this.filesStore;
const { viewAs } = this.filesStore;
if (viewAs === "tile") setHotkeyCaret(this.nextForTileDown);
else if (this.nextFile) setHotkeyCaret(this.nextFile);
if (viewAs === "tile") this.setCaret(this.nextForTileDown);
else if (this.nextFile) this.setCaret(this.nextFile);
};
moveCaretUpper = () => {
const { viewAs, setHotkeyCaret } = this.filesStore;
const { viewAs } = this.filesStore;
if (viewAs === "tile") setHotkeyCaret(this.prevForTileUp);
else if (this.prevFile) setHotkeyCaret(this.prevFile);
if (viewAs === "tile") this.setCaret(this.prevForTileUp);
else if (this.prevFile) this.setCaret(this.prevFile);
};
moveCaretLeft = () => {
if (this.prevFile) this.filesStore.setHotkeyCaret(this.prevFile);
if (this.prevFile) this.setCaret(this.prevFile);
};
moveCaretRight = () => {
if (this.nextFile) this.filesStore.setHotkeyCaret(this.nextFile);
if (this.nextFile) this.setCaret(this.nextFile);
};
openItem = () => {
@ -411,14 +451,13 @@ class HotkeyStore {
const {
filesList,
hotkeyCaret,
setHotkeyCaret,
setHotkeyCaretStart,
setSelected,
} = this.filesStore;
setSelected("all");
if (!hotkeyCaret) {
setHotkeyCaret(filesList[0]);
this.setCaret(filesList[0]);
setHotkeyCaretStart(filesList[0]);
}
};
@ -489,11 +528,14 @@ class HotkeyStore {
get caretIndex() {
const { filesList, hotkeyCaret, selection } = this.filesStore;
const id =
const item =
selection.length && selection.length === 1 && !hotkeyCaret
? selection[0].id
: hotkeyCaret?.id;
const caretIndex = filesList.findIndex((f) => f.id === id);
? selection[0]
: hotkeyCaret;
const caretIndex = filesList.findIndex(
(f) => f.id === item?.id && f.isFolder === item?.isFolder
);
if (caretIndex !== -1) return caretIndex;
else return null;

View File

@ -581,6 +581,7 @@ class UploadDataStore {
filter,
setFilter,
} = this.filesStore;
if (window.location.pathname.indexOf("/history") === -1) {
const newFiles = files;
const newFolders = folders;
@ -617,7 +618,7 @@ class UploadDataStore {
newFolders.unshift(folderInfo);
setFolders(newFolders);
const newFilter = filter;
newFilter.total = newFilter.total += 1;
newFilter.total += 1;
setFilter(newFilter);
}
} else {
@ -626,7 +627,7 @@ class UploadDataStore {
newFiles.unshift(currentFile.fileInfo);
setFiles(newFiles);
const newFilter = filter;
newFilter.total = newFilter.total += 1;
newFilter.total += 1;
setFilter(newFilter);
} else if (!this.settingsStore.storeOriginalFiles) {
newFiles[fileIndex] = currentFile.fileInfo;
@ -640,7 +641,7 @@ class UploadDataStore {
filter.filterType ||
filter.authorType ||
filter.search ||
filter.page !== 0;
(this.filesStore.withPaging && filter.page !== 0);
if ((!currentFile && !folderInfo) || isFiltered) return;
if (folderInfo && this.selectedFolderStore.id === folderInfo.id) return;
@ -653,7 +654,7 @@ class UploadDataStore {
}
}
if (filter.total >= filter.pageCount) {
if (filter.total >= filter.pageCount && this.filesStore.withPaging) {
if (files.length) {
fileIndex === -1 && newFiles.pop();
addNewFile();
@ -926,7 +927,7 @@ class UploadDataStore {
};
finishUploadFiles = () => {
const { fetchFiles, filter } = this.filesStore;
const { fetchFiles, filter, withPaging } = this.filesStore;
const totalErrorsCount = sumBy(this.files, (f) => (f.error ? 1 : 0));
@ -947,7 +948,7 @@ class UploadDataStore {
if (this.files.length > 0) {
const toFolderId = this.files[0]?.toFolderId;
fetchFiles(toFolderId, filter);
withPaging && fetchFiles(toFolderId, filter);
if (toFolderId) {
const { socketHelper } = this.filesStore.settingsStore;

View File

@ -135,6 +135,7 @@ class FilesFilter {
sortBy,
sortOrder,
withSubfolders,
startIndex,
} = this;
const isFilterSet =
@ -148,7 +149,7 @@ class FilesFilter {
const dtoFilter = {
count: pageCount,
startIndex: this.getStartIndex(),
startIndex: startIndex ? startIndex : this.getStartIndex(),
page: page,
sortby: sortBy,
sortOrder: sortOrder,

View File

@ -150,6 +150,7 @@ class Section extends React.Component {
this.intervalHandler = null;
this.scroll = null;
this.selectoRef = React.createRef(null);
}
componentDidUpdate(prevProps) {
@ -224,6 +225,7 @@ class Section extends React.Component {
isInfoPanelAvailable,
settingsStudio,
clearUploadedFilesHistory,
withPaging,
} = this.props;
let sectionHeaderContent = null;
@ -362,6 +364,8 @@ class Section extends React.Component {
viewAs={viewAs}
isHomepage={isHomepage}
settingsStudio={settingsStudio}
withPaging={withPaging}
selectoRef={this.selectoRef}
>
{isMobile && (
<StyledMainBar
@ -498,11 +502,12 @@ class Section extends React.Component {
return (
<>
{renderSection()}
{!isMobile && uploadFiles && !dragging && (
{!isMobile && uploadFiles && !dragging && withPaging && (
<StyledSelectoWrapper>
<Selecto
boundContainer={".section-wrapper"}
dragContainer={".section-body"}
ref={this.selectoRef}
boundContainer=".section-wrapper-content"
dragContainer=".section-wrapper"
selectableTargets={[".files-item"]}
hitRate={0}
selectByClick={false}
@ -546,6 +551,7 @@ Section.propTypes = {
isHomepage: PropTypes.bool,
isInfoPanelAvailable: PropTypes.bool,
settingsStudio: PropTypes.bool,
withPaging: PropTypes.bool,
};
Section.defaultProps = {
@ -553,6 +559,7 @@ Section.defaultProps = {
withBodyAutoFocus: false,
isInfoPanelAvailable: true,
settingsStudio: false,
withPaging: true,
};
Section.InfoPanelHeader = InfoPanelHeader;

View File

@ -10,33 +10,45 @@ import Scrollbar from "@docspace/components/scrollbar";
import DragAndDrop from "@docspace/components/drag-and-drop";
import {
tablet,
mobile,
desktop,
smallTablet,
} from "@docspace/components/utils/device";
const settingsStudioStyles = css`
${({ settingsStudio }) =>
settingsStudio
? css`
padding: 0 7px 16px 20px;
@media ${tablet} {
padding: 0 0 16px 24px;
}
@media ${smallTablet} {
padding: 8px 0 16px 24px;
}
`
: css`
@media ${tablet} {
padding: ${({ viewAs, withPaging }) =>
viewAs === "tile"
? "19px 0 16px 24px"
: withPaging
? "19px 0 16px 24px"
: "19px 0 16px 8px"};
}
`}
`;
const paddingStyles = css`
padding: ${(props) =>
props.settingsStudio
? "0 7px 16px 20px"
: props.viewAs === "row"
? "19px 3px 16px 16px"
padding: ${({ viewAs, withPaging }) =>
viewAs === "row"
? withPaging
? "19px 3px 16px 16px"
: "19px 3px 16px 0px"
: "19px 3px 16px 20px"};
@media ${tablet} {
padding: ${(props) =>
props.settingsStudio ? "0 0 16px 24px" : "19px 0 16px 24px"};
}
@media ${smallTablet} {
padding: ${(props) =>
props.settingsStudio ? "8px 0 16px 24px" : "19px 0 16px 24px"};
}
@media ${mobile} {
padding: ${(props) =>
props.settingsStudio ? "8px 0 16px 24px" : "19px 0 16px 24px"};
}
${settingsStudioStyles};
${isMobile &&
css`
@ -60,6 +72,7 @@ const commonStyles = css`
border-top: none;
.section-wrapper {
height: 100%;
${(props) =>
!props.withScroll &&
`display: flex; flex-direction: column; height: 100%; box-sizing:border-box`};
@ -134,6 +147,7 @@ const StyledSectionBody = styled.div`
const StyledDropZoneBody = styled(DragAndDrop)`
max-width: 100vw !important;
${commonStyles} .drag-and-drop {
user-select: none;
height: 100%;
@ -188,6 +202,12 @@ class SectionBody extends React.Component {
this.focusRef = null;
}
onScroll = (e) => {
this.props.selectoRef.current &&
this.props.selectoRef.current.checkScroll();
return e;
};
render() {
//console.log(" SectionBody render" );
const {
@ -202,6 +222,7 @@ class SectionBody extends React.Component {
isDesktop,
isHomepage,
settingsStudio,
withPaging,
} = this.props;
const focusProps = autoFocus
@ -221,11 +242,17 @@ class SectionBody extends React.Component {
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}
withPaging={withPaging}
className="section-body"
>
{withScroll ? (
!isMobileOnly ? (
<Scrollbar scrollclass="section-scroll" stype="mediumBlack">
<Scrollbar
id="sectionScroll"
scrollclass="section-scroll"
stype="mediumBlack"
onScroll={this.onScroll}
>
<div className="section-wrapper">
<div className="section-wrapper-content" {...focusProps}>
{children}
@ -256,10 +283,11 @@ class SectionBody extends React.Component {
isLoaded={isLoaded}
isDesktop={isDesktop}
settingsStudio={settingsStudio}
withPaging={withPaging}
>
{withScroll ? (
!isMobileOnly ? (
<Scrollbar stype="mediumBlack">
<Scrollbar id="sectionScroll" stype="mediumBlack">
<div className="section-wrapper">
<div className="section-wrapper-content" {...focusProps}>
{children}

View File

@ -32,7 +32,7 @@
"react-resize-detector": "^6.7.6",
"react-router": "^5.2.1",
"react-router-dom": "^5.3.0",
"react-selecto": "^1.12.0",
"react-selecto": "^1.18.2",
"react-tooltip": "^4.2.21",
"react-viewer": "^3.2.2",
"react-virtualized-auto-sizer": "^1.0.6",

View File

@ -0,0 +1,103 @@
import React, { useCallback, useEffect, createRef } from "react";
import { InfiniteLoader, WindowScroller } from "react-virtualized";
import { StyledList } from "./StyledInfiniteLoader";
const GridComponent = ({
hasMoreFiles,
filesLength,
itemCount,
loadMoreItems,
onScroll,
countTilesInRow,
children,
className,
scroll,
selectedFolderId,
}) => {
const loaderRef = createRef();
useEffect(() => {
setTimeout(() => loaderRef?.current?.resetLoadMoreRowsCache(true), 1000);
}, [loaderRef, selectedFolderId, filesLength]);
const isItemLoaded = useCallback(
({ index }) => {
return !hasMoreFiles || (index + 1) * countTilesInRow < filesLength;
},
[filesLength, hasMoreFiles, countTilesInRow]
);
const renderTile = ({ index, style, key }) => {
return (
<div className="window-item" style={style} key={key}>
{children[index]}
</div>
);
};
const getItemSize = ({ index }) => {
const itemClassNames = children[index]?.props?.className;
const isFile = itemClassNames?.includes("isFile");
const isFolder = itemClassNames?.includes("isFolder");
const isRoom = itemClassNames?.includes("isRoom");
const isFolderHeader = itemClassNames?.includes("folder_header");
const horizontalGap = 16;
const verticalGap = 14;
const headerMargin = 15;
const folderHeight = 64 + verticalGap;
const roomHeight = 122 + verticalGap;
const fileHeight = 220 + horizontalGap;
const titleHeight = 20 + headerMargin + (isFolderHeader ? 0 : 11);
if (isRoom) return roomHeight;
if (isFolder) return folderHeight;
if (isFile) return fileHeight;
return titleHeight;
};
return (
<InfiniteLoader
isRowLoaded={isItemLoaded}
rowCount={itemCount}
loadMoreRows={loadMoreItems}
ref={loaderRef}
>
{({ onRowsRendered, registerChild }) => (
<WindowScroller scrollElement={scroll}>
{({ height, isScrolling, onChildScroll, scrollTop }) => {
if (height === undefined) {
height = scroll.getBoundingClientRect().height;
}
const width =
document.getElementById("tileContainer")?.getBoundingClientRect()
.width ?? 0;
return (
<StyledList
autoHeight
height={height}
onRowsRendered={onRowsRendered}
ref={registerChild}
rowCount={children.length}
rowHeight={getItemSize}
rowRenderer={renderTile}
width={width}
className={className}
isScrolling={isScrolling}
onChildScroll={onChildScroll}
scrollTop={scrollTop}
overscanRowCount={3}
onScroll={onScroll}
/>
);
}}
</WindowScroller>
)}
</InfiniteLoader>
);
};
export default GridComponent;

View File

@ -0,0 +1,36 @@
import React from "react";
import PropTypes from "prop-types";
import { isMobileOnly } from "react-device-detect";
import ListComponent from "./List";
import GridComponent from "./Grid";
import { isMobile } from "../utils/device";
const InfiniteLoaderComponent = (props) => {
const { viewAs } = props;
const scroll = isMobileOnly
? document.querySelector("#customScrollBar > .scroll-body")
: document.querySelector("#sectionScroll > .scroll-body");
if (viewAs === "row" && scroll) scroll.style.paddingRight = 0;
else scroll.style.paddingRight = isMobile() ? "8px" : "17px";
return viewAs === "tile" ? (
<GridComponent scroll={scroll} {...props} />
) : (
<ListComponent scroll={scroll} {...props} />
);
};
InfiniteLoaderComponent.propTypes = {
viewAs: PropTypes.string.isRequired,
hasMoreFiles: PropTypes.bool.isRequired,
filesLength: PropTypes.number.isRequired,
itemCount: PropTypes.number.isRequired,
loadMoreItems: PropTypes.func.isRequired,
itemSize: PropTypes.number,
children: PropTypes.any.isRequired,
/** Called when the list scroll positions changes */
onScroll: PropTypes.func,
};
export default InfiniteLoaderComponent;

View File

@ -0,0 +1,139 @@
import React, { useCallback, useEffect, createRef } from "react";
import { InfiniteLoader, WindowScroller } from "react-virtualized";
import Loaders from "@docspace/common/components/Loaders";
import { StyledList } from "./StyledInfiniteLoader";
const ListComponent = ({
viewAs,
hasMoreFiles,
filesLength,
itemCount,
onScroll,
loadMoreItems,
itemSize,
columnStorageName,
columnInfoPanelStorageName,
children,
className,
scroll,
infoPanelVisible,
selectedFolderId,
}) => {
const loaderRef = createRef();
useEffect(() => {
setTimeout(() => loaderRef?.current?.resetLoadMoreRowsCache(true), 1000);
}, [loaderRef, selectedFolderId, filesLength]);
const renderRow = ({ key, index, style }) => {
const isLoaded = isItemLoaded({ index });
if (!isLoaded) return getLoader(style, key);
return (
<div className="row-list-item window-item" style={style} key={key}>
{children[index]}
</div>
);
};
const isItemLoaded = useCallback(
({ index }) => !hasMoreFiles || index < filesLength,
[filesLength, hasMoreFiles]
);
const renderTable = ({ index, style, key }) => {
const storageSize = infoPanelVisible
? localStorage.getItem(columnInfoPanelStorageName)
: localStorage.getItem(columnStorageName);
const isLoaded = isItemLoaded({ index });
if (!isLoaded) return getLoader(style, key);
return (
<div
className="table-list-item window-item"
style={{
...style,
display: "grid",
gridTemplateColumns: storageSize,
}}
key={key}
>
{children[index]}
</div>
);
};
const getLoader = (style, key) => {
switch (viewAs) {
case "table":
return (
<Loaders.TableLoader
key={key}
style={style}
className="table-container_body-loader"
count={1}
/>
);
case "row":
return (
<Loaders.Rows
key={key}
style={style}
className="row-loader"
count={1}
/>
);
default:
return <></>;
}
};
return (
<InfiniteLoader
isRowLoaded={isItemLoaded}
rowCount={itemCount}
loadMoreRows={loadMoreItems}
ref={loaderRef}
>
{({ onRowsRendered, registerChild }) => (
<WindowScroller scrollElement={scroll}>
{({ height, isScrolling, onChildScroll, scrollTop }) => {
if (height === undefined) {
height = scroll.getBoundingClientRect().height;
}
const viewId =
viewAs === "table" ? "table-container" : "rowContainer";
const width =
document.getElementById(viewId)?.getBoundingClientRect().width ??
0;
return (
<StyledList
autoHeight
height={height}
onRowsRendered={onRowsRendered}
ref={registerChild}
rowCount={hasMoreFiles ? children.length + 2 : children.length}
rowHeight={itemSize}
rowRenderer={viewAs === "table" ? renderTable : renderRow}
width={width}
className={className}
isScrolling={isScrolling}
onChildScroll={onChildScroll}
scrollTop={scrollTop}
overscanRowCount={3}
onScroll={onScroll}
viewAs={viewAs}
/>
);
}}
</WindowScroller>
)}
</InfiniteLoader>
);
};
export default ListComponent;

View File

@ -0,0 +1,8 @@
import React, { forwardRef } from "react";
import { StyledScroll } from "./StyledInfiniteLoader";
const Scroll = forwardRef((props, ref) => {
return <StyledScroll {...props} forwardedRef={ref} />;
});
export default Scroll;

View File

@ -0,0 +1,87 @@
import { List } from "react-virtualized";
import styled, { css } from "styled-components";
import Base from "../themes/base";
import { desktop, mobile, tablet } from "../utils/device";
const StyledScroll = styled.div`
overflow: scroll;
/* Chrome, Edge и Safari */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background-color: ${({ theme }) => theme.scrollbar.backgroundColorVertical};
border-radius: 3px;
:hover {
background-color: ${({ theme }) =>
theme.scrollbar.hoverBackgroundColorVertical};
}
}
/* Firefox */
scrollbar-width: thin;
scrollbar-color: ${({ theme }) => theme.scrollbar.backgroundColorVertical};
`;
const rowStyles = css`
.row-list-item,
.row-loader {
padding-left: 16px;
width: calc(100% - 33px) !important;
@media ${mobile} {
width: calc(100% - 24px) !important;
}
}
.row-loader {
padding-left: 22px;
}
`;
const tableStyles = css`
margin-left: -20px;
width: ${({ width }) => width + 40 + "px !important"};
.ReactVirtualized__Grid__innerScrollContainer {
max-width: ${({ width }) => width + 40 + "px !important"};
}
.table-container_body-loader {
width: calc(100% - 48px) !important;
}
.table-list-item,
.table-container_body-loader {
padding-left: 20px;
}
`;
const tileStyles = css`
.files_header {
padding-top: 11px;
}
`;
const StyledList = styled(List)`
outline: none;
overflow: hidden !important;
${({ viewAs }) =>
viewAs === "row"
? rowStyles
: viewAs === "table"
? tableStyles
: tileStyles}
`;
StyledScroll.defaultProps = {
theme: Base,
};
export { StyledScroll, StyledList };

View File

@ -0,0 +1 @@
export default from "./InfiniteLoader";

View File

@ -38,6 +38,7 @@
"react-toastify": "^7.0.0",
"react-tooltip": "^4.2.21",
"react-transition-group": "^4.4.1",
"react-virtualized": "^9.22.3",
"react-window": "^1.8.6",
"resize-image": "^0.1.0"
},

View File

@ -1,16 +1,10 @@
/* eslint-disable react/display-name */
import React, { memo } from "react";
import React from "react";
import PropTypes from "prop-types";
import CustomScrollbarsVirtualList from "../scrollbar/custom-scrollbars-virtual-list";
import { FixedSizeList as List, areEqual } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import StyledRowContainer from "./styled-row-container";
import InfiniteLoaderComponent from "../infinite-loader";
class RowContainer extends React.PureComponent {
renderRow = memo(({ data, index, style }) => {
return <div style={style}>{data[index]}</div>;
}, areEqual);
render() {
const {
manualHeight,
@ -21,23 +15,13 @@ class RowContainer extends React.PureComponent {
className,
style,
onScroll,
filesLength,
itemCount,
fetchMoreFiles,
hasMoreFiles,
selectedFolderId,
} = this.props;
const renderList = ({ height, width }) => (
<List
onScroll={onScroll}
className="List"
height={height}
width={width}
itemSize={itemHeight}
itemCount={children.length}
itemData={children}
outerElementType={CustomScrollbarsVirtualList}
>
{this.renderRow}
</List>
);
return (
<StyledRowContainer
id={id}
@ -46,7 +30,23 @@ class RowContainer extends React.PureComponent {
manualHeight={manualHeight}
useReactWindow={useReactWindow}
>
{useReactWindow ? <AutoSizer>{renderList}</AutoSizer> : children}
{useReactWindow ? (
<InfiniteLoaderComponent
className="List"
viewAs="row"
hasMoreFiles={hasMoreFiles}
filesLength={filesLength}
itemCount={itemCount}
loadMoreItems={fetchMoreFiles}
itemSize={itemHeight}
onScroll={onScroll}
selectedFolderId={selectedFolderId}
>
{children}
</InfiniteLoaderComponent>
) : (
children
)}
</StyledRowContainer>
);
}
@ -69,6 +69,10 @@ RowContainer.propTypes = {
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
/** Called when the list scroll positions changes */
onScroll: PropTypes.func,
filesLength: PropTypes.number,
itemCount: PropTypes.number,
loadMoreItems: PropTypes.func,
hasMoreFiles: PropTypes.bool,
};
RowContainer.defaultProps = {

View File

@ -8,7 +8,7 @@ const StyledScrollbar = styled(Scrollbars)`
props.color
? props.color
: props.theme.scrollbar.backgroundColorVertical};
z-index: 1;
z-index: 201;
}
.nav-thumb-horizontal {
background-color: ${(props) =>

View File

@ -4,6 +4,16 @@ import { mobile, tablet, hugeMobile } from "../utils/device";
import Scrollbar from "../scrollbar";
import { isMobile } from "react-device-detect";
const reactWindowContainerStyles = css`
height: 100%;
display: block;
`;
const reactWindowBodyStyles = css`
display: block;
height: 100%;
`;
const StyledTableContainer = styled.div`
-moz-user-select: none;
@ -75,6 +85,8 @@ const StyledTableContainer = styled.div`
width: 22px;
}
}
${({ useReactWindow }) => useReactWindow && reactWindowContainerStyles}
`;
StyledTableContainer.defaultProps = { theme: Base };
@ -278,6 +290,8 @@ StyledTableHeaderCell.defaultProps = { theme: Base };
const StyledTableBody = styled.div`
display: contents;
${({ useReactWindow }) => useReactWindow && reactWindowBodyStyles}
`;
const StyledTableRow = styled.div`

View File

@ -1,8 +1,54 @@
import React from "react";
import { StyledTableBody } from "./StyledTableContainer";
import InfiniteLoaderComponent from "../infinite-loader";
const TableBody = (props) => {
return <StyledTableBody className="table-container_body" {...props} />;
const {
columnStorageName,
columnInfoPanelStorageName,
fetchMoreFiles,
children,
filesLength,
hasMoreFiles,
itemCount,
itemHeight,
useReactWindow,
onScroll,
infoPanelVisible,
selectedFolderId,
} = props;
return useReactWindow ? (
<StyledTableBody
useReactWindow={useReactWindow}
className="table-container_body"
>
<InfiniteLoaderComponent
className="TableList"
viewAs="table"
hasMoreFiles={hasMoreFiles}
filesLength={filesLength}
itemCount={itemCount}
loadMoreItems={fetchMoreFiles}
columnStorageName={columnStorageName}
columnInfoPanelStorageName={columnInfoPanelStorageName}
itemSize={itemHeight}
onScroll={onScroll}
infoPanelVisible={infoPanelVisible}
selectedFolderId={selectedFolderId}
>
{children}
</InfiniteLoaderComponent>
</StyledTableBody>
) : (
<StyledTableBody className="table-container_body" {...props} />
);
};
TableBody.defaultProps = {
itemHeight: 41,
useReactWindow: false,
infoPanelVisible: false,
};
export default TableBody;

View File

@ -3,18 +3,26 @@ import { StyledTableContainer } from "./StyledTableContainer";
import PropTypes from "prop-types";
const TableContainer = (props) => {
const { forwardedRef, useReactWindow, ...rest } = props;
return (
<StyledTableContainer
id="table-container"
className="table-container"
ref={props.forwardedRef}
{...props}
ref={forwardedRef}
useReactWindow={useReactWindow}
{...rest}
/>
);
};
TableContainer.defaultProps = {
useReactWindow: false,
};
TableContainer.propTypes = {
forwardedRef: PropTypes.shape({ current: PropTypes.any }),
useReactWindow: PropTypes.bool,
};
export default TableContainer;

View File

@ -190,8 +190,12 @@ class TableHeader extends React.Component {
this.moveToRight(widths, newWidth);
}
containerRef.current.style.gridTemplateColumns = widths.join(" ");
this.headerRef.current.style.gridTemplateColumns = widths.join(" ");
const str = widths.join(" ");
containerRef.current.style.gridTemplateColumns = str;
this.headerRef.current.style.gridTemplateColumns = str;
this.updateTableRows(str);
};
onMouseUp = () => {
@ -544,6 +548,9 @@ class TableHeader extends React.Component {
if (str) {
container.style.gridTemplateColumns = str;
this.updateTableRows(str);
if (this.headerRef.current) {
this.headerRef.current.style.gridTemplateColumns = str;
this.headerRef.current.style.width = containerWidth + "px";
@ -559,6 +566,18 @@ class TableHeader extends React.Component {
}
};
updateTableRows = (str) => {
if (!this.props.useReactWindow) return;
const rows = document.querySelectorAll(".table-row, .table-list-item");
if (rows?.length) {
for (let i = 0; i < rows.length; i++) {
rows[i].style.gridTemplateColumns = str;
}
}
};
resetColumns = (resetToDefault = false) => {
const {
containerRef,
@ -695,6 +714,7 @@ class TableHeader extends React.Component {
TableHeader.defaultProps = {
sortingVisible: true,
infoPanelVisible: false,
useReactWindow: false,
};
TableHeader.propTypes = {
@ -709,6 +729,7 @@ TableHeader.propTypes = {
isLengthenHeader: PropTypes.bool,
sortingVisible: PropTypes.bool,
infoPanelVisible: PropTypes.bool,
useReactWindow: PropTypes.bool,
};
export default TableHeader;

View File

@ -118,7 +118,7 @@ const ForgotPasswordModalDialog = (props) => {
<ModalDialog.Footer>
<Button
className="modal-dialog-button"
key="SendBtn"
key="ForgotSendBtn"
label={
isLoading ? t("Common:LoadingProcessing") : t("Common:SendButton")
}

View File

@ -146,7 +146,7 @@ const RecoverAccessModalDialog = ({ t, visible, onClose }) => {
<ModalDialog.Footer>
<Button
className="recover-button-dialog"
key="SendBtn"
key="RecoverySendBtn"
label={loading ? t("Common:Sending") : t("Common:SendButton")}
size="normal"
primary={true}

View File

@ -99,7 +99,7 @@ const RegisterModalDialog = ({
<ModalDialog.Footer>
<Button
className="modal-dialog-button"
key="SendBtn"
key="RegisterSendBtn"
label={loading ? t("Common:Sending") : t("RegisterSendButton")}
size="normal"
scale={false}

View File

@ -219,7 +219,14 @@ internal abstract class SharpBoxDaoBase : ThirdPartyProviderDao<SharpBoxProvider
protected string MakePath(object entryId)
{
return $"/{Convert.ToString(entryId, CultureInfo.InvariantCulture).Trim('/')}";
var id = Convert.ToString(entryId, CultureInfo.InvariantCulture);
if (!string.IsNullOrEmpty(id))
{
id = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(id)).Trim('/');
}
return $"/{id}";
}
protected override string MakeId(string path = null)
@ -245,7 +252,7 @@ internal abstract class SharpBoxDaoBase : ThirdPartyProviderDao<SharpBoxProvider
{
path = entry.Id;
}
var p = string.IsNullOrEmpty(path) || path == "/" ? "" : ("-" + path.Replace('/', '|'));
var p = string.IsNullOrEmpty(path) || path == "/" ? "" : ("-" + WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(path)));
return $"{PathPrefix}{p}";
}

View File

@ -189,7 +189,8 @@ global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Mvc.Filters;
global using Microsoft.AspNetCore.Mvc.ModelBinding;
global using Microsoft.AspNetCore.Mvc.ModelBinding;
global using Microsoft.AspNetCore.WebUtilities;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.Infrastructure;
global using Microsoft.EntityFrameworkCore.Metadata;

View File

@ -955,6 +955,7 @@ public class EntryManager
var folders = entries.Where(r => r.FileEntryType == FileEntryType.Folder).Except(pinnedRooms).Except(rooms);
var files = entries.Where(r => r.FileEntryType == FileEntryType.File);
pinnedRooms = pinnedRooms.OrderBy(r => r, comparer);
rooms = rooms.OrderBy(r => r, comparer);
folders = folders.OrderBy(r => r, comparer);
files = files.OrderBy(r => r, comparer);

View File

@ -111,7 +111,14 @@ public class FileShareLink
}
var fileId = Parse<T>(doc);
var file = await fileDao.GetFileAsync(fileId);
File<T> file = null;
if (!EqualityComparer<T>.Default.Equals(fileId, default(T)))
{
file = await fileDao.GetFileAsync(fileId);
}
if (file == null)
{
return (FileShare.Restrict, file);

View File

@ -235,17 +235,20 @@ public class FilesControllerHelper<T> : FilesHelperBase<T>
public async Task<FileDto<T>> UpdateFileAsync(T fileId, string title, int lastVersion)
{
File<T> file = null;
if (!string.IsNullOrEmpty(title))
{
await _fileStorageService.FileRenameAsync(fileId, title);
file = await _fileStorageService.FileRenameAsync(fileId, title);
}
if (lastVersion > 0)
{
await _fileStorageService.UpdateToVersionAsync(fileId, lastVersion);
var result = await _fileStorageService.UpdateToVersionAsync(fileId, lastVersion);
file = result.Key;
}
return await GetFileInfoAsync(fileId);
return await GetFileInfoAsync(file.Id);
}
public async Task<FileDto<T>> UpdateFileStreamAsync(Stream file, T fileId, string fileExtension, bool encrypted = false, bool forcesave = false)

View File

@ -2067,7 +2067,7 @@ __metadata:
react-resize-detector: ^6.7.6
react-router: ^5.2.1
react-router-dom: ^5.3.0
react-selecto: ^1.12.0
react-selecto: ^1.18.2
react-tooltip: ^4.2.21
react-viewer: ^3.2.2
react-virtualized-auto-sizer: ^1.0.6
@ -2155,6 +2155,7 @@ __metadata:
react-tooltip: ^4.2.21
react-transition-group: ^4.4.1
react-values: ^0.3.3
react-virtualized: ^9.22.3
react-window: ^1.8.6
resize-image: ^0.1.0
styled-components: ^5.3.1
@ -3591,13 +3592,13 @@ __metadata:
languageName: node
linkType: hard
"@scena/dragscroll@npm:^1.1.1":
version: 1.2.0
resolution: "@scena/dragscroll@npm:1.2.0"
"@scena/dragscroll@npm:^1.2.1":
version: 1.2.1
resolution: "@scena/dragscroll@npm:1.2.1"
dependencies:
"@daybrush/utils": 1.6.0
"@scena/event-emitter": ^1.0.2
checksum: cac9db269d865a0acacf836327d26966d8acf10764bdeb52c4be5953797a805169dbcd6df15d090864046c6a2ed6bc49a0e22309b20e36dda8e73844e6833fa1
checksum: 217b0612b0b5d2e721cd41b7534cc1b831aca236a4256b30bdeb965b3b0b7cf9f3934801a7158cb38483242e2568f8b734eeccfaee43b5df4e5df9ead7653dd5
languageName: node
linkType: hard
@ -8899,7 +8900,7 @@ __metadata:
languageName: node
linkType: hard
"clsx@npm:^1.1.1":
"clsx@npm:^1.0.4, clsx@npm:^1.1.1":
version: 1.2.1
resolution: "clsx@npm:1.2.1"
checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12
@ -10551,7 +10552,7 @@ __metadata:
languageName: node
linkType: hard
"dom-helpers@npm:^5.0.1":
"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.3":
version: 5.2.1
resolution: "dom-helpers@npm:5.2.1"
dependencies:
@ -12727,13 +12728,13 @@ __metadata:
languageName: node
linkType: hard
"gesto@npm:^1.9.0":
version: 1.11.1
resolution: "gesto@npm:1.11.1"
"gesto@npm:^1.11.1":
version: 1.12.1
resolution: "gesto@npm:1.12.1"
dependencies:
"@daybrush/utils": ^1.7.1
"@scena/event-emitter": ^1.0.2
checksum: cfd0c17bc84865f062b8ae03a99fe21373239a5e0a593d185a0d0713d3dc0ebf21738859b7473c7949ae9b79f40e02188d5c384656bab6932e439c380ed04a91
checksum: e9b3f461e3ac05cc77187052660dbd0efa0aa7922bc650ced2d1d24ee77633046a92b8093eb584475bed1e3e82236999c86aef7007f949d55b4cce45c1d2bc5b
languageName: node
linkType: hard
@ -20537,12 +20538,12 @@ __metadata:
languageName: node
linkType: hard
"react-selecto@npm:^1.12.0":
version: 1.17.0
resolution: "react-selecto@npm:1.17.0"
"react-selecto@npm:^1.18.2":
version: 1.18.2
resolution: "react-selecto@npm:1.18.2"
dependencies:
selecto: ~1.17.0
checksum: 135bc3f9100a6631232b408ecb4ce008219ccce5b106ccc1cf03c7a0aba62cbe8bdf0427d88db5323988f181affc1635adb448f149eed93b7aa72d24c1a41e35
selecto: ~1.18.2
checksum: f1207f66b0a806e0129e9e611b720c160f5362a28cc9c381f8428fca0c4dc1df92a0a03c31f3b61a6c3de02f615eabbcf43cb785d908103548cc5ef3d1acda27
languageName: node
linkType: hard
@ -20755,6 +20756,23 @@ __metadata:
languageName: node
linkType: hard
"react-virtualized@npm:^9.22.3":
version: 9.22.3
resolution: "react-virtualized@npm:9.22.3"
dependencies:
"@babel/runtime": ^7.7.2
clsx: ^1.0.4
dom-helpers: ^5.1.3
loose-envify: ^1.4.0
prop-types: ^15.7.2
react-lifecycles-compat: ^3.0.4
peerDependencies:
react: ^15.3.0 || ^16.0.0-alpha
react-dom: ^15.3.0 || ^16.0.0-alpha
checksum: 5e3b566592293bc0057bc6be4f6ee29c58c8931421d2882a3ef45ca9b24c6f3ea78bbc5a182c2916af6520845e5a90f569b3a63c9b5e89428720913e6d6239cc
languageName: node
linkType: hard
"react-window-infinite-loader@npm:^1.0.7":
version: 1.0.8
resolution: "react-window-infinite-loader@npm:1.0.8"
@ -21894,21 +21912,21 @@ __metadata:
languageName: node
linkType: hard
"selecto@npm:~1.17.0":
version: 1.17.0
resolution: "selecto@npm:1.17.0"
"selecto@npm:~1.18.2":
version: 1.18.2
resolution: "selecto@npm:1.18.2"
dependencies:
"@daybrush/utils": ^1.7.1
"@egjs/children-differ": ^1.0.1
"@scena/dragscroll": ^1.1.1
"@scena/dragscroll": ^1.2.1
"@scena/event-emitter": ^1.0.5
css-styled: ^1.0.0
css-to-mat: ^1.0.3
framework-utils: ^1.1.0
gesto: ^1.9.0
gesto: ^1.11.1
keycon: ^1.2.0
overlap-area: ^1.1.0
checksum: ad288ee4be3bc2610925010a5a215370e0664f2075d7157ef2c020d08ac7244e9d6818fccb67998dbd655be8c5b9f2912cac329b8a9961787c9e86873adba47d
checksum: 192221746c8688c11e20ea1fc5918de8674e5eeba2b6d3a15324fb6befec3005abfaa42625eda36095f79df5bc76036576e8aa9a29a0a05476e1528666809b1e
languageName: node
linkType: hard