-
Notifications
You must be signed in to change notification settings - Fork 90
Collections #1810
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
base: master
Are you sure you want to change the base?
Collections #1810
Changes from all commits
813bd48
52543c4
5f137eb
51adc61
13a90da
2780959
4c7c190
0f47090
fc55074
b2e0277
5ba088d
ae9950b
d679849
58d845f
3d7a668
5ffea0b
d5fed09
04ba23d
e2bc70a
18c9548
034528c
f62d388
81affae
79787d1
82b3e2a
d8fe151
058d326
8fd039a
cd609e0
f64d1b2
ff18e6b
6a4d773
21cce70
33bcc39
fb104f3
02c50dc
21fedc7
b56c9b0
53d4e1d
f4682e2
f5d8bab
d6953ec
68b615a
65cfda7
fbadeb7
6e9a5db
822953c
2c39aef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,3 +93,12 @@ def display_user_top_donor(context, user, donated_amount): | |
@register.inclusion_tag('accounts/display_user.html', takes_context=True) | ||
def display_user_comment(context, user, comment_created): | ||
return display_user(context, user, size='comment', comment_created=comment_created) | ||
|
||
@register.inclusion_tag('accounts/display_user_selectable.html', takes_context=True) | ||
def display_user_small_selectable(context, user, selected=False): | ||
context = context.get('original_context', context) # This is to allow passing context in nested inclusion tags | ||
tvars = display_user(context, user, size='basic') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. double-check this - to me it seems a bit weird to call a function which is registered as a templatetag that returns HTML. Does this actually work? |
||
tvars.update({ | ||
'selected': selected, | ||
}) | ||
return tvars |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Authors: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a bit weird to have just the |
||
# See AUTHORS file. | ||
# | ||
|
||
|
||
from django import template | ||
from django.conf import settings | ||
|
||
from accounts.models import User | ||
|
||
register = template.Library() | ||
|
||
|
||
@register.inclusion_tag('molecules/object_selector.html', takes_context=True) | ||
def users_selector(context, users, selected_user_ids=[], show_select_all_buttons=False): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using |
||
if users: | ||
if not isinstance(users[0], User): | ||
# users are passed as a list of user ids, retrieve the User objects from DB | ||
users = User.objects.ordered_ids(users) | ||
for user in users: | ||
user.selected = user.id in selected_user_ids | ||
return { | ||
'objects': users, | ||
'type': 'users', | ||
'show_select_all_buttons': show_select_all_buttons, | ||
'original_context': context # This will be used so a nested inclusion tag can get the original context | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unneeded comment |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,7 +76,9 @@ def context_extra(request): | |
'next_path': request.GET.get('next', request.get_full_path()), | ||
'login_form': FsAuthenticationForm(), | ||
'problems_logging_in_form': ProblemsLoggingInForm(), | ||
'system_prefers_dark_theme': request.COOKIES.get('systemPrefersDarkTheme', 'no') == 'yes' # Determine the user's system preference for dark/light theme (for non authenticated users, always use light theme) | ||
'system_prefers_dark_theme': request.COOKIES.get('systemPrefersDarkTheme', 'no') == 'yes', # Determine the user's system preference for dark/light theme (for non authenticated users, always use light theme) | ||
'enable_collections': settings.ENABLE_COLLECTIONS, | ||
'max_sounds_per_collection': settings.MAX_SOUNDS_PER_COLLECTION, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree for |
||
}) | ||
|
||
return tvars |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this allows to differentiate the sound selector in the edit collections page from the users selector (for maintainers) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,11 +46,30 @@ const prepareAddSoundsModalAndFields = (container) => { | |
const removeSoundsButton = addSoundsButton.nextElementSibling; | ||
removeSoundsButton.disabled = true; | ||
|
||
const selectedSoundsDestinationElement = addSoundsButton.parentNode.parentNode.getElementsByClassName('bw-object-selector-container')[0]; | ||
const selectedSoundsDestinationElement = addSoundsButton.parentNode.parentNode.querySelector('.bw-object-selector-container[data-type="sounds"]'); | ||
initializeObjectSelector(selectedSoundsDestinationElement, (element) => { | ||
removeSoundsButton.disabled = element.dataset.selectedIds == "" | ||
}); | ||
|
||
const soundsInput = selectedSoundsDestinationElement.parentNode.parentNode.getElementsByTagName('input')[0]; | ||
if(soundsInput.disabled){ | ||
addSoundsButton.disabled = true | ||
const checkboxes = selectedSoundsDestinationElement.querySelectorAll('span.bw-checkbox-container'); | ||
checkboxes.forEach(checkbox => { | ||
checkbox.remove() | ||
}) | ||
} | ||
|
||
const soundsLabel = selectedSoundsDestinationElement.parentNode.parentNode.getElementsByTagName('label')[0]; | ||
const maxSounds = selectedSoundsDestinationElement.dataset.maxElements; | ||
const maxSoundsHelpText = selectedSoundsDestinationElement.parentNode.parentNode.getElementsByClassName('helptext')[0] | ||
if(maxSounds !== "None"){ | ||
if (soundsInput.value.split(',').length >= maxSounds){ | ||
addSoundsButton.disabled = true | ||
maxSoundsHelpText.style.display = 'block'; | ||
} | ||
} | ||
|
||
removeSoundsButton.addEventListener('click', (evt) => { | ||
evt.preventDefault(); | ||
const soundCheckboxes = selectedSoundsDestinationElement.querySelectorAll('input.bw-checkbox'); | ||
|
@@ -62,6 +81,12 @@ const prepareAddSoundsModalAndFields = (container) => { | |
updateObjectSelectorDataProperties(selectedSoundsDestinationElement); | ||
const selectedSoundsHiddenInput = document.getElementById(addSoundsButton.dataset.selectedSoundsHiddenInputId); | ||
selectedSoundsHiddenInput.value = selectedSoundsDestinationElement.dataset.unselectedIds; | ||
if(maxSounds !== "None" && selectedSoundsHiddenInput.value.split(',').length < maxSounds){ | ||
addSoundsButton.disabled = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also be updated per the comments above. |
||
maxSoundsHelpText.style.display = 'none'; | ||
} | ||
if (soundsLabel){ | ||
soundsLabel.innerHTML = "Sounds in collection (" + selectedSoundsDestinationElement.children.length + ")"} | ||
removeSoundsButton.disabled = true; | ||
}); | ||
|
||
|
@@ -73,11 +98,17 @@ const prepareAddSoundsModalAndFields = (container) => { | |
const newSoundIds = serializedIdListToIntList(selectedSoundIds); | ||
const combinedIds = combineIdsLists(currentSoundIds, newSoundIds); | ||
selectedSoundsHiddenInput.value = combinedIds.join(','); | ||
if(maxSounds !== "None" && selectedSoundsHiddenInput.value.split(',').length >= maxSounds){ | ||
addSoundsButton.disabled = true; | ||
maxSoundsHelpText.style.display = 'block'; | ||
} | ||
if (soundsLabel){ | ||
soundsLabel.innerHTML = "Sounds in collection (" + selectedSoundsDestinationElement.children.length + ")"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A part from removing the hard-coded part and retrieving the number of elements from the sound selector, I automated the display of the help text for this field (informing of maximum number of sounds permitted) and the label of this field (which shows the number of sounds in collection). Although this is also used for pack edits, it has no conflicts (queries for help text and label won't find anything). |
||
initializeObjectSelector(selectedSoundsDestinationElement, (element) => { | ||
removeSoundsButton.disabled = element.dataset.selectedIds == "" | ||
}); | ||
|
||
}); | ||
|
||
}); | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import {dismissModal, handleGenericModal, handleGenericModalWithForm} from "./modal"; | ||
import {showToast} from "./toast"; | ||
import { initializeStuffInContainer } from "../utils/initHelper"; | ||
import {initializeObjectSelector, updateObjectSelectorDataProperties} from "./objectSelector"; | ||
import {combineIdsLists, serializedIdListToIntList} from "../utils/data"; | ||
|
||
const toggleNewCollectionNameDiv = (select, newCollectionNameDiv) => { | ||
if (select.value == '0'){ | ||
// No category is selected, show the new category name input | ||
newCollectionNameDiv.classList.remove('display-none'); | ||
} else { | ||
newCollectionNameDiv.classList.add('display-none'); | ||
} | ||
} | ||
|
||
|
||
const initCollectionFormModal = () => { | ||
|
||
// Modify the form structure to add a "Category" label inline with the select dropdown | ||
const modalContainer = document.getElementById('addSoundToCollectionModal'); | ||
// To display the selector in case of an error in form, the following function is needed, despite it being called in | ||
// handleGenericModal. | ||
initializeStuffInContainer(modalContainer, false, false); | ||
const selectElement = modalContainer.getElementsByTagName('select')[0]; | ||
const wrapper = document.createElement('div'); | ||
wrapper.style = 'display:inline-block;'; | ||
if (selectElement === undefined){ | ||
// If no select element, the modal has probably loaded for an unauthenticated user | ||
return; | ||
} | ||
selectElement.parentNode.insertBefore(wrapper, selectElement.parentNode.firstChild); | ||
const label = document.createElement('div'); | ||
label.innerHTML = "Select a collection:" | ||
label.classList.add('text-grey'); | ||
wrapper.appendChild(label) | ||
wrapper.appendChild(selectElement) | ||
|
||
const categorySelectElement = document.getElementById('id_collection'); | ||
const newCategoryNameElement = document.getElementById('id_new_collection_name'); | ||
toggleNewCollectionNameDiv(categorySelectElement, newCategoryNameElement); | ||
categorySelectElement.addEventListener('change', (event) => { | ||
toggleNewCollectionNameDiv(categorySelectElement, newCategoryNameElement); | ||
});} | ||
|
||
const bindCollectionModals = (container) => { | ||
const collectionButtons = [...container.querySelectorAll('[data-toggle="collection-modal"]')]; | ||
collectionButtons.forEach(element => { | ||
if (element.dataset.alreadyBinded !== undefined){ | ||
return; | ||
} | ||
element.dataset.alreadyBinded = true; | ||
element.addEventListener('click', (evt) => { | ||
evt.preventDefault(); | ||
const modalUrlSplitted = element.dataset.modalContentUrl.split('/') | ||
const soundId = parseInt(modalUrlSplitted[modalUrlSplitted.length - 3], 10) | ||
if (!evt.altKey) { | ||
handleGenericModalWithForm(element.dataset.modalContentUrl, () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in here, bookmarks used a custom saveCollection function, however I found more intuitive and standard to use a handleGenericModalWithForm to properly handle the submission of the SelectCollectionOrNewCollection form, which is the one used to add sounds to Collections from Sound URLS. |
||
initCollectionFormModal(soundId, element.dataset.modalContentUrl); | ||
}, undefined, (req) => {showToast(JSON.parse(req.responseText).message);}, () => {showToast('There were errors processing the form...');}, true, true, undefined, false); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
// TODO: the AddMaintainerModal works really similarly to the AddSoundsModal, so maybe they could share behaviour | ||
// However, it'd be interesting that checked users could be temporarly stored in the modal while other queries are performed | ||
const handleAddMaintainersModal = (modalId, modalUrl, selectedMaintainersDestinationElement, onMaintainersSelectedCallback) => { | ||
handleGenericModalWithForm(modalUrl,(modalContainer) => { | ||
const inputElement = modalContainer.getElementsByTagName('input')[1]; | ||
inputElement.addEventListener('keypress', (evt) => { | ||
if (evt.key === 'Enter'){ | ||
evt.preventDefault(); | ||
const baseUrl = modalUrl.split('?')[0]; | ||
handleAddMaintainersModal(modalId, `${baseUrl}?q=${inputElement.value}`, selectedMaintainersDestinationElement, onMaintainersSelectedCallback); | ||
} | ||
}); | ||
|
||
const objectSelectorElement = modalContainer.getElementsByClassName('bw-object-selector-container')[0]; | ||
initializeObjectSelector(objectSelectorElement, (element) => { | ||
addSelectedMaintainersButton.disabled = element.dataset.selectedIds == "" | ||
}); | ||
|
||
const addSelectedMaintainersButton = modalContainer.getElementsByTagName('button')[0]; | ||
addSelectedMaintainersButton.disabled = true; | ||
addSelectedMaintainersButton.addEventListener('click', evt => { | ||
evt.preventDefault(); | ||
const selectableMaintainerElements = [...modalContainer.getElementsByClassName('bw-selectable-object')]; | ||
selectableMaintainerElements.forEach(element => { | ||
const checkbox = element.querySelectorAll('input.bw-checkbox')[0]; | ||
if (checkbox.checked) { | ||
const clonedCheckbox = checkbox.cloneNode(); | ||
delete(clonedCheckbox.dataset.initialized); | ||
clonedCheckbox.checked = false; | ||
checkbox.parentNode.replaceChild(clonedCheckbox, checkbox) | ||
element.classList.remove('selected'); | ||
selectedMaintainersDestinationElement.appendChild(element.parentNode); | ||
} | ||
}); | ||
onMaintainersSelectedCallback(objectSelectorElement.dataset.selectedIds) | ||
dismissModal(modalId) | ||
}); | ||
}, undefined, showToast('Maintainers added successfully'), showToast('There were some errors handling the modal'), true, true, undefined, false); | ||
}; | ||
|
||
const prepareAddMaintainersModalAndFields = (container) => { | ||
// select all buttons with a toggle that triggers the maintainers modal | ||
const addMaintainersButtons = [...container.querySelectorAll('[data-toggle="add-maintainers-modal"]')]; | ||
// for each button, assign the next sibling to the remove maintainer button and disable it (since nothing will be selected by default) | ||
addMaintainersButtons.forEach(addMaintainersButton => { | ||
const removeMaintainersButton = addMaintainersButton.nextElementSibling; | ||
removeMaintainersButton.disabled = true; | ||
|
||
const selectedMaintainersDestinationElement = addMaintainersButton.parentNode.parentNode.querySelector('.bw-object-selector-container[data-type="users"]'); | ||
initializeObjectSelector(selectedMaintainersDestinationElement, (element) => { | ||
removeMaintainersButton.disabled = element.dataset.selectedIds == "" | ||
}) | ||
|
||
|
||
const maintainersInput = selectedMaintainersDestinationElement.parentNode.parentNode.getElementsByTagName('input')[0]; | ||
if(maintainersInput.disabled !== false){ | ||
addMaintainersButton.disabled = true; | ||
addMaintainersButton.nextElementSibling.remove(); | ||
addMaintainersButton.remove(); | ||
const checkboxes = selectedMaintainersDestinationElement.querySelectorAll('span.bw-checkbox-container'); | ||
checkboxes.forEach(checkbox => { | ||
checkbox.remove() | ||
}) | ||
} | ||
|
||
removeMaintainersButton.addEventListener('click', (evt) => { | ||
evt.preventDefault(); | ||
const maintainerCheckboxes = selectedMaintainersDestinationElement.querySelectorAll('input.bw-checkbox'); | ||
maintainerCheckboxes.forEach(checkbox => { | ||
if (checkbox.checked) { | ||
checkbox.closest('.bw-selectable-object').parentNode.remove(); | ||
} | ||
}); | ||
updateObjectSelectorDataProperties(selectedMaintainersDestinationElement); | ||
const selectedMaintainersHiddenInput = document.getElementById(addMaintainersButton.dataset.selectedMaintainersHiddenInputId); | ||
selectedMaintainersHiddenInput.value = selectedMaintainersDestinationElement.dataset.unselectedIds; | ||
removeMaintainersButton.disabled = true; | ||
}); | ||
|
||
addMaintainersButton.addEventListener('click', (evt) => { | ||
evt.preventDefault(); | ||
handleAddMaintainersModal('addMaintainersModal', addMaintainersButton.dataset.modalUrl, selectedMaintainersDestinationElement, (selectedMaintainersIds) => { | ||
const selectedMaintainersHiddenInput = document.getElementById(addMaintainersButton.dataset.selectedMaintainersHiddenInputId); | ||
const currentMaintainersIds = serializedIdListToIntList(selectedMaintainersHiddenInput.value); | ||
const newMaintainersIds = serializedIdListToIntList(selectedMaintainersIds); | ||
const combinedIds = combineIdsLists(currentMaintainersIds, newMaintainersIds) | ||
selectedMaintainersHiddenInput.value = combinedIds.join(',') | ||
initializeObjectSelector(selectedMaintainersDestinationElement, (element) => { | ||
removeMaintainersButton.disabled = element.dataset.selectedIds == "" | ||
}); | ||
}); | ||
}); | ||
})}; | ||
|
||
export { bindCollectionModals, prepareAddMaintainersModalAndFields }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary comment