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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/bajo/.alias +1 -0
  4. package/bajo/config.json +17 -0
  5. package/bajo/method/admin-menu.js +46 -0
  6. package/bajo/method/get-params.js +18 -0
  7. package/bajo/method/get-schema-ext.js +133 -0
  8. package/bajo/method/method-map.js +9 -0
  9. package/bajo/method/record/create.js +12 -0
  10. package/bajo/method/record/find-one.js +20 -0
  11. package/bajo/method/record/find.js +26 -0
  12. package/bajo/method/record/get.js +14 -0
  13. package/bajo/method/record/remove.js +10 -0
  14. package/bajo/method/record/update.js +12 -0
  15. package/bajo/method/stat/aggregate.js +13 -0
  16. package/bajo/method/stat/histogram.js +13 -0
  17. package/bajoI18N/resource/en-US.json +18 -0
  18. package/bajoI18N/resource/id.json +40 -0
  19. package/lib/crud/add-handler.js +39 -0
  20. package/lib/crud/all-handler.js +31 -0
  21. package/lib/crud/delete-handler.js +37 -0
  22. package/lib/crud/details-handler.js +18 -0
  23. package/lib/crud/edit-handler.js +33 -0
  24. package/lib/crud/export-handler.js +15 -0
  25. package/lib/crud/helper/add-ons-handler.js +46 -0
  26. package/lib/crud/helper/build-params.js +14 -0
  27. package/lib/crud/list-handler.js +27 -0
  28. package/lib/prep-crud.js +17 -0
  29. package/package.json +30 -0
  30. package/waibuBootstrap/theme/component/btn-add.js +15 -0
  31. package/waibuBootstrap/theme/component/btn-back.js +13 -0
  32. package/waibuBootstrap/theme/component/btn-clone.js +30 -0
  33. package/waibuBootstrap/theme/component/btn-columns.js +45 -0
  34. package/waibuBootstrap/theme/component/btn-delete.js +43 -0
  35. package/waibuBootstrap/theme/component/btn-details.js +32 -0
  36. package/waibuBootstrap/theme/component/btn-edit.js +56 -0
  37. package/waibuBootstrap/theme/component/btn-export.js +129 -0
  38. package/waibuBootstrap/theme/component/echarts.js +58 -0
  39. package/waibuBootstrap/theme/component/pagination.js +60 -0
  40. package/waibuBootstrap/theme/component/query.js +137 -0
  41. package/waibuBootstrap/theme/component/recs-info.js +50 -0
  42. package/waibuBootstrap/theme/component/table.js +161 -0
  43. package/waibuMpa/extend/waibuAdmin/route/@model/@action.js +14 -0
  44. package/waibuMpa/partial/crud/_addons.html +5 -0
  45. package/waibuMpa/partial/crud/_form.html +12 -0
  46. package/waibuMpa/partial/crud/add-handler.html +19 -0
  47. package/waibuMpa/partial/crud/details-handler.html +21 -0
  48. package/waibuMpa/partial/crud/echarts-window.html +9 -0
  49. package/waibuMpa/partial/crud/edit-handler.html +19 -0
  50. package/waibuMpa/partial/crud/list-handler.html +26 -0
  51. package/waibuMpa/sumba/route/export/@action.js +14 -0
  52. package/waibuMpa/template/crud/add.html +2 -0
  53. package/waibuMpa/template/crud/details.html +2 -0
  54. package/waibuMpa/template/crud/edit.html +2 -0
  55. package/waibuMpa/template/crud/list.html +2 -0
  56. 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
@@ -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