From a0bae84dba9e287975f9fab90b879fd3b40e7edd Mon Sep 17 00:00:00 2001 From: Boubaker Khanfir Date: Sat, 1 Jun 2024 10:42:48 +0100 Subject: [PATCH] feat: Generate Portlet instance layout preview on save - MEED-6949 - Meeds-io/MIPs#139 (#94) --- .../service/PortletInstanceRenderService.java | 23 +++++++-- layout-webapp/.eslintrc.json | 7 +-- .../locale/portlet/LayoutEditor_en.properties | 4 ++ .../main/webapp/WEB-INF/gatein-resources.xml | 3 ++ .../components/content/Application.vue | 46 +++++++++++++++-- .../components/content/Cell.vue | 13 ++++- .../components/toolbar/Toolbar.vue | 9 +++- .../components/toolbar/actions/EditButton.vue | 48 ++++++++++++++++++ .../components/toolbar/actions/SaveButton.vue | 49 +++++++++++++++++-- .../vue-app/portlet-editor/initComponents.js | 2 + .../webapp/vue-app/portlet-editor/main.js | 2 + .../components/instances/CategoryItem.vue | 4 +- .../portlets/components/instances/List.vue | 6 +++ .../portlets/components/instances/Menu.vue | 1 + .../portlets/components/portlets/Menu.vue | 48 ++++-------------- .../src/main/webapp/vue-app/portlets/main.js | 4 ++ 16 files changed, 212 insertions(+), 57 deletions(-) create mode 100644 layout-webapp/src/main/webapp/vue-app/portlet-editor/components/toolbar/actions/EditButton.vue diff --git a/layout-service/src/main/java/io/meeds/layout/service/PortletInstanceRenderService.java b/layout-service/src/main/java/io/meeds/layout/service/PortletInstanceRenderService.java index 37ba44d42..1c874a456 100644 --- a/layout-service/src/main/java/io/meeds/layout/service/PortletInstanceRenderService.java +++ b/layout-service/src/main/java/io/meeds/layout/service/PortletInstanceRenderService.java @@ -69,8 +69,11 @@ public class PortletInstanceRenderService { private static final Context CONTEXT = Context.GLOBAL.id("PORTLET_INSTANCE"); - private static final Scope SCOPE = - Scope.APPLICATION.id("PORTLET_INSTANCE_APPLICATION"); + private static final Scope PORTLET_INSTANCE_SCOPE = + Scope.APPLICATION.id("PORTLET_INSTANCE_APPLICATION"); + + private static final Scope PAGE_APPLICATION_SCOPE = + Scope.APPLICATION.id("APPLICATION_PORTLET_INSTANCE"); private static final PageKey PORTLET_EDITOR_SYSTEM_PAGE_KEY = new PageKey(SiteKey.portal("global"), "_portletEditor"); @@ -139,6 +142,10 @@ public List getPortletInstancePreferences(long portle } } + public long getApplicationPortletInstanceId(long applicationId) { + return getSettingValue(PAGE_APPLICATION_SCOPE, applicationId); + } + private Application getOrCreatePortletInstanceApplication(String portletInstanceId, String userName) throws IllegalAccessException, ObjectNotFoundException { @@ -202,7 +209,11 @@ private synchronized Application createPortletInstanceApplication(Portl } private long getPortletInstanceApplicationId(long portletInstanceId) { - SettingValue settingValue = settingService.get(CONTEXT, SCOPE, String.valueOf(portletInstanceId)); + return getSettingValue(PORTLET_INSTANCE_SCOPE, portletInstanceId); + } + + private long getSettingValue(Scope scope, long id) { + SettingValue settingValue = settingService.get(CONTEXT, scope, String.valueOf(id)); if (settingValue != null && settingValue.getValue() != null && StringUtils.isNotBlank(settingValue.getValue().toString())) { return Long.parseLong(settingValue.getValue().toString()); } else { @@ -212,9 +223,13 @@ private long getPortletInstanceApplicationId(long portletInstanceId) { private void savePortletInstanceApplicationId(long applicationStorageId, long portletInstanceId) { settingService.set(CONTEXT, - SCOPE, + PORTLET_INSTANCE_SCOPE, String.valueOf(portletInstanceId), SettingValue.create(applicationStorageId)); + settingService.set(CONTEXT, + PAGE_APPLICATION_SCOPE, + String.valueOf(applicationStorageId), + SettingValue.create(portletInstanceId)); } private Container getPortletInstanceSystemContainer() { diff --git a/layout-webapp/.eslintrc.json b/layout-webapp/.eslintrc.json index b16eae5f8..7dd6a74da 100644 --- a/layout-webapp/.eslintrc.json +++ b/layout-webapp/.eslintrc.json @@ -3,13 +3,10 @@ "eslint-config-meedsio" ], "globals": { - "jQuery": true, - "echarts": true, - "fontLibrary": true + "html2canvas": true }, "rules": { "vue/multi-word-component-names": "off", - "vue/no-mutating-props": ["warn"], - "no-restricted-imports": ["warn", "axios"] + "vue/no-mutating-props": ["warn"] } } diff --git a/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties b/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties index 7b8ea45a6..70772e91b 100644 --- a/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties +++ b/layout-webapp/src/main/resources/locale/portlet/LayoutEditor_en.properties @@ -280,6 +280,7 @@ layout.portletInstanceCategoryCreatedSuccessfully=Portlet Instance Category crea layout.portletInstanceCategoryUpdatedSuccessfully=Portlet Instance Category updated successfully layout.portletInstanceCreatedSuccessfully=Portlet Instance created successfully layout.portletInstanceUpdatedSuccessfully=Portlet Instance updated successfully +layout.portletInstanceLayoutUpdatedSuccessfully=Portlet Instance layout and preview updated successfully portlets.portletInstancesList=Instances using {0} portlets.previewInstance=Preview the instance @@ -288,5 +289,8 @@ portlets.instancePreview=Preview portlets.uploadPreviewTitle=Upload an illustration for portlet instance portlets.label.createInstance=Create instance portlets.noPreviewAvailable=No preview available +portlets.switchToEditMode=Edit mode +portlets.switchToViewMode=View mode +portlets.emptyPortletInstanceContent=Empty portlet instance layout.editPortletInstance=Edit portlet instance {0} diff --git a/layout-webapp/src/main/webapp/WEB-INF/gatein-resources.xml b/layout-webapp/src/main/webapp/WEB-INF/gatein-resources.xml index 121665df9..1e24b5434 100644 --- a/layout-webapp/src/main/webapp/WEB-INF/gatein-resources.xml +++ b/layout-webapp/src/main/webapp/WEB-INF/gatein-resources.xml @@ -329,6 +329,9 @@ attachImage + + html2canvas + vue diff --git a/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/content/Application.vue b/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/content/Application.vue index 34bfcbd80..8035cc043 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/content/Application.vue +++ b/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/content/Application.vue @@ -22,19 +22,36 @@
\ No newline at end of file diff --git a/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/toolbar/actions/SaveButton.vue b/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/toolbar/actions/SaveButton.vue index 509c1c0f0..25441e61d 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/toolbar/actions/SaveButton.vue +++ b/layout-webapp/src/main/webapp/vue-app/portlet-editor/components/toolbar/actions/SaveButton.vue @@ -46,12 +46,53 @@ export default { try { const instance = await this.$portletInstanceService.getPortletInstance(this.$root.portletInstanceId); instance.preferences = await this.$portletInstanceService.getPortletInstancePreferences(this.$root.portletInstanceId); - this.$portletInstanceService.updatePortletInstance(instance); - this.$root.$emit('portlet-instance-updated', instance); - this.$root.$emit('alert-message', this.$t('layout.portletInstanceUpdatedSuccessfully'), 'success'); + await this.$portletInstanceService.updatePortletInstance(instance); + + const previewCanvas = await window.html2canvas(this.$root.portletInstanceElement); + const previewImage = previewCanvas.toDataURL('image/png'); + const previewBlob = this.convertPreviewToFile(previewImage); + const uploadId = await this.$uploadService.upload(previewBlob); + await new Promise((resolve, reject) => { + const interval = window.setInterval(() => { + this.$uploadService.getUploadProgress(uploadId) + .then(percent => { + if (Number(percent) === 100) { + window.clearInterval(interval); + resolve(); + } + }) + .catch(e => reject(e)); + }, 200); + }); + await this.$fileAttachmentService.saveAttachments({ + objectType: 'portletInstance', + objectId: this.$root.portletInstanceId, + uploadedFiles: [{uploadId}], + attachedFiles: [], + }); + + if (window?.opener) { + window?.opener?.dispatchEvent?.(new CustomEvent('portlet-instance-layout-updated', { + detail: instance, + })); + window.close(); + } else { + this.$root.$emit('alert-message', this.$t('layout.portletInstanceLayoutUpdatedSuccessfully'), 'success'); + } + } catch (e) { + console.debug('Error saving portlet instance', e); // eslint-disable-line no-console + this.$root.$emit('alert-message', this.$t('layout.portletInstanceLayoutUpdatedSuccessfully'), 'success'); } finally { - window.setTimeout(() => this.loading = false); + window.setTimeout(() => this.loading = false, 50); + } + }, + convertPreviewToFile(previewImage) { + const imgString = window.atob(previewImage.replace(/^data:image\/\w+;base64,/, '')); + const bytes = new Uint8Array(imgString.length); + for (let i = 0; i < imgString.length; i++) { + bytes[i] = imgString.charCodeAt(i); } + return new Blob([bytes], {type: 'image/png'}); }, }, }; diff --git a/layout-webapp/src/main/webapp/vue-app/portlet-editor/initComponents.js b/layout-webapp/src/main/webapp/vue-app/portlet-editor/initComponents.js index f2bc4d6f8..c3c458bd0 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlet-editor/initComponents.js +++ b/layout-webapp/src/main/webapp/vue-app/portlet-editor/initComponents.js @@ -20,6 +20,7 @@ import PortletEditor from './components/PortletEditor.vue'; import Toolbar from './components/toolbar/Toolbar.vue'; +import EditButton from './components/toolbar/actions/EditButton.vue'; import SaveButton from './components/toolbar/actions/SaveButton.vue'; import Content from './components/content/Content.vue'; @@ -30,6 +31,7 @@ import Application from './components/content/Application.vue'; const components = { 'portlet-editor': PortletEditor, 'portlet-editor-toolbar': Toolbar, + 'portlet-editor-toolbar-edit-button': EditButton, 'portlet-editor-toolbar-save-button': SaveButton, 'portlet-editor-content': Content, 'portlet-editor-cell': Cell, diff --git a/layout-webapp/src/main/webapp/vue-app/portlet-editor/main.js b/layout-webapp/src/main/webapp/vue-app/portlet-editor/main.js index db10787ac..f4fd807c9 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlet-editor/main.js +++ b/layout-webapp/src/main/webapp/vue-app/portlet-editor/main.js @@ -48,6 +48,8 @@ export function init() { data: { portletInstanceId: null, portletInstance: null, + portletInstanceElement: null, + portletMode: 'view', }, i18n, created() { diff --git a/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/CategoryItem.vue b/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/CategoryItem.vue index 1abf1476e..710259b56 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/CategoryItem.vue +++ b/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/CategoryItem.vue @@ -30,7 +30,9 @@ - + this.$root.$emit('alert-message', this.$t('portlets.delete.error'), 'error')) .finally(() => this.loading = false); }, + handleLayoutUpdated(instance) { + this.$root.$emit('portlet-instance-saved', instance); + this.$root.$emit('alert-message', this.$t('layout.portletInstanceLayoutUpdatedSuccessfully'), 'success'); + }, }, }; diff --git a/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/Menu.vue b/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/Menu.vue index 434532a6c..27375a64e 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/Menu.vue +++ b/layout-webapp/src/main/webapp/vue-app/portlets/components/instances/Menu.vue @@ -46,6 +46,7 @@ fa-edit diff --git a/layout-webapp/src/main/webapp/vue-app/portlets/components/portlets/Menu.vue b/layout-webapp/src/main/webapp/vue-app/portlets/components/portlets/Menu.vue index 97aab3ac7..31c667801 100644 --- a/layout-webapp/src/main/webapp/vue-app/portlets/components/portlets/Menu.vue +++ b/layout-webapp/src/main/webapp/vue-app/portlets/components/portlets/Menu.vue @@ -19,44 +19,16 @@ -->