From 05967c1f5a8f40976854ac79ca92d7362586dff0 Mon Sep 17 00:00:00 2001 From: Shuvagata Dey Date: Tue, 13 Oct 2026 23:20:42 +0530 Subject: [PATCH] Improve project creation inputs UI and layout #2263 Add a sticky header for staged files with count, total size, and delete all. Prevent rapid delete clicks from opening the file picker dialog. Bound the file list and download URLs textarea to avoid page expansion, and keep the bottom actions pinned to the viewport. Signed-off-by: Shuvagata Dey --- docs/inputs.rst | 5 + scancodeio/static/js/add_inputs.js | 413 +++++++++++++-- scancodeio/static/main.css | 487 ++++++++++++++++++ scanpipe/templates/scanpipe/project_form.html | 56 +- 4 files changed, 896 insertions(+), 65 deletions(-) diff --git a/docs/inputs.rst b/docs/inputs.rst index d87acd2203..3a3c7f14bb 100644 --- a/docs/inputs.rst +++ b/docs/inputs.rst @@ -19,6 +19,11 @@ JSON format). When uploading through the Web UI, navigate to your project and use the upload interface in the "Inputs" panel. +.. note:: + + Uploading directories or folders directly is not supported. Please compress + directories into an archive (such as ``.zip`` or ``.tar.gz``) before uploading. + For REST API uploads, refer to the :ref:`rest_api` documentation for endpoint details. .. _inputs_download_url: diff --git a/scancodeio/static/js/add_inputs.js b/scancodeio/static/js/add_inputs.js index 1850759c38..34eac57aa6 100644 --- a/scancodeio/static/js/add_inputs.js +++ b/scancodeio/static/js/add_inputs.js @@ -22,11 +22,103 @@ const fileInput = document.querySelector("#id_input_files"); let selectedFiles = []; // Store selected files -fileInput.onchange = updateFiles; +let folderAlertTimeout = null; +if (fileInput) { + fileInput.onchange = updateFiles; +} + +function formatFileSize(bytes) { + if (!bytes || bytes <= 0) return "0 B"; + const units = ["B", "KB", "MB", "GB", "TB"]; + const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1); + const size = (bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1); + return `${size} ${units[i]}`; +} + +function updateHeaderSummary() { + const header = document.querySelector(".file-list-header"); + if (!header) return; + const countBadge = header.querySelector(".file-list-count"); + const sizeBadge = header.querySelector(".file-list-size"); + const totalCount = selectedFiles.length; + const totalBytes = selectedFiles.reduce((acc, f) => acc + (f.size || 0), 0); + if (countBadge) countBadge.innerHTML = `${totalCount} file${totalCount === 1 ? "" : "s"}`; + if (sizeBadge) sizeBadge.textContent = formatFileSize(totalBytes); +} + +function renderFileListHeader(container) { + const totalCount = selectedFiles.length; + const totalBytes = selectedFiles.reduce((acc, f) => acc + (f.size || 0), 0); + + const header = document.createElement("div"); + header.className = "file-list-header"; + + const info = document.createElement("div"); + info.className = "file-list-info"; + + const countBadge = document.createElement("span"); + countBadge.className = "file-list-count"; + countBadge.innerHTML = `${totalCount} file${totalCount === 1 ? "" : "s"}`; + + const dot = document.createElement("span"); + dot.className = "file-list-dot"; + dot.textContent = " • "; + + const sizeBadge = document.createElement("span"); + sizeBadge.className = "file-list-size"; + sizeBadge.textContent = formatFileSize(totalBytes); + + info.appendChild(countBadge); + info.appendChild(dot); + info.appendChild(sizeBadge); + + const deleteAllBtn = document.createElement("button"); + deleteAllBtn.type = "button"; + deleteAllBtn.className = "file-list-delete-all"; + deleteAllBtn.id = "file-delete-all-btn"; + deleteAllBtn.setAttribute("aria-label", "Delete all selected files"); + deleteAllBtn.innerHTML = 'Delete all'; + deleteAllBtn.addEventListener("click", function(event) { + disableEvent(event); + deleteAllFiles(); + }); + + header.appendChild(info); + header.appendChild(deleteAllBtn); + container.appendChild(header); +} + +function deleteAllFiles() { + const rows = document.querySelectorAll(".file-item-row"); + rows.forEach(row => row.classList.add("is-deleting")); + const header = document.querySelector(".file-list-header"); + if (header) { + header.style.opacity = "0"; + header.style.transition = "opacity 150ms ease"; + } + + selectedFiles = []; + const dataTransfer = new DataTransfer(); + if (fileInput) { + fileInput.files = dataTransfer.files; + } + + setTimeout(() => { + if (selectedFiles.length === 0) { + const fileNamesContainer = document.querySelector("#inputs_file_name"); + if (fileNamesContainer) { + const emptyNotice = document.createElement("i"); + emptyNotice.className = "file-empty-notice"; + emptyNotice.textContent = "No files selected"; + fileNamesContainer.replaceChildren(emptyNotice); + } + } + }, 180); +} // Update the list of files to be uploaded in the UI function updateFiles() { - if (fileInput.files.length > 0) { + if (fileInput && fileInput.files.length > 0) { const fileName = document.querySelector("#inputs_file_name"); fileName.replaceChildren(); @@ -39,40 +131,51 @@ function updateFiles() { // Concatenate the unique files to the existing selectedFiles array selectedFiles = selectedFiles.concat(filteredNewFiles); + const dataTransfer = new DataTransfer(); + for (const file of selectedFiles) { + dataTransfer.items.add(file); + } + fileInput.files = dataTransfer.files; + + if (selectedFiles.length > 0) { + renderFileListHeader(fileName); + } + for (let file of selectedFiles) { const fileNameWithoutSpaces = file.name.replace(/\s/g, ""); // Build the wrapper span const wrapper = document.createElement("span"); - wrapper.className = "is-flex is-justify-content-space-between is-block"; + wrapper.className = "file-item-row"; wrapper.id = `file-name-${fileNameWithoutSpaces}`; + wrapper.dataset.fileName = file.name; - // File name label - textContent is safe, no HTML injection possible + // File name label const label = document.createElement("span"); - label.className = "is-block"; + label.className = "file-item-name"; label.textContent = file.name; + label.title = file.name; // Delete button - const deleteLink = document.createElement("a"); - deleteLink.href = "#"; - deleteLink.className = "model-button"; - deleteLink.id = `file-delete-btn-${fileNameWithoutSpaces}`; - deleteLink.addEventListener("click", function(event) { + const deleteBtn = document.createElement("button"); + deleteBtn.type = "button"; + deleteBtn.className = "file-item-delete"; + deleteBtn.id = `file-delete-btn-${fileNameWithoutSpaces}`; + deleteBtn.setAttribute("aria-label", `Remove ${file.name}`); + deleteBtn.title = `Remove ${file.name}`; + deleteBtn.addEventListener("click", function(event) { disableEvent(event); - removeFile(fileNameWithoutSpaces); - if (selectedFiles.length == 0) { - const emptyNotice = document.createElement("i"); - emptyNotice.textContent = "No files selected"; - fileName.replaceChildren(emptyNotice); - } + if (wrapper.classList.contains("is-deleting")) return; + removeFile(file.name); }); const icon = document.createElement("i"); icon.className = "fa-solid fa-trash-can"; + icon.setAttribute("aria-hidden", "true"); - deleteLink.appendChild(icon); + deleteBtn.appendChild(icon); wrapper.appendChild(label); - wrapper.appendChild(deleteLink); + wrapper.appendChild(deleteBtn); fileName.appendChild(wrapper); } } @@ -84,40 +187,274 @@ function disableEvent(event) { event.preventDefault(); } -function removeFile(fileName) { - selectedFiles = selectedFiles.filter(file => { - const fileNameWithoutSpaces = file.name.replace(/\s/g, ""); - return fileNameWithoutSpaces !== fileName; - }); - - const fileNameElement = document.getElementById(`file-name-${fileName}`); - if (fileNameElement) { - fileNameElement.remove(); - } +function removeFile(targetFileName) { + selectedFiles = selectedFiles.filter(file => file.name !== targetFileName); const dataTransfer = new DataTransfer(); for (let file of selectedFiles) { dataTransfer.items.add(file); } - fileInput.files = dataTransfer.files; + if (fileInput) { + fileInput.files = dataTransfer.files; + } + updateHeaderSummary(); + + if (selectedFiles.length === 0) { + const header = document.querySelector(".file-list-header"); + if (header) { + header.style.opacity = "0"; + header.style.transition = "opacity 150ms ease"; + } + } + + const fileNameWithoutSpaces = targetFileName.replace(/\s/g, ""); + const fileNameElement = document.getElementById(`file-name-${fileNameWithoutSpaces}`); + if (fileNameElement) { + fileNameElement.classList.add("is-deleting"); + setTimeout(() => { + if (fileNameElement.parentNode) { + fileNameElement.remove(); + } + if (selectedFiles.length === 0) { + const fileNamesContainer = document.querySelector("#inputs_file_name"); + if (fileNamesContainer && !fileNamesContainer.querySelector(".file-item-row:not(.is-deleting)")) { + const emptyNotice = document.createElement("i"); + emptyNotice.className = "file-empty-notice"; + emptyNotice.textContent = "No files selected"; + fileNamesContainer.replaceChildren(emptyNotice); + } + } + }, 180); + } +} + +const fileNameContainer = document.querySelector("#inputs_file_name"); +if (fileNameContainer) { + fileNameContainer.addEventListener("click", function(event) { + if (selectedFiles.length > 0) { + event.preventDefault(); + } + + if (event.target.closest("#file-delete-all-btn")) { + return; + } + + if (event.target.closest(".file-item-delete")) { + return; + } + + const rect = fileNameContainer.getBoundingClientRect(); + if (event.clientX > rect.left + fileNameContainer.clientWidth) { + return; + } + + if (event.clientX >= rect.right - 55) { + const rowUnderCursor = document.elementFromPoint(event.clientX, event.clientY)?.closest(".file-item-row:not(.is-deleting)"); + if (rowUnderCursor && rowUnderCursor.dataset.fileName) { + removeFile(rowUnderCursor.dataset.fileName); + } + } + }); +} + +function inspectDataTransfer(dataTransfer) { + const validFiles = []; + const rejectedFolders = []; + + const items = dataTransfer.items; + if (items && items.length > 0) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind !== "file") continue; + + const entry = item.webkitGetAsEntry ? item.webkitGetAsEntry() : null; + if (entry) { + if (entry.isDirectory) { + rejectedFolders.push(entry.name || "folder"); + continue; + } + } + + const file = item.getAsFile ? item.getAsFile() : null; + if (file) { + validFiles.push(file); + } + } + } else if (dataTransfer.files && dataTransfer.files.length > 0) { + for (const file of dataTransfer.files) { + validFiles.push(file); + } + } + + return { validFiles, rejectedFolders }; +} + +function showFolderUploadWarning(count) { + const inputFilesBox = document.querySelector("#input_files_box"); + if (!inputFilesBox) return; + + // Gentle tactile pulse on the dropzone without shaking + const fileCta = inputFilesBox.querySelector(".file-cta"); + if (fileCta) { + fileCta.classList.remove("has-folder-error"); + void fileCta.offsetWidth; + fileCta.classList.add("has-folder-error"); + setTimeout(() => { + fileCta.classList.remove("has-folder-error"); + }, 600); + } + + if (folderAlertTimeout) { + clearTimeout(folderAlertTimeout); + folderAlertTimeout = null; + } + + let tray = inputFilesBox.querySelector(".folder-notice-tray"); + let isNew = false; + + if (!tray) { + isNew = true; + tray = document.createElement("div"); + tray.className = "folder-notice-tray"; + tray.setAttribute("role", "alert"); + tray.setAttribute("aria-live", "polite"); + + const inner = document.createElement("div"); + inner.className = "folder-notice-inner"; + + const bar = document.createElement("div"); + bar.className = "folder-notice-bar"; + + const main = document.createElement("div"); + main.className = "folder-notice-main"; + + const iconSpan = document.createElement("span"); + iconSpan.className = "folder-notice-icon"; + const icon = document.createElement("i"); + icon.className = "fa-solid fa-folder-minus"; + iconSpan.appendChild(icon); + main.appendChild(iconSpan); + + const msg = document.createElement("span"); + msg.className = "folder-notice-msg"; + msg.append("Folders cannot be uploaded directly. Please upload a "); + + const zipKbd = document.createElement("kbd"); + zipKbd.className = "folder-notice-kbd"; + zipKbd.textContent = ".zip"; + msg.appendChild(zipKbd); + + msg.append(" or "); + + const tarKbd = document.createElement("kbd"); + tarKbd.className = "folder-notice-kbd"; + tarKbd.textContent = ".tar.gz"; + msg.appendChild(tarKbd); + + msg.append(" archive instead."); + + const countSpan = document.createElement("span"); + countSpan.className = "folder-notice-count"; + countSpan.textContent = `(Folders skipped: ${count || 1})`; + msg.appendChild(countSpan); + + main.appendChild(msg); + + const dismissBtn = document.createElement("button"); + dismissBtn.type = "button"; + dismissBtn.className = "folder-notice-dismiss"; + dismissBtn.setAttribute("aria-label", "Dismiss notice"); + dismissBtn.setAttribute("title", "Dismiss"); + const dismissIcon = document.createElement("i"); + dismissIcon.className = "fa-solid fa-xmark"; + dismissBtn.appendChild(dismissIcon); + + bar.appendChild(main); + bar.appendChild(dismissBtn); + inner.appendChild(bar); + tray.appendChild(inner); + + inputFilesBox.appendChild(tray); + inputFilesBox.classList.add("has-folder-notice"); + } else { + const countSpan = tray.querySelector(".folder-notice-count"); + if (countSpan && count) { + countSpan.textContent = `(Folders skipped: ${count})`; + } + } + + function dismiss() { + if (folderAlertTimeout) { + clearTimeout(folderAlertTimeout); + folderAlertTimeout = null; + } + if (tray && tray.parentNode) { + tray.classList.remove("is-open"); + setTimeout(() => { + if (tray && tray.parentNode) { + tray.remove(); + } + if (inputFilesBox && !inputFilesBox.querySelector(".folder-notice-tray")) { + inputFilesBox.classList.remove("has-folder-notice"); + } + }, 230); + } + } + + const dismissBtn = tray.querySelector(".folder-notice-dismiss"); + if (dismissBtn) { + dismissBtn.onclick = dismiss; + } + + if (isNew) { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + tray.classList.add("is-open"); + }); + }); + } + + folderAlertTimeout = setTimeout(dismiss, 8000); + tray.onmouseenter = () => { + if (folderAlertTimeout) { + clearTimeout(folderAlertTimeout); + folderAlertTimeout = null; + } + }; + tray.onmouseleave = () => { + folderAlertTimeout = setTimeout(dismiss, 3000); + }; } function dropHandler(event) { disableEvent(event); - // Merge existing files and dropped files, let updateFiles handle dedup - const dataTransfer = new DataTransfer(); - for (const file of [...fileInput.files, ...event.dataTransfer.files]) { - dataTransfer.items.add(file); + const { validFiles, rejectedFolders } = inspectDataTransfer(event.dataTransfer); + + if (rejectedFolders.length > 0) { + showFolderUploadWarning(rejectedFolders.length); } - fileInput.files = dataTransfer.files; - updateFiles(); + if (validFiles.length > 0) { + // Merge existing files and dropped valid files, let updateFiles handle dedup + const dataTransfer = new DataTransfer(); + const existing = fileInput ? fileInput.files : []; + for (const file of [...existing, ...validFiles]) { + dataTransfer.items.add(file); + } + + if (fileInput) { + fileInput.files = dataTransfer.files; + fileInput.dispatchEvent(new Event("change")); + } + } } // Handle drag and drop events const inputFilesBox = document.querySelector("#input_files_box"); -inputFilesBox.addEventListener("dragenter", disableEvent); -inputFilesBox.addEventListener("dragover", disableEvent); -inputFilesBox.addEventListener("drop", dropHandler); +if (inputFilesBox) { + inputFilesBox.addEventListener("dragenter", disableEvent); + inputFilesBox.addEventListener("dragover", disableEvent); + inputFilesBox.addEventListener("drop", dropHandler); +} diff --git a/scancodeio/static/main.css b/scancodeio/static/main.css index f85577d7d1..d6ca714d09 100644 --- a/scancodeio/static/main.css +++ b/scancodeio/static/main.css @@ -331,6 +331,463 @@ button.as-link { border-width: medium; border-style: dashed; } +/* Folder upload rejection notice & dropzone interaction */ +:root { + --folder-notice-bg: #fffdf7; + --folder-notice-border: #fcd34d; + --folder-notice-divider: rgba(217, 119, 6, 0.2); + --folder-notice-accent: #d97706; + --folder-notice-text: #78350f; + --folder-notice-kbd-bg: rgba(217, 119, 6, 0.08); + --folder-notice-kbd-border: rgba(217, 119, 6, 0.22); + --folder-notice-kbd-text: #92400e; + --folder-notice-hover: rgba(120, 53, 15, 0.08); + --folder-notice-glow: rgba(217, 119, 6, 0.18); + --folder-notice-cta-tint: rgba(245, 158, 11, 0.04); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --folder-notice-bg: #1c1f26; + --folder-notice-border: #374151; + --folder-notice-divider: rgba(245, 158, 11, 0.25); + --folder-notice-accent: #fbbf24; + --folder-notice-text: #f3f4f6; + --folder-notice-kbd-bg: rgba(255, 255, 255, 0.08); + --folder-notice-kbd-border: rgba(255, 255, 255, 0.16); + --folder-notice-kbd-text: #fde68a; + --folder-notice-hover: rgba(255, 255, 255, 0.1); + --folder-notice-glow: rgba(245, 158, 11, 0.2); + --folder-notice-cta-tint: rgba(245, 158, 11, 0.06); + } +} + +[data-theme="dark"], +.theme-dark { + --folder-notice-bg: #1c1f26; + --folder-notice-border: #374151; + --folder-notice-divider: rgba(245, 158, 11, 0.25); + --folder-notice-accent: #fbbf24; + --folder-notice-text: #f3f4f6; + --folder-notice-kbd-bg: rgba(255, 255, 255, 0.08); + --folder-notice-kbd-border: rgba(255, 255, 255, 0.16); + --folder-notice-kbd-text: #fde68a; + --folder-notice-hover: rgba(255, 255, 255, 0.1); + --folder-notice-glow: rgba(245, 158, 11, 0.2); + --folder-notice-cta-tint: rgba(245, 158, 11, 0.06); +} + +[data-theme="light"], +.theme-light { + --folder-notice-bg: #fffdf7; + --folder-notice-border: #fcd34d; + --folder-notice-divider: rgba(217, 119, 6, 0.2); + --folder-notice-accent: #d97706; + --folder-notice-text: #78350f; + --folder-notice-kbd-bg: rgba(217, 119, 6, 0.08); + --folder-notice-kbd-border: rgba(217, 119, 6, 0.22); + --folder-notice-kbd-text: #92400e; + --folder-notice-hover: rgba(120, 53, 15, 0.08); + --folder-notice-glow: rgba(217, 119, 6, 0.18); + --folder-notice-cta-tint: rgba(245, 158, 11, 0.04); +} + +#input_files_box { + flex-direction: column; +} + +.file.is-boxed.has-name .file-cta { + border-width: medium; + border-style: dashed; + transition: border-color 220ms cubic-bezier(0.25, 1, 0.5, 1), + background-color 220ms cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 220ms cubic-bezier(0.25, 1, 0.5, 1); +} + +.file.is-boxed.has-name .file-cta.has-folder-error { + border-color: var(--folder-notice-accent) !important; + background-color: var(--folder-notice-cta-tint) !important; + box-shadow: 0 0 0 3px var(--folder-notice-glow) !important; +} + +#input_files_box.has-folder-notice #inputs_file_name { + border-bottom-left-radius: 0 !important; + border-bottom-right-radius: 0 !important; + border-bottom-color: var(--folder-notice-divider) !important; + transition: border-radius 220ms cubic-bezier(0.25, 1, 0.5, 1), + border-bottom-color 220ms ease; +} + +.folder-notice-tray { + display: grid; + grid-template-rows: 0fr; + opacity: 0; + width: 100%; + transition: grid-template-rows 220ms cubic-bezier(0.25, 1, 0.5, 1), + opacity 180ms ease; + will-change: grid-template-rows, opacity; +} + +.folder-notice-tray.is-open { + grid-template-rows: 1fr; + opacity: 1; + transition: grid-template-rows 280ms cubic-bezier(0.16, 1, 0.3, 1), + opacity 200ms ease; +} + +.folder-notice-inner { + overflow: hidden; + min-height: 0; +} + +.folder-notice-bar { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.85rem; + padding: 0.55rem 0.85rem; + font-size: 0.8125rem; + line-height: 1.45; + background-color: var(--folder-notice-bg); + border: 1px solid var(--folder-notice-border); + border-top: none; + border-bottom-left-radius: var(--bulma-file-radius, var(--bulma-radius, 6px)); + border-bottom-right-radius: var(--bulma-file-radius, var(--bulma-radius, 6px)); + color: var(--folder-notice-text); + box-shadow: inset 0 1px 0 var(--folder-notice-divider); +} + +.folder-notice-main { + display: flex; + align-items: flex-start; + gap: 0.75rem; + flex: 1; + min-width: 0; +} + +.folder-notice-icon { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--folder-notice-accent); + font-size: 0.95rem; + line-height: 1; + margin-top: 0.125rem; +} + +.folder-notice-msg { + color: var(--folder-notice-text); +} + +.folder-notice-msg strong { + color: inherit; + font-weight: 600; +} + +.folder-notice-kbd { + display: inline-block; + padding: 0.08em 0.35em; + font-family: var(--bulma-family-monospace, monospace); + font-size: 0.75rem; + font-weight: 600; + line-height: 1.2; + border-radius: 3px; + background-color: var(--folder-notice-kbd-bg); + border: 1px solid var(--folder-notice-kbd-border); + color: var(--folder-notice-kbd-text); + vertical-align: baseline; +} + +.folder-notice-count { + display: inline-block; + margin-left: 0.4rem; + font-size: 0.775rem; + opacity: 0.85; + white-space: nowrap; +} + +.folder-notice-dismiss { + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.35rem; + height: 1.35rem; + padding: 0; + border: none; + background: transparent; + color: var(--folder-notice-text); + opacity: 0.6; + border-radius: 4px; + cursor: pointer; + font-size: 0.875rem; + line-height: 1; + margin-top: -0.05rem; + transition: opacity 140ms ease, background-color 140ms ease, transform 80ms ease; +} + +.folder-notice-dismiss:hover { + opacity: 1; + background-color: var(--folder-notice-hover); +} + +.folder-notice-dismiss:active { + transform: scale(0.9); +} + +#inputs_file_name { + max-height: 180px; + overflow-y: scroll; + scrollbar-gutter: stable; + overflow-x: hidden; + overscroll-behavior: contain; + scroll-behavior: smooth; + scrollbar-width: thin; + scrollbar-color: var(--bulma-border, #dbdbdb) transparent; + padding: 0.35rem 0.5rem; +} + +#inputs_file_name::-webkit-scrollbar { + width: 6px; +} + +#inputs_file_name::-webkit-scrollbar-track { + background: transparent; +} + +#inputs_file_name::-webkit-scrollbar-thumb { + background-color: var(--bulma-border, #dbdbdb); + border-radius: 9999px; +} + +#inputs_file_name::-webkit-scrollbar-thumb:hover { + background-color: var(--bulma-border-hover, #b5b5b5); +} + +.file-item-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + padding: 0.35rem 0.5rem; + border-radius: 6px; + max-height: 48px; + opacity: 1; + transform: translateZ(0) scale(1) translateX(0); + transition: + opacity 180ms cubic-bezier(0.16, 1, 0.3, 1), + transform 180ms cubic-bezier(0.16, 1, 0.3, 1), + max-height 180ms cubic-bezier(0.25, 1, 0.5, 1), + padding 180ms cubic-bezier(0.25, 1, 0.5, 1), + margin 180ms cubic-bezier(0.25, 1, 0.5, 1), + background-color 150ms ease; + overflow: hidden; +} + +.file-item-row:hover { + background-color: rgba(125, 125, 125, 0.08); +} + +.file-item-row.is-deleting { + opacity: 0; + transform: scale(0.96) translateX(12px); + max-height: 0; + padding-top: 0; + padding-bottom: 0; + margin-top: 0; + margin-bottom: 0; + pointer-events: none !important; +} + +.file-list-header { + position: sticky; + top: -0.35rem; + z-index: 5; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.35rem 0.5rem; + margin: -0.35rem -0.5rem 0.4rem -0.5rem; + background-color: var(--bulma-scheme-main-ter, #f5f5f5); + border-bottom: 1px solid var(--bulma-border, #dbdbdb); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + user-select: none; +} + +[data-theme="dark"] .file-list-header, +.theme-dark .file-list-header, +:root:not([data-theme="light"]) .file-list-header { + background-color: rgba(30, 34, 42, 0.95); + border-bottom-color: rgba(255, 255, 255, 0.1); +} + +.file-list-info { + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 0.8rem; + color: var(--bulma-text, #4a4a4a); + min-width: 0; +} + +.file-list-count strong { + color: var(--bulma-text-strong, #363636); +} + +.file-list-dot { + opacity: 0.5; +} + +.file-list-size { + opacity: 0.75; +} + +.file-list-delete-all { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background-color: var(--bulma-danger, #f14668); + color: #ffffff; + border: none; + border-radius: 5px; + padding: 0.2rem 0.55rem; + font-size: 0.75rem; + font-weight: 600; + cursor: pointer; + transition: background-color 150ms ease, transform 100ms ease; + line-height: 1.2; +} + +.file-list-delete-all:hover { + background-color: #e03131; + transform: scale(1.02); +} + +.file-list-delete-all:active { + transform: scale(0.98); +} + +.file-empty-notice { + display: inline-block; + opacity: 0; + animation: fileFadeIn 180ms cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes fileFadeIn { + from { + opacity: 0; + transform: translateY(3px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.file-item-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + flex: 1; + font-size: 0.875rem; +} + +.file-item-delete { + flex-shrink: 0; + color: var(--bulma-text, #4a4a4a); + opacity: 0.6; + padding: 0.15rem 0.35rem; + border: none; + background: transparent; + border-radius: 3px; + cursor: pointer; + transition: opacity 150ms ease, color 150ms ease, transform 100ms ease; + line-height: 1; +} + +.file-item-delete:hover { + opacity: 1; + color: var(--bulma-danger, #f14668); + transform: scale(1.1); +} + +#project-form-columns { + min-height: calc(100vh - 140px); +} + +.project-create-form { + display: flex; + flex-direction: column; + flex-grow: 1; + padding-bottom: 0; +} + +.project-create-form .box { + box-shadow: none; +} + +.project-form-sticky-actions { + position: sticky; + bottom: 1rem; + margin-top: auto; + z-index: 25; + width: 100%; + border-radius: 14px; + padding: 1.15rem 1.25rem 1rem; + box-sizing: border-box; + background-color: var(--bulma-scheme-main, #ffffff); + border: none; + box-shadow: none; +} + +@supports (backdrop-filter: blur(16px)) { + .project-form-sticky-actions { + background-color: color-mix(in srgb, var(--bulma-scheme-main, #ffffff) 88%, transparent); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + } +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) .project-form-sticky-actions { + background-color: #1a1d24; + border: none; + box-shadow: none; + } + + @supports (backdrop-filter: blur(16px)) { + :root:not([data-theme="light"]) .project-form-sticky-actions { + background-color: rgba(26, 29, 36, 0.88); + } + } +} + +[data-theme="dark"] .project-form-sticky-actions, +.theme-dark .project-form-sticky-actions { + background-color: #1a1d24; + border: none; + box-shadow: none; +} + +@supports (backdrop-filter: blur(16px)) { + [data-theme="dark"] .project-form-sticky-actions, + .theme-dark .project-form-sticky-actions { + background-color: rgba(26, 29, 36, 0.88); + } +} + +@media (prefers-reduced-motion: reduce) { + .folder-notice-tray, + .folder-notice-bar, + .file.is-boxed.has-name .file-cta, + #input_files_box.has-folder-notice #inputs_file_name { + transition: none !important; + transform: none !important; + } +} progress.file-upload::before { content: "Files upload: " attr(value) "%"; position: absolute; @@ -394,6 +851,36 @@ th#column-summary { } textarea.is-dynamic { min-height: 70px; + max-height: 180px; + overflow-y: auto; + resize: vertical; +} + +textarea.is-dynamic::-webkit-scrollbar { + width: 6px; +} + +textarea.is-dynamic::-webkit-scrollbar-track { + background: transparent; +} + +textarea.is-dynamic::-webkit-scrollbar-thumb { + background-color: var(--bulma-border, #dbdbdb); + border-radius: 9999px; +} + +textarea.is-dynamic::-webkit-scrollbar-thumb:hover { + background-color: var(--bulma-border-hover, #b5b5b5); +} + +[data-theme="dark"] textarea.is-dynamic::-webkit-scrollbar-thumb, +.theme-dark textarea.is-dynamic::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] textarea.is-dynamic::-webkit-scrollbar-thumb:hover, +.theme-dark textarea.is-dynamic::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.35); } #project-list-header ul#id_is_archived { display: inline-flex; diff --git a/scanpipe/templates/scanpipe/project_form.html b/scanpipe/templates/scanpipe/project_form.html index 9cf97735bd..227bb379ed 100644 --- a/scanpipe/templates/scanpipe/project_form.html +++ b/scanpipe/templates/scanpipe/project_form.html @@ -14,10 +14,10 @@

{% include 'scanpipe/forms/form_errors.html' %} -
-
+
+
-
{% csrf_token %} + {% csrf_token %}
@@ -55,33 +55,35 @@

-
-
- - -
-
- - {{ form.pipeline }} - +
+
+
+ + +
+
+ + {{ form.pipeline }} + +
-
- -
- {{ form.selected_groups }} -
-
-
- Cancel +
+ {{ form.selected_groups }}
-
- + +
+
+ Cancel +
+
+ +