@operato/data-grist 2.0.0-alpha.133 → 2.0.0-alpha.134

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 (30) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/src/data-grid/data-grid-header.js +1 -1
  3. package/dist/src/data-grid/data-grid-header.js.map +1 -1
  4. package/dist/src/data-grist.js +34 -36
  5. package/dist/src/data-grist.js.map +1 -1
  6. package/dist/src/filters/filter-styles.js +21 -11
  7. package/dist/src/filters/filter-styles.js.map +1 -1
  8. package/dist/src/filters/filters-form.d.ts +8 -2
  9. package/dist/src/filters/filters-form.js +128 -62
  10. package/dist/src/filters/filters-form.js.map +1 -1
  11. package/dist/src/personalizer/ox-grist-filter-personalizer.d.ts +8 -0
  12. package/dist/src/personalizer/ox-grist-filter-personalizer.js +177 -0
  13. package/dist/src/personalizer/ox-grist-filter-personalizer.js.map +1 -0
  14. package/dist/src/personalizer/ox-grist-personalizer.js +7 -1
  15. package/dist/src/personalizer/ox-grist-personalizer.js.map +1 -1
  16. package/dist/src/types.d.ts +11 -0
  17. package/dist/src/types.js.map +1 -1
  18. package/dist/stories/grid-setting.stories.d.ts +1 -0
  19. package/dist/stories/grid-setting.stories.js +21 -29
  20. package/dist/stories/grid-setting.stories.js.map +1 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +6 -6
  23. package/src/data-grid/data-grid-header.ts +1 -1
  24. package/src/data-grist.ts +36 -38
  25. package/src/filters/filter-styles.ts +21 -11
  26. package/src/filters/filters-form.ts +196 -125
  27. package/src/personalizer/ox-grist-filter-personalizer.ts +191 -0
  28. package/src/personalizer/ox-grist-personalizer.ts +7 -1
  29. package/src/types.ts +12 -0
  30. package/stories/grid-setting.stories.ts +22 -29
