From 8a5cc9a2e6d44547240fd026380e67fc9a19293e Mon Sep 17 00:00:00 2001 From: Romaric Mourgues Date: Wed, 5 Apr 2023 15:39:21 +0200 Subject: [PATCH] Implemented link-files --- .../icons-colored/assets/file-type-link.svg | 11 +++ .../src/app/atoms/icons-colored/index.tsx | 3 + .../files/api/file-upload-api-client.ts | 4 +- .../app/features/files/utils/type-mimes.ts | 2 + .../body/drive/documents/document-row.tsx | 3 + .../body/drive/modals/create/create-link.tsx | 80 +++++++++++++++++++ .../client/body/drive/modals/create/index.tsx | 21 ++++- .../app/views/client/viewer/drive-display.tsx | 3 + .../app/views/client/viewer/link/display.tsx | 70 ++++++++++++++++ 9 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 twake/frontend/src/app/atoms/icons-colored/assets/file-type-link.svg create mode 100644 twake/frontend/src/app/views/client/body/drive/modals/create/create-link.tsx create mode 100644 twake/frontend/src/app/views/client/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 00000000..90548bcc --- /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 f20d2311..bdd6178f 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/features/files/api/file-upload-api-client.ts b/twake/frontend/src/app/features/files/api/file-upload-api-client.ts index 5fcb7d12..f2e69b1e 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 42a5fb1a..0a18dd45 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/views/client/body/drive/documents/document-row.tsx b/twake/frontend/src/app/views/client/body/drive/documents/document-row.tsx index 9997c70e..8e045e08 100644 --- a/twake/frontend/src/app/views/client/body/drive/documents/document-row.tsx +++ b/twake/frontend/src/app/views/client/body/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/client/body/drive/modals/create/create-link.tsx b/twake/frontend/src/app/views/client/body/drive/modals/create/create-link.tsx new file mode 100644 index 00000000..ae899059 --- /dev/null +++ b/twake/frontend/src/app/views/client/body/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/client/body/drive/modals/create/index.tsx b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx index b73d9085..584d7e0b 100644 --- a/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx +++ b/twake/frontend/src/app/views/client/body/drive/modals/create/index.tsx @@ -1,11 +1,10 @@ import { Transition } from '@headlessui/react'; import { ChevronLeftIcon, - DesktopComputerIcon, DocumentDownloadIcon, FolderAddIcon, FolderDownloadIcon, - FolderIcon, + LinkIcon, } from '@heroicons/react/outline'; import Avatar from 'app/atoms/avatar'; import A from 'app/atoms/link'; @@ -17,6 +16,7 @@ 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; @@ -93,6 +93,11 @@ export const CreateModal = ({ text="Upload folders from device" onClick={() => selectFolderFromDevice()} /> + } + text="Create a link file" + onClick={() => setState({ ...state, type: 'link' })} + /> {(applications || []) .filter(app => app.display?.twake?.files?.editor?.empty_files?.length) @@ -148,6 +153,18 @@ export const CreateModal = ({ > + + + + diff --git a/twake/frontend/src/app/views/client/viewer/drive-display.tsx b/twake/frontend/src/app/views/client/viewer/drive-display.tsx index 58277012..ea469805 100644 --- a/twake/frontend/src/app/views/client/viewer/drive-display.tsx +++ b/twake/frontend/src/app/views/client/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/client/viewer/link/display.tsx b/twake/frontend/src/app/views/client/viewer/link/display.tsx new file mode 100644 index 00000000..4a15df57 --- /dev/null +++ b/twake/frontend/src/app/views/client/viewer/link/display.tsx @@ -0,0 +1,70 @@ +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]) { + 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. +
+
+ +
+
+ ); +};