feat(frontend): improve werkgebied display and database UI
- Fix polygon rendering with static paint properties instead of data-driven - Add ensureSourceAndLayers() helper for reliable layer management - Use setPaintProperty() for historical vs modern styling distinction - Improve Database page layout with back buttons and cleaner navigation - Add ResizableNestedTable component for DuckLake data display - Optimize spacing and layout in Database.css - Update schema manifest
This commit is contained in:
parent
f284e87d13
commit
4825f57951
5 changed files with 2619 additions and 202 deletions
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"generated": "2025-12-06T23:25:26.568Z",
|
||||
"generated": "2025-12-07T12:41:26.252Z",
|
||||
"version": "1.0.0",
|
||||
"categories": [
|
||||
{
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -209,34 +209,31 @@ export function useWerkgebiedMapLibre(map: maplibregl.Map | null): WerkgebiedHoo
|
|||
});
|
||||
}
|
||||
|
||||
// Add fill layer (below markers)
|
||||
// Add fill layer (below markers) - use static values, we'll update them when showing
|
||||
if (!map.getLayer(WERKGEBIED_FILL_LAYER_ID)) {
|
||||
// Check if institutions layer exists to insert before it
|
||||
const beforeLayer = map.getLayer('institutions-circles') ? 'institutions-circles' : undefined;
|
||||
map.addLayer({
|
||||
id: WERKGEBIED_FILL_LAYER_ID,
|
||||
type: 'fill',
|
||||
source: WERKGEBIED_SOURCE_ID,
|
||||
paint: {
|
||||
'fill-color': ['get', 'fillColor'],
|
||||
'fill-opacity': ['get', 'fillOpacity'],
|
||||
'fill-color': WERKGEBIED_FILL_COLOR,
|
||||
'fill-opacity': WERKGEBIED_FILL_OPACITY,
|
||||
},
|
||||
}, beforeLayer);
|
||||
});
|
||||
}
|
||||
|
||||
// Add line layer for borders
|
||||
if (!map.getLayer(WERKGEBIED_LINE_LAYER_ID)) {
|
||||
const beforeLayer = map.getLayer('institutions-circles') ? 'institutions-circles' : undefined;
|
||||
map.addLayer({
|
||||
id: WERKGEBIED_LINE_LAYER_ID,
|
||||
type: 'line',
|
||||
source: WERKGEBIED_SOURCE_ID,
|
||||
paint: {
|
||||
'line-color': ['get', 'lineColor'],
|
||||
'line-width': ['get', 'lineWidth'],
|
||||
'line-color': WERKGEBIED_LINE_COLOR,
|
||||
'line-width': WERKGEBIED_LINE_WIDTH,
|
||||
'line-dasharray': [5, 5],
|
||||
},
|
||||
}, beforeLayer);
|
||||
});
|
||||
}
|
||||
|
||||
layersAddedRef.current = true;
|
||||
|
|
@ -298,6 +295,54 @@ export function useWerkgebiedMapLibre(map: maplibregl.Map | null): WerkgebiedHoo
|
|||
setHistoricalDescription(null);
|
||||
}, [map]);
|
||||
|
||||
// Helper to ensure source and layers exist
|
||||
const ensureSourceAndLayers = useCallback(() => {
|
||||
if (!map) return false;
|
||||
|
||||
// Add source if missing
|
||||
if (!map.getSource(WERKGEBIED_SOURCE_ID)) {
|
||||
console.log('[useWerkgebiedMapLibre] Adding missing source');
|
||||
map.addSource(WERKGEBIED_SOURCE_ID, {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add fill layer if missing
|
||||
if (!map.getLayer(WERKGEBIED_FILL_LAYER_ID)) {
|
||||
console.log('[useWerkgebiedMapLibre] Adding missing fill layer');
|
||||
map.addLayer({
|
||||
id: WERKGEBIED_FILL_LAYER_ID,
|
||||
type: 'fill',
|
||||
source: WERKGEBIED_SOURCE_ID,
|
||||
paint: {
|
||||
'fill-color': WERKGEBIED_FILL_COLOR,
|
||||
'fill-opacity': WERKGEBIED_FILL_OPACITY,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add line layer if missing
|
||||
if (!map.getLayer(WERKGEBIED_LINE_LAYER_ID)) {
|
||||
console.log('[useWerkgebiedMapLibre] Adding missing line layer');
|
||||
map.addLayer({
|
||||
id: WERKGEBIED_LINE_LAYER_ID,
|
||||
type: 'line',
|
||||
source: WERKGEBIED_SOURCE_ID,
|
||||
paint: {
|
||||
'line-color': WERKGEBIED_LINE_COLOR,
|
||||
'line-width': WERKGEBIED_LINE_WIDTH,
|
||||
'line-dasharray': [5, 5],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [map]);
|
||||
|
||||
// Helper to update map with GeoJSON features
|
||||
const updateMapWithFeatures = useCallback((
|
||||
features: GeoJSONFeature[] | HALCFeature[],
|
||||
|
|
@ -306,26 +351,50 @@ export function useWerkgebiedMapLibre(map: maplibregl.Map | null): WerkgebiedHoo
|
|||
) => {
|
||||
if (!map) return;
|
||||
|
||||
// Ensure source and layers exist before updating
|
||||
if (!ensureSourceAndLayers()) {
|
||||
console.warn('[useWerkgebiedMapLibre] Failed to ensure source and layers');
|
||||
return;
|
||||
}
|
||||
|
||||
const { fitBounds = true, fitBoundsPadding = [50, 50], maxZoom = 12 } = options;
|
||||
|
||||
// Add style properties to features
|
||||
const styledFeatures = features.map(f => ({
|
||||
...f,
|
||||
properties: {
|
||||
...f.properties,
|
||||
fillColor: isHistorical ? HISTORICAL_FILL_COLOR : WERKGEBIED_FILL_COLOR,
|
||||
fillOpacity: isHistorical ? HISTORICAL_FILL_OPACITY : WERKGEBIED_FILL_OPACITY,
|
||||
lineColor: isHistorical ? HISTORICAL_LINE_COLOR : WERKGEBIED_LINE_COLOR,
|
||||
lineWidth: isHistorical ? HISTORICAL_LINE_WIDTH : WERKGEBIED_LINE_WIDTH,
|
||||
},
|
||||
}));
|
||||
// Update paint properties based on historical vs modern
|
||||
const fillColor = isHistorical ? HISTORICAL_FILL_COLOR : WERKGEBIED_FILL_COLOR;
|
||||
const fillOpacity = isHistorical ? HISTORICAL_FILL_OPACITY : WERKGEBIED_FILL_OPACITY;
|
||||
const lineColor = isHistorical ? HISTORICAL_LINE_COLOR : WERKGEBIED_LINE_COLOR;
|
||||
const lineWidth = isHistorical ? HISTORICAL_LINE_WIDTH : WERKGEBIED_LINE_WIDTH;
|
||||
|
||||
// Update layer paint properties
|
||||
if (map.getLayer(WERKGEBIED_FILL_LAYER_ID)) {
|
||||
map.setPaintProperty(WERKGEBIED_FILL_LAYER_ID, 'fill-color', fillColor);
|
||||
map.setPaintProperty(WERKGEBIED_FILL_LAYER_ID, 'fill-opacity', fillOpacity);
|
||||
}
|
||||
if (map.getLayer(WERKGEBIED_LINE_LAYER_ID)) {
|
||||
map.setPaintProperty(WERKGEBIED_LINE_LAYER_ID, 'line-color', lineColor);
|
||||
map.setPaintProperty(WERKGEBIED_LINE_LAYER_ID, 'line-width', lineWidth);
|
||||
}
|
||||
|
||||
// Ensure werkgebied layers are below institutions layer
|
||||
if (map.getLayer('institutions-circles')) {
|
||||
if (map.getLayer(WERKGEBIED_FILL_LAYER_ID)) {
|
||||
map.moveLayer(WERKGEBIED_FILL_LAYER_ID, 'institutions-circles');
|
||||
}
|
||||
if (map.getLayer(WERKGEBIED_LINE_LAYER_ID)) {
|
||||
map.moveLayer(WERKGEBIED_LINE_LAYER_ID, 'institutions-circles');
|
||||
}
|
||||
}
|
||||
|
||||
// Update source data
|
||||
const source = map.getSource(WERKGEBIED_SOURCE_ID) as maplibregl.GeoJSONSource | undefined;
|
||||
if (source) {
|
||||
source.setData({
|
||||
type: 'FeatureCollection',
|
||||
features: styledFeatures,
|
||||
features: features,
|
||||
} as GeoJSON.FeatureCollection);
|
||||
console.log('[useWerkgebiedMapLibre] Updated source with', features.length, 'features');
|
||||
} else {
|
||||
console.warn('[useWerkgebiedMapLibre] Source still not found after ensure:', WERKGEBIED_SOURCE_ID);
|
||||
}
|
||||
|
||||
// Fit map bounds to werkgebied - only if camera is zoomed out far
|
||||
|
|
@ -356,7 +425,7 @@ export function useWerkgebiedMapLibre(map: maplibregl.Map | null): WerkgebiedHoo
|
|||
maxZoom: maxZoom,
|
||||
});
|
||||
}
|
||||
}, [map]);
|
||||
}, [map, ensureSourceAndLayers]);
|
||||
|
||||
// Show werkgebied for an archive
|
||||
const showWerkgebied = useCallback((archiveId: string, options: WerkgebiedOptions = {}) => {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import { useState } from 'react';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { DuckDBPanel } from '../components/database/DuckDBPanel';
|
||||
import { DuckLakePanel } from '../components/database/DuckLakePanel';
|
||||
import { PostgreSQLPanel } from '../components/database/PostgreSQLPanel';
|
||||
import { TypeDBPanel } from '../components/database/TypeDBPanel';
|
||||
|
|
@ -52,7 +51,7 @@ const DATABASES: DatabaseInfo[] = [
|
|||
nl: 'Lakehouse met tijdreizen, ACID-transacties en schema-evolutie',
|
||||
en: 'Lakehouse with time travel, ACID transactions, and schema evolution',
|
||||
},
|
||||
icon: '🏔️',
|
||||
icon: '🦆',
|
||||
color: '#FFC107',
|
||||
},
|
||||
{
|
||||
|
|
@ -105,7 +104,7 @@ const TEXT = {
|
|||
loading: { nl: 'Laden...', en: 'Loading...' },
|
||||
|
||||
// Quick comparison
|
||||
quickComparison: { nl: 'Snelle vergelijking', en: 'Quick Comparison' },
|
||||
quickComparison: { nl: 'Gegevensbanken', en: 'Databases' },
|
||||
database: { nl: 'Database', en: 'Database' },
|
||||
status: { nl: 'Status', en: 'Status' },
|
||||
dataCount: { nl: 'Data', en: 'Data' },
|
||||
|
|
@ -152,71 +151,79 @@ export function Database() {
|
|||
<p>{t('pageSubtitle')}</p>
|
||||
</header>
|
||||
|
||||
{/* Database Type Tabs */}
|
||||
<nav className="database-nav">
|
||||
<button
|
||||
className={`db-nav-button ${activeDatabase === 'all' ? 'active' : ''}`}
|
||||
onClick={() => setActiveDatabase('all')}
|
||||
>
|
||||
<span className="db-icon">📊</span>
|
||||
<span className="db-name">{t('allDatabases')}</span>
|
||||
</button>
|
||||
{DATABASES.map((db) => (
|
||||
<button
|
||||
key={db.id}
|
||||
className={`db-nav-button ${activeDatabase === db.id ? 'active' : ''}`}
|
||||
onClick={() => setActiveDatabase(db.id)}
|
||||
style={{ '--db-color': db.color } as React.CSSProperties}
|
||||
>
|
||||
<span className="db-icon">{db.icon}</span>
|
||||
<span className="db-name">{db.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Content based on active tab */}
|
||||
<div className="database-content">
|
||||
{activeDatabase === 'all' && <AllDatabasesView language={language} />}
|
||||
{activeDatabase === 'duckdb' && <DuckLakePanel />}
|
||||
{activeDatabase === 'postgres' && <PostgreSQLPanel />}
|
||||
{activeDatabase === 'typedb' && <TypeDBPanel />}
|
||||
{activeDatabase === 'oxigraph' && <OxigraphPanel />}
|
||||
{activeDatabase === 'all' && <AllDatabasesView language={language} onSelectDatabase={setActiveDatabase} />}
|
||||
{activeDatabase === 'duckdb' && (
|
||||
<>
|
||||
<button className="back-button" onClick={() => setActiveDatabase('all')}>
|
||||
← {language === 'nl' ? 'Terug naar overzicht' : 'Back to overview'}
|
||||
</button>
|
||||
<DuckLakePanel />
|
||||
</>
|
||||
)}
|
||||
{activeDatabase === 'postgres' && (
|
||||
<>
|
||||
<button className="back-button" onClick={() => setActiveDatabase('all')}>
|
||||
← {language === 'nl' ? 'Terug naar overzicht' : 'Back to overview'}
|
||||
</button>
|
||||
<PostgreSQLPanel />
|
||||
</>
|
||||
)}
|
||||
{activeDatabase === 'typedb' && (
|
||||
<>
|
||||
<button className="back-button" onClick={() => setActiveDatabase('all')}>
|
||||
← {language === 'nl' ? 'Terug naar overzicht' : 'Back to overview'}
|
||||
</button>
|
||||
<TypeDBPanel />
|
||||
</>
|
||||
)}
|
||||
{activeDatabase === 'oxigraph' && (
|
||||
<>
|
||||
<button className="back-button" onClick={() => setActiveDatabase('all')}>
|
||||
← {language === 'nl' ? 'Terug naar overzicht' : 'Back to overview'}
|
||||
</button>
|
||||
<OxigraphPanel />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Info Section */}
|
||||
<section className="info-card">
|
||||
<h2>{t('databaseTypes')}</h2>
|
||||
<div className="db-types-grid">
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#FFC107' }}>
|
||||
<span className="db-type-icon">🏔️</span>
|
||||
<div>
|
||||
<strong>DuckLake</strong>
|
||||
<p>{t('ducklakeDescription')}</p>
|
||||
{/* Info Section - only show on overview */}
|
||||
{activeDatabase === 'all' && (
|
||||
<section className="info-card">
|
||||
<h2>{t('databaseTypes')}</h2>
|
||||
<div className="db-types-grid">
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#FFC107' }}>
|
||||
<span className="db-type-icon">🦆</span>
|
||||
<div>
|
||||
<strong>DuckLake</strong>
|
||||
<p>{t('ducklakeDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#336791' }}>
|
||||
<span className="db-type-icon">🐘</span>
|
||||
<div>
|
||||
<strong>PostgreSQL</strong>
|
||||
<p>{t('relationalDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#6B5CE7' }}>
|
||||
<span className="db-type-icon">🧠</span>
|
||||
<div>
|
||||
<strong>TypeDB</strong>
|
||||
<p>{t('graphDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#00A86B' }}>
|
||||
<span className="db-type-icon">🔗</span>
|
||||
<div>
|
||||
<strong>Oxigraph</strong>
|
||||
<p>{t('rdfDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#336791' }}>
|
||||
<span className="db-type-icon">🐘</span>
|
||||
<div>
|
||||
<strong>PostgreSQL</strong>
|
||||
<p>{t('relationalDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#6B5CE7' }}>
|
||||
<span className="db-type-icon">🧠</span>
|
||||
<div>
|
||||
<strong>TypeDB</strong>
|
||||
<p>{t('graphDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="db-type-card" style={{ borderLeftColor: '#00A86B' }}>
|
||||
<span className="db-type-icon">🔗</span>
|
||||
<div>
|
||||
<strong>Oxigraph</strong>
|
||||
<p>{t('rdfDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -224,7 +231,7 @@ export function Database() {
|
|||
/**
|
||||
* All Databases Overview View
|
||||
*/
|
||||
function AllDatabasesView({ language }: { language: 'nl' | 'en' }) {
|
||||
function AllDatabasesView({ language, onSelectDatabase }: { language: 'nl' | 'en'; onSelectDatabase: (db: DatabaseType) => void }) {
|
||||
const t = (key: keyof typeof TEXT) => TEXT[key][language];
|
||||
|
||||
// Initialize all database hooks
|
||||
|
|
@ -295,7 +302,7 @@ function AllDatabasesView({ language }: { language: 'nl' | 'en' }) {
|
|||
|
||||
return (
|
||||
<div className="all-databases-view">
|
||||
{/* Quick Comparison Grid */}
|
||||
{/* Database Cards Grid */}
|
||||
<section className="comparison-section">
|
||||
<h2>{t('quickComparison')}</h2>
|
||||
<div className="comparison-grid">
|
||||
|
|
@ -305,26 +312,11 @@ function AllDatabasesView({ language }: { language: 'nl' | 'en' }) {
|
|||
db={db}
|
||||
language={language}
|
||||
stats={dbStats[db.id]}
|
||||
onClick={() => onSelectDatabase(db.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Compact panels for each database */}
|
||||
<div className="database-panels-grid">
|
||||
<div className="panel-wrapper duckdb">
|
||||
<DuckDBPanel compact />
|
||||
</div>
|
||||
<div className="panel-wrapper postgres">
|
||||
<PostgreSQLPanel compact />
|
||||
</div>
|
||||
<div className="panel-wrapper typedb">
|
||||
<TypeDBPanel compact />
|
||||
</div>
|
||||
<div className="panel-wrapper oxigraph">
|
||||
<OxigraphPanel compact />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -336,10 +328,12 @@ function DatabaseStatusCard({
|
|||
db,
|
||||
language,
|
||||
stats,
|
||||
onClick,
|
||||
}: {
|
||||
db: DatabaseInfo;
|
||||
language: 'nl' | 'en';
|
||||
stats: DatabaseStats;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
// Format large numbers
|
||||
const formatNumber = (n: number) => {
|
||||
|
|
@ -350,8 +344,12 @@ function DatabaseStatusCard({
|
|||
|
||||
return (
|
||||
<div
|
||||
className="db-status-card"
|
||||
className="db-status-card clickable"
|
||||
style={{ '--db-color': db.color } as React.CSSProperties}
|
||||
onClick={onClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onClick()}
|
||||
>
|
||||
<div className="db-status-header">
|
||||
<span className="db-icon-large">{db.icon}</span>
|
||||
|
|
|
|||
Loading…
Reference in a new issue