@operato/data-grist 0.3.13 → 0.3.17

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 (126) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/assets/images/no-image.png +0 -0
  3. package/custom-elements.json +2382 -1803
  4. package/demo/index.html +41 -62
  5. package/dist/src/configure/list-option-builder.js +0 -2
  6. package/dist/src/configure/list-option-builder.js.map +1 -1
  7. package/dist/src/configure/zero-config.d.ts +0 -1
  8. package/dist/src/configure/zero-config.js +0 -2
  9. package/dist/src/configure/zero-config.js.map +1 -1
  10. package/dist/src/data-card/data-card.d.ts +3 -6
  11. package/dist/src/data-card/data-card.js +3 -131
  12. package/dist/src/data-card/data-card.js.map +1 -1
  13. package/dist/src/data-card/record-card.d.ts +0 -3
  14. package/dist/src/data-card/record-card.js +22 -71
  15. package/dist/src/data-card/record-card.js.map +1 -1
  16. package/dist/src/data-grid/data-grid-body.d.ts +7 -1
  17. package/dist/src/data-grid/data-grid-body.js +26 -5
  18. package/dist/src/data-grid/data-grid-body.js.map +1 -1
  19. package/dist/src/data-grid/data-grid-field.js +1 -1
  20. package/dist/src/data-grid/data-grid-field.js.map +1 -1
  21. package/dist/src/data-grid/data-grid-header.d.ts +1 -0
  22. package/dist/src/data-grid/data-grid-header.js +9 -1
  23. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  24. package/dist/src/data-grid/data-grid.d.ts +8 -4
  25. package/dist/src/data-grid/data-grid.js +12 -132
  26. package/dist/src/data-grid/data-grid.js.map +1 -1
  27. package/dist/src/data-grist.d.ts +1 -0
  28. package/dist/src/data-grist.js +3 -0
  29. package/dist/src/data-grist.js.map +1 -1
  30. package/dist/src/data-list/data-list.d.ts +3 -6
  31. package/dist/src/data-list/data-list.js +3 -131
  32. package/dist/src/data-list/data-list.js.map +1 -1
  33. package/dist/src/data-list/record-partial.d.ts +0 -2
  34. package/dist/src/data-list/record-partial.js +7 -58
  35. package/dist/src/data-list/record-partial.js.map +1 -1
  36. package/dist/src/data-manipulator.d.ts +20 -0
  37. package/dist/src/data-manipulator.js +148 -0
  38. package/dist/src/data-manipulator.js.map +1 -0
  39. package/dist/src/editors/image-editor.d.ts +9 -0
  40. package/dist/src/editors/image-editor.js +53 -0
  41. package/dist/src/editors/image-editor.js.map +1 -0
  42. package/dist/src/editors/image-input.d.ts +7 -0
  43. package/dist/src/editors/image-input.js +31 -0
  44. package/dist/src/editors/image-input.js.map +1 -0
  45. package/dist/src/editors/index.d.ts +1 -0
  46. package/dist/src/editors/index.js +1 -0
  47. package/dist/src/editors/index.js.map +1 -1
  48. package/dist/src/editors/input-editors copy.d.ts +75 -0
  49. package/dist/src/editors/input-editors copy.js +373 -0
  50. package/dist/src/editors/input-editors copy.js.map +1 -0
  51. package/dist/src/editors/input-editors.d.ts +1 -8
  52. package/dist/src/editors/input-editors.js +3 -47
  53. package/dist/src/editors/input-editors.js.map +1 -1
  54. package/dist/src/editors/registry.d.ts +1 -1
  55. package/dist/src/editors/registry.js +2 -1
  56. package/dist/src/editors/registry.js.map +1 -1
  57. package/dist/src/handlers/record-view-handler.d.ts +1 -2
  58. package/dist/src/handlers/record-view-handler.js +5 -35
  59. package/dist/src/handlers/record-view-handler.js.map +1 -1
  60. package/dist/src/handlers/select-row-toggle.d.ts +1 -1
  61. package/dist/src/handlers/select-row-toggle.js.map +1 -1
  62. package/dist/src/record-view/event-handlers/record-view-body-click-handler.d.ts +7 -0
  63. package/dist/src/record-view/event-handlers/record-view-body-click-handler.js +24 -0
  64. package/dist/src/record-view/event-handlers/record-view-body-click-handler.js.map +1 -0
  65. package/dist/src/record-view/event-handlers/record-view-body-keydown-handler.d.ts +7 -0
  66. package/dist/src/record-view/event-handlers/record-view-body-keydown-handler.js +22 -0
  67. package/dist/src/record-view/event-handlers/record-view-body-keydown-handler.js.map +1 -0
  68. package/dist/src/record-view/index.d.ts +1 -0
  69. package/dist/src/record-view/index.js +1 -0
  70. package/dist/src/record-view/index.js.map +1 -1
  71. package/dist/src/record-view/record-creator copy.d.ts +13 -0
  72. package/dist/src/record-view/record-creator copy.js +90 -0
  73. package/dist/src/record-view/record-creator copy.js.map +1 -0
  74. package/dist/src/record-view/record-creator-backup.d.ts +13 -0
  75. package/dist/src/record-view/record-creator-backup.js +90 -0
  76. package/dist/src/record-view/record-creator-backup.js.map +1 -0
  77. package/dist/src/record-view/record-creator.d.ts +16 -0
  78. package/dist/src/record-view/record-creator.js +145 -0
  79. package/dist/src/record-view/record-creator.js.map +1 -0
  80. package/dist/src/record-view/record-view-body.d.ts +3 -4
  81. package/dist/src/record-view/record-view-body.js +15 -42
  82. package/dist/src/record-view/record-view-body.js.map +1 -1
  83. package/dist/src/record-view/record-view-handler.d.ts +9 -0
  84. package/dist/src/record-view/record-view-handler.js +57 -0
  85. package/dist/src/record-view/record-view-handler.js.map +1 -0
  86. package/dist/src/record-view/record-view.d.ts +5 -1
  87. package/dist/src/record-view/record-view.js +61 -36
  88. package/dist/src/record-view/record-view.js.map +1 -1
  89. package/dist/src/renderers/image-renderer.js +12 -4
  90. package/dist/src/renderers/image-renderer.js.map +1 -1
  91. package/dist/src/sorters/sorters-control.d.ts +12 -0
  92. package/dist/src/sorters/sorters-control.js +106 -0
  93. package/dist/src/sorters/sorters-control.js.map +1 -0
  94. package/dist/src/types.d.ts +2 -3
  95. package/dist/src/types.js.map +1 -1
  96. package/dist/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +10 -8
  98. package/src/configure/list-option-builder.ts +0 -2
  99. package/src/configure/zero-config.ts +0 -2
  100. package/src/data-card/data-card.ts +4 -158
  101. package/src/data-card/record-card.ts +30 -87
  102. package/src/data-grid/data-grid-body.ts +43 -6
  103. package/src/data-grid/data-grid-field.ts +1 -1
  104. package/src/data-grid/data-grid-header.ts +11 -1
  105. package/src/data-grid/data-grid.ts +25 -143
  106. package/src/data-grist.ts +4 -0
  107. package/src/data-list/data-list.ts +4 -158
  108. package/src/data-list/record-partial.ts +14 -73
  109. package/src/data-manipulator.ts +201 -0
  110. package/src/editors/image-input.ts +29 -0
  111. package/src/editors/index.ts +1 -0
  112. package/src/editors/input-editors.ts +5 -48
  113. package/src/editors/registry.ts +3 -4
  114. package/src/handlers/record-view-handler.ts +8 -44
  115. package/src/handlers/select-row-toggle.ts +1 -2
  116. package/src/record-view/event-handlers/record-view-body-click-handler.ts +30 -0
  117. package/src/record-view/event-handlers/record-view-body-keydown-handler.ts +26 -0
  118. package/src/record-view/index.ts +1 -0
  119. package/src/record-view/record-creator.ts +180 -0
  120. package/src/record-view/record-view-body.ts +16 -55
  121. package/src/record-view/record-view-handler.ts +86 -0
  122. package/src/record-view/record-view.ts +69 -42
  123. package/src/renderers/image-renderer.ts +14 -5
  124. package/src/sorters/sorters-control.ts +111 -0
  125. package/src/types.ts +10 -3
  126. package/yarn-error.log +16718 -0
