Skip to content

Commit 6e7460e

Browse files
Dean SoferDean Sofer
authored andcommitted
Trying to fix 19hz tags
1 parent 394d636 commit 6e7460e

2 files changed

Lines changed: 32 additions & 74 deletions

File tree

19hz/index.html

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta charset="utf-8">
66
<title>19hz Mapper</title>
77
<meta name="description"
8-
content="The Unofficial Map of 19hz Events Updated Daily! Pick a day and a category to plan activities or just clear the filters and browse through all the most popular events the Bay Area has to offer. Free. Open Source. No registration necessary. Works with Mobile and Tablets as well as Computers!" />
8+
content="The Unofficial Map of 19hz Events Updated Daily! Pick a day and a tag to plan activities or just clear the filters and browse through all the most popular events the Bay Area has to offer. Free. Open Source. No registration necessary. Works with Mobile and Tablets as well as Computers!" />
99
<link rel="stylesheet" href="../style.css">
1010
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
1111
<link rel="icon" href="../favicon_fc.ico" />
@@ -24,7 +24,7 @@
2424
Feedback
2525
</a>
2626

27-
<form id="controls" onreset="filter({ date: '', category: '' })">
27+
<form id="controls" onreset="filter({ date: '', tag: '' })">
2828
<details open>
2929
<summary>
3030
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="filter-icon">
@@ -38,46 +38,12 @@
3838
<button type="reset">Clear</button>
3939
</p>
4040
<p>
41-
<label for="category">Categories: <output name="countCategories" for="category">All</output></label>
41+
<label for="tag">Tags: <output name="countTags" for="tag">All</output></label>
4242
</p>
4343
<p>
44-
<!-- <select id="category" onchange="filter({ category: this.value })"> -->
45-
<select id="category" name="category" multiple
46-
onchange="filter({ category: Array.from(this.selectedOptions).map(o => o.value).join('~') })">
47-
<!-- These are programmatically replaced after data loads -->
48-
<option value="**Annual Event**">Annual Event</option>
49-
<option value="*Top Pick*">Top Pick</option>
50-
<option value="420">420</option>
51-
<option value="4th of July">4th of July</option>
52-
<option value="Top Annual Event">Top Annual Event</option>
53-
<option value="Adults Only">Adults Only</option>
54-
<option value="Art & Museums">Art &amp; Museums</option>
55-
<option value="Charity & Volunteering">Charity &amp; Volunteering</option>
56-
<option value="Cheap Drinks">Cheap Drinks</option>
57-
<option value="Club / DJ">Club / DJ</option>
58-
<option value="Comedy">Comedy</option>
59-
<option value="Discount Tix / Promo Codes">Discount Tix / Promo Codes</option>
60-
<option value="Eating & Drinking">Eating &amp; Drinking</option>
61-
<option value="Fairs & Festivals">Fairs &amp; Festivals</option>
62-
<option value="Free Food">Free Food</option>
63-
<option value="Fun & Games">Fun &amp; Games</option>
64-
<option value="Geek Event">Geek Event</option>
65-
<option value="In Person">In Person</option>
66-
<option value="Kids & Families">Kids &amp; Families</option>
67-
<option value="Lectures & Workshops">Lectures &amp; Workshops</option>
68-
<option value="LGBTQ+">LGBTQ+</option>
69-
<option value="Literature">Literature</option>
70-
<option value="Live Music">Live Music</option>
71-
<option value="Movies">Movies</option>
72-
<option value="Outdoors">Outdoors</option>
73-
<option value="Other">Other</option>
74-
<option value="Rainy Day Fun">Rainy Day Fun</option>
75-
<option value="San FranFREEsco">San FranFREEsco</option>
76-
<option value="Shopping & Fashion">Shopping &amp; Fashion</option>
77-
<option value="Sponsored">Sponsored</option>
78-
<option value="Sports & Wellness">Sports &amp; Wellness</option>
79-
<option value="Theater & Performance">Theater &amp; Performance</option>
80-
<option value="Walks & Tours">Walks &amp; Tours</option>
44+
<select id="tag" name="tag" multiple
45+
onchange="filter({ tag: Array.from(this.selectedOptions).map(o => o.value).join('~') })">
46+
<option value="" disabled>Loading…</option>
8147
</select>
8248
</p>
8349
</details>

19hz/map.js

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* global google */
2-
// Delimiter for multiple category selections (must not appear in category names and not require URI encoding)
3-
const CATEGORY_DELIMITER = '~';
2+
// Delimiter for multiple tag selections (must not appear in tag names and not require URI encoding)
3+
const TAG_DELIMITER = '~';
44

