@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.
- package/CHANGELOG.md +20 -0
- package/dist/src/data-grid/data-grid-header.js +1 -1
- package/dist/src/data-grid/data-grid-header.js.map +1 -1
- package/dist/src/data-grist.js +34 -36
- package/dist/src/data-grist.js.map +1 -1
- package/dist/src/filters/filter-styles.js +21 -11
- package/dist/src/filters/filter-styles.js.map +1 -1
- package/dist/src/filters/filters-form.d.ts +8 -2
- package/dist/src/filters/filters-form.js +128 -62
- package/dist/src/filters/filters-form.js.map +1 -1
- package/dist/src/personalizer/ox-grist-filter-personalizer.d.ts +8 -0
- package/dist/src/personalizer/ox-grist-filter-personalizer.js +177 -0
- package/dist/src/personalizer/ox-grist-filter-personalizer.js.map +1 -0
- package/dist/src/personalizer/ox-grist-personalizer.js +7 -1
- package/dist/src/personalizer/ox-grist-personalizer.js.map +1 -1
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.js.map +1 -1
- package/dist/stories/accumulator-format.stories.d.ts +4 -0
- package/dist/stories/accumulator-format.stories.js +4 -3
- package/dist/stories/accumulator-format.stories.js.map +1 -1
- package/dist/stories/grid-setting.stories.d.ts +5 -0
- package/dist/stories/grid-setting.stories.js +23 -30
- package/dist/stories/grid-setting.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/data-grid/data-grid-header.ts +1 -1
- package/src/data-grist.ts +36 -38
- package/src/filters/filter-styles.ts +21 -11
- package/src/filters/filters-form.ts +196 -125
- package/src/personalizer/ox-grist-filter-personalizer.ts +191 -0
- package/src/personalizer/ox-grist-personalizer.ts +7 -1
- package/src/types.ts +12 -0
- package/stories/accumulator-format.stories.ts +11 -3
- 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
|
-
|
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> = ({
|
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
|
-
|
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
|
428
|
-
|
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">
|