Redesign 3D gallery as single hall per artist with exit navigation.
Restore React client source, add hall-to-hall navigation via painting influences grouped by movement, and update documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
44d3d4359a
commit
08f99d7a29
@@ -98,6 +98,76 @@ app.get('/api/artists', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Artist detail with periods and paintings
|
||||
app.get('/api/artists/:id/navigation', async (req, res) => {
|
||||
try {
|
||||
const artistId = parseInt(req.params.id, 10);
|
||||
|
||||
const groupByMovement = (rows) => {
|
||||
const map = new Map();
|
||||
for (const row of rows) {
|
||||
const key = row.movement_id ?? 0;
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
movement_id: row.movement_id,
|
||||
movement_name: row.movement_name || 'Other',
|
||||
movement_color: row.movement_color || '#8B7355',
|
||||
artists: [],
|
||||
});
|
||||
}
|
||||
const group = map.get(key);
|
||||
if (!group.artists.some((a) => a.id === row.id)) {
|
||||
group.artists.push({
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
birth_year: row.birth_year,
|
||||
death_year: row.death_year,
|
||||
portrait_path: row.portrait_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
return Array.from(map.values()).sort((a, b) =>
|
||||
a.movement_name.localeCompare(b.movement_name)
|
||||
);
|
||||
};
|
||||
|
||||
const [predecessors, successors] = await Promise.all([
|
||||
pool.query(
|
||||
`SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path,
|
||||
m.id AS movement_id, m.name AS movement_name, m.color AS movement_color
|
||||
FROM painting_influences pi
|
||||
JOIN paintings p_work ON pi.painting_id = p_work.id
|
||||
JOIN paintings p_pred ON pi.influenced_by_painting_id = p_pred.id
|
||||
JOIN artists a ON p_pred.artist_id = a.id
|
||||
LEFT JOIN art_movements m ON a.movement_id = m.id
|
||||
WHERE p_work.artist_id = $1 AND a.id <> $1
|
||||
ORDER BY m.name NULLS LAST, a.birth_year NULLS LAST, a.name`,
|
||||
[artistId]
|
||||
),
|
||||
pool.query(
|
||||
`SELECT DISTINCT a.id, a.name, a.birth_year, a.death_year, a.portrait_path,
|
||||
m.id AS movement_id, m.name AS movement_name, m.color AS movement_color
|
||||
FROM painting_influences pi
|
||||
JOIN paintings p_src ON pi.influenced_by_painting_id = p_src.id
|
||||
JOIN paintings p_work ON pi.painting_id = p_work.id
|
||||
JOIN artists a ON p_work.artist_id = a.id
|
||||
LEFT JOIN art_movements m ON a.movement_id = m.id
|
||||
WHERE p_src.artist_id = $1 AND a.id <> $1
|
||||
ORDER BY m.name NULLS LAST, a.birth_year NULLS LAST, a.name`,
|
||||
[artistId]
|
||||
),
|
||||
]);
|
||||
|
||||
res.json({
|
||||
predecessors: groupByMovement(predecessors.rows),
|
||||
successors: groupByMovement(successors.rows),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch artist navigation' });
|
||||
}
|
||||
});
|
||||
|
||||
// Artist detail with periods and paintings
|
||||
app.get('/api/artists/:id', async (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user