diff --git a/assets/img/empty-file.svg b/assets/img/empty-file.svg new file mode 100644 index 0000000..cf649a9 --- /dev/null +++ b/assets/img/empty-file.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/deployment/server/dashboard.html b/deployment/server/dashboard.html index 840b755..63c4e32 100644 --- a/deployment/server/dashboard.html +++ b/deployment/server/dashboard.html @@ -72,6 +72,12 @@ src="./Cress-gh/assets/img/new-folder.svg" title="folder" /> +
New folder
+ +
+
New Empty File
+
`; // If a single file is selected diff --git a/src/Dashboard/Dashboard.ts b/src/Dashboard/Dashboard.ts index d80d972..3f58033 100644 --- a/src/Dashboard/Dashboard.ts +++ b/src/Dashboard/Dashboard.ts @@ -1,10 +1,11 @@ import { IEntry, IFile, IFolder, FileSystemTools } from './FileSystem'; -import { deleteDocument, updateDocName } from './Storage'; +import { deleteDocument, updateDocName, addDocument } from './Storage'; import { FileSystemManager } from './FileSystem'; import { ShiftSelectionManager, dashboardState } from './DashboardTools'; import { InitUploadArea } from './UploadArea'; import * as contextMenuContent from './ContextMenuContent'; import { ModalWindow, ModalWindowView } from '../utils/ModalWindow'; +import { v4 as uuidv4 } from 'uuid'; const documentsContainer: HTMLDivElement = document.querySelector( '#fs-content-container', @@ -25,6 +26,8 @@ const uploadDocumentsButton: HTMLButtonElement = document.querySelector( ); const newFolderButton: HTMLButtonElement = document.querySelector('#add-folder-button'); +const newFileButton: HTMLButtonElement = + document.querySelector('#add-file-button'); const shiftSelection = new ShiftSelectionManager(); const fsm = FileSystemManager(); @@ -49,6 +52,7 @@ openButton?.addEventListener('click', openDocsHandler); removeButton?.addEventListener('click', removeDocsHandler); uploadDocumentsButton?.addEventListener('click', openUploadAreaHandler); newFolderButton?.addEventListener('click', openNewFolderWindow); +newFileButton?.addEventListener('click', openNewFileWindow); // Sorting algorithms // const sortByAlphanumerical = (a: IEntry, b: IEntry) => a.name.localeCompare(b.name); @@ -517,6 +521,7 @@ function updateActionBarButtons() { ) { uploadDocumentsButton.classList.remove('active'); newFolderButton.classList.remove('active'); + newFileButton.classList.remove('active'); removeButton.classList.remove('active'); if (state.getSelectedEntries().length) { openButton.classList.add('active'); @@ -528,6 +533,7 @@ function updateActionBarButtons() { else if (state.isInTrash()) { uploadDocumentsButton.classList.remove('active'); newFolderButton.classList.remove('active'); + newFileButton.classList.remove('active'); removeButton.classList.remove('active'); openButton.classList.remove('active'); } @@ -537,6 +543,7 @@ function updateActionBarButtons() { removeButton.classList.remove('active'); uploadDocumentsButton.classList.remove('active'); newFolderButton.classList.remove('active'); + newFileButton.classList.remove('active'); } // selecting entries else if (state.getSelectedEntries().length) { @@ -544,6 +551,7 @@ function updateActionBarButtons() { removeButton.classList.add('active'); uploadDocumentsButton.classList.remove('active'); newFolderButton.classList.remove('active'); + newFileButton.classList.remove('active'); } // nothing selected, not in ./Samples or ./Trash else { @@ -551,6 +559,7 @@ function updateActionBarButtons() { removeButton.classList.remove('active'); uploadDocumentsButton.classList.add('active'); newFolderButton.classList.add('active'); + newFileButton.classList.add('active'); } } @@ -723,6 +732,48 @@ function handleAddFolder(folderName: string) { } } +/** + * Add new File to current folder and refresh dashboard + */ +function handleAddFile(fileName: string, rowNum: number) { + // create new file element + const newFileTile = document.createElement('div'); + newFileTile.classList.add('document-entry'); + newFileTile.classList.add('file-entry'); + newFileTile.setAttribute('id', 'new-file'); + + const newFileId = uuidv4(); + + // add new empty json file to db + const headers = ['image', 'name', 'classification', 'mei']; + const data = Array(rowNum).fill({}); + data.forEach((row) => { + headers.forEach((header) => { + row[header] = ''; + }); + }); + const jsonBlob = new Blob([JSON.stringify([headers, ...data], null, 2)], { + type: 'application/json', + }); + addDocument(newFileId, fileName, jsonBlob); + + // create new file object to dashboard + const datetime = new Date().toLocaleString(); + const fileEntry = FileSystemTools.createFile(fileName, newFileId); + const docEntry = FileSystemTools.addMetadata(fileEntry, { + created_on: datetime, + }); + const succeeded = FileSystemTools.addEntry(docEntry, state.getParentFolder()); + if (succeeded) { + newFileTile.setAttribute('id', fileName); + updateDashboard(); + return true; + } else { + newFileTile.remove(); + return false; + } +} + /** * Renames current selection of document on dashboard, updating the database for files * @@ -977,6 +1028,80 @@ function openNewFolderWindow() { }); } +/** + * Opens New Empty File menu modal window that prompts for a name. + * On clicking the Create button, closes modal window and creates a new empty file. + */ +function openNewFileWindow() { + if (!newFileButton.classList.contains('active')) return; + + // generate modal window + const modalWindow = new ModalWindow(); + modalWindow.setModalWindowView(ModalWindowView.NEW_FILE); + modalWindow.openModalWindow(); + + const inputContainer = document.getElementById( + 'dashboard_input_container', + ) as HTMLDivElement; + const cancelButton = document.getElementById( + 'cancel_dashboard', + ) as HTMLButtonElement; + const confirmButton = document.getElementById( + 'confirm_dashboard', + ) as HTMLButtonElement; + + // Create input field for file name + const fileNameInput = document.createElement('input'); + fileNameInput.id = 'dashboard_input'; + fileNameInput.type = 'text'; + fileNameInput.placeholder = 'Untitled File'; + fileNameInput.value = 'Untitled File'; + // label for file name + const fileNameLabel = document.createElement('label'); + fileNameLabel.htmlFor = 'dashboard_input'; + fileNameLabel.innerText = 'File Name:'; + inputContainer.appendChild(fileNameLabel); + inputContainer.appendChild(fileNameInput); + + fileNameInput.select(); + fileNameInput.focus(); + + // Create input field for number of rows + const rowNumInput = document.createElement('input'); + rowNumInput.id = 'dashboard_input'; + rowNumInput.type = 'number'; + rowNumInput.min = '1'; + rowNumInput.max = '100'; + rowNumInput.value = '10'; + // label for number of rows + const rowNumLabel = document.createElement('label'); + rowNumLabel.htmlFor = 'dashboard_input'; + rowNumLabel.innerText = 'Number of Rows:'; + inputContainer.appendChild(rowNumLabel); + inputContainer.appendChild(rowNumInput); + + cancelButton.addEventListener('click', () => modalWindow.hideModalWindow()); + confirmButton.addEventListener('click', () => + confirmNewFileAction( + modalWindow, + fileNameInput.value, + parseInt(rowNumInput.value), + ), + ); + + inputContainer.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + modalWindow.hideModalWindow(); + } else if (event.key === 'Enter') { + confirmNewFileAction( + modalWindow, + fileNameInput.value, + parseInt(rowNumInput.value), + ); + } + }); +} + function confirmNewFolderAction(modalWindow: ModalWindow, folderName: string) { if (!nameExists(folderName)) { modalWindow.hideModalWindow(); @@ -987,6 +1112,20 @@ function confirmNewFolderAction(modalWindow: ModalWindow, folderName: string) { } } +function confirmNewFileAction( + modalWindow: ModalWindow, + fileName: string, + rowNum: number, +) { + if (!nameExists(fileName)) { + modalWindow.hideModalWindow(); + handleAddFile(fileName, rowNum); + } else { + window.alert('The file name already exists in the current folder!'); + openNewFileWindow(); + } +} + /** * Opens Rename menu modal window that prompts for a new name. */ @@ -1261,6 +1400,7 @@ function showContextMenu(view: string, clientX: number, clientY: number) { const moveBtn = document.getElementById('cm-move-btn'); const updateDocBtn = document.getElementById('cm-upload-doc-btn'); const newFolderBtn = document.getElementById('cm-new-folder-btn'); + const newFileBtn = document.getElementById('cm-new-file-btn'); if (deleteBtn) { deleteBtn.classList.add('disabled'); @@ -1277,6 +1417,9 @@ function showContextMenu(view: string, clientX: number, clientY: number) { if (newFolderBtn) { newFolderBtn.classList.add('disabled'); } + if (newFileBtn) { + newFileBtn.classList.add('disabled'); + } } // get the position of the user's mouse @@ -1478,6 +1621,14 @@ function setContextMenuItemsEventListeners(view: string) { contextMenu.classList.add('hidden'); openNewFolderWindow(); }); + + // "New file" menu item + document + .querySelector(`.${btnClassname}#cm-new-file-btn`) + .addEventListener('click', (_e) => { + contextMenu.classList.add('hidden'); + openNewFileWindow(); + }); } } diff --git a/src/Dashboard/DashboardContent.ts b/src/Dashboard/DashboardContent.ts index 52ecc5c..3418a17 100644 --- a/src/Dashboard/DashboardContent.ts +++ b/src/Dashboard/DashboardContent.ts @@ -33,6 +33,17 @@ export const newFolderHTML = ` `; +export const newFileHTML = ` +
+
+
+
+ + +
+
+ `; + export const renameHTML = `
diff --git a/src/utils/ModalWindow.ts b/src/utils/ModalWindow.ts index f394be7..69453ef 100644 --- a/src/utils/ModalWindow.ts +++ b/src/utils/ModalWindow.ts @@ -2,6 +2,7 @@ import { ModalWindowInterface } from '../Interfaces'; import { hotkeysModal } from '../Contents'; import { newFolderHTML, + newFileHTML, renameHTML, uploadAreaHTML, } from '../Dashboard/DashboardContent'; @@ -17,6 +18,7 @@ export enum ModalWindowView { MOVE_TO, NEW_FOLDER, RENAME, + NEW_FILE, } enum ModalWindowState { @@ -110,6 +112,8 @@ export class ModalWindow implements ModalWindowInterface { // break; case ModalWindowView.NEW_FOLDER: + case ModalWindowView.NEW_FILE: + case ModalWindowView.RENAME: default: @@ -132,6 +136,7 @@ export class ModalWindow implements ModalWindowInterface { case ModalWindowView.DOCUMENT_UPLOAD: case ModalWindowView.MOVE_TO: case ModalWindowView.NEW_FOLDER: + case ModalWindowView.NEW_FILE: case ModalWindowView.RENAME: document.getElementById( 'cress-modal-window-content-container', @@ -191,6 +196,11 @@ export class ModalWindow implements ModalWindowInterface { container.innerHTML = newFolderHTML; break; + case ModalWindowView.NEW_FILE: + title.innerText = 'NEW FILE'; + container.innerHTML = newFileHTML; + break; + case ModalWindowView.RENAME: title.innerText = 'RENAME'; container.innerHTML = renameHTML;