From 06f53277ce3ae1f0fea1f9cd306a495f6a447182 Mon Sep 17 00:00:00 2001 From: Romaric Mourgues Date: Wed, 5 Apr 2023 16:11:27 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20Patch=203=20(#2790)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix unable to preview migrated files, fix error when switching workspaces * Fix missing events and tasks, remove email invitation calendar * Put back link-files --- .../icons-colored/assets/file-type-link.svg | 11 +++ .../src/app/atoms/icons-colored/index.tsx | 3 + .../CollectionsV1/Collections/Collection.js | 3 +- .../features/drive/hooks/use-drive-preview.ts | 7 +- .../files/api/file-upload-api-client.ts | 4 +- .../app/features/files/utils/type-mimes.ts | 2 + .../global/services/websocket-service.ts | 2 +- .../app/features/viewer/hooks/use-viewer.ts | 3 +- .../calendar/modals/Part/Participants.jsx | 1 - .../drive/documents/document-row.tsx | 3 + .../drive/modals/create/create-link.tsx | 80 +++++++++++++++++++ .../drive/modals/create/index.tsx | 21 ++++- .../app/views/applications/viewer/display.tsx | 10 ++- .../applications/viewer/drive-display.tsx | 3 + .../applications/viewer/link/display.tsx | 71 ++++++++++++++++ 15 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 twake/frontend/src/app/atoms/icons-colored/assets/file-type-link.svg create mode 100644 twake/frontend/src/app/views/applications/drive/modals/create/create-link.tsx create mode 100644 twake/frontend/src/app/views/applications/viewer/link/display.tsx diff --git a/twake/frontend/src/app/atoms/icons-colored/assets/file-type-link.svg b/twake/frontend/src/app/atoms/icons-colored/assets/file-type-link.svg new file mode 100644 index 0000000000..90548bccab --- /dev/null +++ b/twake/frontend/src/app/atoms/icons-colored/assets/file-type-link.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/twake/frontend/src/app/atoms/icons-colored/index.tsx b/twake/frontend/src/app/atoms/icons-colored/index.tsx index f20d231185..bdd6178fd1 100644 --- a/twake/frontend/src/app/atoms/icons-colored/index.tsx +++ b/twake/frontend/src/app/atoms/icons-colored/index.tsx @@ -9,6 +9,7 @@ import { ReactComponent as FileTypeSpreadsheetSvg } from './assets/file-type-spr import { ReactComponent as FileTypeUnknownSvg } from './assets/file-type-unknown.svg'; import { ReactComponent as FileTypeMediaSvg } from './assets/file-type-media.svg'; import { ReactComponent as FileTypeSlidesSvg } from './assets/file-type-slides.svg'; +import { ReactComponent as FileTypeLinkSvg } from './assets/file-type-link.svg'; import { ReactComponent as RemoveSvg } from './assets/remove.svg'; import { ReactComponent as SentSvg } from './assets/sent.svg'; @@ -34,6 +35,8 @@ export const FileTypeSlidesIcon = (props: ComponentProps<'svg'>) => ( ); +export const FileTypeLinkIcon = (props: ComponentProps<'svg'>) => ; + export const RemoveIcon = (props: ComponentProps<'svg'>) => ; export const SentIcon = (props: ComponentProps<'svg'>) => ; diff --git a/twake/frontend/src/app/deprecated/CollectionsV1/Collections/Collection.js b/twake/frontend/src/app/deprecated/CollectionsV1/Collections/Collection.js index 6a09bd434b..e8d334dae9 100755 --- a/twake/frontend/src/app/deprecated/CollectionsV1/Collections/Collection.js +++ b/twake/frontend/src/app/deprecated/CollectionsV1/Collections/Collection.js @@ -153,7 +153,8 @@ export default class Collection extends Observable { if ( routes.length === 0 || (options.http_options || {})._http_force_load || - !waiting_one_route + !waiting_one_route || + true //Keep it working after making it highly deprecated ) { initHttp(); } diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts index bf7d3ff75e..cd6a691582 100644 --- a/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-preview.ts @@ -11,7 +11,10 @@ export const useDrivePreviewModal = () => { const [status, setStatus] = useRecoilState(DriveViewerState); const open: (item: DriveItem) => void = (item: DriveItem) => { - if (item.last_version_cache?.file_metadata?.source === 'internal') { + if ( + !item.last_version_cache?.file_metadata?.source || + item.last_version_cache?.file_metadata?.source === 'internal' + ) { setStatus({ item, loading: true }); } }; @@ -79,5 +82,5 @@ export const useDrivePreviewDisplayData = () => { fileId: status.details?.item.last_version_cache.file_metadata.external_id || '', }); - return { download, id, name, type, extension }; + return { download, id, name, type, extension, size: status.details?.item.size }; }; diff --git a/twake/frontend/src/app/features/files/api/file-upload-api-client.ts b/twake/frontend/src/app/features/files/api/file-upload-api-client.ts index 5fcb7d1205..f2e69b1e9f 100644 --- a/twake/frontend/src/app/features/files/api/file-upload-api-client.ts +++ b/twake/frontend/src/app/features/files/api/file-upload-api-client.ts @@ -105,7 +105,8 @@ class FileUploadAPIClient { } public mimeToType(mime: string, extension?: string): FileTypes { - const { archives, images, pdf, slides, audio, spreadsheets, videos, documents } = fileTypeMimes; + const { archives, images, pdf, slides, audio, spreadsheets, videos, documents, links } = + fileTypeMimes; if (images.includes(mime)) return 'image'; if (videos.includes(mime)) return 'video'; @@ -115,6 +116,7 @@ class FileUploadAPIClient { if (archives.includes(mime)) return 'archive'; if (spreadsheets.includes(mime)) return 'spreadsheet'; if (documents.includes(mime)) return 'document'; + if (links.includes(mime)) return 'link'; if (extension && AceModeList.getMode(extension) !== 'text') { return 'code'; diff --git a/twake/frontend/src/app/features/files/utils/type-mimes.ts b/twake/frontend/src/app/features/files/utils/type-mimes.ts index 42a5fb1a4c..0a18dd456c 100644 --- a/twake/frontend/src/app/features/files/utils/type-mimes.ts +++ b/twake/frontend/src/app/features/files/utils/type-mimes.ts @@ -151,6 +151,7 @@ export const fileTypeMimes: FileTypeMimes = { 'text/csv', 'application/vnd.ms-excel.sheet.macroEnabled.12', ], + links: ['text/uri-list'], }; export type FileTypeMimes = { @@ -162,4 +163,5 @@ export type FileTypeMimes = { archives: string[]; pdf: string[]; documents: string[]; + links: string[]; }; diff --git a/twake/frontend/src/app/features/global/services/websocket-service.ts b/twake/frontend/src/app/features/global/services/websocket-service.ts index 02547a0d41..b39a2bb1fc 100644 --- a/twake/frontend/src/app/features/global/services/websocket-service.ts +++ b/twake/frontend/src/app/features/global/services/websocket-service.ts @@ -207,7 +207,7 @@ class WebSocketService extends EventEmitter { * @param tag */ public leave(path: string, tag: string) { - const name = path.replace(/\/$/, ''); + const name = path ? path.replace(/\/$/, '') : ''; this.wsListeners[name] = this.wsListeners[name] || {}; delete this.wsListeners[name][tag]; diff --git a/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts b/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts index f43d6782c4..91e9fbb19b 100644 --- a/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts +++ b/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts @@ -24,7 +24,8 @@ export const useFileViewerModal = () => { return { open: (file: MessageFileType) => { - if (file.metadata?.source === 'internal') setStatus({ file, loading: true }); + if (!file.metadata?.source || file.metadata?.source === 'internal') + setStatus({ file, loading: true }); }, close: () => setStatus({ file: null, loading: true }), isOpen: !!status?.file, diff --git a/twake/frontend/src/app/views/applications/calendar/modals/Part/Participants.jsx b/twake/frontend/src/app/views/applications/calendar/modals/Part/Participants.jsx index a9e4d5b59f..0121aa495a 100755 --- a/twake/frontend/src/app/views/applications/calendar/modals/Part/Participants.jsx +++ b/twake/frontend/src/app/views/applications/calendar/modals/Part/Participants.jsx @@ -26,7 +26,6 @@ export default class Participants extends Component { return { id: participant.user_id_or_mail }; })} scope="workspace" - allowMails onUpdate={ids_mails => { this.props.onChange && this.props.onChange( diff --git a/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx b/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx index e721330d62..4f95b1f1f0 100644 --- a/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx +++ b/twake/frontend/src/app/views/applications/drive/documents/document-row.tsx @@ -3,6 +3,7 @@ import { Button } from 'app/atoms/button/button'; import { FileTypeArchiveIcon, FileTypeDocumentIcon, + FileTypeLinkIcon, FileTypeMediaIcon, FileTypePdfIcon, FileTypeSlidesIcon, @@ -89,6 +90,8 @@ export const DocumentRow = ({ ) : fileType === 'slides' ? ( + ) : fileType === 'link' ? ( + ) : ( )} diff --git a/twake/frontend/src/app/views/applications/drive/modals/create/create-link.tsx b/twake/frontend/src/app/views/applications/drive/modals/create/create-link.tsx new file mode 100644 index 0000000000..ae89905990 --- /dev/null +++ b/twake/frontend/src/app/views/applications/drive/modals/create/create-link.tsx @@ -0,0 +1,80 @@ +import { Button } from 'app/atoms/button/button'; +import { Input } from 'app/atoms/input/input-text'; +import { Info } from 'app/atoms/text'; +import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; +import { useState } from 'react'; +import { useRecoilState } from 'recoil'; +import { CreateModalAtom } from '.'; +import FileUploadService from 'features/files/services/file-upload-service'; + +export const CreateLink = () => { + const [name, setName] = useState(''); + const [link, setLink] = useState(''); + const [loading] = useState(false); + const [state, setState] = useRecoilState(CreateModalAtom); + const { create } = useDriveActions(); + + const createLink = async () => { + let finalLink = link.trim(); + if (!/^https?:\/\//i.test(finalLink)) finalLink = 'http://' + finalLink; + const file = new File(['[InternetShortcut]\nURL=' + finalLink], name?.trim() + '.url', { + type: 'text/uri-list', + }); + + await FileUploadService.upload([file], { + context: { + parentId: state.parent_id, + }, + callback: (file, context) => { + if (file) + create( + { name, parent_id: context.parentId, size: file.upload_data?.size }, + { + provider: 'internal', + application_id: '', + file_metadata: { + name: file.metadata?.name, + size: file.upload_data?.size, + mime: file.metadata?.mime, + thumbnails: file?.thumbnails, + source: 'internal', + external_id: file.id, + }, + }, + ); + }, + }); + }; + + return ( + <> + Create a link + + setName(e.target.value)} + /> + + setLink(e.target.value)} + /> + + + + ); +}; diff --git a/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx b/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx index a68a9d038a..5053ee419f 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx +++ b/twake/frontend/src/app/views/applications/drive/modals/create/index.tsx @@ -1,17 +1,17 @@ import { Transition } from '@headlessui/react'; import { ChevronLeftIcon, DesktopComputerIcon } from '@heroicons/react/outline'; -import { FolderIcon } from '@heroicons/react/solid'; +import { FolderIcon, LinkIcon } from '@heroicons/react/solid'; import Avatar from 'app/atoms/avatar'; import A from 'app/atoms/link'; import { Modal, ModalContent } from 'app/atoms/modal'; import { Base } from 'app/atoms/text'; -import { useApplications } from 'app/features/applications/hooks/use-applications'; import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; import { Application } from 'app/features/applications/types/application'; import { ReactNode } from 'react'; import { atom, useRecoilState } from 'recoil'; import { slideXTransition, slideXTransitionReverted } from 'src/utils/transitions'; import { CreateFolder } from './create-folder'; +import { CreateLink } from './create-link'; export type CreateModalAtomType = { open: boolean; @@ -81,6 +81,11 @@ export const CreateModal = ({ text="Upload document from device" onClick={() => selectFromDevice()} /> + } + text="Create a link file" + onClick={() => setState({ ...state, type: 'link' })} + /> {(applications || []) .filter(app => app.display?.twake?.files?.editor?.empty_files?.length) @@ -136,6 +141,18 @@ export const CreateModal = ({ > + + + + diff --git a/twake/frontend/src/app/views/applications/viewer/display.tsx b/twake/frontend/src/app/views/applications/viewer/display.tsx index f31ad90019..236b5dd43e 100644 --- a/twake/frontend/src/app/views/applications/viewer/display.tsx +++ b/twake/frontend/src/app/views/applications/viewer/display.tsx @@ -20,7 +20,7 @@ export default () => { } if (type === 'image') { - return ; + return ; } if (type === 'video' || type === 'audio') { @@ -39,6 +39,14 @@ export default () => { return ; } + if (type === 'link') { + return ( +
+ Opening link... +
+ ); + } + // /* Uncomment after https://github.com/linagora/Twake/issues/2453 is done if (type) { return ; diff --git a/twake/frontend/src/app/views/applications/viewer/drive-display.tsx b/twake/frontend/src/app/views/applications/viewer/drive-display.tsx index 58277012cf..ea4698058c 100644 --- a/twake/frontend/src/app/views/applications/viewer/drive-display.tsx +++ b/twake/frontend/src/app/views/applications/viewer/drive-display.tsx @@ -9,6 +9,7 @@ import PdfDisplay from './pdf/display'; import CodeDisplay from './code/display'; import ArchiveDisplay from './archive/display'; import OtherDisplay from './other/display'; +import LinkDisplay from './link/display'; export default (): React.ReactElement => { const { download, type, name, id } = useDrivePreviewDisplayData(); @@ -39,6 +40,8 @@ export default (): React.ReactElement => { return ; case 'pdf': return ; + case 'link': + return ; default: return ; } diff --git a/twake/frontend/src/app/views/applications/viewer/link/display.tsx b/twake/frontend/src/app/views/applications/viewer/link/display.tsx new file mode 100644 index 0000000000..16be26bf09 --- /dev/null +++ b/twake/frontend/src/app/views/applications/viewer/link/display.tsx @@ -0,0 +1,71 @@ +import { Button } from 'app/atoms/button/button'; +import { useDrivePreviewDisplayData } from 'app/features/drive/hooks/use-drive-preview'; +import { useEffect, useState } from 'react'; + +export default (props: { download: string; name: string }) => { + const { size } = useDrivePreviewDisplayData(); + const [error, setError] = useState(false); + const [loading, setLoading] = useState(true); + + const openLink = async () => { + if ((size || 10000000) < 10000) { + setLoading(true); + //Download file content and extract link from url props.download + try { + const response = await fetch(props.download); + const blob = await response.blob(); + const reader = new FileReader(); + reader.readAsText(blob); + reader.onloadend = () => { + const result = reader.result as string; + const link = result.match(/URL=(.*)/); + if (link && link[1]) { + if (!link[1].match(/^(http|https):\/\//)) throw new Error('Invalid link'); + window.open(link[1], '_blank'); + } else { + setError(true); + } + setLoading(false); + }; + } catch (e) { + setError(true); + setLoading(false); + } + return; + } + setError(true); + }; + + useEffect(() => { + openLink(); + }, []); + + if (loading) { + return ( +
+ Opening link... +
+ ); + } + + if (error) { + return ( +
+ + We can't open '{props.name}' as a link. You can download it instead. + +
+ ); + } + + return ( +
+ + Link was open on another tab. +
+
+ +
+
+ ); +};