Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create My Books Dropper #8019

Merged
merged 41 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a818eab
Create My Books Dropper using generic dropper
jimchamp May 30, 2023
e47eb35
Create JS classes for My Books dropper components
jimchamp Jun 7, 2023
2db3a5e
Add list functionality to dropper
jimchamp Jun 12, 2023
a150988
Adds lists to search results droppers
jimchamp Jun 27, 2023
9da52c3
Clean up code
jimchamp Jun 28, 2023
7f5ff1f
Fix linting errors
jimchamp Jul 3, 2023
f1be6ae
Disable My Books Dropper via config
jimchamp Jul 3, 2023
831004a
Use correct worksearch dropper
jimchamp Jul 3, 2023
78d9671
Remove My Books Dropper from /subjects pages
jimchamp Jul 3, 2023
662dac1
Use old styling for author page, orphaned editions
jimchamp Jul 4, 2023
8d70c0c
Adjust testing and bundlesize thresholds
jimchamp Jul 4, 2023
48b2019
Remove active lists showcase
jimchamp Jul 18, 2023
c5d59ed
Remove feature flag
jimchamp Jul 18, 2023
63be412
Disable dropper when not logged in
jimchamp Jul 19, 2023
1856506
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
2c321c7
Prevent dropper closing on list click
jimchamp Jul 19, 2023
dcdc23e
Populate list creation calls with correct data
jimchamp Jul 27, 2023
cb1a7af
Revert "Remove feature flag"
jimchamp Jul 28, 2023
d2a0b09
Asynchronously load lists once per page
jimchamp Aug 2, 2023
e93e35a
Add Dropper tests
jimchamp Aug 2, 2023
9952398
Restructure my-books directory
jimchamp Aug 7, 2023
3aa080b
Reinstate active lists showcase
jimchamp Aug 8, 2023
f4ec53b
Add work checkbox to search result items
jimchamp Aug 9, 2023
fcbfe07
Fix updating "saving..." message
jimchamp Aug 9, 2023
0426419
Add more tests
jimchamp Aug 9, 2023
8abd55d
Fix bugs after rebase
jimchamp Aug 18, 2023
1d21101
Modify store API
jimchamp Aug 18, 2023
acdef29
Ensure disabled droppers cannot open
jimchamp Aug 18, 2023
ce0938f
Check if edition before accessing `works` list
jimchamp Sep 12, 2023
cc1dc5a
Increment bundlesize threshold for admin page
jimchamp Sep 12, 2023
f3bf182
Remove unhelpful comment
jimchamp Sep 15, 2023
144441c
Add dropper event methods
jimchamp Sep 16, 2023
bfa1299
Redirect to login on disabled dropper arrow click
jimchamp Sep 16, 2023
c02e60f
Remove unused data attribute
jimchamp Sep 16, 2023
0c8bc93
Fix store bugs
jimchamp Sep 19, 2023
e6c31f2
Remove unused class member
jimchamp Sep 19, 2023
78c0426
Use list key instead of default cover URL
jimchamp Sep 19, 2023
86d3490
Access data attribute correctly
jimchamp Sep 19, 2023
db954ac
Pass list cover URL to new showcase item
jimchamp Sep 19, 2023
a56313d
Update openlibrary/plugins/openlibrary/js/my-books/index.js
jimchamp Sep 21, 2023
12230de
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,19 @@
},
{
"path": "static/build/lists.*.js",
"maxSize": "5KB"
"maxSize": "5.5KB"
},
{
"path": "static/build/all.js",
"maxSize": "129KB"
"maxSize": "130KB"
},
{
"path": "static/build/page-admin.css",
"maxSize": "26KB"
"maxSize": "27KB"
},
{
"path": "static/build/page-book.css",
"maxSize": "13KB"
"maxSize": "13.5KB"
},
{
"path": "static/build/page-edit.css",
Expand All @@ -98,7 +98,7 @@
},
{
"path": "static/build/page-plain.css",
"maxSize": "25KB"
"maxSize": "26KB"
},
{
"path": "static/build/page-subject.css",
Expand Down
2 changes: 2 additions & 0 deletions conf/openlibrary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ features:
support: admin
undo: enabled
upstream: enabled
# Uncomment below line to enable the My Books Dropper
# my_books_dropper: enabled

upstream_to_www_migration: true
default_template_root: /upstream
Expand Down
13 changes: 12 additions & 1 deletion openlibrary/macros/SearchResultsWork.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$def with (doc, decorations=None, cta=True, availability=None, extra=None, attrs=None, rating=None, reading_log=None, show_librarian_extras=False)
$def with (doc, decorations=None, cta=True, availability=None, extra=None, attrs=None, rating=None, reading_log=None, show_librarian_extras=False, include_dropper=False)

$code:
max_rendered_authors = 9
Expand Down Expand Up @@ -129,6 +129,17 @@ <h3 itemprop="name" class="booktitle">
$if reading_log:
$:reading_log

$if include_dropper:
$ edition_key = None
$if doc_type == 'solr_edition':
$ edition_key = selected_ed.key
$elif doc_type == 'infogami_edition':
$ edition_key = doc.key
$elif doc_type == 'solr_work':
$ edition_key = doc.get('edition_key') and doc.get("edition_key")[0]
$ edition_key = '/books/%s' % edition_key
$:render_template('my_books/dropper', doc, edition_key=edition_key, async_load=True)

$if rating:
$:rating
</div>
Expand Down
2 changes: 1 addition & 1 deletion openlibrary/macros/databarWork.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ <h3 class="header">
$ edition = page.works and page

$ render_times['databarWork: List widget'] = time()
$ lists_widget = render_template('lists/widget', edition or work, include_rating=True, include_header=False, include_widget=True, include_showcase=False, async_load=True)
$ lists_widget = render_template('lists/widget', edition or work, include_rating=True, include_header=False, include_widget=True, include_showcase=False, async_load=True, show_active_lists=True)
$ render_times['databarWork: List widget'] = time() - render_times['databarWork: List widget']

$# For the Works & Editions page, only use ground_truth_availability API for selected Edition
Expand Down
62 changes: 47 additions & 15 deletions openlibrary/plugins/openlibrary/js/dropper/Dropper.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class Dropper {
*
* @member {boolean}
*/
this.isDropperOpen = dropper.classList.contains('dropper-wrapper--active')
this.isDropperOpen = dropper.classList.contains('generic-dropper-wrapper--active')

/**
* Tracks whether this dropper is disabled.
Expand All @@ -70,38 +70,65 @@ export class Dropper {
*
* @member {boolean}
*/
this.isDropperDisabled = dropper.querySelector('.generic-dropper').classList.contains('generic-dropper--disabled')
this.isDropperDisabled = dropper.classList.contains('generic-dropper--disabled')
}

/**
* Adds click listener to dropper's toggle arrow.
*/
initialize() {
this.dropClick.addEventListener('click', () => {
// REVIEWER: `debounce` call was removed here.
// Could not get it to work --- suspect that this has something to
// do with `debounce` being called from an arrow function?
//
// I wonder if it's still needed? I don't think that we're doing
// anything computationally heavy when this is opened or closed.
// No calls to the server, either.
this.toggleDropper()
})
}

/**
* Function that is called after a dropper has opened.
*
* Subclasses of `Dropper` may override this to add
* functionality that should occur on dropper open.
*/
onOpen() {}

/**
* Function that is called after a dropper has closed.
*
* Subclasses of `Dropper` may override this to add
* functionality that should occur on dropper close.
*/
onClose() {}

/**
* Function that is called when the drop-click affordance of
* a disabled dropper is clicked.
*
* Subclasses of `Dropper` may override this as needed.
*/
onDisabledClick() {}

/**
* Closes dropper if opened; opens dropper if closed.
*
* Toggles value of `isDropperOpen`.
*
* Does nothing if this dropper is disabled.
* Calls `onDisabledClick()` if this dropper is disabled.
* Calls either `onOpen()` or `onClose()` after the dropper
* has been toggled.
*/
toggleDropper() {
if (!this.isDropperDisabled) {
if (this.isDropperDisabled) {
this.onDisabledClick();
} else {
this.$dropper.find('.generic-dropper__dropdown').slideToggle(25);
this.$dropper.find('.arrow').toggleClass('up')
this.$dropper.toggleClass('dropper-wrapper--active')
this.$dropper.toggleClass('generic-dropper-wrapper--active')
this.isDropperOpen = !this.isDropperOpen

if (this.isDropperOpen) {
this.onOpen()
} else {
this.onClose()
}
}
}

Expand All @@ -110,14 +137,19 @@ export class Dropper {
*
* Sets `isDropperOpen` to `false`.
*
* Does nothing if this dropper is disabled.
* Calls `onDisabledClick()` if this dropper is disabled.
* Otherwise, closes dropper and calls `onClose()`.
*/
closeDropper() {
if (!this.isDropperDisabled) {
if (this.isDropperDisabled) {
this.onDisabledClick();
} else {
this.$dropper.find('.generic-dropper__dropdown').slideUp(25)
this.$dropper.find('.arrow').removeClass('up');
this.$dropper.removeClass('dropper-wrapper--active')
this.$dropper.removeClass('generic-dropper-wrapper--active')
this.isDropperOpen = false

this.onClose()
}
}
}
6 changes: 3 additions & 3 deletions openlibrary/plugins/openlibrary/js/dropper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ export function initDroppers(dropperElements) {
* @param {jQuery.Object} $container
*/
function closeDropper($container) {
$container.find('.dropdown').slideUp(25);
$container.find('.generic-dropper__dropdown').slideUp(25)
$container.find('.dropdown').slideUp(25); // Legacy droppers
$container.find('.generic-dropper__dropdown').slideUp(25) // New generic droppers
$container.find('.arrow').removeClass('up');
$container.removeClass('dropper-wrapper--active')
$container.removeClass('generic-dropper-wrapper--active')
}

/**
Expand Down
34 changes: 20 additions & 14 deletions openlibrary/plugins/openlibrary/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,25 +381,31 @@ jQuery(function () {
// "Want to Read" buttons:
const readingLogDroppers = document.getElementsByClassName('widget-add');

// Async lists components:
const wtrLoadingIndicator = document.querySelector('.list-loading-indicator')
const overviewLoadingIndicator = document.querySelector('.list-overview-loading-indicator')

if (readingLogDroppers.length || wtrLoadingIndicator || overviewLoadingIndicator) {
if (readingLogDroppers.length) {
// Async lists components:
const wtrLoadingIndicator = document.querySelector('.list-loading-indicator')
const overviewLoadingIndicator = document.querySelector('.list-overview-loading-indicator')
import(/* webpackChunkName: "lists" */ './lists')
.then((module) => {
if (readingLogDroppers.length) {
module.initReadingLogDroppers(readingLogDroppers);
// Removable list items:
// TODO: Is this the correct place to initalize these?
const actionableListItems = document.querySelectorAll('.actionable-item')
module.registerListItems(actionableListItems);
}
if (wtrLoadingIndicator || overviewLoadingIndicator) {
module.initListLoading(wtrLoadingIndicator, overviewLoadingIndicator)
}
}
);
module.initReadingLogDroppers(readingLogDroppers);
// Removable list items:
const actionableListItems = document.querySelectorAll('.actionable-item')
module.registerListItems(actionableListItems);
});
}

