waibu-db 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/bajo/.alias +1 -0
- package/bajo/config.json +17 -0
- package/bajo/method/admin-menu.js +46 -0
- package/bajo/method/get-params.js +18 -0
- package/bajo/method/get-schema-ext.js +133 -0
- package/bajo/method/method-map.js +9 -0
- package/bajo/method/record/create.js +12 -0
- package/bajo/method/record/find-one.js +20 -0
- package/bajo/method/record/find.js +26 -0
- package/bajo/method/record/get.js +14 -0
- package/bajo/method/record/remove.js +10 -0
- package/bajo/method/record/update.js +12 -0
- package/bajo/method/stat/aggregate.js +13 -0
- package/bajo/method/stat/histogram.js +13 -0
- package/bajoI18N/resource/en-US.json +18 -0
- package/bajoI18N/resource/id.json +40 -0
- package/lib/crud/add-handler.js +39 -0
- package/lib/crud/all-handler.js +31 -0
- package/lib/crud/delete-handler.js +37 -0
- package/lib/crud/details-handler.js +18 -0
- package/lib/crud/edit-handler.js +33 -0
- package/lib/crud/export-handler.js +15 -0
- package/lib/crud/helper/add-ons-handler.js +46 -0
- package/lib/crud/helper/build-params.js +14 -0
- package/lib/crud/list-handler.js +27 -0
- package/lib/prep-crud.js +17 -0
- package/package.json +30 -0
- package/waibuBootstrap/theme/component/btn-add.js +15 -0
- package/waibuBootstrap/theme/component/btn-back.js +13 -0
- package/waibuBootstrap/theme/component/btn-clone.js +30 -0
- package/waibuBootstrap/theme/component/btn-columns.js +45 -0
- package/waibuBootstrap/theme/component/btn-delete.js +43 -0
- package/waibuBootstrap/theme/component/btn-details.js +32 -0
- package/waibuBootstrap/theme/component/btn-edit.js +56 -0
- package/waibuBootstrap/theme/component/btn-export.js +129 -0
- package/waibuBootstrap/theme/component/echarts.js +58 -0
- package/waibuBootstrap/theme/component/pagination.js +60 -0
- package/waibuBootstrap/theme/component/query.js +137 -0
- package/waibuBootstrap/theme/component/recs-info.js +50 -0
- package/waibuBootstrap/theme/component/table.js +161 -0
- package/waibuMpa/extend/waibuAdmin/route/@model/@action.js +14 -0
- package/waibuMpa/partial/crud/_addons.html +5 -0
- package/waibuMpa/partial/crud/_form.html +12 -0
- package/waibuMpa/partial/crud/add-handler.html +19 -0
- package/waibuMpa/partial/crud/details-handler.html +21 -0
- package/waibuMpa/partial/crud/echarts-window.html +9 -0
- package/waibuMpa/partial/crud/edit-handler.html +19 -0
- package/waibuMpa/partial/crud/list-handler.html +26 -0
- package/waibuMpa/sumba/route/export/@action.js +14 -0
- package/waibuMpa/template/crud/add.html +2 -0
- package/waibuMpa/template/crud/details.html +2 -0
- package/waibuMpa/template/crud/edit.html +2 -0
- package/waibuMpa/template/crud/list.html +2 -0
- package/waibuMpa/template/disabled.html +8 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
async function addOnsHandler ({ req, reply, data, schema }) {
|
|
2
|
+
const { base64JsonEncode } = this.app.waibuMpa
|
|
3
|
+
const { statAggregate } = this.app.waibuDb
|
|
4
|
+
const { get, map, pick, pullAt } = this.app.bajo.lib._
|
|
5
|
+
const opts = map(get(schema, 'view.stat.aggregate', []), item => {
|
|
6
|
+
const dbOpts = pick(item, ['fields', 'group', 'aggregate'])
|
|
7
|
+
const name = item.name ?? `field.${item.fields[0]}`
|
|
8
|
+
return { name, dbOpts }
|
|
9
|
+
})
|
|
10
|
+
if (opts.length === 0) return []
|
|
11
|
+
const dropped = []
|
|
12
|
+
for (const idx in opts) {
|
|
13
|
+
const o = opts[idx]
|
|
14
|
+
try {
|
|
15
|
+
const resp = await statAggregate({ model: schema.name, req, reply, options: o.dbOpts })
|
|
16
|
+
const data = []
|
|
17
|
+
for (const d of resp.data) {
|
|
18
|
+
const key = o.dbOpts.fields[0]
|
|
19
|
+
data.push({
|
|
20
|
+
name: d[key],
|
|
21
|
+
value: d[key + 'Count']
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
opts[idx].chartOpts = base64JsonEncode({
|
|
25
|
+
tooltip: {
|
|
26
|
+
trigger: 'item'
|
|
27
|
+
},
|
|
28
|
+
series: [{
|
|
29
|
+
type: 'pie',
|
|
30
|
+
data
|
|
31
|
+
}]
|
|
32
|
+
})
|
|
33
|
+
} catch (err) {
|
|
34
|
+
dropped.push(idx)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (dropped.length > 0) pullAt(opts, dropped)
|
|
38
|
+
return map(opts, o => {
|
|
39
|
+
return {
|
|
40
|
+
data: { option: o.chartOpts, name: o.name },
|
|
41
|
+
resource: 'waibuDb.partial:/crud/echarts-window.html'
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default addOnsHandler
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function buildParams ({ model, req, reply, action }) {
|
|
2
|
+
const { camelCase, kebabCase, map, upperFirst, get } = this.app.bajo.lib._
|
|
3
|
+
const { getSchema } = this.app.dobo
|
|
4
|
+
const [alias, ...names] = map(kebabCase(model).split('-'), n => upperFirst(n))
|
|
5
|
+
const schema = getSchema(camelCase(model), false)
|
|
6
|
+
const modelTitle = this.app[schema.ns].title + ': ' + names.join(' ')
|
|
7
|
+
const page = {
|
|
8
|
+
title: req.t(get(req, 'routeOptions.config.title', alias)),
|
|
9
|
+
modelTitle
|
|
10
|
+
}
|
|
11
|
+
return { page }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default buildParams
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
async function listHandler ({ req, reply, model, template, params = {}, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { recordFind, getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { get, merge, isArray, upperFirst } = this.app.bajo.lib._
|
|
5
|
+
const qsKey = this.app.waibu.config.qsKey
|
|
6
|
+
const options = { count: true }
|
|
7
|
+
model = model ?? pascalCase(req.params.model)
|
|
8
|
+
const { schema } = await getSchemaExt(model, 'list')
|
|
9
|
+
if (schema.disabled.includes('find')) return reply.view(templateDisabled, { action: 'list' })
|
|
10
|
+
for (const key of ['sort', 'limit', 'fields']) {
|
|
11
|
+
const sessKey = `wdb${model}${upperFirst(key)}`
|
|
12
|
+
if (!req.query[qsKey[key]]) req.query[qsKey[key]] = req.session[sessKey] ?? get(schema, `view.qs.${key}`)
|
|
13
|
+
else req.session[sessKey] = req.query[qsKey[key]]
|
|
14
|
+
}
|
|
15
|
+
if (!req.query[qsKey.page]) req.query[qsKey.page] = 1
|
|
16
|
+
// req.query.attachment = true
|
|
17
|
+
const list = await recordFind({ model, req, options })
|
|
18
|
+
let addOns = []
|
|
19
|
+
if (addOnsHandler) {
|
|
20
|
+
addOns = await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: list, schema })
|
|
21
|
+
if (!isArray(addOns)) addOns = [addOns]
|
|
22
|
+
}
|
|
23
|
+
merge(params, { list, schema, addOns })
|
|
24
|
+
return reply.view(template, params)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default listHandler
|
package/lib/prep-crud.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function prepCrud ({ model, body, id, req, options, args }) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { cloneDeep } = this.app.bajo.lib._
|
|
4
|
+
const opts = cloneDeep(options)
|
|
5
|
+
const params = this.getParams(req, ...args)
|
|
6
|
+
for (const k of ['count', 'fields']) {
|
|
7
|
+
opts[k] = opts[k] ?? params[k]
|
|
8
|
+
}
|
|
9
|
+
opts.dataOnly = false
|
|
10
|
+
opts.req = req
|
|
11
|
+
const recId = id ?? params.id ?? req.query.id
|
|
12
|
+
const name = pascalCase(model ?? params.model)
|
|
13
|
+
const input = body ?? params.body
|
|
14
|
+
return { name, recId, input, opts }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default prepCrud
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "waibu-db",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "DB Helper",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ardhi/waibu-db.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"db",
|
|
16
|
+
"waibu",
|
|
17
|
+
"web",
|
|
18
|
+
"webserver",
|
|
19
|
+
"bajo",
|
|
20
|
+
"framework",
|
|
21
|
+
"fastify",
|
|
22
|
+
"modular"
|
|
23
|
+
],
|
|
24
|
+
"author": "Ardhi Lukianto <ardhi@lukianto.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/ardhi/waibu-db/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/ardhi/waibu-db#readme"
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async function btnAdd (params = {}) {
|
|
2
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
3
|
+
params.noTag = true
|
|
4
|
+
const schema = get(this, 'locals.schema', {})
|
|
5
|
+
if (schema.view.disabled.includes('create')) {
|
|
6
|
+
params.html = ''
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Add')
|
|
10
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
11
|
+
if (!params.attr.href) params.attr.href = this._buildUrl({ base: 'add' })
|
|
12
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default btnAdd
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async function btnBack (params = {}) {
|
|
2
|
+
const { isEmpty } = this.plugin.app.bajo.lib._
|
|
3
|
+
const { attrToArray } = this.plugin.app.waibuMpa
|
|
4
|
+
params.noTag = true
|
|
5
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Back')
|
|
6
|
+
if (isEmpty(params.attr.icon)) params.attr.icon = 'arrowStart'
|
|
7
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
8
|
+
params.attr.excludeQs = ['mode', 'id', ...attrToArray(params.attr.excludeQs ?? '')]
|
|
9
|
+
if (!params.attr.href) params.attr.href = this._buildUrl({ base: 'list', exclude: params.attr.excludeQs })
|
|
10
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default btnBack
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
async function btnClone (params = {}) {
|
|
2
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
3
|
+
params.noTag = true
|
|
4
|
+
const schema = get(this, 'locals.schema', {})
|
|
5
|
+
if (schema.view.disabled.includes('create')) {
|
|
6
|
+
params.html = ''
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Clone')
|
|
10
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
11
|
+
if (!params.attr.href) params.attr.href = this._buildUrl({ base: 'add', exclude: ['id'] }) + '&mode=clone'
|
|
12
|
+
if (params.attr.onList) {
|
|
13
|
+
params.attr['x-ref'] = 'clone'
|
|
14
|
+
params.attr.disabled = true
|
|
15
|
+
params.attr['x-data'] = `{
|
|
16
|
+
path: '${params.attr.href}'
|
|
17
|
+
}`
|
|
18
|
+
params.attr['@on-selection.window'] = `
|
|
19
|
+
const recId = $event.detail[0] ?? ''
|
|
20
|
+
if ($event.detail.length === 1) $refs.clone.classList.remove('disabled')
|
|
21
|
+
else $refs.clone.classList.add('disabled')
|
|
22
|
+
$refs.clone.href = path + '&id=' + recId
|
|
23
|
+
`
|
|
24
|
+
} else {
|
|
25
|
+
params.attr.href += '&id=' + this.req.query.id
|
|
26
|
+
}
|
|
27
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default btnClone
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
async function btnColumns (params = {}) {
|
|
2
|
+
const { get, isEmpty, without } = this.plugin.app.bajo.lib._
|
|
3
|
+
const { jsonStringify } = this.plugin.app.waibuMpa
|
|
4
|
+
const qsKey = this.plugin.app.waibu.config.qsKey
|
|
5
|
+
const schema = get(this, 'locals.schema', {})
|
|
6
|
+
if (schema.view.disabled.includes('find')) {
|
|
7
|
+
params.html = ''
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
let fields = without(get(this, `locals._meta.query.${qsKey.fields}`, '').split(','), '')
|
|
11
|
+
if (isEmpty(fields)) fields = schema.view.fields
|
|
12
|
+
const items = []
|
|
13
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
14
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Columns')
|
|
15
|
+
for (const f of schema.view.fields) {
|
|
16
|
+
if (f === 'id') {
|
|
17
|
+
items.push(await this.buildTag({ tag: 'formCheck', attr: { checked: true, label: this.req.t('ID'), value: f, disabled: true } }))
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
const attr = { 'x-model': 'selected', label: this.req.t(`field.${f}`), value: f }
|
|
21
|
+
if (fields.includes(f)) attr.checked = true
|
|
22
|
+
items.push(await this.buildTag({ tag: 'formCheck', attr }))
|
|
23
|
+
}
|
|
24
|
+
const href = this._buildUrl({ exclude: [qsKey.fields] })
|
|
25
|
+
const html = ['<form class="mt-1 mb-2 mx-3" ']
|
|
26
|
+
html.push(`x-data="{
|
|
27
|
+
selected: ${jsonStringify(fields, true)},
|
|
28
|
+
all: ${jsonStringify(schema.view.fields, true)}
|
|
29
|
+
}"`)
|
|
30
|
+
html.push(`x-init="
|
|
31
|
+
$refs.apply.href = '${href}&${qsKey.fields}=' + selected.join(',')
|
|
32
|
+
$watch('selected', v => {
|
|
33
|
+
$refs.apply.href = '${href}&${qsKey.fields}=' + v.join(',')
|
|
34
|
+
})
|
|
35
|
+
">`)
|
|
36
|
+
html.push(...items)
|
|
37
|
+
const attr = { size: 'sm', 'x-ref': 'apply', margin: 'top-2', color: params.attr.applyColor ?? 'primary', icon: params.attr.applyIcon ?? 'arrowsStartEnd', href }
|
|
38
|
+
html.push(await this.buildTag({ tag: 'btn', attr, html: this.req.t('Apply') }))
|
|
39
|
+
html.push('</form>')
|
|
40
|
+
params.attr.autoClose = 'outside'
|
|
41
|
+
params.html = await this.buildTag({ tag: 'dropdown', attr: params.attr, html: html.join('\n') })
|
|
42
|
+
params.noTag = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default btnColumns
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
async function btnDelete (params = {}) {
|
|
2
|
+
const { generateId } = this.plugin.app.bajo
|
|
3
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
4
|
+
params.noTag = true
|
|
5
|
+
const schema = get(this, 'locals.schema', {})
|
|
6
|
+
if (schema.view.disabled.includes('remove')) {
|
|
7
|
+
params.html = ''
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Delete')
|
|
11
|
+
params.attr.color = params.attr.color ?? 'danger-outline'
|
|
12
|
+
params.attr.id = generateId('alpha')
|
|
13
|
+
if (params.attr.onList) {
|
|
14
|
+
params.attr.disabled = true
|
|
15
|
+
params.attr['x-data'] = `{
|
|
16
|
+
selected: [],
|
|
17
|
+
remove (ids) {
|
|
18
|
+
wmpa.postForm({ ids }, '${this._buildUrl({ base: 'delete' })}')
|
|
19
|
+
}
|
|
20
|
+
}`
|
|
21
|
+
params.attr['@on-selection.window'] = `
|
|
22
|
+
const el = document.getElementById('${params.attr.id}')
|
|
23
|
+
selected = $event.detail
|
|
24
|
+
if (selected.length > 0) el.classList.remove('disabled')
|
|
25
|
+
else el.classList.add('disabled')
|
|
26
|
+
`
|
|
27
|
+
} else {
|
|
28
|
+
params.attr['x-data'] = `{
|
|
29
|
+
selected: ['${this.req.query.id}'],
|
|
30
|
+
remove (modalId, ids) {
|
|
31
|
+
wmpa.postForm({ ids }, '${this._buildUrl({ base: 'delete', exclude: ['id', 'page'] })}')
|
|
32
|
+
}
|
|
33
|
+
}`
|
|
34
|
+
}
|
|
35
|
+
const msg = 'You\'re about to remove one or more records. Are you really sure to do this?'
|
|
36
|
+
params.attr['@click'] = `
|
|
37
|
+
const opts = selected.join(',')
|
|
38
|
+
const id = await wbs.confirmation(\`${this.req.t(msg)}\`, { ok: '${params.attr.id}:remove', close: 'y', opts })
|
|
39
|
+
`
|
|
40
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default btnDelete
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
async function btnDetails (params = {}) {
|
|
2
|
+
const { generateId } = this.plugin.app.bajo
|
|
3
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
4
|
+
params.noTag = true
|
|
5
|
+
const schema = get(this, 'locals.schema', {})
|
|
6
|
+
if (schema.view.disabled.includes('update')) {
|
|
7
|
+
params.html = ''
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Details')
|
|
11
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
12
|
+
params.attr.id = generateId('alpha')
|
|
13
|
+
if (!params.attr.href) params.attr.href = this._buildUrl({ base: 'details', exclude: ['id'] })
|
|
14
|
+
if (params.attr.onList) {
|
|
15
|
+
params.attr.disabled = true
|
|
16
|
+
params.attr['x-ref'] = 'details'
|
|
17
|
+
params.attr['x-data'] = `{
|
|
18
|
+
path: '${params.attr.href}'
|
|
19
|
+
}`
|
|
20
|
+
params.attr['@on-selection.window'] = `
|
|
21
|
+
const recId = $event.detail[0] ?? ''
|
|
22
|
+
if ($event.detail.length === 1) $refs.details.classList.remove('disabled')
|
|
23
|
+
else $refs.details.classList.add('disabled')
|
|
24
|
+
$refs.details.href = path + '&id=' + recId
|
|
25
|
+
`
|
|
26
|
+
} else {
|
|
27
|
+
params.attr.href += '&id=' + this.req.query.id
|
|
28
|
+
}
|
|
29
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default btnDetails
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
async function btnEdit (params = {}) {
|
|
2
|
+
const { generateId } = this.plugin.app.bajo
|
|
3
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
4
|
+
params.noTag = true
|
|
5
|
+
const schema = get(this, 'locals.schema', {})
|
|
6
|
+
if (schema.view.disabled.includes('update')) {
|
|
7
|
+
params.html = ''
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
if (isEmpty(params.attr.content)) params.attr.content = this.req.t('Edit')
|
|
11
|
+
params.attr.color = params.attr.color ?? 'secondary-outline'
|
|
12
|
+
params.attr.id = generateId('alpha')
|
|
13
|
+
if (!params.attr.href) params.attr.href = this._buildUrl({ base: 'edit', exclude: ['id'] })
|
|
14
|
+
if (params.attr.onList) {
|
|
15
|
+
params.attr.split = true
|
|
16
|
+
params.attr.disabled = true
|
|
17
|
+
params.attr['x-data'] = `{
|
|
18
|
+
path: '${params.attr.href}'
|
|
19
|
+
}`
|
|
20
|
+
if (params.attr.noClone) {
|
|
21
|
+
params.attr['@on-selection.window'] = `
|
|
22
|
+
const recId = $event.detail[0] ?? ''
|
|
23
|
+
const el = document.getElementById('${params.attr.id}')
|
|
24
|
+
if ($event.detail.length === 1) el.classList.remove('disabled')
|
|
25
|
+
else el.classList.add('disabled')
|
|
26
|
+
el.href = path + '&id=' + recId
|
|
27
|
+
`
|
|
28
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
29
|
+
} else {
|
|
30
|
+
params.attr['@on-selection.window'] = `
|
|
31
|
+
const recId = $event.detail[0] ?? ''
|
|
32
|
+
const elId = '${params.attr.id}'
|
|
33
|
+
for (const id of [elId, elId + '-split']) {
|
|
34
|
+
const el = document.getElementById(id)
|
|
35
|
+
if ($event.detail.length === 1) el.classList.remove('disabled')
|
|
36
|
+
else el.classList.add('disabled')
|
|
37
|
+
const href = path + '&id=' + recId
|
|
38
|
+
if (id.slice(-6) === '-split') {
|
|
39
|
+
const selector = '#' + id.replace('-split', '-menu') + ' a.dropdown-item'
|
|
40
|
+
const item = document.querySelector(selector)
|
|
41
|
+
item.href = href.replace('/edit', '/add') + '&mode=clone'
|
|
42
|
+
} else el.href = href
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
const html = [
|
|
46
|
+
await this.buildTag({ tag: 'dropdownItem', attr: { content: this.req.t('Add as New Clone') } })
|
|
47
|
+
]
|
|
48
|
+
params.html = await this.buildTag({ tag: 'dropdown', attr: params.attr, html: html.join('\n') })
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
params.attr.href += '&id=' + this.req.query.id
|
|
52
|
+
params.html = await this.buildTag({ tag: 'btn', attr: params.attr, html: params.html })
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default btnEdit
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
async function btnExport (params = {}) {
|
|
2
|
+
const { isEmpty, get } = this.plugin.app.bajo.lib._
|
|
3
|
+
params.noTag = true
|
|
4
|
+
const schema = get(this, 'locals.schema', {})
|
|
5
|
+
if (schema.view.disabled.includes('find')) {
|
|
6
|
+
params.html = ''
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
if (isEmpty(params.attr.launch)) params.attr.launch = this.req.t('Export')
|
|
10
|
+
params.attr.launchColor = params.attr.launchColor ?? 'secondary-outline'
|
|
11
|
+
params.attr.title = this.req.t('Data Export')
|
|
12
|
+
const html = await this.buildSentence(`
|
|
13
|
+
<c:div x-data="{
|
|
14
|
+
delivery: 'clipboard',
|
|
15
|
+
options: [],
|
|
16
|
+
ftype: 'json',
|
|
17
|
+
toggle (val) {
|
|
18
|
+
if (val === 'clipboard') {
|
|
19
|
+
$refs.zip.setAttribute('disabled', '')
|
|
20
|
+
$refs.xlsx.setAttribute('disabled', '')
|
|
21
|
+
$refs.xml.setAttribute('disabled', '')
|
|
22
|
+
_.pull(this.options, 'zip')
|
|
23
|
+
if (!['json', 'csv'].includes(this.ftype)) this.ftype = 'json'
|
|
24
|
+
} else {
|
|
25
|
+
$refs.zip.removeAttribute('disabled')
|
|
26
|
+
$refs.xlsx.removeAttribute('disabled')
|
|
27
|
+
$refs.xml.removeAttribute('disabled')
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
extractForm (selector) {
|
|
31
|
+
let item = {}
|
|
32
|
+
const els = document.querySelectorAll(selector + ' [data-value]')
|
|
33
|
+
for (const el of els) {
|
|
34
|
+
const value = this.options.includes('fvalue') ? el.getAttribute('value') : wmpa.parseValue(el.dataset.value, el.dataset.type)
|
|
35
|
+
let key = el.getAttribute('name')
|
|
36
|
+
if (this.options.includes('fkey')) {
|
|
37
|
+
try {
|
|
38
|
+
const elLabel = document.querySelector('label[for=' + el.getAttribute('id') + ']')
|
|
39
|
+
key = elLabel.innerText
|
|
40
|
+
} catch (err) {}
|
|
41
|
+
}
|
|
42
|
+
item[key] = value
|
|
43
|
+
}
|
|
44
|
+
return this.ftype === 'csv' ? CSVJSON.json2csv(item) : JSON.stringify(item)
|
|
45
|
+
},
|
|
46
|
+
extractTable (selector) {
|
|
47
|
+
let items = []
|
|
48
|
+
let checker = false
|
|
49
|
+
const keys = []
|
|
50
|
+
const types = []
|
|
51
|
+
let els = document.querySelectorAll(selector + ' thead th')
|
|
52
|
+
for (const el of els) {
|
|
53
|
+
keys.push(this.options.includes('fkey') ? el.innerText : el.dataset.key)
|
|
54
|
+
types.push(el.dataset.type)
|
|
55
|
+
}
|
|
56
|
+
if (_.isEmpty(keys[0])) {
|
|
57
|
+
checker = true
|
|
58
|
+
keys.shift()
|
|
59
|
+
types.shift()
|
|
60
|
+
}
|
|
61
|
+
els = document.querySelectorAll(selector + ' tbody tr')
|
|
62
|
+
for (const el of els) {
|
|
63
|
+
let data = []
|
|
64
|
+
_.each(el.children, (v, i) => {
|
|
65
|
+
if ((i + '') === '0' && checker) return undefined
|
|
66
|
+
data.push(this.options.includes('fvalue') ? v.innerText : wmpa.parseValue(v.dataset.value, types[parseInt(i - 1)]))
|
|
67
|
+
})
|
|
68
|
+
const item = {}
|
|
69
|
+
for (const i in keys) {
|
|
70
|
+
item[keys[i]] = data[i]
|
|
71
|
+
}
|
|
72
|
+
items.push(item)
|
|
73
|
+
}
|
|
74
|
+
return this.ftype === 'csv' ? CSVJSON.json2csv(items) : JSON.stringify(items)
|
|
75
|
+
},
|
|
76
|
+
async submit () {
|
|
77
|
+
const instance = wbs.getInstance('Modal', $refs.export)
|
|
78
|
+
const handler = '${params.attr.handler ?? ''}'
|
|
79
|
+
if (this.delivery === 'clipboard') {
|
|
80
|
+
const selector = '${params.attr.selector}'
|
|
81
|
+
if (_.isEmpty(selector)) {
|
|
82
|
+
await wbs.notify('Cant get data selector', { type: 'danger' })
|
|
83
|
+
} else {
|
|
84
|
+
const item = handler === 'list' ? this.extractTable(selector) : this.extractForm(selector)
|
|
85
|
+
await wbs.copyToClipboard(item)
|
|
86
|
+
}
|
|
87
|
+
instance.hide()
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
wmpa.postForm({ options: this.options.join(','), ftype: this.ftype, handler }, '${this._buildUrl({ base: 'export' })}')
|
|
91
|
+
instance.hide()
|
|
92
|
+
}
|
|
93
|
+
}" x-init="
|
|
94
|
+
toggle(delivery)
|
|
95
|
+
$watch('delivery', val => toggle(val))
|
|
96
|
+
">
|
|
97
|
+
<c:grid-row gutter="2">
|
|
98
|
+
<c:grid-col col="6-md">
|
|
99
|
+
<c:fieldset t:legend="Delivery" legend-type="6">
|
|
100
|
+
<c:form-radio x-model="delivery" value="file" t:label="Save as File" />
|
|
101
|
+
<c:form-radio x-model="delivery" value="clipboard" t:label="Copy to Clipboard" />
|
|
102
|
+
</c:fieldset>
|
|
103
|
+
<c:fieldset t:legend="Options" legend-type="6" margin="top-2">
|
|
104
|
+
<c:form-check x-ref="fkey" x-model="options" value="fkey" t:label="Formatted Field" />
|
|
105
|
+
<c:form-check x-ref="fvalue" x-model="options" value="fvalue" t:label="Formatted Value" />
|
|
106
|
+
<c:form-check x-ref="zip" x-model="options" value="zip" t:label="Zipped" />
|
|
107
|
+
</c:fieldset>
|
|
108
|
+
</c:grid-col>
|
|
109
|
+
<c:grid-col col="6-md">
|
|
110
|
+
<c:fieldset t:legend="File Type" legend-type="6">
|
|
111
|
+
<c:form-radio x-ref="xlsx" x-model="ftype" value="xlsx" t:label="Excel XLSX" />
|
|
112
|
+
<c:form-radio x-ref="csv" x-model="ftype" value="csv" t:label="CSV" />
|
|
113
|
+
<c:form-radio x-ref="xml" x-model="ftype" value="xml" t:label="XML" />
|
|
114
|
+
<c:form-radio x-ref="json" x-model="ftype" value="json" t:label="JSON" />
|
|
115
|
+
</c:fieldset />
|
|
116
|
+
</c:grid-col>
|
|
117
|
+
</c:grid-row>
|
|
118
|
+
<c:div flex="justify-content:end" margin="top-3">
|
|
119
|
+
<c:btn color="secondary" t:content="Close" dismiss />
|
|
120
|
+
<c:btn color="primary" t:content="Submit" margin="start-2" @click="await submit()" />
|
|
121
|
+
</c:div>
|
|
122
|
+
</c:div>
|
|
123
|
+
`)
|
|
124
|
+
params.attr['x-data'] = true
|
|
125
|
+
params.attr['x-ref'] = 'export'
|
|
126
|
+
params.html = await this.buildTag({ tag: 'modal', attr: params.attr, html })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default btnExport
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const defOption = {
|
|
2
|
+
grid: {
|
|
3
|
+
top: 8,
|
|
4
|
+
bottom: 20,
|
|
5
|
+
left: 25,
|
|
6
|
+
right: 0
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const chart = {
|
|
11
|
+
scripts: [
|
|
12
|
+
'^waibuDb.virtual:/echarts/echarts.min.js'
|
|
13
|
+
],
|
|
14
|
+
handler: async function (params = {}) {
|
|
15
|
+
const { defaultsDeep, generateId } = this.plugin.app.bajo
|
|
16
|
+
const { base64JsonDecode, jsonStringify } = this.plugin.app.waibuMpa
|
|
17
|
+
const { cloneDeep } = this.plugin.app.bajo.lib._
|
|
18
|
+
this._normalizeAttr(params, { tag: 'div' })
|
|
19
|
+
params.attr.dim = params.attr.dim ?? 'width:100 height:100'
|
|
20
|
+
params.attr.id = generateId('alpha')
|
|
21
|
+
params.attr['x-data'] = `chart${params.attr.id}`
|
|
22
|
+
params.attr['@resize.window.debounce.500ms'] = `
|
|
23
|
+
if (chart) {
|
|
24
|
+
chart.resize()
|
|
25
|
+
}
|
|
26
|
+
`
|
|
27
|
+
let option = cloneDeep(defOption)
|
|
28
|
+
if (params.attr.option === true) params.attr.option = 'e30='
|
|
29
|
+
if (params.attr.option) option = defaultsDeep(base64JsonDecode(params.attr.option), defOption)
|
|
30
|
+
params.attr['x-init'] = `
|
|
31
|
+
$watch('option', val => {
|
|
32
|
+
if (chart) chart.setOption(val)
|
|
33
|
+
})
|
|
34
|
+
`
|
|
35
|
+
params.append = `
|
|
36
|
+
<script>
|
|
37
|
+
document.addEventListener('alpine:init', () => {
|
|
38
|
+
Alpine.data('chart${params.attr.id}', () => {
|
|
39
|
+
let chart
|
|
40
|
+
return {
|
|
41
|
+
init () {
|
|
42
|
+
const el = document.getElementById('${params.attr.id}')
|
|
43
|
+
chart = echarts.init(el, null, { renderer: 'canvas' })
|
|
44
|
+
chart.setOption(this.option)
|
|
45
|
+
},
|
|
46
|
+
get chart () {
|
|
47
|
+
return chart
|
|
48
|
+
},
|
|
49
|
+
option: ${jsonStringify(option, true)}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
</script>
|
|
54
|
+
`
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default chart
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function getUrlOpts (params = {}) {
|
|
2
|
+
const { get } = this.plugin.app.bajo.lib._
|
|
3
|
+
return {
|
|
4
|
+
params,
|
|
5
|
+
excludes: [
|
|
6
|
+
get(this, 'plugin.app.waibu.config.qsKey.lang', 'lang'),
|
|
7
|
+
get(this, 'plugin.app.waibuMpa.config.darkMode.qsKey', 'dark-mode')
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function pagination (params = {}) {
|
|
13
|
+
const { attrToObject, paginationLayout, groupAttrs } = this.plugin.app.waibuMpa
|
|
14
|
+
const { get, isNumber } = this.plugin.app.bajo.lib._
|
|
15
|
+
const schema = get(this, 'locals.schema', {})
|
|
16
|
+
if (schema.view.disabled.includes('find')) {
|
|
17
|
+
params.html = ''
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
let { count, limit, page } = attrToObject(params.attr.options)
|
|
21
|
+
count = count ?? get(this, 'locals.list.count', 0)
|
|
22
|
+
limit = limit ?? get(this, 'locals.list.limit', 25)
|
|
23
|
+
page = page ?? get(this, 'locals.list.page', 1)
|
|
24
|
+
const pages = paginationLayout(count, limit, page) ?? []
|
|
25
|
+
params.noTag = true
|
|
26
|
+
const group = groupAttrs(params.attr, ['pagination'])
|
|
27
|
+
const html = []
|
|
28
|
+
let icon
|
|
29
|
+
let attr
|
|
30
|
+
if (params.attr.first) {
|
|
31
|
+
icon = await this.buildTag({ tag: 'icon', attr: { name: params.attr.firstIcon ?? 'playSkipStart' } })
|
|
32
|
+
attr = { disabled: page <= pages[0], href: this._buildUrl(getUrlOpts.call(this, { page: 1 })) }
|
|
33
|
+
html.push(await this.buildTag({ tag: 'paginationItem', attr, html: icon }))
|
|
34
|
+
}
|
|
35
|
+
if (params.attr.prev) {
|
|
36
|
+
icon = await this.buildTag({ tag: 'icon', attr: { name: params.attr.prevIcon ?? 'playFastBackward' } })
|
|
37
|
+
attr = { disabled: page <= pages[0], href: this._buildUrl(getUrlOpts.call(this, { page: page - 1 })) }
|
|
38
|
+
html.push(await this.buildTag({ tag: 'paginationItem', attr, html: icon }))
|
|
39
|
+
}
|
|
40
|
+
if (!params.attr.noPages) {
|
|
41
|
+
for (const p of pages) {
|
|
42
|
+
attr = { disabled: p === '...', href: this._buildUrl(getUrlOpts.call(this, { page: p })), active: p === page }
|
|
43
|
+
html.push(await this.buildTag({ tag: 'paginationItem', attr, html: isNumber(p) ? this.req.format(p, 'integer') : p }))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (params.attr.next) {
|
|
47
|
+
icon = await this.buildTag({ tag: 'icon', attr: { name: params.attr.nextIcon ?? 'playFastForward' } })
|
|
48
|
+
attr = { disabled: page >= pages[pages.length - 1], href: this._buildUrl(getUrlOpts.call(this, { page: page + 1 })) }
|
|
49
|
+
html.push(await this.buildTag({ tag: 'paginationItem', attr, html: icon }))
|
|
50
|
+
}
|
|
51
|
+
if (params.attr.last) {
|
|
52
|
+
icon = await this.buildTag({ tag: 'icon', attr: { name: params.attr.lastIcon ?? 'playSkipEnd' } })
|
|
53
|
+
attr = { disabled: page >= pages[pages.length - 1], href: this._buildUrl(getUrlOpts.call(this, { page: pages[pages.length - 1] })) }
|
|
54
|
+
html.push(await this.buildTag({ tag: 'paginationItem', attr, html: icon }))
|
|
55
|
+
}
|
|
56
|
+
params.attr = group.pagination
|
|
57
|
+
params.html = await this.buildTag({ tag: 'pagination', attr: params.attr, html: html.join('\n') })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default pagination
|