diff --git a/zero.Api/Endpoints/PageModules/PageModuleModule.cs b/zero.Api/Endpoints/PageModules/PageModuleModule.cs new file mode 100644 index 00000000..7f819fe0 --- /dev/null +++ b/zero.Api/Endpoints/PageModules/PageModuleModule.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace zero.Api.Endpoints.PageModules; + +public class PageModuleModule : ZeroModule +{ + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + } +} \ No newline at end of file diff --git a/zero.Api/Endpoints/PageModules/PageModulePermissions.cs b/zero.Api/Endpoints/PageModules/PageModulePermissions.cs new file mode 100644 index 00000000..82269abc --- /dev/null +++ b/zero.Api/Endpoints/PageModules/PageModulePermissions.cs @@ -0,0 +1,31 @@ +namespace zero.Api.Endpoints.PageModules; + +public class PageModulePermissions : PermissionProvider +{ + public const string Group = "zero.pagemodules"; + + public const string Create = "zero.pagemodules.create"; + public const string Read = "zero.pagemodules.read"; + public const string Update = "zero.pagemodules.update"; + public const string Delete = "zero.pagemodules.delete"; + + + public override Task Configure(IPermissionContext context) + { + PermissionGroup group = new(Group, "@module.list"); + group.Permissions.Add(new Permission("zero.pagemodules.defaults", "Default permissions") + { + Children = new() + { + new(Create, "@permission.states.create"), + new(Read, "@permission.states.read"), + new(Update, "@permission.states.update"), + new(Delete, "@permission.states.delete") + } + }); + + context.AddGroup(group); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/zero.Api/Endpoints/PageModules/PageModulesController.cs b/zero.Api/Endpoints/PageModules/PageModulesController.cs new file mode 100644 index 00000000..13ba6624 --- /dev/null +++ b/zero.Api/Endpoints/PageModules/PageModulesController.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc; + +namespace zero.Api.Endpoints.PageModules; + +public class PageModulesController : ZeroApiController +{ + readonly IPageModuleTypeService ModuleTypeService; + + public PageModulesController(IPageModuleTypeService moduleTypeService) + { + ModuleTypeService = moduleTypeService; + } + + + [HttpGet("")] + [ZeroAuthorize(PageModulePermissions.Read)] + public async Task>> GetModuleTypes([FromQuery] string[] tags = default, [FromQuery] string pageId = default) => Ok(await ModuleTypeService.GetModuleTypes(tags, pageId)); + + + [HttpGet("{alias}")] + [ZeroAuthorize(PageModulePermissions.Read)] + public ActionResult GetModuleType(string alias) => Ok(ModuleTypeService.GetModuleType(alias)); + + + [HttpGet("{alias}/empty")] + [ZeroAuthorize(PageModulePermissions.Read)] + public ActionResult GetEmpty(string alias) + { + PageModule module = ModuleTypeService.GetEmpty(alias); + + if (module == null) + { + return NotFound(); + } + + return module; + } +} \ No newline at end of file diff --git a/zero.Api/Plugin.cs b/zero.Api/Plugin.cs index fa11d257..23584ccd 100644 --- a/zero.Api/Plugin.cs +++ b/zero.Api/Plugin.cs @@ -41,6 +41,7 @@ public class ZeroApiPlugin : ZeroPlugin modules.Add(); modules.Add(); modules.Add(); + modules.Add(); modules.ConfigureServices(services, configuration); diff --git a/zero.Backoffice.UI/app/core/zeroRuntime.ts b/zero.Backoffice.UI/app/core/zeroRuntime.ts index e058f8bf..97f68400 100644 --- a/zero.Backoffice.UI/app/core/zeroRuntime.ts +++ b/zero.Backoffice.UI/app/core/zeroRuntime.ts @@ -21,7 +21,8 @@ import translationPlugin, integrationPlugin, userPlugin, - linksPlugin + linksPlugin, + pageModulePlugin } from '../modules'; import editorPlugin from '../editor/plugin'; import { ZeroSchema } from 'zero/schemas'; @@ -34,7 +35,7 @@ import { Emitter, EventType } from 'mitt'; plugins.push( editorPlugin, countryPlugin, applicationPlugin, settingsPlugin, languagePlugin, mediaPlugin, spacePlugin, pagePlugin, mailTemplatePlugin, - translationPlugin, integrationPlugin, userPlugin, linksPlugin + translationPlugin, integrationPlugin, userPlugin, linksPlugin, pageModulePlugin ); diff --git a/zero.Backoffice.UI/app/editor/_fields/linkpicker.vue b/zero.Backoffice.UI/app/editor/_fields/linkpicker.vue deleted file mode 100644 index aef0d4ce..00000000 --- a/zero.Backoffice.UI/app/editor/_fields/linkpicker.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/_fields/modules.vue b/zero.Backoffice.UI/app/editor/fields/modules.vue similarity index 100% rename from zero.Backoffice.UI/app/editor/_fields/modules.vue rename to zero.Backoffice.UI/app/editor/fields/modules.vue diff --git a/zero.Backoffice.UI/app/modules/index.ts b/zero.Backoffice.UI/app/modules/index.ts index 79a62143..8e4daf9d 100644 --- a/zero.Backoffice.UI/app/modules/index.ts +++ b/zero.Backoffice.UI/app/modules/index.ts @@ -10,4 +10,5 @@ export * from './mails'; export * from './translations'; export * from './integrations'; export * from './users'; -export * from './links'; \ No newline at end of file +export * from './links'; +export * from './pageModules'; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/actions.ts b/zero.Backoffice.UI/app/modules/pageModules/actions.ts new file mode 100644 index 00000000..d2f82cfd --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/actions.ts @@ -0,0 +1,103 @@ + +import api from './api'; +import * as overlays from '../../services/overlay'; +import * as notifications from '../../services/notification'; +import { localize } from '../../services/localization'; +import { Router } from 'vue-router'; + + +export default { + + async create(router: Router, parent: object) + { + const result = await overlays.open({ + component: () => import('./overlays/create.vue'), + width: 480, + model: { + parent + } + }); + + if (result.value) + { + router.push({ + name: 'pages-create', + params: { + flavor: result.value.alias, + parent: parent ? parent.id : undefined + } + }) + } + }, + + + async move(model: object) + { + const result = await overlays.open({ + component: () => import('./overlays/move.vue'), + display: 'editor', + width: 480, + model: model + }); + + if (result.eventType == 'confirm') + { + console.info('okay'); + } + }, + + + async copy(model: object) + { + const result = await overlays.open({ + component: () => import('./overlays/copy.vue'), + display: 'editor', + width: 480, + model: model + }); + + if (result.eventType == 'confirm') + { + console.info('okay'); + } + }, + + + async sort(model: object) + { + const result = await overlays.open({ + component: () => import('./overlays/sort.vue'), + display: 'editor', + width: 480, + model: model + }); + + if (result.eventType == 'confirm') + { + console.info('okay'); + } + }, + + + async remove(model: object) + { + const response = await api.getDependencies(model.id); + + const result = await overlays.confirmDelete("@page.deleteoverlay.title", '@page.deleteoverlay.text', localize('@page.deleteoverlay.warning', { + tokens: { + pages: response.data.pages + } + })); + + if (result.eventType == 'confirm') + { + result.value.state('loading'); + + await api.delete(model.id); + + result.value.state('success'); + result.value.close(); + } + } + +}; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/api.ts b/zero.Backoffice.UI/app/modules/pageModules/api.ts new file mode 100644 index 00000000..115569f5 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/api.ts @@ -0,0 +1,10 @@ +import { get, ApiRequestConfig } from '../../services/request'; + +export default { + + getEmpty: (alias: string, config?: ApiRequestConfig) => get(`pagemodules/${alias}/empty`, { ...config }), + + getTypes: (pageId?: string, tags?: string[], config?: ApiRequestConfig) => get('pagemodules', { ...config, params: { pageId, tags } }), + + getType: (alias: string, config?: ApiRequestConfig) => get('pagemodules/' + alias, { ...config }) +}; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/editor-modules.vue b/zero.Backoffice.UI/app/modules/pageModules/editor-modules.vue new file mode 100644 index 00000000..d8b51f2d --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/editor-modules.vue @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/index.ts b/zero.Backoffice.UI/app/modules/pageModules/index.ts new file mode 100644 index 00000000..8db59b14 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/index.ts @@ -0,0 +1,7 @@ +import api from './api'; +import plugin from './plugin'; + +export { + api as pageModuleApi, + plugin as pageModulePlugin +}; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/overlays/edit.vue b/zero.Backoffice.UI/app/modules/pageModules/overlays/edit.vue new file mode 100644 index 00000000..7e2c270f --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/overlays/edit.vue @@ -0,0 +1,113 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/overlays/select.vue b/zero.Backoffice.UI/app/modules/pageModules/overlays/select.vue new file mode 100644 index 00000000..bf7c4ada --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/overlays/select.vue @@ -0,0 +1,169 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/partials/preview-inner.vue b/zero.Backoffice.UI/app/modules/pageModules/partials/preview-inner.vue new file mode 100644 index 00000000..6ec5d002 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/partials/preview-inner.vue @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/partials/preview.vue b/zero.Backoffice.UI/app/modules/pageModules/partials/preview.vue new file mode 100644 index 00000000..cc231341 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/partials/preview.vue @@ -0,0 +1,289 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/plugin.ts b/zero.Backoffice.UI/app/modules/pageModules/plugin.ts new file mode 100644 index 00000000..3c16b24e --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/plugin.ts @@ -0,0 +1,30 @@ +import { ZeroPlugin, ZeroPluginOptions } from '../../core'; +import { defineAsyncComponent } from 'vue'; + +export default { + name: "zero.pagemodules", + + install(app: ZeroPluginOptions) + { + app.vue.component('ui-modules', defineAsyncComponent(() => import('./ui-modules.vue'))); + app.fieldType('modules', defineAsyncComponent(() => import('./editor-modules.vue'))); + } +} as ZeroPlugin; + + +declare module 'zero/schemas' +{ + export interface ZeroEditorField + { + /** + * Renders modules + * @param {ModulesFieldOptions} [options] - Custom options + */ + modules(options?: ModulesFieldOptions): ZeroEditorField; + } + + export interface ModulesFieldOptions extends PickerFieldOptions + { + + } +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pageModules/ui-modules.vue b/zero.Backoffice.UI/app/modules/pageModules/ui-modules.vue new file mode 100644 index 00000000..ebaeb5d6 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/pageModules/ui-modules.vue @@ -0,0 +1,229 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/pages/page.vue b/zero.Backoffice.UI/app/modules/pages/page.vue index 36debd5e..7cbf8b5a 100644 --- a/zero.Backoffice.UI/app/modules/pages/page.vue +++ b/zero.Backoffice.UI/app/modules/pages/page.vue @@ -147,8 +147,6 @@ onEditorConfigure(editor) { - console.info('configure', editor); - //if (this.isFolder) //{ // return; diff --git a/zero.Core/Pages/Modules/PageModuleOptions.cs b/zero.Core/Pages/Modules/PageModuleOptions.cs index 2b39de7a..3128b72b 100644 --- a/zero.Core/Pages/Modules/PageModuleOptions.cs +++ b/zero.Core/Pages/Modules/PageModuleOptions.cs @@ -2,12 +2,6 @@ public class PageModuleOptions : List { - public void Add(PageModuleType moduleType) where T : PageModule, new() - { - Add(PageModuleType.Convert(moduleType)); - } - - public void Add(string alias, string name, string description, string icon, string group = null, List tags = null, List disallowedPageTypes = null) where T : PageModule, new() { Add(new PageModuleType(typeof(T)) @@ -17,23 +11,9 @@ public class PageModuleOptions : List Description = description, Icon = icon, Group = group, - Tags = tags ?? new List(), - DisallowedPageTypes = disallowedPageTypes ?? new List() - }); - } - - - public void Add(Type type, string alias, string name, string description, string icon, string group = null, List tags = null, List disallowedPageTypes = null) - { - Add(new PageModuleType(type) - { - Alias = alias, - Name = name, - Description = description, - Icon = icon, - Group = group, - Tags = tags ?? new List(), - DisallowedPageTypes = disallowedPageTypes ?? new List() + Tags = tags ?? new(), + DisallowedPageTypes = disallowedPageTypes ?? new(), + Construct = _ => new T() }); } } \ No newline at end of file diff --git a/zero.Core/Pages/Modules/PageModuleType.cs b/zero.Core/Pages/Modules/PageModuleType.cs index 7dd2f68f..2fc73ccf 100644 --- a/zero.Core/Pages/Modules/PageModuleType.cs +++ b/zero.Core/Pages/Modules/PageModuleType.cs @@ -3,42 +3,8 @@ /// /// A module type holds information about a Module implementation /// -public class PageModuleType : PageModuleType where T : PageModule, new() +public class PageModuleType : FlavorConfig { - public PageModuleType() : base(typeof(T)) { } -} - - -/// -/// A module type holds information about a Module implementation -/// -public class PageModuleType -{ - /// - /// Type of the associated module class - /// - public Type ContentType { get; private set; } - - /// - /// Alias for querying - /// - public string Alias { get; set; } - - /// - /// Name of the module type - /// - public string Name { get; set; } - - /// - /// Optional description - /// - public string Description { get; set; } - - /// - /// Icon of the module type - /// - public string Icon { get; set; } - /// /// Optionally group modules together /// @@ -56,22 +22,5 @@ public class PageModuleType public List DisallowedPageTypes { get; set; } = new(); - public PageModuleType(Type type) - { - ContentType = type; - } - - public static PageModuleType Convert(PageModuleType model) where T : PageModule, new() - { - return new PageModuleType(model.ContentType) - { - Alias = model.Alias, - Name = model.Name, - Description = model.Description, - Icon = model.Icon, - Group = model.Group, - Tags = model.Tags, - DisallowedPageTypes = model.DisallowedPageTypes - }; - } + public PageModuleType(Type type) : base(type) {} } \ No newline at end of file diff --git a/zero.Core/Pages/Modules/PageModuleTypeService.cs b/zero.Core/Pages/Modules/PageModuleTypeService.cs index a4aaf4c3..50b3aef7 100644 --- a/zero.Core/Pages/Modules/PageModuleTypeService.cs +++ b/zero.Core/Pages/Modules/PageModuleTypeService.cs @@ -23,6 +23,29 @@ public class PageModuleTypeService : IPageModuleTypeService } + /// + public T GetEmpty(string alias) where T : PageModule + { + PageModuleType type = GetModuleType(alias); + T module = type?.Construct(type) as T; + + if (module == null) + { + return default; + } + + module.ModuleTypeAlias = type.Alias; + module.Id = IdGenerator.Create(8); + module.IsActive = true; + + return module; + } + + + /// + public PageModule GetEmpty(string alias) => GetEmpty(alias); + + /// public async Task> GetModuleTypes(string[] tags = default, string pageId = default) { @@ -61,6 +84,16 @@ public class PageModuleTypeService : IPageModuleTypeService public interface IPageModuleTypeService { + /// + /// Get a new instance of a page module of the specified type + /// + T GetEmpty(string alias) where T : PageModule; + + /// + /// Get a new instance of a page module of the specified type + /// + PageModule GetEmpty(string alias); + /// /// Get all available module types (can be limited to the passed tags) ///