Skip to content

Commit

Permalink
feat: Allow to create a Space Template Site from a Space Site - MEED-…
Browse files Browse the repository at this point in the history
…7736 - Meeds-io/MIPs#160

This change will implement a UI and update the Backend to allow creating a Group/Space Template from a Space Site.
  • Loading branch information
boubaker committed Nov 8, 2024
1 parent 7d86467 commit d217c64
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,35 +52,35 @@
@Service
public class SpaceTemplateService {

public static final String SPACE_TEMPLATE_CREATED_EVENT = "space.template.created";
public static final String SPACE_TEMPLATE_CREATED_EVENT = "space.template.created";

public static final String SPACE_TEMPLATE_UPDATED_EVENT = "space.template.updated";
public static final String SPACE_TEMPLATE_UPDATED_EVENT = "space.template.updated";

public static final String SPACE_TEMPLATE_DELETED_EVENT = "space.template.deleted";
public static final String SPACE_TEMPLATE_DELETED_EVENT = "space.template.deleted";

public static final String SPACE_TEMPLATE_SITE_PROP_NAME = "SPACE_TEMPLATE";
public static final String SPACE_TEMPLATE_SITE_PROP_NAME = "SPACE_TEMPLATE";

public static final String SPACE_TEMPLATE_ID_PROP_NAME = "SPACE_TEMPLATE_ID";
public static final String SPACE_TEMPLATE_ID_PROP_NAME = "SPACE_TEMPLATE_ID";

public static final String DEFAULT_SITE_TEMPLATE = "space";
public static final String DEFAULT_SITE_TEMPLATE = "space";

private static final Log LOG = ExoLogger.getLogger(SpaceTemplateService.class);
private static final Log LOG = ExoLogger.getLogger(SpaceTemplateService.class);

private TranslationService translationService;
private TranslationService translationService;

private AttachmentService attachmentService;
private AttachmentService attachmentService;

private UserACL userAcl;
private UserACL userAcl;

private SpaceTemplateStorage spaceTemplateStorage;
private SpaceTemplateStorage spaceTemplateStorage;

private UserPortalConfigService userPortalConfigService;
private UserPortalConfigService userPortalConfigService;

private LayoutService layoutService;
private LayoutService layoutService;

private NavigationService navigationService;
private NavigationService navigationService;

private ListenerService listenerService;
private ListenerService listenerService;

public SpaceTemplateService(TranslationService translationService,
AttachmentService attachmentService,
Expand Down Expand Up @@ -196,17 +196,19 @@ public SpaceTemplate createSpaceTemplate(SpaceTemplate spaceTemplate) throws Obj
if (spaceTemplate.getId() != 0) {
throw new IllegalArgumentException("Space template to create shouldn't have an id");
}
String layout = StringUtils.firstNonBlank(spaceTemplate.getLayout(), DEFAULT_SITE_TEMPLATE);
if (layoutService.getPortalConfig(SiteKey.groupTemplate(layout)) == null) {
throw new ObjectNotFoundException(String.format("Space Template layout '%s' wasn't found", layout));
String sourceLayout = StringUtils.firstNonBlank(spaceTemplate.getLayout(), DEFAULT_SITE_TEMPLATE);
SiteKey sourceSiteKey = sourceLayout.contains("::") ? new SiteKey(sourceLayout.split("::")[0], sourceLayout.split("::")[1]) :
SiteKey.groupTemplate(sourceLayout);
if (layoutService.getPortalConfig(sourceSiteKey) == null) {
throw new ObjectNotFoundException(String.format("Space Template layout '%s' wasn't found", sourceLayout));
}

SpaceTemplate spaceTemplateToCreate = spaceTemplate.clone();
spaceTemplateToCreate.setSystem(false);
spaceTemplateToCreate.setDeleted(false);
spaceTemplateToCreate.setLayout(null);
SpaceTemplate createdSpaceTemplate = spaceTemplateStorage.createSpaceTemplate(spaceTemplateToCreate);
createdSpaceTemplate = createSpaceTemplateLayout(createdSpaceTemplate, layout);
createdSpaceTemplate = createSpaceTemplateLayout(createdSpaceTemplate, sourceSiteKey);
listenerService.broadcast(SPACE_TEMPLATE_CREATED_EVENT, spaceTemplate, createdSpaceTemplate);
return createdSpaceTemplate;
}
Expand Down Expand Up @@ -274,8 +276,7 @@ public void deleteSpaceTemplate(long templateId) throws ObjectNotFoundException
}

private SpaceTemplate createSpaceTemplateLayout(SpaceTemplate spaceTemplate,
String sourceLayout) throws ObjectNotFoundException {
SiteKey sourceSiteKey = SiteKey.groupTemplate(sourceLayout);
SiteKey sourceSiteKey) throws ObjectNotFoundException {
SiteKey targetSiteKey = SiteKey.groupTemplate(String.valueOf(spaceTemplate.getId()));
userPortalConfigService.createSiteFromTemplate(sourceSiteKey, targetSiteKey);
PortalConfig targetPortalConfig = layoutService.getPortalConfig(targetSiteKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ social.spaces.administration.manageSpaces.spaceDeletionError=An error occurred w
social.spaces.administration.manageSpaces.openSettings=Open Settings
social.spaces.administration.manageSpaces.syncMembers=Sync Members
social.spaces.administration.manageSpaces.applyTemplate=Apply Template
social.spaces.administration.manageSpaces.templateSelection=Space Template
social.spaces.administration.manageSpaces.saveAsTemplate=Save as Template
social.spaces.administration.manageSpaces.syncReportsTitle=Sync Reports
social.spaces.administration.manageSpaces.syncMembersTitle=Sync Members
social.spaces.administration.manageSpaces.syncMembersDescription=Select a group to auto import its members to the space and consider it automatically as members of the space
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export default {

const specificGroupEntries = permissions?.filter?.(p =>
p !== this.$root.administratorsPermission
&& (!p.includes(':') || p.split(':')[1] !== this.$root.administratorsPermission)
&& (!this.users || p !== this.$root.usersPermission)
&& (!this.spaceAdmin || p !== 'spaceAdmin')
) || null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
v-if="spacesCount"
:title="spacesCount"
elevation="0"
class="me-2"
@click="openSpacesList">
{{ spacesCountLabel }}
</v-chip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,16 @@ extensionRegistry.registerExtension('space-templates', 'space-templates-item-act
name: 'delete',
componentName: 'space-templates-management-menu-item-delete',
});

extensionRegistry.registerExtension('spaces-administration', 'main', {
rank: 200,
id: 'space-templates-management-name',
name: 'space-templates-management-name',
componentName: 'space-templates-management-name-drawer',
});
extensionRegistry.registerExtension('spaces-administration', 'main', {
rank: 210,
id: 'space-templates-management-characteristics',
name: 'space-templates-management-characteristics',
componentName: 'space-templates-management-characteristics-drawer',
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,18 @@
@loading-spaces="loadingSpaces = $event"
@loaded="spacesLoaded" />
</v-main>
<div>
<component
v-for="extension in $root.mainExtensions"
:key="extension.name"
:is="extension.componentName" />
</div>
<spaces-administration-managers-drawer />
<spaces-administration-sync-reports-drawer />
<spaces-administration-sync-members-drawer />
<spaces-administration-permissions-drawer />
<spaces-administration-apply-template-drawer />
<space-form-drawer />
<component
v-for="extension in $root.mainExtensions"
:key="extension.name"
:is="extension.componentName" />
</v-app>
</template>
<script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
</div>
<select
v-model="spaceTemplateId"
class="flex-grow-0 ignore-vuetify-classes py-2 height-auto full-width text-truncate mt-0 mb-4"
@change="$emit('filter-select-change', select)">
:aria-label="$t('social.spaces.administration.manageSpaces.templateSelection')"
class="flex-grow-0 ignore-vuetify-classes py-2 height-auto full-width text-truncate mt-0 mb-4">
<option
v-for="item in spaceTemplateItems"
:key="item.value"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!--

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-list-item
dense
@click="saveAsTemplate">
<v-card
class="d-flex full-height justify-center"
color="transparent"
width="20"
flat>
<v-icon size="16">fa-columns</v-icon>
</v-card>
<v-list-item-title class="ps-2">
{{ $t('social.spaces.administration.manageSpaces.saveAsTemplate') }}
</v-list-item-title>
</v-list-item>
</template>
<script>
export default {
props: {
space: {
type: Object,
default: null,
},
},
methods: {
saveAsTemplate() {
this.$emit('loading', true);
window.require(['PORTLET/social/SpaceTemplateManagement'], () => {
window.setTimeout(async () => {
const bannerBlob = await fetch(this.space.bannerUrl, {
credentials: 'include',
method: 'GET',
}).then(resp => resp?.ok && resp.blob());
const bannerData = bannerBlob && await this.$utils.blobToBase64(bannerBlob);
const bannerUploadId = bannerBlob && await this.$uploadService.upload(bannerBlob);

const translationConfiguration = await this.$translationService.getTranslationConfiguration();
const nameTranslations = {};
const descriptionTranslations = {};
nameTranslations[translationConfiguration?.defaultLanguage] = this.space.displayName;
descriptionTranslations[translationConfiguration?.defaultLanguage] = this.space.description;
const spaceTemplate = this.space.templateId && this.$root.spaceTemplates.find(t => t.id === this.space.templateId);
const permissions = await this.$spaceAdministrationService.getSpacePermission(this.space.id);

this.$root.$emit('space-templates-name-open',
{
enabled: true,
icon: spaceTemplate?.icon,
spaceFields: spaceTemplate?.spaceFields || ['name', 'invitation', 'properties', 'access'],
permissions: spaceTemplate?.permissions,
layout: `group::${this.space.groupId}`,
spaceDefaultVisibility: this.space.visibility?.toUpperCase?.(),
spaceDefaultRegistration: this.space.subscription?.toUpperCase?.(),
spaceAllowContentCreation: !!this.space.redactorsCount,
spaceLayoutPermissions: permissions?.layoutPermissions?.map?.(p => (p === `manager:${this.space.groupId}` ? 'spaceAdmin' : p)),
spacePublicSitePermissions: permissions?.publicSitePermissions?.map?.(p => (p === `manager:${this.space.groupId}` ? 'spaceAdmin' : p)),
spaceDeletePermissions: permissions?.deletePermissions?.map?.(p => (p === `manager:${this.space.groupId}` ? 'spaceAdmin' : p)),
},
this.space.displayName,
nameTranslations,
this.space.description,
descriptionTranslations,
true,
bannerUploadId,
bannerData);
this.$emit('loading', false);
}, 200);
});
},
},
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,18 @@ extensionRegistry.registerExtension('spaces-administration', 'menu-action', {

extensionRegistry.registerExtension('spaces-administration', 'menu-action', {
rank: 40,
name: 'save-as-template',
componentName: 'spaces-administration-save-as-template-menu-item',
});

extensionRegistry.registerExtension('spaces-administration', 'menu-action', {
rank: 50,
name: 'permissions',
componentName: 'spaces-administration-permissions-menu-item',
});

extensionRegistry.registerExtension('spaces-administration', 'menu-action', {
rank: 50,
rank: 60,
name: 'delete',
componentName: 'spaces-administration-delete-menu-item',
});
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import SpacesAdministrationSettingsMenuItem from './components/menu-action/Space
import SpacesAdministrationSynMembersMenuItem from './components/menu-action/SpacesAdministrationSynMembersMenuItem.vue';
import SpacesAdministrationPermissionsMenuItem from './components/menu-action/SpacesAdministrationPermissionsMenuItem.vue';
import SpacesAdministrationApplyTemplateMenuItem from './components/menu-action/SpacesAdministrationApplyTemplateMenuItem.vue';
import SpacesAdministrationSaveAsTemplateMenuItem from './components/menu-action/SpacesAdministrationSaveAsTemplateMenuItem.vue';

import SpacesAdministrationBindingReportItem from './components/binding-report/SpacesAdministrationBindingReportItem.vue';
import SpacesAdministrationBindingReportList from './components/binding-report/SpacesAdministrationBindingReportList.vue';
Expand Down Expand Up @@ -63,6 +64,7 @@ const components = {
'spaces-administration-sync-members-menu-item': SpacesAdministrationSynMembersMenuItem,
'spaces-administration-permissions-menu-item': SpacesAdministrationPermissionsMenuItem,
'spaces-administration-apply-template-menu-item': SpacesAdministrationApplyTemplateMenuItem,
'spaces-administration-save-as-template-menu-item': SpacesAdministrationSaveAsTemplateMenuItem,

'spaces-administration-managers-drawer': SpacesAdministrationManagersDrawer,
'spaces-administration-sync-members-drawer': SpacesAdministrationSyncMembersDrawer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function init() {
exoi18n.loadLanguageAsync(lang, [
`/social/i18n/locale.portlet.social.SpacesAdministrationPortlet?lang=${lang}`,
`/social/i18n/locale.portlet.social.SpacesListApplication?lang=${lang}`,
`/social/i18n/locale.portlet.SpaceTemplatesManagement?lang=${lang}`,
`/social/i18n/locale.portal.webui?lang=${lang}`,
])
.then(i18n => Vue.createApp({
Expand Down

0 comments on commit d217c64

Please sign in to comment.