Pace IGDB requests; rewrite the new help text
IGDB allows 4 requests a second, and an import searching three entries at once (plus detail lookups) went over it. Every IGDB request now waits at least 300 ms after the last one, and a 429 is retried after 1, 2 and 4 seconds before giving up. The import dialog, ranking editor and their error messages are reworded in plainer language, and talk about "spots" instead of "places". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62fcf6b45d
commit
1f3fa4fb2b
7 files changed
+60
-37
No files matched your search
+14
-11
@@ -8,15 +8,15 @@ const CONCURRENCY = 3;
|
||||
export function openImport({ list, cat, onListChange, onDone }) {
|
||||
let dlg;
|
||||
const file = h("input", { type: "file", accept: ".csv,.tsv,.txt,text/csv,text/plain,text/tab-separated-values", class: "import-file" });
|
||||
const text = h("textarea", { rows: 6, placeholder: "Or paste titles here, one per line", "aria-label": "Titles to import" });
|
||||
const text = h("textarea", { rows: 6, placeholder: "Or paste your list here", "aria-label": "Titles to import" });
|
||||
const error = h("p", { class: "alert alert--error", role: "alert", hidden: true });
|
||||
const next = h("button", { type: "submit", class: "btn btn--primary" }, "Find matches");
|
||||
const fileLabel = h("span", { class: "import-drop-text" }, h("b", {}, "Choose a .csv or .txt file"), h("span", { class: "muted small" }, "or drop it here"));
|
||||
const fileLabel = h("span", { class: "import-drop-text" }, h("b", {}, "Choose a .txt or .csv file"), h("span", { class: "muted small" }, "or drag it here"));
|
||||
const drop = h("label", { class: "import-drop" }, icon("upload"), fileLabel, file);
|
||||
|
||||
file.addEventListener("change", () => {
|
||||
const f = file.files[0];
|
||||
fileLabel.replaceChildren(h("b", {}, f ? f.name : "Choose a .csv or .txt file"), h("span", { class: "muted small" }, f ? `${Math.ceil(f.size / 1024)} KB · click to change` : "or drop it here"));
|
||||
fileLabel.replaceChildren(h("b", {}, f ? f.name : "Choose a .txt or .csv file"), h("span", { class: "muted small" }, f ? `${Math.ceil(f.size / 1024)} KB · click to pick another` : "or drag it here"));
|
||||
});
|
||||
for (const ev of ["dragenter", "dragover"]) drop.addEventListener(ev, (e) => { e.preventDefault(); drop.classList.add("is-over"); });
|
||||
for (const ev of ["dragleave", "drop"]) drop.addEventListener(ev, () => drop.classList.remove("is-over"));
|
||||
@@ -31,9 +31,12 @@ export function openImport({ list, cat, onListChange, onDone }) {
|
||||
const form = h("form", {},
|
||||
dialogHead("Import a list", () => dlg),
|
||||
h("div", { class: "dialog-body stack" },
|
||||
h("p", { class: "muted" },
|
||||
`Line 1 becomes #1, line 38 becomes #38, and a blank line leaves its place empty${list.itemCount ? " (entries already at those places move down to make room)" : ""}. Use one title per line, or a CSV with columns like Title, Year, ${artistWord(cat)} and Note. `,
|
||||
"Each one is looked up and matched for you; anything Canon isn't sure about is left for you to check."),
|
||||
h("p", {},
|
||||
"Put one title on each line. The line number is the spot on your list, so line 1 is #1 and line 38 is #38. Leave a line blank to keep that spot open.",
|
||||
list.itemCount ? " Anything already in those spots gets bumped down." : ""),
|
||||
h("p", { class: "muted small" },
|
||||
`Using a spreadsheet? Columns called Title, Year, ${artistWord(cat)} and Note are picked up automatically. `,
|
||||
"Canon looks everything up for you and only asks about the ones it isn't sure of."),
|
||||
drop,
|
||||
text,
|
||||
error,
|
||||
@@ -85,7 +88,7 @@ export function openImport({ list, cat, onListChange, onDone }) {
|
||||
const summary = h("p", { class: "import-summary", "aria-live": "polite" });
|
||||
const filter = h("label", { class: "check small" }, h("input", {
|
||||
type: "checkbox", onchange: (e) => { onlyCheck = e.target.checked; renderAll(); },
|
||||
}), "Show only ones to check");
|
||||
}), "Only show the ones to check");
|
||||
const listEl = h("ol", { class: "import-list" });
|
||||
const room = h("p", { class: "alert alert--warn", hidden: true });
|
||||
const go = h("button", { type: "button", class: "btn btn--primary", disabled: true }, "Import");
|
||||
@@ -127,8 +130,8 @@ export function openImport({ list, cat, onListChange, onDone }) {
|
||||
if (!room.hidden) {
|
||||
const want = Math.min(Math.max(list.itemCount + n, lastPlace), 65535);
|
||||
room.replaceChildren(icon("info"), lastPlace > list.maxItems
|
||||
? `The file goes up to #${fmt.format(lastPlace)}, but this list ends at #${fmt.format(list.maxItems)}. `
|
||||
: `This list has room for ${fmt.format(list.maxItems - list.itemCount)} more. `,
|
||||
? `Your file goes up to #${fmt.format(lastPlace)}, but this list stops at #${fmt.format(list.maxItems)}. `
|
||||
: `There's only room for ${fmt.format(list.maxItems - list.itemCount)} more. `,
|
||||
h("button", {
|
||||
type: "button", class: "btn btn--sm", onclick: async (e) => {
|
||||
busy(e.currentTarget, true);
|
||||
@@ -138,7 +141,7 @@ export function openImport({ list, cat, onListChange, onDone }) {
|
||||
renderTop();
|
||||
} catch (err) { toast(err.message, { error: true }); busy(e.currentTarget, false); }
|
||||
},
|
||||
}, `Raise the size to ${fmt.format(want)}`));
|
||||
}, `Make it a Top ${fmt.format(want)}`));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +194,7 @@ export function openImport({ list, cat, onListChange, onDone }) {
|
||||
content);
|
||||
const opts = e.results.map((r, idx) => option(idx, resultCard(r, idx === 0 && e.state !== "none" ? "Recommended" : "")));
|
||||
opts.push(option("manual", h("span", { class: "import-new" }, icon("plus"), h("span", {}, "Add ", h("b", {}, `“${row.title}”`), " as written")), "is-new"));
|
||||
opts.push(option("skip", h("span", { class: "muted" }, "Skip it"), "is-skip"));
|
||||
opts.push(option("skip", h("span", { class: "muted" }, "Skip"), "is-skip"));
|
||||
|
||||
const q = h("input", { type: "search", value: row.title, "aria-label": `Search again for ${row.title}` });
|
||||
const again = h("form", { class: "import-again" }, icon("search"), q,
|
||||
|
||||
Reference in new issue
Block a user