|
1 | | -import React, { |
2 | | - lazy, |
3 | | - memo, |
4 | | - Suspense, |
5 | | - useEffect, |
6 | | - useMemo, |
7 | | - useRef, |
8 | | - useState, |
9 | | -} from 'react'; |
10 | | -import {Tooltip} from 'react-tooltip'; |
11 | | -import FigureLabels from '../finder/FigureLabels'; |
12 | | -import FileUploader from '../finder/FileUploader'; |
13 | | -import FolderStructure from './FolderStructure'; |
14 | | -import Menu from './Menu'; |
15 | | - |
16 | | - |
17 | | -function StaticFigure(props) { |
18 | | - return (<>{props.children}</>); |
19 | | -} |
20 | | - |
21 | | - |
22 | | -function Figure(props) { |
23 | | - const FigBody = useMemo(() => { |
24 | | - if (props.browser_component) { |
25 | | - const component = `./components/folderitem/${props.browser_component}.js`; |
26 | | - const LazyItem = lazy(() => import(component)); |
27 | | - return (props) => ( |
28 | | - <Suspense> |
29 | | - <LazyItem {...props}>{props.children}</LazyItem> |
30 | | - </Suspense> |
31 | | - ); |
32 | | - } |
33 | | - return StaticFigure; |
34 | | - },[]); |
35 | | - |
36 | | - return ( |
37 | | - <figure className="figure"> |
38 | | - <FigBody {...props}> |
39 | | - <FigureLabels labels={props.labels}> |
40 | | - <img src={props.thumbnail_url} {...props.listeners} {...props.attributes} /> |
41 | | - </FigureLabels> |
42 | | - </FigBody> |
43 | | - <figcaption> |
44 | | - {props.name} |
45 | | - </figcaption> |
46 | | - </figure> |
47 | | - ); |
48 | | -} |
49 | | - |
50 | | - |
51 | | -const FilesList = memo((props: any) => { |
52 | | - const {files} = props; |
53 | | - |
54 | | - function selectFile(file) { |
55 | | - console.log(file); |
56 | | - } |
57 | | - |
58 | | - console.log('FolderList', files); |
59 | | - |
60 | | - return ( |
61 | | - <ul className="files-list">{ |
62 | | - files.length === 0 ? |
63 | | - <li className="status">{gettext("Empty folder")}</li> : |
64 | | - files.map(file => ( |
65 | | - <li key={file.id} onClick={() => selectFile(file)}><Figure {...file} /></li> |
66 | | - ))} |
67 | | - </ul> |
68 | | - ); |
69 | | -}); |
| 1 | +import React, {useEffect, useRef, useState} from 'react'; |
| 2 | +import FileSelectDialog from './FileSelectDialog'; |
| 3 | +import CloseIcon from '../icons/close.svg'; |
70 | 4 |
|
71 | 5 |
|
72 | 6 | export default function FinderFileSelect(props) { |
| 7 | + const shadowRoot = props.container; |
73 | 8 | const baseUrl = props['base-url']; |
74 | | - const [structure, setStructure] = useState({root_folder: null, last_folder: null, files: null}); |
75 | | - const folderListRef = useRef(null); |
76 | | - const uploaderRef = useRef(null); |
| 9 | + const styleUrl = props['style-url']; |
| 10 | + const [selectedFile, setSelectedFile] = useState(props['selected-file']); |
| 11 | + const slotRef = useRef(null); |
| 12 | + const dialogRef = useRef(null); |
| 13 | + const csrfToken = shadowRoot.host.closest('form')?.querySelector('input[name="csrfmiddlewaretoken"]')?.value; |
77 | 14 |
|
78 | 15 | useEffect(() => { |
79 | | - getStructure(); |
| 16 | + // Create a styles element for the shadow DOM |
| 17 | + const link = document.createElement('link'); |
| 18 | + link.href = styleUrl; |
| 19 | + link.media = 'all'; |
| 20 | + link.rel = 'stylesheet'; |
| 21 | + shadowRoot.insertBefore(link, shadowRoot.firstChild); |
80 | 22 | }, []); |
81 | 23 |
|
82 | | - async function getStructure() { |
83 | | - const response = await fetch(`${baseUrl}structure/${props.realm}`); |
84 | | - if (response.ok) { |
85 | | - setStructure(await response.json()); |
86 | | - } else { |
87 | | - console.error(response); |
| 24 | + useEffect(() => { |
| 25 | + const handleEscape = (event) => { |
| 26 | + if (event.key === 'Escape') { |
| 27 | + closeDialog(); |
| 28 | + } |
| 29 | + }; |
| 30 | + window.addEventListener('keydown', handleEscape); |
| 31 | + return () => { |
| 32 | + window.removeEventListener('keydown', handleEscape); |
88 | 33 | } |
| 34 | + }, []); |
| 35 | + |
| 36 | + function openDialog() { |
| 37 | + dialogRef.current.showModal(); |
89 | 38 | } |
90 | 39 |
|
91 | | - async function fetchFiles(folderId: string, searchQuery='') { |
92 | | - const fetchUrl = (() => { |
93 | | - if (searchQuery) { |
94 | | - const params = new URLSearchParams({q: searchQuery}); |
95 | | - return `${baseUrl}${folderId}/search?${params.toString()}`; |
96 | | - } |
97 | | - return `${baseUrl}${folderId}/list`; |
98 | | - })(); |
99 | | - const newStructure = {root_folder: structure.root_folder, last_folder: folderId, files: null}; |
100 | | - const response = await fetch(fetchUrl); |
101 | | - if (response.ok) { |
102 | | - const body = await response.json(); |
103 | | - newStructure.files = body.files; |
104 | | - } else { |
105 | | - console.error(response); |
106 | | - } |
107 | | - setStructure(newStructure); |
| 40 | + function closeDialog() { |
| 41 | + dialogRef.current.close(); |
108 | 42 | } |
109 | 43 |
|
110 | | - function refreshStructure() { |
111 | | - console.log('refreshStructure'); |
112 | | - setStructure({...structure}); |
| 44 | + function deleteFile() { |
| 45 | + setSelectedFile(null); |
113 | 46 | } |
114 | 47 |
|
115 | | - function handleUpload(folderId) { |
116 | | - fetchFiles(folderId); |
| 48 | + function selectFile(file) { |
| 49 | + setSelectedFile(file); |
| 50 | + const inputElement = slotRef.current.assignedElements()[0]; |
| 51 | + if (inputElement instanceof HTMLInputElement) { |
| 52 | + inputElement.value = file.id; |
| 53 | + } |
| 54 | + closeDialog(); |
117 | 55 | } |
118 | 56 |
|
119 | | - return structure.root_folder && (<> |
120 | | - <nav className="folder-structure"> |
121 | | - <ul> |
122 | | - <FolderStructure |
123 | | - baseUrl={baseUrl} |
124 | | - folder={structure.root_folder} |
125 | | - lastFolderId={structure.last_folder} |
126 | | - fetchFiles={fetchFiles} |
127 | | - refreshStructure={refreshStructure} |
128 | | - /> |
129 | | - </ul> |
130 | | - </nav> |
131 | | - <div className="file-browser"> |
132 | | - <Menu |
133 | | - lastFolderId={structure.last_folder} |
134 | | - fetchFiles={fetchFiles} |
135 | | - openUploader={() => uploaderRef.current.openUploader()} |
136 | | - /> |
137 | | - <FileUploader |
138 | | - folderId={structure.last_folder} |
139 | | - handleUpload={handleUpload} |
140 | | - ref={uploaderRef} |
141 | | - settings={{csrf_token: props['csrf-token'], base_url: props['base-url']}} |
142 | | - >{ |
143 | | - structure.files === null ? |
144 | | - <div className="status">{gettext("Loading files…")}</div> : |
145 | | - <FilesList files={structure.files} /> |
146 | | - }</FileUploader> |
| 57 | + function renderTimestamp(timestamp) { |
| 58 | + const date = new Date(timestamp); |
| 59 | + return date.toLocaleString(); |
| 60 | + } |
| 61 | + |
| 62 | + return (<> |
| 63 | + <slot ref={slotRef} /> |
| 64 | + <div className="finder-file-select"> |
| 65 | + <figure>{selectedFile ? <> |
| 66 | + <img src={selectedFile.thumbnail_url} onClick={openDialog} onDragEnter={openDialog} /> |
| 67 | + <figcaption> |
| 68 | + <dl> |
| 69 | + <dt>{gettext("Name")}:</dt> |
| 70 | + <dd>{selectedFile.name}</dd> |
| 71 | + </dl> |
| 72 | + <dl> |
| 73 | + <dt>{gettext("Content-Type (Size)")}:</dt> |
| 74 | + <dd>{selectedFile.mime_type} ({selectedFile.file_size})</dd> |
| 75 | + </dl> |
| 76 | + <dl> |
| 77 | + <dt>{gettext("Modified at")}:</dt> |
| 78 | + <dd>{renderTimestamp(selectedFile.last_modified_at)}</dd> |
| 79 | + </dl> |
| 80 | + <div> |
| 81 | + <button type="button" onClick={deleteFile}>{gettext("Delete")}</button> |
| 82 | + </div> |
| 83 | + </figcaption> |
| 84 | + </> : |
| 85 | + <span onClick={openDialog} onDragEnter={openDialog}> |
| 86 | + <p>{gettext("Select File")}</p> |
| 87 | + </span> |
| 88 | + }</figure> |
147 | 89 | </div> |
148 | | - <Tooltip id="django-finder-tooltip" place="bottom-start" /> |
| 90 | + <dialog ref={dialogRef}> |
| 91 | + <FileSelectDialog |
| 92 | + baseUrl={baseUrl} |
| 93 | + csrfToken={csrfToken} |
| 94 | + realm={props.realm} |
| 95 | + selectFile={selectFile} |
| 96 | + /> |
| 97 | + <div |
| 98 | + className="close-button" |
| 99 | + role="button" |
| 100 | + onClick={closeDialog} |
| 101 | + aria-label={gettext("Close dialog")} |
| 102 | + > |
| 103 | + <CloseIcon/> |
| 104 | + </div> |
| 105 | + </dialog> |
149 | 106 | </>); |
150 | 107 | } |
0 commit comments