// New "My Books" dropper:
const myBooksDroppers = document.querySelectorAll('.my-books-dropper')
if (myBooksDroppers.length) {
const actionableListShowcases = document.querySelectorAll('.actionable-item')

import(/* webpackChunkName: "my-books" */ './my-books')
.then((module) => {
module.initMyBooksAffordances(myBooksDroppers, actionableListShowcases)
})
}

const nativeDialogs = document.querySelectorAll('.native-dialog')
Expand Down
69 changes: 68 additions & 1 deletion openlibrary/plugins/openlibrary/js/lists/ListService.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function post(data) {
* @param {object} data Object containing the new list's name, description, and seeds.
* @param {function} success Callback to be executed on successful POST.
*/
export function createList(userKey, data, success) {
export function createNewList(userKey, data, success) {
post({
url: `${userKey}/lists.json`,
data: data,
Expand All @@ -42,6 +42,24 @@ export function createList(userKey, data, success) {
});
}

/**
* Submits request to create new list. Returns Promise.
*
* @param {string} userKey The patron's key, in the form "/people/{username}"
* @param {object} data Object containing the new list's name, description, and seeds.
* @returns {Promise<Response>} The results of the POST request
*/
export async function createList(userKey, data) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if there is a request error/failure?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing now, but maybe we trigger a toast message?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note: any errors are meant to be handled by the caller of this function. Whatever we decide to do on list creation failure will be included inside of a catch() call, here:

await createList(this.userKey, data)
.then(response => response.json())
.then((data) => {
this.onListCreationSuccess(data['key'], listTitle)
})

return await fetch(`${userKey}/lists.json`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify(data)
})
}

/**
* Submits request to add given seed to list.
*
Expand All @@ -58,6 +76,25 @@ export function addToList(listKey, seed, success) {
});
}

/**
* Adds an item to a list. Promise-based.
*
* @param {string} listKey The patron's key, in the form "/people/{username}"
* @param {object} seed Object containing the new list's name, description, and seeds.
* @returns {Promise<Response>} The result of the POST request
*/
export async function addItem(listKey, seed) {
const body = { add: [seed] }
return await fetch(`${listKey}/seeds.json`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify(body)
})
}

/**
* Submits request to remove given seed from list.
*
Expand All @@ -74,6 +111,25 @@ export function removeFromList(listKey, seed, success) {
});
}

/**
* Submits request to remove given seed from list. Promise-based.
*
* @param {string} listKey The list's key.
* @param {string|{ key: string }} seed The item being removed from the list.
* @returns {Promise<Response>} The POST response
*/
export async function removeItem(listKey, seed) {
const body = { remove: [seed] }
return await fetch(`${listKey}/seeds.json`, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify(body)
})
}

/**
* Submits reading log update form data and executes the given callback on success.
*
Expand Down Expand Up @@ -106,3 +162,14 @@ export function fetchPartials(key, success) {
success: success
})
}

// XXX : jsdoc
export async function getListPartials() {
return await fetch('/lists/partials.json', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
}
})
}
Loading