feat: filtros em cascata estado, cidade e tópico no lugar de assunto
This commit is contained in:
+95
-26
@@ -211,6 +211,7 @@
|
|||||||
}
|
}
|
||||||
/* the popup list is painted by the OS — give it a real ground, not the
|
/* the popup list is painted by the OS — give it a real ground, not the
|
||||||
panel's translucent one, or it inherits the wall behind the field */
|
panel's translucent one, or it inherits the wall behind the field */
|
||||||
|
.f-row select:disabled { opacity: .45; }
|
||||||
.f-row select { text-overflow: ellipsis; } /* long author names on a narrow phone */
|
.f-row select { text-overflow: ellipsis; } /* long author names on a narrow phone */
|
||||||
.f-row option { background: #15151a; color: var(--f-text); }
|
.f-row option { background: #15151a; color: var(--f-text); }
|
||||||
.f-row input::placeholder { color: var(--f-faint); }
|
.f-row input::placeholder { color: var(--f-faint); }
|
||||||
@@ -589,8 +590,16 @@
|
|||||||
<select id="f-author"><option value="">todos</option></select>
|
<select id="f-author"><option value="">todos</option></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="f-row">
|
<div class="f-row">
|
||||||
<label for="f-subject">Assunto</label>
|
<label for="f-estado">Estado</label>
|
||||||
<select id="f-subject"><option value="">todos</option></select>
|
<select id="f-estado"><option value="">todos</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="f-row">
|
||||||
|
<label for="f-cidade">Cidade</label>
|
||||||
|
<select id="f-cidade" disabled><option value="">escolha um estado</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="f-row">
|
||||||
|
<label for="f-topico">Tópico</label>
|
||||||
|
<select id="f-topico"><option value="">todos</option></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="f-row">
|
<div class="f-row">
|
||||||
<label for="f-type">Tipo de suporte</label>
|
<label for="f-type">Tipo de suporte</label>
|
||||||
@@ -1483,9 +1492,38 @@ function renderSubjectTree(el, meta) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// estado / cidade / tópico facets of one piece, as sets of plain keys:
|
||||||
|
// _st "S" _ct "S|C" _tp "S|C|T" · "S||T" · "||T" (a topic is findable at
|
||||||
|
// any level of the cascade; a theme with no place only at the top one)
|
||||||
|
const CATALOGUE_TAG = /^cole[cç][aã]o\b|^(?:externa|interna|diurna|noturna|horizontal|vertical)$/i;
|
||||||
|
function subjectFacets(meta) {
|
||||||
|
const st = new Set(), ct = new Set(), tp = new Set();
|
||||||
|
const { root, others } = subjectTree(meta);
|
||||||
|
const brasil = root.kids.get('Brasil');
|
||||||
|
const topics = (node, path, add) => node.kids.forEach((kid, name) => {
|
||||||
|
const p = path.concat(name);
|
||||||
|
add(p.join(' - '));
|
||||||
|
topics(kid, p, add);
|
||||||
|
});
|
||||||
|
if (brasil) brasil.kids.forEach((stNode, sName) => {
|
||||||
|
st.add(sName);
|
||||||
|
stNode.kids.forEach((kid, name) => {
|
||||||
|
const addTopic = t => { tp.add(`${sName}||${t}`); tp.add(`||${t}`); };
|
||||||
|
if (kid.place) {
|
||||||
|
ct.add(`${sName}|${name}`);
|
||||||
|
topics(kid, [], t => { tp.add(`${sName}|${name}|${t}`); addTopic(t); });
|
||||||
|
} else {
|
||||||
|
addTopic(name); topics(kid, [name], addTopic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
others.filter(t => deburr(t) !== 'brasil' && !CATALOGUE_TAG.test(t.trim())).forEach(t => tp.add(`||${t}`));
|
||||||
|
return { st, ct, tp };
|
||||||
|
}
|
||||||
|
|
||||||
function setupFilters(mapData) {
|
function setupFilters(mapData) {
|
||||||
const entries = Object.values(mapData.map);
|
const entries = Object.values(mapData.map);
|
||||||
const instCounts = new Map(), typeCounts = new Map(), authorGroups = new Map(), subjectGroups = new Map();
|
const instCounts = new Map(), typeCounts = new Map(), authorGroups = new Map();
|
||||||
let minYear = Infinity, maxYear = -Infinity;
|
let minYear = Infinity, maxYear = -Infinity;
|
||||||
|
|
||||||
entries.forEach(e => {
|
entries.forEach(e => {
|
||||||
@@ -1493,9 +1531,6 @@ function setupFilters(mapData) {
|
|||||||
if (meta.community) instCounts.set(meta.community, (instCounts.get(meta.community) || 0) + 1);
|
if (meta.community) instCounts.set(meta.community, (instCounts.get(meta.community) || 0) + 1);
|
||||||
const t = cleanType(meta.type);
|
const t = cleanType(meta.type);
|
||||||
if (t) typeCounts.set(t, (typeCounts.get(t) || 0) + 1);
|
if (t) typeCounts.set(t, (typeCounts.get(t) || 0) + 1);
|
||||||
subjectsOf(meta).map(subjectKey).forEach(k => {
|
|
||||||
subjectGroups.set(k, (subjectGroups.get(k) || 0) + 1);
|
|
||||||
});
|
|
||||||
const a = cleanAuthor(meta.contributor);
|
const a = cleanAuthor(meta.contributor);
|
||||||
if (a) {
|
if (a) {
|
||||||
const k = authorKey(a), g = authorGroups.get(k) || { n: 0, forms: new Map() };
|
const k = authorKey(a), g = authorGroups.get(k) || { n: 0, forms: new Map() };
|
||||||
@@ -1533,18 +1568,6 @@ function setupFilters(mapData) {
|
|||||||
selAuthor.appendChild(opt);
|
selAuthor.appendChild(opt);
|
||||||
});
|
});
|
||||||
|
|
||||||
const subjectNames = new Map(); // key → a readable spelling
|
|
||||||
entries.forEach(e => subjectsOf(e.meta).forEach(t => {
|
|
||||||
const k = subjectKey(t);
|
|
||||||
if (k && subjectGroups.get(k) >= SUBJECT_MIN && !subjectNames.has(k)) subjectNames.set(k, String(t).trim());
|
|
||||||
}));
|
|
||||||
const selSubject = document.getElementById('f-subject');
|
|
||||||
[...subjectGroups.entries()].filter(([, n]) => n >= SUBJECT_MIN).sort((a, b) => b[1] - a[1]).forEach(([k, n]) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = k; opt.textContent = `${subjectNames.get(k)} (${n.toLocaleString('pt-BR')})`;
|
|
||||||
selSubject.appendChild(opt);
|
|
||||||
});
|
|
||||||
|
|
||||||
const decadeMin = Math.floor(minYear / 10) * 10;
|
const decadeMin = Math.floor(minYear / 10) * 10;
|
||||||
const decadeMax = Math.floor(maxYear / 10) * 10;
|
const decadeMax = Math.floor(maxYear / 10) * 10;
|
||||||
const nDecades = (decadeMax - decadeMin) / 10 + 1;
|
const nDecades = (decadeMax - decadeMin) / 10 + 1;
|
||||||
@@ -1576,7 +1599,7 @@ function setupFilters(mapData) {
|
|||||||
const a = cleanAuthor(meta.contributor);
|
const a = cleanAuthor(meta.contributor);
|
||||||
m.userData._author = a ? authorKey(a) : '';
|
m.userData._author = a ? authorKey(a) : '';
|
||||||
m.userData._type = cleanType(meta.type);
|
m.userData._type = cleanType(meta.type);
|
||||||
m.userData._subjects = new Set(subjectsOf(meta).map(subjectKey));
|
Object.assign(m.userData, (({ st, ct, tp }) => ({ _st: st, _ct: ct, _tp: tp }))(subjectFacets(meta)));
|
||||||
// one haystack per piece: every metadata field, not a chosen few
|
// one haystack per piece: every metadata field, not a chosen few
|
||||||
// the handle is the map's key, not a field of meta — it still has to be
|
// the handle is the map's key, not a field of meta — it still has to be
|
||||||
// searchable (people paste a handle or its number straight into the box)
|
// searchable (people paste a handle or its number straight into the box)
|
||||||
@@ -1601,7 +1624,43 @@ function setupFilters(mapData) {
|
|||||||
bars.push(bar);
|
bars.push(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = { search: '', institution: '', yearLo: decadeMin, yearHi: decadeMax, yearTouched: false, author: '', type: '', subject: '' };
|
const selEstado = document.getElementById('f-estado');
|
||||||
|
const selCidade = document.getElementById('f-cidade');
|
||||||
|
const selTopico = document.getElementById('f-topico');
|
||||||
|
const fillSelect = (sel, rows, first) => {
|
||||||
|
sel.replaceChildren(Object.assign(document.createElement('option'), { value: '', textContent: first }));
|
||||||
|
rows.forEach(([value, n]) => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = value; opt.textContent = `${value} (${n.toLocaleString('pt-BR')})`;
|
||||||
|
sel.appendChild(opt);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const tally = (prefix, keyOf, min) => {
|
||||||
|
const c = new Map();
|
||||||
|
tileMeshes.forEach(m => keyOf(m.userData).forEach(k => {
|
||||||
|
if (k.startsWith(prefix)) { const v = k.slice(prefix.length); c.set(v, (c.get(v) || 0) + 1); }
|
||||||
|
}));
|
||||||
|
return [...c].filter(([, n]) => n >= min).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'pt'));
|
||||||
|
};
|
||||||
|
// topics under the current place: the whole catalogue is thousands, so the top
|
||||||
|
// level lists only recurring ones; inside a state or city every topic is shown
|
||||||
|
function topicPrefix() { return state.estado ? (state.cidade ? `${state.estado}|${state.cidade}|` : `${state.estado}||`) : '||'; }
|
||||||
|
function rebuildPlace() {
|
||||||
|
fillSelect(selCidade, state.estado ? tally(`${state.estado}|`, d => d._ct, 1) : [], state.estado ? 'todas' : 'escolha um estado');
|
||||||
|
selCidade.disabled = !state.estado;
|
||||||
|
selCidade.value = state.cidade;
|
||||||
|
fillSelect(selTopico, tally(topicPrefix(), d => d._tp, state.estado ? 1 : SUBJECT_MIN), 'todos');
|
||||||
|
selTopico.value = state.topico;
|
||||||
|
}
|
||||||
|
// changing a level empties the ones below it, whose lists just changed
|
||||||
|
function resetPlace(level, rebuild) {
|
||||||
|
if (level === 'estado') { if (!rebuild) { state.estado = ''; selEstado.value = ''; } state.cidade = ''; }
|
||||||
|
if (level === 'cidade' && !rebuild) { state.cidade = ''; selCidade.value = ''; }
|
||||||
|
state.topico = '';
|
||||||
|
rebuildPlace();
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = { search: '', institution: '', yearLo: decadeMin, yearHi: decadeMax, yearTouched: false, author: '', type: '', estado: '', cidade: '', topico: '' };
|
||||||
|
|
||||||
function resetYear() {
|
function resetYear() {
|
||||||
state.yearTouched = false;
|
state.yearTouched = false;
|
||||||
@@ -1623,7 +1682,9 @@ function setupFilters(mapData) {
|
|||||||
(!state.institution || meta.community === state.institution) &&
|
(!state.institution || meta.community === state.institution) &&
|
||||||
(!state.type || d._type === state.type) &&
|
(!state.type || d._type === state.type) &&
|
||||||
(!state.author || d._author === state.author) &&
|
(!state.author || d._author === state.author) &&
|
||||||
(!state.subject || d._subjects.has(state.subject)) &&
|
(!state.estado || d._st.has(state.estado)) &&
|
||||||
|
(!state.cidade || d._ct.has(`${state.estado}|${state.cidade}`)) &&
|
||||||
|
(!state.topico || d._tp.has(topicPrefix() + state.topico)) &&
|
||||||
terms.every(t => d._hay.includes(t));
|
terms.every(t => d._hay.includes(t));
|
||||||
if (okRest && d._year != null) {
|
if (okRest && d._year != null) {
|
||||||
const i = Math.floor((d._year - decadeMin) / 10);
|
const i = Math.floor((d._year - decadeMin) / 10);
|
||||||
@@ -1684,7 +1745,9 @@ function setupFilters(mapData) {
|
|||||||
if (state.institution) out.push({ key: 'institution', text: state.institution });
|
if (state.institution) out.push({ key: 'institution', text: state.institution });
|
||||||
if (state.yearTouched) out.push({ key: 'year', text: yearLabel(state.yearLo, state.yearHi) });
|
if (state.yearTouched) out.push({ key: 'year', text: yearLabel(state.yearLo, state.yearHi) });
|
||||||
if (state.author) out.push({ key: 'author', text: authorNames.get(state.author) });
|
if (state.author) out.push({ key: 'author', text: authorNames.get(state.author) });
|
||||||
if (state.subject) out.push({ key: 'subject', text: subjectNames.get(state.subject) });
|
if (state.estado) out.push({ key: 'estado', text: state.estado });
|
||||||
|
if (state.cidade) out.push({ key: 'cidade', text: state.cidade });
|
||||||
|
if (state.topico) out.push({ key: 'topico', text: state.topico });
|
||||||
if (state.type) out.push({ key: 'type', text: state.type });
|
if (state.type) out.push({ key: 'type', text: state.type });
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -1693,7 +1756,9 @@ function setupFilters(mapData) {
|
|||||||
if (key === 'search') { state.search = ''; searchInput.value = ''; }
|
if (key === 'search') { state.search = ''; searchInput.value = ''; }
|
||||||
if (key === 'institution') { state.institution = ''; selInst.value = ''; }
|
if (key === 'institution') { state.institution = ''; selInst.value = ''; }
|
||||||
if (key === 'type') { state.type = ''; selType.value = ''; }
|
if (key === 'type') { state.type = ''; selType.value = ''; }
|
||||||
if (key === 'subject') { state.subject = ''; selSubject.value = ''; }
|
if (key === 'estado') resetPlace('estado');
|
||||||
|
if (key === 'cidade') resetPlace('cidade');
|
||||||
|
if (key === 'topico') { state.topico = ''; selTopico.value = ''; }
|
||||||
if (key === 'author') { state.author = ''; selAuthor.value = ''; }
|
if (key === 'author') { state.author = ''; selAuthor.value = ''; }
|
||||||
if (key === 'year') resetYear();
|
if (key === 'year') resetYear();
|
||||||
apply();
|
apply();
|
||||||
@@ -1735,7 +1800,9 @@ function setupFilters(mapData) {
|
|||||||
});
|
});
|
||||||
searchX.addEventListener('click', () => { clearTimeout(searchTimer); clearOne('search'); searchInput.focus(); });
|
searchX.addEventListener('click', () => { clearTimeout(searchTimer); clearOne('search'); searchInput.focus(); });
|
||||||
|
|
||||||
selSubject.addEventListener('change', () => { state.subject = selSubject.value; apply(); });
|
selEstado.addEventListener('change', () => { state.estado = selEstado.value; resetPlace('estado', true); apply(); });
|
||||||
|
selCidade.addEventListener('change', () => { state.cidade = selCidade.value; resetPlace('cidade', true); apply(); });
|
||||||
|
selTopico.addEventListener('change', () => { state.topico = selTopico.value; apply(); });
|
||||||
selAuthor.addEventListener('change', () => { state.author = selAuthor.value; apply(); });
|
selAuthor.addEventListener('change', () => { state.author = selAuthor.value; apply(); });
|
||||||
|
|
||||||
function onYear(e) {
|
function onYear(e) {
|
||||||
@@ -1755,12 +1822,14 @@ function setupFilters(mapData) {
|
|||||||
|
|
||||||
clearBtn.addEventListener('click', () => {
|
clearBtn.addEventListener('click', () => {
|
||||||
clearTimeout(searchTimer);
|
clearTimeout(searchTimer);
|
||||||
state.search = ''; state.institution = ''; state.type = ''; state.author = ''; state.subject = '';
|
state.search = ''; state.institution = ''; state.type = ''; state.author = ''; state.estado = state.cidade = state.topico = '';
|
||||||
searchInput.value = ''; selInst.value = ''; selType.value = ''; selAuthor.value = ''; selSubject.value = '';
|
searchInput.value = ''; selInst.value = ''; selType.value = ''; selAuthor.value = ''; selEstado.value = ''; rebuildPlace();
|
||||||
resetYear();
|
resetYear();
|
||||||
apply();
|
apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fillSelect(selEstado, tally('', d => d._st, 1), 'todos');
|
||||||
|
rebuildPlace();
|
||||||
apply();
|
apply();
|
||||||
toggleFilters(!isMobile); // on a phone the wall matters more than the panel
|
toggleFilters(!isMobile); // on a phone the wall matters more than the panel
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user