From 2b56266fa80f25e9cbfb9406e13c81cd57138259 Mon Sep 17 00:00:00 2001 From: Hayden Date: Tue, 19 Nov 2024 11:21:36 -0600 Subject: [PATCH 1/2] correct implementation for issue43. I forgot to pull main when I started this issue. --- web/js/manifestStorage.js | 206 ++++++++++++++++++++++++++++++++++++-- web/styles.css | 94 +++++++++++++++++ web/tools.html | 109 ++++++++++++++++++-- 3 files changed, 393 insertions(+), 16 deletions(-) diff --git a/web/js/manifestStorage.js b/web/js/manifestStorage.js index ef0593e..f0322bc 100644 --- a/web/js/manifestStorage.js +++ b/web/js/manifestStorage.js @@ -1,21 +1,211 @@ const MANIFEST_LINKS_KEY ='storedManifestLinks'; -/** - * Save the given manifest link to local storage. - */ + + // Save the given manifest link to local storage. + export function storeManifestLink(manifestLink) { let manifestLinks = getStoredManifestLinks(); if (!manifestLinks.includes(manifestLink)) { manifestLinks.push(manifestLink); - } - // save updated links array to local storage - localStorage.setItem(MANIFEST_LINKS_KEY, JSON.stringify(manifestLinks)); + // save updated links array to local storage + localStorage.setItem(MANIFEST_LINKS_KEY, JSON.stringify(manifestLinks)); + + // refresh the manifest cards display + renderManifestCards(); + } } export function getStoredManifestLinks() { - // get the stored links from local storage, or return an empty array if none are found const manifestLinks = localStorage.getItem(MANIFEST_LINKS_KEY); return manifestLinks ? JSON.parse(manifestLinks) : []; -} \ No newline at end of file +} + +async function fetchManifestDetails(uri) { + try { + const response = await fetch(uri); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + // Extract title from various possible manifest structures + let title = 'Untitled Manifest'; + if (data.label) { + if (typeof data.label === 'string') { + title = data.label; + } + else if (data.label.en && Array.isArray(data.label.en)) { + title = data.label.en[0]; + } + else if (Array.isArray(data.label)) { + title = data.label[0]; + } + else if (typeof data.label === 'object') { + // This handles IIIF 3.0 language map format + const firstKey = Object.keys(data.label)[0]; + if (firstKey && Array.isArray(data.label[firstKey])) { + title = data.label[firstKey][0]; + } + } + } + + // Extract thumbnail URL + let thumbnail = null; + if (data.thumbnail) { + if (typeof data.thumbnail === 'string') { + thumbnail = data.thumbnail; + } + else if (Array.isArray(data.thumbnail)) { + thumbnail = data.thumbnail[0].id || data.thumbnail[0]; + } + else if (data.thumbnail.id) { + thumbnail = data.thumbnail.id; + } + else if (data.thumbnail['@id']) { + thumbnail = data.thumbnail['@id']; + } + } + + return { + title: title, + itemCount: data.items?.length || 0, + thumbnail: thumbnail, + uri: uri + }; + } catch (error) { + console.error('Error fetching manifest:', error); + return { + title: 'Error Loading Manifest', + itemCount: 0, + thumbnail: null, + uri: uri + }; + } +} + + + + function createManifestCard(details) { + const card = document.createElement('div'); + card.className = 'manifest-card'; + + const thumbnailHtml = details.thumbnail + ? `${details.title}` + : `
No Image Available
`; + + card.innerHTML = ` +
+ ${thumbnailHtml} +
+
+

${details.title}

+

Items: ${details.itemCount}