@@ -0,0 +1,191 @@
1
+ import '@material/web/button/outlined-button.js'
2
+
3
+ import { css, html, LitElement } from 'lit'
4
+ import { customElement, property, state } from 'lit/decorators.js'
5
+
6
+ import { i18next } from '@operato/i18n'
7
+ import { OxPopupList } from '@operato/popup'
8
+
9
+ import { FilterConfigObject, FilterPreference, PersonalGristPreference } from '../types.js'
10
+
11
+ import { OxFiltersForm } from '../filters/filters-form'
12
+
13
+ @customElement('ox-grist-filter-personalizer')
14
+ export class OxGristFilterPersonalizer extends LitElement {
15
+ static styles = [
16
+ css`
17
+ md-icon {
18
+ --md-icon-size: 16px;
19
+ width: 36px;
20
+ height: 36px;
21
+ color: var(--md-sys-color-secondary-container);
22
+ cursor: pointer;
23
+
24
+ display: flex;
25
+ place-content: center;
26
+ place-items: center;
27
+ position: relative;
28
+
29
+ &:hover {
30
+ color: var(--md-sys-color-primary-container);
31
+ }
32
+ }
33
+
34
+ md-ripple {
35
+ border-radius: 50%;
36
+ inset: unset;
37
+ height: 36px;
38
+ width: 36px;
39
+ }
40
+ `
41
+ ]
42
+
43
+ @property({ type: Boolean, attribute: true }) debug: boolean = false
44
+
45
+ @state() private preference?: PersonalGristPreference
46
+
47
+ render() {
48
+ return html`
49
+ <md-icon
50
+ @click=${async (e: MouseEvent) => {
51
+ const form = this.closest('ox-filters-form') as OxFiltersForm
52
+ const { filterColumns, personalFilters = [] } = form
53
+ const queryFilters = await form.getQueryFilters()
54
+
55
+ this.preference = {
56
+ filters: (
57
+ personalFilters.map((filter: FilterPreference) => {
58
+ const originFilterColumn =
59
+ filter.name == 'search'
60
+ ? { filter: { name: 'search' } }
61
+ : filterColumns.find(f => f.name == filter.name)
62
+
63
+ if (!originFilterColumn) {
64
+ /* 원래 filters 설정에 있는 것들 만을 유지한다. */
65
+ return
66
+ }
67
+
68
+ const { value } = originFilterColumn.filter! as FilterConfigObject
69
+
70
+ return {
71
+ name: filter.name,
72
+ hidden: filter.hidden,
73
+ /* 만약, filters에 기본값이 이미 설정되어 있다면, 그대로 유지한다. */
74
+ value: value ?? queryFilters.find(f => f.name == filter.name)?.value
75
+ }
76
+ }) || []
77
+ ).filter(Boolean) as FilterPreference[]
78
+ }
79
+
80
+ const template = html`
81
+ <div class="personalizer-header" slot="header">
82
+ <md-icon
83
+ style="margin-left: auto;"
84
+ @click=${async (e: MouseEvent) => {
85
+ if (form.personalConfigProvider) {
86
+ form.personalConfig = await form.personalConfigProvider.save(this.preference)
87
+ }
88
+ popup.close()
89
+ }}
90
+ title=${String(i18next.t('button.save'))}
91
+ >keep</md-icon
92
+ ><md-icon
93
+ @click=${async (e: MouseEvent) => {
94
+ if (form.personalConfigProvider) {
95
+ form.personalConfig = this.preference = {}
96
+ await form.personalConfigProvider.reset()
97
+ }
98
+ popup.close()
99
+ }}
100
+ title=${String(i18next.t('button.delete'))}
101
+ >keep_off</md-icon
102
+ ><md-icon @click=${async (e: MouseEvent) => popup.close()} title=${String(i18next.t('button.close'))}
103
+ >close</md-icon
104
+ >
105
+ </div>
106
+
107
+ ${this.preference?.filters!.map(
108
+ filter => html`
109
+ <ox-checkbox label="checkbox" ?checked=${!filter.hidden} value=${filter.name} option
110
+ >${filter.name}<span style="position: absolute; right: 10px; cursor: move;" handle
111
+ >☰</span
112
+ ></ox-checkbox
113
+ >
114
+ `
115
+ )}
116
+ `
117
+
118
+ const popup = OxPopupList.open({
119
+ template,
120
+ multiple: true,
121
+ sortable: true,
122
+ debug: this.debug,
123
+ attrSelected: 'checked',
124
+ top: e.pageY,
125
+ left: e.pageX,
126
+ styles: css`
127
+ :host {
128
+ width: 240px;
129
+ max-height: 300px;
130
+ overflow: auto;
131
+ }
132
+
133
+ ::slotted(.personalizer-header) {
134
+ --md-icon-size: 1.4em;
135
+
136
+ display: flex;
137
+ flex-direction: row;
138
+ align-items: center;
139
+ text-transform: capitalize;
140
+ box-shadow: 0 3px 3px rgba(0, 0, 0, 0.3);
141
+ }
142
+
143
+ ::slotted([option]) {
144
+ position: relative;
145
+ user-select: none;
146
+ }
147
+ `
148
+ })
149
+
150
+ popup.onselect = (e: Event) => {
151
+ const selected = (e as CustomEvent).detail
152
+
153
+ const pconfig: PersonalGristPreference = { ...form.personalConfig }
154
+ const pfilters = this.preference?.filters!
155
+
156
+ pconfig.filters = pfilters.map(filter => {
157
+ return {
158
+ name: filter.name,
159
+ hidden: selected.indexOf(filter.name) == -1,
160
+ value: filter.value
161
+ }
162
+ })
163
+
164
+ form.personalConfig = this.preference = pconfig
165
+
166
+ form.applyUpdatedConfiguration()
167
+ }
168
+
169
+ popup.addEventListener('sorted', (e: Event) => {
170
+ const sorted = (e as CustomEvent).detail as HTMLElement[]
171
+
172
+ const pconfig: PersonalGristPreference = { ...form.personalConfig }
173
+ const pfilters = this.preference?.filters!
174
+
175
+ pconfig.filters = sorted
176
+ .map(element => {
177
+ const name = (element as HTMLInputElement).value
178
+ return pfilters.find(filter => filter.name == name)!
179
+ })
180
+ .filter(Boolean)
181
+
182
+ form.personalConfig = this.preference = pconfig
183
+
184
+ form.applyUpdatedConfiguration()
185
+ })
186
+ }}
187
+ >settings<md-ripple></md-ripple
188
+ ></md-icon>
189
+ `
190
+ }
191
+ }
@@ -72,7 +72,13 @@ export class OxGristPersonalizer extends LitElement {
72
72
  style="margin-left: auto;"
73
73
  @click=${async (e: MouseEvent) => {
74
74
  if (grist.personalConfigProvider) {
75
- grist.personalConfig = this.preference = await grist.personalConfigProvider.save(this.preference)
75
+ const { mode, columns, sorters, pagination } = this.preference || {}
76
+ grist.personalConfig = this.preference = await grist.personalConfigProvider.save({
77
+ mode,
78
+ columns,
79
+ sorters,
80
+ pagination
81
+ })
76
82
  }
77
83
  popup.close()
78
84
  }}
package/src/types.ts CHANGED
@@ -106,6 +106,7 @@ export type FilterChangedCallback = (value: any, form: OxFiltersForm) => boolean
106
106
  * @property {string} type - The type of the filter condition.
107
107
  * @property {FilterOperator} [operator] - The filter operator used to compare values (optional).
108
108
  * @property {Object} [options] - Additional options or parameters for the filter condition (optional).
109
+ * @property {boolean|undefined} [hidden] - The hidden flag for the filter condition (optional).
109
110
  * @property {string|number|boolean|string[]|number[]|undefined} [value] - The value to compare with in the filter condition (optional).
110
111
  * @property {string} [label] - The label to display for the filter condition (optional).
111
112
  */
@@ -116,6 +117,7 @@ export type FilterConfigObject = {
116
117
  value?: string | number | boolean | string[] | number[] | undefined
117
118
  label?: string
118
119
  boundTo?: string[]
120
+ hidden?: boolean
119
121
  onchange?: FilterChangedCallback
120
122
  }
121
123
 
@@ -775,5 +777,15 @@ export type GristSelectFunction = (record: GristRecord) => boolean
775
777
  */
776
778
  export type PersonalGristPreference = {
777
779
  columns?: Partial<ColumnConfig>[]
780
+ filters?: FilterPreference[]
781
+ pagination?: PaginationConfig
782
+ sorters?: SortersConfig
783
+ mode?: 'GRID' | 'LIST' | 'CARD'
778
784
  [key: string]: any
779
785
  }
786
+
787
+ export type FilterPreference = {
788
+ name: string
789
+ hidden?: boolean
790
+ value?: any
791
+ }
@@ -8,6 +8,7 @@ import '../src/filters/filters-form.js'
8
8
  import '../src/sorters/sorters-control.js'
9
9
  import '../src/record-view/record-creator.js'
10
10
  import '../src/personalizer/ox-grist-personalizer.js'
11
+ import '../src/personalizer/ox-grist-filter-personalizer.js'
11
12
 
12
13
  import { html, TemplateResult } from 'lit'
13
14
 
@@ -364,6 +365,17 @@ interface ArgTypes {
364
365
  debug: boolean
365
366
  }
366
367
 
368
+ var personalConfig: PersonalGristPreference = {
369
+ columns: [
370
+ { name: 'name', hidden: false, width: 200 },
371
+ { name: 'description', hidden: true }
372
+ ],
373
+ pagination: {
374
+ pages: [20, 30, 50, 100, 200],
375
+ limit: 30
376
+ }
377
+ }
378
+
367
379
  const Template: Story<ArgTypes> = ({
368
380
  config,
369
381
  mode = 'GRID',
@@ -399,23 +411,19 @@ const Template: Story<ArgTypes> = ({
399
411
  .personalConfigProvider=${{
400
412
  async load() {
401
413
  await sleep(1000)
402
- return {
403
- columns: [
404
- { name: 'name', hidden: false, width: 200 },
405
- { name: 'description', hidden: true }
406
- ],
407
- pagination: {
408
- pages: [20, 30, 50, 100, 200],
409
- limit: 30
410
- }
411
- }
414
+ return personalConfig
412
415
  },
413
416
  async save(preference: PersonalGristPreference) {
414
417
  await sleep(1000)
415
- console.log('saving preference', preference)
418
+ personalConfig = {
419
+ ...personalConfig,
420
+ ...preference
421
+ }
422
+ console.log('saving preference', personalConfig)
416
423
  return preference
417
424
  },
418
425
  async reset() {
426
+ personalConfig = {}
419
427
  await sleep(1000)
420
428
  }
421
429
  }}
@@ -424,24 +432,9 @@ const Template: Story<ArgTypes> = ({
424
432
  >
425
433
  <div slot="headroom">
426
434
  <div id="filters">
427
- <ox-filters-form autofocus></ox-filters-form>
428
- </div>
429
-
430
- <div id="sorters">
431
- Sort
432
- <md-icon
433
- @click=${(e: Event) => {
434
- const target = e.currentTarget as HTMLElement
435
- ;(target.closest('#sorters')!.querySelector('#sorter-control') as any).open({
436
- right: 0,
437
- top: target.offsetTop + target.offsetHeight
438
- })
439
- }}
440
- >expand_more</md-icon
441
- >
442
- <ox-popup id="sorter-control">
443
- <ox-sorters-control> </ox-sorters-control>
444
- </ox-popup>
435
+ <ox-filters-form autofocus>
436
+ <ox-grist-filter-personalizer slot="setting"></ox-grist-filter-personalizer>
437
+ </ox-filters-form>
445
438
  </div>
446
439
 
447
440
  <div id="modes">