Migrate filter bars to FilterSelect
Replace the bespoke SelectableFilter in all three bars with FilterSelect: enum
fields (status, type, ownership) pre-render their fixed options; model-backed
fields (game(s), platform, device) use the search endpoints with prefetch and
resolve only the selected ids to pill labels — dropping the per-page queries that
fetched every game/platform/device. filter_bar.js now reads filter-mode
SearchSelect widgets via readSearchSelect (data-included/excluded/modifier),
preserving the {value, excludes, modifier} JSON and id Number() coercion; the
redundant session game/device blocks are gone. Drop FilterBar's now-unused
platform_options param. Rebuild base.css for the inline filter-pill utilities and
update the bar tests to the new markup.
https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
This commit is contained in:
@@ -811,6 +811,9 @@
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
.sticky {
|
||||
position: sticky;
|
||||
}
|
||||
.inset-0 {
|
||||
inset: calc(var(--spacing) * 0);
|
||||
}
|
||||
@@ -1285,6 +1288,9 @@
|
||||
.ml-1 {
|
||||
margin-left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.ml-2 {
|
||||
margin-left: calc(var(--spacing) * 2);
|
||||
}
|
||||
.ml-4 {
|
||||
margin-left: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -2074,6 +2080,12 @@
|
||||
.bg-amber-50 {
|
||||
background-color: var(--color-amber-50);
|
||||
}
|
||||
.bg-amber-500\/15 {
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-black\/70 {
|
||||
background-color: color-mix(in srgb, #000 70%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2179,6 +2191,12 @@
|
||||
.bg-red-500 {
|
||||
background-color: var(--color-red-500);
|
||||
}
|
||||
.bg-red-500\/15 {
|
||||
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-red-500) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-red-600 {
|
||||
background-color: var(--color-red-600);
|
||||
}
|
||||
@@ -2562,6 +2580,9 @@
|
||||
.text-amber-500 {
|
||||
color: var(--color-amber-500);
|
||||
}
|
||||
.text-amber-600 {
|
||||
color: var(--color-amber-600);
|
||||
}
|
||||
.text-amber-800 {
|
||||
color: var(--color-amber-800);
|
||||
}
|
||||
@@ -2658,12 +2679,18 @@
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.line-through {
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
.no-underline\! {
|
||||
text-decoration-line: none !important;
|
||||
}
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
.decoration-red-400 {
|
||||
text-decoration-color: var(--color-red-400);
|
||||
}
|
||||
.decoration-slate-500 {
|
||||
text-decoration-color: var(--color-slate-500);
|
||||
}
|
||||
@@ -2913,6 +2940,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-brand {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-default {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2934,6 +2968,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-brand {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-brand-strong {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
@@ -2993,6 +3034,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-neutral-secondary-strong {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-neutral-secondary-strong);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-neutral-tertiary-medium {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
|
||||
@@ -59,62 +59,34 @@
|
||||
filter.search = { value: searchInput.value.trim(), modifier: "INCLUDES" };
|
||||
}
|
||||
|
||||
// ── Generic SelectableFilter widgets ──
|
||||
readSelectableFilters(form);
|
||||
var widgets = form.querySelectorAll("[data-selectable-filter]");
|
||||
widgets.forEach(function (w) {
|
||||
var field = w.getAttribute("data-selectable-filter");
|
||||
var inc = parseJSONAttr(w, "data-included");
|
||||
var exc = parseJSONAttr(w, "data-excluded");
|
||||
var mod = w.getAttribute("data-modifier");
|
||||
if (mod === "NOT_NULL" || mod === "IS_NULL") {
|
||||
filter[field] = { modifier: mod };
|
||||
} else if (inc.length > 0 || exc.length > 0) {
|
||||
var isIdField = field === "platform" || field === "game" || field === "device" || field === "games";
|
||||
// ── FilterSelect widgets (data-ss-mode="filter") ──
|
||||
// readSearchSelect serialises each into data-included/data-excluded/data-modifier.
|
||||
readSearchSelect(form);
|
||||
var widgets = form.querySelectorAll('[data-search-select][data-ss-mode="filter"]');
|
||||
widgets.forEach(function (widget) {
|
||||
var field = widget.getAttribute("data-name");
|
||||
var included = parseJSONAttr(widget, "data-included");
|
||||
var excluded = parseJSONAttr(widget, "data-excluded");
|
||||
var modifier = widget.getAttribute("data-modifier");
|
||||
if (modifier === "NOT_NULL" || modifier === "IS_NULL") {
|
||||
filter[field] = { modifier: modifier };
|
||||
} else if (included.length > 0 || excluded.length > 0) {
|
||||
var isIdField =
|
||||
field === "platform" ||
|
||||
field === "game" ||
|
||||
field === "device" ||
|
||||
field === "games";
|
||||
filter[field] = {
|
||||
value: isIdField ? inc.map(Number) : inc,
|
||||
excludes: isIdField ? exc.map(Number) : exc,
|
||||
modifier: mod || "INCLUDES",
|
||||
value: isIdField ? included.map(Number) : included,
|
||||
excludes: isIdField ? excluded.map(Number) : excluded,
|
||||
modifier: modifier || "INCLUDES",
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// ── Session-specific fields ──
|
||||
var pageIsSessions = !!form.querySelector('[data-selectable-filter="game"]');
|
||||
|
||||
// Game (sessions page)
|
||||
var gameWidget = form.querySelector('[data-selectable-filter="game"]');
|
||||
if (gameWidget) {
|
||||
var gIncluded = parseJSONAttr(gameWidget, "data-included");
|
||||
var gExcluded = parseJSONAttr(gameWidget, "data-excluded");
|
||||
var gMod = gameWidget.getAttribute("data-modifier");
|
||||
if (gMod === "NOT_NULL" || gMod === "IS_NULL") {
|
||||
filter.game = { modifier: gMod };
|
||||
} else if (gIncluded.length > 0 || gExcluded.length > 0) {
|
||||
filter.game = {
|
||||
value: gIncluded.map(Number),
|
||||
excludes: gExcluded.map(Number),
|
||||
modifier: gMod || "INCLUDES",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Device (sessions page)
|
||||
var deviceWidget = form.querySelector('[data-selectable-filter="device"]');
|
||||
if (deviceWidget) {
|
||||
var dIncluded = parseJSONAttr(deviceWidget, "data-included");
|
||||
var dExcluded = parseJSONAttr(deviceWidget, "data-excluded");
|
||||
var dMod = deviceWidget.getAttribute("data-modifier");
|
||||
if (dMod === "NOT_NULL" || dMod === "IS_NULL") {
|
||||
filter.device = { modifier: dMod };
|
||||
} else if (dIncluded.length > 0 || dExcluded.length > 0) {
|
||||
filter.device = {
|
||||
value: dIncluded.map(Number),
|
||||
excludes: dExcluded.map(Number),
|
||||
modifier: dMod || "INCLUDES",
|
||||
};
|
||||
}
|
||||
}
|
||||
var pageIsSessions =
|
||||
!!form.querySelector('[data-search-select][data-ss-mode="filter"][data-name="game"]');
|
||||
|
||||
// Emulated checkbox (sessions page)
|
||||
var emulated = form.querySelector('[name="filter-emulated"]');
|
||||
|
||||
Reference in New Issue
Block a user