edit, remove + toggle page modules
This commit is contained in:
@@ -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: [
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
alias: 'module.offset',
|
||||
|
||||
fields: []
|
||||
};
|
||||
@@ -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: [
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user