Add an expandable metadata grid

This commit is contained in:
Kylian Schmidt
2025-07-29 10:31:35 +02:00
parent f3adbe49fa
commit bcb083bbb9
10 changed files with 430 additions and 269 deletions
+110 -134
View File
@@ -1,203 +1,179 @@
/* ========================================
FOLDER METADATA DISPLAY STYLES
FOLDER METADATA STYLES
======================================== */
/* Folder Metadata Container */
.folder-metadata-container {
margin: 2rem 0;
padding: 1.5rem;
background: var(--card-background);
border: 1px solid var(--border-color);
margin: 1rem 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background: var(--card-bg);
border: 1px solid var(--border-color);
overflow: hidden;
}
.folder-metadata-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 2px solid var(--border-color);
}
.folder-metadata-title {
margin: 0;
color: var(--text-color);
font-size: 1.25rem;
font-weight: 600;
}
.folder-metadata-edit-btn {
background: var(--accent-color);
color: white;
/* Folder Metadata Toggle Button */
.folder-metadata-toggle {
width: 100%;
padding: 0.75rem 1rem;
background: var(--card-bg);
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: space-between;
transition: background-color 0.2s ease;
font-size: 0.95rem;
color: var(--text-color);
position: relative;
z-index: 10;
outline: none;
}
.folder-metadata-edit-btn:hover {
background: var(--accent-color-dark);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
.folder-metadata-toggle:hover {
background: var(--button-hover);
color: white;
}
.folder-metadata-edit-btn:active {
transform: translateY(0);
.folder-metadata-toggle:focus {
outline: 2px solid var(--link-color);
outline-offset: 2px;
}
.folder-metadata-icon {
margin-right: 0.5rem;
}
.folder-metadata-label {
flex: 1;
text-align: left;
font-weight: 500;
}
.folder-metadata-arrow {
transition: transform 0.2s ease;
font-size: 0.8rem;
}
.folder-metadata-container.expanded .folder-metadata-arrow {
transform: rotate(180deg);
}
/* Folder Metadata Content - HIDDEN BY DEFAULT */
.folder-metadata-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
background: var(--bg-color);
opacity: 0;
display: none; /* Force hide initially */
}
/* Show content when expanded */
.folder-metadata-container.expanded .folder-metadata-content {
max-height: 1000px;
border-top: 1px solid var(--border-color);
opacity: 1;
display: block; /* Show when expanded */
}
/* Folder Metadata Grid */
.folder-metadata-grid {
padding: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.5rem 1rem;
}
/* Folder Metadata Items */
.folder-metadata-item {
display: flex;
flex-direction: column;
background: var(--background);
border: 1px solid var(--border-color-light);
border-radius: 6px;
padding: 1rem;
transition: all 0.2s ease;
}
.folder-metadata-item:hover {
background: var(--hover-background);
border-color: var(--accent-color);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
align-items: flex-start;
gap: 0.5rem;
padding: 0.25rem 0;
}
.folder-metadata-key {
font-weight: 600;
color: var(--accent-color);
margin-bottom: 0.5rem;
font-size: 0.95rem;
text-transform: capitalize;
border-bottom: 1px solid var(--border-color-light);
padding-bottom: 0.25rem;
color: var(--link-color);
white-space: nowrap;
min-width: fit-content;
}
.folder-metadata-value {
color: var(--text-color);
line-height: 1.5;
word-break: break-word;
flex: 1;
}
.folder-metadata-value a {
color: var(--accent-color);
text-decoration: none;
border-bottom: 1px dotted var(--accent-color);
transition: all 0.2s ease;
}
.folder-metadata-value a:hover {
color: var(--accent-color-dark);
border-bottom-style: solid;
}
/* Tags for list items */
.folder-metadata-list {
margin: 0;
padding-left: 1.5rem;
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.folder-metadata-list li {
margin-bottom: 0.25rem;
.folder-metadata-tag {
background: var(--link-color);
color: white;
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
white-space: nowrap;
}
/* Nested metadata */
.folder-metadata-nested {
background: var(--background-dark);
padding: 0.75rem;
background: var(--card-bg);
padding: 0.5rem;
border-radius: 4px;
border-left: 3px solid var(--accent-color);
border-left: 3px solid var(--link-color);
}
.folder-metadata-nested-item {
margin-bottom: 0.5rem;
}
.folder-metadata-nested-item:last-child {
margin-bottom: 0;
}
.folder-metadata-nested-item strong {
color: var(--accent-color);
}
.folder-metadata-long-text {
display: inline;
margin: 0.25rem 0;
font-size: 0.9rem;
}
/* Long text handling */
.folder-metadata-expand {
background: none;
border: none;
color: var(--accent-color);
color: var(--link-color);
cursor: pointer;
text-decoration: underline;
font-size: 0.9rem;
margin-left: 0.5rem;
padding: 0;
margin-left: 0.5rem;
font-size: 0.85rem;
}
.folder-metadata-expand:hover {
color: var(--accent-color-dark);
color: var(--link-hover);
}
.folder-metadata-full-text {
display: none;
/* Links */
.folder-metadata-value a {
color: var(--link-color);
text-decoration: none;
}
/* Responsive adjustments */
.folder-metadata-value a:hover {
color: var(--link-hover);
text-decoration: underline;
}
/* Responsive design */
@media (max-width: 768px) {
.folder-metadata-container {
margin: 1rem 0;
padding: 1rem;
}
.folder-metadata-grid {
grid-template-columns: 1fr;
gap: 0.75rem;
gap: 0.5rem;
}
.folder-metadata-header {
.folder-metadata-item {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
gap: 0.25rem;
}
.folder-metadata-edit-btn {
align-self: flex-end;
}
}
/* Dark theme adjustments */
@media (prefers-color-scheme: dark) {
.folder-metadata-container {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.folder-metadata-item:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
}
/* Animation for expanding text */
.folder-metadata-full-text.show {
display: inline;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
.folder-metadata-key {
white-space: normal;
}
}
+2 -2
View File
@@ -3,8 +3,8 @@
======================================== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 1.2rem;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.8rem;
padding: 1rem 0;
}
+1
View File
@@ -16,6 +16,7 @@
@import url('./stats.css');
@import url('./comparison.css');
@import url('./metadata.css');
@import url('./metadata-section.css');
@import url('./folder-metadata.css');
@import url('./export.css');
+163
View File
@@ -0,0 +1,163 @@
/* ========================================
METADATA SECTION STYLES
======================================== */
/* Metadata Section Container */
.metadata-section {
margin: 1rem 0;
border-radius: 8px;
background: var(--card-bg);
border: 1px solid var(--border-color);
overflow: hidden;
}
/* Toggle Button */
.metadata-toggle-btn {
width: 100%;
padding: 0.75rem 1rem;
background: var(--card-bg);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
transition: background-color 0.2s ease;
font-size: 0.95rem;
color: var(--text-color);
position: relative;
z-index: 10;
}
.metadata-toggle-btn:hover {
background: var(--button-hover);
color: white;
}
.metadata-toggle-btn:focus {
outline: 2px solid var(--link-color);
outline-offset: 2px;
}
.metadata-icon {
margin-right: 0.5rem;
}
.metadata-label {
flex: 1;
text-align: left;
font-weight: 500;
}
.metadata-arrow {
transition: transform 0.2s ease;
font-size: 0.8rem;
}
/* Content Area - Hidden by default */
.metadata-content {
background: var(--bg-color);
border-top: 1px solid var(--border-color);
display: none; /* Hidden by default */
}
/* Grid Layout */
.metadata-grid {
padding: 1rem;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.5rem 1rem;
}
/* Metadata Items */
.metadata-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.25rem 0;
}
.metadata-key {
font-weight: 600;
color: var(--link-color);
white-space: nowrap;
min-width: fit-content;
}
.metadata-value {
color: var(--text-color);
word-break: break-word;
flex: 1;
}
/* Tags for list items */
.metadata-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.metadata-tag {
background: var(--link-color);
color: white;
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
white-space: nowrap;
}
/* Nested metadata */
.metadata-nested {
background: var(--card-bg);
padding: 0.5rem;
border-radius: 4px;
border-left: 3px solid var(--link-color);
}
.metadata-nested-item {
margin: 0.25rem 0;
font-size: 0.9rem;
}
/* Long text handling */
.metadata-expand {
background: none;
border: none;
color: var(--link-color);
cursor: pointer;
text-decoration: underline;
padding: 0;
margin-left: 0.5rem;
font-size: 0.85rem;
}
.metadata-expand:hover {
color: var(--link-hover);
}
/* Links */
.metadata-value a {
color: var(--link-color);
text-decoration: none;
}
.metadata-value a:hover {
color: var(--link-hover);
text-decoration: underline;
}
/* Responsive design */
@media (max-width: 768px) {
.metadata-grid {
grid-template-columns: 1fr;
gap: 0.5rem;
}
.metadata-item {
flex-direction: column;
gap: 0.25rem;
}
.metadata-key {
white-space: normal;
}
}
+58 -46
View File
@@ -1,69 +1,81 @@
/**
* Folder Metadata functionality for Gallery
*
* Handles interactions with folder-level metadata display
* Handles folder metadata dropdown display and interaction
*/
// Function to toggle expansion of long metadata text
function toggleMetadataText(button) {
// Define function immediately (not waiting for DOM)
window.toggleFolderMetadata = function() {
console.log('toggleFolderMetadata called');
const container = document.querySelector('.folder-metadata-container');
const content = document.getElementById('folderMetadataContent');
if (!container) {
console.log('No metadata container found');
return;
}
if (!content) {
console.log('No metadata content found');
return;
}
const isExpanded = container.classList.contains('expanded');
console.log('Current state - expanded:', isExpanded);
if (isExpanded) {
// Collapse
container.classList.remove('expanded');
content.style.display = 'none';
console.log('Collapsed dropdown');
} else {
// Expand
container.classList.add('expanded');
content.style.display = 'block';
console.log('Expanded dropdown');
}
// Save state
localStorage.setItem('folderMetadataExpanded', (!isExpanded).toString());
};
// Also define as regular function for alternative access
function toggleFolderMetadata() {
window.toggleFolderMetadata();
}
// Toggle long text display
window.toggleMetadataText = function(button) {
const longText = button.previousElementSibling;
const fullText = button.nextElementSibling;
if (fullText.style.display === 'none') {
// Show full text
longText.style.display = 'none';
fullText.style.display = 'inline';
fullText.classList.add('show');
button.textContent = 'Show less';
} else {
// Show truncated text
longText.style.display = 'inline';
fullText.style.display = 'none';
fullText.classList.remove('show');
button.textContent = 'Show more';
}
}
};
// Function to handle folder metadata editing (placeholder for future implementation)
function editFolderMetadata() {
// For now, just show an alert that this feature is coming soon
alert('Folder metadata editing functionality is coming soon!');
// Future implementation will:
// 1. Open an edit modal with form fields for each metadata key
// 2. Allow adding/removing metadata fields
// 3. Validate the input
// 4. Send updates to the server
// 5. Refresh the page or update the display dynamically
}
// Initialize folder metadata functionality when DOM is loaded
// Initialize folder metadata on page load
document.addEventListener('DOMContentLoaded', function() {
console.log('Folder metadata functionality initialized');
console.log('Folder metadata script loaded');
// Add keyboard shortcuts for metadata editing (future feature)
document.addEventListener('keydown', function(e) {
// Ctrl+M for metadata editing
if (e.ctrlKey && e.key === 'm') {
e.preventDefault();
const editBtn = document.querySelector('.folder-metadata-edit-btn');
if (editBtn) {
editFolderMetadata();
}
// Make sure all containers start collapsed
const containers = document.querySelectorAll('.folder-metadata-container');
console.log('Found', containers.length, 'metadata containers');
containers.forEach(container => {
const content = container.querySelector('.folder-metadata-content');
if (content) {
// Force initial hidden state
container.classList.remove('expanded');
content.style.display = 'none';
console.log('Initialized container as collapsed');
}
});
// Add accessibility improvements
const metadataItems = document.querySelectorAll('.folder-metadata-item');
metadataItems.forEach(item => {
item.setAttribute('tabindex', '0');
item.setAttribute('role', 'listitem');
});
// Add ARIA labels for better accessibility
const metadataContainer = document.querySelector('.folder-metadata-container');
if (metadataContainer) {
metadataContainer.setAttribute('role', 'region');
metadataContainer.setAttribute('aria-label', 'Folder metadata information');
}
});
+74
View File
@@ -0,0 +1,74 @@
/**
* Simple Metadata Section Toggle
*
* Handles showing/hiding the metadata grid with a simple button
*/
// Global function to toggle metadata section visibility
function toggleMetadataSection() {
console.log('toggleMetadataSection called');
const content = document.getElementById('metadataContent');
const arrow = document.getElementById('metadataArrow');
if (!content) {
console.log('No metadata content found');
return;
}
const isVisible = content.style.display !== 'none';
if (isVisible) {
// Hide the content
content.style.display = 'none';
if (arrow) arrow.textContent = '▼';
console.log('Metadata hidden');
} else {
// Show the content
content.style.display = 'block';
if (arrow) arrow.textContent = '▲';
console.log('Metadata shown');
}
// Save state to localStorage
localStorage.setItem('metadataVisible', (!isVisible).toString());
}
// Function to expand long text
function expandText(button) {
const longText = button.previousElementSibling;
const fullText = button.nextElementSibling;
if (fullText.style.display === 'none') {
longText.style.display = 'none';
fullText.style.display = 'inline';
button.textContent = 'Show less';
} else {
longText.style.display = 'inline';
fullText.style.display = 'none';
button.textContent = 'Show more';
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
console.log('Metadata section script loaded');
const content = document.getElementById('metadataContent');
if (content) {
// Check if user previously had it expanded
const wasVisible = localStorage.getItem('metadataVisible') === 'true';
if (wasVisible) {
content.style.display = 'block';
const arrow = document.getElementById('metadataArrow');
if (arrow) arrow.textContent = '▲';
} else {
content.style.display = 'none';
const arrow = document.getElementById('metadataArrow');
if (arrow) arrow.textContent = '▼';
}
console.log('Metadata section initialized, visible:', wasVisible);
}
});
-28
View File
@@ -1,28 +0,0 @@
# Example folder metadata file
# This file should be named "meta.yaml" in a folder containing plots
# Folder-level metadata that applies to all plots in this folder
title: "Analysis Results"
description: "ttbar"
experiment: "CMS"
dataset: "Run2_2016_nano_v9"
analysis_type: "ttbar_analysis"
# Analysis parameters that apply to all plots in this folder
parameters:
luminosity: "35.9 fb^-1"
center_of_mass_energy: "13 TeV"
selection: "baseline"
# Tags for categorization
tags:
- "ttbar"
- "run2"
- "cms"
- "analysis"
# Custom styling or display options
display:
highlight: true
category: "primary_results"
order_priority: 1
-19
View File
@@ -1,19 +0,0 @@
{
"title": "Sample Research Project",
"description": "This folder contains plots and analysis results for our sample research project studying data visualization techniques.",
"author": "Research Team",
"created": "2025-01-15",
"project_id": "PROJ-2025-001",
"status": "Active",
"tags": ["data-science", "visualization", "research"],
"collaborators": ["Alice Smith", "Bob Johnson", "Carol Davis"],
"funding": {
"agency": "National Science Foundation",
"grant_number": "NSF-2025-12345",
"amount": "$150,000"
},
"contact": "research-team@example.com",
"repository": "https://github.com/example/sample-project",
"documentation": "https://docs.example.com/sample-project",
"notes": "This is a long note that demonstrates how the system handles lengthy text content. It should be truncated in the display and allow users to expand it to see the full content. This helps keep the interface clean while still providing access to detailed information when needed."
}
-22
View File
@@ -1,22 +0,0 @@
{
"title": "Specific Plot Analysis",
"description": "This metadata overrides folder metadata for this specific plot",
"plot_type": "histogram",
"variables": {
"x_axis": "invariant_mass",
"y_axis": "events",
"units": "GeV"
},
"selection": "signal_region",
"statistics": {
"entries": 125000,
"mean": 125.3,
"std_dev": 2.1
},
"tags": ["signal", "mass_peak", "important"],
"display": {
"highlight": true,
"featured": true,
"order_priority": 0
}
}
+22 -18
View File
@@ -34,36 +34,37 @@
<!-- Folder Tree -->
<div class="folder-tree" id="folderTree"></div>
<!-- Folder Metadata Dropdown -->
<!-- Folder Metadata Section -->
{% if folder_metadata %}
<div class="folder-metadata-container">
<button class="folder-metadata-toggle" onclick="toggleFolderMetadata()">
<span class="folder-metadata-icon">📋</span>
<span class="folder-metadata-label">Folder Information</span>
<span class="folder-metadata-arrow"></span>
<div class="metadata-section">
<button class="metadata-toggle-btn" onclick="toggleMetadataSection()">
<span class="metadata-icon">📋</span>
<span class="metadata-label">Folder Information</span>
<span class="metadata-arrow" id="metadataArrow"></span>
</button>
<div class="folder-metadata-content" id="folderMetadataContent">
<div class="folder-metadata-grid">
<div class="metadata-content" id="metadataContent" style="display: none;">
<div class="metadata-grid">
{% for key, value in folder_metadata.items() %}
<div class="folder-metadata-item">
<span class="folder-metadata-key">{{ key }}:</span>
<span class="folder-metadata-value">
<div class="metadata-item">
<span class="metadata-key">{{ key }}:</span>
<span class="metadata-value">
{% if value is string and (value.startswith('http://') or value.startswith('https://')) %}
<a href="{{ value }}" target="_blank" rel="noopener noreferrer">{{ value }}</a>
{% elif value is string and value|length > 100 %}
<span class="folder-metadata-long-text">{{ value[:100] }}...</span>
<button class="folder-metadata-expand" onclick="toggleMetadataText(this)">Show more</button>
<span class="folder-metadata-full-text" style="display: none;">{{ value }}</span>
<span class="metadata-long-text">{{ value[:100] }}...</span>
<button class="metadata-expand" onclick="expandText(this)">Show more</button>
<span class="metadata-full-text" style="display: none;">{{ value }}</span>
{% elif value is iterable and value is not string and value is not mapping %}
<div class="folder-metadata-list">
<div class="metadata-list">
{% for item in value %}
<span class="folder-metadata-tag">{{ item }}</span>
<span class="metadata-tag">{{ item }}</span>
{% endfor %}
</div>
{% elif value is mapping %}
<div class="folder-metadata-nested">
<div class="metadata-nested">
{% for subkey, subvalue in value.items() %}
<div class="folder-metadata-nested-item">
<div class="metadata-nested-item">
<strong>{{ subkey }}:</strong> {{ subvalue }}
</div>
{% endfor %}
@@ -309,6 +310,9 @@
<!-- Metadata Popup Script -->
<script src="{{ assets_path }}/js/metadata-popup.js"></script>
<!-- Metadata Section Script -->
<script src="{{ assets_path }}/js/metadata-section.js"></script>
<!-- Folder Metadata Script -->
<script src="{{ assets_path }}/js/folder-metadata.js"></script>