+
+ View Manifest + +
+
+ `; + + // Add click handler for the Load button + const loadBtn = card.querySelector('.manifest-load-btn'); + if (loadBtn) { + loadBtn.addEventListener('click', (e) => { + const uri = e.target.dataset.uri; + const manifestUrlInput = document.getElementById('manifestUrl'); + if (manifestUrlInput) { + manifestUrlInput.value = uri; + const loadButton = document.getElementById('loadManifest'); + if (loadButton) { + loadButton.click(); + } + } + }); + } + + return card; +} + + + export async function renderManifestCards() { + const manifestContainer = document.getElementById('stored_manifest_links'); + if (!manifestContainer) { + console.error("Manifest container not found."); + return; + } + + //Clear previous content + manifestContainer.innerHTML = ''; + + const storedManifests = getStoredManifestLinks(); + + if (storedManifests.length === 0) { + manifestContainer.innerHTML = '

No stored manifest links.

'; + return; + } + + const loadingMessage = document.createElement('p'); + loadingMessage.textContent = 'Loading manifests...'; + loadingMessage.className = 'loading-message'; + manifestContainer.appendChild(loadingMessage); + + const cardsContainer = document.createElement('div'); + cardsContainer.className = 'manifest-cards-grid'; + + try { + // Fetch all manifest details concurrently + const manifestDetails = await Promise.all( + storedManifests.map(uri => fetchManifestDetails(uri)) + ); + + loadingMessage.remove(); + + // Create and append all cards + manifestDetails.forEach(details => { + const card = createManifestCard(details); + cardsContainer.appendChild(card); + }); + + manifestContainer.appendChild(cardsContainer); + } catch (error) { + console.error('Error rendering manifest cards:', error); + manifestContainer.innerHTML = '

Error loading manifests. Please try again.

'; + } +} + + // Toggle the dropdown visibility and render cards + + export function toggleDropdown() { + const manifestContainer = document.getElementById('stored_manifest_links'); + const dropdownArrow = document.getElementById('dropdownArrow'); + + if (!manifestContainer || !dropdownArrow) { + console.error("Required elements not found"); + return; + } + + if (manifestContainer.style.display === 'none' || manifestContainer.style.display === '') { + manifestContainer.style.display = 'block'; + dropdownArrow.textContent = '▲'; + renderManifestCards(); // This calls the render function when opening the dropdown + } else { + manifestContainer.style.display = 'none'; + dropdownArrow.textContent = '▼'; + } + } + + + //Initialize event listeners when the module loads + document.addEventListener('DOMContentLoaded', () => { + const dropdownLabel = document.getElementById('dropdownLabel'); + const dropdownArrow = document.getElementById('dropdownArrow') + + if (dropdownLabel) { + dropdownLabel.addEventListener('click', toggleDropdown); + } + if (dropdownArrow) { + dropdownArrow.addEventListener('click', toggleDropdown); + } + }); \ No newline at end of file diff --git a/web/styles.css b/web/styles.css index e4956d7..a166cf2 100644 --- a/web/styles.css +++ b/web/styles.css @@ -305,4 +305,98 @@ footer::before{ font-weight: bold; color: #3498db; font-size: 16px; +} + +.manifest-cards-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 20px; + padding: 20px; +} + +.manifest-card { + border: 1px solid #ddd; + border-radius: 8px; + overflow: hidden; + background: white; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.manifest-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +.manifest-card-image { + height: 200px; + background: #f5f5f5; + position: relative; +} + +.manifest-card-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.placeholder-image { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: #eee; + color: #666; + font-size: 14px; +} + +.manifest-card-content { + padding: 15px; +} + +.manifest-title { + margin: 0 0 10px 0; + font-size: 16px; + font-weight: bold; + color: #333; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.manifest-items { + margin: 0 0 10px 0; + font-size: 14px; + color: #666; +} + +.manifest-link { + display: inline-block; + color: #3370f6; + text-decoration: none; + font-size: 14px; +} + +.manifest-link:hover { + text-decoration: underline; +} + +.no-manifests { + text-align: center; + padding: 20px; + color: #666; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .manifest-cards-grid { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; + padding: 15px; + } + + .manifest-card-image { + height: 150px; + } } \ No newline at end of file diff --git a/web/tools.html b/web/tools.html index 67447f3..2733224 100644 --- a/web/tools.html +++ b/web/tools.html @@ -4,8 +4,6 @@ Tools | Rerum Playground - - @@ -49,7 +47,13 @@ - +
@@ -80,11 +84,7 @@

Tools Available in Rerum Playground

Load Manifest
- -
-
-
-
Loading Manifest...
+
@@ -102,5 +102,98 @@

Tools

+ + + + + + \ No newline at end of file From 780cf3c180cd04548260701823c173a60155d138 Mon Sep 17 00:00:00 2001 From: Hayden Date: Tue, 19 Nov 2024 14:14:03 -0600 Subject: [PATCH 2/2] fixed card styling, seperated js code to js files. --- web/js/manifestStorage.js | 43 +++++++++- web/js/tools.js | 173 +++++++------------------------------- web/styles.css | 139 ++++++++++++++++++++++-------- web/tools.html | 74 ---------------- 4 files changed, 173 insertions(+), 256 deletions(-) diff --git a/web/js/manifestStorage.js b/web/js/manifestStorage.js index f0322bc..89258c4 100644 --- a/web/js/manifestStorage.js +++ b/web/js/manifestStorage.js @@ -208,4 +208,45 @@ async function fetchManifestDetails(uri) { if (dropdownArrow) { dropdownArrow.addEventListener('click', toggleDropdown); } - }); \ No newline at end of file + }); + + export async function loadManifest(url) { + const manifestMessage = document.getElementById('manifestMessage'); + const loadMessage = document.getElementById('loadMessage'); + const manifestLabelField = document.getElementById('manifestLabelField'); + + if (!url) { + manifestMessage.textContent = 'Please enter a URL.'; + manifestMessage.style.color = 'red'; + return; + } + + manifestMessage.textContent = 'Loading...'; + manifestMessage.style.color = 'black'; + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const loadedManifest = await response.json(); + + manifestMessage.textContent = 'Manifest loaded successfully!'; + manifestMessage.style.color = 'green'; + + // Store the manifest link + await storeManifestLink(url); + + // Display manifest metadata + const manifestLabel = loadedManifest.label?.en?.[0] || 'Untitled'; + const manifestType = loadedManifest.type || 'Unknown Type'; + const manifestItemCount = loadedManifest.items?.length || 0; + + loadMessage.innerHTML = "Current Object:"; + manifestLabelField.innerHTML = `Name: ${manifestLabel}      Type: ${manifestType}      Number of Items: ${manifestItemCount}`; + } catch (error) { + manifestMessage.textContent = "Failed to load manifest. Please check the URL and try again."; + manifestMessage.style.color = "red"; + console.error('Error:', error); + } + } \ No newline at end of file diff --git a/web/js/tools.js b/web/js/tools.js index 0c940ae..68f1815 100644 --- a/web/js/tools.js +++ b/web/js/tools.js @@ -1,5 +1,5 @@ // Import the function for storing manifest links -import { storeManifestLink, getStoredManifestLinks } from './manifestStorage.js'; +import { toggleDropdown, loadManifest, renderManifestCards } from './manifestStorage.js'; // Playground scripting utilities. Will be available as github CDN. import { default as UTILS } from 'https://centerfordigitalhumanities.github.io/rerum-playground/web/js/utilities.js' @@ -152,39 +152,6 @@ function renderTools() { console.log("rendered tool order:", sortedTools.map(tool => tool.label)); } -/** - * Render stored manifest links. - */ -function renderStoredManifests() { - const manifestContainer = document.getElementById('stored_manifest_links'); - const storedManifests = getStoredManifestLinks(); - - if (!manifestContainer) { - console.error("Manifest set container not found."); - return; - } - - manifestContainer.innerHTML = ''; - - if (storedManifests.length === 0) { - manifestContainer.innerHTML = '

No stored manifest links.

'; - return; - } - - storedManifests.forEach(manifestLink => { - const manifestHTML = ` - -

