Merge branch 'develop' into feature/security-history-audit

This commit is contained in:
Dmitry Sychugov 2022-08-30 11:55:19 +05:00
commit 605aee5529
171 changed files with 7789 additions and 756 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

@ -42,6 +42,7 @@
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"firebase": "^8.10.0",
"react-avatar-editor": "^13.0.0",
"react-hotkeys-hook": "^3.4.4",
"react-markdown": "^7.0.1",
"react-smartbanner": "^5.1.4",

View File

@ -0,0 +1,3 @@
<svg width="216" height="216" viewBox="0 0 216 216" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M144.761 0.744576V71.7716H215.787V72.7315H144.761L144.761 143.758H215.787V144.718H144.761V215.745H143.802V144.718H72.772V215.745H71.8122V144.718H0.78653V143.758H71.8122L71.8122 72.7315H0.78653V71.7716H71.8122V0.744576H72.772V71.7716H143.802V0.744576H144.761ZM72.772 72.7315L72.772 143.758H143.802V72.7315H72.772ZM2.77727 0.744576H30.6476V2.73532H2.77727V30.6057H0.78653V2.73532V0.744576H2.77727ZM185.925 0.744576H213.796H215.787V2.73532L215.787 30.6057H213.796V2.73532H185.925V0.744576ZM185.925 213.754H213.796V185.883H215.787L215.787 213.754V215.745H213.796H185.925V213.754ZM2.77727 213.754H30.6476V215.745H2.77727H0.78653V213.754V185.883H2.77727V213.754Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 829 B

View File

@ -0,0 +1,41 @@
{
"CreateRoom": "Create room",
"ChooseRoomType": "Choose room type",
"RoomEditing": "Room editing",
"FillingFormsRoomTitle": "Filling forms room",
"CollaborationRoomTitle": "Collaboration room",
"ReviewRoomTitle": "Review room",
"ViewOnlyRoomTitle": "View-only room",
"CustomRoomTitle": "Custom room",
"FillingFormsRoomDescription": "Build, share and fill document templates or work with the ready presets to quickly create documents of any type",
"CollaborationRoomDescription": "Collaborate on one or multiple documents with your team",
"ReviewRoomDescription": "Request a review or comments on the documents",
"ViewOnlyRoomDescription": "Share any ready documents, reports, documentation, and other files for viewing",
"CustomRoomDescription": "Apply your own settings to use this room for any custom purpose",
"NamePlaceholder": "Enter name",
"TagsPlaceholder": "Add a tag",
"CreateTagOption": "Create tag",
"MakeRoomPrivateTitle": "Make the Room Private",
"MakeRoomPrivateDescription": "All files in this room will be encrypted",
"MakeRoomPrivateLimitationsWarningDescription": "With this feature, you can invite only existing users on the portal. After creating a room, you will not be able to change the list of users.",
"ThirdPartyStorageTitle": "Third party storage",
"ThirdPartyStorageDescription": "Use third-party services as data storage for this room. A new folder for storing this rooms data will be created in the connected storage",
"ThirdPartyStorageComboBoxPlaceholder": "Select storage",
"ThirdPartyStorageNoStorageAlert": "No storage is connected in the portal Settings. Go to the Integrations section to enable data storage on a third-party",
"ThirdPartyStorageNoStorageAlertLink": "Third-party services",
"ThirdPartyStorageRememberChoice": "Remember this choice for new rooms",
"ThirdPartyStoragePermanentSettingDescription": "Files are stored in a third-party {{thirdpartyTitle}} storage in the \"{{thirdpartyFolderName}}\" folder.\n<strong>{{thirdpartyPath}}</strong>",
"FolderNameTitle": "Folder Name",
"FolderNameDescription": "A new folder for storing this rooms data will be created in the connected storage",
"DropzoneTitleLink": "Select new image",
"DropzoneTitleSecondary": "or drop file here",
"DropzoneTitleExsts": "(JPG or PNG, max 1 MB)"
}

View File

@ -55,7 +55,6 @@
"NewRoom": "New room",
"NewSpreadsheet": "New spreadsheet",
"NoSubfolders": "No subfolders",
"NoTag": "No tag",
"Open": "Open",
"OpenLocation": "Open location",
"Presentation": "Presentation",

View File

@ -0,0 +1,41 @@
{
"CreateRoom": "Создание комнаты",
"ChooseRoomType": "Выберите тип комнаты",
"RoomEditing": "Редактирование комнаты",
"FillingFormsRoomTitle": "Комната для заполнения форм",
"CollaborationRoomTitle": "Комната для совместного редактирования",
"ReviewRoomTitle": "Комната для рецензирования",
"ViewOnlyRoomTitle": "Комната для просмотра",
"CustomRoomTitle": "Пользовательская комната",
"FillingFormsRoomDescription": "Данная комната подойдет для сбора форм/анкет/тестов и тд.",
"CollaborationRoomDescription": "Совместная работа над одним или несколькими документами с вашей командой",
"ReviewRoomDescription": "Запроситe рецензию или комментарии к документам",
"ViewOnlyRoomDescription": "Предоставляйте доступ к любым готовым документам, отчетам, документации и другим файлам для просмотра",
"CustomRoomDescription": "Примените собственные настройки, чтобы использовать эту комнату для любых пользовательских целей",
"NamePlaceholder": "Введите имя",
"TagsPlaceholder": "Добавьте тэг",
"CreateTagOption": "Создать тэг",
"MakeRoomPrivateTitle": "Сделайте комнату приватной",
"MakeRoomPrivateDescription": "Все файлы в этой комнате будут зашифрованы",
"MakeRoomPrivateLimitationsWarningDescription": "С данной функцией Вы можете пригласить только уже существующих пользователей на портале. После создания комнаты изменить список пользобателей будет нельзя.",
"ThirdPartyStorageTitle": "Место хранения",
"ThirdPartyStorageDescription": "Используйте сторонние сервисы в качестве хранилища данных для этой комнаты. В подключенном хранилище будет создана новая папка для хранения данных этой комнаты",
"ThirdPartyStorageComboBoxPlaceholder": "Выберите хранилище",
"ThirdPartyStorageNoStorageAlert": "В Настройках портала не подключено ни одного хранилища. Перейдите в раздел Интеграций, чтобы включить возможность хранения данных на стороннем сервисе ",
"ThirdPartyStorageNoStorageAlertLink": "Сторонние сервисы",
"ThirdPartyStorageRememberChoice": "Запомнить этот выбор для новых комнат",
"ThirdPartyStoragePermanentSettingDescription": "Файлы хранятся в стороннем хранилище {{thirdpartyTitle}} в папке \"{{thirdpartyFolderName}}\".\n<strong>{{thirdpartyPath}}</strong>",
"FolderNameTitle": "Имя папки",
"FolderNameDescription": "В подключенном хранилище будет создана новая папка для хранения данных этой комнаты",
"DropzoneTitleLink": "Выберите новое изображение",
"DropzoneTitleSecondary": "или перетащите сюда файл",
"DropzoneTitleExsts": "(JPG или PNG, не более 1 МБ)"
}

View File

@ -7,11 +7,13 @@
"ByCreationDate": "Создан",
"ByLastModifiedDate": "Изменен",
"ByTitle": "Название",
"CollaborationRooms": "Совместное редактирование",
"CommonEmptyContainerDescription": "В разделе «Общие документы» отображаются все документы, которыми администратор портала предоставил общий доступ. Только администраторы портала могут создавать папки в этом разделе, но с предоставленным доступом пользователи портала также могут загружать свои файлы здесь. Перетащите файлы со своего компьютера сюда, чтобы загрузить их на свой портал еще проще.",
"ContainsSpecCharacter": "Название не должно содержать следующих символов: *+:\"<>?|/",
"Convert": "Конвертация",
"CopyItem": "<strong>{{title}}</strong> скопирован",
"CopyItems": "Скопировано элементов: <strong>{{qty}}</strong>",
"CustomRooms": "Пользовательская",
"Document": "Документ",
"EmptyFile": "Пустой файл",
"EmptyFilterDescriptionText": "В этом разделе нет файлов или папок, соответствующих фильтру. Пожалуйста, выберите другие параметры или очистите фильтр, чтобы показать все файлы в этом разделе. Вы можете также поискать нужный файл в других разделах.",
@ -23,6 +25,7 @@
"FavoritesEmptyContainerDescription": "Чтобы добавить файлы в избранное или удалить их из этого списка, используйте контекстное меню.",
"FileRemoved": "Файл перемещен в корзину",
"FileRenamed": "Документ '{{oldTitle}}' переименован в '{{newTitle}}'",
"FillingFormRooms": "Заполнение форм",
"Filter": "Фильтр",
"FinalizeVersion": "Сформировать версию",
"Folder": "Папка",
@ -64,6 +67,7 @@
"RemoveFromList": "Убрать из списка",
"RemovedFromFavorites": "Удалено из избранного",
"Rename": "Переименовать",
"ReviewRooms": "Рецензирование",
"SendByEmail": "Отправить по почте",
"Share": "Доступ",
"SharedEmptyContainerDescription": "Раздел 'Доступно для меня' используется для отображения файлов, к которым ваши друзья или коллеги предоставили вам доступ. Если вы не видели последние изменения в документах, они помечаются как «новые». Вы можете удалить файлы из списка, нажав соответствующую кнопку.",
@ -79,5 +83,6 @@
"VersionBadge": "B.{{version}}",
"VersionHistory": "История версий",
"ViewList": "Список",
"ViewOnlyRooms": "Просмотр",
"ViewTiles": "Плитки"
}

View File

