Skip to content

Commit 5066094

Browse files
authored
Merge pull request #3 from hinxcode/loc-maps
Loc maps adaptation
2 parents 5a865b2 + 8cb724f commit 5066094

File tree

6 files changed

+138
-49
lines changed

6 files changed

+138
-49
lines changed

src/backend/services/embedding_service.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,9 @@ def load_embeddings(self) -> None:
5757
logger.info("Embeddings loaded successfully")
5858
else:
5959
if not embeddings_path.exists():
60-
logger.warning(f"Embeddings file not found at {embeddings_path}")
60+
logger.error(f"Embeddings file not found at {embeddings_path}")
6161
if not item_ids_path.exists():
62-
logger.warning(f"Item IDs file not found at {item_ids_path}")
63-
64-
logger.error("Failed to load embeddings: files not found")
62+
logger.error(f"Item IDs file not found at {item_ids_path}")
6563
except Exception as e:
6664
logger.error(f"Error loading embeddings: {str(e)}")
6765
logger.error(traceback.format_exc())

src/frontend/maps/src/App.jsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React, { useState, useCallback, useRef, useEffect } from 'react';
22
import SearchBar from './components/SearchBar';
33
import Lightbox from './components/Lightbox';
44
import SearchResults from './components/SearchResults';
5-
import { searchByText, searchByImage, getImageUrl, getEmbeddingStats } from './services/api';
5+
import { searchByText, searchByImage, getEmbeddingStats, getIiifInfo } from './services/api';
66
import './App.css';
77

88
function App() {
9-
const [photos, setPhotos] = useState([]);
9+
const [maps, setMaps] = useState([]);
1010
const [searchQuery, setSearchQuery] = useState('');
1111
const [isLoading, setIsLoading] = useState(false);
1212
const [error, setError] = useState(null);
@@ -32,9 +32,9 @@ function App() {
3232
fetchEmbeddingStats();
3333
}, []);
3434

