first draft of ui-editor

This commit is contained in:
2021-12-14 14:21:48 +01:00
parent 75964911d0
commit e7ada02e1d
16 changed files with 111 additions and 109 deletions
+5 -1
View File
@@ -1,5 +1,5 @@
<template>
<section v-show="active" class="ui-tab" :aria-hidden="!active" role="tabpanel">
<section v-show="active && !hidden" class="ui-tab" :aria-hidden="!active" role="tabpanel">
<slot />
</section>
</template>
@@ -22,6 +22,10 @@
disabled: {
type: Boolean,
default: false
},
hidden: {
type: Boolean,
default: false
}
},
@@ -232,7 +232,6 @@
getLink(item)
{
return '/'; // TODO
if (!this.listConfig.link)
{
return null;
@@ -7,6 +7,7 @@ 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 { countryPlugin, applicationPlugin, settingsPlugin } from '../modules';
import { ZeroSchema } from 'zero/schemas';
@@ -52,6 +53,7 @@ export class ZeroRuntime implements Zero
registerDirectives(app);
registerComponents(app);
registerFormComponents(app);
registerEditorComponents(app);
}
@@ -12,7 +12,7 @@
:can-unlock="canUnlock || false"
@unlock="unlock"
@lock="lock">
<component :is="config.component" v-bind="config.componentOptions" :model-value="model" :entity="modelValue" :meta="meta" @input="onChange" :disabled="isDisabled" />
<component :is="config.component" v-bind="config.componentOptions" :value="model" :entity="value" :meta="meta" @input="onChange" :disabled="isDisabled" />
<p v-if="config.options.helpText" class="ui-property-help" v-localize="config.options.helpText"></p>
</ui-property>
</template>
@@ -38,7 +38,7 @@
type: Object,
required: true
},
modelValue: {
value: {
type: Object,
required: true
},
@@ -49,7 +49,7 @@
},
watch: {
modelValue: {
value: {
deep: true,
handler: function ()
{
@@ -74,15 +74,15 @@
computed: {
isHidden()
{
return this.loaded && typeof this.config.options.condition === 'function' && !this.config.options.condition(this.modelValue, this);
return this.loaded && typeof this.config.options.condition === 'function' && !this.config.options.condition(this.value, this);
},
isRequired()
{
return typeof this.config.isRequired === 'function' ? this.config.isRequired(this.modelValue) : this.config.isRequired;
return typeof this.config.isRequired === 'function' ? this.config.isRequired(this.value) : this.config.isRequired;
},
isDisabled()
{
return this.manualDisabled || this.disabled || (typeof this.config.options.disabled === 'boolean' && this.config.options.disabled) || (typeof this.config.options.disabled === 'function' && this.config.options.disabled(this.modelValue, this.model));
return this.manualDisabled || this.disabled || (typeof this.config.options.disabled === 'boolean' && this.config.options.disabled) || (typeof this.config.options.disabled === 'function' && this.config.options.disabled(this.value, this.model));
},
label()
{
@@ -94,11 +94,11 @@
},
isLocked()
{
return !this.editor.blueprint.unlocked(this.modelValue, this.config);
return this.editor.blueprint && !this.editor.blueprint.unlocked(this.value, this.config);
},
canUnlock()
{
return this.editor.blueprint.canUnlock(this.modelValue, this.config);
return this.editor.blueprint && this.editor.blueprint.canUnlock(this.value, this.config);
}
},
@@ -107,7 +107,7 @@
rebuildModel()
{
this.selector = selectorToArray(this.config.path);
let currentValue = this.modelValue;
let currentValue = this.value;
let found = false;
if (!this.selector || !this.selector.length || !currentValue)
@@ -132,6 +132,8 @@
this.model = found ? currentValue : null;
}
//console.info(this.config.path + ' => ' + this.model);
},
@@ -141,19 +143,19 @@
if (typeof value === 'function')
{
value(this.modelValue);
value(this.value);
}
else
{
setObjectValue(this.modelValue, this.selector, value);
setObjectValue(this.value, this.selector, value);
}
this.$emit('input', this.modelValue);
this.$emit('input', this.value);
if (typeof this.config.options.onChange === 'function')
{
this.config.options.onChange(value, {
oldValue,
model: this.modelValue,
model: this.value,
component: this
});
}
@@ -181,7 +183,7 @@
async lock()
{
let originalValue = await this.editor.blueprint.lock(this.modelValue, this.config);
let originalValue = await this.editor.blueprint.lock(this.value, this.config);
this.onChange(originalValue);
}
}
+12 -11
View File
@@ -5,17 +5,17 @@
</header>
<div class="editor" :class="['display-' + display, { 'has-sidebar': asideDefined, 'hide-tabs': tabs.length < 2, 'has-below': belowDefined }]">
<ui-tabs class="editor-tabs">
<ui-tab v-if="!tab.disabled(modelValue)" v-for="(tab, index) in tabs" class="ui-box" :class="tab.class" :label="tab.name" :count="tab.count(modelValue)" :key="index">
<ui-tab v-for="(tab, index) in tabs" class="ui-box" :class="tab.class" :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="modelValue && editorConfig.blueprint" :model-value="modelValue" :meta="meta" :config="editorConfig.blueprint" />
<!--<blueprint-property v-if="value && editorConfig.blueprint" :model-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" :model-value="modelValue"
<editor-component v-for="(field, fieldIndex) in fieldset.fields" :disabled="disabled" :key="fieldIndex" :config="field" @input="onChange" :editor="editorConfig" :value="value"
:class="field.options.class" :data-cols="!!field.options.fieldset" :style="{ 'grid-column': field.options.fieldset ? 'span ' + field.options.fieldsetColumns : null }" />
</div>
<component v-if="tab.component" :is="tab.component" v-model="modelValue" />
<component v-if="tab.component" :is="tab.component" v-model="value" />
</ui-tab>
</ui-tabs>
<aside class="editor-aside" v-if="asideDefined">
@@ -35,8 +35,9 @@
import EditorAside from './editor-aside.vue';
import { createBlueprintConfig } from './editor-blueprint';
import BlueprintProperty from './blueprint/property.vue';
import { defineComponent } from 'vue';
export default {
export default defineComponent({
name: 'uiEditor',
provide: function ()
@@ -60,7 +61,7 @@
type: Boolean,
default: false
},
modelValue: {
value: {
type: Object
},
onConfigure: {
@@ -93,10 +94,10 @@
}
},
created()
async created()
{
this.editorConfig = typeof this.config === 'string' ? this.zero.getEditor(this.config) : this.config;
this.editorConfig.blueprint = createBlueprintConfig(this.zero, this.editorConfig, this.modelValue);
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.tabs = this.editorConfig.tabs.map(tab =>
{
@@ -121,8 +122,8 @@
onChange()
{
this.$emit('input', this.modelValue);
this.$emit('input', this.value);
}
}
}
})
</script>
@@ -1,12 +1,12 @@
<template>
<ui-toggle :model-value="modelValue" @input="$emit('input', $event)" v-bind="{ disabled, negative, onContent, offContent }" />
<ui-toggle :on="value" @update:on="$emit('input', $event)" v-bind="{ disabled, negative, onContent, offContent }" />
</template>
<script>
export default {
props: {
modelValue: Boolean,
value: Boolean,
disabled: Boolean,
negative: Boolean,
onContent: String,
+5
View File
@@ -0,0 +1,5 @@
import uiEditor from './editor.vue';
export {
uiEditor
};
+12
View File
@@ -0,0 +1,12 @@
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,10 +1,10 @@
<template>
<div class="ui-toggle" :class="{'is-disabled': disabled, 'is-negative': negative, 'is-active': modelValue, 'is-content-left': contentLeft }">
<input type="checkbox" :value="modelValue" @input="onChange" :disabled="disabled" />
<span class="ui-toggle-switch" :class="{ 'is-active': modelValue }"><i></i></span>
<i class="fth-minus-circle ui-toggle-off-warning" v-if="offContent && !modelValue && offWarning"></i>
<span class="ui-toggle-text" v-if="onContent && modelValue" v-localize="onContent"></span>
<span class="ui-toggle-text" v-if="offContent && !modelValue" v-localize="offContent"></span>
<div class="ui-toggle" :class="{'is-disabled': disabled, 'is-negative': negative, 'is-active': on, 'is-content-left': contentLeft }">
<input type="checkbox" :value="on" @input="onChange" :disabled="disabled" />
<span class="ui-toggle-switch" :class="{ 'is-active': on }"><i></i></span>
<i class="fth-minus-circle ui-toggle-off-warning" v-if="offContent && !on && offWarning"></i>
<span class="ui-toggle-text" v-if="onContent && on" v-localize="onContent"></span>
<span class="ui-toggle-text" v-if="offContent && !on" v-localize="offContent"></span>
</div>
</template>
@@ -14,7 +14,7 @@
name: 'uiToggle',
props: {
modelValue: {
on: {
type: Boolean,
default: false
},
@@ -48,7 +48,7 @@
onChange(ev)
{
this.$emit('update:modelValue', !this.modelValue);
this.$emit('update:on', !this.on);
}
}
}
@@ -1,21 +1,21 @@
<template>
<ui-header-bar class="ui-form-header" :back-button="true">
<template v-slot:title>
<h2 class="ui-header-bar-title" :class="{'is-empty': title && !modelValue.name && !titleDisabled}">
<h2 class="ui-header-bar-title" :class="{'is-empty': title && !value.name && !titleDisabled}">
<template v-for="prefix in prefixes">
<span class="-minor -prefix" v-localize:html="prefix"></span>
<ui-icon class="-chevron" symbol="fth-chevron-right" :size="14" />
</template>
<ui-error field="name" />
<input v-if="!titleDisabled" class="ui-form-header-title-input" type="text" v-model="modelValue.name" v-localize:placeholder="title" :readonly="titleDisabled || disabled" />
<!--<ui-alias class="ui-form-header-title-alias" v-if="hasAlias" v-model="modelValue.alias" :name="modelValue.name" :disabled="disabled" />-->
<span v-if="titleDisabled" v-localize="forceTitle ? title : (modelValue.name || title)"></span>
<input v-if="!titleDisabled" class="ui-form-header-title-input" type="text" v-model="value.name" v-localize:placeholder="title" :readonly="titleDisabled || disabled" />
<!--<ui-alias class="ui-form-header-title-alias" v-if="hasAlias" v-model="value.alias" :name="value.name" :disabled="disabled" />-->
<span v-if="titleDisabled" v-localize="forceTitle ? title : (value.name || title)"></span>
</h2>
</template>
<div class="ui-form-header-aside">
<slot></slot>
<div v-if="!activeDisabled && typeof modelValue.isActive !== 'undefined'" class="ui-form-header-toggle">
<ui-toggle v-model="modelValue.isActive" class="is-accent" off-content="@ui.inactive" :off-warning="true" on-content="@ui.active" :content-left="true" :disabled="disabled" />
<div v-if="!activeDisabled && typeof value.isActive !== 'undefined'" class="ui-form-header-toggle">
<ui-toggle v-model:on="value.isActive" class="is-accent" off-content="@ui.inactive" :off-warning="true" on-content="@ui.active" :content-left="true" :disabled="disabled" />
</div>
<slot name="buttons"></slot>
<ui-dropdown v-if="actionsDefined" align="right">
@@ -47,8 +47,9 @@
prefix: {
type: [String, Array]
},
modelValue: {
type: Object
value: {
type: Object,
default: {}
},
canDelete: {
type: Boolean,
+13 -40
View File
@@ -104,52 +104,25 @@
},
// loads data on creation of the form
load(promise)
async load(promise)
{
this.loadingState = 'loading';
const response = await promise();
return new Promise((resolve, reject) =>
if (!response.success)
{
promise
.then(
response =>
{
if (response.meta && typeof response.meta.canEdit === 'boolean')
{
this.canEdit = response.meta.canEdit;
this.loadingState = 'error';
this.loadingError = response.errors[0].message;
return null;
}
//if (response.entity)
//{
// if (!response.entity.id && this.$route.params.scope === zero.sharedAppId)
// {
// response.entity.appId = zero.sharedAppId;
// }
// this.isShared = response.entity.appId === zero.sharedAppId;
//} // TODO appx
}
resolve(response);
this.loadingState = 'default';
this.$nextTick(() =>
{
this.$emit('loaded', this);
});
},
(error) =>
{
this.loadingState = 'error';
this.loadingError = error;
reject(error);
}
)
.catch(exception =>
{
this.loadingState = 'error';
this.loadingError = exception;
});
this.canEdit = true;
this.loadingState = 'default';
this.$nextTick(() =>
{
this.$emit('loaded', this);
});
return response.data;
},
// handles a promise as result of the form submission
@@ -1,6 +1,6 @@
<template>
<div class="root">
countries óÒ<br />
country óÒ<br />
<ui-search v-model="search" @submit="doSearch" />
<div v-for="item in items" :key="item.id">
{{item.name}}
@@ -1,6 +1,6 @@
<template>
<ui-form ref="form" class="country" v-slot="form" @submit="onSubmit" @load="onLoad" :route="route">
<ui-form-header v-model="model" prefix="@country.list" title="@country.name" :disabled="disabled" :is-create="!$route.params.id" :state="form.state" :can-delete="meta.canDelete" @delete="onDelete" />
<ui-form-header v-model:value="model" prefix="@country.list" title="@country.name" :disabled="disabled" :is-create="!$route.params.id" :state="form.state" :can-delete="meta.canDelete" @delete="onDelete" />
<ui-editor config="country" v-model="model" :meta="meta" :disabled="disabled">
<template v-slot:below>
<ui-editor-infos v-model="model" :disabled="disabled" />
@@ -15,7 +15,7 @@
<script>
import { countriesApi } from 'zerox';
import api from './api';
export default {
props: ['id'],
@@ -23,33 +23,29 @@
data: () => ({
meta: {},
model: { name: null, features: [], domains: [] },
route: __zero.alias.settings.countries + '-edit',
route: 'countries-edit',
disabled: false
}),
methods: {
onLoad(form)
async onLoad(form)
{
form.load(!this.$route.params.id ? countriesApi.getEmpty() : countriesApi.getById(this.id, { scope: 'hallo' })).then(response =>
{
this.disabled = !response.meta.canEdit;
this.meta = response.meta;
this.model = response.entity;
});
const response = await form.load(() => api.getById(this.id));
this.model = response;
},
onSubmit(form)
{
form.handle(countriesApi.save(this.model));
form.handle(api.save(this.model));
},
onDelete(item, opts)
{
opts.hide();
this.$refs.form.onDelete(countriesApi.delete.bind(this, this.id));
this.$refs.form.onDelete(api.delete.bind(this, this.id));
}
}
}
@@ -7,7 +7,8 @@ export default {
install(app: ZeroPluginOptions)
{
app.vue.component('ui-countrypicker', defineAsyncComponent(() => import('./ui-countrypicker.vue')));
app.route({ path: '/settings/countries', component: () => import('./countries.vue') });
app.route({ name: 'countries', path: '/settings/countries', component: () => import('./countries.vue') });
app.route({ name: 'countries-edit', path: '/settings/countries/edit/:id', component: () => import('./country.vue'), props: true });
app.schema('countries', () => import('./list'));
app.schema('country', () => import('./editor'));
+4 -3
View File
@@ -22,11 +22,12 @@
"pinia": "^2.0.6"
},
"devDependencies": {
"sass": "^1.44.0",
"vite": "^2.6.14",
"@vue/compiler-sfc": "^3.2.24",
"@vitejs/plugin-vue": "^1.10.1",
"@vue/compiler-sfc": "^3.2.24",
"sass": "^1.44.0",
"typescript": "^4.5.2",
"vite": "^2.6.14",
"vite-plugin-rewrite-all": "^0.1.2",
"vue-tsc": "^0.29.8"
}
}
+12 -7
View File
@@ -2,6 +2,7 @@ const path = require('path');
const fs = require('fs');
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import pluginRewriteAll from 'vite-plugin-rewrite-all'
let loadedPlugins = JSON.parse(process.env.ZERO_PLUGINS || "[]");
@@ -72,15 +73,19 @@ let config = defineConfig({
vue: '@vue/compat'
}
},
plugins: [vue({
template: {
compilerOptions: {
compatConfig: {
MODE: 2
plugins: [
pluginRewriteAll(),
vue({
template: {
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
}
}), ...zeroPlugins],
}),
...zeroPlugins
],
build: {
manifest: true,
outDir: 'dist/zero',