remove legacy stuff + allow custom components for editor-fields

This commit is contained in:
2021-12-23 13:47:34 +01:00
parent 2b7fecff52
commit e566ec7cf0
66 changed files with 79 additions and 2120 deletions
+21 -1
View File
@@ -27,6 +27,16 @@ import uiPick from './ui-pick.vue';
import uiDatagrid from './ui-datagrid.vue';
import uiProgress from './ui-progress.vue';
import uiDate from './ui-date.vue';
import uiRte from './ui-rte/ui-rte.vue';
import uiCheckList from './ui-check-list.vue';
import uiSearch from './ui-search.vue';
import uiToggle from './ui-toggle.vue';
import uiProperty from './ui-property.vue';
import uiColorpicker from './ui-colorpicker.vue';
import uiDatepicker from './ui-datepicker.vue';
import uiSelect from './ui-select.vue';
import uiForm from './ui-form.vue';
import uiFormHeader from './ui-form-header.vue';
export {
uiIcon,
@@ -57,5 +67,15 @@ export {
uiPick,
uiDatagrid,
uiProgress,
uiDate
uiDate,
uiRte,
uiCheckList,
uiSearch,
uiToggle,
uiProperty,
uiColorpicker,
uiDatepicker,
uiSelect,
uiForm,
uiFormHeader
};
@@ -10,7 +10,7 @@
<script>
import { generateId } from '../../utils/numbers';
import { generateId } from '../utils/numbers';
export default {
name: 'uiColorpicker',
@@ -12,8 +12,8 @@
<script>
import { generateId } from '../../utils/numbers';
import { formatDate, toIsoDate } from '../../utils/dates';
import { generateId } from '../utils/numbers';
import { formatDate, toIsoDate } from '../utils/dates';
const DATETIME_FORMAT = 'DD.MM.YY HH:mm';
const DATE_FORMAT = 'DD.MM.YY';
@@ -4,6 +4,9 @@
<div v-if="loadingState == 'loading'" class="ui-form-loading">
<i class="ui-form-loading-progress"></i>
</div>
<div v-if="loadingState === 'error'">
error [not implemented]
</div>
<form-error-view v-if="loadingState === 'error'" :error="loadingError" />
</form>
</template>
@@ -11,7 +14,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import FormErrorView from './form-error-view.vue';
//import FormErrorView from './form-error-view.vue';
import * as overlays from '../services/overlay';
import * as notifications from '../services/notification';
import { arrayGroupBy } from '../utils/arrays';
@@ -19,7 +22,7 @@
export default defineComponent({
name: 'uiForm',
components: { FormErrorView },
//components: { FormErrorView },
props: {
errorComponents: {
@@ -34,8 +34,8 @@
<script lang="ts">
import { generateId } from '../../../utils/numbers';
import { debounce } from '../../../utils/timing';
import { generateId } from '../../utils/numbers';
import { debounce } from '../../utils/timing';
import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-3';
// import { Placeholder } from '@tiptap/vue-3';
import EditorMenuBar from './rte.menubar';
@@ -6,7 +6,6 @@ import { Zero, ZeroOptions } from './types/zero';
import { createRouter, RouteRecordRaw, RouterOptions } from 'vue-router';
import registerDirectives from '../directives/register';
import registerComponents from '../components/register';
import registerFormComponents from '../forms/register';
import registerEditorComponents from '../editor/register';
import { getRouterConfig, appendRouterGuards } from './router/routerConfig';
import
@@ -26,7 +25,6 @@ import editorPlugin from '../editor/plugin';
import { ZeroSchema } from 'zero/schemas';
import { ZeroSchemaProp } from './zero';
import * as zeroOptions from '../options';
import { createEditor } from '../editor/_new/editor';
export class ZeroRuntime implements Zero
{
@@ -72,7 +70,6 @@ export class ZeroRuntime implements Zero
registerDirectives(app);
registerComponents(app);
registerFormComponents(app);
registerEditorComponents(app);
}
@@ -153,14 +150,6 @@ export class ZeroRuntime implements Zero
if (typeof schema === 'function')
{
const res = await schema();
if (typeof res.default === 'function')
{
const editor = createEditor();
res.default(editor);
return editor;
}
return res.default;
}
@@ -1,36 +0,0 @@
import { EditorField, EditorFieldConfiguration } from "zero/editor";
import { EditorFieldBase, createFieldProxy } from "./editorField";
export class Editor
{
fields: EditorField[];
constructor()
{
this.fields = [];
}
field(alias: string, config?: EditorFieldConfiguration): EditorField
{
const field = new EditorFieldBase(alias, config);
const proxy = createFieldProxy(field);
this.fields.push(proxy);
return proxy;
}
}
//export interface Editor
//{
// field(alias: string, config?: EditorFieldConfiguration): EditorField;
//}
export declare type EditorBuilderExpression = (editor: Editor) => void;
export function createEditor(): Editor
{
return new Editor();
}
@@ -1,109 +0,0 @@
import { Component, defineAsyncComponent } from 'vue';
import { EditorField, EditorFieldConfiguration, TextFieldOptions, EditorFieldDefinition } from 'zero/editor';
export const createFieldProxy = (field: EditorFieldBase) => new Proxy(field, {
get: function (target: EditorFieldBase, prop: string | symbol, receiver: any): any
{
// handle internals
if (prop in target)
{
return target[prop];
}
// handle dynamic fields + extensions
return (...args) =>
{
target.custom(prop, args);
return target;
};
}
})// as EditorField;
export function createFieldConfiguration(): EditorFieldConfiguration
{
return {
optional: false,
readonly: false,
hidden: false,
label: null,
hideLabel: false,
description: null,
helpText: null,
classes: null,
horizontal: false
} as EditorFieldConfiguration;
}
export class EditorFieldBase
{
path: string;
configuration: EditorFieldConfiguration;
fieldType?: string;
options?: any;
constructor(path: string, config?: EditorFieldConfiguration)
{
this.path = path;
this.configuration = config || createFieldConfiguration();
}
/**
* Set this field as optional
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
*/
setOptional(condition: Function | boolean): EditorField
{
this.configuration.required = condition;
return this;
}
/**
* Whether the input next to the headline or below
* @param {boolean} isHorizontal
* @returns {EditorField}
*/
setHorizontal(isHorizontal: boolean): EditorField
{
this.configuration.horizontal = !isHorizontal;
return this;
}
/**
* Set this field to disabled
*/
setReadonly(condition: Function | boolean): EditorField
{
this.configuration.readonly = condition;
return this;
}
/**
* Conditionally hide this field
* @param {function} condition - function which returns a boolean and gets passed the current model
*/
setHidden(condition: Function | boolean): EditorField
{
this.configuration.hidden = condition;
return this;
}
/**
* Set a custom component for a field
* @param {Component} component - The component to render (can be an async component too)
* @param {T} [options] = Custom options to pass to this editor
*/
custom<T>(fieldType: string, options?: T): EditorField
{
this.fieldType = fieldType;
this.options = options;
return this;
}
}
-89
View File
@@ -1,89 +0,0 @@
import { Component } from 'vue';
declare module 'zero/editor'
{
export interface EditorFieldConfiguration
{
/**
* Whether this field is optional or required (additional validation is done on the server)
*/
optional?: Function | boolean;
/**
* Whether this field is readonly and can't be changed
*/
readonly?: Function | boolean;
/**
* Conditionally hide the field
*/
hidden?: Function | boolean;
/**
* A custom label for this field (otherwise it's generated via `onLabelCreate`)
**/
label?: string | null,
/**
* Hide the field label
**/
hideLabel?: boolean,
/**
* A custom description for this field (otherwise it's generated via `onDescriptionCreate`)
**/
description?: string | null,
/**
* Display a help text below the field
**/
helpText?: string | null,
/**
* Append HTML class to the generated property
**/
classes?: string | null,
/**
* Whether to render the label next to the input
**/
horizontal?: boolean
}
export interface EditorFieldDefinition<T>
{
component: Component;
options?: T;
// return this._setComponent(() => import('./fields/text.vue'), { maxLength, placeholder });
//this._component = component;
//this._componentOptions = options || {};
}
export interface EditorField
{
/**
* Set this field as optional
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
*/
setOptional(condition: Function | boolean): EditorField;
/**
* Whether the input next to the headline or below
* @param {boolean} isHorizontal
* @returns {EditorField}
*/
setHorizontal(isHorizontal: boolean): EditorField;
/**
* Set this field to disabled
*/
setReadonly(condition: Function | boolean): EditorField;
/**
* Conditionally hide this field
* @param {function} condition - function which returns a boolean and gets passed the current model
*/
setHidden(condition: Function | boolean): EditorField;
/**
* Set a custom component for a field
* @param {Component} component - The component to render (can be an async component too)
* @param {T} [options] = Custom options to pass to this editor
*/
custom<T>(component: Component, options?: T): EditorField;
}
}
-34
View File
@@ -1,34 +0,0 @@
declare module 'zero/editor'
{
//export interface FieldSupportsMaxLength
//{
// maxLength?: number;
//}
//export interface FieldSupportsPlaceholder
//{
// placeholder?: string | null;
//}
//export type TextFieldOptions = FieldSupportsMaxLength | FieldSupportsPlaceholder;
//export type NumberFieldOptions = FieldSupportsMaxLength | FieldSupportsPlaceholder;
//export interface EditorField
//{
// /**
// * Set this field as required
// * @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
// */
// text(options?: TextFieldOptions): EditorField;
// /**
// * Set this field as required
// * @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
// */
// number(options?: NumberFieldOptions): EditorField;
//}
}
@@ -1,32 +0,0 @@
<template>
<input :value="value" @input="$emit('input', $event.target.value)" type="text" class="ui-input" v-placeholder="{ placeholder: options.placeholder, model: entity }" :maxlength="options.maxLength" :disabled="disabled" />
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { TextFieldOptions } from 'zero/editor';
export default defineComponent({
props: {
value: {
type: String,
default: null
},
maxLength: {
type: Number,
default: null
},
disabled: {
type: Boolean,
default: false
},
placeholder: {
type: [String, Function],
default: null
},
entity: Object,
options: Object as PropType<TextFieldOptions>
}
});
</script>
@@ -1,11 +0,0 @@
import { defineAsyncComponent } from 'vue';
import { ZeroPlugin, ZeroPluginOptions } from '../../core';
export default {
name: "zero.editor",
install(app: ZeroPluginOptions)
{
app.fieldType('text', defineAsyncComponent(() => import('./fields/text.vue')));
}
} as ZeroPlugin;
-120
View File
@@ -1,120 +0,0 @@
import { Component, ComponentPropsOptions } from 'vue';
import { proxy, fieldTypes } from '../editorFieldProxy';
import TestCmp from './_testcmp.vue';
export interface ZeroEditor
{
}
export interface ZeroFieldType
{
component: Promise<Component>;
options: any;
}
//const testField = {
// component: () => import('./_testcmp.vue'),
// options: {
// maxLength: {
// type: Number,
// default: null
// },
// placeholder: {
// type: [String, Function],
// default: null
// }
// }
//} as ZeroFieldType;
export class ZeroEditorCanvasBase<TModel>
{
field<TField>(path: string, component: Component): ZeroEditorField<TModel, TField>
{
return new ZeroEditorField(path, component);
}
}
export class ZeroEditorCanvasBaseWithFieldset<TModel> extends ZeroEditorCanvasBase<TModel>
{
fieldset(): ZeroEditorCanvasBase<TModel>
{
return new ZeroEditorCanvasBase();
}
}
export class ZeroEditorCanvas<TModel> extends ZeroEditorCanvasBaseWithFieldset<TModel>
{
tab(alias: string, name: string): ZeroEditorTab<TModel>
{
return new ZeroEditorTab(alias, name);
}
}
export class ZeroEditorTab<TModel> extends ZeroEditorCanvasBaseWithFieldset <TModel>
{
alias: string;
name: string;
constructor(alias: string, name: string)
{
super();
this.alias = alias;
this.name = name;
}
}
export declare type BooleanExpression<TModel, TField> = (model: TModel, value: TField) => boolean;
export class ZeroEditorField<TModel, TField>
{
path: string;
component: Component;
constructor(path: string, component: Component)
{
this.path = path;
this.component = component;
}
when(condition: BooleanExpression<TModel, TField>): ZeroEditorField<TModel, TField>
{
return this;
}
required(required: boolean | BooleanExpression<TModel, TField>): ZeroEditorField<TModel, TField>
{
return this;
}
configure(opts: ComponentPropsOptions)
{
}
}
export function test()
{
fieldTypes.text = (maxLength?: number, placeholder?: string | Function) => console.log(`text() called with maxLength: ${maxLength}, placeholder: ${placeholder}`);
proxy.text(17, 'Enter your text...');
//var editor = new ZeroEditorCanvas<any>();
//editor.field('hi', TestCmp)
//console.info(TestCmp.);
}
+1 -1
View File
@@ -59,7 +59,7 @@ export interface ZeroCompiledEditorField
export function compileField(zero: Zero, editor: ZeroEditor, field: ZeroEditorField): ZeroCompiledEditorField | undefined
{
const component = zero.getFieldTypeComponent(field.fieldType);
const component = field.customComponent || zero.getFieldTypeComponent(field.fieldType);
if (!component)
{
+12 -9
View File
@@ -1,6 +1,6 @@
import { ZeroEditorField } from "zero/schemas";
import { Component } from "vue";
import { ZeroEditorField } from "zero/schemas";
import { extendObject } from '../utils/objects';
import { ZeroFieldType } from "./_new/types";
export declare type BooleanExpression = (model: any, value: any) => boolean;
@@ -10,14 +10,16 @@ export const createFieldProxy = (field: ZeroEditorFieldImpl) => new Proxy(field,
{
// handle internals
if (prop in target)
{
return target[prop]; // @tsignore
{
// @ts-ignore
return target[prop];
}
// handle dynamic fields + extensions
return (args) =>
return (args: any) =>
{
target.custom(prop, args);
target.fieldType = prop;
target.options = args;
return target;
};
}
@@ -29,8 +31,9 @@ export class ZeroEditorFieldImpl implements ZeroEditorField
{
path: string;
configuration: ZeroEditorFieldConfiguration;
fieldType: string;
fieldType: string | symbol;
options?: any;
customComponent?: Component;
constructor(path: string, config?: ZeroEditorFieldConfiguration)
{
@@ -87,9 +90,9 @@ export class ZeroEditorFieldImpl implements ZeroEditorField
* @param {Component} component - The component to render (can be an async component too)
* @param {T} [options] = Custom options to pass to this editor
*/
custom<T>(fieldType: string, options?: T): ZeroEditorField
component(component: Component, options?: any): ZeroEditorField
{
this.fieldType = fieldType;
this.customComponent = component;
this.options = options;
return this;
}
+8 -14
View File
@@ -1,3 +1,4 @@
import { Component } from "vue";
import { ZeroEditorFieldConfiguration } from "./editor-field";
declare module 'zero/schemas'
@@ -33,13 +34,18 @@ declare module 'zero/schemas'
/**
* Type of the field which has to be registered in the zero runtime
*/
fieldType: string;
fieldType: string | symbol;
/**
* Custom options which are passed to the component
*/
options?: any;
/**
* Set a custom render component for this field
*/
customComponent?: Component;
/**
* Set this field as optional
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
@@ -69,18 +75,6 @@ declare module 'zero/schemas'
* @param {Component} component - The component to render (can be an async component too)
* @param {T} [options] = Custom options to pass to this editor
*/
custom<T>(component: Component, options?: T): ZeroEditorField;
/**
* Set this field as required
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
*/
//text(options?: TextFieldOptions): EditorField;
///**
// * Set this field as required
// * @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
// */
//number(options?: NumberFieldOptions): EditorField;
component(component: Component, options?: any): ZeroEditorField;
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { ZeroEditorField } from "zero/schemas";
import { ZeroEditorField, ZeroSchema } from "zero/schemas";
import { ZeroEditorCanvas } from "./editor-canvas";
export class ZeroEditor extends ZeroEditorCanvas
export class ZeroEditor extends ZeroEditorCanvas implements ZeroSchema
{
alias: string;
@@ -1,323 +0,0 @@
<template>
<div class="blueprint" v-if="value && (isParent || isChild)">
<div class="blueprint-box">
<template v-if="isChild">
<div class="blueprint-inner">
<ui-icon symbol="fth-cloud" :size="22" />
<p v-localize:html="'@blueprint.hint.childText'"></p>
</div>
<aside>
<ui-button class="blueprint-button-settings" type="blank small" icon="fth-settings"
:title="{ key: value.blueprint.desync.length > 0 ? '@blueprint.hint.xUnlocked' : '@blueprint.hint.settingsButton', tokens: { count: value.blueprint.desync.length }}"
@click="openSettings" />
<router-link replace :to="switchLink" class="ui-button type-light type-small" v-localize="'@blueprint.hint.goToBlueprint'"></router-link>
</aside>
</template>
<template v-if="isParent">
<div class="blueprint-inner">
<ui-icon symbol="fth-cloud" :size="22" />
<p v-localize:html="value.id ? '@blueprint.hint.parentText' : '@blueprint.hint.parentCreateText'"></p>
</div>
<aside v-if="value.id">
<router-link replace :to="switchLink" class="ui-button type-light type-small" v-localize="'@blueprint.hint.goToChild'"></router-link>
</aside>
</template>
</div>
</div>
</template>
<script>
import * as overlays from '../../services/overlay';
export default {
props: {
value: {
type: Object
},
meta: {
type: Object,
default: () => { }
},
config: {
type: Object,
default: () => { }
},
disabled: {
type: Boolean,
default: false
}
},
inject: ['editor'],
watch: {
'$route': function ()
{
this.bind();
}
},
computed: {
isParent()
{
return this.config.isBlueprintParent(this.$route, this.value);
},
isChild()
{
return this.config.isBlueprintChild(this.$route, this.value);
},
switchLink()
{
return {
name: this.$route.name,
params: this.$route.params,
query: {
...(this.$route.query || {}),
scope: !this.isChild ? undefined : 'shared'
}
};
}
},
mounted()
{
this.bind();
},
methods: {
async openSettings()
{
const editor = typeof this.editor === 'string' ? this.zero.getSchema(this.editor) : this.editor;
const result = await overlays.open({
component: () => import('./settings.vue'),
display: 'editor',
model: {
value: this.value,
blueprintConfig: this.config
}
});
console.info(result);
return;
this.value.blueprint = res.blueprint;
this.$emit('input', res.blueprint);
if (typeof res.update === 'function')
{
res.update(this.value);
}
//EventHub.$emit('page.update');
},
bind()
{
const form = this.getForm();
const onLoaded = () =>
{
this.$nextTick(() =>
{
this.setupSync(form);
});
};
if (form.loadingState === 'default')
{
onLoaded();
}
else
{
form.$on('loaded', onLoaded);
}
},
setupSync(form)
{
const meta = form.$parent.meta;
if (meta)
{
meta.canDelete = this.isBlueprint;
meta.canEdit = this.isBlueprint;
}
if (this.hasBlueprint)
{
const properties = this.getProperties(form);
properties.forEach(property =>
{
if (property.config.path !== 'blueprint')
{
//property.setBlock(BlueprintBlockComponent);
//property.setDisabled(true);
}
//property.$el.classList.add('is-property-locked');
//property.setLocked(true);
});
}
},
getForm()
{
let component = this.$parent;
do
{
if (component.$options.name === 'uiForm')
{
return component;
}
}
while (component = component.$parent);
return null;
},
// find all form properties
getProperties(form)
{
let find = 'uiEditorComponent';
let components = [];
// find components which can output errors
let traverseChildren = (parent) =>
{
parent.$children.forEach(component =>
{
if (component.$options.name === find)
{
components.push(component);
}
else
{
traverseChildren(component);
}
});
};
traverseChildren(form);
return components;
}
}
}
</script>
<style lang="scss">
.ui-property.is-property-locked
{
pointer-events: none;
opacity: .8;
}
/*.language .ui-property.has-block:after,
.mails .ui-property.has-block:after
{
position: absolute;
left: -13px;
top: -7px;
display: inline-flex;
justify-content: center;
align-items: center;
width: 32px;
height: 32px;
border-radius: 16px;
background: var(--color-box);
content: "\e887";
font-family: 'Feather';
font-size: 15px;
font-weight: 400;
color: var(--color-text-dim);
}*/
.blueprint
{
position: relative;
margin: 0 -32px 0;
padding: 0 32px 0;
margin-bottom: 30px;
//background: var(--color-box-nested);
border-top-left-radius: var(--radius);
border-top-right-radius: var(--radius);
aside
{
display: flex;
}
&.is-shared
{
//border-bottom: 1px dotted var(--color-accent-error);
}
}
.blueprint-box
{
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--padding-s);
padding-left: var(--padding-m);
border-radius: var(--radius);
border: 1px dashed var(--color-line-dashed);
//background: repeating-linear-gradient(-45deg, transparent, transparent 2px, var(--color-bg-shade-2) 2px, var(--color-bg-shade-2) 4px);
}
.blueprint-button-settings
{
margin-right: -5px;
}
.blueprint-inner
{
display: grid;
grid-template-columns: auto minmax(0, 1fr);
grid-gap: var(--padding-m);
align-items: center;
font-size: var(--font-size);
line-height: 1.4;
position: relative;
padding-right: var(--padding-s);
p
{
margin: 0;
color: var(--color-text);
}
.ui-icon
{
color: var(--color-accent);
margin-top: -2px;
}
}
</style>
<!--<button type="button" class="ui-property-lock" v-if="locked" > <i class="fth-lock" > </i > </button >
/*.ui-property.is-locked
{
}*/
.ui-property-lock
{
display: inline-flex;
width: 20px;
height: 20px;
border-radius: 10px;
background: var(--color-button-light);
color: var(--color-text);
justify-content: center;
align-items: center;
font-size: 10px;
position: relative;
top: -1px;
margin-right: 8px;
}-->
@@ -1,224 +0,0 @@
<template>
<ui-trinity class="blueprint-settings">
<template v-slot:header>
<ui-header-bar title="Synchronisation" :back-button="false" :close-button="true" />
</template>
<template v-slot:footer>
<ui-button type="light onbg" label="@ui.close" @click="config.close" />
<ui-button type="primary" label="@ui.confirm" @click="onSave" :state="state" />
</template>
<p class="blueprint-settings-text">By default all properties of your entity are synced with its blueprint.<br />You can disable synchronisation per property so it won't be overridden on changes.</p>
<div class="ui-box" v-if="loaded">
<ui-property class="blueprint-settings-tableheader" :key="-1" label="Property" :vertical="false">
<b>Synchronized</b>
</ui-property>
<template v-for="(field, index) in items">
<ui-property v-if="!field.disabled" :key="index" :label="field.label" :description="field.description" :vertical="false" :class="{'not-synced': !field.synced}">
<ui-toggle v-model:on="field.synced" />
</ui-property>
</template>
</div>
</ui-trinity>
</template>
<script>
import { arrayRemove } from '../../utils/arrays';
//import Localization from 'zero/helpers/localization.js';
//import BlueprintApi from 'zero/api/blueprint.js';
export default {
props: {
model: Object,
config: Object
},
data: () => ({
state: 'default',
loaded: false,
editor: null,
items: []
}),
mounted()
{
this.model.blueprintConfig.fields.forEach(field =>
{
let item = JSON.parse(JSON.stringify(field));
item.synced = field.synced(this.model.value);
this.items.push(item);
});
this.loaded = true;
},
methods: {
onSave()
{
this.state = 'loading';
let desync = JSON.parse(JSON.stringify(this.model.value.blueprint.desync));
let resync = [];
this.items.forEach(field =>
{
let desynced = this.model.value.blueprint.desync.indexOf(field.path) > -1;
if (field.synced && desynced)
{
arrayRemove(desync, field.path);
resync.push(field.path);
}
else if (!field.synced && !desynced)
{
desync.push(field.path);
}
});
this.model.value.blueprint.desync = desync;
// we need to revert changed values which were switch backed to synchronised state
// to do this we load the blueprint entity and its copy properties
if (resync.length > 0)
{
BlueprintApi.getById(this.model.value.blueprint.id).then(blueprint =>
{
this.config.confirm({
blueprint: this.model.value.blueprint,
update: entity =>
{
resync.forEach(path =>
{
entity[path] = blueprint[path]; // TODO does not work for nested paths
});
}
});
});
}
else
{
//this.state = 'success';
this.config.confirm({
blueprint: this.model.value.blueprint
});
}
},
//rebuildModel()
//{
// this.selector = Strings.selectorToArray(this.config.path);
// let currentValue = this.value;
// let found = false;
// if (!this.selector || !this.selector.length || !currentValue)
// {
// found = true;
// this.model = null;
// }
// else
// {
// for (var key of this.selector)
// {
// if (key in currentValue)
// {
// found = true;
// currentValue = currentValue[key];
// }
// else
// {
// break;
// }
// }
// this.model = found ? currentValue : null;
// }
//},
}
}
</script>
<style lang="scss">
.blueprint-settings-text
{
margin: 0 0 var(--padding);
line-height: 1.5;
}
.blueprint-settings-headline
{
margin: 0 0 var(--padding) !important;
}
.blueprint-settings .ui-property
{
display: flex;
justify-content: space-between;
}
.blueprint-settings .blueprint-settings-tableheader
{
border-bottom: 1px dashed var(--color-line-dashed);
padding-bottom: 20px;
margin-bottom: 26px;
b
{
display: inline-block;
margin-top: 3px;
}
}
.blueprint-settings .ui-property + .ui-property
{
margin-top: var(--padding-s);
padding-top: 0;
border-top: none;
}
.blueprint-settings .ui-property-content
{
display: inline;
flex: 0 0 auto;
}
.blueprint-settings .ui-property-label
{
padding-top: 1px;
}
.blueprint-settings .ui-property.not-synced .ui-property-label
{
font-weight: 400;
color: var(--color-text-dim);
}
.blueprint-settings-lock
{
color: var(--color-text-dim);
}
/*.blueprint-settings .ui-property.not-synced .ui-property-label:before
{
content: "\e929";
font-family: 'Feather';
margin-right: 0.8em;
color: var(--color-text-dim);
font-weight: 400;
}
.blueprint-settings .ui-property:not(.not-synced) .ui-property-label:before
{
content: "\e8f8";
font-family: 'Feather';
margin-right: 0.8em;
color: var(--color-primary);
font-weight: 400;
}*/
</style>
@@ -1,15 +0,0 @@
export interface EditorFieldMeta
{
canEdit: boolean;
}
export interface EditorFieldProps<T>
{
value: T;
model: any;
meta: EditorFieldMeta;
disabled: boolean;
system: boolean;
}
@@ -1,611 +0,0 @@
class EditorField
{
path = null;
options = {
label: null,
hideLabel: false,
description: null,
helpText: null,
condition: null,
disabled: false,
tab: null,
allTabs: false,
vertical: true,
coreDatabase: false,
fieldset: null,
fieldsetColumns: null,
class: '',
onChange: null
};
_preview = {
icon: 'fth-filter',
preview: x => x,
hasValue: x => !!x
};
_component = null;
_componentOptions = {};
_required = false;
_isReadOnly = false;
constructor(path, options)
{
this.path = path;
this.options = { ...this.options, ...options };
}
get component()
{
return this._component;
}
get componentOptions()
{
return this._componentOptions;
}
get isRequired()
{
return this._required;
}
get previewOptions()
{
return this._preview;
}
/**
* Set another editor field as the base for this editor (copies properties)
* @param {EditorField} field - Base editor field
* @returns {EditorField}
*/
setBase(field)
{
this.path = field.path;
this.options = { ...field.options };
this._preview = { ...field.previewOptions };
this._component = field.component;
this._componentOptions = field.componentOptions;
this._required = field.isRequired;
return this;
}
/**
*
*/
_setComponent(component, options?)
{
this._component = component;
this._componentOptions = options || {};
return this;
}
/**
* Sets the column count for this field, only available within a an editor fieldset
* @param {number} columnCount - Column count between 1 and 12
* @returns {EditorField}
*/
cols(columnCount)
{
this.options.fieldsetColumns = columnCount < 1 ? 1 : (columnCount > 12 ? 12 : columnCount);
return this;
}
/**
* Whether the input is below the headline or next to it
* @param {boolean} isVertical
* @returns {EditorField}
*/
vertical(isVertical)
{
this.options.vertical = isVertical;
return this;
}
/**
* Set this field to disabled
*/
disabled()
{
this.options.disabled = true;
return this;
}
/**
* Set this field as required
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
*/
required(condition)
{
if (typeof condition === 'function')
{
this._required = condition;
}
else if (typeof condition === 'boolean')
{
this._required = condition;
}
else
{
this._required = true;
}
return this;
}
/**
* Conditionally render this field (this is an alternative method to the field options 'condition')
* @param {function} condition - function which returns a boolean and gets passed the current model
*/
when(condition)
{
this.options.condition = condition;
return this;
}
/**
* The expression argument is called when the value of the field changes
* @param {function} expression - function which is called
*/
onChange(expression)
{
this.options.onChange = expression;
return this;
}
/**
* Create a preview for this field
* This is only used in list filters, ...
* @param {object} options - Custom options
* @param {string} options.icon - Custom icon
* @param {string|function} options.preview - Render the preview when this filter has been filled out
* @param {boolean} options.hasValue - Determine if the filter has a value or not
* @returns {EditorField}
*/
preview(options)
{
this._preview = { ...this.preview, ...options };
return this;
}
/**
* Render a custom component
* @param {object} component - The custom vue component
* @param {object} [options] - Custom options
* @returns {EditorField}
*/
custom(component, options)
{
return this._setComponent(component, { ...options });
}
/**
* Render a text input field
* @param {number} [maxLength] - Maximum length of the input
* @param {string|function} [placeholder] - Placeholder text (can be a translation) or function
* @returns {EditorField}
*/
text(maxLength, placeholder)
{
return this._setComponent(() => import('./fields/text.vue'), { maxLength, placeholder });
}
/**
* Render a password input field
* @param {number} [maxLength] - Maximum length of the input
* @param {string|function} [placeholder] - Placeholder text (can be a translation) or function
* @returns {EditorField}
*/
password(maxLength, placeholder)
{
return this._setComponent(() => import('./fields/password.vue'), { maxLength, placeholder });
}
/**
* Render a password hash field
* @param {number} [maxLength] - Maximum length of the password
* @returns {EditorField}
*/
passwordHash(maxLength)
{
return this._setComponent(() => import('./fields/password-hash.vue'), { maxLength });
}
/**
* Render a currency input field
* @param {string|function} [placeholder] - Placeholder text (can be a translation) or function
* @returns {EditorField}
*/
currency(placeholder)
{
return this._setComponent(() => import('./fields/currency.vue'), { placeholder });
}
/**
* Render a number input field
* @param {number} [maxLength] - Maximum length of the input
* @param {string|function} [placeholder] - Placeholder text (can be a translation) or function
* @returns {EditorField}
*/
number(maxLength, placeholder)
{
return this._setComponent(() => import('./fields/number.vue'), { maxLength, placeholder });
}
/**
* Render a rich-text editor field
* @param {object} [options] - Custom options
* @param {number} [options.maxLength=null] - Maximum characters
* @param {string} [options.placeholder=null] - Placeholder text (can be a translation) or function
* @param {function} [options.setup=value] - Called on RTE setup
* @returns {EditorField}
*/
rte(options)
{
return this._setComponent(() => import('./fields/rte.vue'), { ...options });
}
/**
* @typedef {object} EditorSelectItem
* @param {object} key - Key/Id of the item
* @param {string} value - Label/Value of the item (can be a translation)
*/
/**
* Render a select dropdown with the specified items
* @param {EditorSelectItem[]|function} items - Set items to pick from
* @param {object} [options] - Custom options
* @param {number} [options.emptyOption=false] - Adds an empty option so the field can be blank
* @returns {EditorField}
*/
select(items, options)
{
return this._setComponent(() => import('./fields/select.vue'), { items, ...options });
}
/**
* Render a text area
* @param {number} [maxLength] - Maximum length of the input
* @returns {EditorField}
*/
textarea(maxLength)
{
return this._setComponent(() => import('./fields/textarea.vue'), { maxLength });
}
/**
* Render a toggle
* @param {boolean} [negative] - Toggle with a negative color / red background
* @param {object} [options] - Custom options
* @param {number} [options.negative=false] - Display the toggle in red when set to TRUE
* @param {number} [options.onContent=null] - Text next to toggle when it is set to TRUE
* @param {number} [options.offContent=null] - Text next to toggle when it is set to FALSE
* @returns {EditorField}
*/
toggle(options)
{
this.options.vertical = false;
return this._setComponent(() => import('./fields/toggle.vue'), { ...options });
}
/**
* Renders the field value
* @param {function} [render] - Render the output based on the given function
* @returns {EditorField}
*/
output(render)
{
this._isReadOnly = true;
return this._setComponent(() => import('./fields/output.vue'), { render });
}
/**
* Renders an input which generates an alias for a given name or an alternative custom alias
* @param {string} [namePath] - Optional path to the name value which is used to auto-generate the alias
* @returns {EditorField}
*/
alias(namePath)
{
return this._setComponent(() => import('./fields/alias.vue'), { namePath });
}
/**
* Renders an input which generates an alias for a given name or an alternative custom alias
* @param {EditorSelectItem[]|function} items - Set items to choose from, either via an array or a promise which returns such array
* @param {object} [options] - Custom options
* @param {number} [options.limit=100] - Maximum items to be checked
* @param {boolean} [options.reverse=false] - Reverse the checklist behaviour, so all items are checked by default and unchecking them adds them to the result list
* @param {string} [options.labelKey=value] - Object key to get the label
* @param {string} [options.idKey=key] - Object key to get the id
* @returns {EditorField}
*/
checkList(items, options)
{
return this._setComponent(() => import('./fields/checklist.vue'), { items, ...options });
}
/**
* Renders a HEX color picker
* @returns {EditorField}
*/
colorPicker()
{
return this._setComponent(() => import('./fields/colorpicker.vue'));
}
/**
* Renders a country picker
* @param {number} [limit=1] - Maximum items to be selected
* @returns {EditorField}
*/
countryPicker(limit)
{
return this._setComponent(() => import('./fields/countrypicker.vue'), { limit });
}
/**
* Renders a space picker
* @param {number} [limit=1] - Maximum items to be selected
* @returns {EditorField}
*/
spacePicker(limit)
{
return this._setComponent(() => import('./fields/spacepicker.vue'), { limit });
}
/**
* Renders a culture picker
* @returns {EditorField}
*/
culturePicker()
{
return this._setComponent(() => import('./fields/culturepicker.vue'));
}
/**
* Renders a mail template picker
* @param {number} [limit=1] - Maximum items to be selected
* @returns {EditorField}
*/
mailTemplatePicker(limit)
{
return this._setComponent(() => import('./fields/mailtemplatepicker.vue'), { limit });
}
/**
* Renders a date picker
* @param {object} [options] - Custom options
* @param {string} [options.format] - Format the date output
* @param {boolean} [options.time=false] - Allow time input
* @param {string|Date} [options.maxDate] - Maximum selectable date
* @param {string|Date} [options.minDate] - Minimum selectable date
* @param {string} [options.amPm] - Render time as AM/PM
* @returns {EditorField}
*/
datePicker(options)
{
return this._setComponent(() => import('./fields/datepicker.vue'), { ...options });
}
/**
* Renders a date range picker
* @param {object} [options] - Custom options
* @param {string} [options.format] - Format the date output
* @param {boolean} [options.time=false] - Allow time input
* @param {string|Date} [options.maxDate] - Maximum selectable date
* @param {string|Date} [options.minDate] - Minimum selectable date
* @param {string} [options.fromLabel] - Label next to the "from" date input
* @param {string} [options.toLabel] - Label next to the "to" date input
* @param {string} [options.amPm] - Render time as AM/PM
* @param {string} [options.inline] - Don't render the range picker on an overlay
* @returns {EditorField}
*/
dateRangePicker(options)
{
return this._setComponent(() => import('./fields/daterangepicker.vue'), { ...options });
}
/**
* Pick an icon from the specified icon collection
* @param {string} [iconSetAlias] - Custom icon set alias (defined in ZeroOptions.Icons)
* @returns {EditorField}
*/
iconPicker(iconSetAlias)
{
return this._setComponent(() => import('./fields/iconPicker.vue'), { set: iconSetAlias });
}
/**
* Renders a page picker
* @param {object} [options] - Custom options
* @param {number} [options.limit=1] - Limit of selection
* @returns {EditorField}
*/
pagePicker(options)
{
return this._setComponent(() => import('./fields/pagepicker.vue'), { ...options });
}
/**
* Render a link picker
* @param {object} [options] - Custom options
* @param {number} [options.limit=1] - Limit of selection
* @param {boolean} [options.title=true] - Allow input of custom link title
* @param {boolean} [options.target=true] - Allow selection of the link target
* @param {boolean} [options.label=false] - Allow input of a custom label for button generation
* @param {boolean} [options.suffix=false] - Allow input of custom link URL suffix (query or hash)
* @param {string[]} [options.areas] - Limit link areas to the specified values (built-in are zero.pages, zero.media and zero.url)
* @returns {EditorField}
*/
linkPicker(options)
{
return this._setComponent(() => import('./fields/linkpicker.vue'), { ...options });
}
/**
* Create a list of strings
* @param {number} [limit=10] - Limit the inputs
* @param {number} [maxItemLength=200] - Maximum length for an item input
* @param {string} [addLabel] - Label for the add button
* @returns {EditorField}
*/
inputList(limit, maxItemLength, addLabel)
{
return this._setComponent(() => import('./fields/inputlist.vue'), { limit, maxItemLength, addLabel });
}
/**
* Append tags to an entity
* @param {number} [limit=10] - Limit the tags
* @param {number} [maxItemLength=200] - Maximum length for a tag
* @returns {EditorField}
*/
tags(limit, maxItemLength)
{
return this._setComponent(() => import('./fields/tags.vue'), { limit, maxItemLength });
}
/**
* Pick a language
* @returns {EditorField}
*/
languagePicker()
{
return this._setComponent(() => import('./fields/language.vue'));
}
/**
* Display a module renderer which allows you to select from defined modules
* @param {string[]} [tags] - Only allow selection of modules which match defined tags
* @returns {EditorField}
*/
modules(tags)
{
return this._setComponent(() => import('./fields/modules.vue'), { tags });
}
/**
* Display a module renderer which allows you to select from defined modules
* @param {Editor} [editor] - Use the specified editor for each item
* @param {object} [options] - Custom options
* @param {number} [options.limit=100] - Limit the creation of items
* @param {string} [options.title] - Headline in the editor overlay
* @param {string} [options.addLabel] - Label for the add button
* @param {function} [options.itemLabel] - Function which generates the label for the current item
* @param {function} [options.itemDescription] - Function which generates the description for the current item
* @param {string|function} [options.itemIcon] - Static icon or function which generates the icon for the current item
* @param {object} [options.template] - Template which is used when adding an item
* @param {object} [options.width=820] - Width of the overlay panel
* @returns {EditorField}
*/
nested(editor, options)
{
return this._setComponent(() => import('./fields/nested.vue'), { editor, ...options });
}
/**
* Render a select as a button group
* @param {EditorSelectItem[]} items - Set items to choose from
* @returns {EditorField}
*/
state(items)
{
return this._setComponent(() => import('./fields/state.vue'), { items });
}
/**
* Render a media upload + picker
* @param {object} [options] - Custom options
* @param {number} [options.limit=1] - Limit the media select count
* @param {boolean} [options.disallowSelect=false] - Disallow the selection (only upload) of media items
* @param {boolean} [options.disallowUpload=false] - Disallow upload (only selection) of media items
* @param {string[]} [options.fileExtensions] - Allow upload + selection only for the specified file extensions
* @param {number} [options.maxFileSize=10] - Maximum allowed file size for uploads in Mibibytes
* @returns {EditorField}
*/
media(options)
{
return this._setComponent(() => import('./fields/media.vue'), { ...options });
}
/**
* Render a media upload + picker
* @param {object} [options] - Custom options
* @param {number} [options.limit=1] - Limit the media select count
* @param {boolean} [options.disallowSelect=false] - Disallow the selection (only upload) of media items
* @param {boolean} [options.disallowUpload=false] - Disallow upload (only selection) of media items
* @param {string[]} [options.fileExtensions] - Allow upload + selection only for the specified file extensions
* @param {number} [options.maxFileSize=10] - Maximum allowed file size for uploads in Mibibytes
* @returns {EditorField}
*/
image(options)
{
return this._setComponent(() => import('./fields/media.vue'), { ...options, fileExtensions: ['.jpg', '.jpeg', '.png', '.webp', '.svg'] });
}
/**
* Render a video (YouTube/vimeo) picker
* @param {number} [limit=1] - Limit the videos
* @returns {EditorField}
*/
video(limit)
{
return this._setComponent(() => import('./fields/video.vue'), { limit });
}
}
export default EditorField;
@@ -1,360 +0,0 @@
import { generateId } from '../utils/numbers';
import EditorField from './editor-field';
class Editor
{
_alias;
_prefix;
_preview = {
icon: null,
template: null,
hideLabel: false
};
/**
* Overrides the string generation for the label
*/
templateLabel = field => this._prefix + field;
/**
* Overrides the string generation for the description
*/
templateDescription = field => this._prefix + field + '_text';
tabs = [];
fields = [];
blueprintAlias?: string;
options = {
disabled: false,
display: 'tabs',
coreDatabase: false
};
constructor(prefix)
{
this._alias = null;
this._prefix = prefix || '';
}
get alias()
{
return this._alias;
}
get previewOptions()
{
return this._preview;
}
/**
* A tab within an editor
* @typedef {object} EditorTab
* @param {string} alias - Alias for the tab
* @param {string} name - Name of the tab (can be a translation)
* @param {number|function} [count] - Output a count indicator
* @param {boolean|function} [disabled] - Conditionally disable the tab and its content
*/
/**
* Add a new tab to the editor or returns the tab in case it was already added
* @param {string} alias - Alias for the tab
* @param {string} name - Name of the tab (can be a translation)
* @param {number|function} [count] - Output a count indicator
* @param {boolean|function} [disabled] - Conditionally disable the tab and its content
* @param {string} [classes] - Append HTML class to the generated tab
* @param {object} [component] - Render a custom vue component instead of editor fields
* @returns {EditorTab}
*/
tab(alias, name, count, disabled, classes, component)
{
if (typeof disabled !== 'undefined' && disabled != null && typeof disabled !== 'boolean' && typeof disabled !== 'function')
{
console.warn(`[zero] editor.tab: the disabled property has to be of type [boolean, function, undefined]`);
return;
}
if (typeof count !== 'undefined' && count != null && typeof count !== 'number' && typeof count !== 'function')
{
console.warn(`[zero] editor.tab: the count property has to be of type [number, function, undefined]`);
return;
}
let tab = this.tabs.find(x => x.alias === alias);
if (!tab)
{
tab = this._createTab(alias, name, disabled, count, classes, component);
this.tabs.push(tab);
}
return tab;
}
/**
* Add a new field to the editor or returns the field in case it was already added
* @param {string} path - Model path
* @param {object} [options] - Custom options
* @param {string} [options.label] - A custom label for this field (otherwise it's generated via `onLabelCreate`)
* @param {string} [options.hideLabel] - Hide the field label and make the content full-width
* @param {string} [options.description] - A custom description for this field (otherwise it's generated via `onDescriptionCreate`)
* @param {string} [options.helpText] - Display a help text below the field
* @param {boolean|function} [options.condition] - Conditionally hide the field
* @param {boolean|function} [options.disabled=false] - Conditionally disable the field
* @param {boolean} [options.coreDatabase] - Operate on the core database for this field (default is set by Editor.options.coreDatabase)
* @param {string|object} [options.tab] - Add this field to a tab (by passing the alias or the tab instance)
* @param {boolean} [options.allTabs] - Add this field to all defined tabs (could be an information property for instance)
* @param {string} [options.classes] - Append HTML class to the generated property
* @returns {EditorField}
*/
field(path, options)
{
options = options || {};
//let field = this.fields.find(x => x.path === path);
// TODO we need another method to retrieve existing fields
// but we can't do it this way anymore as sometimes a field is defined multiple times with a condition
if (this.tabs.length < 1)
{
this.tab('content', '@ui.tab_content', x => 0, x => false, null, null);
}
//if (!field)
//{
if (typeof options.coreDatabase === 'undefined')
{
options.coreDatabase = this.options.coreDatabase;
}
if (!options.tab)
{
options.tab = 'content';
}
let field = new EditorField(path, options);
this.fields.push(field);
//}
//else
//{
// field.options = { ...field.options, ...options };
//}
return field;
}
/**
* Add a new fieldset to the editor or returns the tab in case it was already added
* A fieldset combines properties in a row (side-by-side)
* @param {function} [configure] - Configures the fieldset
*/
fieldset(configure)
{
let set = this._createFieldset();
configure(set);
}
/**
* Adds an info tab to the editor which outputs data for ZeroEntity.
* This won't work as expected for other entities as they probably do not have required properties defined.
* @returns {EditorTab}
*/
infoTab()
{
return this.tab('infos', '@ui.tab_infos', x => 0, false, 'is-blank', () => import('./editor-infos.vue'));
}
/**
* Conditionally disable this editor
* @param {function|boolean} [condition] - Optionally only disable this editor when a condition is fulfilled or reset the required state with true/false
*/
disabled(condition)
{
this.options.disabled = condition;
return this;
}
/**
* Get fields for the specified tab
* @param {string|EditorTab} [tab] - Pass the tab or its alias
* @returns {EditorField[]}
*/
getFields(tab)
{
const alias = typeof tab === 'undefined' ? null : (typeof tab === 'string' ? tab : tab.alias);
return this.fields.filter(x => !alias || x.options.allTabs ? true : x.options.tab === alias);;
}
/**
* Get fields grouped in fieldsets for the specified tab
* @param {string|EditorTab} [tab] - Pass the tab or its alias
* @returns {EditorField[]}
*/
getFieldsets(tab)
{
let fields = this.getFields(tab);
let currentFieldset = "__undefined";
let fieldsets = [];
fields.forEach(field =>
{
if (field.options.fieldset != currentFieldset || !field.options.fieldset)
{
currentFieldset = field.options.fieldset;
fieldsets.push({
fields: [],
cols: []
});
}
fieldsets[fieldsets.length - 1].fields.push(field);
fieldsets[fieldsets.length - 1].cols.push(field.options.fieldsetColumns);
});
fieldsets.forEach(fieldset =>
{
fieldset.count = fieldset.fields.length;
let reserved = fieldset.cols.reduce((acc, x) => acc + (x || 0), 0);
let rest = reserved < 1 ? 12 : (12 - reserved) % 12;
let columnsToFill = fieldset.cols.filter(x => !x).length;
let perColumn = Math.floor(rest / columnsToFill);
fieldset.fields.forEach(field =>
{
if (!field.options.fieldsetColumns)
{
field.options.fieldsetColumns = perColumn;
}
});
});
return fieldsets;
}
/**
* Removes a field by path name
* @param {string} path - Model path
*/
removeField(path)
{
const field = this.fields.find(x => x.path === path);
if (field != null)
{
const index = this.fields.indexOf(field);
this.fields.splice(index, 1);
}
}
/**
* Removes a tab from the editor
* @param {EditorTab|string} tab - The tab to remove
*/
removeTab(tab)
{
const alias = typeof tab === 'object' ? tab.alias : tab;
const foundTab = this.tabs.find(x => x.alias === alias);
if (foundTab != null)
{
const index = this.tabs.indexOf(foundTab);
this.tabs.splice(index, 1);
this.fields.filter(x => x.tab === alias).forEach(field => this.removeField(field.path));
}
}
/**
* Set another editor as the base for this editor (copies fields and tabs)
* @param {Editor} editor - Base editor
* @returns {Editor}
*/
setBase(editor)
{
this.fields = editor.fields.map(x => new EditorField(x.path).setBase(x));
this.tabs = editor.tabs.map(x => this._createTab(x.alias, x.name, x.disabled, x.count, x.component));
this._preview = { ...editor.previewOptions };
return this;
}
/**
* Create a preview for this editor
* This is primarly used for <ui-modules /> module previews
* @param {string|object} template - A vue template string (`model` is passed as property for the module content) or a custom vue component
* @param {object} [options] - Custom options
* @param {number} [options.icon] - Custom icon
* @param {number} [options.hideLabel=false] - Whether to hide the displayed module-type label in the preview
* @returns {Editor}
*/
preview(template, options)
{
this._preview = { ...this._preview, template, ...options };
return this;
}
/**
* Create a new tab instance
* @returns {EditorTab}
*/
_createTab(alias, name, disabled, count, classes, component)
{
return {
alias,
name,
disabled,
count,
class: classes,
component,
field: (path, options) =>
{
options = options || {};
options.tab = alias;
return this.field(path, options);
},
fieldset: configure =>
{
let set = this._createFieldset(alias);
configure(set);
},
removeField: path => this.removeField(path)
};
}
/**
* Create a new fieldset
*/
_createFieldset(tab)
{
let id = generateId();
return {
id,
field: (path, options) =>
{
options = options || {};
options.fieldset = id;
options.tab = tab;
return this.field(path, options);
},
removeField: path => this.removeField(path)
};
}
};
export default Editor;
@@ -1,11 +0,0 @@
<template>
<hr style="margin: 50px 0;" />
</template>
<script>
export default {
props: {
config: Object
}
}
</script>
@@ -1,5 +0,0 @@
import uiEditor from './editor.vue';
export {
uiEditor
};
@@ -1,12 +0,0 @@
import { App } from 'vue';
import * as components from './index';
export default function (app: App)
{
for (var key in components)
{
let component = components[key];
app.component(key, component.default || component);
}
};
@@ -1,19 +0,0 @@
import uiRte from './ui-rte/ui-rte.vue';
import uiCheckList from './ui-check-list.vue';
import uiSearch from './ui-search.vue';
import uiToggle from './ui-toggle.vue';
import uiProperty from './ui-property.vue';
import uiColorpicker from './ui-colorpicker.vue';
import uiDatepicker from './ui-datepicker.vue';
import uiSelect from './ui-select.vue';
export {
uiRte,
uiCheckList,
uiSearch,
uiToggle,
uiProperty,
uiColorpicker,
uiDatepicker,
uiSelect
};
-7
View File
@@ -1,7 +0,0 @@
import uiForm from './ui-form.vue';
import uiFormHeader from './ui-form-header.vue';
export {
uiForm,
uiFormHeader
};
-19
View File
@@ -1,19 +0,0 @@
import { App } from 'vue';
import * as partials from './components/index';
import * as components from './index';
export default function (app: App)
{
for (var key in partials)
{
let component = partials[key];
app.component(key, component.default || component);
}
for (var key in components)
{
let component = components[key];
app.component(key, component.default || component);
}
};
@@ -1,26 +1,26 @@
<template>
<div class="media-detail-metadata">
<ui-property label="@media.fields.size" :vertical="false">
<span v-filesize="entity.size"></span>
<span v-filesize="config.model.size"></span>
</ui-property>
<ui-property label="@ui.createdDate" field="createdDate" :vertical="false">
<ui-date :value="entity.createdDate" />
<ui-date :value="config.model.createdDate" />
</ui-property>
<ui-property v-if="entity.lastModifiedDate" :vertical="false" label="@ui.modifiedDate">
<ui-date :value="entity.lastModifiedDate" />
<ui-property v-if="config.model.lastModifiedDate" :vertical="false" label="@ui.modifiedDate">
<ui-date :value="config.model.lastModifiedDate" />
</ui-property>
<template v-if="entity.imageMeta">
<ui-property v-if="entity.imageMeta.width" label="@media.fields.imageMeta.dimension" :is-text="true" :vertical="false">
{{entity.imageMeta.width}} × {{entity.imageMeta.height}}
<template v-if="config.model.imageMeta">
<ui-property v-if="config.model.imageMeta.width" label="@media.fields.imageMeta.dimension" :is-text="true" :vertical="false">
{{config.model.imageMeta.width}} × {{config.model.imageMeta.height}}
</ui-property>
<ui-property v-if="entity.imageMeta.dpi != 0" label="@media.fields.imageMeta.dpi" :is-text="true" :vertical="false">
{{entity.imageMeta.dpi}}
<ui-property v-if="config.model.imageMeta.dpi != 0" label="@media.fields.imageMeta.dpi" :is-text="true" :vertical="false">
{{config.model.imageMeta.dpi}}
</ui-property>
<ui-property v-if="entity.imageMeta.colorSpace" label="@media.fields.imageMeta.colorSpace" :is-text="true" :vertical="false">
{{entity.imageMeta.colorSpace}}
<ui-property v-if="config.model.imageMeta.colorSpace" label="@media.fields.imageMeta.colorSpace" :is-text="true" :vertical="false">
{{config.model.imageMeta.colorSpace}}
</ui-property>
<ui-property v-if="entity.imageMeta.frames > 1" label="@media.fields.imageMeta.frames" :is-text="true" :vertical="false">
{{entity.imageMeta.frames}}
<ui-property v-if="config.model.imageMeta.frames > 1" label="@media.fields.imageMeta.frames" :is-text="true" :vertical="false">
{{config.model.imageMeta.frames}}
</ui-property>
</template>
</div>
@@ -30,7 +30,7 @@
export default {
props: {
value: Object,
entity: Object
config: Object,
}
}
</script>
@@ -1,15 +1,14 @@
export default {};
//import Editor from '../../../editor/editor';
//import { getFilesize, formatDate } from '../../../utils';
//import MetadataEditor from '../pages/detail/metadata.vue';
import { ZeroEditor } from '../../../editor/editor';
import MetadataEditor from '../pages/detail/metadata.vue';
//const editor = new Editor('media:edit', '@media.fields.');
const editor = new ZeroEditor(); //('media:edit', '@media.fields.');
editor.resourcePrefix = '@media.fields.';
//editor.blueprintAlias = 'media';
//editor.field('name', { label: '@ui.name' }).text().required();
//editor.field('imageMeta.alternativeText').when(x => x.imageMeta).text();
//editor.field('caption').textarea();
//editor.field('createdDate', { hideLabel: true }).custom(MetadataEditor);
editor.field('name', { label: '@ui.name' }).text();
editor.field('imageMeta.alternativeText', { optional: true, hidden: x => x.imageMeta }).text();
editor.field('caption').textarea();
editor.field('createdDate', { hideLabel: true }).component(MetadataEditor);
//export default editor;
export default editor;
+2 -2
View File
@@ -1,9 +1,9 @@
import ListColumn from './list-column';
import ListAction from './list-action';
import { ListSchema } from 'zero/schemas';
import { ZeroSchema } from 'zero/schemas';
class List implements ListSchema
class List implements ZeroSchema
{
alias: string;
-11
View File
@@ -5,15 +5,4 @@ declare module 'zero/schemas'
{
alias: string;
}
export interface EditorSchema extends ZeroSchema
{
}
export interface ListSchema extends ZeroSchema
{
}
}
@@ -97,7 +97,6 @@
mounted()
{
console.info(this.appStore);
this.currentApplication = this.appStore.applications[0];
this.appId = this.currentApplication.id;
},