UNPKG

waibu-db

Version:
222 lines (210 loc) 9.76 kB
const defReadonly = ['id', 'createdAt', 'updatedAt'] const defFormatter = {} function getCommons (action, schema, ext, options = {}) { const { defaultsDeep } = this.app.lib.aneka const { merge, map, get, set, without, uniq, pull } = this.app.lib._ const forceVisible = get(ext, `view.${action}.forceVisible`, get(ext, 'common.forceVisible', [])) const widget = defaultsDeep(get(ext, `view.${action}.widget`), get(ext, 'common.widget', {})) const noEscape = get(ext, `view.${action}.noEscape`, get(ext, 'common.noEscape', [])) const control = defaultsDeep(get(ext, `view.${action}.control`), get(ext, 'common.control', {})) const formatCell = defaultsDeep(get(ext, `view.${action}.formatCell`), get(ext, 'common.formatCell', {})) const format = defaultsDeep(get(ext, `view.${action}.format`), get(ext, 'common.format', {})) const card = get(ext, `view.${action}.card`, get(ext, 'common.card', true)) let hidden = get(ext, `view.${action}.hidden`, get(ext, 'common.hidden', [])) const disabled = get(ext, `view.${action}.disabled`, get(ext, 'common.disabled', [])) const x = defaultsDeep(get(ext, `view.${action}.x`), get(ext, 'common.x', {})) const aggregate = get(ext, `view.${action}.stat.aggregate`, get(ext, 'common.stat.aggregate', [])) const hasXSite = get(options, 'args.0.req.routeOptions.config.xSite') const hasSiteId = schema.model.hasProperty('siteId') const hasImmutable = schema.model.hasProperty('_immutable') let attachment = get(ext, `view.${action}.attachment`, get(ext, 'common.attachment', schema.attachment)) if (!schema.options.attachment || action === 'list') attachment = false hidden.push(...schema.hidden, ...(options.hidden ?? [])) if (hasSiteId && !hasXSite) hidden.push('siteId') if (hasImmutable && !hasXSite) hidden.push('_immutable') hidden = uniq(hidden) pull(hidden, ...forceVisible) const allFields = without(map(schema.properties, 'name'), ...hidden) const forFields = get(ext, `view.${action}.fields`, get(ext, 'common.fields', allFields)) set(schema, 'view.attachment', attachment) set(schema, 'view.noEscape', noEscape) set(schema, 'view.widget', widget) set(schema, 'view.formatCell', formatCell) set(schema, 'view.format', merge({}, defFormatter, format)) set(schema, 'view.stat.aggregate', aggregate) set(schema, 'view.disabled', disabled) set(schema, 'view.control', control) set(schema, 'view.x', x) if (schema.disabled.length > 0) schema.view.disabled.push(...schema.disabled) let fields = forFields.filter(f => allFields.includes(f)) fields = uniq(without(fields, ...hidden)) options.forceShowId = options.forceShowId ?? true if (options.forceShowId && action !== 'add' && !fields.includes('id')) fields.unshift('id') let noWrap = get(ext, `view.${action}.noWrap`, get(ext, 'common.noWrap', true)) if (noWrap === true) noWrap = fields else if (noWrap === false) noWrap = [] set(schema, 'view.noWrap', noWrap) return { fields, allFields, card } } function autoLayout ({ action, schema, ext, layout, options }) { const { forOwn, keys } = this.app.lib._ const matches = ['id', 'createdAt', 'updatedAt', 'siteId'] const meta = [] const general = [] forOwn(schema.view.widget, (w, f) => { if (matches.includes(f)) meta.push(f) else general.push(f) }) if (meta.length <= 1) layout.push({ name: '_common', fields: keys(schema.view.widget) }) else { layout.push({ name: 'Meta', fields: meta }) layout.push({ name: 'General', fields: general }) } } function customLayout ({ action, schema, ext, layout, readonly }) { const { defaultsDeep } = this.app.lib.aneka const { isEmpty } = this.app.lib._ const items = [...layout] for (const item of items) { for (const idx in item.fields) { const f = item.fields[idx] const [name, col, label, component] = f.split(':') item.fields[idx] = name const w = { name, attr: {} } w.attr.label = isEmpty(label) ? `field.${name}` : label if (!isEmpty(col)) w.attr.col = col if (!isEmpty(component)) w.component = component schema.view.widget[name] = defaultsDeep(w, schema.view.widget[w.name]) } } } async function applyLayout (action, schema, ext, options) { const { defaultsDeep } = this.app.lib.aneka const { set, get, isEmpty, find } = this.app.lib._ const { fields, card } = getCommons.call(this, action, schema, ext, options) const layout = get(ext, `view.${action}.layout`, get(ext, 'common.layout', [])) const readonly = get(ext, `view.${action}.readonly`, get(ext, 'common.readonly', defReadonly)) const widget = {} let { req, model } = (options.args ?? [])[0] ?? {} if (!model) model = this.app.dobo.getModel(schema.name) for (const f of fields) { const prop = find(schema.properties, { name: f }) if (!prop) continue const result = schema.view.widget[f] ?? {} const orgCmp = result.component result.name = result.name ?? f result.attr = defaultsDeep(result.attr, { col: '4-md', label: `field.${f}` }) const orgCol = result.attr.col if (!prop.ref && ['object', 'text'].includes(prop.type)) { result.attr.col = '12' result.component = 'form-textarea' result.attr.rows = '3' } if (action === 'details') { result.component = 'form-plaintext' result.attr.col = orgCol } else { if (prop.type === 'array') { result.component = 'form-select-ext' result.attr.multiple = true result.attr.allowCreate = true } if (prop.type === 'boolean') { result.component = orgCmp ?? 'form-select' result.attr.options = 'false:no;true:yes' } if (prop.ref) { result.component = orgCmp ?? 'wdb-lookup-select' if (prop.type === 'array') { result.attr.multiple = true } } if (prop.values) { const values = await model.buildPropValues(prop, { req }) result.component = orgCmp ?? 'form-select' if (prop.type === 'array') { result.component = 'form-select-ext' result.attr.multiple = true result.attr.removeBtn = true delete result.attr.allowCreate } result.attr.options = values.map(item => `${item.value}:${item.text}`).join(';') if (result.attr.options.split(';').length > 8 && !orgCmp) result.component = 'form-select-ext' } if (['string', 'text'].includes(prop.type) && prop.maxLength) set(result, 'attr.maxlength', prop.maxLength) if (readonly.includes(f)) result.component = 'form-plaintext' if (!result.component) result.component = 'form-input' } /* for (const k in result.attr ?? {}) { const newKey = kebabCase(k) if (k !== newKey) { result.attr[newKey] = result.attr[k] delete result.attr[k] } } */ widget[f] = result } set(schema, 'view.widget', widget) if (isEmpty(layout)) autoLayout.call(this, { layout, schema, action, ext, options }) else customLayout.call(this, { layout, schema, action, ext, readonly, options }) set(schema, 'view.layout', layout) set(schema, 'view.fields', fields) set(schema, 'view.card', card) } const handler = { list: async function (schema, ext, options) { const { get, set } = this.app.lib._ const { fields } = getCommons.call(this, 'list', schema, ext, options) const qsFields = [] for (const f of get(schema, 'view.qs.fields', '').split(',')) { if (fields.includes(f)) qsFields.push(f) } const sort = get(schema, 'view.qs.sort') if (sort) { let [col, dir] = get(schema, 'view.qs.sort', '').split(':') if (!fields.includes(col) || !col) col = 'id' if (!['1', '-1'].includes(dir)) dir = '1' set(schema, 'view.qs.sort', `${col}:${dir}`) } set(schema, 'view.fields', fields) set(schema, 'view.qs.fields', qsFields.join(',')) }, details: async function (schema, ext, options) { await applyLayout.call(this, 'details', schema, ext, options) }, add: async function (schema, ext, options) { await applyLayout.call(this, 'add', schema, ext, options) }, edit: async function (schema, ext, options) { await applyLayout.call(this, 'edit', schema, ext, options) }, delete: async function (schema, ext, options) { await applyLayout.call(this, 'delete', schema, ext, options) } } async function getSchemaExt (modelName, view, options = {}) { const { readConfig } = this.app.bajo const { defaultsDeep } = this.app.lib.aneka const { pick, isString, get, cloneDeep } = this.app.lib._ const model = isString(modelName) ? this.app.dobo.getModel(modelName) : modelName const ns = model.plugin.ns const schema = pick(cloneDeep(model), ['name', 'properties', 'indexes', 'disabled', 'sortables', 'scanables', 'view', 'hidden', 'options']) schema.ns = ns schema.model = model const parserOpts = { args: options.args } let ext = await readConfig(`${ns}:/extend/waibuDb/schema/${model.baseName}.*`, { ns, baseNs: 'waibuDb', parserOpts }) ext = defaultsDeep(options.schema ?? {}, ext) await handler[view].call(this, schema, ext, options) delete schema.model const req = get(options, 'args.0.req') if (req && req.routeOptions.config.mainSiteEdit && req.site.alias !== 'default') { schema.disabled = schema.disabled ?? [] schema.disabled.push('create', 'update', 'remove') schema.view.disabled = schema.view.disabled ?? [] schema.view.disabled.push(...schema.disabled) } return { schema, ext, model } } export default getSchemaExt