From 1f1618d74f76f742d618c3790953f61c91dcef46 Mon Sep 17 00:00:00 2001 From: Lukasz Ostafin Date: Mon, 26 Feb 2024 08:48:35 +0100 Subject: [PATCH] UX improvments for upload popup --- .../public/js/scripts/admin.error.page.js | 9 +- .../public/js/scripts/core/notifications.js | 22 ++ .../js/scripts/helpers/context.helper.js | 8 + .../js/scripts/helpers/notification.helper.js | 9 +- .../modules/muti-file-upload/_drop.area.scss | 24 +- .../muti-file-upload/_progress.bar.scss | 6 +- .../muti-file-upload/_upload.item.scss | 53 +++- .../muti-file-upload/_upload.popup.scss | 18 +- .../ibexa_multi_file_upload.en.xliff | 15 +- .../upload-list/upload.item.component.js | 259 +++++------------- .../upload-popup/upload.popup.component.js | 2 +- .../multi.file.upload.module.js | 11 +- .../services/multi.file.upload.service.js | 75 +++-- .../services/universal.discovery.service.js | 1 + 14 files changed, 267 insertions(+), 245 deletions(-) create mode 100644 src/bundle/Resources/public/js/scripts/core/notifications.js diff --git a/src/bundle/Resources/public/js/scripts/admin.error.page.js b/src/bundle/Resources/public/js/scripts/admin.error.page.js index 32ef0fc1b2..af69cc64af 100644 --- a/src/bundle/Resources/public/js/scripts/admin.error.page.js +++ b/src/bundle/Resources/public/js/scripts/admin.error.page.js @@ -1,5 +1,9 @@ (function (global, doc, iconPaths) { - const notificationsContainer = doc.querySelector('.ibexa-notifications-container'); + const configRootNodeSelector = getRootNodeSelector(); + const noticeRootNode = configRootNodeSelector + ? doc.querySelector(configRootNodeSelector) + : doc.body + const notificationsContainer = noticeRootNode.querySelector('.ibexa-notifications-container'); const notifications = JSON.parse(notificationsContainer.dataset.notifications); const { template } = notificationsContainer.dataset; const iconsMap = { @@ -16,6 +20,7 @@ return stringTempNode.innerHTML; }; const addNotification = ({ detail }) => { + console.log('add n') const { label, message } = detail; const container = doc.createElement('div'); const iconSetPath = iconPaths.iconSets[iconPaths.defaultIconSet]; @@ -38,5 +43,5 @@ messages.forEach((message) => addNotification({ detail: { label, message } })); }); - doc.body.addEventListener('ibexa-notify', addNotification, false); + noticeRootNode.addEventListener('ibexa-notify', addNotification, false); })(window, window.document, window.ibexa.iconPaths); diff --git a/src/bundle/Resources/public/js/scripts/core/notifications.js b/src/bundle/Resources/public/js/scripts/core/notifications.js new file mode 100644 index 0000000000..dd205989e2 --- /dev/null +++ b/src/bundle/Resources/public/js/scripts/core/notifications.js @@ -0,0 +1,22 @@ +import { getAdminUiConfig, getRootNodeSelector } from "./helpers/context.helper"; +import { escapeHTML } from "./helpers/text.helper"; +import { getIconPath } from "./helpers/icon.helper"; + +const notificationsContainer = null; +const notifications = null; + +export const initNotifications = (notificationsContainer) => { + const adminUiConfig = getAdminUiConfig(); + const notifications = JSON.parse(notificationsContainer.dataset.notifications); + + console.log("Selector:", getRootNodeSelector); + + Object.entries(notifications).forEach(([label, messages]) => { + messages.forEach((message) => addNotification({ detail: { label, message } })); + }); + + doc.body.addEventListener('ibexa-notify', addNotification, false); +} + +export const addNotification = ({ detail }) => { +} \ No newline at end of file diff --git a/src/bundle/Resources/public/js/scripts/helpers/context.helper.js b/src/bundle/Resources/public/js/scripts/helpers/context.helper.js index 44d0db3888..e78955d20e 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/context.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/context.helper.js @@ -1,5 +1,6 @@ let { bootstrap, flatpickr, moment, Popper, Routing, Translator } = window; let adminUiConfig = window.ibexa?.adminUiConfig; +let rootNodeSelector = null; const restInfo = { accessToken: null, instanceUrl: window.location.origin, @@ -44,6 +45,12 @@ export const setTranslator = (TranslatorInstance, forceSet = false) => { Translator = TranslatorInstance; } }; +export const setRootNodeSelector = (rootNodeSelectorParam, forceSet = false) => { + if (!rootNodeSelector || forceSet) { + rootNodeSelector = rootNodeSelectorParam; + } +}; + export const getAdminUiConfig = () => adminUiConfig; export const getBootstrap = () => bootstrap; @@ -53,3 +60,4 @@ export const getPopper = () => Popper; export const getRouting = () => Routing; export const getTranslator = () => Translator; export const getRestInfo = () => restInfo; +export const getRootNodeSelector = () => rootNodeSelector; diff --git a/src/bundle/Resources/public/js/scripts/helpers/notification.helper.js b/src/bundle/Resources/public/js/scripts/helpers/notification.helper.js index fbaee33f5a..70387b45fb 100644 --- a/src/bundle/Resources/public/js/scripts/helpers/notification.helper.js +++ b/src/bundle/Resources/public/js/scripts/helpers/notification.helper.js @@ -1,3 +1,5 @@ +import { getRootNodeSelector } from "./context.helper"; + const { document: doc } = window; const NOTIFICATION_INFO_LABEL = 'info'; @@ -16,9 +18,14 @@ const NOTIFICATION_ERROR_LABEL = 'error'; * @param {Object} detail.rawPlaceholdersMap */ const showNotification = (detail) => { + const configRootNodeSelector = getRootNodeSelector(); + const eventDispatchNode = configRootNodeSelector + ? doc.querySelector(configRootNodeSelector) + : doc.body + const event = new CustomEvent('ibexa-notify', { detail }); - doc.body.dispatchEvent(event); + eventDispatchNode.dispatchEvent(event); }; /** diff --git a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_drop.area.scss b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_drop.area.scss index c4a006bef8..e479563959 100644 --- a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_drop.area.scss +++ b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_drop.area.scss @@ -5,22 +5,22 @@ flex-direction: column; justify-content: center; align-items: center; + padding: calculateRem(47px); &__message { - color: $ibexa-color-dark-400; - margin-bottom: calculateRem(12px); + color: $ibexa-color-dark; + margin-bottom: calculateRem(16px); + } - &--main { - cursor: auto; - font-weight: bold; - margin-top: calculateRem(44px); - } + &__message--main { + cursor: auto; + font-weight: 600; + } - &--filesize { - color: $ibexa-color-dark-300; - font-size: $ibexa-text-font-size-medium; - margin: calculateRem(12px) 0 calculateRem(44px); - } + &__message--filesize { + margin: calculateRem(16px) 0 0 0; + color: $ibexa-color-dark-400; + font-size: $ibexa-text-font-size-medium; } &__input--hidden { diff --git a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_progress.bar.scss b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_progress.bar.scss index 88c2ff41ff..0a7dd9ff5a 100644 --- a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_progress.bar.scss +++ b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_progress.bar.scss @@ -9,13 +9,13 @@ border-radius: calculateRem(4px); transition: width 0.2s linear; height: calculateRem(8px); - width: 10vw; + width: calculateRem(176px); position: relative; &::after { content: ''; width: calc(100% - var(--progress)); - height: calculateRem(10px); + height: calculateRem(11px); border-radius: calculateRem(4px); position: absolute; right: 0; @@ -29,7 +29,7 @@ } &__label { - font-size: $ibexa-text-font-size-small; + font-size: $ibexa-text-font-size-medium; color: $ibexa-color-dark-400; } } diff --git a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.item.scss b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.item.scss index b858b87bd9..9aafbd5fb5 100644 --- a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.item.scss +++ b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.item.scss @@ -1,15 +1,15 @@ .c-upload-list-item { display: flex; + flex-wrap: wrap; background: $ibexa-color-white; padding: calculateRem(8px) 0; margin: calculateRem(8px) 0; min-height: calculateRem(60px); &--errored { - background: $ibexa-color-danger-100; + background: $ibexa-color-danger-200; color: $ibexa-color-danger-600; border-radius: $ibexa-border-radius; - padding-right: calculateRem(32px); .ibexa-icon { fill: $ibexa-color-danger-600; @@ -20,6 +20,22 @@ } } + &--expanded-multiple-errors { + padding-bottom: 0; + + .c-upload-list-item { + &__multiple-errors-list { + display: block; + padding: calculateRem(4px) 0 calculateRem(8px) calculateRem(26px); + margin-top: calculateRem(4px); + } + + &__multiple-errors-toggle-btn { + transform: rotate(180deg); + } + } + } + &__icon-wrapper { flex: 0 0 calculateRem(20px); display: flex; @@ -29,7 +45,6 @@ &__meta { padding: 0 calculateRem(8px); - line-height: 1.4; max-width: 25vw; display: flex; justify-content: center; @@ -37,15 +52,15 @@ } &__name { - font-size: calculateRem(16px); margin-right: calculateRem(8px); - max-width: 15vw; + max-width: calculateRem(172px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } &__size { + padding-top: calculateRem(6px); color: $ibexa-color-dark-400; font-size: $ibexa-text-font-size-small; } @@ -59,6 +74,7 @@ &__message { font-size: $ibexa-text-font-size-medium; + line-height: $ibexa-text-font-size-medium; .ibexa-icon { margin-right: calculateRem(4px); @@ -73,7 +89,11 @@ } &--error { + display: flex; + align-items: center; font-size: $ibexa-text-font-size-small; + line-height: $ibexa-text-font-size-small; + padding-right: calculateRem(32px); } } @@ -98,4 +118,27 @@ margin-right: 0; } } + + &__multiple-errors-toggle-btn { + border: none; + outline: none; + margin: 0 0 0 calculateRem(8px); + padding: 0; + transition: all $ibexa-admin-transition-duration $ibexa-admin-transition; + } + + &__multiple-errors-list { + display: none; + flex-basis: 100%; + background: $ibexa-color-danger-100; + padding: 0; + margin: 0; + list-style: none; + border-radius: 0 0 $ibexa-border-radius $ibexa-border-radius; + } + + &__multiple-errors-item { + margin: calculateRem(4px) 0; + font-size: $ibexa-text-font-size-medium; + } } diff --git a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.popup.scss b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.popup.scss index 7fe19b3811..340c5458cc 100644 --- a/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.popup.scss +++ b/src/bundle/Resources/public/scss/ui/modules/muti-file-upload/_upload.popup.scss @@ -9,25 +9,35 @@ color: $ibexa-color-dark; &__label { - margin-top: calculateRem(16px); + margin-bottom: calculateRem(8px); color: $ibexa-color-dark-400; font-size: $ibexa-text-font-size-small; + line-height: calculateRem(18px); } .c-tooltip-popup { width: 100%; - max-width: calculateRem(774px); + max-width: calculateRem(800px); position: absolute; z-index: 2; left: 50%; top: 50%; transform: translate(-50%, -50%); - padding: calculateRem(16px) calculateRem(32px) calculateRem(32px); + padding: calculateRem(32px); &__header { @include modal-header(); - padding: $modal-header-padding-y $modal-header-padding-x; + padding: 0; + margin-bottom: calculateRem(36px); + } + + &__title { + line-height: calculateRem(42px); + } + + &__close { + top: 0; } &__subtitle { diff --git a/src/bundle/Resources/translations/ibexa_multi_file_upload.en.xliff b/src/bundle/Resources/translations/ibexa_multi_file_upload.en.xliff index 2bcffc3b07..60e31eca80 100644 --- a/src/bundle/Resources/translations/ibexa_multi_file_upload.en.xliff +++ b/src/bundle/Resources/translations/ibexa_multi_file_upload.en.xliff @@ -21,11 +21,6 @@ Cannot get content type by identifier key: cannot_get_content_type_identifier.message - - Cannot upload file - Cannot upload file - key: cannot_upload.message - Delete Delete @@ -61,6 +56,11 @@ Edit key: edit.label + + An error occurred while publishing a file + An error occurred while publishing a file + key: general.error.message + Max file size: Max file size: @@ -76,6 +76,11 @@ Under %name% key: multi_file_upload_popup.subtitle + + Failed to upload + Failed to upload + key: multierror.label + Uploading... Uploading... diff --git a/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-list/upload.item.component.js b/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-list/upload.item.component.js index 1b2441de8a..d3869cc864 100644 --- a/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-list/upload.item.component.js +++ b/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-list/upload.item.component.js @@ -27,6 +27,7 @@ export default class UploadItemComponent extends Component { this.abortUploading = this.abortUploading.bind(this); this.initPublishFile = this.initPublishFile.bind(this); this.deleteFile = this.deleteFile.bind(this); + this.handleContentError = this.handleContentError.bind(this); this.contentInfoInput = null; this.contentVersionInfoInput = null; this.contentVersionNoInput = null; @@ -46,6 +47,9 @@ export default class UploadItemComponent extends Component { struct: props.data.struct || null, totalSize: fileSizeToString(props.data.file.size), uploadedSize: '0', + isError: false, + errorMsgs: [], + isMultipleErrosExpanded: false, }; } @@ -92,11 +96,13 @@ export default class UploadItemComponent extends Component { return; } - createFileStruct(data.file, { + const createFileStructParams = { parentInfo, config: adminUiConfig, languageCode: currentLanguage, - }).then(this.initPublishFile); + }; + + createFileStruct(data.file, createFileStructParams, this.handleContentError).then(this.initPublishFile); } /** @@ -121,17 +127,30 @@ export default class UploadItemComponent extends Component { onerror: this.handleUploadError, }, this.handleUploadEnd, + this.handleContentError, ); } - /** - * Handles the case when a file cannot be upload because of file type - * - * @method handleFileTypeNotAllowed - * @memberof UploadItemComponent - */ - handleFileTypeNotAllowed() { - this.setState(() => ({ + handleContentError = (errorMsg, callback) => { + this.setState((prevState) => ({ + isError: true, + errorMsgs: [...prevState.errorMsgs, errorMsg], + })); + }; + + // ======================================================================================================== + // TO DO: maybe put this funcstions to one, diff is + // - disallowedType: true, + // - disallowedSize: false, + // - disallowedContentType: false + // commone params + // - uploading: false + // - disallowed: true + // - uploaded: false + // - aborted: false + // - failed: true + handleFileTypeNotAllowed(errorMsg = '') { + this.setState((prevState) => ({ uploading: false, disallowed: true, disallowedType: true, @@ -140,17 +159,12 @@ export default class UploadItemComponent extends Component { uploaded: false, aborted: false, failed: true, + isError: true, + errorMsgs: [...prevState.errorMsgs, errorMsg], })); } - - /** - * Handles the case when a file cannot be upload because of file size - * - * @method handleFileSizeNotAllowed - * @memberof UploadItemComponent - */ - handleFileSizeNotAllowed() { - this.setState(() => ({ + handleFileSizeNotAllowed(errorMsg = '') { + this.setState((prevState) => ({ uploading: false, disallowed: true, disallowedType: false, @@ -159,11 +173,12 @@ export default class UploadItemComponent extends Component { uploaded: false, aborted: false, failed: true, + isError: true, + errorMsgs: [...prevState.errorMsgs, errorMsg], })); } - - handleContentTypeNotAllowed() { - this.setState(() => ({ + handleContentTypeNotAllowed(errorMsg = '') { + this.setState((prevState) => ({ uploading: false, disallowed: true, disallowedType: false, @@ -172,16 +187,12 @@ export default class UploadItemComponent extends Component { uploaded: false, aborted: false, failed: true, + isError: true, + errorMsgs: [...prevState.errorMsgs, errorMsg], })); } + // ======================================================================================================== - /** - * Handles the upload load start event - * - * @method handleLoadStart - * @param {Event} event - * @memberof UploadItemComponent - */ handleLoadStart(event) { this.setState(() => ({ uploading: true, @@ -196,12 +207,6 @@ export default class UploadItemComponent extends Component { })); } - /** - * Handles the upload abort event - * - * @method handleUploadAbort - * @memberof UploadItemComponent - */ handleUploadAbort() { this.setState(() => ({ uploading: false, @@ -215,12 +220,6 @@ export default class UploadItemComponent extends Component { })); } - /** - * Handles the upload error event - * - * @method handleUploadError - * @memberof UploadItemComponent - */ handleUploadError() { this.setState((state) => ({ uploading: false, @@ -234,12 +233,6 @@ export default class UploadItemComponent extends Component { })); } - /** - * Handles the upload load event - * - * @method handleUploadLoad - * @memberof UploadItemComponent - */ handleUploadLoad() { this.setState(() => ({ uploading: false, @@ -253,13 +246,6 @@ export default class UploadItemComponent extends Component { })); } - /** - * Handles the upload progress event - * - * @method handleUploadProgress - * @param {Event} event - * @memberof UploadItemComponent - */ handleUploadProgress(event) { const fraction = event.loaded / event.total; const progress = parseInt(fraction * 100, 10); @@ -278,12 +264,6 @@ export default class UploadItemComponent extends Component { })); } - /** - * Handles the upload end event - * - * @method handleUploadEnd - * @memberof UploadItemComponent - */ handleUploadEnd() { this.setState( (state) => { @@ -309,23 +289,11 @@ export default class UploadItemComponent extends Component { ); } - /** - * Aborts file upload - * - * @method abortUploading - * @memberof UploadItemComponent - */ abortUploading() { this.state.xhr.abort(); this.props.onAfterAbort(this.props.data); } - /** - * Deletes a file - * - * @method deleteFile - * @memberof UploadItemComponent - */ deleteFile() { this.setState( () => ({ deleted: true }), @@ -333,24 +301,10 @@ export default class UploadItemComponent extends Component { ); } - /** - * Handles the file deleted event - * - * @method handleFileDeleted - * @memberof UploadItemComponent - */ handleFileDeleted() { this.props.onAfterDelete(this.props.data); } - /** - * Returns content type identifier - * based on Content object returned from server after upload - * - * @method getContentTypeIdentifier - * @memberof UploadItemComponent - * @returns {String|null} - */ getContentTypeIdentifier() { const { contentTypesMap, data } = this.props; @@ -365,12 +319,6 @@ export default class UploadItemComponent extends Component { return contentTypeIdentifier; } - /** - * Renders an icon of a content type - * - * @method renderIcon - * @returns {JSX.Element|null} - */ renderIcon() { const contentTypeIdentifier = this.getContentTypeIdentifier(); @@ -380,16 +328,9 @@ export default class UploadItemComponent extends Component { const contentTypeIconUrl = getContentTypeIconUrl(contentTypeIdentifier); - return ; + return ; } - /** - * Renders a progress bar - * - * @method renderProgressBar - * @memberof UploadItemComponent - * @returns {null|Element} - */ renderProgressBar() { const { uploaded, aborted, progress, totalSize, uploadedSize, disallowed } = this.state; @@ -400,66 +341,39 @@ export default class UploadItemComponent extends Component { return ; } - /** - * Renders an error message - * - * @method renderErrorMessage - * @memberof UploadItemComponent - * @returns {null|Element} - */ - renderErrorMessage() { - const Translator = getTranslator(); - const { uploaded, aborted, disallowedType, disallowedSize, failed, uploading, disallowedContentType } = this.state; - const isError = !uploaded && !aborted && (disallowedSize || disallowedType || disallowedContentType) && failed && !uploading; - const cannotUploadMessage = Translator.trans( - /*@Desc("Cannot upload file")*/ 'cannot_upload.message', - {}, - 'ibexa_multi_file_upload', - ); - const disallowedTypeMessage = Translator.trans( - /*@Desc("File type is not allowed")*/ 'disallowed_type.message', - {}, - 'ibexa_multi_file_upload', - ); - const disallowedSizeMessage = Translator.trans( - /*@Desc("File size is not allowed")*/ 'disallowed_size.message', - {}, - 'ibexa_multi_file_upload', - ); - const disallowedContentTypeMessage = Translator.trans( - /*@Desc("You do not have permission to create this Content item")*/ 'disallowed_content_type.message', - {}, - 'ibexa_multi_file_upload', - ); - let msg = cannotUploadMessage; + renderErrorInfo() { + const { isError, errorMsgs, uploading } = this.state; - if (disallowedType) { - msg = disallowedTypeMessage; - } - - if (disallowedSize) { - msg = disallowedSizeMessage; + if (!isError) { + return null; } - if (disallowedContentType) { - msg = disallowedContentTypeMessage; - } + const Translator = getTranslator(); + const isMultipleErros = errorMsgs.length > 1; + const label = isMultipleErros + ? Translator.trans(/*@Desc("Failed to upload ")*/ 'multierror.label', {}, 'ibexa_multi_file_upload') + : errorMsgs[0]; - return isError ? ( + return (
- {msg} + {label} + {isMultipleErros && ( + + )}
- ) : null; + ); } - /** - * Renders an error message - * - * @method renderErrorMessage - * @memberof UploadItemComponent - * @returns {null|Element} - */ renderSuccessMessage() { const Translator = getTranslator(); const { uploaded, aborted, disallowedSize, disallowedType, failed, uploading } = this.state; @@ -474,13 +388,6 @@ export default class UploadItemComponent extends Component { ) : null; } - /** - * Renders an abort upload button - * - * @method renderAbortBtn - * @memberof UploadItemComponent - * @returns {null|Element} - */ renderAbortBtn() { const Translator = getTranslator(); const { uploaded, aborted, disallowedSize, disallowedType, failed, uploading } = this.state; @@ -504,13 +411,6 @@ export default class UploadItemComponent extends Component { ); } - /** - * Handles the edit button click event. Fills in the hidden form to redirect a user to a correct content edit location. - * - * @method handleEditBtnClick - * @memberof UploadItemComponent - * @param {Event} event - */ handleEditBtnClick(event) { event.preventDefault(); @@ -527,13 +427,6 @@ export default class UploadItemComponent extends Component { this.contentEditBtn.click(); } - /** - * Renders an edit content button - * - * @method renderEditBtn - * @memberof UploadItemComponent - * @returns {null|Element} - */ renderEditBtn() { const Translator = getTranslator(); const { instanceUrl } = getRestInfo(); @@ -558,13 +451,6 @@ export default class UploadItemComponent extends Component { ); } - /** - * Renders an delete content button - * - * @method renderDeleteBtn - * @memberof UploadItemComponent - * @returns {null|Element} - */ renderDeleteBtn() { const { uploaded, aborted, disallowedSize, disallowedType, failed, uploading } = this.state; const canDelete = this.props.isUploaded || (uploaded && !aborted && !(disallowedSize || disallowedType) && !failed && !uploading); @@ -589,12 +475,12 @@ export default class UploadItemComponent extends Component { } render() { - const { uploaded, aborted, disallowedType, disallowedSize, failed, uploading, disallowedContentType, deleted, totalSize } = - this.state; - const isError = !uploaded && !aborted && (disallowedSize || disallowedType || disallowedContentType) && failed && !uploading; + const { isError, deleted, totalSize, errorMsgs, isMultipleErrosExpanded } = this.state; + const isMultipleErros = errorMsgs.length > 1; const wrapperClassName = createCssClassNames({ 'c-upload-list-item': true, 'c-upload-list-item--errored': isError, + 'c-upload-list-item--expanded-multiple-errors': isMultipleErrosExpanded, }); if (deleted) { @@ -609,7 +495,7 @@ export default class UploadItemComponent extends Component {
{totalSize}
- {this.renderErrorMessage()} + {this.renderErrorInfo()} {!isError && this.renderSuccessMessage()} {!isError && this.renderProgressBar()}
@@ -618,6 +504,13 @@ export default class UploadItemComponent extends Component { {!isError && this.renderEditBtn()} {!isError && this.renderDeleteBtn()} + {isMultipleErros && ( +
    + {errorMsgs.map((errorMsg) => { + return
  • {errorMsg}
  • ; + })} +
+ )} ); } diff --git a/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-popup/upload.popup.component.js b/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-popup/upload.popup.component.js index 4061a27c91..5508ded636 100644 --- a/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-popup/upload.popup.component.js +++ b/src/bundle/ui-dev/src/modules/multi-file-upload/components/upload-popup/upload.popup.component.js @@ -35,7 +35,7 @@ export default class UploadPopupModule extends Component { }; const Translator = getTranslator(); const title = Translator.trans(/*@Desc("Multi-file upload")*/ 'upload_popup.close', {}, 'ibexa_multi_file_upload'); - +console.log(listAttrs) return (
diff --git a/src/bundle/ui-dev/src/modules/multi-file-upload/multi.file.upload.module.js b/src/bundle/ui-dev/src/modules/multi-file-upload/multi.file.upload.module.js index 2b2257ec2f..34fee302d4 100644 --- a/src/bundle/ui-dev/src/modules/multi-file-upload/multi.file.upload.module.js +++ b/src/bundle/ui-dev/src/modules/multi-file-upload/multi.file.upload.module.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; -import { getTranslator } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; +import { getTranslator, getRootNodeSelector } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; import UploadPopupComponent from './components/upload-popup/upload.popup.component'; import { createFileStruct, publishFile, deleteFile, checkCanUpload } from './services/multi.file.upload.service'; @@ -21,6 +21,7 @@ export default class MultiFileUploadModule extends Component { let popupVisible = true; + this.configRootNodeSelector = getRootNodeSelector(); this._itemsUploaded = []; if (!props.itemsToUpload || !props.itemsToUpload.length) { @@ -291,11 +292,15 @@ export default class MultiFileUploadModule extends Component { addItemsToUpload: this.addItemsToUpload, removeItemsToUpload: this.removeItemsToUpload, }; - const portalTarget = document.querySelector('.ibexa-assets-library-widget-container'); + const portalTarget = this.configRootNodeSelector + ? document.querySelector(this.configRootNodeSelector) + : document.body; - return createPortal(, portalTarget ?? document.body); + return createPortal(, portalTarget); } + rootNodeSelector; + render() { return (
diff --git a/src/bundle/ui-dev/src/modules/multi-file-upload/services/multi.file.upload.service.js b/src/bundle/ui-dev/src/modules/multi-file-upload/services/multi.file.upload.service.js index 22bdfdd019..653c48a65a 100644 --- a/src/bundle/ui-dev/src/modules/multi-file-upload/services/multi.file.upload.service.js +++ b/src/bundle/ui-dev/src/modules/multi-file-upload/services/multi.file.upload.service.js @@ -1,5 +1,4 @@ import { getTranslator, getRestInfo } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/context.helper'; -import { showErrorNotification } from '@ibexa-admin-ui/src/bundle/Resources/public/js/scripts/helpers/notification.helper'; import { getRequestHeaders, getRequestMode } from '../../../../../Resources/public/js/scripts/helpers/request.helper'; const handleOnReadyStateChange = (xhr, onSuccess, onError) => { @@ -77,7 +76,7 @@ const getFieldDefinitionByIdentifier = (contentTypeId, fieldIdentifier) => { return fetch(request).then(handleRequestResponse); }; -const prepareStruct = ({ parentInfo, config, languageCode }, data) => { +const prepareStruct = ({ parentInfo, config, languageCode }, data, contentErrorCallback) => { const Translator = getTranslator(); let parentLocation = `/api/ibexa/v2/content/locations${parentInfo.locationPath}`; @@ -87,15 +86,15 @@ const prepareStruct = ({ parentInfo, config, languageCode }, data) => { return getContentTypeByIdentifier(mapping.contentTypeIdentifier) .then((response) => response.json()) - .catch(() => - showErrorNotification( + .catch(() => { + contentErrorCallback( Translator.trans( /*@Desc("Cannot get content type by identifier")*/ 'cannot_get_content_type_identifier.message', {}, 'ibexa_multi_file_upload', ), - ), - ) + ) + }) .then((response) => { const fileValue = { fileName: data.file.name, @@ -107,15 +106,15 @@ const prepareStruct = ({ parentInfo, config, languageCode }, data) => { return getFieldDefinitionByIdentifier(contentType.id, contentFieldIdentifier) .then((parsedResponse) => parsedResponse.json()) - .catch(() => - showErrorNotification( + .catch(() => { + contentErrorCallback( Translator.trans( /*@Desc("Cannot get content type by identifier")*/ 'cannot_get_content_type_identifier.message', {}, 'ibexa_multi_file_upload', ), - ), - ) + ); + }) .then((parsedResponse) => { const fieldDefinition = parsedResponse.FieldDefinition; @@ -143,25 +142,25 @@ const prepareStruct = ({ parentInfo, config, languageCode }, data) => { return struct; }) - .catch(() => - showErrorNotification( + .catch(() => { + contentErrorCallback( Translator.trans( /*@Desc("Cannot create content structure")*/ 'cannot_create_content_structure.message', {}, 'ibexa_multi_file_upload', ), - ), - ); + ); + }); }) - .catch(() => - showErrorNotification( + .catch(() => { + contentErrorCallback( Translator.trans( /*@Desc("Cannot create content structure")*/ 'cannot_create_content_structure.message', {}, 'ibexa_multi_file_upload', ), - ), - ); + ); + }); }; const createDraft = (struct, requestEventHandlers) => { const { instanceUrl, token, siteaccess, accessToken } = getRestInfo(); @@ -239,34 +238,58 @@ const canCreateContent = (file, parentInfo, config) => { }; export const checkCanUpload = (file, parentInfo, config, callbacks) => { + const Translator = getTranslator(); const locationMapping = config.locationMappings.find((item) => item.contentTypeIdentifier === parentInfo.contentTypeIdentifier); if (!canCreateContent(file, parentInfo, config)) { - callbacks.contentTypeNotAllowedCallback(); + callbacks.contentTypeNotAllowedCallback( + Translator.trans( + /*@Desc("You do not have permission to create this Content item")*/ 'disallowed_content_type.message', + {}, + 'ibexa_multi_file_upload', + ), + ); return false; } if (!checkFileTypeAllowed(file, locationMapping)) { - callbacks.fileTypeNotAllowedCallback(); + callbacks.fileTypeNotAllowedCallback( + Translator.trans(/*@Desc("File type is not allowed")*/ 'disallowed_type.message', {}, 'ibexa_multi_file_upload'), + ); return false; } if (file.size > config.maxFileSize) { - callbacks.fileSizeNotAllowedCallback(); + callbacks.fileSizeNotAllowedCallback( + Translator.trans(/*@Desc("File size is not allowed")*/ 'disallowed_size.message', {}, 'ibexa_multi_file_upload'), + ); return false; } return true; }; -export const createFileStruct = (file, params) => new Promise(readFile.bind(new FileReader(), file)).then(prepareStruct.bind(null, params)); -export const publishFile = (data, requestEventHandlers, callback) => { - createDraft(data, requestEventHandlers) +export const createFileStruct = (file, params, contentErrorCallback) => { + return new Promise(readFile.bind(new FileReader(), file)).then((fileData) => prepareStruct(params, fileData, contentErrorCallback)); +}; + +export const publishFile = (data, requestEventHandlers, successCallback, contentErrorCallback) => { + createDraft(data, requestEventHandlers, contentErrorCallback) .then(publishDraft) - .then(callback) - .catch(showErrorNotification('An error occurred while publishing a file')); + .then(successCallback) + .catch(() => { + const Translator = getTranslator(); + + contentErrorCallback( + Translator.trans( + /*@Desc("An error occurred while publishing a file")*/ 'general.error.message', + {}, + 'ibexa_multi_file_upload', + ), + ); + }); }; export const deleteFile = (struct, callback) => { const { instanceUrl, token, siteaccess, accessToken } = getRestInfo(); diff --git a/src/bundle/ui-dev/src/modules/universal-discovery/services/universal.discovery.service.js b/src/bundle/ui-dev/src/modules/universal-discovery/services/universal.discovery.service.js index a110668fa0..e84854f2ac 100644 --- a/src/bundle/ui-dev/src/modules/universal-discovery/services/universal.discovery.service.js +++ b/src/bundle/ui-dev/src/modules/universal-discovery/services/universal.discovery.service.js @@ -54,6 +54,7 @@ export const findLocationsByParentLocationId = ( }, callback, ) => { + console.log('UDW findLocationsByParentLocationId'); let url = `${instanceUrl}${ENDPOINT_LOCATION}/${parentLocationId}`; if (gridView) { url += '/gridview';