Skip to content

Commit e6a9279

Browse files
committed
refactor(ui): split sidebar and map state modules
- Extract control sidebar sections, overlay visibility, and render-state helpers from app.js - Move weighted companion-layer actions into a UI registry
1 parent 396d01a commit e6a9279

39 files changed

Lines changed: 5233 additions & 3102 deletions

docs/app.js

Lines changed: 345 additions & 355 deletions
Large diffs are not rendered by default.

docs/control-sidebar.css

Lines changed: 774 additions & 0 deletions
Large diffs are not rendered by default.

docs/data/citywide_stats.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/data/neighborhood_surface.geojson

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/index.html

Lines changed: 28 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
rel="stylesheet"
1515
/>
1616
<link rel="stylesheet" href="style.css" />
17+
<link rel="stylesheet" href="control-sidebar.css" />
1718
</head>
1819
<body>
1920
<div
@@ -59,167 +60,24 @@ <h1 id="desktop-only-title">Cities for Children</h1>
5960

6061
<div id="map"></div>
6162

62-
<div id="controls">
63-
<div class="panel-header">
64-
<h2>Cities for Children</h2>
65-
<span class="subtitle">Accessibility Explorer</span>
66-
<button id="info-btn" class="info-btn" title="About this map">
67-
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
68-
<circle cx="12" cy="5" r="2.5"></circle>
69-
<rect x="9.5" y="10" width="5" height="12" rx="1"></rect>
70-
</svg>
71-
</button>
72-
</div>
73-
74-
<div class="section mode-section">
75-
<div class="section-title">Analysis</div>
76-
<div class="mode-toggle" id="mode-toggle">
77-
<button class="mode-opt active" data-mode="house">
78-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12l9-8 9 8"/><path d="M5 10v10h14V10"/></svg>
79-
<span>Building</span>
80-
</button>
81-
<button class="mode-opt" data-mode="neighborhood">
82-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
83-
<span>Neighborhood</span>
84-
</button>
85-
<button class="mode-opt" data-mode="citywide">
86-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="5" height="14"/><rect x="7" y="3" width="5" height="17"/><rect x="13" y="8" width="5" height="12"/><rect x="19" y="5" width="4" height="15"/></svg>
87-
<span>City</span>
88-
</button>
89-
</div>
90-
</div>
91-
92-
<div class="controls-row">
93-
<div class="section" id="amenity-filter-section">
94-
<div class="section-title">Filter only</div>
95-
<div class="filter-dropdown">
96-
<button id="filter-btn" class="filter-btn">
97-
<span class="filter-btn-label-wrap">
98-
<span class="filter-label-prefix">Filter Only:</span>
99-
<span id="filter-label">All types</span>
100-
</span>
101-
<svg width="12" height="12" viewBox="0 0 12 12"><path fill="currentColor" d="M2 4l4 4 4-4"/></svg>
102-
</button>
103-
<div id="filter-popup" class="filter-popup">
104-
<div id="filter-items"></div>
105-
</div>
106-
</div>
107-
</div>
108-
109-
<div class="section" id="radius-section">
110-
<div class="section-title">Walking Time</div>
111-
<div class="radius-toggle" id="radius-toggle">
112-
<button class="radius-opt active" data-minutes="5">5 min</button>
113-
<button class="radius-opt" data-minutes="10">10 min</button>
114-
<button class="radius-opt" data-minutes="15">15 min</button>
63+
<aside id="control-sidebar" class="control-sidebar" aria-label="Map controls">
64+
<div class="control-sidebar-inner">
65+
<header class="control-sidebar-header">
66+
<div class="control-sidebar-heading">
67+
<h2>Cities for Children</h2>
68+
<p>Accessibility Explorer</p>
11569
</div>
116-
<div class="hint" id="mode-hint">Click map to analyze nearest building</div>
117-
</div>
118-
</div>
119-
120-
<div class="section" id="points-visibility-section">
121-
<div class="section-title" id="points-visibility-section-title">Layers</div>
122-
<div class="layers-control" id="points-visibility-layers-control">
123-
<button id="layers-btn" class="layers-btn" type="button" aria-expanded="false" aria-controls="layers-popup">
124-
<span class="layers-btn-text">
125-
<span class="layers-btn-title">Layer controls</span>
126-
<span class="layers-btn-meta" id="layers-btn-meta">2 of 6 enabled</span>
127-
</span>
128-
<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path fill="currentColor" d="M2 4l4 4 4-4"/></svg>
70+
<button id="info-btn" class="info-btn" title="About this map" aria-label="About this map">
71+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
72+
<circle cx="12" cy="5" r="2.5"></circle>
73+
<rect x="9.5" y="10" width="5" height="12" rx="1"></rect>
74+
</svg>
12975
</button>
130-
<div id="layers-popup" class="layers-popup" role="dialog" aria-label="Layer controls">
131-
<div class="layers-popup-header">
132-
<strong>Map Layers</strong>
133-
<div class="layers-popup-actions">
134-
<button type="button" id="layers-select-all" class="layers-action-btn">Select all</button>
135-
<button type="button" id="layers-deselect-all" class="layers-action-btn">Deselect all</button>
136-
</div>
137-
</div>
138-
<div class="detail-point-toggles layers-grid" id="urban95-point-toggles">
139-
<label class="toggle toggle-with-icon">
140-
<input type="checkbox" id="show-urban-nature-toggle" />
141-
<span class="toggle-slider"></span>
142-
<span class="toggle-icon" aria-hidden="true"><img src="./icons/park-alt1.svg" alt="" /></span>
143-
<span class="toggle-label">Urban nature</span>
144-
</label>
145-
<label class="toggle toggle-with-icon">
146-
<input type="checkbox" id="show-trees-toggle" checked />
147-
<span class="toggle-slider"></span>
148-
<span class="toggle-icon" aria-hidden="true"><img src="./icons/park-alt1.svg" alt="" /></span>
149-
<span class="toggle-label">Trees</span>
150-
</label>
151-
<label class="toggle toggle-with-icon">
152-
<input type="checkbox" id="show-lights-toggle" />
153-
<span class="toggle-slider"></span>
154-
<span class="toggle-icon" aria-hidden="true"><img src="./icons/lighthouse.svg" alt="" /></span>
155-
<span class="toggle-label">Lights</span>
156-
</label>
157-
<label class="toggle toggle-with-icon" id="school-points-toggle-wrap">
158-
<input type="checkbox" id="show-schools-toggle" checked />
159-
<span class="toggle-slider"></span>
160-
<span class="toggle-icon" aria-hidden="true"><img src="./icons/town-hall.svg" alt="" /></span>
161-
<span class="toggle-label">Schools</span>
162-
</label>
163-
<label class="toggle">
164-
<input type="checkbox" id="show-heatmap-toggle" checked />
165-
<span class="toggle-slider"></span>
166-
<span class="toggle-label">Heatmap</span>
167-
</label>
168-
<label class="toggle">
169-
<input type="checkbox" id="show-kids-population-toggle" />
170-
<span class="toggle-slider"></span>
171-
<span class="toggle-label">Pop. Kids</span>
172-
</label>
173-
<label class="toggle">
174-
<input type="checkbox" id="show-socioeconomic-toggle" />
175-
<span class="toggle-slider"></span>
176-
<span class="toggle-label">SES Clusters</span>
177-
</label>
178-
<label class="toggle">
179-
<input type="checkbox" id="show-roads-toggle" />
180-
<span class="toggle-slider"></span>
181-
<span class="toggle-label">Roads</span>
182-
</label>
183-
<label class="toggle" id="amenity-points-toggle-wrap">
184-
<input type="checkbox" id="show-amenity-points-toggle" checked />
185-
<span class="toggle-slider"></span>
186-
<span class="toggle-label">Amenities</span>
187-
</label>
188-
</div>
189-
</div>
190-
</div>
191-
<div class="radius-toggle basemap-toggle" id="basemap-toggle" role="radiogroup" aria-label="Basemap">
192-
<button type="button" class="radius-opt active" data-basemap="street">Map</button>
193-
<button type="button" class="radius-opt" data-basemap="satellite">Satellite</button>
194-
</div>
195-
</div>
196-
197-
<div class="section">
198-
<div class="section-title">Building score</div>
199-
<div class="metric-options segmented-options" id="score-model-toggle" role="radiogroup" aria-label="Building score">
200-
<label class="radio-option">
201-
<input type="radio" name="score-model" value="weighted" checked />
202-
<span>Urban95</span>
203-
</label>
204-
<label class="radio-option">
205-
<input type="radio" name="score-model" value="expanded" />
206-
<span>Amenities Focus</span>
207-
</label>
208-
</div>
209-
</div>
210-
211-
<div class="section legend-section">
212-
<div class="section-title">Accessibility (0-100)</div>
213-
<div class="legend-gradient"></div>
214-
<div id="legend-labels" class="legend-labels">
215-
<span>0</span>
216-
<span>25</span>
217-
<span>50</span>
218-
<span>75</span>
219-
<span>100</span>
220-
</div>
76+
</header>
77+
<div id="control-sidebar-body" class="control-sidebar-body"></div>
78+
<footer id="control-legend" class="control-sidebar-legend"></footer>
22179
</div>
222-
</div>
80+
</aside>
22381