${manifestLink}

-
- `; - manifestContainer.innerHTML += manifestHTML; - }); -} - -window.onload = function() { - renderStoredManifests(); -}; - /** * Handle tool click event to manage recently used logic and allow default navigation */ @@ -213,122 +180,40 @@ window.updateToolOrder = function(toolLabel) { } } -document.addEventListener('DOMContentLoaded', () => { -/** -* These are promises so we can control the chaining how we like, if necessary. -*/ +// Initialize everything when DOM is loaded +document.addEventListener('DOMContentLoaded', function() { try { - initializeInterfaces(PLAYGROUND.INTERFACES) - initializeTechnologies(PLAYGROUND.TECHNOLOGIES) + initializeInterfaces(PLAYGROUND.INTERFACES); + initializeTechnologies(PLAYGROUND.TECHNOLOGIES); renderTools(); - renderStoredManifests(); - } catch (err) { - console.error("Error initializing the playground: ", err); - } -}); -// Wait until the DOM is fully loaded -document.addEventListener('DOMContentLoaded', function() { - const manifestUrl = document.getElementById('manifestUrl'); - const loadManifest = document.getElementById('loadManifest'); - const manifestMessage = document.getElementById('manifestMessage'); - const loadMessage = document.getElementById("loadMessage"); - const manifestLabelField = document.getElementById("manifestLabelField"); - const loadingOverlay = document.querySelector('.loading-overlay'); + // Set up manifest-related event listeners + const dropdownLabel = document.getElementById('dropdownLabel'); + const dropdownArrow = document.getElementById('dropdownArrow'); + const manifestUrl = document.getElementById('manifestUrl'); + const loadManifestBtn = document.getElementById('loadManifest'); - // Function to show loading overlay - function showLoading() { - loadingOverlay.style.display = 'flex'; - manifestMessage.textContent = ''; - } - - // Function to hide loading overlay - function hideLoading() { - loadingOverlay.style.display = 'none'; - } - - // Event listener for loading manifest - loadManifest.addEventListener('click', async function() { - const url = manifestUrl.value.trim(); - if (!url) { - manifestMessage.textContent = 'Please enter a URL.'; - manifestMessage.style.color = 'red'; - return; + if (dropdownLabel) { + dropdownLabel.addEventListener('click', toggleDropdown); } - - showLoading(); - - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const data = await response.json(); - - hideLoading(); - manifestMessage.textContent = 'Manifest loaded successfully!'; - manifestMessage.style.color = 'green'; - - storeManifestLink(url); - - try { - let manifestLabel = `Name: ${data.label.en[0]}`; - let manifestType = `Type: ${data.type}`; - let manifestItemCount = `Number of Items: ${data.items.length}`; - - loadMessage.innerHTML = "Current Object:"; - manifestLabelField.innerHTML = `${manifestLabel}      ${manifestType}      ${manifestItemCount}`; - } catch (metadataError) { - loadMessage.innerHTML = "No metadata available!"; - manifestLabelField.innerHTML = ""; - } - } catch (error) { - hideLoading(); - manifestMessage.textContent = 'Failed to load manifest. Please check the URL and try again.'; - manifestMessage.style.color = 'red'; - console.error('Error:', error); + if (dropdownArrow) { + dropdownArrow.addEventListener('click', toggleDropdown); + } + if (loadManifestBtn && manifestUrl) { + loadManifestBtn.addEventListener('click', () => { + loadManifest(manifestUrl.value.trim()); + }); } - }); -}); - -// Toggle dropdown function, defined outside of DOMContentLoaded to ensure it's globally accessible -function toggleDropdown() { - const manifestContainer = document.getElementById('stored_manifest_links'); - const dropdownArrow = document.getElementById('dropdownArrow'); - - if (manifestContainer.style.display === 'none' || manifestContainer.style.display === '') { - manifestContainer.style.display = 'block'; - dropdownArrow.textContent = '▲'; - } else { - manifestContainer.style.display = 'none'; - dropdownArrow.textContent = '▼'; - } -} - -document.getElementById('dropdownLabel').addEventListener('click', toggleDropdown); -document.getElementById('dropdownArrow').addEventListener('click', toggleDropdown); - -/** - * Update tool order when a tool is clicked. - */ -window.updateToolOrder = function(toolLabel) { - const clickedTool = ToolsCatalog.find(tool => tool.label === toolLabel); - if (clickedTool) { - updateRecentlyUsedTools(clickedTool); - renderTools(); - } -} -document.addEventListener('DOMContentLoaded', () => { -/** -* These are promises so we can control the chaining how we like, if necessary. -*/ - try { - initializeInterfaces(PLAYGROUND.INTERFACES) - initializeTechnologies(PLAYGROUND.TECHNOLOGIES) - renderTools(); - renderStoredManifests(); + // Initial render of manifest cards if dropdown is visible + const manifestContainer = document.getElementById('stored_manifest_links'); + if (manifestContainer && manifestContainer.style.display !== 'none') { + renderManifestCards(); + } } catch (err) { - console.error("Error initializing the playground: ", err); + console.error("Error initializing the playground:", err); } -}); \ No newline at end of file +}); + +// Export for global access if needed +window.updateToolOrder = handleToolClick; \ No newline at end of file diff --git a/web/styles.css b/web/styles.css index a166cf2..6e767d3 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,6 +1,7 @@ body{ margin: 0; padding: 0; + padding-bottom: 100px; font-family: Arial, Helvetica, sans-serif; box-sizing: border-box; overflow-x: hidden; @@ -60,6 +61,12 @@ body{ background-color: rgba(255, 255, 255, 0.1); /* Subtle background */ } +#tool_set { + padding: 20px; + margin-bottom: 120px; + text-align: center; +} + /* Main Content Styles */ .content, .container, #tool_set { transition: margin-left 0.3s ease; /* Smooth content shift */ @@ -90,23 +97,42 @@ body{ } } -.thumb img { - max-height: 100px; - max-width: 100px; - object-fit: contain; - display: block; - margin: 0 auto; -} - .catalogEntry { - padding: 10px; - border: 2px solid black; + display: inline-block; + padding: 15px; + border: 1px solid #ccc; margin: 10px; text-align: center; - display: inline-block; - vertical-align: top; width: 30%; position: relative; + text-decoration: none; + color: inherit; + background: white; +} + +.thumb { + margin: 0; +} + +.thumb img { + max-height: 80px; + max-width: 80px; + object-fit: contain; + margin: 15px auto; +} + +.thumb label { + display: block; + color: #007bff; + font-size: 16px; + margin-bottom: 10px; +} + +.thumb figcaption { + color: #666; + font-size: 14px; + line-height: 1.4; + margin-top: 10px; } h4 { @@ -158,10 +184,6 @@ h4 { vertical-align: middle; } -body { - font-family: Arial, Helvetica, sans-serif; -} - h2 { text-align: center; font-size: 32px; @@ -191,27 +213,29 @@ p{ } .recent-badge{ - display: inline-block; - padding: 4px 8px; - background-color: #f00; - color: white; - font-size: 10px; - border-radius: 4px; position: absolute; top: 5px; right: 5px; + background-color: #ff0000; + color: white; + padding: 2px 8px; + border-radius: 4px; + font-size: 10px; } /*Footer and button styling from RERUM homepage.*/ footer { z-index: 3; - position:fixed; - bottom:0; - width:100vw; + position: fixed; + bottom: 0; + width: 100vw; overflow: hidden; padding: 1rem 0; background-color: #dfdfdf; - font-family:Arial, Helvetica, sans-serif; + font-family: Arial, Helvetica, sans-serif; + margin-top: 20px; + + } footer .button { @@ -261,6 +285,9 @@ footer::before{ padding: 10px; border: 1px solid #ccc; margin-top: 5px; + margin-bottom: 100px; + max-height: calc(100vh - 350px); + overflow-y: auto; } .spinner { @@ -308,19 +335,22 @@ footer::before{ } .manifest-cards-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; + display: flex; + flex-direction: column; + gap: 12px; padding: 20px; + max-width: 1200px; + margin: 0 auto; } .manifest-card { + display: flex; border: 1px solid #ddd; - border-radius: 8px; - overflow: hidden; + border-radius: 4px; background: white; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - transition: transform 0.2s, box-shadow 0.2s; + height: 120px; + width: 100%; } .manifest-card:hover { @@ -329,9 +359,10 @@ footer::before{ } .manifest-card-image { - height: 200px; + height: 100%; + width: 120px; + min-width: 120px; background: #f5f5f5; - position: relative; } .manifest-card-image img { @@ -352,11 +383,16 @@ footer::before{ } .manifest-card-content { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; padding: 15px; + overflow: hidden; } .manifest-title { - margin: 0 0 10px 0; + margin: 0; font-size: 16px; font-weight: bold; color: #333; @@ -366,11 +402,30 @@ footer::before{ } .manifest-items { - margin: 0 0 10px 0; + margin: 5px 0; font-size: 14px; color: #666; } +.manifest-actions { + display: flex; + gap: 10px; + align-items: center; +} + +.manifest-load-btn { + padding: 6px 12px; + background: #7397f9; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.manifest-load-btn:hover { + background: #5a7ce0; +} + .manifest-link { display: inline-block; color: #3370f6; @@ -389,7 +444,7 @@ footer::before{ } /* Responsive adjustments */ -@media (max-width: 768px) { +@media (max-width: 600px) { .manifest-cards-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; @@ -399,4 +454,14 @@ footer::before{ .manifest-card-image { height: 150px; } + + .catalogEntry { + width: 100%; + } +} + +@media (max-width: 900px) { + .catalogEntry { + width: 45%; + } } \ No newline at end of file diff --git a/web/tools.html b/web/tools.html index 2733224..5ee797d 100644 --- a/web/tools.html +++ b/web/tools.html @@ -121,79 +121,5 @@

Tools

}) .catch(error => console.error('Error loading footer:', error)); - - \ No newline at end of file