465 lines
15 KiB
TypeScript
465 lines
15 KiB
TypeScript
|
|
/**
|
||
|
|
* For the full copyright and license information, please view the
|
||
|
|
* docs/licenses/LICENSE.txt file that was distributed with this source code.
|
||
|
|
*/
|
||
|
|
import {EventEmitter} from 'events';
|
||
|
|
import ConfirmModal from '@components/modal';
|
||
|
|
import ComponentsMap from './components-map';
|
||
|
|
|
||
|
|
const ModuleCardMap = ComponentsMap.moduleCard;
|
||
|
|
|
||
|
|
const {$} = window;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Class is responsible for handling Module Card behavior
|
||
|
|
*/
|
||
|
|
export default class ModuleCard {
|
||
|
|
moduleActionMenuLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuInstallLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuEnableLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuUninstallLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuDisableLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuResetLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuUpdateLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionMenuDeleteLinkSelector: string;
|
||
|
|
|
||
|
|
moduleItemListSelector: string;
|
||
|
|
|
||
|
|
moduleItemActionsSelector: string;
|
||
|
|
|
||
|
|
moduleActionModalDisableLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionModalResetLinkSelector: string;
|
||
|
|
|
||
|
|
moduleActionModalUninstallLinkSelector: string;
|
||
|
|
|
||
|
|
forceDeletionOption: string;
|
||
|
|
|
||
|
|
private pendingRequest: boolean = false;
|
||
|
|
|
||
|
|
private eventEmitter: EventEmitter;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
/* Selectors for module action links (uninstall, reset, etc...) to add a confirm popin */
|
||
|
|
this.moduleActionMenuLinkSelector = 'button.module_action_menu_';
|
||
|
|
this.moduleActionMenuInstallLinkSelector = 'button.module_action_menu_install';
|
||
|
|
this.moduleActionMenuEnableLinkSelector = 'button.module_action_menu_enable';
|
||
|
|
this.moduleActionMenuUninstallLinkSelector = 'button.module_action_menu_uninstall';
|
||
|
|
this.moduleActionMenuDisableLinkSelector = 'button.module_action_menu_disable';
|
||
|
|
this.moduleActionMenuResetLinkSelector = 'button.module_action_menu_reset';
|
||
|
|
this.moduleActionMenuUpdateLinkSelector = 'button.module_action_menu_upgrade';
|
||
|
|
this.moduleActionMenuDeleteLinkSelector = 'button.module_action_menu_delete';
|
||
|
|
this.moduleItemListSelector = '.module-item-list';
|
||
|
|
this.moduleItemActionsSelector = '.module-actions';
|
||
|
|
|
||
|
|
/* Selectors only for modal buttons */
|
||
|
|
this.moduleActionModalDisableLinkSelector = 'a.module_action_modal_disable';
|
||
|
|
this.moduleActionModalResetLinkSelector = 'a.module_action_modal_reset';
|
||
|
|
this.moduleActionModalUninstallLinkSelector = 'a.module_action_modal_uninstall';
|
||
|
|
this.forceDeletionOption = '#force_deletion';
|
||
|
|
|
||
|
|
this.eventEmitter = window.prestashop.component.EventEmitter;
|
||
|
|
|
||
|
|
this.initActionButtons();
|
||
|
|
}
|
||
|
|
|
||
|
|
initActionButtons(): void {
|
||
|
|
const self = this;
|
||
|
|
|
||
|
|
$(document).on('click', this.forceDeletionOption, function () {
|
||
|
|
const btn = $(
|
||
|
|
self.moduleActionModalUninstallLinkSelector,
|
||
|
|
$(ModuleCardMap.moduleItemList(<string>$(this).attr('data-tech-name'))),
|
||
|
|
);
|
||
|
|
|
||
|
|
if ($(this).prop('checked') === true) {
|
||
|
|
btn.attr('data-deletion', 'true');
|
||
|
|
} else {
|
||
|
|
btn.removeAttr('data-deletion');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionMenuInstallLinkSelector,
|
||
|
|
function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('install', this)
|
||
|
|
&& self.confirmAction('install', this)
|
||
|
|
&& self.requestToController('install', $(this))
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionMenuEnableLinkSelector,
|
||
|
|
function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('enable', this)
|
||
|
|
&& self.confirmAction('enable', this)
|
||
|
|
&& self.requestToController('enable', $(this))
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionMenuUninstallLinkSelector,
|
||
|
|
function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('uninstall', this)
|
||
|
|
&& self.confirmAction('uninstall', this)
|
||
|
|
&& self.requestToController('uninstall', $(this))
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionMenuDeleteLinkSelector,
|
||
|
|
function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('delete', this)
|
||
|
|
&& self.confirmAction('delete', this)
|
||
|
|
&& self.requestToController('delete', $(this))
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionMenuDisableLinkSelector,
|
||
|
|
function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('disable', this)
|
||
|
|
&& self.confirmAction('disable', this)
|
||
|
|
&& self.requestToController('disable', $(this))
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on('click', this.moduleActionMenuResetLinkSelector, function () {
|
||
|
|
return (
|
||
|
|
self.dispatchPreEvent('reset', this)
|
||
|
|
&& self.confirmAction('reset', this)
|
||
|
|
&& self.requestToController('reset', $(this))
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).on('click', this.moduleActionMenuUpdateLinkSelector, function (
|
||
|
|
event,
|
||
|
|
) {
|
||
|
|
event.preventDefault();
|
||
|
|
const modal = $(`#${$(this).data('confirm_modal')}`);
|
||
|
|
const isMaintenanceMode = window.isShopMaintenance;
|
||
|
|
|
||
|
|
if (modal.length !== 1) {
|
||
|
|
// Modal body element
|
||
|
|
const maintenanceLink = document.createElement('a');
|
||
|
|
maintenanceLink.classList.add('btn', 'btn-primary', 'btn-lg');
|
||
|
|
maintenanceLink.setAttribute('href', window.moduleURLs.maintenancePage);
|
||
|
|
maintenanceLink.innerHTML = window.moduleTranslations.moduleModalUpdateMaintenance;
|
||
|
|
|
||
|
|
const updateConfirmModal = new ConfirmModal(
|
||
|
|
{
|
||
|
|
id: 'confirm-module-update-modal',
|
||
|
|
confirmTitle:
|
||
|
|
window.moduleTranslations.singleModuleModalUpdateTitle,
|
||
|
|
closeButtonLabel: window.moduleTranslations.moduleModalUpdateCancel,
|
||
|
|
confirmButtonLabel: isMaintenanceMode
|
||
|
|
? window.moduleTranslations.moduleModalUpdateUpgrade
|
||
|
|
: window.moduleTranslations.upgradeAnywayButtonText,
|
||
|
|
confirmButtonClass: isMaintenanceMode
|
||
|
|
? 'btn-primary'
|
||
|
|
: 'btn-secondary',
|
||
|
|
confirmMessage: isMaintenanceMode
|
||
|
|
? ''
|
||
|
|
: window.moduleTranslations.moduleModalUpdateConfirmMessage,
|
||
|
|
closable: true,
|
||
|
|
customButtons: isMaintenanceMode ? [] : [maintenanceLink],
|
||
|
|
},
|
||
|
|
|
||
|
|
() => self.dispatchPreEvent('update', this)
|
||
|
|
&& self.confirmAction('update', this)
|
||
|
|
&& self.upgradeWithUploadFallback(this),
|
||
|
|
);
|
||
|
|
|
||
|
|
updateConfirmModal.show();
|
||
|
|
} else {
|
||
|
|
return self.dispatchPreEvent('update', this)
|
||
|
|
&& self.confirmAction('update', this)
|
||
|
|
&& self.upgradeWithUploadFallback(this);
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
});
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionModalDisableLinkSelector,
|
||
|
|
function () {
|
||
|
|
return self.requestToController(
|
||
|
|
'disable',
|
||
|
|
$(
|
||
|
|
self.moduleActionMenuDisableLinkSelector,
|
||
|
|
$(
|
||
|
|
ModuleCardMap.moduleItemList(
|
||
|
|
<string>$(this).attr('data-tech-name'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionModalResetLinkSelector,
|
||
|
|
function () {
|
||
|
|
return self.requestToController(
|
||
|
|
'reset',
|
||
|
|
$(
|
||
|
|
self.moduleActionMenuResetLinkSelector,
|
||
|
|
$(
|
||
|
|
ModuleCardMap.moduleItemList(
|
||
|
|
<string>$(this).attr('data-tech-name'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
$(document).on(
|
||
|
|
'click',
|
||
|
|
this.moduleActionModalUninstallLinkSelector,
|
||
|
|
(e) => {
|
||
|
|
$(e.target)
|
||
|
|
.parents('.modal')
|
||
|
|
.on('hidden.bs.modal', () => self.requestToController(
|
||
|
|
'uninstall',
|
||
|
|
$(
|
||
|
|
self.moduleActionMenuUninstallLinkSelector,
|
||
|
|
$(
|
||
|
|
ModuleCardMap.moduleItemList(
|
||
|
|
<string>$(e.target).attr('data-tech-name'),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
$(e.target).attr('data-deletion'),
|
||
|
|
),
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
confirmAction(action: string, element: string): boolean {
|
||
|
|
const modal = $(
|
||
|
|
ComponentsMap.confirmModal($(element).data('confirm_modal')),
|
||
|
|
);
|
||
|
|
|
||
|
|
if (modal.length !== 1) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
modal.first().modal('show');
|
||
|
|
|
||
|
|
return false; // do not allow a.href to reload the page. The confirm modal dialog will do it async if needed.
|
||
|
|
}
|
||
|
|
|
||
|
|
dispatchPreEvent(action: string, element: string): boolean {
|
||
|
|
const event = jQuery.Event('module_card_action_event');
|
||
|
|
|
||
|
|
$(element).trigger(event, [action]);
|
||
|
|
if (
|
||
|
|
event.isPropagationStopped() !== false
|
||
|
|
|| event.isImmediatePropagationStopped() !== false
|
||
|
|
) {
|
||
|
|
return false; // if all handlers have not been called, then stop propagation of the click event.
|
||
|
|
}
|
||
|
|
|
||
|
|
// @ts-ignore-next-line
|
||
|
|
return event.result !== false; // explicit false must be set from handlers to stop propagation of the click event.
|
||
|
|
}
|
||
|
|
|
||
|
|
hasPendingRequest(): boolean {
|
||
|
|
return this.pendingRequest;
|
||
|
|
}
|
||
|
|
|
||
|
|
requestToController(
|
||
|
|
action: string,
|
||
|
|
element: JQuery,
|
||
|
|
forceDeletion: string | boolean = false,
|
||
|
|
callback: (response?: any) => boolean = () => true,
|
||
|
|
): boolean {
|
||
|
|
if (this.pendingRequest) {
|
||
|
|
$.growl.warning({
|
||
|
|
message: window.translate_javascripts['An action is already in progress. Please wait for it to finish.'],
|
||
|
|
});
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.pendingRequest = true;
|
||
|
|
|
||
|
|
let jqElementObj = element.closest(this.moduleItemActionsSelector);
|
||
|
|
const form = element.closest('form');
|
||
|
|
const spinnerObj = $(
|
||
|
|
'<button class="btn-primary-reverse onclick unbind spinner "></button>',
|
||
|
|
);
|
||
|
|
// Use custom upload_url for 'upgrade' if available, otherwise use the default URL.
|
||
|
|
let url = `//${window.location.host}${form.attr('action')}`;
|
||
|
|
|
||
|
|
if (action === 'upload' && form.data('upload-url')) {
|
||
|
|
url = form.data('upload-url');
|
||
|
|
}
|
||
|
|
const actionParams = form.serializeArray();
|
||
|
|
let refreshNeeded = false;
|
||
|
|
|
||
|
|
if (forceDeletion === 'true' || forceDeletion === true) {
|
||
|
|
actionParams.push({name: 'actionParams[deletion]', value: 'true'});
|
||
|
|
}
|
||
|
|
$.ajax({
|
||
|
|
url,
|
||
|
|
dataType: 'json',
|
||
|
|
method: 'POST',
|
||
|
|
data: actionParams,
|
||
|
|
beforeSend() {
|
||
|
|
jqElementObj.hide();
|
||
|
|
jqElementObj.after(spinnerObj);
|
||
|
|
},
|
||
|
|
})
|
||
|
|
.done((result) => {
|
||
|
|
if (result === undefined) {
|
||
|
|
$.growl.error({
|
||
|
|
message: 'No answer received from server',
|
||
|
|
fixed: true,
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (typeof result.status !== 'undefined' && result.status === false) {
|
||
|
|
$.growl.error({message: result.msg, fixed: true});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const moduleTechName = Object.keys(result)[0];
|
||
|
|
|
||
|
|
if (result[moduleTechName].status === false) {
|
||
|
|
$.growl.error({message: result[moduleTechName].msg, fixed: true});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (result[moduleTechName].refresh_needed === true) {
|
||
|
|
refreshNeeded = true;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const alteredSelector = this.moduleItemListSelector.replace('.', '');
|
||
|
|
let mainElement = null;
|
||
|
|
|
||
|
|
if (action === 'delete' && !result[moduleTechName].has_download_url) {
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
this.eventEmitter.emit('Module Delete', mainElement);
|
||
|
|
} else if (action === 'uninstall') {
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
mainElement.attr('data-installed', '0');
|
||
|
|
mainElement.attr('data-active', '0');
|
||
|
|
|
||
|
|
if ((forceDeletion === 'true' || forceDeletion === true) && !result[moduleTechName].has_download_url) {
|
||
|
|
this.eventEmitter.emit('Module Delete', mainElement);
|
||
|
|
} else {
|
||
|
|
this.eventEmitter.emit('Module Uninstalled', mainElement);
|
||
|
|
}
|
||
|
|
} else if (action === 'disable') {
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
mainElement.addClass(`${alteredSelector}-isNotActive`);
|
||
|
|
mainElement.attr('data-active', '0');
|
||
|
|
|
||
|
|
this.eventEmitter.emit('Module Disabled', mainElement);
|
||
|
|
} else if (action === 'enable') {
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
mainElement.removeClass(`${alteredSelector}-isNotActive`);
|
||
|
|
mainElement.attr('data-active', '1');
|
||
|
|
|
||
|
|
this.eventEmitter.emit('Module Enabled', mainElement);
|
||
|
|
} else if (action === 'install') {
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
mainElement.attr('data-installed', '1');
|
||
|
|
mainElement.attr('data-active', '1');
|
||
|
|
mainElement.removeClass(`${alteredSelector}-isNotActive`);
|
||
|
|
|
||
|
|
this.eventEmitter.emit('Module Installed', mainElement);
|
||
|
|
} else if (action === 'update' || action === 'upgrade') { // because the action is update on ModuleManager button and upgrade on bulk actions
|
||
|
|
mainElement = jqElementObj.closest(`.${alteredSelector}`);
|
||
|
|
|
||
|
|
this.eventEmitter.emit('Module Upgraded', mainElement);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action !== 'upload') {
|
||
|
|
$.growl({
|
||
|
|
message: result[moduleTechName].msg,
|
||
|
|
duration: 6000,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Since we replace the DOM content
|
||
|
|
// we need to update the jquery object reference to target the new content,
|
||
|
|
// and we need to hide the new content which is not hidden by default
|
||
|
|
jqElementObj = $(result[moduleTechName].action_menu_html).replaceAll(jqElementObj);
|
||
|
|
jqElementObj.hide();
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.fail(() => {
|
||
|
|
const moduleItem = jqElementObj.closest('module-item-list');
|
||
|
|
const techName = moduleItem.data('techName');
|
||
|
|
$.growl.error({
|
||
|
|
message: `Could not perform action ${action} for module ${techName}`,
|
||
|
|
fixed: true,
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.always((response) => {
|
||
|
|
if (refreshNeeded) {
|
||
|
|
document.location.reload();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
jqElementObj.fadeIn();
|
||
|
|
spinnerObj.remove();
|
||
|
|
this.pendingRequest = false;
|
||
|
|
|
||
|
|
if (callback) {
|
||
|
|
callback(Object.values(response)[0]);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
upgradeWithUploadFallback(element: string, callback = () => true): boolean {
|
||
|
|
const form = $(element).closest('form');
|
||
|
|
|
||
|
|
// If the form contains a data-upload-url attribute, we use two step workflow.
|
||
|
|
if (form.data('upload-url')) {
|
||
|
|
try {
|
||
|
|
return this.requestToController('upload', $(element), false, (response): boolean => {
|
||
|
|
if (response.status === true) {
|
||
|
|
return this.requestToController('upgrade', $(element), false, callback);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error making request', error);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return this.requestToController('upgrade', $(element), false, callback);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|