remove legacy stuff + allow custom components for editor-fields
This commit is contained in:
@@ -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
|
||||
};
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
|
||||
<script>
|
||||
import { generateId } from '../../utils/numbers';
|
||||
import { generateId } from '../utils/numbers';
|
||||
|
||||
export default {
|
||||
name: 'uiColorpicker',
|
||||
+2
-2
@@ -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';
|
||||
+5
-2
@@ -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: {
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
@@ -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.);
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import uiForm from './ui-form.vue';
|
||||
import uiFormHeader from './ui-form-header.vue';
|
||||
|
||||
export {
|
||||
uiForm,
|
||||
uiFormHeader
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user