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:
Danila Khodjaef
2026-06-19 10:10:13 +03:00
co-authored by Cursor
parent 44d3d4359a
commit 08f99d7a29
25 changed files with 2954 additions and 218 deletions
+70
View File
@@ -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 {