Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cursor/rules/django_javascript_implementation.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ alwaysApply: true
- HTML fragments that may change throughout the page lifecycle should be created as django template files under `components/`
- Any variables within those components should be handled in the Javascript handlers/managers and passed as context objects to the Django view `RenderHTMLFragmentView` which renders the Django template files in `components/`using a context object passed into a Javascript `APIClient` call.
- Never try to render HTML fragments in Javascript.
- Where appropriate, use programmatic DOM construction through the JS DOM APIs instead of HTML template literals in Javascript. All user defined inputs should be passed to the DOM as text strings (e.g. using `textContent`, `document.createTextNode()`, `String()` when setting user defined values/attributes/text).
Original file line number Diff line number Diff line change
Expand Up @@ -629,11 +629,10 @@ def test_get_dataset_files_missing_uuid(self):
assert response.status_code == status.HTTP_404_NOT_FOUND

def test_get_dataset_files_unauthenticated_access(self):
"""Test access without authentication."""
self.client.force_authenticate(user=None)

"""Test access without API key (no forced user on test client)."""
url = reverse("api:datasets-files", kwargs={"pk": self.dataset.uuid})
response = self.client.get(url)
unauthenticated_client = APIClient()
response = unauthenticated_client.get(url)

assert response.status_code == status.HTTP_401_UNAUTHORIZED

Expand Down
13 changes: 1 addition & 12 deletions gateway/sds_gateway/static/css/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -1127,21 +1127,10 @@ body {
border-radius: 0.375rem;
}

#file-tree-table {
#file-tree-root {
margin-bottom: 0;
}

#file-tree-table thead.sticky-top {
position: sticky;
top: 0;
z-index: 2;
background-color: var(--bs-white);
}

#file-tree-table tbody tr:hover {
cursor: pointer;
}

.action-buttons {
display: flex;
gap: 0.5rem;
Expand Down
111 changes: 111 additions & 0 deletions gateway/sds_gateway/static/css/spectrumx_theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,116 @@ textarea:focus-visible {
max-width: 600px;
}

.file-browser-modal {
max-width: none;
width: 100%;
}

/* Modal file picker: compact rows, hidden checkboxes, click-to-select */
.file-browser-modal #file-tree-root {
padding-left: 0;
}

.file-browser-modal .file-browser-row {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
width: 100%;
box-sizing: border-box;
}

.file-browser-modal [role="treeitem"]:focus-visible {
outline: 2px solid #005a9c;
outline-offset: 2px;
}

.file-browser-modal .file-item > .file-browser-row:hover {
background-color: rgba(0, 90, 156, 0.08);
}

.file-browser-modal .file-item.is-selected > .file-browser-row {
background-color: rgba(0, 90, 156, 0.16);
color: #003d6b;
font-weight: 500;
}

.file-browser-modal .file-item.is-selected > .file-browser-row .bi {
color: #005a9c;
}

.file-browser-modal .folder-item > .file-browser-row:hover {
background-color: #e9ecef;
}

.file-browser-modal .folder-item.is-selected > .file-browser-row {
background-color: rgba(0, 90, 156, 0.16);
color: #003d6b;
font-weight: 500;
}

.file-browser-modal .folder-expand-toggle {
display: none;
flex-shrink: 0;
line-height: 1;
color: inherit;
text-decoration: none;
}

.file-browser-modal.folder-selection-mode-active .folder-expand-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
}

.file-browser-modal .folder-expand-icon {
transition: transform 0.15s ease;
}

.file-browser-modal .folder-expand-icon.folder-expand-icon-open {
transform: rotate(90deg);
}

.file-browser-modal .file-item.readonly-row > .file-browser-row {
cursor: not-allowed;
opacity: 0.65;
}

.file-browser-modal .file-item.readonly-row > .file-browser-row:hover {
background-color: transparent;
}

.file-browser-modal .item-content {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
min-width: 0;
justify-content: flex-start;
}

