Skip to content

Commit

Permalink
feat: Generate Portlet instance layout preview on save - MEED-6949 - M…
Browse files Browse the repository at this point in the history
  • Loading branch information
boubaker committed Jun 4, 2024
1 parent 7b6e095 commit a0bae84
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -139,6 +142,10 @@ public List<PortletInstancePreference> getPortletInstancePreferences(long portle
}
}

public long getApplicationPortletInstanceId(long applicationId) {
return getSettingValue(PAGE_APPLICATION_SCOPE, applicationId);
}

private Application<?> getOrCreatePortletInstanceApplication(String portletInstanceId,
String userName) throws IllegalAccessException,
ObjectNotFoundException {
Expand Down Expand Up @@ -202,7 +209,11 @@ private synchronized Application<Portlet> 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 {
Expand All @@ -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() {
Expand Down
7 changes: 2 additions & 5 deletions layout-webapp/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}
3 changes: 3 additions & 0 deletions layout-webapp/src/main/webapp/WEB-INF/gatein-resources.xml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@
<depends>
<module>attachImage</module>
</depends>
<depends>
<module>html2canvas</module>
</depends>
<depends>
<module>vue</module>
</depends>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,36 @@
<div
ref="content"
:id="id"
:style="cssStyle"
:class="$root.portletMode !== 'view' && 'white card-border-radius border-box-sizing pa-5'"
class="layout-application no-applications-spacing full-width"></div>
</template>
<script>
export default {
data: () => ({
applicationContent: null,
contentRetrieved: 0,
}),
computed: {
id() {
return `UIPortlet-${this.portletInstanceId}`;
},
portletMode() {
return this.$root.portletMode;
},
portletInstanceId() {
return this.$root.portletInstanceId;
},
id() {
return `UIPortlet-${this.portletInstanceId}`;
isEmpty() {
return this.contentRetrieved
&& !this.applicationContent
&& !this.$el?.querySelector?.('.PORTLET-FRAGMENT')?.offsetHeight;
},
cssStyle() {
return this.isEmpty && {
'min-height': '100px',
'display': 'block !important',
} || null;
},
},
watch: {
Expand All @@ -43,23 +60,46 @@ export default {
this.installApplication();
}
},
portletMode() {
this.retrieveData();
},
isEmpty() {
this.$emit('empty', this.isEmpty);
},
},
created() {
document.dispatchEvent(new CustomEvent('displayTopBarLoading'));
this.retrieveData();
},
mounted() {
this.installApplication();
this.$root.portletInstanceElement = this.$el;
},
methods: {
installApplication() {
if (this.$refs.content && this.applicationContent) {
this.$applicationUtils.handleApplicationContent(this.applicationContent, this.$refs.content);
// Cleanup JS memory
this.applicationContent = null;
// Wait for some seconds for application to be displayed
this.contentRetrieved++;
const interval = window.setInterval(() => {
if (this.contentRetrieved >= 100 || !this.isEmpty) {
document.dispatchEvent(new CustomEvent('hideTopBarLoading'));
window.clearInterval(interval);
} else if (this.isEmpty) {
this.contentRetrieved++;
if (this.contentRetrieved === 20) {
document.dispatchEvent(new CustomEvent('hideTopBarLoading'));
this.$emit('empty-message');
}
}
}, 50);
}
},
retrieveData() {
fetch(`/portal/${eXo.env.portal.portalName}/portlet-viewer?portletInstanceId=${this.portletInstanceId}&noCache=true`, {
this.contentRetrieved = 0;
fetch(`/portal/${eXo.env.portal.portalName}/portlet-viewer?portletInstanceId=${this.portletInstanceId}&noCache=true&maximizedPortletMode=${this.portletMode}`, {
credentials: 'include',
method: 'GET',
redirect: 'manual'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@
<div
:style="cssStyle"
class="d-flex position-relative">
<portlet-editor-application ref="application" />
<v-card
v-if="isEmpty"
v-text="displayEmptyMessage && $t('portlets.emptyPortletInstanceContent') || ''"
class="d-flex align-center card-border-radius justify-center position-absolute full-width full-height"
flat />
<portlet-editor-application
ref="application"
@empty="isEmpty = $event"
@empty-message="displayEmptyMessage = true" />
<portlet-editor-cell-resize-button
ref="resizeButton"
v-if="$root.portletMode === 'view' && !isEmpty"
:moving="moving"
@move-start="moveStart" />
</div>
Expand All @@ -40,6 +49,8 @@ export default {
diffY: 0,
updateDisplayInterval: 0,
moving: false,
isEmpty: false,
displayEmptyMessage: false,
}),
computed: {
displayResizeButton() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
<v-icon class="icon-default-color">fa-pager</v-icon>
<span class="px-2" v-sanitized-html="title"></span>
<v-spacer />
<portlet-editor-toolbar-save-button />
<portlet-editor-toolbar-edit-button
v-if="canSwitchToEditMode"
class="mx-2" />
<portlet-editor-toolbar-save-button
:disabled="$root.portletMode !== 'view'" />
</v-card>
</template>
<script>
Expand All @@ -37,6 +41,9 @@ export default {
title() {
return this.$t('layout.editPortletInstance', {0: `<strong>${this.$root?.portletInstance?.name}</strong>`});
},
canSwitchToEditMode() {
return this.$root.portletInstance?.supportedModes?.find?.(mode => mode === 'edit');
},
},
mounted() {
document.querySelector('#layoutTopBarContentChildren').append(this.$el);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!--

This file is part of the Meeds project (https://meeds.io/).

Copyright (C) 2020 - 2024 Meeds Association contact@meeds.io

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

-->
<template>
<v-btn
:loading="loading"
:aria-label="label"
class="btn d-flex align-center"
elevation="0"
outlined
@click="switchMode">
<span class="text-none">{{ label }}</span>
</v-btn>
</template>
<script>
export default {
data: () => ({
loading: false,
}),
computed: {
label() {
return this.$root.portletMode === 'view' ? this.$t('portlets.switchToEditMode') : this.$t('portlets.switchToViewMode');
},
},
methods: {
switchMode() {
this.$root.portletMode = this.$root.portletMode === 'view' ? 'edit' : 'view';
},
},
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -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'});
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions layout-webapp/src/main/webapp/vue-app/portlet-editor/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export function init() {
data: {
portletInstanceId: null,
portletInstance: null,
portletInstanceElement: null,
portletMode: 'view',
},
i18n,
created() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
</v-card>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="category.name" />
<v-list-item-title
:title="category.name"
v-text="category.name" />
</v-list-item-content>
<v-list-item-action class="my-auto">
<v-card
Expand Down
Loading

0 comments on commit a0bae84

Please sign in to comment.