diff --git a/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts b/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts index d85b1f71..6a462f68 100644 --- a/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts +++ b/zero.Backoffice.UI/app/core/types/zeroPluginOptions.ts @@ -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; schema: (alias: string, schema: ZeroSchemaProp) => void; + fieldTypes: Record; + fieldType: (alias: string, component: Component) => void; } //export class Zero diff --git a/zero.Backoffice.UI/app/core/zeroRuntime.ts b/zero.Backoffice.UI/app/core/zeroRuntime.ts index 3b62aa71..9f4d0ec5 100644 --- a/zero.Backoffice.UI/app/core/zeroRuntime.ts +++ b/zero.Backoffice.UI/app/core/zeroRuntime.ts @@ -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 = {}; + _fieldTypes: Record = {}; /** * 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); diff --git a/zero.Backoffice.UI/app/editor/_wip/editor.d.ts b/zero.Backoffice.UI/app/editor/_wip/editor2.d.ts similarity index 93% rename from zero.Backoffice.UI/app/editor/_wip/editor.d.ts rename to zero.Backoffice.UI/app/editor/_wip/editor2.d.ts index 0216e1ca..f4ebf709 100644 --- a/zero.Backoffice.UI/app/editor/_wip/editor.d.ts +++ b/zero.Backoffice.UI/app/editor/_wip/editor2.d.ts @@ -1,6 +1,6 @@ -declare module 'zero/editor' +declare module 'zerox/editor' { export interface FieldSupportsMaxLength { diff --git a/zero.Backoffice.UI/app/editor/blueprint/property.vue b/zero.Backoffice.UI/app/editor/blueprint/property.vue index 015372d4..5a5ffe06 100644 --- a/zero.Backoffice.UI/app/editor/blueprint/property.vue +++ b/zero.Backoffice.UI/app/editor/blueprint/property.vue @@ -1,4 +1,4 @@ - + - + ---> diff --git a/zero.Backoffice.UI/app/editor/blueprint/settings.vue b/zero.Backoffice.UI/app/editor/blueprint/settings.vue index 5d09f367..17af9b90 100644 --- a/zero.Backoffice.UI/app/editor/blueprint/settings.vue +++ b/zero.Backoffice.UI/app/editor/blueprint/settings.vue @@ -1,10 +1,10 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/editor-blueprint.ts b/zero.Backoffice.UI/app/editor/editor-blueprint.ts index d1291b7d..591f24e3 100644 --- a/zero.Backoffice.UI/app/editor/editor-blueprint.ts +++ b/zero.Backoffice.UI/app/editor/editor-blueprint.ts @@ -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 diff --git a/zero.Backoffice.UI/app/editor/editor-field-props.ts b/zero.Backoffice.UI/app/editor/editor-field-props.ts new file mode 100644 index 00000000..f7c4098b --- /dev/null +++ b/zero.Backoffice.UI/app/editor/editor-field-props.ts @@ -0,0 +1,15 @@ + +export interface EditorFieldMeta +{ + canEdit: boolean; +} + + +export interface EditorFieldProps +{ + value: T; + model: any; + meta: EditorFieldMeta; + disabled: boolean; + system: boolean; +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/editor-field.ts b/zero.Backoffice.UI/app/editor/editor-field.ts index 33a65033..2a387575 100644 --- a/zero.Backoffice.UI/app/editor/editor-field.ts +++ b/zero.Backoffice.UI/app/editor/editor-field.ts @@ -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; - } } diff --git a/zero.Backoffice.UI/app/editor/editor.ts b/zero.Backoffice.UI/app/editor/editor.ts index e39d8829..d1fcf0f3 100644 --- a/zero.Backoffice.UI/app/editor/editor.ts +++ b/zero.Backoffice.UI/app/editor/editor.ts @@ -34,9 +34,9 @@ class Editor }; - constructor(alias, prefix) + constructor(prefix) { - this._alias = alias; + this._alias = null; this._prefix = prefix || ''; } diff --git a/zero.Backoffice.UI/app/editor/editor.vue b/zero.Backoffice.UI/app/editor/editor.vue index 08fcd22d..b7411e5c 100644 --- a/zero.Backoffice.UI/app/editor/editor.vue +++ b/zero.Backoffice.UI/app/editor/editor.vue @@ -8,7 +8,7 @@

