feat: Add pagination and random display feature
This commit is contained in:
+29
-10
@@ -253,12 +253,13 @@ class _SAADataStore:
|
||||
result.append({"name": g, "count": counts.get(g, 0)})
|
||||
return result
|
||||
|
||||
def list_characters(self, search="", group="All", limit=120, favorites_only=False):
|
||||
def list_characters(self, search="", group="All", limit=100, page=1, favorites_only=False, seed=None):
|
||||
with self._lock:
|
||||
chars = self._characters
|
||||
favs = self._favorites
|
||||
q = (search or "").strip().lower()
|
||||
result = []
|
||||
|
||||
filtered = []
|
||||
for item in chars:
|
||||
if favorites_only and item["id"] not in favs:
|
||||
continue
|
||||
@@ -267,6 +268,19 @@ class _SAADataStore:
|
||||
if q:
|
||||
if q not in item["name_en"].lower() and q not in item["name_zh"].lower() and q not in item["origin"].lower():
|
||||
continue
|
||||
filtered.append(item)
|
||||
|
||||
if seed is not None and seed != "":
|
||||
import random
|
||||
rng = random.Random(seed)
|
||||
rng.shuffle(filtered)
|
||||
|
||||
total = len(filtered)
|
||||
start_idx = (page - 1) * limit
|
||||
end_idx = start_idx + limit
|
||||
|
||||
result = []
|
||||
for item in filtered[start_idx:end_idx]:
|
||||
result.append(
|
||||
{
|
||||
"id": item["id"],
|
||||
@@ -277,9 +291,7 @@ class _SAADataStore:
|
||||
"is_favorite": item["id"] in favs,
|
||||
}
|
||||
)
|
||||
if len(result) >= limit:
|
||||
break
|
||||
return result
|
||||
return {"items": result, "total": total, "page": page, "limit": limit}
|
||||
|
||||
def get_character(self, char_id):
|
||||
item = self._resolve_character(char_id)
|
||||
@@ -387,13 +399,20 @@ async def saa_selector_characters(request):
|
||||
search = request.query.get("search", "")
|
||||
group = request.query.get("group", "All")
|
||||
fav_only = request.query.get("favorites_only", "false").lower() == "true"
|
||||
seed = request.query.get("seed", "")
|
||||
try:
|
||||
limit = int(request.query.get("limit", "120"))
|
||||
limit = int(request.query.get("limit", "100"))
|
||||
except ValueError:
|
||||
limit = 120
|
||||
limit = max(1, min(600, limit))
|
||||
items = STORE.list_characters(search=search, group=group, limit=limit, favorites_only=fav_only)
|
||||
return web.json_response({"items": items})
|
||||
limit = 100
|
||||
limit = max(1, limit)
|
||||
try:
|
||||
page = int(request.query.get("page", "1"))
|
||||
except ValueError:
|
||||
page = 1
|
||||
page = max(1, page)
|
||||
|
||||
data = STORE.list_characters(search=search, group=group, limit=limit, page=page, favorites_only=fav_only, seed=seed)
|
||||
return web.json_response(data)
|
||||
|
||||
@PromptServer.instance.routes.post("/saa_selector/favorite/{char_id}")
|
||||
async def saa_selector_toggle_favorite(request):
|
||||
|
||||
+72
-2
@@ -206,6 +206,8 @@ function attachUI(node) {
|
||||
favToggleBtn.textContent = "☆ Fav";
|
||||
favToggleBtn.style.color = "#ccc";
|
||||
}
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
loadCharacters().catch((err) => {
|
||||
status.textContent = `Filter failed: ${String(err)}`;
|
||||
});
|
||||
@@ -221,6 +223,49 @@ function attachUI(node) {
|
||||
top.appendChild(favToggleBtn);
|
||||
top.appendChild(refreshBtn);
|
||||
|
||||
const paginationRow = document.createElement("div");
|
||||
paginationRow.className = "saa-top";
|
||||
|
||||
const randomBtn = document.createElement("button");
|
||||
randomBtn.type = "button";
|
||||
randomBtn.textContent = "Random";
|
||||
|
||||
const prevBtn = document.createElement("button");
|
||||
prevBtn.type = "button";
|
||||
prevBtn.textContent = "<";
|
||||
|
||||
const pageLabel = document.createElement("span");
|
||||
pageLabel.style.fontSize = "12px";
|
||||
pageLabel.style.color = "#ccc";
|
||||
pageLabel.textContent = "1 / 1";
|
||||
|
||||
const nextBtn = document.createElement("button");
|
||||
nextBtn.type = "button";
|
||||
nextBtn.textContent = ">";
|
||||
|
||||
paginationRow.appendChild(randomBtn);
|
||||
paginationRow.appendChild(prevBtn);
|
||||
paginationRow.appendChild(pageLabel);
|
||||
paginationRow.appendChild(nextBtn);
|
||||
|
||||
randomBtn.addEventListener("click", () => {
|
||||
currentSeed = String(Math.floor(Math.random() * 1000000));
|
||||
currentPage = 1;
|
||||
loadCharacters().catch(err => status.textContent = `Error: ${String(err)}`);
|
||||
});
|
||||
|
||||
prevBtn.addEventListener("click", () => {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
loadCharacters().catch(err => status.textContent = `Error: ${String(err)}`);
|
||||
}
|
||||
});
|
||||
|
||||
nextBtn.addEventListener("click", () => {
|
||||
currentPage++;
|
||||
loadCharacters().catch(err => status.textContent = `Error: ${String(err)}`);
|
||||
});
|
||||
|
||||
const progressRow = document.createElement("div");
|
||||
progressRow.className = "saa-progress-row";
|
||||
const progress = document.createElement("progress");
|
||||
@@ -265,6 +310,7 @@ function attachUI(node) {
|
||||
gridRow.appendChild(scrollWrap);
|
||||
|
||||
wrap.appendChild(top);
|
||||
wrap.appendChild(paginationRow);
|
||||
wrap.appendChild(progressRow);
|
||||
wrap.appendChild(status);
|
||||
wrap.appendChild(gridRow);
|
||||
@@ -281,6 +327,9 @@ function attachUI(node) {
|
||||
let hasHydratedAfterReady = false;
|
||||
let lastSyncedGroupValue = "";
|
||||
let suppressWidgetCallback = false;
|
||||
|
||||
let currentPage = 1;
|
||||
let currentSeed = "";
|
||||
|
||||
function updateResponsiveColumns() {
|
||||
const width = Math.max(320, wrap.clientWidth || 320);
|
||||
@@ -365,11 +414,20 @@ function attachUI(node) {
|
||||
const q = encodeURIComponent(search.value || "");
|
||||
const g = encodeURIComponent(group.value || "All");
|
||||
const favOnly = node.__saaFavoritesOnly ? "true" : "false";
|
||||
const data = await apiGet(`/saa_selector/characters?search=${q}&group=${g}&limit=200&favorites_only=${favOnly}`);
|
||||
const s = encodeURIComponent(currentSeed || "");
|
||||
const data = await apiGet(`/saa_selector/characters?search=${q}&group=${g}&limit=100&page=${currentPage}&seed=${s}&favorites_only=${favOnly}`);
|
||||
renderCards(node, grid, data.items || []);
|
||||
updateResponsiveColumns();
|
||||
updateScrollProgressFromGrid();
|
||||
status.textContent = `Loaded ${data.items?.length || 0} items`;
|
||||
|
||||
const total = data.total || 0;
|
||||
const limit = data.limit || 100;
|
||||
const pages = Math.max(1, Math.ceil(total / limit));
|
||||
pageLabel.textContent = `${currentPage} / ${pages}`;
|
||||
prevBtn.disabled = currentPage <= 1;
|
||||
nextBtn.disabled = currentPage >= pages;
|
||||
|
||||
status.textContent = `Loaded ${data.items?.length || 0} items (Total: ${total})`;
|
||||
}
|
||||
|
||||
async function syncFromSourceWidget(force = false) {
|
||||
@@ -383,6 +441,8 @@ function attachUI(node) {
|
||||
}
|
||||
if (force || nextGroup !== lastSyncedGroupValue) {
|
||||
lastSyncedGroupValue = nextGroup;
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
await loadCharacters();
|
||||
}
|
||||
}
|
||||
@@ -424,6 +484,8 @@ function attachUI(node) {
|
||||
pLabel.textContent = "0%";
|
||||
hasHydratedAfterReady = false;
|
||||
lastIsLoading = true;
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
await apiPost("/saa_selector/reload");
|
||||
refreshStatus();
|
||||
});
|
||||
@@ -431,6 +493,8 @@ function attachUI(node) {
|
||||
search.addEventListener("input", () => {
|
||||
if (searchTimer) clearTimeout(searchTimer);
|
||||
searchTimer = setTimeout(() => {
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
loadCharacters().catch((err) => {
|
||||
status.textContent = `Search failed: ${String(err)}`;
|
||||
});
|
||||
@@ -438,6 +502,8 @@ function attachUI(node) {
|
||||
});
|
||||
searchClearBtn.addEventListener("click", () => {
|
||||
search.value = "";
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
loadCharacters().catch((err) => {
|
||||
status.textContent = `Search failed: ${String(err)}`;
|
||||
});
|
||||
@@ -455,6 +521,8 @@ function attachUI(node) {
|
||||
sourceWidget.value = "All";
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
loadCharacters().catch((err) => {
|
||||
status.textContent = `Filter failed: ${String(err)}`;
|
||||
});
|
||||
@@ -477,6 +545,8 @@ function attachUI(node) {
|
||||
suppressWidgetCallback = false;
|
||||
node.setDirtyCanvas(true, true);
|
||||
}
|
||||
currentPage = 1;
|
||||
currentSeed = "";
|
||||
loadCharacters().catch((err) => {
|
||||
status.textContent = `Filter failed: ${String(err)}`;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user