Add movement gallery wings with period interiors and expand timeline features.
Movement galleries split large catalogs into chronological wings (~55 works), use era-themed 3D interiors with side-wall windows, wing navigator on the back exit, and front archways between wings. Also adds painting annotations, timeline event guides, portrait hover highlights, and documentation/API updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
0972b5df99
commit
df29848d89
+48
-2
@@ -12,7 +12,7 @@ const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.json({ limit: '20mb' }));
|
||||
app.use('/images', express.static(IMAGE_DIR));
|
||||
|
||||
const INFLUENCE_LINKS_EXISTS = `
|
||||
@@ -92,6 +92,44 @@ app.get('/api/timeline', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Movement gallery — all paintings by artists in the movement, chronological
|
||||
app.get('/api/movements/:id/gallery', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const movement = await pool.query(
|
||||
`SELECT m.*, e.name AS era_name
|
||||
FROM art_movements m
|
||||
LEFT JOIN historical_eras e ON m.era_id = e.id
|
||||
WHERE m.id = $1`,
|
||||
[id]
|
||||
);
|
||||
if (movement.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Movement not found' });
|
||||
}
|
||||
|
||||
const paintings = await pool.query(
|
||||
`SELECT p.*,
|
||||
a.name AS artist_name,
|
||||
p.checkup_checked,
|
||||
p.checkup_fixed,
|
||||
(${INFLUENCE_LINKS_EXISTS}) AS has_influence_links
|
||||
FROM paintings p
|
||||
INNER JOIN artists a ON p.artist_id = a.id
|
||||
WHERE a.movement_id = $1
|
||||
ORDER BY p.year NULLS LAST, p.sort_order, a.birth_year NULLS LAST, p.title`,
|
||||
[id]
|
||||
);
|
||||
|
||||
res.json({
|
||||
movement: movement.rows[0],
|
||||
paintings: paintings.rows,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Failed to fetch movement gallery' });
|
||||
}
|
||||
});
|
||||
|
||||
// Artists for a movement in a time range
|
||||
app.get('/api/movements/:id/artists', async (req, res) => {
|
||||
try {
|
||||
@@ -570,7 +608,7 @@ app.get('/api/paintings/:id', async (req, res) => {
|
||||
return res.status(404).json({ error: 'Painting not found' });
|
||||
}
|
||||
|
||||
const [influencedBy, influenced] = await Promise.all([
|
||||
const [influencedBy, influenced, annotations] = await Promise.all([
|
||||
pool.query(INFLUENCED_BY_SQL, [id]),
|
||||
pool.query(
|
||||
`SELECT * FROM (
|
||||
@@ -593,12 +631,20 @@ app.get('/api/paintings/:id', async (req, res) => {
|
||||
ORDER BY year NULLS LAST`,
|
||||
[id]
|
||||
),
|
||||
pool.query(
|
||||
`SELECT id, label, body, category, pos_x, pos_y, source_author, source, source_url, sort_order, confidence
|
||||
FROM painting_annotations
|
||||
WHERE painting_id = $1
|
||||
ORDER BY sort_order, id`,
|
||||
[id]
|
||||
),
|
||||
]);
|
||||
|
||||
res.json({
|
||||
painting: painting.rows[0],
|
||||
influencedBy: influencedBy.rows,
|
||||
influenced: influenced.rows,
|
||||
annotations: annotations.rows,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
Reference in New Issue
Block a user