55
// Spotify API configuration
66
// For production, these should be loaded from environment variables or a secure backend
@@ -160,13 +160,12 @@ async function initialize() {
160160
console.log('Loading Events...');
161161
window.events.load()
162162
.then(events => {
163-
let minDate, maxDate, categories = new Set();
163+
let minDate, maxDate, tags = new Set();
164164
events.forEach(event => {
165165
if (!event.title) return console.warn('Event Title Missing', { event });
166166
if (!event.geometry) return console.warn('Event Geometry Missing', { event });
167-
// Add categories to the set
168-
if (event.categories) {
169-
event.categories.forEach(category => categories.add(category));
167+
if (event.tags) {
168+
event.tags.forEach(tag => tags.add(tag));
170169
}
171170
// Calculate min/max date
172171
if (event.date) {
@@ -206,9 +205,8 @@ async function initialize() {
206205
form.elements['date'].min = minDate.toLocaleDateString('fr-ca');
207206
form.elements['date'].max = maxDate.toLocaleDateString('fr-ca');
208207
}
209-
// Update the category select
210-
form.elements['category'].innerHTML = Array.from(categories).sort().map(category =>
211-
`<option value="${category}">${category}</option>`
208+
form.elements['tag'].innerHTML = Array.from(tags).sort().map(tag =>
209+
`<option value="${tag}">${tag}</option>`
212210
).join('');
213211
// Apply URL filters
214212
let filters = {};
@@ -232,10 +230,10 @@ async function initialize() {
232230

233231
let options = {};
234232
/**
235-
* Callback for datepicker, filters all visible events to specified date and category
233+
* Callback for datepicker, filters all visible events to specified date and tag
236234
* @param {object} filters
237235
* @param {string} [filters.date]
238-
* @param {string} [filters.category]
236+
* @param {string} [filters.tag]
239237
*/
240238
window.filter = async function (filters = {}) {
241239
Object.assign(options, filters);
@@ -244,9 +242,9 @@ window.filter = async function (filters = {}) {
244242
date = options.date.replace(/-/gi, '/');
245243
date = new Date(date);
246244
}
247-
let categories = [];
248-
if (options.category) {
249-
categories = options.category.split(CATEGORY_DELIMITER)
245+
let selectedTags = [];
246+
if (options.tag) {
247+
selectedTags = options.tag.split(TAG_DELIMITER)
250248
}
251249

252250
// Clear existing cluster markers
@@ -277,8 +275,7 @@ window.filter = async function (filters = {}) {
277275
}
278276
}
279277
}
280-
// check category
281-
if (categories.length && !event.categories.some(category => categories.includes(category))) {
278+
if (selectedTags.length && !event.tags?.some(tag => selectedTags.includes(tag))) {
282279
event.visible = false;
283280
}
284281

@@ -370,14 +367,14 @@ window.filter = async function (filters = {}) {
370367
let element = form.elements[option];
371368
if (element) {
372369
if (element.type === 'select-multiple') {
373-
const selected = options[option].split(CATEGORY_DELIMITER);
374-
for (const option of element.options) {
375-
option.selected = selected.includes(option.value)
370+
const selected = options[option].split(TAG_DELIMITER);
371+
for (const opt of element.options) {
372+
opt.selected = selected.includes(opt.value)
376373
}
377-
setTimeout(element => {
378-
element.querySelector('option:checked')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
374+
setTimeout(elem => {
375+
elem.querySelector('option:checked')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
379376
}, 200, element);
380-
query.push(encodeURIComponent(option) + '=' + categories.map(category => encodeURIComponent(category)).join(CATEGORY_DELIMITER));
377+
query.push(encodeURIComponent(option) + '=' + selectedTags.map(tag => encodeURIComponent(tag)).join(TAG_DELIMITER));
381378
} else {
382379
element.value = options[option];
383380
query.push(encodeURIComponent(option) + '=' + encodeURIComponent(options[option]));
@@ -386,7 +383,7 @@ window.filter = async function (filters = {}) {
386383
}
387384
window.history.replaceState({}, '', '?' + query.join('&'));
388385
form.elements['countEvents'].innerText = count;
389-
form.elements['countCategories'].innerText = categories.length || 'All';
386+
form.elements['countTags'].innerText = selectedTags.length || 'All';
390387
};
391388

392389
/**
@@ -592,13 +589,8 @@ class Events {
592589
.trim();
593590
}
594591

595-
/**
596-
* Normalizes category name by trimming whitespace and removing the word 'music'
597-
* @param {string} category - Raw category name
598-
* @returns {string} Normalized category name
599-
*/
600-
static normalizeCategory(category) {
601-
return category
592+
static normalizeTag(tag) {
593+
return tag
602594
.trim()
603595
.replace(/\s+music$/i, '')
604596
.trim();
@@ -864,7 +856,7 @@ class Events {
864856

865857
content.innerHTML = `
866858
<div class="info-header">
867-
<p><strong>Genres:</strong> ${event.categories.map(category => `<a onclick="filter({category:'${category}'})">${category}</a>`).join(', ')}</p>
859+
<p><strong>Tags:</strong> ${event.tags?.map(tag => `<a onclick="filter({tag:'${tag}'})">${tag}</a>`).join(', ') || '—'}</p>
868860
${hasValidArtists ? `<p><strong>Artists:</strong> ${artistsHTML}</p>` : ''}
869861
<p><strong>Age:</strong> ${ageInfo}</p>
870862
</div>
@@ -957,7 +949,7 @@ class Events {
957949
return;
958950
}
959951
const extractedArtists = Events.extractArtistsFromTitle(row.title) || 'TBA';
960-
const genresList = row.tags ? row.tags.split(',').map(genre => Events.normalizeCategory(genre)).filter(category => category) : [];
952+
const tagsList = row.tags ? row.tags.split(',').map(raw => Events.normalizeTag(raw)).filter(Boolean) : [];
961953
const eventUrl = row.url1 || row.url2 || '#';
962954
events.push({
963955
title: row.title,
@@ -969,12 +961,12 @@ class Events {
969961
cost: row.price,
970962
cost_details: row.age,
971963
extractedArtists,
972-
categories: genresList,
964+
tags: tagsList,
973965
url: eventUrl,
974966
eventUrl,
975967
promoter: row.organizers || '',
976968
details: `
977-
<p><strong>Genres:</strong> ${genresList.length ? genresList.join(', ') : 'N/A'}</p>
969+
<p><strong>Tags:</strong> ${tagsList.length ? tagsList.join(', ') : 'N/A'}</p>
978970
<p><strong>Artists:</strong> ${extractedArtists}</p>
979971
${row.organizers ? `<p><strong>Promoter:</strong> ${row.organizers}</p>` : ''}
980972
<p><strong>Age:</strong> ${row.age}</p>

0 commit comments

Comments
 (0)