edit, remove + toggle page modules

This commit is contained in:
2020-08-20 14:35:08 +02:00
parent 5319516922
commit 4abe35d422
19 changed files with 348 additions and 40 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
// define a preview which is rendered in the overview
preview: {
template: '<div v-html="model.headline"></div>{{model.subline}}<ui-button label="Click" />'
template: '<ui-module-preview-headline :text="model.headline" :subline="model.subline" />'
},
fields: [
+5
View File
@@ -0,0 +1,5 @@
export default {
alias: 'module.offset',
fields: []
};
+1 -1
View File
@@ -15,7 +15,7 @@
// define a preview which is rendered in the overview
preview: {
template: '<div v-html="model.text"></div>'
template: '<ui-module-preview-text :text="model.text" />'
},
fields: [
+1 -1
View File
@@ -3,7 +3,7 @@
// define a preview which is rendered in the overview
preview: {
template: '<div v-html="model.text"></div>'
template: '<ui-module-preview-text :text="model.text" />'
},
fields: [
@@ -0,0 +1,10 @@
using System;
using zero.Core.Entities;
namespace zero.TestData
{
public class OffsetModule : Module
{
}
}
+2 -1
View File
@@ -35,7 +35,8 @@ namespace zero.TestData
zero.Modules.Add<HeadlineModule>("headline", "Headline", "Headline with optional subline", "fth-underline", "Texts");
zero.Modules.Add<TextWithImageModule>("textWithImage", "Text with image", "Short textblock with image", "fth-layers", "Texts", new List<string>() { "root" });
zero.Modules.Add<GalleryModule>("gallery", "Gallery", "Image gallery grid", "fth-image", "Media");
zero.Modules.Add<DownloadModule>("download", "Downloads", "List containing downloads", "fth-download", "Lists");
zero.Modules.Add<DownloadModule>("download", "Downloads", "List containing downloads", "fth-download", "Misc");
zero.Modules.Add<OffsetModule>("offset", "Offset", "Offset between two modules", "fth-code", "Misc");
}
public void ConfigureServices(IServiceCollection services)
@@ -35,6 +35,7 @@
components: { UiEditor },
data: () => ({
isAdd: true,
disabled: false,
id: null,
loading: true,
@@ -48,11 +49,21 @@
onLoad(form)
{
form.load(!this.id ? ModulesApi.getEmpty(this.config.module.alias) : null).then(response =>
if (this.config.model)
{
this.isAdd = false;
this.model = JSON.parse(JSON.stringify(this.config.model));
}
form.load(ModulesApi.getEmpty(this.config.module.alias)).then(response =>
{
this.disabled = !response.meta.canEdit;
this.meta = response.meta;
this.model = response.entity;
if (this.isAdd)
{
this.model = response.entity;
}
this.loading = false;
});
},
@@ -1,6 +1,4 @@
<script>
import Vue from 'vue';
export default {
name: 'uiModulePreviewInner',
@@ -1,14 +1,27 @@
<template>
<div class="ui-module-item" v-if="!loading">
<div class="ui-module-item" v-if="!loading" :data-module="module.alias">
<header class="ui-module-item-header"><i :class="module.icon"></i> {{module.name}}</header>
<ui-module-preview-inner :template="renderer.preview.template" :value="value" />
<!--<component :is="component" :module="module" />-->
<ui-module-preview-inner v-if="renderer.preview && renderer.preview.template" :template="renderer.preview.template" :value="value" />
<div v-if="!value.isActive" class="ui-module-item-disabled"></div>
<!--<pre><code>{{value}}</code></pre>-->
<div class="ui-module-item-actions">
<ui-icon-button v-if="!value.isActive" class="ui-module-item-disabled-icon" icon="fth-lock" title="Disabled" @click="value.isActive = true" />
<ui-dropdown v-else align="right">
<template v-slot:button>
<ui-icon-button icon="fth-more-horizontal" title="Actions" />
</template>
<ui-dropdown-button label="Edit" icon="fth-edit-2" @click="$emit('edit', module, value)" />
<ui-dropdown-button v-if="value.isActive" label="Disable" icon="fth-lock" @click="value.isActive = false" />
<ui-dropdown-button v-else label="Enable" icon="fth-unlock" @click="value.isActive = true" />
<ui-dropdown-button label="Remove" icon="fth-trash" @click="$emit('remove', module, value)" />
</ui-dropdown>
</div>
</div>
</template>
<script>
import Vue from 'vue';
import { find as _find } from 'underscore';
export default {
@@ -30,8 +43,7 @@
data: () => ({
loading: true,
module: {},
renderer: {},
component: null
renderer: {}
}),
@@ -43,17 +55,6 @@
},
components: {
'preview': (a,b,c) =>
{
console.info(a,b,c);
return {
}
}
},
mounted()
{
this.render(this.value);
@@ -78,11 +79,17 @@
<style lang="scss">
.ui-module-item
{
display: grid !important;
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
grid-column-gap: var(--padding);
position: relative;
}
.ui-module-item-header
{
grid-column: 1;
grid-row: 1;
display: flex;
align-items: center;
color: var(--color-fg-dim);
@@ -96,4 +103,67 @@
top: -1px;
}
}
.ui-module-item-disabled
{
background: var(--color-bg-bright);
opacity: .6;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
}
.ui-module-item-disabled-icon
{
position: relative;
z-index: 1;
background: var(--color-accent-error-bg);
.ui-button-icon
{
color: var(--color-accent-error);
}
}
.ui-module-preview-inner
{
grid-column: 1;
grid-row: 2;
margin-top: 20px;
max-width: 1060px;
p
{
margin: 0;
}
p + p
{
margin-top: 10px;
}
a
{
color: var(--color-fg);
text-decoration: underline dotted var(--color-fg-dim);
text-underline-offset: 3px;
}
}
.ui-module-item-actions
{
display: flex;
grid-column: 2;
grid-row: span 2 / auto;
align-self: center;
margin: -10px 0;
> * + *
{
margin-left: 10px;
}
}
</style>
+27 -6
View File
@@ -1,7 +1,7 @@
<template>
<div class="ui-modules-inner">
<ui-module-preview v-for="item in items" :key="item.id" :types="moduleTypes" :value="item" />
<ui-modules-select ref="moduleSelect" :types="moduleTypes" :value="value" v-if="canAdd" @selected="onSelected" />
<ui-module-preview v-for="item in items" :key="item.id" :types="moduleTypes" :value="item" @edit="edit" @remove="remove" />
<ui-modules-select ref="moduleSelect" :types="moduleTypes" :value="value" v-if="canAdd" @selected="onAdd" />
</div>
</template>
@@ -10,6 +10,7 @@
import ModulesApi from 'zero/resources/modules.js';
import EditModuleOverlay from './edit-module';
import Overlay from 'zero/services/overlay.js';
import Arrays from 'zero/services/arrays.js';
export default {
name: 'uiModules',
@@ -56,24 +57,44 @@
},
onSelected(module, isAdd)
onAdd(module)
{
this.edit(module, null, true);
},
edit(module, model, isAdd)
{
return Overlay.open({
component: EditModuleOverlay,
display: 'editor',
module: module,
renderer: 'module.' + module.alias,
model: {},
model: model,
width: 1100
}).then(value =>
{
this.items.push(value);
if (isAdd)
{
this.items.push(value);
this.$refs.moduleSelect.reset();
}
else
{
Arrays.replace(this.items, model, value);
}
this.onChange();
this.$refs.moduleSelect.reset();
});
},
remove(module, model)
{
Arrays.remove(this.items, model);
},
onChange()
{
this.$emit('input', this.items);
@@ -0,0 +1,41 @@
<template>
<div class="ui-module-preview-headline">
<article class="-text" v-if="text" v-html="text"></article>
<article class="-subline" v-if="subline" v-html="subline"></article>
</div>
</template>
<script>
export default {
name: 'uiModulePreviewText',
props: {
text: {
type: String
},
subline: {
type: String
}
}
}
</script>
<style lang="scss">
.ui-module-preview-headline
{
font-size: var(--font-size);
line-height: 1.5;
.-text
{
font-size: var(--font-size-xl);
font-weight: 600;
}
.-subline
{
color: var(--color-fg-dim);
font-size: var(--font-size-s);
}
}
</style>
@@ -0,0 +1,48 @@
<template>
<div class="ui-module-preview-text">
<article class="-text" v-if="text" v-html="text"></article>
<article class="-subline" v-if="subline" v-html="subline"></article>
</div>
</template>
<script>
export default {
name: 'uiModulePreviewText',
props: {
text: {
type: String
},
subline: {
type: String
}
}
}
</script>
<style lang="scss">
.ui-module-preview-text
{
font-size: var(--font-size);
line-height: 1.5;
.-text
{
}
.-subline
{
color: var(--color-fg-dim);
font-size: var(--font-size-s);
}
.-text, .-subline
{
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
display: -webkit-box;
}
}
</style>
@@ -41,6 +41,10 @@
disabled: {
type: Boolean,
default: false
},
prevent: {
type: Boolean,
default: false
}
},
@@ -63,7 +67,7 @@
}
while (current = current.$parent);
if (false && !this.dropdown)
if (!this.dropdown)
{
warn('ui-dropdown-button: Could not find parent <ui-dropdown />');
}
@@ -77,6 +81,11 @@
if (!this.loading && !this.disabled)
{
if (!this.prevent)
{
this.dropdown.hide();
}
this.$emit('click', this.value, {
dropdown: this.dropdown,
hide()
+35
View File
@@ -7,6 +7,7 @@ const arrayMoveMutate = (array, from, to) =>
};
export default {
/// <summary>
/// Move an item in an array
/// </summary>
@@ -15,5 +16,39 @@ export default {
array = array.slice();
arrayMoveMutate(array, from, to);
return array;
},
/// <summary>
/// Replaces an item in an array
/// </summary>
replace(array, origin, target)
{
const index = array.indexOf(origin);
if (index < 0)
{
return index;
}
array.splice(index, 1);
array.splice(index, 0, target);
return index;
},
/// <summary>
/// Removes an item from an array
/// </summary>
remove(array, value)
{
const index = array.indexOf(value);
if (index < 0)
{
return;
}
array.splice(index, 1);
return index;
}
};
+1 -1
View File
@@ -157,7 +157,7 @@
--color-accent-warn: rgb(211, 172, 35);
--color-accent-warn-bg: rgba(233, 193, 56, 0.1);
--color-accent-error: rgb(216, 40, 83);
--color-accent-error-bg: rgba(216, 40, 83, 0.08);
--color-accent-error-bg: rgba(216, 40, 83, 0.15);
// sizes
+28 -4
View File
@@ -57,17 +57,15 @@ namespace zero.Web.Controllers
/// <summary>
/// Creates an edit model with appropriate options and permissions
/// </summary>
public JsonResult Edit<T>(T data, bool typed = true, Action<dynamic> transform = null) where T : IZeroIdEntity
public JsonResult Edit<T>(T data, bool typed = true, Action<EditModel<T>> transform = null) where T : IZeroIdEntity
{
Type type = typeof(T);
bool canBeShared = AppAwareShareableType.IsAssignableFrom(type);
//ControllerContext.ActionDescriptor.FilterDescriptors[0].
dynamic model = new ExpandoObject();
EditModel<T> model = new EditModel<T>();
model.Entity = data;
model.Meta = new ExpandoObject();
model.Meta.Token = Token.Get(data);
model.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type);
model.Meta.CanBeShared = canBeShared;
@@ -82,6 +80,32 @@ namespace zero.Web.Controllers
}
/// <summary>
/// Creates an edit model with appropriate options and permissions
/// </summary>
public JsonResult Edit<T, TWrapper>(TWrapper data, bool typed = true, Action<EditModel<T>> transform = null)
where T : IZeroIdEntity
where TWrapper : EditModel<T>, new()
{
Type type = typeof(T);
bool canBeShared = AppAwareShareableType.IsAssignableFrom(type);
//ControllerContext.ActionDescriptor.FilterDescriptors[0].
data.Meta.Token = Token.Get(data.Entity);
data.Meta.IsAppAware = AppAwareType.IsAssignableFrom(type);
data.Meta.CanBeShared = canBeShared;
data.Meta.CanCreate = true;
data.Meta.CanCreateShared = canBeShared;
data.Meta.CanEdit = true;
data.Meta.CanDelete = true;
transform?.Invoke(data);
return Json(data, typed);
}
public IActionResult JsonPreviews<T>(Dictionary<string, T> items, Func<T, PreviewModel> transform)
{
IList<PreviewModel> previews = new List<PreviewModel>();
+12 -1
View File
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Web.Models;
namespace zero.Web.Controllers
{
@@ -22,7 +23,17 @@ namespace zero.Web.Controllers
public IActionResult GetPageType([FromQuery] string alias) => Json(Api.GetPageType(alias));
public async Task<IActionResult> GetById([FromQuery] string id) => Edit(await Api.GetById(id));
public async Task<IActionResult> GetById([FromQuery] string id)
{
T entity = await Api.GetById(id);
return Edit<T, PageEditModel<T>>(new PageEditModel<T>()
{
Entity = entity,
Revisions = await RevisionsApi.GetPaged<T>(id),
PageType = Api.GetPageType(entity.PageTypeAlias)
});
}
public IActionResult GetEmpty(string type, string parent = null) => Edit(new T()
{
+9 -1
View File
@@ -4,7 +4,6 @@ namespace zero.Web.Models
{
public class EditModel : EditModel<object> { }
public class EditModel<T>
{
/// <summary>
@@ -12,6 +11,15 @@ namespace zero.Web.Models
/// </summary>
public T Entity { get; set; }
/// <summary>
/// Meta data
/// </summary>
public EditMetaModel Meta { get; set; } = new EditMetaModel();
}
public class EditMetaModel
{
/// <summary>
/// Whether an entity of this type can be created
/// </summary>
+16
View File
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using zero.Core.Entities;
namespace zero.Web.Models
{
public class PageEditModel<T> : EditModel<T> where T : IPage
{
public PageType PageType { get; set; }
public ListResult<Revision> Revisions { get; set; }
}
}