From 4dfa59e07cd2028b62a0bfcc9b2daa0e62895dea Mon Sep 17 00:00:00 2001 From: Romaric Mourgues Date: Thu, 9 Mar 2023 13:54:13 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=81=20Canary=20drive=20fix=201=20(#277?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix plugins, public write, apps crash * Asynchronous menus for drive * Load page before to change it * Remove id restriction and allow delete only to manage * Several fixes around versions --- .../channels/services/member/service.ts | 2 +- .../channels/web/controllers/channel.ts | 2 +- .../src/services/documents/services/index.ts | 15 +- .../node/src/services/documents/utils.ts | 4 +- .../documents/web/controllers/documents.ts | 2 - .../node/src/services/files/services/index.ts | 40 ++- .../messages/web/controllers/threads.ts | 2 +- .../messages/web/controllers/views.ts | 2 +- .../pending-file-row.tsx | 4 +- .../pending-files-list.tsx | 2 +- .../app/components/menus/menus-manager.jsx | 7 +- .../drive/hooks/use-drive-actions.tsx | 5 +- .../features/drive/hooks/use-drive-item.tsx | 5 +- .../files/services/file-upload-service.ts | 3 +- .../services/message-editor-service.ts | 2 +- .../app/views/applications/drive/browser.tsx | 206 ++++--------- .../views/applications/drive/context-menu.tsx | 289 ++++++++++++++++++ .../drive/{item-row => documents}/common.tsx | 5 +- .../{item-row => documents}/document-row.tsx | 102 +------ .../drive/documents/folder-row.tsx | 70 +++++ .../views/applications/drive/header-path.tsx | 5 +- .../drive/item-row/folder-row.tsx | 154 ---------- .../modals/update-access/internal-access.tsx | 11 +- .../update-access/public-link-access.tsx | 8 +- .../drive/modals/versions/index.tsx | 12 +- .../parts/PossiblyPendingAttachment.tsx | 6 +- .../Pages/Applications/ApplicationsTable.tsx | 2 +- .../Applications/CompanyApplicationsTable.tsx | 4 +- 28 files changed, 510 insertions(+), 461 deletions(-) create mode 100644 twake/frontend/src/app/views/applications/drive/context-menu.tsx rename twake/frontend/src/app/views/applications/drive/{item-row => documents}/common.tsx (90%) rename twake/frontend/src/app/views/applications/drive/{item-row => documents}/document-row.tsx (50%) create mode 100644 twake/frontend/src/app/views/applications/drive/documents/folder-row.tsx delete mode 100644 twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx diff --git a/twake/backend/node/src/services/channels/services/member/service.ts b/twake/backend/node/src/services/channels/services/member/service.ts index 5d29d1942e..e344771d02 100644 --- a/twake/backend/node/src/services/channels/services/member/service.ts +++ b/twake/backend/node/src/services/channels/services/member/service.ts @@ -344,7 +344,7 @@ export class MemberServiceImpl { if (ChannelEntity.isDirectChannel(channel) || ChannelEntity.isPrivateChannel(channel)) { const isMember = await this.getChannelMember(context.user, channel, undefined, context); - if (!isMember && !context.user.server_request) { + if (!isMember && !context.user.application_id && !context.user.server_request) { throw CrudException.badRequest("User does not have enough rights to get channels"); } } diff --git a/twake/backend/node/src/services/channels/web/controllers/channel.ts b/twake/backend/node/src/services/channels/web/controllers/channel.ts index a65a033807..43091f79e1 100644 --- a/twake/backend/node/src/services/channels/web/controllers/channel.ts +++ b/twake/backend/node/src/services/channels/web/controllers/channel.ts @@ -86,7 +86,7 @@ export class ChannelCrudController context, ); - if (!isMember) { + if (!isMember && !context.user.application_id && !context.user.server_request) { throw CrudException.badRequest("User does not have enough rights to get channel"); } } diff --git a/twake/backend/node/src/services/documents/services/index.ts b/twake/backend/node/src/services/documents/services/index.ts index 1cc4594baf..3a15e9c28f 100644 --- a/twake/backend/node/src/services/documents/services/index.ts +++ b/twake/backend/node/src/services/documents/services/index.ts @@ -200,7 +200,7 @@ export class DocumentsService { ): Promise => { try { const driveItem = getDefaultDriveItem(content, context); - const driveItemVersion = getDefaultDriveItemVersion(version, context); + let driveItemVersion = getDefaultDriveItemVersion(version, context); const hasAccess = await checkAccess( driveItem.parent_id, @@ -445,7 +445,7 @@ export class DocumentsService { } try { - if (!(await checkAccess(item.id, item, "write", this.repository, context))) { + if (!(await checkAccess(item.id, item, "manage", this.repository, context))) { this.logger.error("user does not have access drive item ", id); throw Error("user does not have access to this item"); } @@ -569,6 +569,17 @@ export class DocumentsService { this.notifyWebsocket(item.parent_id, context); + globalResolver.platformServices.messageQueue.publish( + "services:documents:process", + { + data: { + item, + version: driveItemVersion, + context, + }, + }, + ); + return driveItemVersion; } catch (error) { this.logger.error("Failed to create Drive item version", error); diff --git a/twake/backend/node/src/services/documents/utils.ts b/twake/backend/node/src/services/documents/utils.ts index d59d12a591..667902db84 100644 --- a/twake/backend/node/src/services/documents/utils.ts +++ b/twake/backend/node/src/services/documents/utils.ts @@ -350,7 +350,7 @@ export const getAccessLevel = async ( company_id: context.company.id, application_id: context.user.application_id, }) - )?.application?.access //TODO check precise access right + )?.application?.access //TODO check precise access right for applications ) { return "manage"; } @@ -699,7 +699,7 @@ export const getItemName = async ( context: CompanyExecutionContext, ): Promise => { try { - let newName = name; + let newName = name.substring(0, 255); let exists = true; const children = await repository.find( { diff --git a/twake/backend/node/src/services/documents/web/controllers/documents.ts b/twake/backend/node/src/services/documents/web/controllers/documents.ts index c42ef09c0f..c5e4cdfee3 100644 --- a/twake/backend/node/src/services/documents/web/controllers/documents.ts +++ b/twake/backend/node/src/services/documents/web/controllers/documents.ts @@ -122,8 +122,6 @@ export class DocumentsController { const context = getDriveExecutionContext(request); const { id } = request.params; - if (!id) throw new CrudException("Missing id", 400); - return { ...(await globalResolver.services.documents.documents.get(id, context)), websockets: request.currentUser?.id diff --git a/twake/backend/node/src/services/files/services/index.ts b/twake/backend/node/src/services/files/services/index.ts index ffd2e406c7..ba5732861e 100644 --- a/twake/backend/node/src/services/files/services/index.ts +++ b/twake/backend/node/src/services/files/services/index.ts @@ -152,20 +152,10 @@ export class FileServiceImpl { ); if (options.waitForThumbnail) { - for (let i = 1; i < 100; i++) { - entity = await this.repository.findOne( - { - company_id: context.company.id, - id: entity.id, - }, - {}, - context, - ); - if (entity.metadata.thumbnails_status === "done") { - break; - } - await new Promise(r => setTimeout(r, i * 200)); - } + entity = await gr.services.files.getFile({ + id: entity.id, + company_id: context.company.id, + }); } } catch (err) { entity.metadata.thumbnails_status = "error"; @@ -247,8 +237,26 @@ export class FileServiceImpl { return this.getFile({ id, company_id: context.company.id }, context); } - async getFile(pk: Pick, context?: ExecutionContext): Promise { - return this.repository.findOne(pk, {}, context); + async getFile( + pk: Pick, + context?: ExecutionContext, + options?: { + waitForThumbnail?: boolean; + }, + ): Promise { + let entity = await this.repository.findOne(pk, {}, context); + + if (options?.waitForThumbnail) { + for (let i = 1; i < 100; i++) { + if (entity.metadata.thumbnails_status === "done") { + break; + } + await new Promise(r => setTimeout(r, i * 200)); + entity = await this.repository.findOne(pk, {}, context); + } + } + + return entity; } getThumbnailRoute(file: File, index: string) { diff --git a/twake/backend/node/src/services/messages/web/controllers/threads.ts b/twake/backend/node/src/services/messages/web/controllers/threads.ts index 495179b1d1..4d44110a51 100644 --- a/twake/backend/node/src/services/messages/web/controllers/threads.ts +++ b/twake/backend/node/src/services/messages/web/controllers/threads.ts @@ -55,7 +55,7 @@ export class ThreadsController id: participant.id, }, ); - if (!isMember) { + if (!isMember && !context.user.application_id && !context.user.server_request) { throw CrudException.notFound("Channel not found"); } } diff --git a/twake/backend/node/src/services/messages/web/controllers/views.ts b/twake/backend/node/src/services/messages/web/controllers/views.ts index a633c5ab5f..dbb530eec0 100644 --- a/twake/backend/node/src/services/messages/web/controllers/views.ts +++ b/twake/backend/node/src/services/messages/web/controllers/views.ts @@ -50,7 +50,7 @@ export class ViewsController { id: request.params.channel_id, }, ); - if (!isMember) { + if (!isMember && !context.user.application_id && !context.user.server_request) { throw CrudException.notFound("Channel not found"); } diff --git a/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-file-row.tsx b/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-file-row.tsx index 8c747e2411..ba47ec819f 100644 --- a/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-file-row.tsx +++ b/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-file-row.tsx @@ -69,7 +69,7 @@ export default ({ pendingFileState, pendingFile }: PropsType) => { }} > - {pendingFile.originalFile.name ? ( + {pendingFile?.originalFile.name ? ( { verticalAlign: 'middle', }} > - {capitalize(pendingFile.originalFile.name)} + {capitalize(pendingFile?.originalFile.name)} {isPendingFileStatusPause(pendingFile.status) && ( diff --git a/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-files-list.tsx b/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-files-list.tsx index 875c5d1e7a..321d9dc031 100644 --- a/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-files-list.tsx +++ b/twake/frontend/src/app/components/file-uploads/pending-file-components/pending-files-list.tsx @@ -28,7 +28,7 @@ export default ({ pendingFilesState, visible }: PropsType) => { const uploadingFiles = pendingFiles.filter(f => f?.resumable && f.resumable.isUploading()); const remainingSizeTotal = uploadingFiles - .map(f => (1 - f.progress) * f.originalFile.size) + .map(f => (1 - f.progress) * (f?.originalFile?.size || 0)) .reduce((accumulator: number, nextValue: number) => accumulator + nextValue, 0); const speed = diff --git a/twake/frontend/src/app/components/menus/menus-manager.jsx b/twake/frontend/src/app/components/menus/menus-manager.jsx index 52f2c1ddd9..e40fd538e0 100755 --- a/twake/frontend/src/app/components/menus/menus-manager.jsx +++ b/twake/frontend/src/app/components/menus/menus-manager.jsx @@ -56,7 +56,12 @@ class MenusManager extends Observable { this.notify(); } - openMenu(menu, domRect, positionType, options) { + async openMenu(menu, domRect, positionType, options) { + + if(typeof menu === 'function') { + menu = await menu(); + } + this.menus = []; if (this.closeMenuTimeout) { diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-actions.tsx b/twake/frontend/src/app/features/drive/hooks/use-drive-actions.tsx index db26dcaf3c..0f1d0f4410 100644 --- a/twake/frontend/src/app/features/drive/hooks/use-drive-actions.tsx +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-actions.tsx @@ -24,7 +24,10 @@ export const useDriveActions = () => { set(DriveItemAtom(parentId), details); for (const child of details.children) { const currentValue = snapshot.getLoadable(DriveItemAtom(child.id)).contents; - set(DriveItemAtom(child.id), { ...currentValue, item: child }); + if (!currentValue) { + //only update if not already in cache to avoid concurrent updates + set(DriveItemAtom(child.id), { item: child }); + } } return details; } catch (e) { diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx b/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx index ce9138bc88..f63eb7ff37 100644 --- a/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx @@ -76,6 +76,7 @@ export const useDriveItem = (id: string) => { inTrash, loading: loading, children: children || [], + details: item, path: item?.path, item: item?.item, access: item?.access, @@ -89,8 +90,8 @@ export const useDriveItem = (id: string) => { }; }; -export const usePublicLink = (item?: DriveItem): string => { - const translator = useRef(short()).current; +const translator = short(); +export const getPublicLink = (item?: DriveItem): string => { let publicLink = `${document.location.protocol}//${document.location.host}`; try { publicLink += diff --git a/twake/frontend/src/app/features/files/services/file-upload-service.ts b/twake/frontend/src/app/features/files/services/file-upload-service.ts index 79ba3a96d5..6d3e4425fc 100644 --- a/twake/frontend/src/app/features/files/services/file-upload-service.ts +++ b/twake/frontend/src/app/features/files/services/file-upload-service.ts @@ -109,7 +109,8 @@ class FileUploadService { pendingFile.resumable.on('fileAdded', () => pendingFile.resumable.upload()); pendingFile.resumable.on('fileProgress', (f: any) => { - const bytesDelta = (f.progress() - pendingFile.progress) * pendingFile.originalFile.size; + const bytesDelta = + (f.progress() - pendingFile.progress) * (pendingFile?.originalFile.size || 0); const timeDelta = new Date().getTime() - pendingFile.lastProgress; // To avoid jumping time ? diff --git a/twake/frontend/src/app/features/messages/services/message-editor-service.ts b/twake/frontend/src/app/features/messages/services/message-editor-service.ts index 8531bdb55f..df0592d1c0 100644 --- a/twake/frontend/src/app/features/messages/services/message-editor-service.ts +++ b/twake/frontend/src/app/features/messages/services/message-editor-service.ts @@ -148,7 +148,7 @@ export class MessageEditorService extends Observable { if (this.shouldLimitAttachements(threadId)) { return Toaster.error( Languages.t('services.apps.messages.message_editor_service.upload_error_toaster', [ - file.originalFile.name, + file?.originalFile?.name || 'unknown file', this.ATTACHEMENTS_LIMIT, ]), 4, diff --git a/twake/frontend/src/app/views/applications/drive/browser.tsx b/twake/frontend/src/app/views/applications/drive/browser.tsx index 90b2aa9006..57810ede9e 100644 --- a/twake/frontend/src/app/views/applications/drive/browser.tsx +++ b/twake/frontend/src/app/views/applications/drive/browser.tsx @@ -1,31 +1,28 @@ import { ChevronDownIcon, PlusIcon } from '@heroicons/react/outline'; import { Button } from 'app/atoms/button/button'; -import { Base, BaseSmall, Info, Subtitle, Title } from 'app/atoms/text'; +import { Base, BaseSmall, Subtitle, Title } from 'app/atoms/text'; import Menu from 'app/components/menus/menu'; import { getFilesTree } from 'app/components/uploads/file-tree-utils'; import UploadZone from 'app/components/uploads/upload-zone'; import { setTwakeTabToken } from 'app/features/drive/api-client/api-client'; -import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; -import { useDriveItem, usePublicLink } from 'app/features/drive/hooks/use-drive-item'; +import { useDriveItem } from 'app/features/drive/hooks/use-drive-item'; import { useDriveRealtime } from 'app/features/drive/hooks/use-drive-realtime'; import { useDriveUpload } from 'app/features/drive/hooks/use-drive-upload'; import { DriveItemSelectedList } from 'app/features/drive/state/store'; import { formatBytes } from 'app/features/drive/utils'; -import { ToasterService } from 'app/features/global/services/toaster-service'; -import { copyToClipboard } from 'app/features/global/utils/CopyClipboard'; import useRouterCompany from 'app/features/router/hooks/use-router-company'; import _ from 'lodash'; -import { memo, Suspense, useCallback, useEffect, useRef } from 'react'; +import { memo, Suspense, useCallback, useEffect, useRef, useState } from 'react'; import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil'; import { DrivePreview } from '../viewer/drive-preview'; +import { useOnBuildContextMenu } from './context-menu'; +import { DocumentRow } from './documents/document-row'; +import { FolderRow } from './documents/folder-row'; import HeaderPath from './header-path'; -import { DocumentRow } from './item-row/document-row'; -import { FolderRow } from './item-row/folder-row'; -import { ConfirmDeleteModal, ConfirmDeleteModalAtom } from './modals/confirm-delete'; -import { ConfirmTrashModal, ConfirmTrashModalAtom } from './modals/confirm-trash'; +import { ConfirmDeleteModal } from './modals/confirm-delete'; +import { ConfirmTrashModal } from './modals/confirm-trash'; import { CreateModal, CreateModalAtom } from './modals/create'; import { PropertiesModal } from './modals/properties'; -import { SelectorModalAtom } from './modals/selector'; import { AccessModal } from './modals/update-access'; import { VersionsModal } from './modals/versions'; @@ -47,26 +44,52 @@ export default memo( const companyId = useRouterCompany(); setTwakeTabToken(twakeTabContextToken || null); - const [parentId, setParentId] = useRecoilState( + const [parentId, _setParentId] = useRecoilState( DriveCurrentFolderAtom(initialParentId || 'root'), ); - const { download, downloadZip, update } = useDriveActions(); - const { access, item, inTrash, refresh, children, loading, path } = useDriveItem(parentId); - const publicLink = usePublicLink(item); + const [loadingParentChange, setLoadingParentChange] = useState(false); + const { + details, + access, + item, + inTrash, + refresh, + children, + loading: loadingParent, + path, + } = useDriveItem(parentId); const { item: trash } = useDriveItem('trash'); const { uploadTree, uploadFromUrl } = useDriveUpload(); useDriveRealtime(parentId); + const loading = loadingParent || loadingParentChange; + const uploadZone = 'drive_' + companyId; const uploadZoneRef = useRef(null); const setCreationModalState = useSetRecoilState(CreateModalAtom); - const setSelectorModalState = useSetRecoilState(SelectorModalAtom); - const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom); - const setConfirmTrashModalState = useSetRecoilState(ConfirmTrashModalAtom); const [checked, setChecked] = useRecoilState(DriveItemSelectedList); + const setParentId = useCallback( + async (id: string) => { + setLoadingParentChange(true); + try { + await refresh(id); + _setParentId(id); + } catch (e) { + console.error(e); + } + setLoadingParentChange(false); + }, + [_setParentId], + ); + + //In case we are kicked out of the current folder, we need to reset the parent id + useEffect(() => { + if (!loading && !path?.length) setParentId('root'); + }, [path, loading, setParentId]); + useEffect(() => { setChecked({}); refresh(parentId); @@ -91,6 +114,9 @@ export default memo( ) .filter(i => !i.is_directory) .sort((a, b) => a.name.localeCompare(b.name)); + + const onBuildContextMenu = useOnBuildContextMenu(children, initialParentId); + return ( {document.location.origin.includes('canary') && access !== 'read' && !inPublicSharing && ( @@ -154,123 +180,7 @@ export default memo( {access !== 'read' && ( {formatBytes(item?.size || 0)} used in this folder )} - - setSelectorModalState({ - open: true, - parent_id: inTrash ? 'root' : parentId, - title: 'Move ' + selectedCount + ' items', - mode: 'move', - onSelected: async ids => { - for (const item of children.filter(c => checked[c.id])) { - await update( - { - parent_id: ids[0], - }, - item.id, - item.parent_id, - ); - } - setChecked({}); - }, - }), - }, - { - type: 'menu', - text: 'Download ' + selectedCount + ' items', - onClick: () => - selectedCount === 1 - ? download(Object.keys(checked)[0]) - : downloadZip(Object.keys(checked)), - }, - { type: 'separator', hide: access === 'read' }, - { - type: 'menu', - text: 'Delete ' + selectedCount + ' items', - hide: !inTrash || access !== 'manage', - className: 'error', - onClick: () => { - setConfirmDeleteModalState({ - open: true, - items: children.filter(a => checked[a.id]), - }); - }, - }, - { - type: 'menu', - text: 'Move ' + selectedCount + ' items to trash', - hide: inTrash || access === 'read', - className: 'error', - onClick: async () => - setConfirmTrashModalState({ - open: true, - items: children.filter(a => checked[a.id]), - }), - }, - ] - : inTrash - ? [ - { - type: 'menu', - text: 'Exit trash', - onClick: () => setParentId('root'), - }, - { type: 'separator' }, - { - type: 'menu', - text: 'Empty trash', - className: 'error', - hide: parentId != 'trash' || access !== 'manage', - onClick: () => { - setConfirmDeleteModalState({ - open: true, - items: children, //Fixme: Here it works because this menu is displayed only in the trash root folder - }); - }, - }, - ] - : [ - { - type: 'menu', - text: 'Add document or folder', - hide: inTrash || access === 'read', - onClick: () => openItemModal(), - }, - - { - type: 'menu', - text: 'Download folder', - hide: inTrash, - onClick: () => downloadZip([parentId]), - }, - { - type: 'menu', - text: 'Copy public link', - hide: - !item?.access_info?.public?.level || - item?.access_info?.public?.level === 'none', - onClick: () => { - copyToClipboard(publicLink); - ToasterService.success('Public link copied to clipboard'); - }, - }, - { type: 'separator', hide: inTrash || access === 'read' }, - { - type: 'menu', - text: 'Go to trash', - hide: inTrash || access === 'read', - onClick: () => setParentId('trash'), - }, - ] - } - > + onBuildContextMenu(details)}> {' '} + + + ); +}; diff --git a/twake/frontend/src/app/views/applications/drive/header-path.tsx b/twake/frontend/src/app/views/applications/drive/header-path.tsx index 3ec3d5f497..4f8838df69 100644 --- a/twake/frontend/src/app/views/applications/drive/header-path.tsx +++ b/twake/frontend/src/app/views/applications/drive/header-path.tsx @@ -24,7 +24,6 @@ export default ({ export const PathRender = ({ path, - inTrash, onClick, }: { path: DriveItem[]; @@ -70,7 +69,9 @@ const PathItem = ({ onClick(item?.id || ''); }} > - {item?.name || ''} + + {item?.name || ''} + {item?.access_info?.public?.level && item?.access_info?.public?.level !== 'none' && ( )} diff --git a/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx b/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx deleted file mode 100644 index 512c44590e..0000000000 --- a/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { DotsHorizontalIcon } from '@heroicons/react/outline'; -import { FolderIcon } from '@heroicons/react/solid'; -import { Button } from 'app/atoms/button/button'; -import { Base, BaseSmall } from 'app/atoms/text'; -import Menu from 'app/components/menus/menu'; -import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; -import { usePublicLink } from 'app/features/drive/hooks/use-drive-item'; -import { formatBytes } from 'app/features/drive/utils'; -import { ToasterService } from 'app/features/global/services/toaster-service'; -import { copyToClipboard } from 'app/features/global/utils/CopyClipboard'; -import { useState } from 'react'; -import { useSetRecoilState } from 'recoil'; -import { PublicIcon } from '../components/public-icon'; -import { ConfirmDeleteModalAtom } from '../modals/confirm-delete'; -import { ConfirmTrashModalAtom } from '../modals/confirm-trash'; -import { PropertiesModalAtom } from '../modals/properties'; -import { SelectorModalAtom } from '../modals/selector'; -import { AccessModalAtom } from '../modals/update-access'; -import { CheckableIcon, DriveItemProps } from './common'; - -export const FolderRow = ({ - item, - inTrash, - className, - onCheck, - checked, - onClick, - parentAccess, -}: DriveItemProps) => { - const [hover, setHover] = useState(false); - const { download, update } = useDriveActions(); - const setSelectorModalState = useSetRecoilState(SelectorModalAtom); - const setAccessModalState = useSetRecoilState(AccessModalAtom); - const setPropertiesModalState = useSetRecoilState(PropertiesModalAtom); - const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom); - const setConfirmTrashModalState = useSetRecoilState(ConfirmTrashModalAtom); - const publicLink = usePublicLink(item); - - return ( -
setHover(true)} - onMouseLeave={() => setHover(false)} - onClick={e => { - if (e.shiftKey || e.ctrlKey) onCheck(!checked); - else if (onClick) onClick(); - }} - > -
e.stopPropagation()}> - } - /> -
-
- {item.name} -
-
- {item?.access_info?.public?.level !== 'none' && ( - - )} -
-
- {formatBytes(item.size)} -
-
- download(item.id), - }, - { type: 'separator' }, - { - type: 'menu', - text: 'Modify properties', - hide: parentAccess === 'read', - onClick: () => setPropertiesModalState({ open: true, id: item.id }), - }, - { - type: 'menu', - text: 'Manage access', - hide: parentAccess === 'read', - onClick: () => setAccessModalState({ open: true, id: item.id }), - }, - { - type: 'menu', - text: 'Copy public link', - hide: !item.access_info.public?.level || item.access_info.public?.level === 'none', - onClick: () => { - copyToClipboard(publicLink); - ToasterService.success('Public link copied to clipboard'); - }, - }, - { - type: 'menu', - text: 'Move', - hide: parentAccess === 'read', - onClick: () => - setSelectorModalState({ - open: true, - parent_id: inTrash ? 'root' : item.parent_id, - mode: 'move', - title: `Move '${item.name}'`, - onSelected: async ids => { - await update( - { - parent_id: ids[0], - }, - item.id, - item.parent_id, - ); - }, - }), - }, - { type: 'separator', hide: parentAccess === 'read' }, - { - type: 'menu', - text: 'Move to trash', - className: 'error', - hide: inTrash || parentAccess === 'read', - onClick: () => setConfirmTrashModalState({ open: true, items: [item] }), - }, - { - type: 'menu', - text: 'Delete', - className: 'error', - hide: !inTrash || parentAccess === 'read', - onClick: () => setConfirmDeleteModalState({ open: true, items: [item] }), - }, - ]} - > - -
-
- ); -}; diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx b/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx index fa01fd443f..62c3b2675c 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx +++ b/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx @@ -4,6 +4,7 @@ import { Base, Info } from 'app/atoms/text'; import { useDriveItem } from 'app/features/drive/hooks/use-drive-item'; import { DriveFileAccessLevel } from 'app/features/drive/types'; import AlertManager from 'app/features/global/services/alert-manager-service'; +import { useCurrentUser } from 'app/features/users/hooks/use-current-user'; import { useUser } from 'app/features/users/hooks/use-user'; import currentUserService from 'app/features/users/services/current-user-service'; import { UserType } from 'app/features/users/types/user'; @@ -112,7 +113,9 @@ export const InternalAccessManager = ({ id, disabled }: { id: string; disabled: }, }); }, - () => {}, + () => { + //Do nothing + }, { text: 'You will need to go to Twake chat to give back access to this item.', }, @@ -203,6 +206,7 @@ const UserAccessLevel = ({ }) => { const { item, loading, update } = useDriveItem(id); const user = useUser(userId); + const { user: currentUser } = useCurrentUser(); const level = item?.access_info.entities.filter(a => a.type === 'user' && a.id === userId)?.[0]?.level || 'none'; @@ -217,11 +221,12 @@ const UserAccessLevel = ({ />
- {!!user && currentUserService.getFullName(user)} + {!!user && currentUserService.getFullName(user)}{' '} + {user?.id === currentUser?.id && (you)}
{ diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx b/twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx index dfc3490659..9c5935a389 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx +++ b/twake/frontend/src/app/views/applications/drive/modals/update-access/public-link-access.tsx @@ -1,13 +1,13 @@ import A from 'app/atoms/link'; import { Base, Info } from 'app/atoms/text'; -import { useDriveItem, usePublicLink } from 'app/features/drive/hooks/use-drive-item'; +import { useDriveItem, getPublicLink } from 'app/features/drive/hooks/use-drive-item'; import { ToasterService } from 'app/features/global/services/toaster-service'; import { copyToClipboard } from 'app/features/global/utils/CopyClipboard'; import { AccessLevel } from './common'; export const PublicLinkManager = ({ id, disabled }: { id: string; disabled?: boolean }) => { const { item, loading, update } = useDriveItem(id); - const publicLink = usePublicLink(item); + const publicLink = getPublicLink(item); return ( <> Public link access @@ -36,9 +36,7 @@ export const PublicLinkManager = ({ id, disabled }: { id: string; disabled?: boo
{ diff --git a/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx b/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx index 2391cf2843..29f53518e7 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx +++ b/twake/frontend/src/app/views/applications/drive/modals/versions/index.tsx @@ -1,15 +1,14 @@ import { Button } from 'app/atoms/button/button'; import { Modal, ModalContent } from 'app/atoms/modal'; -import { Base, BaseSmall, Title } from 'app/atoms/text'; +import { Base, BaseSmall } from 'app/atoms/text'; import UploadZone from 'app/components/uploads/upload-zone'; import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; import { useDriveItem } from 'app/features/drive/hooks/use-drive-item'; -import { useDriveUpload } from 'app/features/drive/hooks/use-drive-upload'; import { formatBytes } from 'app/features/drive/utils'; import { formatDate } from 'app/features/global/utils/format-date'; +import _ from 'lodash'; import { useEffect, useRef } from 'react'; import { atom, useRecoilState } from 'recoil'; -import { DocumentRow } from '../../item-row/document-row'; export type VersionsModalType = { open: boolean; @@ -86,13 +85,16 @@ const VersionModalContent = ({ id }: { id: string }) => {
)} - {[...(versions?.length ? versions : [item.last_version_cache])].map((version, index) => ( + {_.orderBy( + [...(versions?.length ? versions : [item.last_version_cache])], + f => -f.date_added, + ).map((version, index) => (
diff --git a/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx b/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx index 8ceb95b22c..066afb5a84 100644 --- a/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx +++ b/twake/frontend/src/app/views/applications/messages/message/parts/PossiblyPendingAttachment.tsx @@ -50,11 +50,11 @@ export default ({ file, onRemove, type, large, xlarge }: PropsType) => { formatedFile = { id: pendingFile?.backendFile?.id || '', company_id: pendingFile?.backendFile?.company_id || '', - name: pendingFile.originalFile.name, - size: pendingFile.originalFile.size, + name: pendingFile?.originalFile.name || '', + size: pendingFile?.originalFile.size || 0, thumbnail: URL.createObjectURL(pendingFile.originalFile), thumbnail_ratio: 1, - type: FileUploadAPIClient.mimeToType(pendingFile.originalFile.type || ''), + type: FileUploadAPIClient.mimeToType(pendingFile?.originalFile.type || ''), }; status = pendingFile.status || undefined; progress = pendingFile.progress; diff --git a/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/ApplicationsTable.tsx b/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/ApplicationsTable.tsx index 70f1fa71fc..7efa5a923f 100644 --- a/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/ApplicationsTable.tsx +++ b/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/ApplicationsTable.tsx @@ -37,7 +37,7 @@ export default () => { useEffect(() => { refreshApplications(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [applicationsList]); + }, [applicationsList?.length]); const refreshApplications = () => { applicationsList && setData(applicationsList); diff --git a/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/CompanyApplicationsTable.tsx b/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/CompanyApplicationsTable.tsx index bfa834b749..630b3678db 100644 --- a/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/CompanyApplicationsTable.tsx +++ b/twake/frontend/src/app/views/client/popup/WorkspaceParameter/Pages/Applications/CompanyApplicationsTable.tsx @@ -46,7 +46,7 @@ export default () => { useEffect(() => { refreshCompanyApplications(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [companyApplications]); + }, [companyApplications.length]); const refreshCompanyApplications = () => { companyApplications && setData(companyApplications); @@ -118,7 +118,7 @@ export default () => { index: number, ) => { return ( - console.log('iciii', companyApplications)}> +