From c7dab5e502ac0037d74f42b0d9b3c8024c06c253 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Tue, 10 Oct 2023 11:06:44 +0200 Subject: [PATCH 1/8] intermediate update on displays and forms 1/2 --- app/static/js/ResourceLists/ResourceList.js | 54 ++++--- app/static/js/forms/base-form.js | 138 +++++++++++++++++ app/static/js/forms/index.js | 139 ------------------ app/static/js/resource-displays/index.js | 47 ------ .../js/resource-displays/resource-display.js | 46 ++++++ app/templates/_scripts.html.j2 | 2 + 6 files changed, 216 insertions(+), 210 deletions(-) create mode 100644 app/static/js/forms/base-form.js create mode 100644 app/static/js/resource-displays/resource-display.js diff --git a/app/static/js/ResourceLists/ResourceList.js b/app/static/js/ResourceLists/ResourceList.js index 6bc6ac1f..97e65d70 100644 --- a/app/static/js/ResourceLists/ResourceList.js +++ b/app/static/js/ResourceLists/ResourceList.js @@ -1,31 +1,37 @@ -class ResourceList { +var ResourceLists = {}; + +ResourceLists.autoInit = () => { + for (let propertyName in ResourceLists) { + let property = ResourceLists[propertyName]; + // Call autoInit of all properties that are subclasses of `ResourceLists.BaseList`. + // This does not include `ResourceLists.BaseList` itself. + if (property.prototype instanceof ResourceLists.BaseList) { + // Check if the static `htmlClass` property is defined. + if (property.htmlClass === undefined) {return;} + // Gather all HTML elements that have the `this.htmlClass` class + // and do not have the `no-autoinit` class. + let listElements = document.querySelectorAll(`.${property.htmlClass}:not(.no-autoinit)`); + // Create an instance of this class for each display element. + for (let listElement of listElements) {new property(listElement);} + } + } +}; + +ResourceLists.defaultOptions = { + page: 5, + pagination: { + innerWindow: 2, + outerWindow: 2 + } +}; + +ResourceLists.BaseList = class BaseList { /* A wrapper class for the list.js list. * This class is not meant to be used directly, instead it should be used as * a base class for concrete resource list implementations. */ - static autoInit() { - CorpusList.autoInit(); - CorpusFileList.autoInit(); - JobList.autoInit(); - JobInputList.autoInit(); - JobResultList.autoInit(); - SpaCyNLPPipelineModelList.autoInit(); - TesseractOCRPipelineModelList.autoInit(); - UserList.autoInit(); - AdminUserList.autoInit(); - CorpusFollowerList.autoInit(); - CorpusTextInfoList.autoInit(); - CorpusTokenList.autoInit(); - } - - static defaultOptions = { - page: 5, - pagination: { - innerWindow: 2, - outerWindow: 2 - } - }; + static htmlClass; constructor(listContainerElement, options = {}) { if ('items' in options) { @@ -36,7 +42,7 @@ class ResourceList { } let _options = Utils.mergeObjectsDeep( {item: this.item, valueNames: this.valueNames}, - ResourceList.defaultOptions, + ResourceLists.defaultOptions, options ); this.listContainerElement = listContainerElement; diff --git a/app/static/js/forms/base-form.js b/app/static/js/forms/base-form.js new file mode 100644 index 00000000..b8560485 --- /dev/null +++ b/app/static/js/forms/base-form.js @@ -0,0 +1,138 @@ +Forms.BaseForm = class BaseForm { + static htmlClass; + + constructor(formElement) { + this.formElement = formElement; + this.eventListeners = { + 'requestLoad': [] + }; + this.afterRequestListeners = []; + + for (let selectElement of this.formElement.querySelectorAll('select')) { + selectElement.removeAttribute('required'); + } + + this.formElement.addEventListener('submit', (event) => { + event.preventDefault(); + this.submit(event); + }); + } + + addEventListener(eventType, listener) { + if (eventType in this.eventListeners) { + this.eventListeners[eventType].push(listener); + } else { + throw `Unknown event type ${eventType}`; + } + } + + submit(event) { + let request = new XMLHttpRequest(); + let modalElement = Utils.HTMLToElement( + ` + + ` + ); + document.querySelector('#modals').appendChild(modalElement); + let modal = M.Modal.init( + modalElement, + { + dismissible: false, + onCloseEnd: () => { + modal.destroy(); + modalElement.remove(); + } + } + ); + modal.open(); + + // Remove all previous helper text elements that indicate errors + let errorHelperTextElements = this.formElement + .querySelectorAll('.helper-text[data-helper-text-type="error"]'); + for (let errorHelperTextElement of errorHelperTextElements) { + errorHelperTextElement.remove(); + } + + // Check if select elements are filled out properly + for (let selectElement of this.formElement.querySelectorAll('select')) { + if (selectElement.value === '') { + let inputFieldElement = selectElement.closest('.input-field'); + let errorHelperTextElement = Utils.HTMLToElement( + 'Please select an option.' + ); + inputFieldElement.appendChild(errorHelperTextElement); + inputFieldElement.querySelector('.select-dropdown').classList.add('invalid'); + modal.close(); + return; + } + } + + // Setup abort handling + let cancelElement = modalElement.querySelector('.action-button[data-action="cancel"]'); + cancelElement.addEventListener('click', (event) => {request.abort();}); + + // Setup load handling (after the request completed) + request.addEventListener('load', (event) => { + for (let listener of this.eventListeners['requestLoad']) { + listener(event); + } + if (request.status === 400) { + let responseJson = JSON.parse(request.responseText); + for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) { + let inputFieldElement = this.formElement + .querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`) + .closest('.input-field'); + for (let inputError of inputErrors) { + let errorHelperTextElement = Utils.HTMLToElement( + `${inputError}` + ); + inputFieldElement.appendChild(errorHelperTextElement); + } + } + } + if (request.status === 500) { + app.flash('Internal Server Error', 'error'); + } + modal.close(); + }); + + // Setup progress handling + let progressBarElement = modalElement.querySelector('.progress > .determinate'); + request.upload.addEventListener('progress', (event) => { + let progress = Math.floor(100 * event.loaded / event.total); + progressBarElement.style.width = `${progress}%`; + }); + + request.open(this.formElement.method, this.formElement.action); + request.setRequestHeader('Accept', 'application/json'); + let formData = new FormData(this.formElement); + switch (this.formElement.enctype) { + case 'application/x-www-form-urlencoded': { + let urlSearchParams = new URLSearchParams(formData); + request.send(urlSearchParams); + break; + } + case 'multipart/form-data': { + request.send(formData); + break; + } + case 'text/plain': { + throw 'enctype "text/plain" is not supported'; + break; + } + default: { + break; + } + } + } +}; diff --git a/app/static/js/forms/index.js b/app/static/js/forms/index.js index 0e7529f6..02909448 100644 --- a/app/static/js/forms/index.js +++ b/app/static/js/forms/index.js @@ -16,142 +16,3 @@ Forms.autoInit = () => { } } }; - -Forms.BaseForm = class BaseForm { - static htmlClass; - - constructor(formElement) { - this.formElement = formElement; - this.eventListeners = { - 'requestLoad': [] - }; - this.afterRequestListeners = []; - - for (let selectElement of this.formElement.querySelectorAll('select')) { - selectElement.removeAttribute('required'); - } - - this.formElement.addEventListener('submit', (event) => { - event.preventDefault(); - this.submit(event); - }); - } - - addEventListener(eventType, listener) { - if (eventType in this.eventListeners) { - this.eventListeners[eventType].push(listener); - } else { - throw `Unknown event type ${eventType}`; - } - } - - submit(event) { - let request = new XMLHttpRequest(); - let modalElement = Utils.HTMLToElement( - ` - - ` - ); - document.querySelector('#modals').appendChild(modalElement); - let modal = M.Modal.init( - modalElement, - { - dismissible: false, - onCloseEnd: () => { - modal.destroy(); - modalElement.remove(); - } - } - ); - modal.open(); - - // Remove all previous helper text elements that indicate errors - let errorHelperTextElements = this.formElement - .querySelectorAll('.helper-text[data-helper-text-type="error"]'); - for (let errorHelperTextElement of errorHelperTextElements) { - errorHelperTextElement.remove(); - } - - // Check if select elements are filled out properly - for (let selectElement of this.formElement.querySelectorAll('select')) { - if (selectElement.value === '') { - let inputFieldElement = selectElement.closest('.input-field'); - let errorHelperTextElement = Utils.HTMLToElement( - 'Please select an option.' - ); - inputFieldElement.appendChild(errorHelperTextElement); - inputFieldElement.querySelector('.select-dropdown').classList.add('invalid'); - modal.close(); - return; - } - } - - // Setup abort handling - let cancelElement = modalElement.querySelector('.action-button[data-action="cancel"]'); - cancelElement.addEventListener('click', (event) => {request.abort();}); - - // Setup load handling (after the request completed) - request.addEventListener('load', (event) => { - for (let listener of this.eventListeners['requestLoad']) { - listener(event); - } - if (request.status === 400) { - let responseJson = JSON.parse(request.responseText); - for (let [inputName, inputErrors] of Object.entries(responseJson.errors)) { - let inputFieldElement = this.formElement - .querySelector(`input[name$="${inputName}"], select[name$="${inputName}"]`) - .closest('.input-field'); - for (let inputError of inputErrors) { - let errorHelperTextElement = Utils.HTMLToElement( - `${inputError}` - ); - inputFieldElement.appendChild(errorHelperTextElement); - } - } - } - if (request.status === 500) { - app.flash('Internal Server Error', 'error'); - } - modal.close(); - }); - - // Setup progress handling - let progressBarElement = modalElement.querySelector('.progress > .determinate'); - request.upload.addEventListener('progress', (event) => { - let progress = Math.floor(100 * event.loaded / event.total); - progressBarElement.style.width = `${progress}%`; - }); - - request.open(this.formElement.method, this.formElement.action); - request.setRequestHeader('Accept', 'application/json'); - let formData = new FormData(this.formElement); - switch (this.formElement.enctype) { - case 'application/x-www-form-urlencoded': { - let urlSearchParams = new URLSearchParams(formData); - request.send(urlSearchParams); - break; - } - case 'multipart/form-data': { - request.send(formData); - break; - } - case 'text/plain': { - throw 'enctype "text/plain" is not supported'; - break; - } - default: { - break; - } - } - } -}; diff --git a/app/static/js/resource-displays/index.js b/app/static/js/resource-displays/index.js index 1f795c44..4ec7e997 100644 --- a/app/static/js/resource-displays/index.js +++ b/app/static/js/resource-displays/index.js @@ -16,50 +16,3 @@ ResourceDisplays.autoInit = () => { } } } - -ResourceDisplays.BaseDisplay = class BaseDisplay { - static htmlClass; - - constructor(displayElement) { - this.displayElement = displayElement; - this.userId = this.displayElement.dataset.userId; - this.isInitialized = false; - if (this.userId) { - app.subscribeUser(this.userId) - .then((response) => { - app.socket.on('PATCH', (patch) => { - if (this.isInitialized) {this.onPatch(patch);} - }); - }); - app.getUser(this.userId) - .then((user) => { - this.init(user); - this.isInitialized = true; - }); - } - } - - init(user) {throw 'Not implemented';} - - onPatch(patch) {throw 'Not implemented';} - - setElement(element, value) { - switch (element.tagName) { - case 'INPUT': { - element.value = value; - M.updateTextFields(); - break; - } - default: { - element.innerText = value; - break; - } - } - } - - setElements(elements, value) { - for (let element of elements) { - this.setElement(element, value); - } - } -}; diff --git a/app/static/js/resource-displays/resource-display.js b/app/static/js/resource-displays/resource-display.js new file mode 100644 index 00000000..81fcda5d --- /dev/null +++ b/app/static/js/resource-displays/resource-display.js @@ -0,0 +1,46 @@ +ResourceDisplays.BaseDisplay = class BaseDisplay { + static htmlClass; + + constructor(displayElement) { + this.displayElement = displayElement; + this.userId = this.displayElement.dataset.userId; + this.isInitialized = false; + if (this.userId) { + app.subscribeUser(this.userId) + .then((response) => { + app.socket.on('PATCH', (patch) => { + if (this.isInitialized) {this.onPatch(patch);} + }); + }); + app.getUser(this.userId) + .then((user) => { + this.init(user); + this.isInitialized = true; + }); + } + } + + init(user) {throw 'Not implemented';} + + onPatch(patch) {throw 'Not implemented';} + + setElement(element, value) { + switch (element.tagName) { + case 'INPUT': { + element.value = value; + M.updateTextFields(); + break; + } + default: { + element.innerText = value; + break; + } + } + } + + setElements(elements, value) { + for (let element of elements) { + this.setElement(element, value); + } + } +}; diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 6213e3df..6d3495c9 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -36,6 +36,7 @@ filters='rjsmin', output='gen/Forms.%(version)s.js', 'js/forms/index.js', + 'js/forms/form.js' 'js/forms/create-contribution-form.js', 'js/forms/create-corpus-file-form.js', 'js/forms/create-job-form.js' @@ -47,6 +48,7 @@ filters='rjsmin', output='gen/resource-displays.%(version)s.js', 'js/resource-displays/index.js', + 'js/resource-displays/base-display.js', 'js/resource-displays/corpus-display.js', 'js/resource-displays/job-display.js' %} From 78dd375ef842de9fe43e6fa1495ee19339087c92 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Tue, 10 Oct 2023 15:28:10 +0200 Subject: [PATCH 2/8] Performance update for the docker entrypoint script --- docker-entrypoint.sh | 92 +++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 49d6e4f0..5507f26c 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -6,50 +6,88 @@ NO_COLOR="\033[0m" CHECK_MARK="\xE2\x9C\x93" CROSS_MARK="\xE2\x9D\x8C" -echo -n "Set container UID and GIDs to match the host system..." -if [[ "${NOPAQUE_UID}" == 0 ]]; then +if [[ "${NOPAQUE_UID}" == "0" ]]; then echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" echo "Running as root is not allowed" exit 1 -else - echo "" fi -echo -n "- Updating docker GID ($(getent group docker | cut -d: -f3) -> ${DOCKER_GID})... " -groupmod --gid "${DOCKER_GID}" docker > /dev/null -if [[ "${?}" == "0" ]]; then + +echo "Set container UID and GIDs to match the host system..." + + +############################################################################## +# docker GID # +############################################################################## +if [[ "${DOCKER_GID}" == "$(getent group docker | cut -d: -f3)" ]]; then + echo -n "- docker GID is already matching..." echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" else - echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" - exit 1 + echo -n "- Updating docker GID ($(getent group docker | cut -d: -f3) -> ${DOCKER_GID})... " + groupmod --gid "${DOCKER_GID}" docker > /dev/null + if [[ "${?}" == "0" ]]; then + echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" + else + echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" + exit 1 + fi fi -echo -n "- Updating nopaque GID ($(id -g nopaque) -> ${NOPAQUE_GID})... " -groupmod --gid "${NOPAQUE_GID}" nopaque > /dev/null -if [[ "${?}" == "0" ]]; then + +############################################################################## +# nopaque GID # +############################################################################## +if [[ "${NOPAQUE_GID}" == "$(id -g nopaque)" ]]; then + echo -n "- nopaque GID is already matching..." echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" else - echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" - exit 1 + echo -n "- Updating nopaque GID ($(id -g nopaque) -> ${NOPAQUE_GID})... " + groupmod --gid "${NOPAQUE_GID}" nopaque > /dev/null + if [[ "${?}" == "0" ]]; then + HAS_NOPAQUE_GID_CHANGED=true + echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" + else + echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" + exit 1 + fi + + echo -n "- Updating nopaque directory group... " + chown -R :nopaque /home/nopaque + if [[ "${?}" == "0" ]]; then + echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" + else + echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" + exit 1 + fi fi -echo -n "- Updating nopaque UID ($(id -u nopaque) -> ${NOPAQUE_UID})... " -usermod --uid "${NOPAQUE_UID}" nopaque > /dev/null -if [[ "${?}" == "0" ]]; then + +############################################################################## +# nopaque UID # +############################################################################## +if [[ "${NOPAQUE_UID}" == "$(id -u nopaque)" ]]; then + echo -n "- nopaque UID is already matching..." echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" else - echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" - exit 1 + echo -n "- Updating nopaque UID ($(id -u nopaque) -> ${NOPAQUE_UID})... " + usermod --uid "${NOPAQUE_UID}" nopaque > /dev/null + if [[ "${?}" == "0" ]]; then + echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" + else + echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" + exit 1 + fi + + echo -n "- Updating nopaque directory owner... " + chown -R nopaque /home/nopaque + if [[ "${?}" == "0" ]]; then + echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" + else + echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" + exit 1 + fi fi -echo -n "- Updating nopaque directory owner and group... " -chown -R nopaque:nopaque /home/nopaque -if [[ "${?}" == "0" ]]; then - echo -e "${GREEN_COLOR}${CHECK_MARK}${NO_COLOR}" -else - echo -e "${RED_COLOR}${CROSS_MARK}${NO_COLOR}" - exit 1 -fi exec gosu nopaque ./boot.sh ${@} From a9203cc40979caebed49f3014b1b412f1c422d5f Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Wed, 11 Oct 2023 14:26:07 +0200 Subject: [PATCH 3/8] Fix forms and displays --- app/static/js/resource-displays/corpus-display.js | 2 +- app/static/js/resource-displays/job-display.js | 2 +- app/static/js/resource-displays/resource-display.js | 2 +- app/templates/_scripts.html.j2 | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/static/js/resource-displays/corpus-display.js b/app/static/js/resource-displays/corpus-display.js index 906b17ac..bf6d2589 100644 --- a/app/static/js/resource-displays/corpus-display.js +++ b/app/static/js/resource-displays/corpus-display.js @@ -1,4 +1,4 @@ -ResourceDisplays.CorpusDisplay = class CorpusDisplay extends ResourceDisplays.BaseDisplay { +ResourceDisplays.CorpusDisplay = class CorpusDisplay extends ResourceDisplays.ResourceDisplay { static htmlClass = 'corpus-display'; constructor(displayElement) { diff --git a/app/static/js/resource-displays/job-display.js b/app/static/js/resource-displays/job-display.js index 4ab370e2..78ebc22c 100644 --- a/app/static/js/resource-displays/job-display.js +++ b/app/static/js/resource-displays/job-display.js @@ -1,4 +1,4 @@ -ResourceDisplays.JobDisplay = class JobDisplay extends ResourceDisplays.BaseDisplay { +ResourceDisplays.JobDisplay = class JobDisplay extends ResourceDisplays.ResourceDisplay { static htmlClass = 'job-display'; constructor(displayElement) { diff --git a/app/static/js/resource-displays/resource-display.js b/app/static/js/resource-displays/resource-display.js index 81fcda5d..39401d2b 100644 --- a/app/static/js/resource-displays/resource-display.js +++ b/app/static/js/resource-displays/resource-display.js @@ -1,4 +1,4 @@ -ResourceDisplays.BaseDisplay = class BaseDisplay { +ResourceDisplays.ResourceDisplay = class ResourceDisplay { static htmlClass; constructor(displayElement) { diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 6d3495c9..31088634 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -36,7 +36,7 @@ filters='rjsmin', output='gen/Forms.%(version)s.js', 'js/forms/index.js', - 'js/forms/form.js' + 'js/forms/base-form.js', 'js/forms/create-contribution-form.js', 'js/forms/create-corpus-file-form.js', 'js/forms/create-job-form.js' @@ -48,7 +48,7 @@ filters='rjsmin', output='gen/resource-displays.%(version)s.js', 'js/resource-displays/index.js', - 'js/resource-displays/base-display.js', + 'js/resource-displays/resource-display.js', 'js/resource-displays/corpus-display.js', 'js/resource-displays/job-display.js' %} From 067318bb89422b909069d808c674a73c95611622 Mon Sep 17 00:00:00 2001 From: Patrick Jentsch Date: Wed, 11 Oct 2023 16:20:17 +0200 Subject: [PATCH 4/8] Huge List class update --- .../CorpusAnalysisStaticVisualization.js | 4 +- app/static/js/resource-displays/index.js | 8 ++-- .../admin-user-list.js} | 10 ++--- .../corpus-file-list.js} | 10 ++--- .../corpus-follower-list.js} | 10 ++--- .../corpus-list.js} | 10 ++--- .../corpus-text-info-list.js} | 13 ++----- .../corpus-token-list.js} | 12 ++---- .../detailed-public-corpus-list.js} | 6 ++- app/static/js/resource-lists/index.js | 18 +++++++++ .../job-input-list.js} | 10 ++--- .../JobList.js => resource-lists/job-list.js} | 10 ++--- .../job-result-list.js} | 10 ++--- .../public-corpus-list.js} | 6 ++- .../resource-list.js} | 39 +++++-------------- .../spacy-nlp-pipeline-model-list.js} | 10 ++--- .../tesseract-ocr-pipeline-model-list.js} | 10 ++--- .../user-list.js} | 10 ++--- app/templates/_scripts.html.j2 | 35 +++++++++-------- app/templates/admin/corpora.html.j2 | 2 +- app/templates/admin/users.html.j2 | 2 +- app/templates/corpora/public_corpus.html.j2 | 4 +- app/templates/main/social_area.html.j2 | 4 +- app/templates/users/user.html.j2 | 4 +- 24 files changed, 106 insertions(+), 151 deletions(-) rename app/static/js/{ResourceLists/AdminUserList.js => resource-lists/admin-user-list.js} (93%) rename app/static/js/{ResourceLists/CorpusFileList.js => resource-lists/corpus-file-list.js} (98%) rename app/static/js/{ResourceLists/CorpusFollowerList.js => resource-lists/corpus-follower-list.js} (96%) rename app/static/js/{ResourceLists/CorpusList.js => resource-lists/corpus-list.js} (98%) rename app/static/js/{ResourceLists/CorpusTextInfoList.js => resource-lists/corpus-text-info-list.js} (93%) rename app/static/js/{ResourceLists/CorpusTokenList.js => resource-lists/corpus-token-list.js} (94%) rename app/static/js/{ResourceLists/DetailledPublicCorpusList.js => resource-lists/detailed-public-corpus-list.js} (93%) create mode 100644 app/static/js/resource-lists/index.js rename app/static/js/{ResourceLists/JobInputList.js => resource-lists/job-input-list.js} (92%) rename app/static/js/{ResourceLists/JobList.js => resource-lists/job-list.js} (98%) rename app/static/js/{ResourceLists/JobResultList.js => resource-lists/job-result-list.js} (93%) rename app/static/js/{ResourceLists/PublicCorpusList.js => resource-lists/public-corpus-list.js} (93%) rename app/static/js/{ResourceLists/ResourceList.js => resource-lists/resource-list.js} (61%) rename app/static/js/{ResourceLists/SpacyNLPPipelineModelList.js => resource-lists/spacy-nlp-pipeline-model-list.js} (96%) rename app/static/js/{ResourceLists/TesseractOCRPipelineModelList.js => resource-lists/tesseract-ocr-pipeline-model-list.js} (96%) rename app/static/js/{ResourceLists/UserList.js => resource-lists/user-list.js} (93%) diff --git a/app/static/js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js b/app/static/js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js index 38c48c3c..4d51b014 100644 --- a/app/static/js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js +++ b/app/static/js/CorpusAnalysis/CorpusAnalysisStaticVisualization.js @@ -104,7 +104,7 @@ class CorpusAnalysisStaticVisualization { renderTextInfoList() { let corpusData = this.data.corpus.o.staticData; let corpusTextInfoListElement = document.querySelector('.corpus-text-info-list'); - let corpusTextInfoList = new CorpusTextInfoList(corpusTextInfoListElement); + let corpusTextInfoList = new ResourceLists.CorpusTextInfoList(corpusTextInfoListElement); let texts = corpusData.s_attrs.text.lexicon; let textData = []; for (let i = 0; i < Object.entries(texts).length; i++) { @@ -213,7 +213,7 @@ class CorpusAnalysisStaticVisualization { async renderTokenList() { let corpusTokenListElement = document.querySelector('.corpus-token-list'); - let corpusTokenList = new CorpusTokenList(corpusTokenListElement); + let corpusTokenList = new ResourceLists.CorpusTokenList(corpusTokenListElement); let filteredData = this.filterData(); let stopwords = this.data.stopwords; if (this.data.stopwords === undefined) { diff --git a/app/static/js/resource-displays/index.js b/app/static/js/resource-displays/index.js index 4ec7e997..8cc8809e 100644 --- a/app/static/js/resource-displays/index.js +++ b/app/static/js/resource-displays/index.js @@ -3,9 +3,9 @@ var ResourceDisplays = {}; ResourceDisplays.autoInit = () => { for (let propertyName in ResourceDisplays) { let property = ResourceDisplays[propertyName]; - // Call autoInit of all properties that are subclasses of `ResourceDisplays.BaseDisplay`. - // This does not include `ResourceDisplays.BaseDisplay` itself. - if (property.prototype instanceof ResourceDisplays.BaseDisplay) { + // Call autoInit of all properties that are subclasses of `ResourceDisplays.ResourceDisplay`. + // This does not include `ResourceDisplays.ResourceDisplay` itself. + if (property.prototype instanceof ResourceDisplays.ResourceDisplay) { // Check if the static `htmlClass` property is defined. if (property.htmlClass === undefined) {return;} // Gather all HTML elements that have the `this.htmlClass` class @@ -15,4 +15,4 @@ ResourceDisplays.autoInit = () => { for (let displayElement of displayElements) {new property(displayElement);} } } -} +}; diff --git a/app/static/js/ResourceLists/AdminUserList.js b/app/static/js/resource-lists/admin-user-list.js similarity index 93% rename from app/static/js/ResourceLists/AdminUserList.js rename to app/static/js/resource-lists/admin-user-list.js index 0b8f0c16..40feab27 100644 --- a/app/static/js/ResourceLists/AdminUserList.js +++ b/app/static/js/resource-lists/admin-user-list.js @@ -1,9 +1,5 @@ -class AdminUserList extends ResourceList { - static autoInit() { - for (let adminUserListElement of document.querySelectorAll('.admin-user-list:not(.no-autoinit)')) { - new AdminUserList(adminUserListElement); - } - } +ResourceLists.AdminUserList = class AdminUserList extends ResourceLists.ResourceList { + static htmlClass = 'admin-user-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -108,4 +104,4 @@ class AdminUserList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/CorpusFileList.js b/app/static/js/resource-lists/corpus-file-list.js similarity index 98% rename from app/static/js/ResourceLists/CorpusFileList.js rename to app/static/js/resource-lists/corpus-file-list.js index 9997b061..7e6a5da9 100644 --- a/app/static/js/ResourceLists/CorpusFileList.js +++ b/app/static/js/resource-lists/corpus-file-list.js @@ -1,9 +1,5 @@ -class CorpusFileList extends ResourceList { - static autoInit() { - for (let corpusFileListElement of document.querySelectorAll('.corpus-file-list:not(.no-autoinit)')) { - new CorpusFileList(corpusFileListElement); - } - } +ResourceLists.CorpusFileList = class CorpusFileList extends ResourceLists.ResourceList { + static htmlClass = 'corpus-file-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -369,4 +365,4 @@ class CorpusFileList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/CorpusFollowerList.js b/app/static/js/resource-lists/corpus-follower-list.js similarity index 96% rename from app/static/js/ResourceLists/CorpusFollowerList.js rename to app/static/js/resource-lists/corpus-follower-list.js index ca70a6c7..b8a4c255 100644 --- a/app/static/js/ResourceLists/CorpusFollowerList.js +++ b/app/static/js/resource-lists/corpus-follower-list.js @@ -1,9 +1,5 @@ -class CorpusFollowerList extends ResourceList { - static autoInit() { - for (let corpusFollowerListElement of document.querySelectorAll('.corpus-follower-list:not(.no-autoinit)')) { - new CorpusFollowerList(corpusFollowerListElement); - } - } +ResourceLists.CorpusFollowerList = class CorpusFollowerList extends ResourceLists.ResourceList { + static htmlClass = 'corpus-follower-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -196,4 +192,4 @@ class CorpusFollowerList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/CorpusList.js b/app/static/js/resource-lists/corpus-list.js similarity index 98% rename from app/static/js/ResourceLists/CorpusList.js rename to app/static/js/resource-lists/corpus-list.js index 985ff1d1..f4289d8b 100644 --- a/app/static/js/ResourceLists/CorpusList.js +++ b/app/static/js/resource-lists/corpus-list.js @@ -1,9 +1,5 @@ -class CorpusList extends ResourceList { - static autoInit() { - for (let corpusListElement of document.querySelectorAll('.corpus-list:not(.no-autoinit)')) { - new CorpusList(corpusListElement); - } - } +ResourceLists.CorpusList = class CorpusList extends ResourceLists.ResourceList { + static htmlClass = 'corpus-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -370,4 +366,4 @@ class CorpusList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/CorpusTextInfoList.js b/app/static/js/resource-lists/corpus-text-info-list.js similarity index 93% rename from app/static/js/ResourceLists/CorpusTextInfoList.js rename to app/static/js/resource-lists/corpus-text-info-list.js index f1545d70..00c5467c 100644 --- a/app/static/js/ResourceLists/CorpusTextInfoList.js +++ b/app/static/js/resource-lists/corpus-text-info-list.js @@ -1,10 +1,5 @@ -class CorpusTextInfoList extends ResourceList { - - static autoInit() { - for (let corpusTextInfoListElement of document.querySelectorAll('.corpus-text-info-list:not(.no-autoinit)')) { - new CorpusTextInfoList(corpusTextInfoListElement); - } - } +ResourceLists.CorpusTextInfoList = class CorpusTextInfoList extends ResourceLists.ResourceList { + static htmlClass = 'corpus-text-info-list'; static defaultOptions = { page: 5 @@ -12,7 +7,7 @@ class CorpusTextInfoList extends ResourceList { constructor(listContainerElement, options = {}) { let _options = Utils.mergeObjectsDeep( - CorpusTextInfoList.defaultOptions, + ResourceLists.CorpusTextInfoList.defaultOptions, options ); super(listContainerElement, _options); @@ -109,4 +104,4 @@ class CorpusTextInfoList extends ResourceList { clickedSortElement.style.color = '#aa9cc9'; clickedSortElement.innerHTML = clickedSortElement.classList.contains('asc') ? 'arrow_drop_down' : 'arrow_drop_up'; } -} +}; diff --git a/app/static/js/ResourceLists/CorpusTokenList.js b/app/static/js/resource-lists/corpus-token-list.js similarity index 94% rename from app/static/js/ResourceLists/CorpusTokenList.js rename to app/static/js/resource-lists/corpus-token-list.js index cc16692b..eddd82ea 100644 --- a/app/static/js/ResourceLists/CorpusTokenList.js +++ b/app/static/js/resource-lists/corpus-token-list.js @@ -1,9 +1,5 @@ -class CorpusTokenList extends ResourceList { - static autoInit() { - for (let corpusTokenListElement of document.querySelectorAll('.corpus-token-list:not(.no-autoinit)')) { - new CorpusTokenList(corpusTokenListElement); - } - } +ResourceLists.CorpusTokenList = class CorpusTokenList extends ResourceLists.ResourceList { + static htmlClass = 'corpus-token-list'; static defaultOptions = { page: 7 @@ -11,7 +7,7 @@ class CorpusTokenList extends ResourceList { constructor(listContainerElement, options = {}) { let _options = Utils.mergeObjectsDeep( - CorpusTokenList.defaultOptions, + ResourceLists.CorpusTokenList.defaultOptions, options ); super(listContainerElement, _options); @@ -138,4 +134,4 @@ class CorpusTokenList extends ResourceList { } } -} +}; diff --git a/app/static/js/ResourceLists/DetailledPublicCorpusList.js b/app/static/js/resource-lists/detailed-public-corpus-list.js similarity index 93% rename from app/static/js/ResourceLists/DetailledPublicCorpusList.js rename to app/static/js/resource-lists/detailed-public-corpus-list.js index 5bfc59ff..e855e07b 100644 --- a/app/static/js/ResourceLists/DetailledPublicCorpusList.js +++ b/app/static/js/resource-lists/detailed-public-corpus-list.js @@ -1,4 +1,6 @@ -class DetailledPublicCorpusList extends CorpusList { +ResourceLists.DetailedPublicCorpusList = class DetailedPublicCorpusList extends ResourceLists.ResourceList { + static htmlClass = 'detailed-public-corpus-list'; + get item() { return (values) => { return ` @@ -68,4 +70,4 @@ class DetailledPublicCorpusList extends CorpusList { 'current-user-is-following': Object.values(corpus.corpus_follower_associations).some(association => association.follower.id === currentUserId) }; } -} +}; diff --git a/app/static/js/resource-lists/index.js b/app/static/js/resource-lists/index.js new file mode 100644 index 00000000..513da46b --- /dev/null +++ b/app/static/js/resource-lists/index.js @@ -0,0 +1,18 @@ +var ResourceLists = {}; + +ResourceLists.autoInit = () => { + for (let propertyName in ResourceLists) { + let property = ResourceLists[propertyName]; + // Call autoInit of all properties that are subclasses of `ResourceLists.ResourceList`. + // This does not include `ResourceLists.ResourceList` itself. + if (property.prototype instanceof ResourceLists.ResourceList) { + // Check if the static `htmlClass` property is defined. + if (property.htmlClass === undefined) {return;} + // Gather all HTML elements that have the `this.htmlClass` class + // and do not have the `no-autoinit` class. + let listElements = document.querySelectorAll(`.${property.htmlClass}:not(.no-autoinit)`); + // Create an instance of this class for each display element. + for (let listElement of listElements) {new property(listElement);} + } + } +}; diff --git a/app/static/js/ResourceLists/JobInputList.js b/app/static/js/resource-lists/job-input-list.js similarity index 92% rename from app/static/js/ResourceLists/JobInputList.js rename to app/static/js/resource-lists/job-input-list.js index 97a8dd14..609702b5 100644 --- a/app/static/js/ResourceLists/JobInputList.js +++ b/app/static/js/resource-lists/job-input-list.js @@ -1,9 +1,5 @@ -class JobInputList extends ResourceList { - static autoInit() { - for (let jobInputListElement of document.querySelectorAll('.job-input-list:not(.no-autoinit)')) { - new JobInputList(jobInputListElement); - } - } +ResourceLists.JobInputList = class JobInputList extends ResourceLists.ResourceList { + static htmlClass = 'job-input-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -90,4 +86,4 @@ class JobInputList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/JobList.js b/app/static/js/resource-lists/job-list.js similarity index 98% rename from app/static/js/ResourceLists/JobList.js rename to app/static/js/resource-lists/job-list.js index 1cb3ea60..c751e705 100644 --- a/app/static/js/ResourceLists/JobList.js +++ b/app/static/js/resource-lists/job-list.js @@ -1,9 +1,5 @@ -class JobList extends ResourceList { - static autoInit() { - for (let jobListElement of document.querySelectorAll('.job-list:not(.no-autoinit)')) { - new JobList(jobListElement); - } - } +ResourceLists.JobList = class JobList extends ResourceLists.ResourceList { + static htmlClass = 'job-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -323,4 +319,4 @@ class JobList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/JobResultList.js b/app/static/js/resource-lists/job-result-list.js similarity index 93% rename from app/static/js/ResourceLists/JobResultList.js rename to app/static/js/resource-lists/job-result-list.js index b0cbc088..e71759a5 100644 --- a/app/static/js/ResourceLists/JobResultList.js +++ b/app/static/js/resource-lists/job-result-list.js @@ -1,9 +1,5 @@ -class JobResultList extends ResourceList { - static autoInit() { - for (let jobResultListElement of document.querySelectorAll('.job-result-list:not(.no-autoinit)')) { - new JobResultList(jobResultListElement); - } - } +ResourceLists.JobResultList = class JobResultList extends ResourceLists.ResourceList { + static htmlClass = 'job-result-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -115,4 +111,4 @@ class JobResultList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/PublicCorpusList.js b/app/static/js/resource-lists/public-corpus-list.js similarity index 93% rename from app/static/js/ResourceLists/PublicCorpusList.js rename to app/static/js/resource-lists/public-corpus-list.js index 1ef98273..659dfd09 100644 --- a/app/static/js/ResourceLists/PublicCorpusList.js +++ b/app/static/js/resource-lists/public-corpus-list.js @@ -1,4 +1,6 @@ -class PublicCorpusList extends CorpusList { +ResourceLists.PublicCorpusList = class PublicCorpusList extends ResourceLists.ResourceList { + static htmlClass = 'public-corpus-list'; + get item() { return (values) => { return ` @@ -52,4 +54,4 @@ class PublicCorpusList extends CorpusList {
    `.trim(); } -} +}; diff --git a/app/static/js/ResourceLists/ResourceList.js b/app/static/js/resource-lists/resource-list.js similarity index 61% rename from app/static/js/ResourceLists/ResourceList.js rename to app/static/js/resource-lists/resource-list.js index 97e65d70..ba71f03f 100644 --- a/app/static/js/ResourceLists/ResourceList.js +++ b/app/static/js/resource-lists/resource-list.js @@ -1,31 +1,4 @@ -var ResourceLists = {}; - -ResourceLists.autoInit = () => { - for (let propertyName in ResourceLists) { - let property = ResourceLists[propertyName]; - // Call autoInit of all properties that are subclasses of `ResourceLists.BaseList`. - // This does not include `ResourceLists.BaseList` itself. - if (property.prototype instanceof ResourceLists.BaseList) { - // Check if the static `htmlClass` property is defined. - if (property.htmlClass === undefined) {return;} - // Gather all HTML elements that have the `this.htmlClass` class - // and do not have the `no-autoinit` class. - let listElements = document.querySelectorAll(`.${property.htmlClass}:not(.no-autoinit)`); - // Create an instance of this class for each display element. - for (let listElement of listElements) {new property(listElement);} - } - } -}; - -ResourceLists.defaultOptions = { - page: 5, - pagination: { - innerWindow: 2, - outerWindow: 2 - } -}; - -ResourceLists.BaseList = class BaseList { +ResourceLists.ResourceList = class ResourceList { /* A wrapper class for the list.js list. * This class is not meant to be used directly, instead it should be used as * a base class for concrete resource list implementations. @@ -33,6 +6,14 @@ ResourceLists.BaseList = class BaseList { static htmlClass; + static defaultOptions = { + page: 5, + pagination: { + innerWindow: 2, + outerWindow: 2 + } + }; + constructor(listContainerElement, options = {}) { if ('items' in options) { throw '"items" is not supported as an option, define it as a getter in the list class'; @@ -42,7 +23,7 @@ ResourceLists.BaseList = class BaseList { } let _options = Utils.mergeObjectsDeep( {item: this.item, valueNames: this.valueNames}, - ResourceLists.defaultOptions, + ResourceLists.ResourceList.defaultOptions, options ); this.listContainerElement = listContainerElement; diff --git a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js b/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js similarity index 96% rename from app/static/js/ResourceLists/SpacyNLPPipelineModelList.js rename to app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js index 46d3739d..195fa60c 100644 --- a/app/static/js/ResourceLists/SpacyNLPPipelineModelList.js +++ b/app/static/js/resource-lists/spacy-nlp-pipeline-model-list.js @@ -1,9 +1,5 @@ -class SpaCyNLPPipelineModelList extends ResourceList { - static autoInit() { - for (let spaCyNLPPipelineModelListElement of document.querySelectorAll('.spacy-nlp-pipeline-model-list:not(.no-autoinit)')) { - new SpaCyNLPPipelineModelList(spaCyNLPPipelineModelListElement); - } - } +ResourceLists.SpaCyNLPPipelineModelList = class SpaCyNLPPipelineModelList extends ResourceLists.ResourceList { + static htmlClass = 'spacy-nlp-pipeline-model-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -220,4 +216,4 @@ class SpaCyNLPPipelineModelList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js b/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js similarity index 96% rename from app/static/js/ResourceLists/TesseractOCRPipelineModelList.js rename to app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js index 765f44a6..975bfe5c 100644 --- a/app/static/js/ResourceLists/TesseractOCRPipelineModelList.js +++ b/app/static/js/resource-lists/tesseract-ocr-pipeline-model-list.js @@ -1,9 +1,5 @@ -class TesseractOCRPipelineModelList extends ResourceList { - static autoInit() { - for (let tesseractOCRPipelineModelListElement of document.querySelectorAll('.tesseract-ocr-pipeline-model-list:not(.no-autoinit)')) { - new TesseractOCRPipelineModelList(tesseractOCRPipelineModelListElement); - } - } +ResourceLists.TesseractOCRPipelineModelList = class TesseractOCRPipelineModelList extends ResourceLists.ResourceList { + static htmlClass = 'tesseract-ocr-pipeline-model-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -229,4 +225,4 @@ class TesseractOCRPipelineModelList extends ResourceList { } } } -} +}; diff --git a/app/static/js/ResourceLists/UserList.js b/app/static/js/resource-lists/user-list.js similarity index 93% rename from app/static/js/ResourceLists/UserList.js rename to app/static/js/resource-lists/user-list.js index 2ba4dc19..6ef14e2f 100644 --- a/app/static/js/ResourceLists/UserList.js +++ b/app/static/js/resource-lists/user-list.js @@ -1,9 +1,5 @@ -class UserList extends ResourceList { - static autoInit() { - for (let userListElement of document.querySelectorAll('.user-list:not(.no-autoinit)')) { - new UserList(userListElement); - } - } +ResourceLists.UserList = class UserList extends ResourceLists.ResourceList { + static htmlClass = 'user-list'; constructor(listContainerElement, options = {}) { super(listContainerElement, options); @@ -101,4 +97,4 @@ class UserList extends ResourceList { } } } -} +}; diff --git a/app/templates/_scripts.html.j2 b/app/templates/_scripts.html.j2 index 31088634..2b84659a 100644 --- a/app/templates/_scripts.html.j2 +++ b/app/templates/_scripts.html.j2 @@ -57,22 +57,23 @@ {%- assets filters='rjsmin', - output='gen/ResourceLists.%(version)s.js', - 'js/ResourceLists/ResourceList.js', - 'js/ResourceLists/CorpusFileList.js', - 'js/ResourceLists/CorpusList.js', - 'js/ResourceLists/PublicCorpusList.js', - 'js/ResourceLists/JobList.js', - 'js/ResourceLists/JobInputList.js', - 'js/ResourceLists/JobResultList.js', - 'js/ResourceLists/SpacyNLPPipelineModelList.js', - 'js/ResourceLists/TesseractOCRPipelineModelList.js', - 'js/ResourceLists/UserList.js', - 'js/ResourceLists/AdminUserList.js', - 'js/ResourceLists/CorpusFollowerList.js', - 'js/ResourceLists/CorpusTextInfoList.js', - 'js/ResourceLists/DetailledPublicCorpusList.js', - 'js/ResourceLists/CorpusTokenList.js' + output='gen/resource-lists.%(version)s.js', + 'js/resource-lists/index.js', + 'js/resource-lists/resource-list.js', + 'js/resource-lists/admin-user-list.js', + 'js/resource-lists/corpus-file-list.js', + 'js/resource-lists/corpus-follower-list.js', + 'js/resource-lists/corpus-list.js', + 'js/resource-lists/corpus-text-info-list.js', + 'js/resource-lists/corpus-token-list.js', + 'js/resource-lists/detailed-public-corpus-list.js', + 'js/resource-lists/job-input-list.js', + 'js/resource-lists/job-list.js', + 'js/resource-lists/job-result-list.js', + 'js/resource-lists/public-corpus-list.js', + 'js/resource-lists/spacy-nlp-pipeline-model-list.js', + 'js/resource-lists/tesseract-ocr-pipeline-model-list.js', + 'js/resource-lists/user-list.js' %} {%- endassets %} @@ -143,7 +144,7 @@ {alignment: 'right', constrainWidth: false, coverTrigger: false} ); ResourceDisplays.autoInit(); - ResourceList.autoInit(); + ResourceLists.autoInit(); Forms.autoInit(); // Display flashed messages diff --git a/app/templates/admin/corpora.html.j2 b/app/templates/admin/corpora.html.j2 index 90a4b28e..3eeb7981 100644 --- a/app/templates/admin/corpora.html.j2 +++ b/app/templates/admin/corpora.html.j2 @@ -17,7 +17,7 @@ {{ super() }} +{%- endassets %} + +{%- assets + filters='rjsmin', + output='gen/utils.%(version)s.js', + 'js/utils/index.js', + 'js/utils/utils.js' %} {%- endassets %} @@ -110,7 +118,7 @@