Skip to content

Commit

Permalink
Show status of auto updating lists (#47)
Browse files Browse the repository at this point in the history
* Show status of auto updating lists in the custom list editor.

* Add/update tests.

* Tweak status descriptions.
  • Loading branch information
ray-lee authored Oct 4, 2022
1 parent ce8cafe commit 2cb3714
Show file tree
Hide file tree
Showing 10 changed files with 467 additions and 222 deletions.
7 changes: 7 additions & 0 deletions src/components/CustomListEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import TextWithEditMode from "./TextWithEditMode";
import ShareIcon from "./icons/ShareIcon";

type CustomListEditorProps = {
autoUpdateStatus?: string;
collections?: AdminCollectionData[];
entries?: CustomListEditorEntriesData;
entryPoints?: string[];
Expand All @@ -32,6 +33,7 @@ type CustomListEditorProps = {
isFetchingMoreSearchResults: boolean;
isLoaded?: boolean;
isModified?: boolean;
isSearchModified?: boolean;
isOwner?: boolean;
isShared?: boolean;
isSharePending?: boolean;
Expand Down Expand Up @@ -73,6 +75,7 @@ type CustomListEditorProps = {
};

export default function CustomListEditor({
autoUpdateStatus,
collections,
entries,
entryPoints,
Expand All @@ -82,6 +85,7 @@ export default function CustomListEditor({
isFetchingMoreSearchResults,
isLoaded,
isModified,
isSearchModified,
isOwner,
isShared,
isSharePending,
Expand Down Expand Up @@ -261,6 +265,7 @@ export default function CustomListEditor({
<CustomListSearch
autoUpdate={properties.autoUpdate}
isOwner={isOwner}
listId={listId}
searchParams={searchParams}
updateAutoUpdate={(value) => updateProperty?.("autoUpdate", value)}
updateSearchParam={updateSearchParam}
Expand All @@ -279,8 +284,10 @@ export default function CustomListEditor({
</section>

<CustomListEntriesEditor
autoUpdateStatus={autoUpdateStatus}
autoUpdate={properties.autoUpdate}
isOwner={isOwner}
isSearchModified={isSearchModified}
searchResults={searchResults}
entries={entries.current}
loadMoreSearchResults={loadMoreSearchResults}
Expand Down
264 changes: 153 additions & 111 deletions src/components/CustomListEntriesEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import LoadButton from "./LoadButton";

export interface CustomListEntriesEditorProps {
autoUpdate?: boolean;
autoUpdateStatus?: string;
entries?: Entry[];
entryCount?: number;
isFetchingMoreCustomListEntries: boolean;
isFetchingSearchResults: boolean;
isFetchingMoreSearchResults: boolean;
isOwner?: boolean;
isSearchModified?: boolean;
listId?: string | number;
opdsFeedUrl?: string;
searchResults?: CollectionData;
Expand Down Expand Up @@ -55,12 +57,14 @@ const renderCatalogLink = (book, opdsFeedUrl?) => {

const CustomListEntriesEditor = ({
autoUpdate,
entries,
entryCount,
autoUpdateStatus,
entries = [],
entryCount = 0,
isFetchingMoreCustomListEntries,
isFetchingSearchResults,
isFetchingMoreSearchResults,
isOwner,
isSearchModified,
listId,
opdsFeedUrl,
searchResults,
Expand Down Expand Up @@ -134,7 +138,7 @@ const CustomListEntriesEditor = ({

const readOnly = !isOwner || autoUpdate;

let searchResultList = null;
let searchResultList: JSX.Element | null = null;

if (isOwner) {
searchResultList = (
Expand Down Expand Up @@ -210,7 +214,7 @@ const CustomListEntriesEditor = ({
<div className="title">{book.title}</div>

<div className="authors">
{book.authors.join(", ")}
{book.authors?.join(", ")}
</div>
</div>

Expand Down Expand Up @@ -256,125 +260,163 @@ const CustomListEntriesEditor = ({
);
}

let entryList = null;

if (!autoUpdate) {
const visibleEntryCount = entries.length;
const startNum = visibleEntryCount > 0 ? 1 : 0;
const endNum = visibleEntryCount;
const booksText = entryCount === 1 ? "book" : "books";

const entryListDisplay =
entryCount > 0
? `Displaying ${startNum} - ${endNum} of ${entryCount} ${booksText}`
: "No books in this list";

entryList = (
<div className="custom-list-entries">
<div className="droppable-header">
<h4>List Entries: {entryListDisplay}</h4>
const visibleEntryCount = entries.length;
const startNum = visibleEntryCount > 0 ? 1 : 0;
const endNum = visibleEntryCount;
const booksText = entryCount === 1 ? "book" : "books";

const entryListDisplay =
entryCount > 0
? `Displaying ${startNum} - ${endNum} of ${entryCount} ${booksText}`
: "No books in this list";

let autoUpdateStatusName = null;
let autoUpdateStatusDescription = null;

if (!listId) {
autoUpdateStatusName = "New";
autoUpdateStatusDescription =
"This is a new list. Once the initial search criteria have been saved, the system will begin to populate its entries; however, the list might not be fully populated until the next scheduled update.";
} else if (isSearchModified) {
autoUpdateStatusName = "Search criteria modified";
autoUpdateStatusDescription =
"There are unsaved changes to the search criteria for this list. Once the changes have been saved, the new search criteria will be used to repopulate the list during the next scheduled update.";
} else {
if (autoUpdateStatus === "init") {
autoUpdateStatusName = "Initializing";
autoUpdateStatusDescription =
"This list was created recently. The system has partially populated the list using the configured search criteria and will fully populate the list during the next scheduled update.";
} else if (autoUpdateStatus === "repopulate") {
autoUpdateStatusName = "Repopulating";
autoUpdateStatusDescription =
"The search criteria for this list were changed recently, but the entries have not yet been updated. The new search criteria will be used to repopulate the list during the next scheduled update.";
} else if (autoUpdateStatus === "updated") {
autoUpdateStatusName = "Updated";
autoUpdateStatusDescription =
"This list was fully populated during the last scheduled update, using the configured search criteria and the titles that were available at the time. New titles matching the criteria will be added to the list during the next scheduled update.";
} else if (!autoUpdateStatus) {
autoUpdateStatusName = "Changing to automatic";
autoUpdateStatusDescription =
"This list was populated manually, but is being changed to be updated automatically. The configured search criteria will be used to repopulate the list during the next scheduled update.";
} else {
autoUpdateStatusName = autoUpdateStatus;
}
}

{!readOnly && entries?.length > 0 && (
<div>
<span>Remove all currently visible items from list:</span>
const entryList = (
<div className="custom-list-entries">
<div className="droppable-header">
<h4>List Entries: {entryListDisplay}</h4>

<Button
className="danger delete-all-button top-align"
callback={deleteAllEntries}
content={
<span>
Delete
<TrashIcon />
</span>
}
/>
{autoUpdate && (
<>
<div className="auto-update-status-name">
Status: {autoUpdateStatusName}
</div>
)}
</div>
<aside className="auto-update-status-desc">
{autoUpdateStatusDescription}
</aside>
</>
)}

{!readOnly && <p>Drag search results here to add them to the list.</p>}

<Droppable
droppableId="custom-list-entries"
isDropDisabled={readOnly || draggingFrom !== "search-results"}
>
{(provided, snapshot) => (
<ul
ref={provided.innerRef}
id="custom-list-entries-droppable"
className={
snapshot.isDraggingOver
? " droppable dragging-over"
: "droppable"
{!readOnly && entries?.length > 0 && (
<div>
<span>Remove all currently visible items from list:</span>

<Button
className="danger delete-all-button top-align"
callback={deleteAllEntries}
content={
<span>
Delete
<TrashIcon />
</span>
}
>
{entries?.map((book) => (
<Draggable
key={book.id}
draggableId={book.id}
isDragDisabled={readOnly}
>
{(provided, snapshot) => (
<li>
<div
className={
"custom-list-entry" +
(snapshot.isDragging ? " dragging" : "")
}
ref={provided.innerRef}
style={provided.draggableStyle}
{...provided.dragHandleProps}
>
{!readOnly && <GrabIcon />}

<div>
<div className="title">{book.title}</div>

<div className="authors">
{book.authors.join(", ")}
</div>
</div>
/>
</div>
)}
</div>

{!readOnly && <p>Drag search results here to add them to the list.</p>}

<Droppable
droppableId="custom-list-entries"
isDropDisabled={readOnly || draggingFrom !== "search-results"}
>
{(provided, snapshot) => (
<ul
ref={provided.innerRef}
id="custom-list-entries-droppable"
className={
snapshot.isDraggingOver ? " droppable dragging-over" : "droppable"
}
>
{entries?.map((book) => (
<Draggable
key={book.id}
draggableId={book.id}
isDragDisabled={readOnly}
>
{(provided, snapshot) => (
<li>
<div
className={
"custom-list-entry" +
(snapshot.isDragging ? " dragging" : "")
}
ref={provided.innerRef}
style={provided.draggableStyle}
{...provided.dragHandleProps}
>
{!readOnly && <GrabIcon />}

{getMediumSVG(getMedium(book))}

<div className="links">
{renderCatalogLink(book, opdsFeedUrl)}

{!readOnly && (
<Button
className="small right-align"
callback={() => deleteEntry?.(book.id)}
content={
<span>
Remove from list
<TrashIcon />
</span>
}
/>
)}
<div>
<div className="title">{book.title}</div>

<div className="authors">
{book.authors?.join(", ")}
</div>
</div>

{provided.placeholder}
</li>
)}
</Draggable>
))}
{getMediumSVG(getMedium(book))}

{provided.placeholder}
</ul>
)}
</Droppable>
<div className="links">
{renderCatalogLink(book, opdsFeedUrl)}

{loadMoreEntries && (
<LoadButton
isFetching={isFetchingMoreCustomListEntries}
loadMore={loadMoreEntries}
/>
{!readOnly && (
<Button
className="small right-align"
callback={() => deleteEntry?.(book.id)}
content={
<span>
Remove from list
<TrashIcon />
</span>
}
/>
)}
</div>
</div>

{provided.placeholder}
</li>
)}
</Draggable>
))}

{provided.placeholder}
</ul>
)}
</div>
);
}
</Droppable>

{loadMoreEntries && (
<LoadButton
isFetching={isFetchingMoreCustomListEntries}
loadMore={loadMoreEntries}
/>
)}
</div>
);

return (
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
Expand Down
Loading

0 comments on commit 2cb3714

Please sign in to comment.