new editor; we need it :-/ forgot why

This commit is contained in:
2021-12-20 16:13:09 +01:00
parent f8aa1915b8
commit 8be66d6ed6
21 changed files with 463 additions and 87 deletions
@@ -1,6 +1,6 @@
import { RouteRecordRaw } from 'vue-router';
import { App } from 'vue';
import { App, Component } from 'vue';
import { ZeroSchemaProp } from '../zero';
export interface ZeroPluginOptions
@@ -10,6 +10,8 @@ export interface ZeroPluginOptions
route: (route: RouteRecordRaw) => void;
schemas: Record<string, ZeroSchemaProp>;
schema: (alias: string, schema: ZeroSchemaProp) => void;
fieldTypes: Record<string, Component>;
fieldType: (alias: string, component: Component) => void;
}
//export class Zero
+11 -2
View File
@@ -1,5 +1,5 @@
import { App } from 'vue';
import { App, Component } from 'vue';
import { ZeroInstallOptions } from './types/zeroInstallOptions';
import { ZeroPluginOptions } from './types/zeroPluginOptions';
import { Zero, ZeroOptions } from './types/zero';
@@ -10,6 +10,7 @@ import registerFormComponents from '../forms/register';
import registerEditorComponents from '../editor/register';
import { getRouterConfig, appendRouterGuards } from './router/routerConfig';
import { countryPlugin, applicationPlugin, settingsPlugin, languagePlugin, mediaPlugin, spacePlugin, pagePlugin } from '../modules';
import editorPlugin from '../editor/new/plugin';
import { ZeroSchema } from 'zero/schemas';
import { ZeroSchemaProp } from './zero';
import * as zeroOptions from '../options';
@@ -20,6 +21,7 @@ export class ZeroRuntime implements Zero
_installOptions: ZeroInstallOptions;
_routerConfig: RouterOptions;
_schemas: Record<string, ZeroSchemaProp> = {};
_fieldTypes: Record<string, Component> = {};
/**
* version of zero backoffice
@@ -71,17 +73,24 @@ export class ZeroRuntime implements Zero
vue: this._app,
routes: this._routerConfig.routes,
schemas: this._schemas,
fieldTypes: this._fieldTypes,
route(route: RouteRecordRaw)
{
this.routes.push(route);
},
schema(alias: 'string', schema: ZeroSchemaProp)
schema(alias: string, schema: ZeroSchemaProp)
{
this.schemas[alias] = schema;
},
fieldType(alias: string, component: Component)
{
this.fieldTypes[alias] = component;
}
} as ZeroPluginOptions;
// install all plugins
editorPlugin.install(pluginOptions);
countryPlugin.install(pluginOptions);
applicationPlugin.install(pluginOptions);
settingsPlugin.install(pluginOptions);
@@ -1,6 +1,6 @@
declare module 'zero/editor'
declare module 'zerox/editor'
{
export interface FieldSupportsMaxLength
{
@@ -1,4 +1,4 @@
<!--<template>
<template>
<div class="blueprint" v-if="value && (isParent || isChild)">
<div class="blueprint-box">
<template v-if="isChild">
@@ -28,9 +28,7 @@
<script>
import Overlay from 'zero/helpers/overlay.js';
import SettingsOverlay from './settings.vue';
import Localization from 'zero/helpers/localization.js';
import * as overlays from '../../services/overlay';
export default {
props: {
@@ -89,25 +87,28 @@
methods: {
openSettings()
async openSettings()
{
const editor = typeof this.editor === 'string' ? this.zero.getEditor(this.editor) : this.editor;
const editor = typeof this.editor === 'string' ? this.zero.getSchema(this.editor) : this.editor;
return Overlay.open({
component: SettingsOverlay,
const result = await overlays.open({
component: () => import('./settings.vue'),
display: 'editor',
model: this.value,
blueprintConfig: this.config
}).then(res =>
{
this.value.blueprint = res.blueprint;
this.$emit('input', res.blueprint);
if (typeof res.update === 'function')
{
res.update(this.value);
model: {
value: this.value,
blueprintConfig: this.config
}
//EventHub.$emit('page.update');
});
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()
@@ -294,11 +295,11 @@
margin-top: -2px;
}
}
</style>-->
</style>
<!--
<button type="button" class="ui-property-lock" v-if="locked" > <i class="fth-lock" > </i > </button >
<!--<button type="button" class="ui-property-lock" v-if="locked" > <i class="fth-lock" > </i > </button >
/*.ui-property.is-locked
{
@@ -317,6 +318,6 @@
position: relative;
top: -1px;
margin-right: 8px;
}
}-->
-->
@@ -1,10 +1,10 @@
<!--<template>
<ui-overlay-editor class="blueprint-settings">
<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.hide" />
<ui-button type="light onbg" label="@ui.close" @click="config.close" />
<ui-button type="primary" label="@ui.confirm" @click="onSave" :state="state" />
</template>
@@ -14,25 +14,26 @@
<ui-property class="blueprint-settings-tableheader" :key="-1" label="Property" :vertical="false">
<b>Synchronized</b>
</ui-property>
<ui-property v-for="(field, index) in items" v-if="!field.disabled" :key="index" :label="field.label" :description="field.description" :vertical="false" :class="{'not-synced': !field.synced}">
<ui-toggle v-model="field.synced" />
</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-overlay-editor>
</ui-trinity>
</template>
<script>
import Arrays from 'zero/helpers/arrays.js';
import Localization from 'zero/helpers/localization.js';
import BlueprintApi from 'zero/api/blueprint.js';
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,
blueprintConfig: Object
config: Object
},
data: () => ({
@@ -45,10 +46,10 @@
mounted()
{
this.blueprintConfig.fields.forEach(field =>
this.model.blueprintConfig.fields.forEach(field =>
{
let item = JSON.parse(JSON.stringify(field));
item.synced = field.synced(this.model);
item.synced = field.synced(this.model.value);
this.items.push(item);
});
this.loaded = true;
@@ -61,16 +62,16 @@
{
this.state = 'loading';
let desync = JSON.parse(JSON.stringify(this.model.blueprint.desync));
let desync = JSON.parse(JSON.stringify(this.model.value.blueprint.desync));
let resync = [];
this.items.forEach(field =>
{
let desynced = this.model.blueprint.desync.indexOf(field.path) > -1;
let desynced = this.model.value.blueprint.desync.indexOf(field.path) > -1;
if (field.synced && desynced)
{
Arrays.remove(desync, field.path);
arrayRemove(desync, field.path);
resync.push(field.path);
}
else if (!field.synced && !desynced)
@@ -79,17 +80,17 @@
}
});
this.model.blueprint.desync = desync;
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.blueprint.id).then(blueprint =>
BlueprintApi.getById(this.model.value.blueprint.id).then(blueprint =>
{
this.config.confirm({
blueprint: this.model.blueprint,
blueprint: this.model.value.blueprint,
update: entity =>
{
resync.forEach(path =>
@@ -104,7 +105,7 @@
{
//this.state = 'success';
this.config.confirm({
blueprint: this.model.blueprint
blueprint: this.model.value.blueprint
});
}
@@ -220,4 +221,4 @@
color: var(--color-primary);
font-weight: 400;
}*/
</style>-->
</style>
@@ -1,6 +1,7 @@
import { localize } from '../services/localization';
import { arrayRemove } from '../utils/arrays';
import { useUiStore } from '../ui/store';
function unlocked(config, model, field)
{
@@ -78,10 +79,11 @@ function unlock(config, model, field)
*/
export function createBlueprintConfig(zero, editor, model)
{
const store = useUiStore();
let blueprintAlias = editor.blueprintAlias;
let config = zero.config.blueprints.find(x => x.alias == blueprintAlias);
let canBeBlueprinted = store.blueprints.indexOf(blueprintAlias) > -1;
if (!blueprintAlias || !config)
if (!blueprintAlias || !canBeBlueprinted)
{
return {
alias: blueprintAlias,
@@ -89,13 +91,15 @@ export function createBlueprintConfig(zero, editor, model)
canUnlock: () => false,
isBlueprintParent: () => false,
isBlueprintChild: () => false,
lock: () => {},
unlock: () => {},
lock: () => { },
unlock: () => { },
fields: []
};
}
let config = {
unlocked: []
}
// check for blueprint availability
//config.isParent = this.value && this.$route.query.scope === 'shared';
@@ -112,15 +116,15 @@ export function createBlueprintConfig(zero, editor, model)
{
processed.push(alias);
if (config.unlocked.indexOf(alias) > -1)
{
//if (config.unlocked.indexOf(alias) > -1)
//{
fields.push({
path: alias,
label: localize("@ui.entityfields." + alias),
description: localize("@ui.entityfields." + alias + "_text", { hideEmpty: true }),
synced: model => !model.blueprint || model.blueprint.desync.indexOf(alias) < 0
});
}
//}
});
// add blueprint setting for custom fields
@@ -0,0 +1,15 @@
export interface EditorFieldMeta
{
canEdit: boolean;
}
export interface EditorFieldProps<T>
{
value: T;
model: any;
meta: EditorFieldMeta;
disabled: boolean;
system: boolean;
}
+25 -16
View File
@@ -165,6 +165,31 @@ class EditorField
}
/**
* 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
@@ -580,22 +605,6 @@ class EditorField
{
return this._setComponent(() => import('./fields/video.vue'), { limit });
}
/**
* 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;
}
}
+2 -2
View File
@@ -34,9 +34,9 @@ class Editor
};
constructor(alias, prefix)
constructor(prefix)
{
this._alias = alias;
this._alias = null;
this._prefix = prefix || '';
}
+2 -2
View File
@@ -8,7 +8,7 @@
<ui-tab v-for="(tab, index) in tabs" class="ui-box" :class="tab.class" :data-alias="tab.alias" :label="tab.name" :count="tab.count(value)" :hidden="tab.disabled(value)" :key="index">
<h3 v-if="display == 'boxes' && tab.name" class="ui-headline editor-tab-headline" v-localize="tab.name"></h3>
<slot name="blueprint">
<!--<blueprint-property v-if="value && editorConfig.blueprint" :model-value="value" :meta="meta" :config="editorConfig.blueprint" />-->
<blueprint-property v-if="value && editorConfig.blueprint" :value="value" :meta="meta" :config="editorConfig.blueprint" />
</slot>
<div class="ui-property ui-property-parent" v-for="fieldset in tab.fieldsets">
<editor-component v-for="(field, fieldIndex) in fieldset.fields" :disabled="disabled" :key="fieldIndex" :config="field" @input="onChange" :editor="editorConfig" :value="value"
@@ -97,7 +97,7 @@
async created()
{
this.editorConfig = typeof this.config === 'string' ? await this.zero.getSchema(this.config) : this.config;
this.editorConfig.blueprint = null; //createBlueprintConfig(this.zero, this.editorConfig, this.value);
this.editorConfig.blueprint = createBlueprintConfig(this.zero, this.editorConfig, this.value);
this.tabs = this.editorConfig.tabs.map(tab =>
{
@@ -0,0 +1,89 @@
import { Component } from "vue";
declare module 'zero/editor'
{
export interface EditorFieldConfiguration
{
/**
* Whether this field is required (additional validation is done on the server)
*/
required?: 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 required
* @param {function|boolean} [condition] - Optionally only require this field when a condition is fulfilled or reset the required state with true/false
*/
setRequired(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;
}
}
@@ -0,0 +1,21 @@
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;
}
}
@@ -0,0 +1,121 @@
import { Component, defineAsyncComponent } from 'vue';
import { EditorField, EditorFieldConfiguration, TextFieldOptions, EditorFieldDefinition } from 'zero/editor';
export const fieldTypes: Record<string, Function> = {};
fieldTypes.text = function (options?: TextFieldOptions)
{
return field.custom(defineAsyncComponent(() => import('./fields/text.vue')), options);
};
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
if (fieldTypes[prop])
{
const fieldDefinition = fieldTypes[prop];
console.info({ target, prop, receiver });
//return fieldDefinition()
return
}
return undefined;
}
})// as EditorField;
export function createFieldConfiguration(): EditorFieldConfiguration
{
return {
required: false,
readonly: false,
hidden: false,
label: null,
hideLabel: false,
description: null,
helpText: null,
classes: null,
horizontal: false
} as EditorFieldConfiguration;
}
export class EditorFieldBase
{
alias: string;
configuration: EditorFieldConfiguration;
component?: Component;
options?: any;
constructor(alias: string, config?: EditorFieldConfiguration)
{
this.alias = alias;
this.configuration = config || createFieldConfiguration();
}
/**
* 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
*/
setRequired(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>(component: Component, options?: T): EditorField
{
this.component = component;
this.options = options;
return this;
}
}
+34
View File
@@ -0,0 +1,34 @@
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;
}
}
@@ -0,0 +1,32 @@
<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>
@@ -0,0 +1,11 @@
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;
@@ -0,0 +1,17 @@
import { defineStore } from 'pinia';
import { Component } from 'vue';
export type EditorState = {
fieldTypes: Record<string, Component>
};
export const useEditorStore = defineStore('zero.editor', {
state: () => ({
fieldTypes: {}
} as EditorState),
actions: {
}
});
@@ -1,12 +1,20 @@
import Editor from '../../../editor/editor';
import { Editor } from '../../../editor/new/editor';
const editor = new Editor('countries:edit', '@country.fields.');
editor.blueprintAlias = 'country';
const editor = new Editor(); //'@country.fields.');
editor.field('name', { label: '@ui.name' }).text(120).required();
//editor.field('alias', { label: '@ui.alias' }).text().required();
editor.field('code').text(2).required();
editor.field('isPreferred').toggle();
editor.field('name').setRequired(true).text({ maxLength: 80 });
export default editor;
export default editor;
//import Editor from '../../../editor/editor';
//const editor = new Editor('@country.fields.');
//editor.blueprintAlias = 'countries';
//editor.field('name', { label: '@ui.name' }).text(120).required();
////editor.field('alias', { label: '@ui.alias' }).text().required();
//editor.field('code').text(2).required();
//editor.field('isPreferred').toggle();
//export default editor;
@@ -1,4 +1,5 @@
import { ZeroPlugin, ZeroPluginOptions } from '../../core';
import { TextFieldOptions } from 'zero/editor';
import { defineAsyncComponent } from 'vue';
export default {
@@ -1,8 +1,8 @@
import Editor from '../../../editor/editor';
const editor = new Editor('languages:edit', '@language.fields.');
editor.blueprintAlias = 'language';
const editor = new Editor('@language.fields.');
editor.blueprintAlias = 'languages';
editor.field('name', { label: '@ui.name' }).text(60).required();
editor.field('code').text(10).required();
editor.field('inheritedLanguageId').languagePicker();
+1
View File
@@ -37,6 +37,7 @@ builder.Services.Configure<FlavorOptions>(opts =>
builder.Services.Configure<BlueprintOptions>(opts =>
{
opts.Add<Country>();
opts.Add<Language>();
});
var app = builder.Build();