22482
<div id="radius-info"></div>
22583

@@ -282,7 +140,6 @@ <h2>Cities for Children</h2>
282140
<div id="tooltip"></div>
283141

284142
<div id="filter-backdrop" class="filter-backdrop"></div>
285-
<div id="layers-backdrop" class="layers-backdrop"></div>
286143

287144
<div id="citywide-modal" class="modal">
288145
<div class="modal-content citywide-content">
@@ -361,8 +218,8 @@ <h2 id="info-modal-title">Cities for Children</h2>
361218
<li>
362219
<strong>Walking time &amp; filter</strong>
363220
<p>
364-
Choose <em>5</em>, <em>10</em>, or <em>15</em> minutes in <em>Amenities Focus</em>.
365-
In <em>Urban95</em>, walking-time and amenity filtering controls are hidden because Urban95 uses a fixed methodology that is not driven directly by those UI controls.
221+
Choose <em>5</em>, <em>10</em>, or <em>15</em> minutes in <em>Amenities Focus</em>.
222+
In <em>Urban95</em>, walking-time and amenity filtering controls are hidden because Urban95 uses a fixed methodology.
366223
</p>
367224
</li>
368225
<li>
@@ -433,14 +290,24 @@ <h2 id="info-modal-title">Cities for Children</h2>
433290
<script src="./js/core/appState.js"></script>
434291
<script src="./js/scoring/scoreModel.js"></script>
435292
<script src="./js/scoring/scoreContext.js"></script>
293+
<script src="./js/scoring/weightedIndicatorIcons.js"></script>
436294
<script src="./js/scoring/scoreExplain.js"></script>
437295
<script src="./js/ui/scoreSidebarChrome.js"></script>
438296
<script src="./js/ui/sidebarChromeBindings.js"></script>
439297
<script src="./js/ui/amenityMode.js"></script>
440298
<script src="./js/ui/controlActions.js"></script>
299+
<script src="./js/ui/controlSidebarMarkup.js"></script>
300+
<script src="./js/ui/weightedMetricShowRegistry.js"></script>
301+
<script src="./js/ui/controlSidebarShow.js"></script>
302+
<script src="./js/ui/controlSidebarFilters.js"></script>
303+
<script src="./js/ui/controlSidebarIndicators.js"></script>
304+
<script src="./js/ui/controlSidebarSections.js"></script>
305+
<script src="./js/ui/controlSidebarAdapter.js"></script>
441306
<script src="./js/map/mapLayers.js"></script>
442307
<script src="./js/map/mapShell.js"></script>
443308
<script src="./js/map/neighborhoodScores.js"></script>
309+
<script src="./js/map/renderState.js"></script>
310+
<script src="./js/ui/overlayVisibility.js"></script>
444311
<script src="./js/map/iconLoader.js"></script>
445312
<script src="./js/map/mapRenderers.js"></script>
446313
<script src="./js/map/selection.js"></script>