@@ -0,0 +1,180 @@
1
+ import '@material/mwc-icon'
2
+ import './record-view'
3
+
4
+ import { html, LitElement } from 'lit'
5
+ import { customElement, property, state } from 'lit/decorators.js'
6
+
7
+ import { OxPopup } from '@operato/popup'
8
+
9
+ import { DataGrist } from '../data-grist'
10
+ import { ColumnConfig, GristRecord } from '../types'
11
+ import { RecordView } from './record-view'
12
+
13
+ @customElement('ox-record-creator')
14
+ export class RecordCreator extends LitElement {
15
+ @state() grist?: DataGrist
16
+
17
+ @property({ type: Object }) callback?: (operation: { [key: string]: any }) => boolean
18
+ @property({ type: Boolean, attribute: 'light-popup' }) lightPopup: boolean = false
19
+
20
+ constructor() {
21
+ super()
22
+
23
+ this.addEventListener('click', (e: Event) => {
24
+ e.preventDefault()
25
+ e.stopPropagation()
26
+
27
+ if (this.lightPopup) {
28
+ this.lightPopupRecordView()
29
+ } else {
30
+ this.popupRecordView()
31
+ }
32
+ })
33
+ }
34
+
35
+ connectedCallback(): void {
36
+ super.connectedCallback()
37
+
38
+ this.grist = this.closest('ox-grist') as DataGrist
39
+ }
40
+
41
+ render() {
42
+ return html`<slot></slot>`
43
+ }
44
+
45
+ lightPopupRecordView() {
46
+ const config = this.grist!.compiledConfig
47
+ var title = 'create'
48
+ const rowIndex = -1
49
+ var record: GristRecord = {}
50
+ const columns = config.columns
51
+
52
+ var popup = OxPopup.open({
53
+ template: html`
54
+ <div title>${title}</div>
55
+ <ox-record-view
56
+ only-for-edit
57
+ @field-change=${(e: CustomEvent) => {
58
+ const view = e.currentTarget as RecordView
59
+
60
+ var { after, before, column, record, row } = (e as CustomEvent).detail as {
61
+ after: any
62
+ before: any
63
+ column: ColumnConfig
64
+ record: GristRecord
65
+ row: number
66
+ }
67
+
68
+ var validation = column.validation
69
+ if (validation && typeof validation == 'function') {
70
+ if (!validation.call(this, after, before, record, column)) {
71
+ return
72
+ }
73
+ }
74
+
75
+ view.record = {
76
+ ...record,
77
+ [column.name]: after
78
+ }
79
+ }}
80
+ .columns=${columns}
81
+ .record=${record}
82
+ .rowIndex=${rowIndex}
83
+ @reset=${(e: Event) => {
84
+ const view = e.currentTarget as RecordView
85
+ view.record = {}
86
+ }}
87
+ @cancel=${(e: Event) => {
88
+ popup.close()
89
+ }}
90
+ @ok=${(e: Event) => {
91
+ popup.close()
92
+
93
+ const view = e.currentTarget as RecordView
94
+
95
+ this.dispatchEvent(
96
+ new CustomEvent('ok', {
97
+ bubbles: true,
98
+ composed: true,
99
+ detail: view.record
100
+ })
101
+ )
102
+ }}
103
+ ></ox-record-view>
104
+ `,
105
+ parent: document.body
106
+ })
107
+ }
108
+
109
+ popupRecordView() {
110
+ const config = this.grist!.compiledConfig
111
+ const rowIndex = -1
112
+ var record: GristRecord = {}
113
+ const columns = config.columns
114
+
115
+ var title = 'create'
116
+
117
+ var recordView = document.createElement('ox-record-view') as RecordView
118
+
119
+ recordView.setAttribute('only-for-edit', '')
120
+ recordView.columns = columns
121
+ recordView.record = record
122
+ recordView.rowIndex = rowIndex
123
+
124
+ document.dispatchEvent(
125
+ new CustomEvent('open-popup', {
126
+ detail: {
127
+ template: recordView,
128
+ options: {
129
+ backdrop: true,
130
+ size: 'large',
131
+ title
132
+ },
133
+ callback: (popup: any) => {
134
+ recordView.addEventListener('reset', (e: Event) => {
135
+ const view = e.currentTarget as RecordView
136
+ view.record = {}
137
+ })
138
+
139
+ recordView.addEventListener('cancel', (e: Event) => {
140
+ popup.close()
141
+ })
142
+
143
+ recordView.addEventListener('ok', async (e: Event) => {
144
+ const view = e.currentTarget as RecordView
145
+ if (await this.callback?.(view.record)) {
146
+ popup.close()
147
+ }
148
+ })
149
+
150
+ recordView.addEventListener('field-change', (e: Event) => {
151
+ const view = e.currentTarget as RecordView
152
+
153
+ var { after, before, column, record, row } = (e as CustomEvent).detail as {
154
+ after: any
155
+ before: any
156
+ column: ColumnConfig
157
+ record: GristRecord
158
+ row: number
159
+ }
160
+
161
+ var validation = column.validation
162
+ if (validation && typeof validation == 'function') {
163
+ if (!validation.call(this, after, before, record, column)) {
164
+ return
165
+ }
166
+ }
167
+
168
+ view.record = {
169
+ ...record,
170
+ [column.name]: after
171
+ }
172
+ })
173
+
174
+ popup.onclosed = () => {}
175
+ }
176
+ }
177
+ })
178
+ )
179
+ }
180
+ }
@@ -5,11 +5,9 @@ import { css, html, LitElement } from 'lit'
5
5
  import { customElement, property } from 'lit/decorators.js'