.file-browser-modal .file-browser-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.file-browser-modal .file-checkbox {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

.file-browser-header {
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -450,6 +560,7 @@ textarea:focus-visible {
}

/* CSS for folder expansion/collapse */
.file-browser li[aria-expanded="true"] > ul,
.file-browser [aria-expanded="true"] ~ ul {
display: block;
}
Expand Down
2 changes: 1 addition & 1 deletion gateway/sds_gateway/static/js/core/PageLifecycleManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class PageLifecycleManager {
searchFormId: "files-search-form",
searchButtonId: "search-files",
clearButtonId: "clear-files-search",
tableBodyId: "file-tree-table",
tableBodyId: "file-tree-root",
paginationContainerId: "files-pagination",
type: "files",
formHandler: this.datasetModeManager?.getHandler(),
Expand Down
61 changes: 40 additions & 21 deletions gateway/sds_gateway/static/js/dataset/DatasetCreationHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class DatasetCreationHandler extends BaseManager {
searchFormId: "files-search-form",
searchButtonId: "search-files",
clearButtonId: "clear-files-search",
tableBodyId: "file-tree-table",
tableBodyId: "file-tree-root",
paginationContainerId: "files-pagination",
confirmFileSelectionId: "confirm-file-selection",
type: "files",
Expand Down Expand Up @@ -220,7 +220,7 @@ class DatasetCreationHandler extends BaseManager {
initializeFileBrowserModal() {
// Modal file selection handlers
document.addEventListener("change", (e) => {
if (e.target.matches('#file-tree-table input[name="files"]')) {
if (e.target.matches('#file-tree-root input[name="files"]')) {
this.handleModalFileSelection(e.target)
}
})
Expand Down Expand Up @@ -304,37 +304,56 @@ class DatasetCreationHandler extends BaseManager {
*/
handleModalFileSelection(checkbox) {
const fileId = checkbox.value
const row = checkbox.closest("tr")
const fileData = this.getFileDataFromRow(checkbox)

// Get file data from the row
const fileData = this.getFileDataFromRow(row)
if (!fileData) {
return
}

if (checkbox.checked) {
// Add to modal intermediate selection
this.modalSelectedFiles.add(fileData)
} else {
// Remove from modal intermediate selection
this.modalSelectedFiles.delete(fileData)
const existing = Array.from(this.modalSelectedFiles).find(
(f) => String(f.id) === String(fileId),
)
if (existing) {
this.modalSelectedFiles.delete(existing)
}
}

// Update select all checkbox state
this.updateSelectAllCheckbox()
}

/**
* Get file data from table row
* @param {Element} row - Table row element
* @returns {Object} File data object
* Get file data from a file-tree list item checkbox
* @param {Element} checkbox - File checkbox in the file tree
* @returns {Object|null} File data object
*/
getFileDataFromRow(row) {
const cells = row.querySelectorAll("td")
getFileDataFromRow(checkbox) {
const fileId = checkbox.value
const fromTree =
this.filesSearchHandler?.getFileSelectionDataById?.(fileId)
if (fromTree) {
return fromTree
}

const listItem = checkbox.closest(".file-item")
if (!listItem) {
return null
}

return {
id: row.querySelector('input[name="files"]').value,
name: cells[1]?.textContent?.trim() || "",
media_type: cells[2]?.textContent?.trim() || "",
relative_path: cells[3]?.textContent?.trim() || "",
size: cells[4]?.textContent?.trim() || "",
created_at: cells[5]?.textContent?.trim() || "",
id: fileId,
name:
listItem
.querySelector(".file-browser-name")
?.textContent?.trim() || "",
media_type: "",
relative_path: "",
size: "",
created_at: "",
}
}

Expand All @@ -344,7 +363,7 @@ class DatasetCreationHandler extends BaseManager {
*/
handleSelectAllFiles(checked) {
const checkboxes = document.querySelectorAll(
'#file-tree-table input[name="files"]',
'#file-tree-root input[name="files"]',
Comment thread
klpoland marked this conversation as resolved.
)
for (const checkbox of checkboxes) {
checkbox.checked = checked
Expand All @@ -360,7 +379,7 @@ class DatasetCreationHandler extends BaseManager {
"select-all-files-checkbox",
)
const allCheckboxes = document.querySelectorAll(
'#file-tree-table input[name="files"]',
'#file-tree-root input[name="files"]',
)

if (selectAllCheckbox && allCheckboxes.length > 0) {
Expand Down Expand Up @@ -432,7 +451,7 @@ class DatasetCreationHandler extends BaseManager {

// Uncheck all checkboxes in modal if it's open
const checkboxes = document.querySelectorAll(
'#file-tree-table input[name="files"]',
'#file-tree-root input[name="files"]',
)
for (const checkbox of checkboxes) {
checkbox.checked = false
Expand Down
21 changes: 19 additions & 2 deletions gateway/sds_gateway/static/js/dataset/DatasetEditingHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class DatasetEditingHandler extends BaseManager {
searchFormId: "files-search-form",
searchButtonId: "search-files",
clearButtonId: "clear-files-search",
tableBodyId: "file-tree-table",
tableBodyId: "file-tree-root",
paginationContainerId: "files-pagination",
confirmFileSelectionId: "confirm-file-selection",
type: "files",
Expand All @@ -87,6 +87,23 @@ class DatasetEditingHandler extends BaseManager {
this.initialFiles,
)
}

const datasetForm = document.getElementById("datasetForm")
if (datasetForm && !datasetForm.dataset.enterSubmitGuardBound) {
datasetForm.dataset.enterSubmitGuardBound = "true"
datasetForm.addEventListener("submit", (e) => {
e.preventDefault()
})
datasetForm.addEventListener("keypress", (e) => {
if (e.key !== "Enter") {
return
}
Comment thread
klpoland marked this conversation as resolved.
if (e.target instanceof HTMLTextAreaElement) {
return
}
e.preventDefault()
})
}
}

/**
Expand Down Expand Up @@ -562,7 +579,7 @@ class DatasetEditingHandler extends BaseManager {

// Also mark in the search results table if visible
const searchRow = document.querySelector(
`#file-tree-table tr[data-file-id="${fileId}"]`,
`#file-tree-root li[data-file-id="${fileId}"]`,
)
if (searchRow) {
searchRow.classList.add("marked-for-removal")
Expand Down
Loading
Loading