docs/js/core/appDependencies.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var ICONS_BASE = requireNamespaceMember(Urban95Config, "Urban95Config", "ICONS_B
1111
var BUILDINGS_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "buildings");
1212
var ROADS_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "roads");
1313
var EDUCATION_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "education");
14+
var BUS_STOPS_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "busStops");
1415
var POPULATION_GRID_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "populationGrid");
1516
var SOCIOECONOMIC_URL = requireStringMember(CONFIG_URLS, "Urban95Config.urls", "socioeconomic");
1617
var BUILDINGS_LOOKUP_URL = requireStringMember(GENERATED_URLS, "Urban95DataArtifacts.urls", "buildingsLookup");
@@ -147,6 +148,13 @@ var weightedCategoryHighlightsFromSource =
147148
requireScoreModelMember(Urban95ScoreModel, "weightedCategoryHighlightsFromSource");
148149
var weightedSubcategoryComparisonRows =
149150
requireScoreModelMember(Urban95ScoreModel, "weightedSubcategoryComparisonRows");
151+
var Urban95WeightedMetricShowRegistry = requireNamespace(window, "Urban95WeightedMetricShowRegistry");
152+
requireNamespaceMember(
153+
Urban95WeightedMetricShowRegistry,
154+
"Urban95WeightedMetricShowRegistry",
155+
"resolveWeightedShowActions",
156+
"function"
157+
);
150158
var Urban95ScoreContext = requireNamespace(window, "Urban95ScoreContext");
151159
requireNamespaceMember(Urban95ScoreContext, "Urban95ScoreContext", "create", "function");
152160
var Urban95ScoreExplain = requireNamespace(window, "Urban95ScoreExplain");
@@ -177,6 +185,15 @@ requireNamespaceMember(
177185
"create",
178186
"function"
179187
);
188+
var Urban95RenderState = requireNamespace(window, "Urban95RenderState");
189+
[
190+
"getWeightedNeighborhoodMetricValue",
191+
"hasWeightedNeighborhoodMetricData",
192+
"getWeightedHistogramDistribution",
193+
"resolvePolygonAnalysisFilter",
194+
].forEach(function (memberName) {
195+
requireNamespaceMember(Urban95RenderState, "Urban95RenderState", memberName, "function");
196+
});
180197
var resolveBuildingContracts =
181198
requireNamespaceMember(
182199
Urban95MapLayers,
@@ -299,6 +316,8 @@ var Urban95ModeController = requireNamespace(window, "Urban95ModeController");
299316
requireNamespaceMember(Urban95ModeController, "Urban95ModeController", "create", "function");
300317
var Urban95MapEvents = requireNamespace(window, "Urban95MapEvents");
301318
requireNamespaceMember(Urban95MapEvents, "Urban95MapEvents", "bind", "function");
319+
var Urban95OverlayVisibility = requireNamespace(window, "Urban95OverlayVisibility");
320+
requireNamespaceMember(Urban95OverlayVisibility, "Urban95OverlayVisibility", "create", "function");
302321
var Urban95MapRenderers = requireNamespace(window, "Urban95MapRenderers");
303322
[
304323
"configure",
@@ -320,7 +339,6 @@ var Urban95MapRenderers = requireNamespace(window, "Urban95MapRenderers");
320339
"scheduleDeckUpdate",
321340
"initDeckAmenityOverlay",
322341
"updateBuildingColors",
323-
"updateAccessibilityLegendLabels",
324342
"updateNeighborhoodSurfaceData",
325343
"updateNeighborhoodColors",
326344
].forEach(function (memberName) {
@@ -362,6 +380,7 @@ window.Urban95AppDependencies = {
362380
BUILDINGS_URL: BUILDINGS_URL,
363381
ROADS_URL: ROADS_URL,
364382
EDUCATION_URL: EDUCATION_URL,
383+
BUS_STOPS_URL: BUS_STOPS_URL,
365384
POPULATION_GRID_URL: POPULATION_GRID_URL,
366385
SOCIOECONOMIC_URL: SOCIOECONOMIC_URL,
367386
BUILDINGS_LOOKUP_URL: BUILDINGS_LOOKUP_URL,
@@ -417,6 +436,7 @@ window.Urban95AppDependencies = {
417436
formatScoreInteger: formatScoreInteger,
418437
weightedCategoryHighlightsFromSource: weightedCategoryHighlightsFromSource,
419438
weightedSubcategoryComparisonRows: weightedSubcategoryComparisonRows,
439+
Urban95WeightedMetricShowRegistry: Urban95WeightedMetricShowRegistry,
420440
Urban95ScoreContext: Urban95ScoreContext,
421441
Urban95ScoreExplain: Urban95ScoreExplain,
422442
Urban95ScoreSidebarChrome: Urban95ScoreSidebarChrome,
@@ -428,6 +448,7 @@ window.Urban95AppDependencies = {
428448
createBaseMap: createBaseMap,
429449
applyBasemap: applyBasemap,
430450
Urban95NeighborhoodScores: Urban95NeighborhoodScores,
451+
Urban95RenderState: Urban95RenderState,
431452
resolveBuildingContracts: resolveBuildingContracts,
432453
createPmtilesProtocol: createPmtilesProtocol,
433454
createBuildingsSource: createBuildingsSource,
@@ -440,6 +461,7 @@ window.Urban95AppDependencies = {
440461
Urban95Dashboards: Urban95Dashboards,
441462
Urban95ModeController: Urban95ModeController,
442463
Urban95MapEvents: Urban95MapEvents,
464+
Urban95OverlayVisibility: Urban95OverlayVisibility,
443465
Urban95MapRenderers: Urban95MapRenderers,
444466
Urban95Selection: Urban95Selection,
445467
Urban95Controls: Urban95Controls,

docs/js/core/appState.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@
2121
return result;
2222
}
2323

24+
function copyLayerVisibility(value) {
25+
return value && typeof value === "object" ? Object.assign({}, value) : {};
26+
}
27+
2428
function create() {
2529
var walkMinutes = 5;
2630
var scoreMode = "weighted";
2731
var selectedAmenityTypes = new Set();
32+
var weightedShownAmenityTypes = new Set();
33+
var layerVisibility = {};
34+
var activeHeatmapId = "u95.overall";
2835
var lastFilterRadioSelection = "all";
2936
var allFilterTypes = [];
3037
var amenitiesInRadiusIds = new Set();
@@ -38,8 +45,15 @@
3845
getScoreMode: function () { return scoreMode; },
3946
setScoreMode: function (value) { scoreMode = value; },
4047

48+
getLayerVisibility: function () { return copyLayerVisibility(layerVisibility); },
49+
setLayerVisibility: function (value) { layerVisibility = copyLayerVisibility(value); },
50+
getActiveHeatmapId: function () { return activeHeatmapId; },
51+
setActiveHeatmapId: function (value) { activeHeatmapId = value || "u95.overall"; },
52+
4153
getSelectedAmenityTypes: function () { return new Set(selectedAmenityTypes); },
4254
setSelectedAmenityTypes: function (value) { selectedAmenityTypes = copySet(value); },
55+
getWeightedShownAmenityTypes: function () { return new Set(weightedShownAmenityTypes); },
56+
setWeightedShownAmenityTypes: function (value) { weightedShownAmenityTypes = copySet(value); },
4357
getLastFilterRadioSelection: function () { return lastFilterRadioSelection; },
4458
setLastFilterRadioSelection: function (value) { lastFilterRadioSelection = value || "all"; },
4559
getAllFilterTypes: function () { return allFilterTypes.slice(); },

docs/js/core/config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
parks: BASE + "/parks.geojson",
99
urbanNatureAreas: BASE + "/urban_nature_areas.geojson",
1010
education: BASE + "/education.geojson",
11+
busStops: BASE + "/bus_stops.geojson",
1112
populationGrid: BASE + "/population-grid.geojson",
1213
socioeconomic: BASE + "/Beersheva_socioeconomic_statareas2023.geojson",
1314
trees: BASE + "/trees.geojson",
@@ -31,6 +32,12 @@
3132
streetLightsPmtiles: BASE + "/street_lights.pmtiles",
3233
};
3334

35+
var generatedArtifactPolicies = {
36+
neighborhood_surface: {
37+
useGeneratedAsset: false,
38+
},
39+
};
40+
3441
var mapContracts = {
3542
buildingSourceLayerFallback: "buildings",
3643
neighborhoodSurfaceSourceLayerFallback: "neighborhood_surface",
@@ -78,6 +85,7 @@
7885
ICONS_BASE: ICONS_BASE,
7986
urls: urls,
8087
generatedFallbacks: generatedFallbacks,
88+
generatedArtifactPolicies: generatedArtifactPolicies,
8189
mapContracts: mapContracts,
8290
cdn: cdn,
8391
sources: sources,

0 commit comments

Comments
 (0)