6
6
 
7
7
  import { ZERO_RECORD } from '../configure/zero-config'
8
- import { DataGridField } from '../data-grid/data-grid-field'
9
8
  import { ColumnConfig, GristRecord } from '../types'
10
-
11
- const KEY_ENTER = 13
12
- const KEY_ESC = 27
9
+ import { recordViewBodyClickHandler } from './event-handlers/record-view-body-click-handler'
10
+ import { recordViewBodyKeydownHandler } from './event-handlers/record-view-body-keydown-handler'
13
11
 
14
12
  @customElement('ox-record-view-body')
15
13
  export class RecordViewBody extends LitElement {
@@ -20,10 +18,7 @@ export class RecordViewBody extends LitElement {
20
18
  grid-template-columns: 1fr 2fr;
21
19
  grid-auto-rows: min-content;
22
20
  grid-gap: var(--record-view-gap);
23
- background-color: var(--record-view-background-color);
24
21
  padding: var(--record-view-padding);
25
-
26
- overflow-y: auto;
27
22
  }
28
23
 
29
24
  label {
@@ -49,6 +44,7 @@ export class RecordViewBody extends LitElement {
49
44
  }
50
45
 
51
46
  ox-grid-field {
47
+ border-top: none;
52
48
  border-bottom: var(--record-view-border-bottom);
53
49
  font: var(--record-view-font);
54
50
  color: var(--record-view-color);
@@ -56,7 +52,6 @@ export class RecordViewBody extends LitElement {
56
52
  }
57
53
 
58
54
  ox-grid-field[editing='true'] {
59
- border-top: none;
60
55
  border-bottom: var(--record-view-edit-border-bottom);
61
56
  }
62
57
 
@@ -70,12 +65,21 @@ export class RecordViewBody extends LitElement {
70
65
  @property({ type: Array }) columns: ColumnConfig[] = []
71
66
  @property({ type: Object }) record: GristRecord = ZERO_RECORD
72
67
  @property({ type: Number }) rowIndex: number = -1
68
+ @property({ type: Boolean, attribute: 'only-for-edit' }) onlyForEdit: boolean = false
69
+
70
+ public currentTarget: any
73
71
 
74
- private editTarget: any
75
- private _focusedListener: any
72
+ connectedCallback() {
73
+ super.connectedCallback()
74
+
75
+ this.setAttribute('tabindex', '0')
76
+
77
+ this.renderRoot.addEventListener('keydown', recordViewBodyKeydownHandler.bind(this))
78
+ this.renderRoot.addEventListener('click', recordViewBodyClickHandler.bind(this))
79
+ }
76
80
 
77
81
  render() {
78
- var columns = this.columns.filter(column => !column.hidden && column.type != 'gutter')
82
+ var columns = this.columns.filter(column => !column.hidden && column.type !== 'gutter')
79
83
  var record = this.record
80
84
  var rowIndex = this.rowIndex
81
85
 
@@ -92,56 +96,13 @@ export class RecordViewBody extends LitElement {
92
96
  .record=${record}
93
97
  .value=${record[column.name]}
94
98
  ?dirty=${!!dirtyFields[column.name]}
99
+ ?editing=${this.onlyForEdit}
95
100
  ></ox-grid-field>
96
101
  `
97
102
  })}
98
103
  `
99
104
  }
100
105
 
101
- firstUpdated() {
102
- this.renderRoot.addEventListener('click', e => {
103
- e.stopPropagation()
104
-
105
- /* target should be 'ox-grid-field' */
106
- var target = e.target as DataGridField
107
-
108
- if (this.editTarget) {
109
- this.editTarget.removeAttribute('editing')
110
- this.editTarget = null
111
- }
112
-
113
- if (target.tagName !== 'OX-GRID-FIELD' || !target.column.record.editable) {
114
- return
115
- }
116
-
117
- this.editTarget = target
118
- target.setAttribute('editing', 'true')
119
- })
120
-
121
- this._focusedListener = (e: KeyboardEvent) => {
122
- var keyCode = e.keyCode
123
- switch (keyCode) {
124
- case KEY_ESC:
125
- /* TODO 편집이 취소되어야 한다. */
126
- case KEY_ENTER:
127
- if (this.editTarget) {
128
- this.editTarget.removeAttribute('editing')
129
- this.editTarget = null
130
- }
131
- break
132
-
133
- default:
134
- }
135
- }
136
- window.addEventListener('keydown', this._focusedListener)
137
- }
138
-
139
- disconnectedCallback() {
140
- super.disconnectedCallback()
141
-
142
- window.removeEventListener('keydown', this._focusedListener)
143
- }
144
-
145
106
  _renderLabel(column: ColumnConfig) {
146
107
  var { renderer } = column.header
147
108
  var title = renderer.call(this, column)
@@ -0,0 +1,86 @@
1
+ import { RecordCard } from '../data-card/record-card'
2
+ import { DataGridBody } from '../data-grid/data-grid-body'
3
+ import { DataGridField } from '../data-grid/data-grid-field'
4
+ import { RecordPartial } from '../data-list/record-partial'
5
+ import { RecordView } from '../record-view/record-view'
6
+ import { ColumnConfig, GristRecord } from '../types'
7
+
8
+ /*
9
+ * handler들은 ox-grid-field 로부터 호출되는 것을 전제로 하며,
10
+ * 전반적인 처리를 위해서, columns 및 data 정보를 포함해서 제공할 수 있어야 한다.
11
+ */
12
+
13
+ export const RecordViewHandler = function (
14
+ columns: ColumnConfig[],
15
+ record: GristRecord,
16
+ rowIndex: number,
17
+ field: DataGridField | RecordCard | RecordPartial | DataGridBody,
18
+ popupOptions: { [key: string]: any },
19
+ closeCallback?: () => void
20
+ ): RecordView {
21
+ var recordView = document.createElement('ox-record-view') as RecordView
22
+
23
+ recordView.columns = columns
24
+ recordView.record = record
25
+ recordView.rowIndex = rowIndex
26
+
27
+ document.dispatchEvent(
28
+ new CustomEvent('open-popup', {
29
+ detail: {
30
+ template: recordView,
31
+ options: {
32
+ backdrop: true,
33
+ size: 'large',
34
+ title: record['name'],
35
+ ...popupOptions
36
+ },
37
+ callback: (popup: any) => {
38
+ recordView.addEventListener('field-change', (e: Event) => {
39
+ field.dispatchEvent(
40
+ new CustomEvent('field-change', {
41
+ bubbles: true,
42
+ composed: true,
43
+ detail: (e as any).detail
44
+ })
45
+ )
46
+ })
47
+
48
+ recordView.addEventListener('reset', (e: Event) => {
49
+ field.dispatchEvent(
50
+ new CustomEvent('record-reset', {
51
+ bubbles: true,
52
+ composed: true,
53
+ detail: {
54
+ record: record,
55
+ row: rowIndex
56
+ }
57
+ })
58
+ )
59
+ })
60
+
61
+ recordView.addEventListener('cancel', (e: Event) => {
62
+ field.dispatchEvent(
63
+ new CustomEvent('record-reset', {
64
+ bubbles: true,
65
+ composed: true,
66
+ detail: {
67
+ record: record,
68
+ row: rowIndex
69
+ }
70
+ })
71
+ )
72
+ popup.close()
73
+ })
74
+
75
+ recordView.addEventListener('ok', (e: Event) => {
76
+ popup.close()
77
+ })
78
+
79
+ popup.onclosed = closeCallback
80
+ }
81
+ }
82
+ } as any)
83
+ )
84
+
85
+ return recordView
86
+ }
@@ -1,63 +1,80 @@
1
1
  import '@material/mwc-icon'
2
2
  import './record-view-body'
3
+ import '@operato/input/ox-input-file.js'
4
+ import '../data-grid/data-grid-field'
3
5
 
4
6
  import { css, html, LitElement } from 'lit'
5
7
  import { customElement, property } from 'lit/decorators.js'
6
8
 
9
+ import { ScrollbarStyles } from '@operato/styles'
10
+
7
11
  import { ZERO_RECORD } from '../configure/zero-config'
8
12
  import { ColumnConfig, GristRecord } from '../types'
9
13
 
10
14
  @customElement('ox-record-view')
11
15
  export class RecordView extends LitElement {
12
- static styles = css`
13
- :host {
14
- display: flex;
15
- flex-direction: column;
16
- }
17
-
18
- ox-record-view-body {
19
- flex: 1;
20
- }
21
-
22
- [footer] {
23
- display: flex;
24
- text-align: right;
25
- background-color: var(--record-view-footer-background);
26
- box-shadow: var(--context-toolbar-shadow-line);
27
- }
28
-
29
- [footer] button {
30
- flex: 1;
31
- background-color: transparent;
32
- border: var(--record-view-footer-button-border);
33
- border-width: var(--record-view-footer-button-border-width);
34
- color: var(--record-view-footer-button-color);
35
- font-size: var(--record-view-footer-button-font);
36
- line-height: 3;
37
- }
38
-
39
- [footer] button * {
40
- vertical-align: middle;
41
- }
42
-
43
- [footer] button mwc-icon {
44
- margin-top: -3px;
45
- margin-right: 5px;
46
- font-size: var(--record-view-footer-iconbutton-size);
47
- }
48
-
49
- [footer] button[ok] {
50
- background-color: var(--record-view-footer-focus-background);
51
- }
52
- `
16
+ static styles = [
17
+ ScrollbarStyles,
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ background-color: var(--record-view-background-color);
23
+
24
+ max-height: 80vh;
25
+ }
26
+
27
+ ox-record-view-body {
28
+ flex: 1;
29
+ overflow-y: auto;
30
+ }
31
+
32
+ [footer] {
33
+ display: flex;
34
+ text-align: right;
35
+ background-color: var(--record-view-footer-background);
36
+ box-shadow: var(--context-toolbar-shadow-line);
37
+ }
38
+
39
+ [footer] button {
40
+ flex: 1;
41
+ background-color: transparent;
42
+ border: var(--record-view-footer-button-border);
43
+ border-width: var(--record-view-footer-button-border-width);
44
+ color: var(--record-view-footer-button-color);
45
+ font-size: var(--record-view-footer-button-font);
46
+ line-height: 3;
47
+ }
48
+
49
+ [footer] button * {
50
+ vertical-align: middle;
51
+ }
52
+
53
+ [footer] button mwc-icon {
54
+ margin-top: -3px;
55
+ margin-right: 5px;
56
+ font-size: var(--record-view-footer-iconbutton-size);
57
+ }
58
+
59
+ [footer] button[ok] {
60
+ background-color: var(--record-view-footer-focus-background);
61
+ }
62
+ `
63
+ ]
53
64
 
54
65
  @property({ type: Array }) columns: ColumnConfig[] = []
55
66
  @property({ type: Object }) record: GristRecord = ZERO_RECORD
56
67
  @property({ type: Number }) rowIndex: number = -1
68
+ @property({ type: Boolean, attribute: 'only-for-edit' }) onlyForEdit: boolean = false
57
69
 
58
70
  render() {
59
71
  return html`
60
- <ox-record-view-body .columns=${this.columns} .record=${this.record} .rowIndex=${this.rowIndex}>
72
+ <ox-record-view-body
73
+ .columns=${this.columns}
74
+ .record=${this.record}
75
+ .rowIndex=${this.rowIndex}
76
+ ?only-for-edit=${this.onlyForEdit}
77
+ >
61
78
  </ox-record-view-body>
62
79
  <div footer>
63
80
  <button @click=${this.onReset.bind(this)}><mwc-icon>refresh</mwc-icon>Reset</button>
@@ -67,7 +84,13 @@ export class RecordView extends LitElement {
67
84
  `
68
85
  }
69
86
 
87
+ firstUpdated() {
88
+ this.setAttribute('tabindex', '0')
89
+ }
90
+
70
91
  onReset() {
92
+ this.focus()
93
+
71
94
  this.dispatchEvent(
72
95
  new CustomEvent('reset', {
73
96
  detail: this.record
@@ -76,6 +99,8 @@ export class RecordView extends LitElement {
76
99
  }
77
100
 
78
101
  onCancel() {
102
+ this.focus()
103
+
79
104
  this.dispatchEvent(
80
105
  new CustomEvent('cancel', {
81
106
  detail: this.record
@@ -84,6 +109,8 @@ export class RecordView extends LitElement {
84
109
  }
85
110
 
86
111
  onOK() {
112
+ this.focus()
113
+
87
114
  this.dispatchEvent(
88
115
  new CustomEvent('ok', {
89
116
  detail: this.record
@@ -1,12 +1,15 @@
1
- import { FieldRenderer } from '../types'
2
1
  import { html } from 'lit'
3
2
 
4
- export const ImageRenderer: FieldRenderer = (value, column, record, rowIndex, field) => {
5
- if (!value) return html``
3
+ import { FieldRenderer } from '../types'
6
4
 
5
+ const IMAGE_FALLBACK = new URL('../../../assets/images/no-image.png', import.meta.url).href
6
+
7
+ export const ImageRenderer: FieldRenderer = (value, column, record, rowIndex, field) => {
7
8
  let src: string
8
9
 
9
- if (typeof value === 'string') {
10
+ if (!value) {
11
+ src = IMAGE_FALLBACK
12
+ } else if (typeof value === 'string') {
10
13
  src = value
11
14
  } else {
12
15
  src = URL.createObjectURL(value)
@@ -15,5 +18,11 @@ export const ImageRenderer: FieldRenderer = (value, column, record, rowIndex, fi
15
18
 
16
19
  const { width, height } = column.record.options || {}
17
20
 
18
- return html` <img src=${src} alt="upload image" width=${width} height=${height} />`
21
+ return html` <img
22
+ src=${src}
23
+ width=${width}
24
+ height=${height}
25
+ style="max-width: 100%;"
26
+ onerror="this.src !== '${IMAGE_FALLBACK}' && (this.src = '${IMAGE_FALLBACK}')"
27
+ />`
19
28
  }
@@ -0,0 +1,111 @@
1
+ import { css, html, LitElement, PropertyValues, TemplateResult } from 'lit'
2
+ import { customElement, state } from 'lit/decorators.js'
3
+
4
+ import { DataGrist } from '../data-grist'
5
+ import { ColumnConfig, GristConfig, SorterConfig } from '../types'
6
+
7
+ @customElement('ox-sorters-control')
8
+ export class SortersControl extends LitElement {
9
+ static styles = [
10
+ css`
11
+ :host {
12
+ display: block;
13
+ }
14
+ `
15
+ ]
16
+
17
+ @state() config!: GristConfig
18
+ @state() columns: ColumnConfig[] = []
19
+ @state() sorters: SorterConfig[] = []
20
+
21
+ connectedCallback(): void {
22
+ super.connectedCallback()
23
+
24
+ const grist = this.closest('ox-grist') as DataGrist
25
+
26
+ if (grist) {
27
+ this.config = grist.config
28
+
29
+ this.closest('ox-grist')?.addEventListener('sorters-change', e => {
30
+ this.sorters = (e as CustomEvent).detail as SorterConfig[]
31
+ })
32
+ }
33
+ }
34
+
35
+ updated(changes: PropertyValues<this>) {
36
+ if (changes.has('config')) {
37
+ const sorters = this.config.sorters || []
38
+
39
+ this.columns = sorters
40
+ .map(({ name }) => this.config.columns.find(column => column.name === name))
41
+ .filter(column => !!column) as ColumnConfig[]
42
+
43
+ this.sorters = this.config.sorters || []
44
+ }
45
+ }
46
+
47
+ render(): TemplateResult {
48
+ const columns = this.columns
49
+
50
+ const current = this.sorters || []
51
+
52
+ return html`
53
+ ${columns.map(column => {
54
+ const { name } = column
55
+ var rank = current.findIndex(sorter => sorter.name === name) + 1
56
+ var desc = rank !== 0 ? current[rank - 1].desc : null
57
+
58
+ if (current.length <= 1) {
59
+ rank = 0
60
+ }
61
+
62
+ return html`
63
+ <div
64
+ option
65
+ @click=${(e: MouseEvent) => {
66
+ this.onChangeSort(name)
67
+ }}
68
+ >
69
+ ${column.header /*.renderer.call(this, column) */}
70
+ ${desc === null
71
+ ? html``
72
+ : html`
73
+ <mwc-icon>${desc ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}</mwc-icon>
74
+ ${rank === 0 ? html`` : html`<sub>${rank}</sub>`}
75
+ `}
76
+ </div>
77
+ `
78
+ })}
79
+ `
80
+ }
81
+
82
+ onChangeSort(name: string) {
83
+ var sorters = [...this.sorters]
84
+
85
+ var idx = sorters.findIndex(sorter => sorter.name == name)
86
+ if (idx !== -1) {
87
+ let sorter = sorters[idx]
88
+ if (sorter.desc) {
89
+ sorters.splice(idx, 1)
90
+ } else {
91
+ sorter.desc = true
92
+ }
93
+ } else {
94
+ var sorter = {
95
+ name
96
+ }
97
+
98
+ sorters.push(sorter)
99
+ }
100
+
101
+ this.sorters = sorters
102
+
103
+ this.dispatchEvent(
104
+ new CustomEvent('sorters-change', {
105
+ bubbles: true,
106
+ composed: true,
107
+ detail: this.sorters
108
+ })
109
+ )
110
+ }
111
+ }