Skip to content

Commit

Permalink
Merge pull request #222 from donhinkelman/MOODLE_42_STABLE
Browse files Browse the repository at this point in the history
Moodle 42 stable
  • Loading branch information
frederikmillingpytlick authored Nov 13, 2024
2 parents 9b1fb86 + 3c9982d commit b7cfbf9
Show file tree
Hide file tree
Showing 27 changed files with 490 additions and 91 deletions.
2 changes: 1 addition & 1 deletion amd/build/app/block/element.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/app/block/element.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/app/block/item/element.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/app/block/item/element.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/app/block/queue/element.min.js.map

Large diffs are not rendered by default.

129 changes: 108 additions & 21 deletions amd/src/app/block/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export default class BlockElement {
*/
#draggedSectionId = null;

/**
* @type {boolean}
*/
#bulkDeleteEnabled = false;

/**
* @param {BaseFactory} baseFactory
* @param {HTMLElement} element
Expand Down Expand Up @@ -106,20 +111,20 @@ export default class BlockElement {
this.#queue = this.#baseFactory.block().queue().element(this, queue);
}

setupItems() {
async setupItems() {
const items = this.#element.querySelectorAll('.sharing_cart_item');

items.forEach((element) => {
this.setupItem(element);
});
for (const element of items) {
await this.setupItem(element);
}

this.#sortable = new Sortable(this.#element.querySelector('.sharing_cart_items'), {
dataIdAttr: 'data-itemid',
onUpdate: () => {
Ajax.call([{
methodname: 'block_sharing_cart_reorder_sharing_cart_items',
args: {
item_ids: this.#sortable.toArray(),
item_ids: this.#sortable.toArray().filter((id) => !isNaN(id)),
},
fail: (data) => {
Notification.exception(data);
Expand Down Expand Up @@ -169,14 +174,37 @@ export default class BlockElement {
const disableBulkDeleteButton = this.#element.querySelector('#block_sharing_cart_cancel_bulk_delete');
const bulkDeleteButton = this.#element.querySelector('#block_sharing_cart_bulk_delete_confirm');

const checkboxSelector = '.sharing_cart_item input[data-action="bulk_select"][type="checkbox"]';
const selectAllContainer = this.#element.querySelector('#select_all_container');
const selectAllCheckbox = this.#element.querySelector('#select_all_box');

selectAllCheckbox.addEventListener('click', async () => {
const itemCheckboxes = this.getItemCheckboxes();
const allSelected = Array.from(itemCheckboxes).every(checkbox => checkbox.checked);
itemCheckboxes.forEach(checkbox => {
checkbox.checked = !allSelected;
});
itemCheckboxes.forEach(checkbox => checkbox.addEventListener('change', async () => {
this.updateSelectAllState();
}));

this.updateSelectAllState();
this.updateBulkDeleteButtonState();
});

enableBulkDeleteButton.addEventListener('click', () => {
if (this.#items.length === 0) {
return;
}

this.#bulkDeleteEnabled = true;

enableBulkDeleteButton.classList.add('d-none');
disableBulkDeleteButton.classList.remove('d-none');

selectAllContainer.classList.remove('d-none');
bulkDeleteButton.classList.remove('d-none');

this.#element.querySelectorAll(checkboxSelector).forEach((checkbox) => {
this.getItemCheckboxes().forEach((checkbox) => {
checkbox.classList.remove('d-none');
checkbox.checked = false;
});
Expand All @@ -187,8 +215,9 @@ export default class BlockElement {
bulkDeleteButton.classList.add('d-none');
bulkDeleteButton.disabled = true;
enableBulkDeleteButton.classList.remove('d-none');
selectAllContainer.classList.add('d-none');

this.#element.querySelectorAll(checkboxSelector).forEach((checkbox) => {
this.getItemCheckboxes().forEach((checkbox) => {
checkbox.classList.add('d-none');
checkbox.checked = false;
});
Expand All @@ -200,7 +229,11 @@ export default class BlockElement {
}

const itemIds = [];
this.#element.querySelectorAll(checkboxSelector + ':checked').forEach((checkbox) => {
this.getItemCheckboxes().forEach((checkbox) => {
if (!checkbox.checked) {
return;
}

itemIds.push(checkbox.value);
});

Expand All @@ -211,14 +244,58 @@ export default class BlockElement {
/**
* @param {HTMLElement} element
*/
setupItem(element) {
async setupItem(element) {
const itemElement = this.#baseFactory.block().item().element(this, element);

this.#element.querySelector('.no-items')?.remove();
if (itemElement.getStatus() !== '0' && this.isBulkDeleteEnabled()) {
const checkbox = element.querySelector('input[data-action="bulk_select"][type="checkbox"]');
checkbox?.classList?.remove('d-none');
}

this.#items.push(
itemElement
);
this.#element.querySelector('.no-items').classList.add('d-none');

const existingItemIndex = this.#items.findIndex((i) => i.getItemId() === itemElement.getItemId());
if (existingItemIndex !== -1) {
this.#items[existingItemIndex] = itemElement;
} else {
this.#items.push(itemElement);
}

this.updateBulkDeleteButtonState();
this.updateSelectAllState();
}

getItemCheckboxes() {
const checkboxSelector = '.sharing_cart_item:not([data-status="0"]) input[data-action="bulk_select"][type="checkbox"]';
return this.#element.querySelectorAll(checkboxSelector);
}

updateBulkDeleteButtonState() {
const bulkDeleteButton = this.#element.querySelector('#block_sharing_cart_bulk_delete_confirm');
bulkDeleteButton.disabled = !Array.from(this.getItemCheckboxes()).some(checkbox => checkbox.checked);
}

updateSelectAllState() {
const selectAllCheckbox = this.#element.querySelector('#select_all_box');
const selectAllLabel = this.#element.querySelector('#select_all_label');

const itemCheckboxes = this.getItemCheckboxes();
const allSelected = Array.from(itemCheckboxes).every(checkbox => checkbox.checked);
const someSelected = Array.from(itemCheckboxes).some(checkbox => checkbox.checked);

const strPromise = allSelected ?
get_string('deselect_all', 'block_sharing_cart') :
get_string('select_all', 'block_sharing_cart');
strPromise.then((str) => {
selectAllLabel.textContent = str;
});

selectAllCheckbox.checked = allSelected;
selectAllCheckbox.indeterminate = !allSelected && someSelected;
}

isBulkDeleteEnabled() {
return this.#bulkDeleteEnabled;
}

/**
Expand Down Expand Up @@ -276,8 +353,7 @@ export default class BlockElement {
item.remove();

if (this.#items.length === 0) {
this.#element.querySelector('.sharing_cart_items')
.innerHTML = await get_string('no_items', 'block_sharing_cart');
this.#element.querySelector('.no-items').classList.remove('d-none');
}
}

Expand All @@ -293,6 +369,7 @@ export default class BlockElement {
done: async (deleted) => {
if (deleted) {
await this.removeItemElement(item);
this.updateSelectAllState();
} else {
await Notification.alert('Failed to delete item');
}
Expand Down Expand Up @@ -325,6 +402,7 @@ export default class BlockElement {

await this.removeItemElement(item);
}
this.updateSelectAllState();

document.getElementById('block_sharing_cart_bulk_delete_confirm').disabled = true;
},
Expand Down Expand Up @@ -472,7 +550,10 @@ export default class BlockElement {
async renderItem(item) {
const existingItemIndex = this.#items.findIndex((i) => i.getItemId() === item.id);
const existingItem = this.#items[existingItemIndex] ?? false;
const oldElement = this.#element.querySelector('.sharing_cart_items .sharing_cart_item[data-itemid="' + item.id + '"]');
const getOldElement = () => {
return this.#element.querySelector('.sharing_cart_items .sharing_cart_item[data-itemid="' + item.id + '"]');
};
const oldElement = getOldElement();
if (existingItem && oldElement) {
const element = await this.#baseFactory.moodle().template().createElementFromFragment(
'block_sharing_cart',
Expand All @@ -483,12 +564,18 @@ export default class BlockElement {
}
);

// Early exit if the element has been removed from the DOM in between rendering and checking earlier.
if (getOldElement() !== oldElement) {
return;
}

this.#element.querySelector('.sharing_cart_items').replaceChild(element, oldElement);
this.#items[existingItemIndex] = this.#baseFactory.block().item().element(this, element);

element.querySelectorAll('.sharing_cart_item').forEach((subItem) => {
this.setupItem(subItem);
});
await this.setupItem(element);
for (const subItem of element.querySelectorAll('.sharing_cart_item')) {
await this.setupItem(subItem);
}

return;
}
Expand All @@ -513,7 +600,7 @@ export default class BlockElement {
);
this.#element.querySelector('.sharing_cart_items').prepend(element);

this.setupItem(element);
await this.setupItem(element);
}

/**
Expand Down
54 changes: 37 additions & 17 deletions amd/src/app/block/item/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Notification from "core/notification";
import {get_strings} from "core/str";
import Ajax from "core/ajax";

const polls = [];

export default class ItemElement {
/**
* @type {BaseFactory}
Expand Down Expand Up @@ -37,10 +39,22 @@ export default class ItemElement {
this.#addEventListeners();
}

#pollItem(currentTry = 0, retries = 10) {
#pollItem(currentTry = 0, retries = -1, uuid = null) {
if (uuid === null) {
uuid = crypto.randomUUID();

if (polls[this.getItemId()]) {
return;
}

polls[this.getItemId()] = uuid;
} else if (polls[this.getItemId()] !== uuid) {
return;
}

currentTry += 1;

if (currentTry >= retries) {
if (retries !== -1 && currentTry >= retries) {
return;
}

Expand All @@ -49,21 +63,20 @@ export default class ItemElement {
args: {
item_id: this.getItemId(),
},
done: async (item) => {
done: async(item) => {
if (item.status === 0) {
new Promise(
(resolve) => {
setTimeout(resolve, currentTry * 1000);
}
).then(
() => {
this.#pollItem(currentTry, retries);
}
);
// Cap the timeout at 10 seconds
const timeOut = currentTry > 10 ? 10000 : currentTry * 1000;

setTimeout(() => {
this.#pollItem(currentTry, retries, uuid);
}, timeOut);
return;
}

// Remove the item from the polls array
polls.splice(this.getItemId(), 1);

await this.#blockElement.renderItem(item);
},
fail: (data) => {
Expand All @@ -76,12 +89,11 @@ export default class ItemElement {
this.#element.querySelector('.info').addEventListener('click', this.toggleCollapseRecursively.bind(this));

const checkbox = this.#element.querySelector('input[data-action="bulk_select"][type="checkbox"]');
checkbox?.addEventListener('click', () => {
const bulkDeleteButton = document.getElementById('block_sharing_cart_bulk_delete_confirm');
checkbox?.addEventListener('click', (e) => {
e.stopImmediatePropagation();

const blockSelector = '.block.block_sharing_cart';
const checkboxSelector = blockSelector + ' .sharing_cart_item input[data-action="bulk_select"][type="checkbox"]';
bulkDeleteButton.disabled = document.querySelectorAll(checkboxSelector + ':checked').length <= 0;
this.#blockElement.updateSelectAllState();
this.#blockElement.updateBulkDeleteButtonState();
});

const actionsContainer = this.#element.querySelector(':scope > .item-body .sharing_cart_item_actions');
Expand All @@ -103,13 +115,15 @@ export default class ItemElement {
async copyItemToCourse(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();

await this.#blockElement.setClipboard(this);
}

async runNow(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();

const currentTarget = e.currentTarget;
currentTarget.disabled = true;
Expand All @@ -133,6 +147,7 @@ export default class ItemElement {
async confirmDeleteItem(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();

const strings = await get_strings([
{
Expand Down Expand Up @@ -181,6 +196,10 @@ export default class ItemElement {
return this.#element;
}

getStatus() {
return this.#element.dataset.status;
}

/**
* @return {String}
*/
Expand Down Expand Up @@ -247,6 +266,7 @@ export default class ItemElement {
toggleCollapseRecursively(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();

if (this.isModule() || this.#element.dataset.status !== '1') {
return;
Expand Down
1 change: 1 addition & 0 deletions amd/src/app/block/queue/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default class QueueElement {
* @return {Promise<void>}
*/
async loadQueue(showSpinner = false, token = {}) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
token.abort = () => {
reject();
Expand Down
4 changes: 3 additions & 1 deletion block_sharing_cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public function get_content(): object|string
'run_now',
'atleast_one_course_module_must_be_included',
'no_course_modules_in_section',
'no_course_modules_in_section_description'
'no_course_modules_in_section_description',
'select_all',
'deselect_all',
], 'block_sharing_cart');
$this->page->requires->strings_for_js([
'import',
Expand Down
Loading

0 comments on commit b7cfbf9

Please sign in to comment.