waibu-db
Version:
222 lines (210 loc) • 9.76 kB
JavaScript
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