35-
const formatPhotosForGallery = (results) => {
35+
const formatItemsForGallery = (results) => {
3636
return results.map(result => ({
37-
src: getImageUrl(result.id, 200),
37+
src: result.metadata.paths.thumbnail,
3838
width: 200,
3939
height: 150,
4040
alt: result.file_name,
@@ -50,7 +50,7 @@ function App() {
5050

5151
try {
5252
const results = await searchByText(query, resultsPerPage, currentPage);
53-
setPhotos(formatPhotosForGallery(results));
53+
setMaps(formatItemsForGallery(results));
5454
setHasMore(results.length >= resultsPerPage);
5555
} catch (error) {
5656
console.error('Error performing search:', error);
@@ -66,7 +66,7 @@ function App() {
6666

6767
try {
6868
const results = await searchByImage(image, resultsPerPage, currentPage);
69-
setPhotos(formatPhotosForGallery(results));
69+
setMaps(formatItemsForGallery(results));
7070
setHasMore(results.length >= resultsPerPage);
7171
} catch (error) {
7272
console.error('Error performing search:', error);
@@ -84,21 +84,28 @@ function App() {
8484
}
8585
}, [currentPage]);
8686

87-
const handleLightboxOpened = useCallback((index) => {
88-
const selectedItem = photos[index].originalData;
89-
const imageUrl = getImageUrl(selectedItem.id, 1600);
87+
const handleLightboxOpened = useCallback(async (selectedMap) => {
88+
try {
89+
const iiifInfo = await getIiifInfo(selectedMap.originalData.metadata.iiif_id);
9090

91-
setSelectedMap(imageUrl);
92-
}, [photos]);
91+
setSelectedMap({
92+
...selectedMap.originalData,
93+
iiifInfo: iiifInfo
94+
});
95+
} catch (error) {
96+
console.error("Failed to get IIIF info:", error);
97+
setError('Failed to get IIIF info from Library of Congress\'s API. Please try again.');
98+
}
99+
}, [setSelectedMap]);
93100

94101
const handleLightboxClosed = useCallback(() => {
95102
setSelectedMap(null);
96-
}, []);
103+
}, [setSelectedMap]);
97104

98105
const handleSearchModeChanged = useCallback((mode) => {
99106
setSearchMode(mode);
100107
setCurrentPage(1);
101-
setPhotos([]);
108+
setMaps([]);
102109
setHasMore(false);
103110

104111
if (mode === 'text') {
@@ -135,7 +142,7 @@ function App() {
135142
</p>
136143
)}
137144
<SearchResults
138-
photos={photos}
145+
items={maps}
139146
isLoading={isLoading}
140147
error={error}
141148
onClick={handleLightboxOpened}
@@ -147,7 +154,7 @@ function App() {
147154

148155
<Lightbox
149156
isVisible={!!selectedMap}
150-
imageUrl={selectedMap}
157+
data={selectedMap}
151158
onBack={handleLightboxClosed}
152159
/>
153160
</div>

src/frontend/maps/src/components/Lightbox.css

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
left: 50%;
8585
transform: translate(-50%, -50%);
8686
text-align: center;
87-
color: white;
87+
color: #eee;
8888
background-color: rgba(0, 0, 0, 0.7);
8989
padding: 20px;
9090
border-radius: 8px;
@@ -106,7 +106,7 @@
106106
margin-top: 10px;
107107
padding: 8px 16px;
108108
background-color: #3498db;
109-
color: white;
109+
color: #eee;
110110
border: none;
111111
border-radius: 4px;
112112
cursor: pointer;
@@ -115,3 +115,33 @@
115115
.lightbox-error button:hover {
116116
background-color: #2980b9;
117117
}
118+
119+
.lightbox-info-overlay {
120+
position: absolute;
121+
bottom: 0;
122+
left: 0;
123+
background-color: rgba(0, 0, 0, 0.7);
124+
color: #eee;
125+
padding: 15px;
126+
margin: 20px;
127+
border-radius: 8px;
128+
max-width: 400px;
129+
text-align: left;
130+
z-index: 1001;
131+
}
132+
133+
.lightbox-title {
134+
margin: 0 0 10px 0;
135+
font-size: 1.1rem;
136+
font-weight: bold;
137+
}
138+
139+
.lightbox-source-link {
140+
color: #a9d9f7;
141+
text-decoration: none;
142+
font-size: 0.9rem;
143+
}
144+
145+
.lightbox-source-link:hover {
146+
text-decoration: underline;
147+
}

src/frontend/maps/src/components/Lightbox.jsx

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,66 @@
11
import React, { useRef, useEffect, useState } from 'react';
22
import OpenSeadragon from 'openseadragon';
3+
import { getLocInfo } from '../services/api';
34
import './Lightbox.css';
45

5-
const Lightbox = React.memo(({ imageUrl, onBack, isVisible }) => {
6+
const Lightbox = React.memo(({ data, onBack, isVisible }) => {
67
const viewerRef = useRef(null);
7-
const [isLoading, setIsLoading] = useState(true);
8+
const [isViewerLoading, setIsViewerLoading] = useState(true);
9+
const [isMapInfoLoading, setIsMapInfoLoading] = useState(true);
810
const [error, setError] = useState(null);
11+
const [mapTitle, setMapTitle] = useState('');
912

1013
useEffect(() => {
1114
if (!isVisible || !viewerRef.current) {
1215
return;
1316
}
1417

15-
setIsLoading(true);
18+
setIsViewerLoading(true);
1619
setError(null);
1720

1821
const viewer = OpenSeadragon({
1922
element: viewerRef.current,
2023
prefixUrl: 'https://openseadragon.github.io/openseadragon/images/',
21-
tileSources: {
22-
type: 'image',
23-
url: imageUrl
24-
},
24+
tileSources: data.iiifInfo,
2525
showNavigator: true,
2626
minZoomLevel: 0.1,
2727
defaultZoomLevel: 0.5,
2828
maxZoomLevel: 10
2929
});
3030

3131
viewer.addHandler('open', () => {
32-
setIsLoading(false);
32+
setIsViewerLoading(false);
3333
});
3434

3535
viewer.addHandler('open-failed', () => {
3636
setError('Failed to load image');
37-
setIsLoading(false);
37+
setIsViewerLoading(false);
3838
});
3939

4040
return () => {
4141
viewer.destroy();
4242
};
43-
}, [imageUrl, isVisible]);
43+
}, [data, isVisible]);
4444

4545
useEffect(() => {
46-
if (!isLoading) return;
46+
if (!data) return;
4747

48-
const timeoutId = setTimeout(() => {
49-
setIsLoading(false);
50-
}, 5000);
51-
52-
return () => clearTimeout(timeoutId);
53-
}, [isLoading]);
48+
setIsMapInfoLoading(true);
49+
setMapTitle('');
50+
51+
try {
52+
const fetchLocInfo = async () => {
53+
const locInfo = await getLocInfo(data.metadata.url);
54+
setMapTitle(locInfo?.item?.title || 'Untitled');
55+
setIsMapInfoLoading(false);
56+
};
57+
fetchLocInfo();
58+
} catch (error) {
59+
console.error('Failed to fetch LOC info:', error);
60+
setError('Failed to get map\'s info from Library of Congress\'s API. Please try again.');
61+
setIsMapInfoLoading(false);
62+
}
63+
}, [data]);
5464

5565
return (
5666
<div className={`lightbox-overlay ${isVisible ? 'visible' : ''}`}>
@@ -59,19 +69,37 @@ const Lightbox = React.memo(({ imageUrl, onBack, isVisible }) => {
5969
Back to results
6070
</button>
6171
<div className="tile-gallery-viewer" ref={viewerRef}>
62-
{isLoading && isVisible && (
72+
{isVisible && isViewerLoading &&(
6373
<div className="lightbox-loading">
6474
<div className="lightbox-spinner" />
6575
<p>Loading map...</p>
6676
</div>
6777
)}
68-
{error && isVisible && (
78+
{isVisible && error && (
6979
<div className="lightbox-error">
7080
<div className="lightbox-error-icon">⚠️</div>
7181
<p>{error}</p>
7282
</div>
7383
)}
7484
</div>
85+
{data && (
86+
<div className="lightbox-info-overlay">
87+
<h3 className="lightbox-title">
88+
{
89+
isMapInfoLoading ? (
90+
<span>Loading map's info...</span>
91+
) : (
92+
<span>{mapTitle}</span>
93+
)
94+
}
95+
</h3>
96+
{data?.metadata?.url && (
97+
<a href={data.metadata.url} target="_blank" rel="noopener noreferrer" className="lightbox-source-link">
98+
View at Library of Congress
99+
</a>
100+
)}
101+
</div>
102+
)}
75103
</div>
76104
</div>
77105
);

src/frontend/maps/src/components/SearchResults.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Pagination from './Pagination';
44
import './SearchResults.css';
55

66
const SearchResults = React.memo(({
7-
photos,
7+
items,
88
isLoading,
99
error,
1010
onClick,
@@ -28,13 +28,13 @@ const SearchResults = React.memo(({
2828
);
2929
}
3030

31-
if (photos.length > 0) {
31+
if (items.length > 0) {
3232
return (
3333
<div className="gallery-container">
3434
<Gallery
35-
images={photos}
35+
images={items}
3636
enableImageSelection={false}
37-
onClick={(index) => onClick(index)}
37+
onClick={(index) => onClick(items[index])}
3838
margin={2}
3939
rowHeight={180}
4040
targetRowHeight={200}

src/frontend/maps/src/services/api.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
11
const API_URL = import.meta.env.API_BASE_URL;
22

3-
export const getImageUrl = (documentId, size) => {
4-
if (!documentId) return '';
5-
if (size) {
6-
return `${API_URL}/images/${encodeURIComponent(documentId)}?size=${size}`;
3+
export const getIiifInfo = async (iiifId) => {
4+
const url = `https://tile.loc.gov/image-services/iiif/${iiifId}/info.json`;
5+
6+
try {
7+
const response = await fetch(url);
8+
9+
if (!response.ok) {
10+
throw new Error(`HTTP error! status: ${response.status}`);
11+
}
12+
return await response.json();
13+
} catch (error) {
14+
console.error(`Failed to fetch IIIF info for ${iiifId}:`, error);
15+
throw error;
16+
}
17+
};
18+
19+
export const getLocInfo = async (locItemUrl) => {
20+
try {
21+
const urlObject = new URL(locItemUrl);
22+
urlObject.protocol = 'https';
23+
urlObject.searchParams.set('fo', 'json');
24+
25+
const response = await fetch(urlObject.href);
26+
27+
if (!response.ok) {
28+
throw new Error(`HTTP error! status: ${response.status}`);
29+
}
30+
return await response.json();
31+
} catch (error) {
32+
console.error(`Failed to process or fetch LOC info for ${locItemUrl}:`, error);
33+
throw error;
734
}
8-
return `${API_URL}/images/${encodeURIComponent(documentId)}`;
935
};
1036

1137
/**

0 commit comments

Comments
 (0)