import: Add rating column (validated to be 0-10) and set import item state to FAILED if request fails

This commit is contained in:
IRHM
2026-07-30 20:47:27 +01:00
committed by momi
parent 5a5ae0ad83
commit 12ceb13bcb
4 changed files with 199 additions and 121 deletions
+4
View File
@@ -7,6 +7,7 @@ These changes are awaiting release:
- HTTP Requests: Replace `axios` with `Fetch`. - HTTP Requests: Replace `axios` with `Fetch`.
- Show `Airs On` date for episodes that have yet to air (thanks [@KarpachMarko]!). - Show `Airs On` date for episodes that have yet to air (thanks [@KarpachMarko]!).
- Show `Aired Date` for episodes. - Show `Aired Date` for episodes.
- Import: Add a `rating` column.
## Fixed ## Fixed
@@ -14,6 +15,9 @@ These changes are awaiting release:
- Removed some legacy code that still thought there was a `store.watchedList`. - Removed some legacy code that still thought there was a `store.watchedList`.
- DropDown: Fix whole page scrolling when pressing letter to scroll to first relevant dropdown item (only the dropdown list should scroll now, which is less jarring). - DropDown: Fix whole page scrolling when pressing letter to scroll to first relevant dropdown item (only the dropdown list should scroll now, which is less jarring).
- Media pages: Only show PosterImage if we have a poster path. - Media pages: Only show PosterImage if we have a poster path.
- Import:
- Mark import item as failed correctly when error is thrown from doImport.
- Make the table horizontally scrollable when necessary (instead of just cutting off the end).
## Maintenance ## Maintenance
+193 -120
View File
@@ -41,6 +41,7 @@
let isImporting = $state(false); let isImporting = $state(false);
let importText = $state(""); let importText = $state("");
let cancelled = $state(false); let cancelled = $state(false);
let importTableEl: HTMLTableElement | undefined = $state();
onDestroy(() => { onDestroy(() => {
cancelled = true; cancelled = true;
@@ -432,8 +433,47 @@
rList = rList; rList = rList;
} }
/**
* inputs that have a `data-validateme` property, we will validate with the
* browser.
*/
function validatableInputsAreValid(): boolean {
if (!importTableEl) {
// Somehow the table isn't defined, i guess allow continuing so we
// dont block.
console.warn("validatableInputsAreValid: There is no table element.");
return true;
}
const inputs = importTableEl.querySelectorAll<HTMLInputElement>(
"tbody input[data-validateme]",
);
if (inputs.length <= 0) {
console.warn("validatableInputsAreValid: No inputs found.");
return true;
}
for (const input of inputs) {
if (!input.reportValidity()) {
// Stop the for loop after we hit one input that is invalid.
// No need to continue after we know at least one is invalid.
console.error("validatableInputsAreValid: Invalid input found.", input);
return false;
}
}
return true;
}
async function startImport() { async function startImport() {
console.log(rList); if (!validatableInputsAreValid()) {
console.warn(
"startImport: Some fields are not valid, not starting import!",
);
notify({
type: "error",
text: "An error was found in one of the inputs, please fix it and try starting the import again.",
});
return;
}
console.log("startImport: Starting.", rList);
isImporting = true; isImporting = true;
window.scrollTo(0, 0); window.scrollTo(0, 0);
for (let i = 0; i < rList.length; i++) { for (let i = 0; i < rList.length; i++) {
@@ -446,6 +486,7 @@
console.log("Importing", li); console.log("Importing", li);
await doImport(li); await doImport(li);
} catch (err) { } catch (err) {
li.state = ImportResponseType.IMPORT_FAILED;
console.error("Failed to import item:", li, "reason:", err); console.error("Failed to import item:", li, "reason:", err);
notify({ notify({
type: "error", type: "error",
@@ -596,131 +637,150 @@
You can fix any failed imports when the process completes. You can fix any failed imports when the process completes.
{/if} {/if}
</h5> </h5>
<table class={isImporting ? "is-importing" : ""}> <div class="table-wrap">
<thead> <table
<tr> bind:this={importTableEl}
{#if isImporting} class={isImporting ? "is-importing" : ""}
<th class="loading-col"></th> >
{/if} <thead>
<th>Name</th>
<th>Year</th>
<th>Type</th>
<th>Status</th>
{#if !isImporting}
<th></th>
{/if}
</tr>
</thead>
<tbody>
<!-- TODO: Fix this to use a keyed each somehow (need unique id for key) -->
<!-- eslint-disable-next-line svelte/require-each-key -->
{#each rList as l}
<tr> <tr>
{#if isImporting} {#if isImporting}
<td class="icon-cell"> <th class="loading-col"></th>
<div>
{#if !l.state}
<SpinnerTiny />
{:else if l.state === ImportResponseType.IMPORT_SUCCESS}
<Icon i="check" wh={22} />
{:else if l.state === ImportResponseType.IMPORT_NOTFOUND}
<Icon i="close" wh={22} />
{:else if l.state === ImportResponseType.IMPORT_FAILED}
<Icon i="close" wh={22} />
{:else if l.state === ImportResponseType.IMPORT_EXISTS}
<Icon i="check" wh={22} />
{/if}
</div>
</td>
{/if} {/if}
<td> <th>Name</th>
<input <th>Year</th>
class="plain" <th>Type</th>
bind:value={l.name} <th>Status</th>
disabled={isImporting} <th>Rating</th>
/>
</td>
<td class="year">
<input
class="plain"
bind:value={l.year}
placeholder="YYYY"
type="number"
disabled={isImporting}
/>
</td>
<td class="type">
<DropDown
options={dropDownSupportedTypes}
bind:active={l.type}
placeholder="Type"
blendIn={true}
disabled={isImporting}
/>
</td>
<td class="type">
<DropDown
options={[
"FINISHED",
"PLANNED",
"WATCHING",
"HOLD",
"DROPPED",
]}
bind:active={l.status}
placeholder="Status"
blendIn={true}
disabled={isImporting}
/>
</td>
{#if !isImporting} {#if !isImporting}
<td> <th></th>
<button
class="plain delete"
onclick={() => {
removeRow(l);
}}
>
<Icon i="close" wh="25" />
</button>
</td>
{/if} {/if}
</tr> </tr>
{/each} </thead>
{#if !isImporting} <tbody>
<tr> <!-- TODO: Fix this to use a keyed each somehow (need unique id for key) -->
<td <!-- eslint-disable-next-line svelte/require-each-key -->
><input {#each rList as l}
class="plain" <tr>
placeholder="Name" {#if isImporting}
onblur={addRow} <td class="icon-cell">
/></td <div>
> {#if !l.state}
<td class="year"> <SpinnerTiny />
<input {:else if l.state === ImportResponseType.IMPORT_SUCCESS}
class="plain" <Icon i="check" wh={22} />
id="addYear" {:else if l.state === ImportResponseType.IMPORT_NOTFOUND}
placeholder="YYYY" <Icon i="close" wh={22} />
type="number" {:else if l.state === ImportResponseType.IMPORT_FAILED}
/> <Icon i="close" wh={22} />
</td> {:else if l.state === ImportResponseType.IMPORT_EXISTS}
<td class="type"></td> <Icon i="check" wh={22} />
<td class="status"></td> {/if}
<td></td> </div>
</tr> </td>
{/if} {/if}
</tbody> <td class="name">
</table> <input
class="plain"
bind:value={l.name}
disabled={isImporting}
/>
</td>
<td class="year">
<input
class="plain"
bind:value={l.year}
placeholder="YYYY"
type="number"
disabled={isImporting}
/>
</td>
<td class="type">
<DropDown
options={dropDownSupportedTypes}
bind:active={l.type}
placeholder="Type"
blendIn={true}
disabled={isImporting}
/>
</td>
<td class="status">
<DropDown
options={[
"FINISHED",
"PLANNED",
"WATCHING",
"HOLD",
"DROPPED",
]}
bind:active={l.status}
placeholder="Status"
blendIn={true}
disabled={isImporting}
/>
</td>
<td class="rating">
<input
class="plain"
data-validateme
bind:value={l.rating}
placeholder="0"
type="number"
min="0"
max="10"
disabled={isImporting}
/>
</td>
{#if !isImporting}
<td>
<button
class="plain delete"
onclick={() => {
removeRow(l);
}}
>
<Icon i="close" wh="25" />
</button>
</td>
{/if}
</tr>
{/each}
{#if !isImporting}
<tr>
<td
><input
class="plain"
placeholder="Name"
onblur={addRow}
/></td
>
<td class="year">
<input
class="plain"
id="addYear"
placeholder="YYYY"
type="number"
/>
</td>
<td class="type"></td>
<td class="status"></td>
<td class="rating"></td>
<td></td>
</tr>
{/if}
</tbody>
</table>
</div>
<div class="btns"> <div class="btns">
<button onclick={() => goto(resolve("/import"))} <button onclick={() => goto(resolve("/import"))}>
><Icon i="arrow" />Back</button <Icon i="arrow" />Back
> </button>
<button onclick={() => changeAllStatuses()} disabled={isImporting}> <button onclick={() => changeAllStatuses()} disabled={isImporting}>
Change Statuses Change All Statuses
</button>
<button onclick={startImport} disabled={isImporting}>
Start Importing
</button> </button>
<button onclick={startImport} disabled={isImporting}
>Start Importing</button
>
</div> </div>
{#if typeof changeAllStatusesModalCb === "function"} {#if typeof changeAllStatusesModalCb === "function"}
<Modal <Modal
@@ -817,8 +877,7 @@
display: flex; display: flex;
flex-flow: column; flex-flow: column;
min-width: 400px; min-width: 400px;
max-width: 600px; max-width: 1200px;
overflow: hidden;
@media screen and (max-width: 410px) { @media screen and (max-width: 410px) {
min-width: 100%; min-width: 100%;
@@ -826,15 +885,29 @@
} }
} }
.table-wrap {
overflow: auto;
margin-top: 20px;
}
table { table {
td { td {
&.name {
width: 100%;
}
&.year { &.year {
width: 70px; width: 70px;
min-width: 67px;
} }
&.type { &.type {
width: 120px; width: 120px;
} }
&.rating {
width: 82px;
}
} }
} }
@@ -89,6 +89,8 @@
<style lang="scss"> <style lang="scss">
table { table {
margin-top: 20px;
td { td {
padding: 12px 15px; padding: 12px 15px;
word-wrap: anywhere; word-wrap: anywhere;
-1
View File
@@ -263,7 +263,6 @@
:global { :global {
table { table {
margin-top: 20px;
width: 100%; width: 100%;
border-spacing: 0px; border-spacing: 0px;
border: 1px solid $accent-color; border: 1px solid $accent-color;