- +
{ diff --git a/zero.Backoffice.UI/app/editor/new/editor.dx.ts b/zero.Backoffice.UI/app/editor/new/editor.dx.ts new file mode 100644 index 00000000..57bb26bb --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/editor.dx.ts @@ -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 + { + 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(component: Component, options?: T): EditorField; + } +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/editor.ts b/zero.Backoffice.UI/app/editor/new/editor.ts new file mode 100644 index 00000000..776cbd86 --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/editor.ts @@ -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; + } +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/editorField.ts b/zero.Backoffice.UI/app/editor/new/editorField.ts new file mode 100644 index 00000000..7d911853 --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/editorField.ts @@ -0,0 +1,121 @@ + +import { Component, defineAsyncComponent } from 'vue'; +import { EditorField, EditorFieldConfiguration, TextFieldOptions, EditorFieldDefinition } from 'zero/editor'; + +export const fieldTypes: Record = {}; + + +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(component: Component, options?: T): EditorField + { + this.component = component; + this.options = options; + return this; + } +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/fields.d.ts b/zero.Backoffice.UI/app/editor/new/fields.d.ts new file mode 100644 index 00000000..f9b444ac --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/fields.d.ts @@ -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; + } +} \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/fields/text.vue b/zero.Backoffice.UI/app/editor/new/fields/text.vue new file mode 100644 index 00000000..ae163cbc --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/fields/text.vue @@ -0,0 +1,32 @@ + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/plugin.ts b/zero.Backoffice.UI/app/editor/new/plugin.ts new file mode 100644 index 00000000..2d6f1374 --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/plugin.ts @@ -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; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/editor/new/store.ts b/zero.Backoffice.UI/app/editor/new/store.ts new file mode 100644 index 00000000..5b714378 --- /dev/null +++ b/zero.Backoffice.UI/app/editor/new/store.ts @@ -0,0 +1,17 @@ + +import { defineStore } from 'pinia'; +import { Component } from 'vue'; + +export type EditorState = { + fieldTypes: Record +}; + +export const useEditorStore = defineStore('zero.editor', { + state: () => ({ + fieldTypes: {} + } as EditorState), + + actions: { + + } +}); \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/countries/schemas/editor.ts b/zero.Backoffice.UI/app/modules/countries/schemas/editor.ts index d884066b..d1361e86 100644 --- a/zero.Backoffice.UI/app/modules/countries/schemas/editor.ts +++ b/zero.Backoffice.UI/app/modules/countries/schemas/editor.ts @@ -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; \ No newline at end of file +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; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/languages/plugin.ts b/zero.Backoffice.UI/app/modules/languages/plugin.ts index 4c5c468c..bff831ce 100644 --- a/zero.Backoffice.UI/app/modules/languages/plugin.ts +++ b/zero.Backoffice.UI/app/modules/languages/plugin.ts @@ -1,4 +1,5 @@ import { ZeroPlugin, ZeroPluginOptions } from '../../core'; +import { TextFieldOptions } from 'zero/editor'; import { defineAsyncComponent } from 'vue'; export default { diff --git a/zero.Backoffice.UI/app/modules/languages/schemas/editor.ts b/zero.Backoffice.UI/app/modules/languages/schemas/editor.ts index a8a1a1d1..71272e6f 100644 --- a/zero.Backoffice.UI/app/modules/languages/schemas/editor.ts +++ b/zero.Backoffice.UI/app/modules/languages/schemas/editor.ts @@ -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(); diff --git a/zero.Demo/Program.cs b/zero.Demo/Program.cs index 188118a1..9c6f96ad 100644 --- a/zero.Demo/Program.cs +++ b/zero.Demo/Program.cs @@ -37,6 +37,7 @@ builder.Services.Configure(opts => builder.Services.Configure(opts => { opts.Add(); + opts.Add(); }); var app = builder.Build();