Use toggle buttons for exif filters
Browse files- backend/app.py +7 -4
- frontend/app.js +24 -9
- frontend/style.css +37 -15
backend/app.py
CHANGED
|
@@ -501,15 +501,18 @@ def exif_items(post_ids, scanned, exif_types=None):
|
|
| 501 |
items = []
|
| 502 |
for pid in post_ids:
|
| 503 |
s = scanned.get(pid)
|
| 504 |
-
if not s
|
| 505 |
continue
|
| 506 |
-
|
|
|
|
|
|
|
|
|
|
| 507 |
continue
|
| 508 |
items.append(
|
| 509 |
{
|
| 510 |
"post_id": pid,
|
| 511 |
"url": s.get("url"),
|
| 512 |
-
"exif_type":
|
| 513 |
"scanned": True,
|
| 514 |
**image_links(pid, s.get("url")),
|
| 515 |
}
|
|
@@ -882,7 +885,7 @@ async def get_results(search_id: str, page: int = 1, exif_types: str = ""):
|
|
| 882 |
allowed = [
|
| 883 |
int(x)
|
| 884 |
for x in exif_types.split(",")
|
| 885 |
-
if x.isdigit() and int(x) in EXIF_TYPE_TO_CODE.values()
|
| 886 |
]
|
| 887 |
source = exif_items(post_ids, scanned, allowed if exif_types != "" else None)
|
| 888 |
page = max(page, 1)
|
|
|
|
| 501 |
items = []
|
| 502 |
for pid in post_ids:
|
| 503 |
s = scanned.get(pid)
|
| 504 |
+
if not s:
|
| 505 |
continue
|
| 506 |
+
exif_type = s.get("exif_type")
|
| 507 |
+
if exif_type is None and (allowed is None or 0 not in allowed):
|
| 508 |
+
continue
|
| 509 |
+
if exif_type is not None and allowed is not None and exif_type not in allowed:
|
| 510 |
continue
|
| 511 |
items.append(
|
| 512 |
{
|
| 513 |
"post_id": pid,
|
| 514 |
"url": s.get("url"),
|
| 515 |
+
"exif_type": exif_type,
|
| 516 |
"scanned": True,
|
| 517 |
**image_links(pid, s.get("url")),
|
| 518 |
}
|
|
|
|
| 885 |
allowed = [
|
| 886 |
int(x)
|
| 887 |
for x in exif_types.split(",")
|
| 888 |
+
if x.isdigit() and int(x) in (*EXIF_TYPE_TO_CODE.values(), 0)
|
| 889 |
]
|
| 890 |
source = exif_items(post_ids, scanned, allowed if exif_types != "" else None)
|
| 891 |
page = max(page, 1)
|
frontend/app.js
CHANGED
|
@@ -2,6 +2,8 @@ const $ = s => document.querySelector(s)
|
|
| 2 |
const $$ = s => document.querySelectorAll(s)
|
| 3 |
const EXIF_NAMES = { 1: "novelai", 2: "sd", 3: "comfy", 4: "mj", 5: "celsys", 6: "photoshop", 7: "stealth" }
|
| 4 |
const EXIF_CODES = Object.keys(EXIF_NAMES).map(Number)
|
|
|
|
|
|
|
| 5 |
const EXIF_FILTER_KEY = "pixif2-exif-types"
|
| 6 |
const LONG_DIGITS_RE = /\d{6,}/g
|
| 7 |
const PAGE_SIZE = 60
|
|
@@ -175,13 +177,13 @@ function renderSearchPager(data) {
|
|
| 175 |
function getExifTypes() {
|
| 176 |
const raw = localStorage.getItem(EXIF_FILTER_KEY)
|
| 177 |
if (raw === null) return [...EXIF_CODES]
|
| 178 |
-
const types = raw.split(",").map(Number).filter(n =>
|
| 179 |
return types
|
| 180 |
}
|
| 181 |
|
| 182 |
function setExifTypes(types) {
|
| 183 |
-
const sorted = [...new Set(types)].filter(n =>
|
| 184 |
-
if (sorted.length === EXIF_CODES.length) {
|
| 185 |
localStorage.removeItem(EXIF_FILTER_KEY)
|
| 186 |
} else {
|
| 187 |
localStorage.setItem(EXIF_FILTER_KEY, sorted.join(","))
|
|
@@ -191,12 +193,25 @@ function setExifTypes(types) {
|
|
| 191 |
|
| 192 |
function renderExifFilters(id) {
|
| 193 |
const active = new Set(getExifTypes())
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
setExifTypes(types)
|
| 201 |
const hash = explorerHash(id, 1)
|
| 202 |
if (location.hash === hash) openSearch(id, 1)
|
|
|
|
| 2 |
const $$ = s => document.querySelectorAll(s)
|
| 3 |
const EXIF_NAMES = { 1: "novelai", 2: "sd", 3: "comfy", 4: "mj", 5: "celsys", 6: "photoshop", 7: "stealth" }
|
| 4 |
const EXIF_CODES = Object.keys(EXIF_NAMES).map(Number)
|
| 5 |
+
const NO_EXIF_CODE = 0
|
| 6 |
+
const FILTER_CODES = [...EXIF_CODES, NO_EXIF_CODE]
|
| 7 |
const EXIF_FILTER_KEY = "pixif2-exif-types"
|
| 8 |
const LONG_DIGITS_RE = /\d{6,}/g
|
| 9 |
const PAGE_SIZE = 60
|
|
|
|
| 177 |
function getExifTypes() {
|
| 178 |
const raw = localStorage.getItem(EXIF_FILTER_KEY)
|
| 179 |
if (raw === null) return [...EXIF_CODES]
|
| 180 |
+
const types = raw.split(",").map(Number).filter(n => FILTER_CODES.includes(n))
|
| 181 |
return types
|
| 182 |
}
|
| 183 |
|
| 184 |
function setExifTypes(types) {
|
| 185 |
+
const sorted = [...new Set(types)].filter(n => FILTER_CODES.includes(n)).sort((a, b) => a - b)
|
| 186 |
+
if (sorted.length === EXIF_CODES.length && sorted.every(n => EXIF_CODES.includes(n))) {
|
| 187 |
localStorage.removeItem(EXIF_FILTER_KEY)
|
| 188 |
} else {
|
| 189 |
localStorage.setItem(EXIF_FILTER_KEY, sorted.join(","))
|
|
|
|
| 193 |
|
| 194 |
function renderExifFilters(id) {
|
| 195 |
const active = new Set(getExifTypes())
|
| 196 |
+
const buttons = FILTER_CODES.map(code => {
|
| 197 |
+
const name = code === NO_EXIF_CODE ? "None" : EXIF_NAMES[code]
|
| 198 |
+
return `<button class="filter-btn${active.has(code) ? " active" : ""}" data-code="${code}">${esc(name)}</button>`
|
| 199 |
+
}).join("")
|
| 200 |
+
$("#exif-filters").innerHTML = `${buttons}
|
| 201 |
+
<button class="filter-action" data-action="all">Select all</button>
|
| 202 |
+
<button class="filter-action" data-action="none">Select none</button>
|
| 203 |
+
<button class="filter-action update" data-action="update">Update</button>`
|
| 204 |
+
$$("#exif-filters .filter-btn").forEach(btn => {
|
| 205 |
+
btn.onclick = () => btn.classList.toggle("active")
|
| 206 |
+
})
|
| 207 |
+
$$("#exif-filters .filter-action").forEach(btn => {
|
| 208 |
+
btn.onclick = () => {
|
| 209 |
+
const action = btn.dataset.action
|
| 210 |
+
const filters = [...$$("#exif-filters .filter-btn")]
|
| 211 |
+
if (action === "all") filters.forEach(el => el.classList.add("active"))
|
| 212 |
+
if (action === "none") filters.forEach(el => el.classList.remove("active"))
|
| 213 |
+
if (action !== "update") return
|
| 214 |
+
const types = filters.filter(el => el.classList.contains("active")).map(el => Number(el.dataset.code))
|
| 215 |
setExifTypes(types)
|
| 216 |
const hash = explorerHash(id, 1)
|
| 217 |
if (location.hash === hash) openSearch(id, 1)
|
frontend/style.css
CHANGED
|
@@ -242,25 +242,47 @@ input:focus, select:focus { outline: none; border-color: var(--accent); backgrou
|
|
| 242 |
color: var(--muted);
|
| 243 |
font-size: .82rem;
|
| 244 |
}
|
| 245 |
-
.check-row {
|
| 246 |
-
display: flex;
|
| 247 |
-
align-items: center;
|
| 248 |
-
gap: .45rem;
|
| 249 |
-
color: var(--muted);
|
| 250 |
-
font-size: .85rem;
|
| 251 |
-
white-space: nowrap;
|
| 252 |
-
}
|
| 253 |
-
.check-row input {
|
| 254 |
-
width: 16px;
|
| 255 |
-
height: 16px;
|
| 256 |
-
accent-color: var(--accent);
|
| 257 |
-
}
|
| 258 |
.exif-filters {
|
| 259 |
display: flex;
|
| 260 |
flex-wrap: wrap;
|
| 261 |
justify-content: end;
|
| 262 |
-
gap: .
|
| 263 |
-
max-width:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
}
|
| 265 |
#results-grid {
|
| 266 |
display: grid;
|
|
|
|
| 242 |
color: var(--muted);
|
| 243 |
font-size: .82rem;
|
| 244 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
.exif-filters {
|
| 246 |
display: flex;
|
| 247 |
flex-wrap: wrap;
|
| 248 |
justify-content: end;
|
| 249 |
+
gap: .4rem;
|
| 250 |
+
max-width: 39rem;
|
| 251 |
+
}
|
| 252 |
+
.filter-btn, .filter-action {
|
| 253 |
+
height: 32px;
|
| 254 |
+
padding: 0 .65rem;
|
| 255 |
+
border-radius: 6px;
|
| 256 |
+
cursor: pointer;
|
| 257 |
+
font-size: .72rem;
|
| 258 |
+
font-weight: 800;
|
| 259 |
+
letter-spacing: .04rem;
|
| 260 |
+
text-transform: uppercase;
|
| 261 |
+
transition: background .15s, border-color .15s, color .15s;
|
| 262 |
+
}
|
| 263 |
+
.filter-btn {
|
| 264 |
+
background: var(--panel);
|
| 265 |
+
border: 1px solid var(--line);
|
| 266 |
+
color: var(--muted);
|
| 267 |
+
}
|
| 268 |
+
.filter-btn:hover, .filter-action:hover {
|
| 269 |
+
border-color: var(--accent);
|
| 270 |
+
color: var(--accent);
|
| 271 |
+
}
|
| 272 |
+
.filter-btn.active, .filter-action.update {
|
| 273 |
+
background: var(--accent);
|
| 274 |
+
border-color: var(--accent);
|
| 275 |
+
color: #00131c;
|
| 276 |
+
}
|
| 277 |
+
.filter-btn.active:hover, .filter-action.update:hover {
|
| 278 |
+
background: var(--accent-2);
|
| 279 |
+
border-color: var(--accent-2);
|
| 280 |
+
color: #00131c;
|
| 281 |
+
}
|
| 282 |
+
.filter-action {
|
| 283 |
+
background: transparent;
|
| 284 |
+
border: 1px solid var(--line);
|
| 285 |
+
color: var(--muted);
|
| 286 |
}
|
| 287 |
#results-grid {
|
| 288 |
display: grid;
|