@ -110,17 +110,9 @@ export default function withContent(WrappedContent) {
return inject(
(
{
filesActionsStore,
filesStore,
treeFoldersStore,
auth,
dialogsStore,
uploadDataStore,
},
{ filesStore, treeFoldersStore, auth, dialogsStore, uploadDataStore },
{ item }
) => {
const { editCompleteAction } = filesActionsStore;
const {
createFile,
createFolder,
@ -133,9 +125,9 @@ export default function withContent(WrappedContent) {
isUpdatingRowItem,
passwordEntryProcess,
addActiveItems,
gallerySelected,
setCreatedItem,
} = filesStore;
const { clearActiveOperations, fileCopyAs } = uploadDataStore;
const {
isRecycleBinFolder,
@ -164,7 +156,6 @@ export default function withContent(WrappedContent) {
createFile,
createFolder,
culture,
editCompleteAction,
folderFormValidation,
homepage: config.homepage,
@ -192,7 +183,6 @@ export default function withContent(WrappedContent) {
titleWithoutExt,
gallerySelected,
setCreatedItem,
personal,
};

View File

@ -176,6 +176,10 @@ export default function withFileActions(WrappedFileItem) {
this.props.selectTag(tag);
};
onSelectType = (type) => {
this.props.selectType(type);
};
getContextModel = () => {
const { getModel, item, t } = this.props;
return getModel(item, t);
@ -237,6 +241,7 @@ export default function withFileActions(WrappedFileItem) {
onMouseClick={this.onMouseClick}
onHideContextMenu={this.onHideContextMenu}
onSelectTag={this.onSelectTag}
onSelectType={this.onSelectType}
getClassName={this.getClassName}
className={className}
isDragging={isDragging}
@ -271,6 +276,7 @@ export default function withFileActions(WrappedFileItem) {
const {
selectRowAction,
selectTag,
selectType,
onSelectItem,
setNewBadgeCount,
openFileAction,
@ -331,7 +337,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,
@ -339,6 +346,7 @@ export default function withFileActions(WrappedFileItem) {
selectRowAction,
onSelectItem,
selectTag,
selectType,
setSharingPanelVisible,
isPrivacy: isPrivacyFolder,
isRoomsFolder,

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

@ -29,6 +29,7 @@ const ArticleMainButtonContent = (props) => {
encrypted,
startUpload,
setAction,
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
isArticleLoading,
isFavoritesFolder,
@ -68,7 +69,6 @@ const ArticleMainButtonContent = (props) => {
const onCreateRoom = React.useCallback(() => {
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
}, []);
@ -172,7 +172,7 @@ const ArticleMainButtonContent = (props) => {
id: "main-button_new-room",
className: "main-button_drop-down",
icon: "images/folder.locked.react.svg",
label: t("Home:NewRoom"),
label: t("Files:NewRoom"),
onClick: onCreateRoom,
action: "room",
key: "room",
@ -345,7 +345,10 @@ export default inject(
isArchiveFolder,
} = treeFoldersStore;
const { startUpload } = uploadDataStore;
const { setSelectFileDialogVisible } = dialogsStore;
const {
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
} = dialogsStore;
const isArticleLoading = (!isLoaded || isLoading) && firstLoad;
@ -371,6 +374,7 @@ export default inject(
startUpload,
setCreateRoomDialogVisible,
setSelectFileDialogVisible,
isLoading,

View File

@ -15,7 +15,12 @@ const linkStyles = {
display: "flex",
};
const EmptyContainer = ({ isFiltered, parentId, theme }) => {
const EmptyContainer = ({
isFiltered,
parentId,
theme,
setCreateRoomDialogVisible,
}) => {
linkStyles.color = theme.filesEmptyContainer.linkColor;
const onCreate = (e) => {
@ -32,11 +37,10 @@ const EmptyContainer = ({ isFiltered, parentId, theme }) => {
window.dispatchEvent(event);
};
const onCreateRoom = React.useCallback(() => {
const onCreateRoom = (e) => {
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
}, []);
};
return isFiltered ? (
<EmptyFilterContainer linkStyles={linkStyles} />
@ -52,7 +56,13 @@ const EmptyContainer = ({ isFiltered, parentId, theme }) => {
};
export default inject(
({ auth, filesStore, treeFoldersStore, selectedFolderStore }) => {
({
auth,
filesStore,
dialogsStore,
treeFoldersStore,
selectedFolderStore,
}) => {
const {
authorType,
search,
@ -61,6 +71,8 @@ export default inject(
} = filesStore.filter;
const { isPrivacyFolder } = treeFoldersStore;
const { setCreateRoomDialogVisible } = dialogsStore;
const isFiltered =
(authorType || search || !withSubfolders || filterType) &&
!(isPrivacyFolder && isMobile);
@ -68,6 +80,7 @@ export default inject(
return {
theme: auth.settingsStore.theme,
isFiltered,
setCreateRoomDialogVisible,
parentId: selectedFolderStore.parentId,
};

View File

@ -21,6 +21,7 @@ import {
ThirdPartyDialog,
ConflictResolveDialog,
ConvertDialog,
CreateRoomDialog,
} from "../dialogs";
import ConvertPasswordDialog from "../dialogs/ConvertPasswordDialog";
@ -47,6 +48,7 @@ const Panels = (props) => {
setSelectFileDialogVisible,
hotkeyPanelVisible,
convertPasswordDialogVisible,
createRoomDialogVisible,
} = props;
const { t } = useTranslation(["Translations", "SelectFile"]);
@ -86,6 +88,7 @@ const Panels = (props) => {
<ConflictResolveDialog key="conflict-resolve-dialog" />
),
convertDialogVisible && <ConvertDialog key="convert-dialog" />,
createRoomDialogVisible && <CreateRoomDialog key="create-room-dialog" />,
selectFileDialogVisible && (
<SelectFileDialog
key="select-file-dialog"
@ -126,6 +129,7 @@ export default inject(
newFilesPanelVisible,
conflictResolveDialogVisible,
convertDialogVisible,
createRoomDialogVisible,
convertPasswordDialogVisible,
connectItem, //TODO:
@ -155,6 +159,7 @@ export default inject(
newFilesPanelVisible,
conflictResolveDialogVisible,
convertDialogVisible,
createRoomDialogVisible,
convertPasswordDialogVisible,
selectFileDialogVisible,
createMasterForm,

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];
@ -252,6 +252,7 @@ export default inject(
treeFoldersStore,
uploadDataStore,
dialogsStore,
oformsStore,
}) => {
const {
setIsLoading,
@ -260,10 +261,11 @@ export default inject(
addActiveItems,
openDocEditor,
setIsUpdatingRowItem,
gallerySelected,
setCreatedItem,
} = filesStore;
const { gallerySelected } = oformsStore;
const { editCompleteAction } = filesActionsStore;
const { clearActiveOperations, fileCopyAs } = uploadDataStore;

View File

@ -1,65 +1,208 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { CreateRoomDialog } from "../dialogs";
import toastr from "client/toastr";
const CreateRoomEvent = ({
visible,
onClose,
import { RoomsType } from "@docspace/common/constants";
createRoom,
createRoomInThirdpary,
createTag,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
fetchTags,
import Dialog from "./sub-components/Dialog";
connectItems,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
const CreateRoomEvent = ({ createRoom, updateCurrentFolder, id, onClose }) => {
const options = [
{ key: RoomsType.CustomRoom, label: "Custom room" },
{ key: RoomsType.FillingFormsRoom, label: "Filling form room" },
{ key: RoomsType.EditingRoom, label: "Editing room" },
{ key: RoomsType.ReviewRoom, label: "Review room" },
{ key: RoomsType.ReadOnlyRoom, label: "View-only room" },
];
currrentFolderId,
updateCurrentFolder,
}) => {
const { t } = useTranslation([
"CreateEditRoomDialog",
"Common",
"Files",
"ToastHeaders",
]);
const [fetchedTags, setFetchedTags] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [selectedOption, setSelectedOption] = React.useState(options[0]);
const onCreate = async (roomParams) => {
const createRoomData = {
roomType: roomParams.type,
title: roomParams.title || t("Files:NewRoom"),
};
const { t } = useTranslation(["Translations", "Common"]);
const isThirdparty =
roomParams.isThirdparty &&
roomParams.storageLocation.isConnected &&
roomParams.storageLocation.thirdpartyFolderId;
const onSelect = (item) => {
setSelectedOption(item);
const addTagsData = roomParams.tags.map((tag) => tag.name);
const createTagsData = roomParams.tags
.filter((t) => t.isNew)
.map((t) => t.name);
const uploadLogoData = new FormData();
uploadLogoData.append(0, roomParams.icon.uploadedFile);
try {
setIsLoading(true);
const room = isThirdparty
? await createRoomInThirdpary(
roomParams.storageLocation.thirdpartyFolderId,
createRoomData
)
: await createRoom(createRoomData);
for (let i = 0; i < createTagsData.length; i++)
await createTag(createTagsData[i]);
await addTagsToRoom(room.id, addTagsData);
if (roomParams.icon.uploadedFile)
await uploadRoomLogo(uploadLogoData).then((response) => {
const url = URL.createObjectURL(roomParams.icon.uploadedFile);
const img = new Image();
img.onload = async () => {
const { x, y, zoom } = roomParams.icon;
await addLogoToRoom(room.id, {
tmpFile: response.data,
...calculateRoomLogoParams(img, x, y, zoom),
});
URL.revokeObjectURL(img.src);
};
img.src = url;
});
} catch (err) {
console.log(err);
} finally {
await updateCurrentFolder(null, currrentFolderId);
setIsLoading(false);
onClose();
}
};
const onSave = (e, value) => {
createRoom(value, selectedOption.key)
.then(() => {
updateCurrentFolder(null, id);
})
.finally(() => {
onClose();
toastr.success(`${value} success created`);
});
};
useEffect(async () => {
let tags = await fetchTags();
setFetchedTags(tags);
}, []);
return (
<Dialog
<CreateRoomDialog
t={t}
title={"Create room"}
startValue={"New room"}
visible={true}
options={options}
selectedOption={selectedOption}
onSelect={onSelect}
onSave={onSave}
onCancel={onClose}
visible={visible && !connectDialogVisible}
onClose={onClose}
onCreate={onCreate}
fetchedTags={fetchedTags}
isLoading={isLoading}
connectItems={connectItems}
connectDialogVisible={connectDialogVisible}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
/>
);
};
export default inject(
({ filesStore, filesActionsStore, selectedFolderStore }) => {
const { createRoom } = filesStore;
({
auth,
filesStore,
tagsStore,
filesActionsStore,
selectedFolderStore,
settingsStore,
dialogsStore,
}) => {
const {
createRoom,
createRoomInThirdpary,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
} = filesStore;
const { createTag, fetchTags } = tagsStore;
const { id: currrentFolderId } = selectedFolderStore;
const { updateCurrentFolder } = filesActionsStore;
const { id } = selectedFolderStore;
const thirdPartyStore = settingsStore.thirdPartyStore;
return { createRoom, updateCurrentFolder, id };
const { openConnectWindow } = settingsStore.thirdPartyStore;
const connectItems = [
thirdPartyStore.googleConnectItem,
thirdPartyStore.boxConnectItem,
thirdPartyStore.dropboxConnectItem,
thirdPartyStore.oneDriveConnectItem,
thirdPartyStore.nextCloudConnectItem,
thirdPartyStore.kDriveConnectItem,
thirdPartyStore.yandexConnectItem,
thirdPartyStore.ownCloudConnectItem,
thirdPartyStore.webDavConnectItem,
thirdPartyStore.sharePointConnectItem,
]
.map(
(item) =>
item && {
isAvialable: !!item,
id: item[0],
providerName: item[0],
isOauth: item.length > 1,
oauthHref: item.length > 1 ? item[1] : "",
}
)
.filter((item) => !!item);
const { getOAuthToken } = auth.settingsStore;
const {
setConnectItem,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
} = dialogsStore;
return {
createRoom,
createRoomInThirdpary,
createTag,
fetchTags,
addTagsToRoom,
calculateRoomLogoParams,
uploadRoomLogo,
addLogoToRoom,
setConnectItem,
connectDialogVisible,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
saveThirdpartyResponse,
openConnectWindow,
connectItems,
getOAuthToken,
currrentFolderId,
updateCurrentFolder,
};
}
)(observer(CreateRoomEvent));

View File

@ -0,0 +1,190 @@
import React, { useState, useEffect } from "react";
import { inject, observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import { EditRoomDialog } from "../dialogs";
import { Encoder } from "@docspace/common/utils/encoder";
const EditRoomEvent = ({
visible,
onClose,
item,
editRoom,
addTagsToRoom,
removeTagsFromRoom,
createTag,
fetchTags,
getThirdPartyIcon,
calculateRoomLogoParams,
uploadRoomLogo,
setFolder,
removeLogoFromRoom,
addLogoToRoom,
currentFolderId,
updateCurrentFolder,
}) => {
const { t } = useTranslation(["CreateEditRoomDialog", "Common", "Files"]);
const [fetchedTags, setFetchedTags] = useState([]);
const [fetchedImage, setFetchedImage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const startTags = Object.values(item.tags);
const startObjTags = startTags.map((tag, i) => ({ id: i, name: tag }));
const fetchedRoomParams = {
title: item.title,
type: item.roomType,
tags: startObjTags,
isThirdparty: !!item.providerKey,
storageLocation: {
title: item.title,
parentId: item.parentId,
providerKey: item.providerKey,
iconSrc: getThirdPartyIcon(item.providerKey),
},
isPrivate: false,
icon: {
uploadedFile: item.logo.original,
tmpFile: "",
x: 0.5,
y: 0.5,
zoom: 1,
},
};
const onSave = async (roomParams) => {
const editRoomParams = {
title: roomParams.title || t("Files:NewRoom"),
};
const tags = roomParams.tags.map((tag) => tag.name);
const newTags = roomParams.tags.filter((t) => t.isNew).map((t) => t.name);
const removedTags = startTags.filter((sT) => !tags.includes(sT));
const uploadLogoData = new FormData();
uploadLogoData.append(0, roomParams.icon.uploadedFile);
try {
setIsLoading(true);
const room = await editRoom(item.id, editRoomParams);
for (let i = 0; i < newTags.length; i++) await createTag(newTags[i]);
await addTagsToRoom(room.id, tags);
await removeTagsFromRoom(room.id, removedTags);
if (!!item.logo.original && !roomParams.icon.uploadedFile)
await removeLogoFromRoom(room.id);
if (roomParams.icon.uploadedFile) {
await setFolder({
...room,
logo: { big: item.logo.small },
});
await uploadRoomLogo(uploadLogoData).then((response) => {
const url = URL.createObjectURL(roomParams.icon.uploadedFile);
const img = new Image();
img.onload = async () => {
const { x, y, zoom } = roomParams.icon;
await addLogoToRoom(room.id, {
tmpFile: response.data,
...calculateRoomLogoParams(img, x, y, zoom),
});
URL.revokeObjectURL(img.src);
};
img.src = url;
});
}
} catch (err) {
console.log(err);
} finally {
await updateCurrentFolder(null, currentFolderId);
setIsLoading(false);
onClose();
}
};
useEffect(async () => {
const imgExst = item.logo.original.slice(".")[1];
if (item.logo.original) {
const file = await fetch(item.logo.original)
.then((res) => res.arrayBuffer())
.then(
(buf) =>
new File([buf], "fetchedFile", {
type: `image/${imgExst}`,
})
);
setFetchedImage(file);
}
}, []);
useEffect(async () => {
const tags = await fetchTags();
setFetchedTags(tags);
}, []);
return (
<EditRoomDialog
t={t}
visible={visible}
onClose={onClose}
fetchedRoomParams={fetchedRoomParams}
onSave={onSave}
fetchedTags={fetchedTags}
fetchedImage={fetchedImage}
isLoading={isLoading}
/>
);
};
export default inject(
({
filesStore,
tagsStore,
filesActionsStore,
selectedFolderStore,
settingsStore,
}) => {
const {
editRoom,
addTagsToRoom,
removeTagsFromRoom,
calculateRoomLogoParams,
uploadRoomLogo,
setFolder,
addLogoToRoom,
removeLogoFromRoom,
} = filesStore;
const { createTag, fetchTags } = tagsStore;
const { id: currentFolderId } = selectedFolderStore;
const { updateCurrentFolder } = filesActionsStore;
const { getThirdPartyIcon } = settingsStore.thirdPartyStore;
return {
editRoom,
addTagsToRoom,
removeTagsFromRoom,
createTag,
fetchTags,
getThirdPartyIcon,
calculateRoomLogoParams,
setFolder,
uploadRoomLogo,
removeLogoFromRoom,
addLogoToRoom,
currentFolderId,
updateCurrentFolder,
};
}
)(observer(EditRoomEvent));

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

@ -1,15 +1,15 @@
import React from "react";
import React, { useState, useEffect, useCallback, memo } from "react";
import { FileAction } from "@docspace/common/constants";
import { Events } from "@docspace/client/src/helpers/filesConstants";
import CreateEvent from "./CreateEvent";
import RenameEvent from "./RenameEvent";
import CreateRoomEvent from "./CreateRoomEvent";
import EditRoomEvent from "./EditRoomEvent";
const GlobalEvents = () => {
const [createDialogProps, setCreateDialogProps] = React.useState({
const [createDialogProps, setCreateDialogProps] = useState({
visible: false,
id: null,
type: null,
@ -20,18 +20,24 @@ const GlobalEvents = () => {
onClose: null,
});
const [createRoomDialogProps, setCreateRoomDialogProps] = React.useState({
visible: false,
onClose: null,
});
const [renameDialogProps, setRenameDialogProps] = React.useState({
const [renameDialogProps, setRenameDialogProps] = useState({
visible: false,
item: null,
onClose: null,
});
const onCreate = React.useCallback((e) => {
const [createRoomDialogProps, setCreateRoomDialogProps] = useState({
visible: false,
onClose: null,
});
const [editRoomDialogProps, setEditRoomDialogProps] = useState({
visible: false,
item: null,
onClose: null,
});
const onCreate = useCallback((e) => {
const { payload } = e;
const visible = payload.id ? true : false;
@ -59,15 +65,7 @@ const GlobalEvents = () => {
});
}, []);
const onCreateRoom = React.useCallback((e) => {
setCreateRoomDialogProps({
visible: true,
onClose: () =>
setCreateRoomDialogProps({ visible: false, onClose: null }),
});
}, []);
const onRename = React.useCallback((e) => {
const onRename = useCallback((e) => {
const visible = e.item ? true : false;
setRenameDialogProps({
@ -77,36 +75,65 @@ const GlobalEvents = () => {
onClose: () => {
setRenameDialogProps({
visible: false,
typ: null,
type: null,
item: null,
});
},
});
}, []);
React.useEffect(() => {
const onCreateRoom = useCallback((e) => {
setCreateRoomDialogProps({
visible: true,
onClose: () =>
setCreateRoomDialogProps({ visible: false, onClose: null }),
});
}, []);
const onEditRoom = useCallback((e) => {
const visible = e.item ? true : false;
setEditRoomDialogProps({
visible: visible,
item: e.item,
onClose: () => {
setEditRoomDialogProps({
visible: false,
item: null,
onClose: null,
});
},
});
}, []);
useEffect(() => {
window.addEventListener(Events.CREATE, onCreate);
window.addEventListener(Events.ROOM_CREATE, onCreateRoom);
window.addEventListener(Events.RENAME, onRename);
window.addEventListener(Events.ROOM_CREATE, onCreateRoom);
window.addEventListener(Events.ROOM_EDIT, onEditRoom);
return () => {
window.removeEventListener(Events.CREATE, onCreate);
window.removeEventListener(Events.ROOM_CREATE, onCreateRoom);
window.removeEventListener(Events.RENAME, onRename);
window.removeEventListener(Events.ROOM_CREATE, onCreateRoom);
window.removeEventListener(Events.ROOM_EDIT, onEditRoom);
};
}, [onRename, onCreate]);
}, [onRename, onCreate, onCreateRoom, onEditRoom]);
return [
createDialogProps.visible && (
<CreateEvent key={Events.CREATE} {...createDialogProps} />
),
renameDialogProps.visible && (
<RenameEvent key={Events.RENAME} {...renameDialogProps} />
),
createRoomDialogProps.visible && (
<CreateRoomEvent key={Events.ROOM_CREATE} {...createRoomDialogProps} />
),
renameDialogProps.visible && (
<RenameEvent key={Events.RENAME} {...renameDialogProps} />
editRoomDialogProps.visible && (
<EditRoomEvent key={Events.ROOM_EDIT} {...editRoomDialogProps} />
),
];
};
export default React.memo(GlobalEvents);
export default memo(GlobalEvents);

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

@ -33,6 +33,8 @@ const PureConnectDialogContainer = (props) => {
folderFormValidation,
updateInfo,
isConnectionViaBackupModule,
roomCreation,
setSaveThirdpartyResponse,
} = props;
const {
corporate,
@ -159,9 +161,12 @@ const PureConnectDialogContainer = (props) => {
isCorporate,
customerTitle,
provider_key || key,
provider_id
provider_id,
roomCreation
)
.then(async () => {
.then(async (res) => {
setSaveThirdpartyResponse(res);
const folderId = isCorporate ? commonFolderId : myFolderId;
const subfolders = await getSubfolders(folderId);
const node = treeFolders.find((x) => x.id === folderId);
@ -240,7 +245,12 @@ const PureConnectDialogContainer = (props) => {
</ModalDialog.Header>
<ModalDialog.Body>
{isAccount ? (
<FieldContainer labelVisible labelText={t("Account")} isVertical>
<FieldContainer
style={roomCreation ? { margin: "0" } : {}}
labelVisible
labelText={t("Account")}
isVertical
>
<Button
label={t("Reconnect")}
size="normal"
@ -295,6 +305,7 @@ const PureConnectDialogContainer = (props) => {
isVertical
hasError={!isPasswordValid}
errorMessage={t("Common:RequiredField")}
style={roomCreation ? { margin: "0" } : {}}
>
<PasswordInput
hasError={!isPasswordValid}
@ -308,7 +319,7 @@ const PureConnectDialogContainer = (props) => {
</FieldContainer>
</>
)}
{!isConnectionViaBackupModule && (
{!(isConnectionViaBackupModule || roomCreation) && (
<FieldContainer
labelText={t("ConnectFolderTitle")}
isRequired
@ -327,7 +338,7 @@ const PureConnectDialogContainer = (props) => {
</FieldContainer>
)}
{!personal && !isConnectionViaBackupModule && (
{!personal && !(isConnectionViaBackupModule || roomCreation) && (
<Checkbox
label={t("ConnectMakeShared")}
isChecked={isCorporate}
@ -354,7 +365,6 @@ const PureConnectDialogContainer = (props) => {
scale={isAccount}
onClick={onClose}
isDisabled={isLoading}
isLoading={isLoading}
/>
</ModalDialog.Footer>
</ModalDialog>
@ -403,6 +413,8 @@ export default inject(
connectDialogVisible: visible,
setConnectDialogVisible,
connectItem,
roomCreation,
setSaveThirdpartyResponse,
} = dialogsStore;
const item = isConnectionViaBackupModule ? passedItem : connectItem;
@ -416,6 +428,8 @@ export default inject(
providers,
visible,
item,
roomCreation,
setSaveThirdpartyResponse,
folderFormValidation,
getOAuthToken,

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

@ -0,0 +1,158 @@
import React, { useState } from "react";
import styled, { css } from "styled-components";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
import TagHandler from "./handlers/tagHandler";
import SetRoomParams from "./sub-components/SetRoomParams";
import RoomTypeList from "./sub-components/RoomTypeList";
import DialogHeader from "./sub-components/DialogHeader";
const StyledModalDialog = styled(ModalDialog)`
.header-with-button {
display: flex;
align-items: center;
flex-direction: row;
gap: 12px;
}
${(props) =>
props.isOauthWindowOpen &&
css`
#modal-dialog {
display: none;
}
`}
`;
const CreateRoomDialog = ({
t,
visible,
onClose,
onCreate,
connectItems,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
fetchedTags,
isLoading,
folderFormValidation,
}) => {
const [isScrollLocked, setIsScrollLocked] = useState(false);
const [isOauthWindowOpen, setIsOauthWindowOpen] = useState(false);
const startRoomParams = {
title: "",
type: undefined,
tags: [],
isPrivate: false,
isThirdparty: false,
storageLocation: {
isConnected: false,
provider: null,
thirdpartyFolderId: "",
storageFolderPath: "",
},
rememberThirdpartyStorage: false,
icon: {
uploadedFile: null,
tmpFile: "",
x: 0.5,
y: 0.5,
zoom: 1,
},
};
const [roomParams, setRoomParams] = useState({ ...startRoomParams });
const setRoomTags = (newTags) =>
setRoomParams({ ...roomParams, tags: newTags });
const tagHandler = new TagHandler(roomParams.tags, setRoomTags, fetchedTags);
const setRoomType = (newRoomType) => {
setRoomParams((prev) => ({
...prev,
type: newRoomType,
}));
};
const onCreateRoom = () => onCreate(roomParams);
const isChooseRoomType = roomParams.type === undefined;
const goBack = () => {
setRoomParams({ ...startRoomParams });
};
return (
<StyledModalDialog
displayType="aside"
withBodyScroll
visible={visible}
onClose={onClose}
isScrollLocked={isScrollLocked}
withFooterBorder
isOauthWindowOpen={isOauthWindowOpen}
>
<ModalDialog.Header>
<DialogHeader
isChooseRoomType={isChooseRoomType}
onArrowClick={goBack}
/>
</ModalDialog.Header>
<ModalDialog.Body>
{isChooseRoomType ? (
<RoomTypeList t={t} setRoomType={setRoomType} />
) : (
<SetRoomParams
t={t}
setIsOauthWindowOpen={setIsOauthWindowOpen}
tagHandler={tagHandler}
roomParams={roomParams}
setRoomParams={setRoomParams}
setRoomType={setRoomType}
setIsScrollLocked={setIsScrollLocked}
connectItems={connectItems}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
/>
)}
</ModalDialog.Body>
{!isChooseRoomType && (
<ModalDialog.Footer>
<Button
tabIndex={5}
label={t("Common:Create")}
size="normal"
primary
scale
onClick={onCreateRoom}
isLoading={isLoading}
/>
<Button
tabIndex={5}
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
/>
</ModalDialog.Footer>
)}
</StyledModalDialog>
);
};
export default CreateRoomDialog;

View File

@ -0,0 +1,95 @@
import React, { useState, useEffect } from "react";
import TagHandler from "./handlers/tagHandler";
import SetRoomParams from "./sub-components/SetRoomParams";
import DialogHeader from "./sub-components/DialogHeader";
import ModalDialog from "@docspace/components/modal-dialog";
import Button from "@docspace/components/button";
const EditRoomDialog = ({
t,
visible,
onClose,
onSave,
isLoading,
fetchedRoomParams,
fetchedTags,
fetchedImage,
folderFormValidation,
}) => {
const [isScrollLocked, setIsScrollLocked] = useState(false);
const [roomParams, setRoomParams] = useState({
...fetchedRoomParams,
});
const setRoomTags = (newTags) =>
setRoomParams({ ...roomParams, tags: newTags });
const tagHandler = new TagHandler(roomParams.tags, setRoomTags, fetchedTags);
const setRoomType = (newRoomType) =>
setRoomParams((prev) => ({
...prev,
type: newRoomType,
}));
const onEditRoom = () => onSave(roomParams);
useEffect(async () => {
if (fetchedImage)
setRoomParams({
...roomParams,
icon: { ...roomParams.icon, uploadedFile: fetchedImage },
});
}, [fetchedImage]);
return (
<ModalDialog
displayType="aside"
withBodyScroll
visible={visible}
onClose={onClose}
isScrollLocked={isScrollLocked}
withFooterBorder
>
<ModalDialog.Header>
<DialogHeader isEdit />
</ModalDialog.Header>
<ModalDialog.Body>
<SetRoomParams
t={t}
tagHandler={tagHandler}
roomParams={roomParams}
setRoomParams={setRoomParams}
setRoomType={setRoomType}
setIsScrollLocked={setIsScrollLocked}
isEdit
/>
</ModalDialog.Body>
<ModalDialog.Footer>
<Button
tabIndex={5}
label={t("Common:SaveButton")}
size="normal"
primary
scale
onClick={onEditRoom}
isLoading={isLoading}
/>
<Button
tabIndex={5}
label={t("Common:CancelButton")}
size="normal"
scale
onClick={onClose}
/>
</ModalDialog.Footer>
</ModalDialog>
);
};
export default EditRoomDialog;

View File

@ -0,0 +1,43 @@
import { RoomsType } from "@docspace/common/constants";
export const roomTypes = [
{
type: RoomsType.FillingFormsRoom,
title: "FillingFormsRoomTitle",
description: "FillingFormsRoomDescription",
defaultTag: "Files:FillingFormRooms",
withSecondaryInfo: true,
secondaryInfo: "FillingFormsRoomSecondaryInfo",
},
{
type: RoomsType.EditingRoom,
title: "CollaborationRoomTitle",
description: "CollaborationRoomDescription",
defaultTag: "Files:CollaborationRooms",
withSecondaryInfo: true,
secondaryInfo: "CollaborationRoomSecondaryInfo",
},
{
type: RoomsType.ReviewRoom,
title: "ReviewRoomTitle",
description: "ReviewRoomDescription",
defaultTag: "Files:ReviewRooms",
withSecondaryInfo: true,
secondaryInfo: "ReviewRoomSecondaryInfo",
},
{
type: RoomsType.ReadOnlyRoom,
title: "ViewOnlyRoomTitle",
description: "ViewOnlyRoomDescription",
defaultTag: "Files:ViewOnlyRooms",
withSecondaryInfo: true,
secondaryInfo: "ViewOnlyRoomSecondaryInfo",
},
{
type: RoomsType.CustomRoom,
title: "CustomRoomTitle",
description: "CustomRoomDescription",
defaultTag: "Files:CustomRooms",
withSecondaryInfo: false,
},
];

View File

@ -0,0 +1,49 @@
class TagHandler {
constructor(tags, setTags, fetchedTags) {
this.tags = tags;
this.setTags = setTags;
this.fetchedTags = fetchedTags;
}
createRandomTagId() {
return "_" + Math.random().toString(36).substr(2, 9);
}
refreshDefaultTag(name) {
let newTags = [...this.tags].filter((tag) => !tag.isDefault);
newTags.unshift({
id: this.createRandomTagId(),
name,
isDefault: true,
});
this.setTags(newTags);
}
addTag(name) {
let newTags = [...this.tags];
newTags.push({
id: this.createRandomTagId(),
name,
});
this.setTags(newTags);
}
addNewTag(name) {
let newTags = [...this.tags];
newTags.push({
id: this.createRandomTagId(),
isNew: true,
name,
});
this.setTags(newTags);
}
deleteTag(id) {
let newTags = [...this.tags];
newTags = newTags.filter((tag) => tag.id !== id);
this.setTags(newTags);
}
}
export default TagHandler;

View File

@ -0,0 +1,32 @@
import React from "react";
import { withTranslation } from "react-i18next";
import withLoader from "@docspace/client/src/HOCs/withLoader";
import Loaders from "@docspace/common/components/Loaders";
import { IconButton } from "@docspace/components";
const DialogHeader = ({ t, isEdit, isChooseRoomType, onArrowClick }) => {
return (
<>
{isEdit ? (
t("RoomEditing")
) : isChooseRoomType ? (
t("ChooseRoomType")
) : (
<div className="header-with-button">
<IconButton
size="15px"
iconName="/static/images/arrow.path.react.svg"
className="sharing_panel-arrow"
onClick={onArrowClick}
/>
<div>{t("CreateRoom")}</div>
</div>
)}
</>
);
};
export default withTranslation(["CreateEditRoomDialog"])(
withLoader(DialogHeader)(<Loaders.CreateEditRoomDilogHeaderLoader />)
);

View File

@ -0,0 +1,115 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { useDropzone } from "react-dropzone";
import resizeImage from "resize-image";
import { Base } from "@docspace/components/themes";
const StyledDropzone = styled.div`
cursor: pointer;
box-sizing: border-box;
width: 100%;
height: 150px;
border: 2px dashed
${(props) => props.theme.createEditRoomDialog.dropzone.borderColor};
border-radius: 6px;
.dropzone {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
user-select: none;
&-link {
display: flex;
flex-direction: row;
gap: 4px;
font-size: 13px;
line-height: 20px;
&-main {
color: ${(props) =>
props.theme.createEditRoomDialog.dropzone.linkMainColor};
font-weight: 600;
text-decoration: underline;
text-decoration-style: dashed;
text-underline-offset: 1px;
}
&-secondary {
font-weight: 400;
color: ${(props) =>
props.theme.createEditRoomDialog.dropzone.linkSecondaryColor};
}
}
&-exsts {
font-weight: 600;
font-size: 12px;
line-height: 16px;
color: ${(props) => props.theme.createEditRoomDialog.dropzone.exstsColor};
}
}
`;
StyledDropzone.defaultProps = { theme: Base };
const Dropzone = ({ t, setUploadedFile }) => {
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
maxFiles: 1,
// maxSize: 1000000,
accept: ["image/png", "image/jpeg"],
});
useEffect(() => {
if (acceptedFiles.length) {
const fr = new FileReader();
fr.readAsDataURL(acceptedFiles[0]);
fr.onload = () => {
const img = new Image();
img.onload = () => {
const canvas = resizeImage.resize2Canvas(img, img.width, img.height);
const data = resizeImage.resize(
canvas,
img.width / 4,
img.height / 4,
resizeImage.JPEG
);
fetch(data)
.then((res) => res.blob())
.then((blob) => {
const file = new File([blob], "File name", {
type: "image/jpg",
});
setUploadedFile(file);
});
};
img.src = fr.result;
};
}
}, [acceptedFiles]);
return (
<StyledDropzone>
<div {...getRootProps({ className: "dropzone" })}>
<input {...getInputProps()} />
<div className="dropzone-link">
<span className="dropzone-link-main">{t("DropzoneTitleLink")}</span>
<span className="dropzone-link-secondary">
{t("DropzoneTitleSecondary")}
</span>
</div>
<div className="dropzone-exsts">{t("DropzoneTitleExsts")}</div>
</div>
</StyledDropzone>
);
};
export default Dropzone;

View File

@ -0,0 +1,206 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import throttle from "lodash/throttle";
import AvatarEditor from "react-avatar-editor";
import Slider from "@docspace/components/slider";
import IconButton from "@docspace/components/icon-button";
import { Base } from "@docspace/components/themes";
const StyledIconCropper = styled.div`
max-width: 216px;
.icon_cropper-crop_area {
width: 216px;
height: 216px;
margin-bottom: 4px;
position: relative;
.icon_cropper-grid {
pointer-events: none;
position: absolute;
width: 216px;
height: 216px;
top: 0;
bottom: 0;
left: 0;
right: 0;
svg {
opacity: 0.2;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.gridColor};
}
}
}
}
.icon_cropper-delete_button {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 6px 0;
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.borderColor};
border-radius: 3px;
margin-bottom: 12px;
transition: all 0.2s ease;
&:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBackground};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton
.hoverBorderColor};
}
&-text {
user-select: none;
font-weight: 600;
line-height: 20px;
color: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.color};
}
svg {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.iconCropper.deleteButton.iconColor};
}
}
}
.icon_cropper-zoom-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 20px;
&-slider {
margin: 0;
}
&-button {
user-select: none;
}
}
`;
StyledIconCropper.defaultProps = { theme: Base };
const IconCropper = ({
t,
icon,
onChangeIcon,
uploadedFile,
setUploadedFile,
setPreviewIcon,
}) => {
let editorRef = null;
const setEditorRef = (editor) => (editorRef = editor);
const handlePositionChange = (position) =>
onChangeIcon({ ...icon, x: position.x, y: position.y });
const handleSliderChange = (e, newZoom = null) =>
onChangeIcon({ ...icon, zoom: newZoom ? newZoom : +e.target.value });
const handleZoomInClick = () =>
handleSliderChange({}, icon.zoom <= 4.5 ? icon.zoom + 0.5 : 5);
const handleZoomOutClick = () =>
handleSliderChange({}, icon.zoom >= 1.5 ? icon.zoom - 0.5 : 1);
const handleDeleteImage = () => setUploadedFile(null);
const handleImageChange = throttle(() => {
if (editorRef) {
const newPreveiwImage = editorRef.getImageScaledToCanvas()?.toDataURL();
setPreviewIcon(newPreveiwImage);
}
}, 300);
useEffect(() => {
handleImageChange();
return () => {
setPreviewIcon("");
};
}, [icon]);
return (
<StyledIconCropper className="icon_cropper">
<div className="icon_cropper-crop_area">
<ReactSVG
className="icon_cropper-grid"
src="images/icon-cropper-grid.svg"
/>
<AvatarEditor
ref={setEditorRef}
image={uploadedFile}
width={216}
height={216}
position={{ x: icon.x, y: icon.y }}
scale={icon.zoom}
color={[6, 22, 38, 0.2]}
border={0}
rotate={0}
borderRadius={108}
onPositionChange={handlePositionChange}
onImageReady={handleImageChange}
disableHiDPIScaling
/>
</div>
<div
className="icon_cropper-delete_button"
onClick={handleDeleteImage}
title={t("Common:Delete")}
>
<ReactSVG src={"images/trash.react.svg"} />
<div className="icon_cropper-delete_button-text">
{t("Common:Delete")}
</div>
</div>
<div className="icon_cropper-zoom-container">
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomOutClick}
iconName={"/static/images/zoom-minus.react.svg"}
isFill={true}
isClickable={false}
/>
<Slider
className="icon_cropper-zoom-container-slider"
max={5}
min={1}
onChange={handleSliderChange}
step={0.01}
value={icon.zoom}
/>
<IconButton
className="icon_cropper-zoom-container-button"
size="16"
onClick={handleZoomInClick}
iconName={"/static/images/zoom-plus.react.svg"}
isFill={true}
isClickable={false}
/>
</div>
</StyledIconCropper>
);
};
export default IconCropper;

View File

@ -0,0 +1,99 @@
import React from "react";
import styled from "styled-components";
import { smallTablet } from "@docspace/components/utils/device";
import Tags from "@docspace/common/components/Tags";
import Tag from "@docspace/components/tag";
import { Base } from "@docspace/components/themes";
const StyledPreviewTile = styled.div`
background: ${(props) =>
props.theme.createEditRoomDialog.previewTile.background};
width: 214px;
border: 1px solid
${(props) => props.theme.createEditRoomDialog.previewTile.borderColor};
height: 120px;
border-radius: 12px;
@media ${smallTablet} {
display: none;
}
.tile-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
padding: 15px;
border-bottom: 1px solid
${(props) => props.theme.createEditRoomDialog.previewTile.borderColor};
&-icon {
width: 32px;
height: 32px;
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.previewTile.iconBorderColor};
border-radius: 6px;
img {
user-select: none;
height: 32px;
width: ${(props) => (props.isGeneratedPreview ? "32px" : "auto")};
border-radius: 6px;
}
}
&-title {
font-weight: 600;
font-size: 16px;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
}
}
.tile-tags {
box-sizing: border-box;
max-width: 100%;
display: flex;
align-items: center;
justify-content: start;
padding: 15px;
.type_tag {
user-select: none;
box-sizing: border-box;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
`;
StyledPreviewTile.defaultProps = { theme: Base };
const PreviewTile = ({ t, title, previewIcon, tags, defaultTagLabel }) => {
return (
<StyledPreviewTile>
<div className="tile-header">
<img className="tile-header-icon" src={previewIcon} alt={title} />
<div className="tile-header-title">{title}</div>
</div>
<div className="tile-tags">
{tags.length ? (
<Tags columnCount={2} tags={tags} />
) : (
<Tag
className="type_tag"
tag="script"
label={defaultTagLabel}
isDefault
/>
)}
</div>
</StyledPreviewTile>
);
};
export default PreviewTile;

View File

@ -0,0 +1,60 @@
import React, { useState } from "react";
import styled from "styled-components";
import Dropzone from "./Dropzone";
const StyledIconEditor = styled.div`
.icon-editor {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: start;
gap: 16px;
}
`;
import IconCropper from "./IconCropper";
import PreviewTile from "./PreviewTile";
const IconEditor = ({
t,
isEdit,
title,
tags,
currentRoomTypeData,
icon,
onChangeIcon,
}) => {
const [previewIcon, setPreviewIcon] = useState(null);
const setUploadedFile = (uploadedFile) =>
onChangeIcon({ ...icon, uploadedFile });
return (
<StyledIconEditor>
{icon.uploadedFile && (
<div className="icon-editor">
<IconCropper
t={t}
icon={icon}
onChangeIcon={onChangeIcon}
uploadedFile={icon.uploadedFile}
setUploadedFile={setUploadedFile}
setPreviewIcon={setPreviewIcon}
/>
<PreviewTile
t={t}
title={title || t("Files:NewRoom")}
previewIcon={previewIcon}
tags={tags.map((tag) => tag.name)}
defaultTagLabel={t(currentRoomTypeData.defaultTag)}
/>
</div>
)}
<Dropzone t={t} setUploadedFile={setUploadedFile} />
</StyledIconEditor>
);
};
export default IconEditor;

View File

@ -0,0 +1,87 @@
import React from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import { Base } from "@docspace/components/themes";
const StyledPrivacyLimitationsWarning = styled.div`
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
background: ${(props) =>
props.theme.createEditRoomDialog.isPrivate.limitations.background};
border-radius: 6px;
padding: 12px 8px;
.warning-title {
display: flex;
flex-direction: row;
gap: 8px;
&-icon-wrapper {
display: flex;
align-items: center;
.warning-title-icon {
width: 16px;
height: 14px;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.isPrivate.limitations.iconColor};
}
}
}
&-text {
font-weight: 600;
font-size: 13px;
line-height: 20px;
color: ${(props) =>
props.theme.createEditRoomDialog.isPrivate.limitations.titleColor};
}
}
.warning-description {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: ${(props) =>
props.theme.createEditRoomDialog.isPrivate.limitations.descriptionColor};
}
.warning-link {
cursor: pointer;
margin-top: 2px;
font-weight: 600;
font-size: 13px;
line-height: 15px;
color: ${(props) =>
props.theme.createEditRoomDialog.isPrivate.limitations.linkColor};
text-decoration: underline;
text-underline-offset: 1px;
}
`;
StyledPrivacyLimitationsWarning.defaultProps = { theme: Base };
const PrivacyLimitationsWarning = ({ t }) => {
return (
<StyledPrivacyLimitationsWarning>
<div className="warning-title">
<div className="warning-title-icon-wrapper">
<ReactSVG
className="warning-title-icon"
src={"/static/images/danger.alert.react.svg"}
/>
</div>
<div className="warning-title-text">{t("Common:Warning")}</div>
</div>
<div className="warning-description">
{t("MakeRoomPrivateLimitationsWarningDescription")}
</div>
<div className="warning-link">{t("Common:LearnMore")}</div>
</StyledPrivacyLimitationsWarning>
);
};
export default PrivacyLimitationsWarning;

View File

@ -0,0 +1,27 @@
import React from "react";
import styled from "styled-components";
import ToggleParam from "../Params/ToggleParam";
import PrivacyLimitationsWarning from "./PrivacyLimitationsWarning";
const StyledIsPrivateParam = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
const IsPrivateParam = ({ t, isPrivate, onChangeIsPrivate }) => {
return (
<StyledIsPrivateParam>
<ToggleParam
title={t("MakeRoomPrivateTitle")}
description={t("MakeRoomPrivateDescription")}
isChecked={isPrivate}
onCheckedChange={onChangeIsPrivate}
/>
{isPrivate && <PrivacyLimitationsWarning t={t} />}
</StyledIsPrivateParam>
);
};
export default IsPrivateParam;

View File

@ -0,0 +1,50 @@
import React from "react";
import styled from "styled-components";
import { StyledParam } from "./StyledParam";
import Label from "@docspace/components/label";
import TextInput from "@docspace/components/text-input";
const StyledInputParam = styled(StyledParam)`
flex-direction: column;
gap: 4px;
.input-label {
cursor: pointer;
user-select: none;
}
`;
const InputParam = ({
id,
title,
placeholder,
value,
onChange,
onFocus,
onBlur,
}) => {
return (
<StyledInputParam>
<Label
title={title}
className="input-label"
display="display"
htmlFor={id}
text={title}
/>
<TextInput
id={id}
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
scale
placeholder={placeholder}
tabIndex={2}
/>
</StyledInputParam>
);
};
export default InputParam;

View File

@ -0,0 +1,57 @@
import styled, { css } from "styled-components";
import { Base } from "@docspace/components/themes";
const StyledParam = styled.div`
box-sizing: border-box;
display: flex;
width: 100%;
${(props) =>
props.storageLocation
? css``
: props.folderName
? css`
flex-direction: column;
gap: 4px;
`
: ""}
.set_room_params-info {
display: flex;
flex-direction: column;
gap: 4px;
.set_room_params-info-title {
user-select: none;
display: flex;
flex-direction: row;
align-items: center;
gap: 6px;
.set_room_params-info-title-text {
user-select: none;
font-weight: 600;
font-size: 13px;
line-height: 20px;
}
}
.set_room_params-info-description {
user-select: none;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: ${(props) =>
props.theme.createEditRoomDialog.commonParam.descriptionColor};
}
}
.set_room_params-toggle {
width: 28px;
height: 16px;
margin: 2px 0;
}
`;
StyledParam.defaultProps = { theme: Base };
export { StyledParam };

View File

@ -0,0 +1,42 @@
import ToggleButton from "@docspace/components/toggle-button";
import React from "react";
import styled from "styled-components";
import { StyledParam } from "./StyledParam";
const StyledToggleParam = styled(StyledParam)`
flex-direction: row;
justify-content: space-between;
gap: 8px;
box-sizing: border-box;
max-width: 100%;
.set_room_params-info-description {
box-sizing: border-box;
max-width: 100%;
}
.set_room_params-toggle {
width: 28px;
min-width: 28px;
}
`;
const ToggleParam = ({ title, description, isChecked, onCheckedChange }) => {
return (
<StyledToggleParam isPrivate>
<div className="set_room_params-info">
<div className="set_room_params-info-title">
<div className="set_room_params-info-title-text">{title}</div>
</div>
<div className="set_room_params-info-description">{description}</div>
</div>
<ToggleButton
className="set_room_params-toggle"
isChecked={isChecked}
onChange={onCheckedChange}
/>
</StyledToggleParam>
);
};
export default ToggleParam;

View File

@ -0,0 +1,104 @@
import React from "react";
import styled, { css } from "styled-components";
import { ReactSVG } from "react-svg";
import SecondaryInfoButton from "../SecondaryInfoButton";
import { Base } from "@docspace/components/themes";
const StyledPermanentSetting = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: ${(props) => (props.isFull ? "column" : "row")};
align-items: ${(props) => (props.isFull ? "start" : "center")};
justify-content: ${(props) => (props.isFull ? "center" : "start")};
gap: 4px;
width: 100%;
max-width: 100%;
padding: 12px 16px;
background: ${(props) =>
props.theme.createEditRoomDialog.permanentSettings.background};
border-radius: 6px;
user-select: none;
.permanent_setting-main_info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
gap: 8px;
&-icon {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
margin: -2px;
svg {
max-width: 100%;
max-height: 100%;
${(props) =>
props.type === "privacy" &&
css`
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.permanentSettings
.isPrivateIcon};
}
`}
}
}
&-title {
font-weight: 600;
font-size: 12px;
line-height: 16px;
}
}
.permanent_setting-help_button {
margin-left: auto;
white-space: pre-line;
}
.permanent_setting-secondary-info {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: ${(props) =>
props.theme.createEditRoomDialog.permanentSettings.descriptionColor};
white-space: pre-line;
}
`;
StyledPermanentSetting.defaultProps = { theme: Base };
const PermanentSetting = ({ isFull, type, icon, title, content }) => {
return (
<StyledPermanentSetting
className="permanent_setting"
isFull={isFull}
type={type}
>
<div className="permanent_setting-main_info">
<ReactSVG className="permanent_setting-main_info-icon" src={icon} />
<div className="permanent_setting-main_info-title">{title}</div>
</div>
{isFull ? (
<div className="permanent_setting-secondary-info">{content}</div>
) : (
<div className="permanent_setting-help_button">
<SecondaryInfoButton content={content} />
</div>
)}
</StyledPermanentSetting>
);
};
export default PermanentSetting;

View File

@ -0,0 +1,72 @@
import React from "react";
import styled from "styled-components";
import { Trans } from "react-i18next";
import {
connectedCloudsTypeIcon as getProviderTypeIcon,
connectedCloudsTypeTitleTranslation as getProviderTypeTitle,
} from "@docspace/client/src/helpers/filesUtils";
import PermanentSetting from "./PermanentSetting";
const StyledPermanentSettings = styled.div`
display: ${(props) => (props.displayNone ? "none" : "flex")};
flex-direction: row;
gap: 8px;
margin-top: -12px;
`;
const PermanentSettings = ({
t,
title,
isThirdparty,
storageLocation,
isPrivate,
}) => {
const createThirdpartyPath = () => {
const path = storageLocation.parentId.split("|");
path.shift();
path.unshift(thirdpartyTitle);
path.push(thirdpartyFolderName);
return `(${path.join("/")})`;
};
const thirdpartyTitle = getProviderTypeTitle(storageLocation?.providerKey, t);
const thirdpartyFolderName = isThirdparty ? storageLocation?.title : "";
const thirdpartyPath = isThirdparty ? createThirdpartyPath() : "";
return (
<StyledPermanentSettings displayNone={!(isPrivate || isThirdparty)}>
{isThirdparty && (
<PermanentSetting
type="storageLocation"
isFull={!isPrivate}
icon={storageLocation.iconSrc}
title={thirdpartyTitle}
content={
<Trans
i18nKey="ThirdPartyStoragePermanentSettingDescription"
ns="CreateEditRoomDialog"
t={t}
>
Files are stored in a third-party {{ thirdpartyTitle }} storage in
the \"{{ thirdpartyFolderName }}\" folder.{" "}
<strong>{{ thirdpartyPath }}</strong>"
</Trans>
}
/>
)}
{isPrivate && (
<PermanentSetting
type="privacy"
isFull={!storageLocation}
icon={"images/security.svg"}
title={"Private room"}
content={`All files in this room will be encrypted`}
/>
)}
</StyledPermanentSettings>
);
};
export default PermanentSettings;

View File

@ -0,0 +1,215 @@
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import IconButton from "@docspace/components/icon-button";
import Text from "@docspace/components/text";
import RoomLogo from "@docspace/components/room-logo";
import { Base } from "@docspace/components/themes";
const StyledRoomType = styled.div`
cursor: pointer;
user-select: none;
outline: 0;
padding: 16px;
width: 100%;
box-sizing: border-box;
display: flex;
gap: 12px;
align-items: center;
.choose_room-logo_wrapper {
width: 32px;
margin-bottom: auto;
}
.choose_room-info_wrapper {
display: flex;
flex-direction: column;
gap: 4px;
.choose_room-title {
display: flex;
flex-direction: row;
gap: 6px;
align-items: center;
.choose_room-title-text {
font-weight: 600;
font-size: 14px;
line-height: 16px;
}
}
.choose_room-description {
font-weight: 400;
font-size: 12px;
line-height: 16px;
}
}
.choose_room-forward_btn {
margin-left: auto;
max-width: 17px;
max-height: 17px;
min-width: 17px;
min-height: 17px;
}
`;
const StyledListItem = styled(StyledRoomType)`
background-color: ${(props) =>
props.theme.createEditRoomDialog.roomType.listItem.background};
border: 1px solid
${(props) => props.theme.createEditRoomDialog.roomType.listItem.borderColor};
border-radius: 6px;
.choose_room-description {
color: ${(props) =>
props.theme.createEditRoomDialog.roomType.listItem.descriptionText};
}
`;
const StyledDropdownButton = styled(StyledRoomType)`
border-radius: 6px;
background-color: ${(props) =>
props.theme.createEditRoomDialog.roomType.dropdownButton.background};
border: 1px solid
${(props) =>
props.isOpen
? props.theme.createEditRoomDialog.roomType.dropdownButton
.isOpenBorderColor
: props.theme.createEditRoomDialog.roomType.dropdownButton.borderColor};
.choose_room-description {
color: ${(props) =>
props.theme.createEditRoomDialog.roomType.dropdownButton.descriptionText};
}
.choose_room-forward_btn {
&.dropdown-button {
transform: ${(props) =>
props.isOpen ? "rotate(-90deg)" : "rotate(90deg)"};
}
}
`;
const StyledDropdownItem = styled(StyledRoomType)`
background-color: ${(props) =>
props.theme.createEditRoomDialog.roomType.dropdownItem.background};
&:hover {
background-color: ${(props) =>
props.theme.createEditRoomDialog.roomType.dropdownItem.hoverBackground};
}
.choose_room-description {
color: ${(props) =>
props.theme.createEditRoomDialog.roomType.dropdownItem.descriptionText};
}
.choose_room-forward_btn {
display: none;
}
`;
const StyledDisplayItem = styled(StyledRoomType)`
cursor: default;
background-color: ${(props) =>
props.theme.createEditRoomDialog.roomType.displayItem.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.roomType.displayItem.borderColor};
border-radius: 6px;
.choose_room-description {
color: ${(props) =>
props.theme.createEditRoomDialog.roomType.displayItem.descriptionText};
}
.choose_room-forward_btn {
display: none;
}
`;
const RoomType = ({ t, room, onClick, type = "listItem", isOpen }) => {
const arrowClassName =
type === "dropdownButton"
? "choose_room-forward_btn dropdown-button"
: type === "dropdownItem"
? "choose_room-forward_btn dropdown-item"
: "choose_room-forward_btn";
const onSecondaryInfoClick = (e) => {
e.stopPropagation();
};
const content = (
<>
<div className="choose_room-logo_wrapper">
<RoomLogo type={room.type} />
</div>
<div className="choose_room-info_wrapper">
<div className="choose_room-title">
<Text noSelect className="choose_room-title-text">
{t(room.title)}
</Text>
{room.withSecondaryInfo && (
<div onClick={onSecondaryInfoClick}>
{/* <SecondaryInfoButton content={t(room.secondaryInfo)} /> */}
</div>
)}
</div>
<Text noSelect className="choose_room-description">
{t(room.description)}
</Text>
</div>
<IconButton
className={arrowClassName}
iconName="images/arrow.react.svg"
size={16}
onClick={() => {}}
/>
</>
);
return type === "listItem" ? (
<StyledListItem title={t(room.title)} onClick={onClick}>
{content}
</StyledListItem>
) : type === "dropdownButton" ? (
<StyledDropdownButton
title={t(room.title)}
onClick={onClick}
isOpen={isOpen}
>
{content}
</StyledDropdownButton>
) : type === "dropdownItem" ? (
<StyledDropdownItem title={t(room.title)} onClick={onClick} isOpen={isOpen}>
{content}
</StyledDropdownItem>
) : (
<StyledDisplayItem title={t(room.title)}>{content}</StyledDisplayItem>
);
};
StyledListItem.defaultProps = { theme: Base };
StyledDropdownButton.defaultProps = { theme: Base };
StyledDropdownItem.defaultProps = { theme: Base };
StyledDisplayItem.defaultProps = { theme: Base };
RoomType.propTypes = {
room: PropTypes.object,
onClick: PropTypes.func,
type: PropTypes.oneOf([
"displayItem",
"listItem",
"dropdownButton",
"dropdownItem",
]),
isOpen: PropTypes.bool,
};
export default RoomType;

View File

@ -0,0 +1,56 @@
import React from "react";
import styled from "styled-components";
import RoomType from "../RoomType";
import { Base } from "@docspace/components/themes";
const StyledDropdownDesktop = styled.div`
max-width: 100%;
position: relative;
${(props) => !props.isOpen && "display: none"};
.dropdown-content {
background: ${(props) =>
props.theme.createEditRoomDialog.roomTypeDropdown.desktop.background};
border: 1px solid
${(props) =>
props.theme.createEditRoomDialog.roomTypeDropdown.desktop.borderColor};
margin-top: 4px;
overflow: visible;
z-index: 400;
top: 0;
left: 0;
box-sizing: border-box;
width: 100%;
position: absolute;
display: flex;
flex-direction: column;
padding: 6px 0;
box-shadow: 0px 12px 40px rgba(4, 15, 27, 0.12);
border-radius: 6px;
}
`;
StyledDropdownDesktop.defaultProps = { theme: Base };
const DropdownDesktop = ({ t, open, roomTypes, chooseRoomType }) => {
return (
<StyledDropdownDesktop className="dropdown-content-wrapper" isOpen={open}>
<div className="dropdown-content">
{roomTypes.map((room) => (
<RoomType
t={t}
key={room.type}
room={room}
type="dropdownItem"
onClick={() => chooseRoomType(room.type)}
/>
))}
</div>
</StyledDropdownDesktop>
);
};
export default DropdownDesktop;

View File

@ -0,0 +1,90 @@
import React from "react";
import styled from "styled-components";
import RoomType from "../RoomType";
import { Scrollbar } from "@docspace/components";
import { Base } from "@docspace/components/themes";
const StyledDropdownMobile = styled.div`
visibility: ${(props) => (props.isOpen ? "visible" : "hidden")};
& > .dropdown-mobile-backdrop {
z-index: 999;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
margin-top: -64px;
.dropdown-mobile-wrapper {
border-radius: 6px 6px 0 0;
padding: 6px 0;
box-shadow: 0px -4px 60px rgba(4, 15, 27, 0.12);
position: fixed;
width: 100%;
height: calc(100vh - 254px);
bottom: -100%;
transition: all 0.2s ease-in-out;
&-active {
bottom: 0;
}
}
.dropdown-mobile-scrollbar {
background: rgba(6, 22, 38, 0.2);
.scroll-body {
padding-right: 0 !important;
* {
-ms-overflow-style: none;
}
::-webkit-scrollbar {
display: none;
}
}
.dropdown-mobile-content {
background: ${(props) =>
props.theme.createEditRoomDialog.roomTypeDropdown.mobile.background};
box-sizing: border-box;
width: 100%;
display: flex;
flex-direction: column;
}
}
}
`;
StyledDropdownMobile.defaultProps = { theme: Base };
const DropdownMobile = ({ t, open, onClose, roomTypes, chooseRoomType }) => {
return (
<StyledDropdownMobile className="dropdown-mobile" isOpen={open}>
<div className="dropdown-mobile-backdrop" onClick={onClose}>
<div
className={`dropdown-mobile-wrapper ${
open && "dropdown-mobile-wrapper-active"
}`}
>
<Scrollbar className="dropdown-mobile-scrollbar">
<div className="dropdown-mobile-content">
{roomTypes.map((room) => (
<RoomType
t={t}
key={room.type}
room={room}
type="dropdownItem"
onClick={() => chooseRoomType(room.type)}
/>
))}
</div>
</Scrollbar>
</div>
</div>
</StyledDropdownMobile>
);
};
export default DropdownMobile;

View File

@ -0,0 +1,67 @@
import { isHugeMobile } from "@docspace/components/utils/device";
import React, { useState } from "react";
import styled from "styled-components";
import { roomTypes } from "../../data";
import RoomType from "../RoomType";
import DropdownDesktop from "./DropdownDesktop";
import DropdownMobile from "./DropdownMobile";
const StyledRoomTypeDropdown = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const RoomTypeDropdown = ({
t,
currentRoom,
setRoomType,
setIsScrollLocked,
}) => {
const [isOpen, setIsOpen] = useState(false);
const toggleDropdown = () => {
if (isOpen) {
setIsScrollLocked(false);
setIsOpen(false);
} else {
setIsScrollLocked(true);
setIsOpen(true);
}
};
const chooseRoomType = (roomType) => {
setRoomType(roomType);
toggleDropdown();
};
return (
<StyledRoomTypeDropdown isOpen={isOpen}>
<RoomType
t={t}
room={currentRoom}
type="dropdownButton"
isOpen={isOpen}
onClick={toggleDropdown}
/>
{isHugeMobile() ? (
<DropdownMobile
t={t}
open={isOpen}
onClose={toggleDropdown}
roomTypes={roomTypes}
chooseRoomType={chooseRoomType}
/>
) : (
<DropdownDesktop
t={t}
open={isOpen}
roomTypes={roomTypes}
chooseRoomType={chooseRoomType}
/>
)}
</StyledRoomTypeDropdown>
);
};
export default RoomTypeDropdown;

View File

@ -0,0 +1,37 @@
import React from "react";
import styled from "styled-components";
import { roomTypes } from "../data";
import { withTranslation } from "react-i18next";
import RoomType from "./RoomType";
import withLoader from "@docspace/client/src/HOCs/withLoader";
import Loaders from "@docspace/common/components/Loaders";
const StyledRoomTypeList = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
`;
const RoomTypeList = ({ t, setRoomType }) => {
return (
<StyledRoomTypeList>
{roomTypes.map((room) => (
<RoomType
t={t}
key={room.type}
room={room}
type={"listItem"}
onClick={() => setRoomType(room.type)}
/>
))}
</StyledRoomTypeList>
);
};
export default withTranslation(["CreateEditRoomDialog"])(
withLoader(RoomTypeList)(<Loaders.RoomTypeListLoader />)
);

View File

@ -0,0 +1,29 @@
import React from "react";
import styled from "styled-components";
import HelpButton from "@docspace/components/help-button";
const StyledHelpButton = styled(HelpButton)`
border-radius: 50%;
background-color: #a3a9ae;
circle,
rect {
fill: #ffffff;
}
`;
const SecondaryInfoButton = ({ content }) => {
return (
<StyledHelpButton
displayType="auto"
className="set_room_params-info-title-help"
iconName="/static/images/info.react.svg"
tooltipProps={{ globalEventOff: "click" }}
tooltipContent={content}
offsetRight={0}
size={12}
/>
);
};
export default SecondaryInfoButton;

View File

@ -0,0 +1,172 @@
import React from "react";
import styled from "styled-components";
import { withTranslation } from "react-i18next";
import { roomTypes } from "../data";
import RoomTypeDropdown from "./RoomTypeDropdown";
import ThirdPartyStorage from "./ThirdPartyStorage";
import TagInput from "./TagInput";
import RoomType from "./RoomType";
import IconEditor from "./IconEditor";
import PermanentSettings from "./PermanentSettings";
import InputParam from "./Params/InputParam";
import IsPrivateParam from "./IsPrivateParam";
import withLoader from "@docspace/client/src/HOCs/withLoader";
import Loaders from "@docspace/common/components/Loaders";
const StyledSetRoomParams = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 20px;
`;
const SetRoomParams = ({
t,
roomParams,
setRoomParams,
setIsOauthWindowOpen,
setRoomType,
tagHandler,
setIsScrollLocked,
isEdit,
connectItems,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
}) => {
const onChangeName = (e) => {
// let value = e.target.value;
// value = value.replace("/", "");
// value = value.replace("\\", "");
// const storageFolderPath = roomParams.storageLocation.storageFolderPath;
// const pathArr = storageFolderPath.split("/");
// const folderName = pathArr.pop();
// if (roomParams.title === folderName)
// setRoomParams({
// ...roomParams,
// title: value,
// storageLocation: {
// ...roomParams.storageLocation,
// storageFolderPath:
// pathArr.join("/") + (!!pathArr.length ? "/" : "") + value,
// },
// });
// else
setRoomParams({ ...roomParams, title: e.target.value });
};
const onChangeIsPrivate = () =>
setRoomParams({ ...roomParams, isPrivate: !roomParams.isPrivate });
// const onChangeThidpartyFolderName = (e) =>
// setRoomParams({ ...roomParams, thirdpartyFolderName: e.target.value });
const onChangeIcon = (icon) => setRoomParams({ ...roomParams, icon: icon });
const onChangeIsThirdparty = () =>
setRoomParams({ ...roomParams, isThirdparty: !roomParams.isThirdparty });
const setChangeStorageLocation = (storageLocation) =>
setRoomParams({ ...roomParams, storageLocation });
const onChangeRememberThirdpartyStorage = () =>
setRoomParams({
...roomParams,
rememberThirdpartyStorage: !roomParams.rememberThirdpartyStorage,
});
const [currentRoomTypeData] = roomTypes.filter(
(room) => room.type === roomParams.type
);
return (
<StyledSetRoomParams>
{isEdit ? (
<RoomType t={t} room={currentRoomTypeData} type="displayItem" />
) : (
<RoomTypeDropdown
t={t}
currentRoom={currentRoomTypeData}
setRoomType={setRoomType}
setIsScrollLocked={setIsScrollLocked}
/>
)}
{isEdit && (
<PermanentSettings
t={t}
title={roomParams.title}
isThirdparty={roomParams.isThirdparty}
storageLocation={roomParams.storageLocation}
isPrivate={roomParams.isPrivate}
/>
)}
<InputParam
id={"room-name"}
title={`${t("Common:Name")}:`}
placeholder={t("NamePlaceholder")}
value={roomParams.title}
onChange={onChangeName}
/>
<TagInput
t={t}
tagHandler={tagHandler}
currentRoomTypeData={currentRoomTypeData}
setIsScrollLocked={setIsScrollLocked}
/>
{/* {!isEdit && (
<IsPrivateParam
t={t}
isPrivate={roomParams.isPrivate}
onChangeIsPrivate={onChangeIsPrivate}
/>
)} */}
{!isEdit && (
<ThirdPartyStorage
t={t}
connectItems={connectItems}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
roomParams={roomParams}
isThirdparty={roomParams.isThirdparty}
onChangeIsThirdparty={onChangeIsThirdparty}
storageLocation={roomParams.storageLocation}
setChangeStorageLocation={setChangeStorageLocation}
rememberThirdpartyStorage={roomParams.rememberThirdpartyStorage}
onChangeRememberThirdpartyStorage={onChangeRememberThirdpartyStorage}
setIsScrollLocked={setIsScrollLocked}
setIsOauthWindowOpen={setIsOauthWindowOpen}
/>
)}
<IconEditor
t={t}
title={roomParams.title}
tags={roomParams.tags}
currentRoomTypeData={currentRoomTypeData}
icon={roomParams.icon}
onChangeIcon={onChangeIcon}
/>
</StyledSetRoomParams>
);
};
export default withTranslation(["CreateEditRoomDialog"])(
withLoader(SetRoomParams)(<Loaders.SetRoomParamsLoader />)
);

View File

@ -0,0 +1,62 @@
import styled from "styled-components";
import { smallTablet } from "@docspace/components/utils/device";
import DropDown from "@docspace/components/drop-down";
import { Base } from "@docspace/components/themes";
const StyledDropDownWrapper = styled.div`
width: 100%;
position: relative;
`;
const StyledDropDown = styled(DropDown)`
margin-top: ${(props) => (props.marginTop ? props.marginTop : "4px")};
padding: 6px 0;
background: ${(props) =>
props.theme.createEditRoomDialog.dropdown.background};
border: 1px solid
${(props) => props.theme.createEditRoomDialog.dropdown.borderColor};
box-shadow: 0px 12px 40px rgba(4, 15, 27, 0.12);
border-radius: 3px;
overflow: hidden;
width: 446px;
max-width: 446px;
div {
max-width: 446px;
}
@media ${smallTablet} {
width: calc(100vw - 34px);
max-width: calc(100vw - 34px);
div {
max-width: calc(100vw - 34px);
}
}
.dropdown-item {
height: 32px !important;
max-height: 32px !important;
cursor: pointer;
box-sizing: border-box;
width: 100%;
padding: 6px 8px;
font-weight: 400;
font-size: 13px;
line-height: 20px;
&:hover {
background: ${(props) =>
props.theme.createEditRoomDialog.dropdown.item.hoverBackground};
}
&-separator {
height: 7px !important;
max-height: 7px !important;
}
}
`;
StyledDropDown.defaultProps = { theme: Base };
export { StyledDropDownWrapper, StyledDropDown };

View File

@ -0,0 +1,116 @@
import React, { useRef, useState, useEffect } from "react";
import { StyledDropDown, StyledDropDownWrapper } from "../StyledDropdown";
import DropDownItem from "@docspace/components/drop-down-item";
import { isHugeMobile } from "@docspace/components/utils/device";
import DomHelpers from "@docspace/components/utils/domHelpers";
const TagDropdown = ({
open,
tagHandler,
tagInputValue,
setTagInputValue,
createTagLabel,
}) => {
const dropdownRef = useRef(null);
const [dropdownMaxHeight, setDropdownMaxHeight] = useState(0);
const chosenTags = tagHandler.tags.map((tag) => tag.name);
const tagsForDropdown = tagHandler.fetchedTags.filter(
(tag) =>
tag.toLowerCase().includes(tagInputValue.toLowerCase()) &&
!chosenTags.includes(tag)
);
const preventDefault = (e) => {
e.preventDefault();
};
const onClickOutside = () => {
document.getElementById("tags-input").blur();
};
const addNewTag = () => {
tagHandler.addNewTag(tagInputValue);
setTagInputValue("");
onClickOutside();
};
const addFetchedTag = (name) => {
tagHandler.addTag(name);
setTagInputValue("");
onClickOutside();
};
const calcualateDisplayedDropdownItems = () => {
let res = tagsForDropdown.map((tag, i) => (
<DropDownItem
className="dropdown-item"
height={32}
heightTablet={32}
key={i}
label={tag}
onClick={() => addFetchedTag(tag)}
/>
));
if (
tagInputValue &&
![...tagsForDropdown, ...chosenTags].find((tag) => tagInputValue === tag)
)
res = [
<DropDownItem
key={-2}
className="dropdown-item"
onMouseDown={preventDefault}
onClick={addNewTag}
label={`${createTagLabel}${tagInputValue}`}
height={32}
heightTablet={32}
/>,
...res,
];
return res;
};
useEffect(() => {
if (dropdownRef && open) {
const { top: offsetTop } = DomHelpers.getOffset(dropdownRef.current);
const offsetBottom = window.innerHeight - offsetTop;
const maxHeight = Math.floor((offsetBottom - 22) / 32) * 32 - 2;
const result = isHugeMobile()
? Math.min(maxHeight, 158)
: Math.min(maxHeight, 382);
setDropdownMaxHeight(result);
}
}, [open]);
const dropdownItems = calcualateDisplayedDropdownItems();
return (
<StyledDropDownWrapper
ref={dropdownRef}
className="dropdown-content-wrapper"
onMouseDown={preventDefault}
>
{!!dropdownItems.length && (
<StyledDropDown
className="dropdown-content"
open={open}
forwardedRef={dropdownRef}
clickOutsideAction={onClickOutside}
maxHeight={dropdownMaxHeight}
showDisabledItems={false}
>
{dropdownItems}
</StyledDropDown>
)}
</StyledDropDownWrapper>
);
};
export default TagDropdown;

View File

@ -0,0 +1,54 @@
import Tag from "@docspace/components/tag";
import React from "react";
import styled from "styled-components";
const StyledTagList = styled.div`
margin-top: 12px;
display: flex;
flex-direction: row;
gap: 4px;
flex-wrap: wrap;
width: 100%;
.set_room_params-tag_input-tag {
padding: 6px 8px;
border-radius: 3px;
margin: 0;
.tag-icon {
margin-left: 10px;
}
}
`;
const TagList = ({ defaultTagLabel, tagHandler }) => {
const { tags } = tagHandler;
return (
<StyledTagList className="set_room_params-tag_input-tag_list">
{tags.length ? (
tags.map((tag) => (
<Tag
key={tag.id}
className="set_room_params-tag_input-tag"
tag="script"
label={tag.name}
isNewTag
onDelete={() => {
tagHandler.deleteTag(tag.id);
}}
/>
))
) : (
<Tag
className="set_room_params-tag_input-tag"
tag="script"
label={defaultTagLabel}
isDefault
/>
)}
</StyledTagList>
);
};
export default TagList;

View File

@ -0,0 +1,76 @@
import React, { useState } from "react";
import styled from "styled-components";
import TagList from "./TagList";
import InputParam from "../Params/InputParam";
import TagDropdown from "./TagDropdown";
const StyledTagInput = styled.div`
.set_room_params-tag_input {
&-label_wrapper {
&-label {
cursor: pointer;
width: auto;
display: inline-block;
}
}
}
.dropdown-content-wrapper {
margin-bottom: -4px;
max-width: 100%;
position: relative;
}
`;
const TagInput = ({
t,
tagHandler,
currentRoomTypeData,
setIsScrollLocked,
}) => {
const [tagInput, setTagInput] = useState("");
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const onTagInputChange = (e) => setTagInput(e.target.value);
const openDropdown = () => {
setIsScrollLocked(true);
setIsDropdownOpen(true);
};
const closeDropdown = () => {
setIsScrollLocked(false);
setIsDropdownOpen(false);
};
return (
<StyledTagInput className="set_room_params-input set_room_params-tag_input">
<InputParam
id={"tags-input"}
title={`${t("Common:Tags")}:`}
placeholder={t("TagsPlaceholder")}
value={tagInput}
onChange={onTagInputChange}
onFocus={openDropdown}
onBlur={closeDropdown}
/>
<TagDropdown
open={isDropdownOpen}
tagHandler={tagHandler}
tagInputValue={tagInput}
setTagInputValue={setTagInput}
createTagLabel={t("CreateTagOption")}
/>
<TagList
tagHandler={tagHandler}
defaultTagLabel={t(currentRoomTypeData.defaultTag)}
/>
</StyledTagInput>
);
};
export default TagInput;

View File

@ -0,0 +1,117 @@
import React, { useState } from "react";
import styled from "styled-components";
import { IconButton, TextInput } from "@docspace/components";
import { Base } from "@docspace/components/themes";
const StyledFolderInput = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: row;
gap: 0px;
width: 100%;
height: 32px;
border-radius: 3px;
transition: all 0.2s ease;
&,
.icon-wrapper {
border: 1px solid
${(props) =>
props.isFocused
? props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.focusBorderColor
: props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.borderColor};
}
&:hover,
&:hover > .icon-wrapper {
border: 1px solid
${(props) =>
props.isFocused
? props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.focusBorderColor
: props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.hoverBorderColor};
}
.root_label {
padding: 5px 2px 5px 7px;
font-weight: 400;
font-size: 13px;
line-height: 20px;
background-color: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.background};
color: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.rootLabelColor};
}
.text_input {
padding-left: 0;
border: none;
border-radius: 0px;
font-weight: 400;
font-size: 13px;
line-height: 20px;
}
.icon-wrapper {
cursor: pointer;
background-color: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.background};
height: 100%;
box-sizing: border-box;
width: 31px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
border-top: none !important;
border-right: none !important;
border-bottom: none !important;
&:hover {
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.folderInput
.iconFill};
}
}
}
`;
StyledFolderInput.defaultProps = { theme: Base };
const FolderInput = ({ value, onChangeFolderPath }) => {
const [isFocused, setIsFocused] = useState();
const onFocus = () => setIsFocused(true);
const onBlur = () => setIsFocused(false);
return (
<StyledFolderInput isFocused={isFocused}>
<span className="root_label">ROOT/</span>
<TextInput
className="text_input"
value={value}
onChange={onChangeFolderPath}
onFocus={onFocus}
onBlur={onBlur}
/>
<div className="icon-wrapper">
<IconButton
size={16}
iconName="images/folder.react.svg"
isClickable
onClick={() => {}}
/>
</div>
</StyledFolderInput>
);
};
export default FolderInput;

View File

@ -0,0 +1,271 @@
import React, { useEffect, useState, useRef } from "react";
import styled from "styled-components";
import { ReactSVG } from "react-svg";
import { StyledDropDown, StyledDropDownWrapper } from "../StyledDropdown";
import { isHugeMobile } from "@docspace/components/utils/device";
import DomHelpers from "@docspace/components/utils/domHelpers";
import Text from "@docspace/components/text";
import Button from "@docspace/components/button";
import DropDownItem from "@docspace/components/drop-down-item";
import { connectedCloudsTypeTitleTranslation } from "@docspace/client/src/helpers/filesUtils";
import { Base } from "@docspace/components/themes";
const StyledStorageLocation = styled.div`
display: flex;
flex-direction: column;
.set_room_params-thirdparty {
display: flex;
flex-direction: row;
gap: 8px;
&-combobox {
cursor: pointer;
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px 7px;
background: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.combobox.background};
border-radius: 3px;
max-height: 32px;
border: ${(props) =>
`1px solid ${
props.isOpen
? props.theme.createEditRoomDialog.thirdpartyStorage.combobox
.isOpenDropdownBorderColor
: props.theme.createEditRoomDialog.thirdpartyStorage.combobox
.dropdownBorderColor
}`};
transition: all 0.2s ease;
&:hover {
border: ${(props) =>
`1px solid ${
props.isOpen
? props.theme.createEditRoomDialog.thirdpartyStorage.combobox
.isOpenDropdownBorderColor
: props.theme.createEditRoomDialog.thirdpartyStorage.combobox
.hoverDropdownBorderColor
}`};
}
&-text {
font-weight: 400;
font-size: 13px;
line-height: 20px;
}
&-expander {
display: flex;
align-items: center;
justify-content: center;
width: 6.35px;
svg {
transform: ${(props) =>
props.isOpen ? "rotate(180deg)" : "rotate(0)"};
width: 6.35px;
height: auto;
path {
fill: ${(props) =>
props.theme.createEditRoomDialog.thirdpartyStorage.combobox
.arrowFill};
}
}
}
}
&-checkbox {
margin-top: 8px;
.checkbox {
margin-right: 8px;
}
.checkbox-text {
font-weight: 400;
font-size: 13px;
line-height: 20px;
}
}
}
`;
StyledStorageLocation.defaultProps = { theme: Base };
const ThirpartyComboBox = ({
t,
storageLocation,
setChangeStorageLocation,
connectItems,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
setIsScrollLocked,
setIsOauthWindowOpen,
}) => {
const dropdownRef = useRef(null);
const thirdparties = connectItems.map((item) => ({
...item,
title: connectedCloudsTypeTitleTranslation(item.providerName, t),
}));
const [isOpen, setIsOpen] = useState(false);
const [dropdownDirection, setDropdownDirection] = useState("bottom");
const toggleIsOpen = () => {
if (isOpen) setIsScrollLocked(false);
else {
setIsScrollLocked(true);
calculateDropdownDirection();
}
setIsOpen(!isOpen);
};
const setStorageLocaiton = (thirparty) => {
setChangeStorageLocation({
...storageLocation,
provider: thirparty,
});
setIsOpen(false);
setIsScrollLocked(false);
};
const calculateDropdownDirection = () => {
const { top: offsetTop } = DomHelpers.getOffset(dropdownRef.current);
const offsetBottom = window.innerHeight - offsetTop;
const neededHeightDesktop = Math.min(thirdparties.length * 32 + 16, 404);
const neededHeightMobile = Math.min(thirdparties.length * 32 + 16, 180);
const neededheight = isHugeMobile()
? neededHeightMobile
: neededHeightDesktop;
setDropdownDirection(neededheight > offsetBottom ? "top" : "bottom");
};
const onShowService = async () => {
setRoomCreation(true);
const item = {
title: connectedCloudsTypeTitleTranslation(
storageLocation.provider.providerName,
t
),
customer_title: "NOTITLE",
provider_key: storageLocation.provider.providerName,
link: storageLocation.provider.oauthHref,
};
if (storageLocation.provider.isOauth) {
setIsOauthWindowOpen(true);
let authModal = window.open(
"",
"Authorization",
"height=600, width=1020"
);
await openConnectWindow(storageLocation.provider.providerName, authModal)
.then(getOAuthToken)
.then((token) => {
authModal.close();
setConnectItem({
...item,
token,
});
setConnectDialogVisible(true);
})
.catch((e) => {
if (!e) return;
console.error(e);
})
.finally(() => {
setIsOauthWindowOpen(false);
});
} else {
setConnectItem(item);
setConnectDialogVisible(true);
}
};
useEffect(() => {
if (!saveThirdpartyResponse) return;
if (saveThirdpartyResponse.id) {
setChangeStorageLocation({
...storageLocation,
isConnected: true,
thirdpartyFolderId: saveThirdpartyResponse.id,
});
} else {
setChangeStorageLocation({
...storageLocation,
isConnected: false,
});
}
}, [saveThirdpartyResponse]);
return (
<StyledStorageLocation isOpen={isOpen}>
<div className="set_room_params-thirdparty">
<div
className="set_room_params-thirdparty-combobox"
onClick={toggleIsOpen}
>
<Text className="set_room_params-thirdparty-combobox-text" noSelect>
{storageLocation?.provider?.title ||
t("ThirdPartyStorageComboBoxPlaceholder")}
</Text>
<ReactSVG
className="set_room_params-thirdparty-combobox-expander"
src={"/static/images/expander-down.react.svg"}
/>
</div>
<Button
isDisabled={!storageLocation?.provider}
className="set_room_params-thirdparty-connect"
size="small"
label={t("Common:Connect")}
onClick={onShowService}
/>
</div>
<StyledDropDownWrapper
className="dropdown-content-wrapper"
ref={dropdownRef}
>
<StyledDropDown
className="dropdown-content"
open={isOpen}
forwardedRef={dropdownRef}
clickOutsideAction={toggleIsOpen}
maxHeight={isHugeMobile() ? 158 : 382}
directionY={dropdownDirection}
marginTop={dropdownDirection === "bottom" ? "4px" : "-36px"}
>
{thirdparties.map((thirdparty) => (
<DropDownItem
className="dropdown-item"
label={thirdparty.title}
key={thirdparty.id}
height={32}
heightTablet={32}
onClick={() => setStorageLocaiton(thirdparty)}
/>
))}
</StyledDropDown>
</StyledDropDownWrapper>
</StyledStorageLocation>
);
};
export default ThirpartyComboBox;

View File

@ -0,0 +1,117 @@
import Checkbox from "@docspace/components/checkbox";
import React from "react";
import styled from "styled-components";
import { StyledParam } from "../Params/StyledParam";
import ToggleParam from "../Params/ToggleParam";
import ThirpartyComboBox from "./ThirpartyComboBox";
import Toast from "@docspace/components/toast";
import toastrHelper from "@docspace/client/src/helpers/toastr";
import FolderInput from "./FolderInput";
const StyledThirdPartyStorage = styled(StyledParam)`
flex-direction: column;
gap: 12px;
`;
const ThirdPartyStorage = ({
t,
connectItems,
setConnectDialogVisible,
setRoomCreation,
saveThirdpartyResponse,
openConnectWindow,
setConnectItem,
getOAuthToken,
isThirdparty,
onChangeIsThirdparty,
storageLocation,
setChangeStorageLocation,
rememberThirdpartyStorage,
onChangeRememberThirdpartyStorage,
setIsScrollLocked,
setIsOauthWindowOpen,
}) => {
const checkForProviders = () => {
if (connectItems.length) onChangeIsThirdparty();
else
toastrHelper.warning(
<div>
<div>{t("ThirdPartyStorageNoStorageAlert")}</div>
<a href="#">Third-party services</a>
</div>,
"Alert",
5000,
true,
false
);
};
const onChangeProvider = (provider) =>
setChangeStorageLocation({ ...storageLocation, provider });
const onChangeFolderPath = (e) =>
setChangeStorageLocation({
...storageLocation,
storageFolderPath: e.target.value,
});
return (
<StyledThirdPartyStorage>
{/* <div className="set_room_params-info">
<div className="set_room_params-info-title">
<Text className="set_room_params-info-title-text">
{t("ThirdPartyStorageTitle")}
</Text>
</div>
<div className="set_room_params-info-description">
{t("ThirdPartyStorageDescription")}
</div>
</div> */}
<ToggleParam
title={t("ThirdPartyStorageTitle")}
description={t("ThirdPartyStorageDescription")}
isChecked={isThirdparty}
onCheckedChange={checkForProviders}
/>
{isThirdparty && (
<ThirpartyComboBox
t={t}
connectItems={connectItems}
setConnectDialogVisible={setConnectDialogVisible}
setRoomCreation={setRoomCreation}
saveThirdpartyResponse={saveThirdpartyResponse}
openConnectWindow={openConnectWindow}
setConnectItem={setConnectItem}
getOAuthToken={getOAuthToken}
storageLocation={storageLocation}
onChangeProvider={onChangeProvider}
setChangeStorageLocation={setChangeStorageLocation}
setIsScrollLocked={setIsScrollLocked}
setIsOauthWindowOpen={setIsOauthWindowOpen}
/>
)}
{/* {isThirdparty && storageLocation.isConnected && (
<FolderInput
value={storageLocation.storageFolderPath}
onChangeFolderPath={onChangeFolderPath}
/>
)} */}
{/* {isThirdparty && storageLocation.isConnected && (
<Checkbox
className="thirdparty-checkbox"
label={t("ThirdPartyStorageRememberChoice")}
isChecked={rememberThirdpartyStorage}
onChange={onChangeRememberThirdpartyStorage}
/>
)} */}
</StyledThirdPartyStorage>
);
};
export default ThirdPartyStorage;

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

@ -20,6 +20,8 @@ import ChangeUserTypeDialog from "./ChangeUserTypeDialog";
import DataLossWarningDialog from "./DataLossWarningDialog";
import ResetApplicationDialog from "./ResetApplicationDialog";
import BackupCodesDialog from "./BackupCodesDialog";
import CreateRoomDialog from "./CreateEditRoomDialog/CreateRoomDialog";
import EditRoomDialog from "./CreateEditRoomDialog/EditRoomDialog";
export {
EmptyTrashDialog,
@ -44,4 +46,6 @@ export {
DataLossWarningDialog,
ResetApplicationDialog,
BackupCodesDialog,
CreateRoomDialog,
EditRoomDialog,
};

View File

@ -24,6 +24,8 @@ class SelectFolderInput extends React.PureComponent {
resultingFolderTree: [],
baseId: "",
};
this._isMount = false;
}
setBaseInfo = async () => {
@ -52,20 +54,22 @@ class SelectFolderInput extends React.PureComponent {
);
} catch (e) {
toastr.error(e);
this.setState({
isLoading: false,
});
this._isMount &&
this.setState({
isLoading: false,
});
return;
}
this.setState({
isPathError: false,
resultingFolderTree,
baseId: resultingId,
baseFolderPath: "",
newFolderPath: "",
...(withoutBasicSelection && { isLoading: false }),
});
this._isMount &&
this.setState({
isPathError: false,
resultingFolderTree,
baseId: resultingId,
baseFolderPath: "",
newFolderPath: "",
...(withoutBasicSelection && { isLoading: false }),
});
};
componentDidMount() {
@ -140,7 +144,7 @@ class SelectFolderInput extends React.PureComponent {
};
onSetNewFolderPath = async (folderId) => {
let timerId = setTimeout(() => {
this.setState({ isLoading: true });
this._isMount && this.setState({ isLoading: true });
}, 500);
try {
const convertFoldersArray = await this.setFolderPath(folderId);
@ -157,10 +161,11 @@ class SelectFolderInput extends React.PureComponent {
toastr.error(e);
clearTimeout(timerId);
timerId = null;
this.setState({
isLoading: false,
isPathError: true,
});
this._isMount &&
this.setState({
isLoading: false,
isPathError: true,
});
}
};
onSetBaseFolderPath = async (folderId) => {
@ -175,14 +180,16 @@ class SelectFolderInput extends React.PureComponent {
});
} catch (e) {
toastr.error(e);
this.setState({
isLoading: false,
isPathError: true,
});
this._isMount &&
this.setState({
isLoading: false,
isPathError: true,
});
}
};
onSelectFolder = (folderId) => {
const { onSelectFolder: onSelect } = this.props;
if (!this._isMount) return;
this.onSetFolderInfo(folderId);
onSelect && onSelect(folderId);
};

View File

@ -15,6 +15,7 @@ export const Events = Object.freeze({
CREATE: "create",
RENAME: "rename",
ROOM_CREATE: "create_room",
ROOM_EDIT: "edit_room",
});
export const FilterGroups = Object.freeze({

View File

@ -205,3 +205,29 @@ export const connectedCloudsTypeTitleTranslation = (key, t) => {
return key;
}
};
export const connectedCloudsTypeIcon = (key) => {
switch (key) {
case "GoogleDrive":
return "/static/images/cloud.services.google.drive.react.svg";
case "Box":
return "/static/images/cloud.services.box.react.svg";
case "DropboxV2":
return "/static/images/cloud.services.dropbox.react.svg";
case "OneDrive":
return "/static/images/cloud.services.onedrive.react.svg";
case "SharePoint":
return "/static/images/cloud.services.onedrive.react.svg";
case "kDrive":
return "/static/images/cloud.services.kdrive.react.svg";
case "Yandex":
return "/static/images/cloud.services.yandex.react.svg";
case "NextCloud":
return "/static/images/cloud.services.nextcloud.react.svg";
case "OwnCloud":
return "/static/images/catalog.folder.react.svg";
case "WebDav":
return "/static/images/cloud.services.webdav.react.svg";
default:
}
};

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} />
))}
@ -51,8 +51,8 @@ const SectionBodyContent = ({
);
};
export default inject(({ filesStore }) => ({
oformFiles: filesStore.oformFiles,
hasGalleryFiles: filesStore.hasGalleryFiles,
setGallerySelected: filesStore.setGallerySelected,
export default inject(({ oformsStore }) => ({
oformFiles: oformsStore.oformFiles,
hasGalleryFiles: oformsStore.hasGalleryFiles,
setGallerySelected: oformsStore.setGallerySelected,
}))(withTranslation("FormGallery")(observer(SectionBodyContent)));

View File

@ -71,9 +71,10 @@ const SectionHeaderContent = (props) => {
);
};
export default inject(({ auth, filesStore }) => {
export default inject(({ auth, filesStore, oformsStore }) => {
const { toggleIsVisible, isVisible } = auth.infoPanelStore;
const { setGallerySelected, categoryType } = filesStore;
const { categoryType } = filesStore;
const { setGallerySelected } = oformsStore;
return {
toggleInfoPanel: toggleIsVisible,
isInfoPanelVisible: isVisible,

View File

@ -210,7 +210,6 @@ const SimpleFilesTileContent = styled(TileContent)`
const paddingCss = css`
@media ${desktop} {
margin-left: 1px;
padding-right: 3px;
}
@ -236,6 +235,7 @@ const StyledGridWrapper = styled.div`
const StyledTileContainer = styled.div`
position: relative;
height: 100%;
.tile-item-wrapper {
position: relative;
@ -386,6 +386,25 @@ const MainContainer = styled.div`
}
`;
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};
`;
export {
StyledTile,
StyledFileTileTop,
@ -399,4 +418,6 @@ export {
StyledTileContent,
MainContainerWrapper,
MainContainer,
StyledCard,
StyledItem,
};

View File

@ -0,0 +1,129 @@
import React from "react";
import { inject, observer } from "mobx-react";
import InfiniteLoaderComponent from "@docspace/components/infinite-loader";
import { StyledCard, StyledItem } from "../StyledTileView";
import Loaders from "@docspace/common/components/Loaders";
import uniqueid from "lodash/uniqueId";
const Card = ({ children, ...rest }) => {
const horizontalGap = 16;
const fileHeight = 220 + horizontalGap;
const cardHeight = fileHeight;
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,
...rest
} = props;
const countTilesInRow = getCountTilesInRow();
let cards = [];
const list = [];
const addItemToList = (key, clear) => {
list.push(
<Item key={key} className="isFile">
{cards}
</Item>
);
if (clear) cards = [];
};
React.Children.map(children, (child) => {
if (child) {
if (cards.length && cards.length === countTilesInRow) {
const listKey = uniqueid("list-item_");
addItemToList(listKey, true);
}
const cardKey = uniqueid("card-item_");
cards.push(<Card key={cardKey}>{child}</Card>);
}
});
if (hasMoreFiles) {
// If cards elements are full, it will add the full line of loaders
if (cards.length === countTilesInRow) {
addItemToList("loaded-row", 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 isFile"}
isFolder={false}
/>
);
}
addItemToList("loaded-row");
} else if (cards.length) {
// Adds loaders until the row is full
const listKey = uniqueid("list-item_");
addItemToList(listKey);
}
// 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}`}
{...rest}
>
{list}
</InfiniteLoaderComponent>
);
};
export default inject(({ filesStore, oformsStore }) => {
const {
oformFiles,
hasMoreForms,
oformsFilterTotal,
loadMoreForms,
} = oformsStore;
const { getCountTilesInRow } = filesStore;
const filesLength = oformFiles.length;
return {
filesList: oformFiles,
hasMoreFiles: hasMoreForms,
filterTotal: oformsFilterTotal,
fetchMoreFiles: loadMoreForms,
filesLength,
getCountTilesInRow,
};
})(observer(InfiniteGrid));

View File

@ -210,19 +210,22 @@ Tile.defaultProps = {
item: {},
};
export default inject(({ filesStore, settingsStore, auth }, { item }) => {
const { gallerySelected, setGallerySelected, categoryType } = filesStore;
const { getIcon } = settingsStore;
const { isVisible, setIsVisible } = auth.infoPanelStore;
export default inject(
({ filesStore, settingsStore, auth, oformsStore }, { item }) => {
const { categoryType } = filesStore;
const { gallerySelected, setGallerySelected } = oformsStore;
const { getIcon } = settingsStore;
const { isVisible, setIsVisible } = auth.infoPanelStore;
const isSelected = item.id === gallerySelected?.id;
const isSelected = item.id === gallerySelected?.id;
return {
isSelected,
setGallerySelected,
getIcon,
setIsInfoPanelVisible: setIsVisible,
isInfoPanelVisible: isVisible,
categoryType,
};
})(withTranslation(["FormGallery", "Common"])(withRouter(observer(Tile))));
return {
isSelected,
setGallerySelected,
getIcon,
setIsInfoPanelVisible: setIsVisible,
isInfoPanelVisible: isVisible,
categoryType,
};
}
)(withTranslation(["FormGallery", "Common"])(withRouter(observer(Tile))));

View File

@ -1,49 +1,17 @@
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";
import InfiniteGrid from "./InfiniteGrid";
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, useReactWindow, ...rest } = this.props;
return (
<StyledTileContainer
id={id}
className={className}
style={style}
useReactWindow={useReactWindow}
>
<StyledTileContainer {...rest}>
{useReactWindow ? (
<AutoSizer>{renderList}</AutoSizer>
<InfiniteGrid>{children}</InfiniteGrid>
) : (
<StyledGridWrapper>{children}</StyledGridWrapper>
)}
@ -53,19 +21,15 @@ class TileContainer extends React.PureComponent {
}
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",
id: "tileContainer",
};
export default withTranslation(["Files", "Common"])(TileContainer);

View File

@ -1,16 +1,26 @@
import React from "react";
import React, { useEffect } from "react";
import Section from "@docspace/common/components/Section";
import { observer, inject } from "mobx-react";
import SectionHeaderContent from "./Header";
import SectionBodyContent from "./Body";
import { InfoPanelBodyContent } from "../Home/InfoPanel";
import InfoPanelHeaderContent from "../Home/InfoPanel/Header";
const FormGallery = () => {
const FormGallery = ({ getOforms, setOformFiles }) => {
useEffect(() => {
getOforms();
return () => {
setOformFiles(null);
};
}, [getOforms, setOformFiles]);
return (
<Section
// withBodyScroll
// withBodyAutoFocus={!isMobile}
// withBodyScroll
// withBodyAutoFocus={!isMobile}
withPaging={false}
>
<Section.SectionHeader>
<SectionHeaderContent />
@ -28,4 +38,11 @@ const FormGallery = () => {
);
};
export default FormGallery;
export default inject(({ oformsStore }) => {
const { getOforms, setOformFiles } = oformsStore;
return {
getOforms,
setOformFiles,
};
})(observer(FormGallery));

View File

@ -55,7 +55,9 @@ const SingleItem = (props) => {
<StyledProperties>
<div className="property">
<Text className="property-title">{t("Home:ByLastModifiedDate")}</Text>
<Text className="property-title">
{t("Files:ByLastModifiedDate")}
</Text>
<Text className="property-content">
{parseAndFormatDate(selectedItem.attributes.updatedAt)}
</Text>

View File

@ -147,7 +147,7 @@ const SingleItem = (props) => {
},
{
id: "ByLastModifiedDate",
title: t("Home:ByLastModifiedDate"),
title: t("Files:ByLastModifiedDate"),
content: styledText(parseAndFormatDate(item.updated)),
},
{

View File

@ -118,6 +118,7 @@ export default inject(
dialogsStore,
treeFoldersStore,
selectedFolderStore,
oformsStore,
}) => {
const { personal, culture } = auth.settingsStore;
@ -126,9 +127,9 @@ export default inject(
bufferSelection,
getFolderInfo,
getShareUsers,
gallerySelected,
createThumbnail,
} = filesStore;
const { gallerySelected } = oformsStore;
const { getIcon, getFolderIcon } = settingsStore;
const { onSelectItem } = filesActionsStore;
const { setSharingPanelVisible } = dialogsStore;

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

@ -12,6 +12,7 @@ import RowContent from "@docspace/components/row-content";
import withContent from "../../../../../HOCs/withContent";
import withBadges from "../../../../../HOCs/withBadges";
import { Base } from "@docspace/components/themes";
import { RoomsTypeTranslations } from "@docspace/common/constants";
const SimpleFilesRowContent = styled(RowContent)`
.row-main-container-wrapper {
@ -105,7 +106,7 @@ const FilesRowContent = ({
if (item.tags.length > 0) {
tags = item?.tags.join(" | ");
} else {
tags = t("NoTag");
tags = t(RoomsTypeTranslations[item.roomType]);
}
}

View File

@ -231,7 +231,7 @@ const SimpleFilesRow = (props) => {
const element = (
<ItemIcon
id={item.id}
icon={item.icon}
icon={item.isRoom && item.logo.big ? item.logo.big : item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
/>

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,42 @@ 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;
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

@ -296,13 +296,14 @@ const FilesTableRow = (props) => {
id,
tagRef,
isRooms,
onSelectType,
} = props;
const { acceptBackground, background } = theme.dragAndDrop;
const element = (
<ItemIcon
id={item.id}
icon={item.icon}
icon={item.isRoom && item.logo.big ? item.logo.big : item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
/>

View File

@ -3,21 +3,28 @@ import React from "react";
import Tags from "@docspace/common/components/Tags";
import Tag from "@docspace/components/tag";
import { RoomsTypeTranslations } from "@docspace/common/constants";
const TagsCell = React.forwardRef(({ t, item, tagCount, onSelectTag }, ref) => {
return (
<div style={{ width: "100%", overflow: "hidden" }} ref={ref}>
{item.tags.length > 0 ? (
<Tags
tags={item.tags}
columnCount={tagCount}
onSelectTag={onSelectTag}
/>
) : (
<Tag label={t("NoTag")} onClick={onSelectTag} />
)}
</div>
);
});
const TagsCell = React.forwardRef(
({ t, item, tagCount, onSelectTag, onSelectType }, ref) => {
return (
<div style={{ width: "100%", overflow: "hidden" }} ref={ref}>
{item.tags.length > 0 ? (
<Tags
tags={item.tags}
columnCount={tagCount}
onSelectTag={onSelectTag}
/>
) : (
<Tag
isDefault
label={t(RoomsTypeTranslations[item.roomType])}
onClick={() => onSelectType(item.roomType)}
/>
)}
</div>
);
}
);
export default React.memo(TagsCell);

View File

@ -43,6 +43,7 @@ const FileTile = (props) => {
setSelection,
id,
onSelectTag,
onSelectType,
columnCount,
} = props;
@ -61,7 +62,7 @@ const FileTile = (props) => {
const element = (
<ItemIcon
id={item.id}
icon={item.icon}
icon={item.isRoom && item.logo.big ? item.logo.big : item.icon}
fileExst={item.fileExst}
isRoom={item.isRoom}
/>
@ -110,6 +111,7 @@ const FileTile = (props) => {
showHotkeyBorder={showHotkeyBorder}
setSelection={setSelection}
selectTag={onSelectTag}
selectType={onSelectType}
columnCount={columnCount}
>
<FilesTileContent

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,204 @@
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;
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

@ -13,6 +13,7 @@ import Loader from "@docspace/components/loader";
import { Base } from "@docspace/components/themes";
import Tags from "@docspace/common/components/Tags";
import Tag from "@docspace/components/tag";
import { RoomsTypeTranslations } from "@docspace/common/constants";
const svgLoader = () => <div style={{ width: "96px" }} />;
@ -466,6 +467,7 @@ class Tile extends React.PureComponent {
t,
columnCount,
selectTag,
selectType,
} = this.props;
const { isFolder, isRoom, id, fileExst } = item;
@ -596,7 +598,11 @@ class Tile extends React.PureComponent {
tags={item.tags}
/>
) : (
<Tag label={t("NoTag")} onClick={selectTag} />
<Tag
isDefault
label={t(RoomsTypeTranslations[item.roomType])}
onClick={() => selectType(item.roomType)}
/>
)}
</div>
</>

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

@ -370,13 +370,13 @@ const SectionFilterContent = ({
});
}
if (roomsFilter.withoutTags) {
filterValues.push({
key: [t("NoTag")],
group: FilterGroups.roomFilterTags,
isMultiSelect: true,
});
}
// if (roomsFilter.withoutTags) {
// filterValues.push({
// key: [t("NoTag")],
// group: FilterGroups.roomFilterTags,
// isMultiSelect: true,
// });
// }
if (roomsFilter?.tags?.length > 0) {
filterValues.push({
@ -654,12 +654,12 @@ const SectionFilterContent = ({
isMultiSelect: true,
}));
tagsOptions.push({
key: t("NoTag"),
group: FilterGroups.roomFilterTags,
label: t("NoTag"),
isMultiSelect: true,
});
// tagsOptions.push({
// key: t("NoTag"),
// group: FilterGroups.roomFilterTags,
// label: t("NoTag"),
// isMultiSelect: true,
// });
filterOptions.push({
key: FilterGroups.roomFilterTags,

View File

@ -72,7 +72,6 @@ class SectionHeaderContent extends React.Component {
onCreateRoom = () => {
const event = new Event(Events.ROOM_CREATE);
window.dispatchEvent(event);
};
@ -567,6 +566,7 @@ export default inject(
setEmptyTrashDialogVisible,
setSelectFileDialogVisible,
setIsFolderActions,
setCreateRoomDialogVisible,
} = dialogsStore;
const {
@ -636,6 +636,7 @@ export default inject(
backToParentFolder,
getCheckboxItemLabel,
setSelectFileDialogVisible,
setCreateRoomDialogVisible,
isRecycleBinFolder,
setEmptyTrashDialogVisible,

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>
</>
);
@ -601,6 +605,7 @@ export default inject(
mediaViewerDataStore,
settingsStore,
filesActionsStore,
oformsStore,
}) => {
const {
secondaryProgressDataStore,
@ -622,7 +627,6 @@ export default inject(
isLoading,
viewAs,
getFileInfo,
gallerySelected,
setIsUpdatingRowItem,
folders,
@ -634,8 +638,11 @@ export default inject(
createRoom,
refreshFiles,
setViewAs,
withPaging,
} = filesStore;
const { gallerySelected } = oformsStore;
const {
isRecycleBinFolder,
isPrivacyFolder,
@ -770,6 +777,7 @@ export default inject(
createRoom,
refreshFiles,
setViewAs,
withPaging,
};
}
)(withRouter(observer(Home)));

View File

@ -11,7 +11,7 @@ import ThirdPartyModule from "./sub-components/ThirdPartyModule";
import RoomsModule from "./sub-components/RoomsModule";
import ThirdPartyStorageModule from "./sub-components/ThirdPartyStorageModule";
import { StyledModules, StyledManualBackup } from "./../StyledBackup";
import { getFromLocalStorage } from "../../../../utils";
import { getFromLocalStorage, saveToLocalStorage } from "../../../../utils";
//import { getThirdPartyCommonFolderTree } from "@docspace/common/api/files";
import DataBackupLoader from "@docspace/common/components/Loaders/DataBackupLoader";
import {

View File

@ -22,12 +22,21 @@ class RoomsModule extends React.Component {
selectedFolder: selectedFolder,
isPanelVisible: false,
};
this._isMount = false;
}
componentDidMount() {
this._isMount = true;
}
componentWillUnmount() {
this._isMount = false;
}
onSelectFolder = (folderId) => {
this.setState({
selectedFolder: folderId,
});
this._isMount &&
this.setState({
selectedFolder: folderId,
});
};
onClickInput = () => {

View File

@ -26,6 +26,15 @@ class ThirdPartyModule extends React.Component {
isError: false,
isLoading: false,
};
this._isMount = false;
}
componentDidMount() {
this._isMount = true;
}
componentWillUnmount() {
this._isMount = false;
}
onSetLoadingData = (isLoading) => {
@ -37,9 +46,10 @@ class ThirdPartyModule extends React.Component {
};
onSelectFolder = (folderId) => {
this.setState({
selectedFolder: folderId,
});
this._isMount &&
this.setState({
selectedFolder: folderId,
});
};
onClickInput = () => {

View File

@ -342,8 +342,10 @@ class ContextOptionsStore {
setIsVisible(true);
};
onClickEditRoom = () => {
console.log("edit room");
onClickEditRoom = (item) => {
const event = new Event(Events.ROOM_EDIT);
event.item = item;
window.dispatchEvent(event);
};
onClickInviteUsers = () => {

View File

@ -27,6 +27,7 @@ class DialogsStore {
selectFileDialogVisible = false;
convertPasswordDialogVisible = false;
isFolderActions = false;
roomCreation = false;
removeItem = null;
connectItem = null;
@ -39,6 +40,7 @@ class DialogsStore {
unsubscribe = null;
convertItem = null;
formCreationInfo = null;
saveThirdpartyResponse = null;
constructor(
authStore,
@ -78,10 +80,18 @@ class DialogsStore {
this.copyPanelVisible = copyPanelVisible;
};
setRoomCreation = (roomCreation) => {
this.roomCreation = roomCreation;
};
setSaveThirdpartyResponse = (saveThirdpartyResponse) => {
this.saveThirdpartyResponse = saveThirdpartyResponse;
};
setConnectDialogVisible = (connectDialogVisible) => {
if (!connectDialogVisible) this.setConnectItem(null);
this.connectDialogVisible = connectDialogVisible;
if (!this.connectDialogVisible) this.setRoomCreation(false);
};
setRemoveItem = (removeItem) => {

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();
});
}
};
@ -915,6 +928,19 @@ class FilesActionStore {
fetchRooms(id, newFilter).finally(() => setIsLoading(false));
};
selectType = (type) => {
const { roomsFilter, fetchRooms, setIsLoading } = this.filesStore;
const { id } = this.selectedFolderStore;
const newFilter = roomsFilter.clone();
const tags = newFilter.tags ? [...newFilter.tags] : [];
newFilter.tags = [...tags];
newFilter.type = type;
setIsLoading(true);
fetchRooms(id, newFilter).finally(() => setIsLoading(false));
};
selectRowAction = (checked, file) => {
const {
selected,

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";
@ -83,8 +84,6 @@ class FilesStore {
headerBorder = false;
enabledHotkeys = true;
oformFiles = null;
gallerySelected = null;
createdItem = null;
scrollToItem = null;
@ -93,6 +92,8 @@ class FilesStore {
pageItemsLength = null;
isHidePagination = false;
trashIsEmpty = false;
filesIsLoading = false;
withPaging = false;
constructor(
authStore,
@ -132,7 +133,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 +182,10 @@ class FilesStore {
})
);
const newFilter = this.filter.clone();
newFilter.total -= 1;
this.setFilter(newFilter);
// Hide pagination when deleting files
runInAction(() => {
this.isHidePagination = true;
@ -383,28 +388,11 @@ class FilesStore {
}
}
requests.push(getFilesSettings());
requests.push(this.getOforms());
requests.push(this.getIsEmptyTrash());
return Promise.all(requests).then(() => (this.isInit = true));
};
getOforms = async () => {
const oformData = await this.authStore.getOforms();
runInAction(() => {
this.oformFiles = oformData?.data?.data ? oformData.data.data : [];
});
};
get hasGalleryFiles() {
return this.oformFiles && !!this.oformFiles.length;
}
setGallerySelected = (gallerySelected) => {
this.gallerySelected = gallerySelected;
};
setFirstLoad = (firstLoad) => {
this.firstLoad = firstLoad;
};
@ -524,9 +512,7 @@ class FilesStore {
};
setHotkeyCaret = (hotkeyCaret) => {
if (hotkeyCaret) {
this.hotkeyCaret = hotkeyCaret;
} else if (this.hotkeyCaret) {
if (hotkeyCaret || this.hotkeyCaret) {
this.hotkeyCaret = hotkeyCaret;
}
};
@ -570,6 +556,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 +575,7 @@ class FilesStore {
};
setFilter = (filter) => {
if (!this.withPaging) filter.pageCount = 100;
this.filter = filter;
};
@ -659,9 +648,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 +667,11 @@ class FilesStore {
filterData.sortOrder = splitFilter[2];
}
if (!this.withPaging) {
filterData.page = 0;
filterData.pageCount = 100;
}
setSelectedNode([folderId + ""]);
//TODO: fix @my
@ -841,6 +835,11 @@ class FilesStore {
filterData.sortOrder = splitFilter[2];
}
if (!this.withPaging) {
filterData.page = 0;
filterData.pageCount = 100;
}
if (folderId) setSelectedNode([folderId + ""]);
const searchArea = folderId
@ -1612,8 +1611,60 @@ class FilesStore {
return api.files.createFolder(parentFolderId, title);
}
createRoom(title, type) {
return api.rooms.createRoom({ title, roomType: type });
createRoom(roomParams) {
return api.rooms.createRoom(roomParams);
}
createRoomInThirdpary(thirpartyFolderId, roomParams) {
console.log(thirpartyFolderId, roomParams);
return api.rooms.createRoomInThirdpary(thirpartyFolderId, roomParams);
}
editRoom(id, roomParams) {
return api.rooms.editRoom(id, roomParams);
}
addTagsToRoom(id, tagArray) {
return api.rooms.addTagsToRoom(id, tagArray);
}
removeTagsFromRoom(id, tagArray) {
return api.rooms.removeTagsFromRoom(id, tagArray);
}
calculateRoomLogoParams(img, x, y, zoom) {
let imgWidth, imgHeight, dimensions;
if (img.width > img.height) {
imgWidth = Math.min(1280, img.width);
imgHeight = Math.round(img.height / (img.width / imgWidth));
dimensions = Math.round(imgHeight / zoom);
} else {
imgHeight = Math.min(1280, img.height);
imgWidth = Math.round(img.width / (img.height / imgHeight));
dimensions = Math.round(imgWidth / zoom);
}
const croppedX = Math.round(x * imgWidth - dimensions / 2);
const croppedY = Math.round(y * imgHeight - dimensions / 2);
return {
x: croppedX,
y: croppedY,
width: dimensions,
height: dimensions,
};
}
uploadRoomLogo(formData) {
return api.rooms.uploadRoomLogo(formData);
}
addLogoToRoom(id, icon) {
return api.rooms.addLogoToRoom(id, icon);
}
removeLogoFromRoom(id) {
return api.rooms.removeLogoFromRoom(id);
}
setFile = (file) => {
@ -1655,6 +1706,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)
@ -1856,6 +1964,7 @@ class FilesStore {
folderId,
foldersCount,
id,
logo,
locked,
parentId,
pureContentLength,
@ -1959,6 +2068,7 @@ class FilesStore {
icon,
id,
isFolder,
logo,
locked,
new: item.new,
parentId,
@ -2497,6 +2607,76 @@ class FilesStore {
setTrashIsEmpty = (isEmpty) => {
this.trashIsEmpty = isEmpty;
};
get roomsFilterTotal() {
return this.roomsFilter.total;
}
get filterTotal() {
return this.filter.total;
}
get hasMoreFiles() {
const { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
// const filterTotal = isRoom ? this.roomsFilterTotal : this.filterTotal;
const filterTotal = isRooms ? this.roomsFilter.total : this.filter.total;
console.log("hasMoreFiles isRooms", isRooms);
console.log("hasMoreFiles filesList", this.filesList.length);
console.log("hasMoreFiles this.filterTotal", this.filterTotal);
console.log("hasMoreFiles this.roomsFilterTotal", this.roomsFilterTotal);
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 { isRoomsFolder, isArchiveFolder } = this.treeFoldersStore;
const isRooms = isRoomsFolder || isArchiveFolder;
this.setFilesIsLoading(true);
// console.log("fetchMoreFiles");
const newFilter = isRooms ? this.roomsFilter.clone() : this.filter.clone();
newFilter.page += 1;
if (isRooms) this.setRoomsFilter(newFilter);
else this.setFilter(newFilter);
const newFiles = isRooms
? 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

@ -0,0 +1,90 @@
import { makeAutoObservable, runInAction } from "mobx";
import api from "@docspace/common/api";
const { OformsFilter } = api;
class OformsStore {
authStore;
oformFiles = null;
oformsFilter = OformsFilter.getDefault();
gallerySelected = null;
oformsIsLoading = false;
constructor(authStore) {
makeAutoObservable(this);
this.authStore = authStore;
}
getOforms = async (filter = OformsFilter.getDefault()) => {
const oformData = await this.authStore.getOforms(filter);
const oformsFilter = oformData?.data?.meta?.pagination;
const newOformsFilter = this.oformsFilter.clone();
if (oformsFilter) {
newOformsFilter.page = oformsFilter.page;
newOformsFilter.total = oformsFilter.total;
}
runInAction(() => {
this.setOformsFilter(newOformsFilter);
this.setOformFiles(oformData?.data?.data ?? []);
});
};
setOformFiles = (oformFiles) => {
this.oformFiles = oformFiles;
};
setOformsFilter = (oformsFilter) => {
this.oformsFilter = oformsFilter;
};
setGallerySelected = (gallerySelected) => {
this.gallerySelected = gallerySelected;
};
setOformsIsLoading = (oformsIsLoading) => {
this.oformsIsLoading = oformsIsLoading;
};
loadMoreForms = async () => {
if (!this.hasMoreForms || this.oformsIsLoading) return;
// console.log("loadMoreForms");
this.setOformsIsLoading(true);
const newOformsFilter = this.oformsFilter.clone();
newOformsFilter.page += 1;
this.setOformsFilter(newOformsFilter);
const oformData = await this.authStore.getOforms(newOformsFilter);
const newForms = oformData?.data?.data ?? [];
runInAction(() => {
this.setOformFiles([...this.oformFiles, ...newForms]);
this.setOformsIsLoading(false);
});
};
get hasGalleryFiles() {
return this.oformFiles && !!this.oformFiles.length;
}
// get oformFilesLength() {
// return this.oformFiles.length;
// }
get oformsFilterTotal() {
return this.oformsFilter.total;
}
get hasMoreForms() {
return this.oformFiles.length < this.oformsFilterTotal;
}
}
export default OformsStore;

View File

@ -0,0 +1,353 @@
import { makeAutoObservable, runInAction } from "mobx";
import api from "@appserver/common/api";
import { AppServerConfig, RoomsType } from "@appserver/common/constants";
import toastr from "studio/toastr";
import history from "@appserver/common/history";
import { combineUrl } from "@appserver/common/utils";
import config from "../../package.json";
const { RoomsFilter } = api;
class RoomsStore {
authStore;
settingsStore;
userStore;
filesStore;
selectedFolderStore;
treeFoldersStore;
filesSettingsStore;
rooms = [];
tags = [];
selection = [];
bufferSelection = [];
filter = RoomsFilter.getDefault();
constructor(
authStore,
settingsStore,
userStore,
filesStore,
selectedFolderStore,
treeFoldersStore,
filesSettingsStore
) {
makeAutoObservable(this);
this.authStore = authStore;
this.settingsStore = settingsStore;
this.userStore = userStore;
this.filesStore = filesStore;
this.selectedFolderStore = selectedFolderStore;
this.treeFoldersStore = treeFoldersStore;
this.filesSettingsStore = filesSettingsStore;
}
setRoom = (room) => {
const idx = this.rooms.findIndex((item) => item.id === room.id);
this.rooms[idx] = room;
};
setRooms = (rooms) => {
this.rooms = rooms;
};
setTags = (tags) => {
this.tags = tags;
};
setRoomsFilter = (filter) => {
this.filter = filter;
};
setFilterUrl = (filter) => {
this.filter = filter;
const urlFilter = filter.toUrlParams();
history.push(
combineUrl(
AppServerConfig.proxyURL,
config.homepage,
`/rooms?${urlFilter}`
)
);
};
setSelection = (items) => {
this.selection = items;
};
setBufferSelection = (item) => {
this.bufferSelection = item;
};
setHeaderVisible = (isHeaderVisible) => {
this.isHeaderVisible = isHeaderVisible;
};
sortRooms = (sortBy, sortOrder) => {
const newFilter = this.filter.clone();
newFilter.page = 0;
newFilter.sortBy = sortBy;
newFilter.sortOrder = sortOrder;
return this.fetchRooms(newFilter.searchArea, newFilter);
};
filterRooms = (types, subjectId, tags) => {
const newFilter = this.filter.clone();
newFilter.page = 0;
newFilter.types = types ? types : null;
newFilter.subjectId = subjectId ? subjectId : null;
newFilter.tags = tags ? tags : null;
return this.fetchRooms(newFilter.searchArea, newFilter);
};
searchRooms = (filterValue) => {
const newFilter = this.filter.clone();
newFilter.page = 0;
newFilter.filterValue = filterValue;
return this.fetchRooms(newFilter.searchArea, newFilter);
};
fetchRooms = (searchArea, filter) => {
const { setSelectedNode } = this.treeFoldersStore;
const filterData = !!filter ? filter.clone() : RoomsFilter.getDefault();
if (searchArea && filterData.searchArea !== searchArea) {
filterData.searchArea = searchArea;
}
const request = () =>
api.rooms
.getRooms(filterData)
.then(async (data) => {
const folderId = data.current.id;
setSelectedNode([folderId + ""]);
filterData.total = data.total;
if (data.total > 0) {
const lastPage = filterData.getLastPage();
if (filterData.page > lastPage) {
filterData.page = lastPage;
return this.fetchFiles(searchArea, filterData);
}
}
this.setFilterUrl(filterData);
runInAction(() => {
this.setRooms(data.folders);
this.fetchTags();
});
this.selectedFolderStore.setSelectedFolder({
folders: data.folders,
...data.current,
pathParts: data.pathParts,
navigationPath: [],
...{ new: data.new },
});
})
.catch((err) => {
toastr.error(err);
});
return request();
};
fetchRoomInfo = (id) => api.rooms.getRoom(id);
selectRoom = (checked, item) => {
this.setBufferSelection(null);
if (checked) {
this.selection.push(item);
} else {
const idx = this.selection.findIndex((room) => room.id === item.id);
this.selection.splice(idx, 1);
}
};
setSelected = (selected) => {
if (selected === "none") {
this.setBufferSelection(null);
}
const newSelection = [];
this.rooms.forEach((room) => {
const checked = this.getRoomChecked(room, selected);
if (checked) newSelection.push(room);
});
this.selection = newSelection;
};
openContextMenu = (item) => {
if (this.selection.length > 0) return;
this.setBufferSelection(item);
};
closeContextMenu = () => {
this.bufferSelection = null;
};
createRoom = (data) => {
const isInThirdparty = !!data.storageLocation;
return isInThirdparty
? api.rooms.createRoomInThirdpary(data.storageLocation.id, data)
: api.rooms.createRoom(data);
};
deleteRoom = (room) => {
const selectedRoom = room
? room
: this.selection.length > 0
? this.selection[0]
: this.bufferSelection;
return api.rooms.deleteRoom(selectedRoom.id);
};
pinRoom = (id) => {
return api.rooms.pinRoom(id);
};
unpinRoom = (id) => {
return api.rooms.unpinRoom(id);
};
moveToArchive = (room) => {
const selectedRoom = room
? room
: this.selection.length > 0
? this.selection[0]
: this.bufferSelection;
return api.rooms.archiveRoom(selectedRoom.id);
};
moveFromArchive = (room) => {
const selectedRoom = room
? room
: this.selection.length > 0
? this.selection[0]
: this.bufferSelection;
return api.rooms.unarchiveRoom(selectedRoom.id);
};
fetchTags = () => {
const request = () =>
api.rooms.getTags().then((res) => {
this.setTags(res);
return res;
});
return request();
};
getRoomCheckboxTitle = (t, key) => {
switch (key) {
case "all":
return t("All");
case RoomsType.FillingFormsRoom:
return "Filling form rooms";
case RoomsType.CustomRoom:
return "Custom rooms";
case RoomsType.EditingRoom:
return "Editing rooms";
case RoomsType.ReviewRoom:
return "Review rooms";
case RoomsType.ReadOnlyRoom:
return "Read-only rooms";
default:
return "";
}
};
getRoomChecked = (room, selected) => {
const type = room.roomType;
switch (selected) {
case "all":
return true;
case RoomsType.FillingFormsRoom:
return type === RoomsType.FillingFormsRoom;
case RoomsType.CustomRoom:
return type === RoomsType.CustomRoom;
case RoomsType.EditingRoom:
return type === RoomsType.EditingRoom;
case RoomsType.ReviewRoom:
return type === RoomsType.ReviewRoom;
case RoomsType.ReadOnlyRoom:
return type === RoomsType.ReadOnlyRoom;
default:
return false;
}
};
get isHeaderVisible() {
return this.selection.length > 0;
}
get isHeaderIndeterminate() {
return this.isHeaderVisible && this.selection.length
? this.selection.length < this.rooms.length
: false;
}
get isHeaderChecked() {
return this.isHeaderVisible && this.selection.length === this.rooms.length;
}
get checkboxMenuItems() {
let cbMenu = ["all"];
for (const item of this.rooms) {
switch (item.roomType) {
case RoomsType.FillingFormsRoom:
cbMenu.push(RoomsType.FillingFormsRoom);
break;
case RoomsType.CustomRoom:
cbMenu.push(RoomsType.CustomRoom);
break;
case RoomsType.EditingRoom:
cbMenu.push(RoomsType.EditingRoom);
break;
case RoomsType.ReviewRoom:
cbMenu.push(RoomsType.ReviewRoom);
break;
case RoomsType.ReadOnlyRoom:
cbMenu.push(RoomsType.ReadOnlyRoom);
break;
}
}
cbMenu = cbMenu.filter((item, index) => cbMenu.indexOf(item) === index);
return cbMenu;
}
}
export default RoomsStore;

View File

@ -13,6 +13,10 @@ class TagsStore {
this.tags = tags;
};
createTag = (name) => {
return api.rooms.createTag(name);
};
fetchTags = () => {
const request = () =>
api.rooms.getTags().then((res) => {

View File

@ -32,7 +32,8 @@ class ThirdPartyStore {
isCorporate,
customerTitle,
providerKey,
providerId
providerId,
isRoomsStorage
) => {
return api.files.saveThirdParty(
url,
@ -42,7 +43,8 @@ class ThirdPartyStore {
isCorporate,
customerTitle,
providerKey,
providerId
providerId,
isRoomsStorage
);
};

Some files were not shown because too many files have changed in this diff Show More