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

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 (34) hide show
  1. package/CHANGELOG.md +20 -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/accumulator-format.stories.d.ts +4 -0
  19. package/dist/stories/accumulator-format.stories.js +4 -3
  20. package/dist/stories/accumulator-format.stories.js.map +1 -1
  21. package/dist/stories/grid-setting.stories.d.ts +5 -0
  22. package/dist/stories/grid-setting.stories.js +23 -30
  23. package/dist/stories/grid-setting.stories.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +6 -6
  26. package/src/data-grid/data-grid-header.ts +1 -1
  27. package/src/data-grist.ts +36 -38
  28. package/src/filters/filter-styles.ts +21 -11
  29. package/src/filters/filters-form.ts +196 -125
  30. package/src/personalizer/ox-grist-filter-personalizer.ts +191 -0
  31. package/src/personalizer/ox-grist-personalizer.ts +7 -1
  32. package/src/types.ts +12 -0
  33. package/stories/accumulator-format.stories.ts +11 -3
  34. package/stories/grid-setting.stories.ts +25 -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
+ }
@@ -187,7 +187,8 @@ export default {
187
187
  argTypes: {
188
188
  config: { control: 'object' },
189
189
  mode: { control: 'select', options: ['GRID', 'LIST', 'CARD'] },
190
- urlParamsSensitive: { control: 'boolean' }
190
+ urlParamsSensitive: { control: 'boolean' },
191
+ withoutSearch: { control: 'boolean' }
191
192
  }
192
193
  }
193
194
 
@@ -201,10 +202,17 @@ interface ArgTypes {
201
202
  config: object
202
203
  mode: string
203
204
  urlParamsSensitive: boolean
205
+ withoutSearch: boolean
204
206
  fetchHandler: object
205
207
  }
206
208
 
207
- const Template: Story<ArgTypes> = ({ config, mode = 'GRID', urlParamsSensitive = false, fetchHandler }: ArgTypes) =>
209
+ const Template: Story<ArgTypes> = ({
210
+ config,
211
+ mode = 'GRID',
212
+ urlParamsSensitive = false,
213
+ withoutSearch = false,
214
+ fetchHandler
215
+ }: ArgTypes) =>
208
216
  html` <link
209
217
  href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL@20..48,100..700,0..1"
210
218
  rel="stylesheet"
@@ -246,7 +254,7 @@ const Template: Story<ArgTypes> = ({ config, mode = 'GRID', urlParamsSensitive =
246
254
  >
247
255
  <div slot="headroom" class="header">
248
256
  <div class="filters">
249
- <ox-filters-form autofocus></ox-filters-form>
257
+ <ox-filters-form ?without-search=${withoutSearch} autofocus></ox-filters-form>
250
258
  <ox-record-creator id="add" light-popup>
251
259
  <button><md-icon>add</md-icon></button>
252
260
  </ox-record-creator>
@@ -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
 
@@ -346,6 +347,7 @@ export default {
346
347
  config: { control: 'object' },
347
348
  mode: { control: 'select', options: ['GRID', 'LIST', 'CARD'] },
348
349
  urlParamsSensitive: { control: 'boolean' },
350
+ withoutSearch: { control: 'boolean' },
349
351
  debug: { control: 'boolean' }
350
352
  }
351
353
  }
@@ -360,14 +362,27 @@ interface ArgTypes {
360
362
  config: object
361
363
  mode: 'GRID' | 'LIST' | 'CARD'
362
364
  urlParamsSensitive: boolean
365
+ withoutSearch: boolean
363
366
  fetchHandler: FetchHandler
364
367
  debug: boolean
365
368
  }
366
369
 
370
+ var personalConfig: PersonalGristPreference = {
371
+ columns: [
372
+ { name: 'name', hidden: false, width: 200 },
373
+ { name: 'description', hidden: true }
374
+ ],
375
+ pagination: {
376
+ pages: [20, 30, 50, 100, 200],
377
+ limit: 30
378
+ }
379
+ }
380
+
367
381
  const Template: Story<ArgTypes> = ({
368
382
  config,
369
383
  mode = 'GRID',
370
384
  urlParamsSensitive = false,
385
+ withoutSearch = false,
371
386
  debug = false,
372
387
  fetchHandler
373
388
  }: ArgTypes) =>
@@ -399,23 +414,19 @@ const Template: Story<ArgTypes> = ({
399
414
  .personalConfigProvider=${{
400
415
  async load() {
401
416
  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
- }
417
+ return personalConfig
412
418
  },
413
419
  async save(preference: PersonalGristPreference) {
414
420
  await sleep(1000)
415
- console.log('saving preference', preference)
421
+ personalConfig = {
422
+ ...personalConfig,
423
+ ...preference
424
+ }
425
+ console.log('saving preference', personalConfig)
416
426
  return preference
417
427
  },
418
428
  async reset() {
429
+ personalConfig = {}
419
430
  await sleep(1000)
420
431
  }
421
432
  }}
@@ -424,24 +435,9 @@ const Template: Story<ArgTypes> = ({
424
435
  >
425
436
  <div slot="headroom">
426
437
  <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>
438
+ <ox-filters-form autofocus ?without-search=${withoutSearch}>
439
+ <ox-grist-filter-personalizer slot="setting"></ox-grist-filter-personalizer>
440
+ </ox-filters-form>
445
441
  </div>
446
442
 
447
443
  <div id="modes">