@operato/data-grist 2.0.0-alpha.132 → 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.
- package/CHANGELOG.md +25 -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.d.ts +5 -6
- package/dist/src/data-grist.js +53 -32
- 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.d.ts +0 -2
- package/dist/src/personalizer/ox-grist-personalizer.js +15 -9
- 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/grid-setting.stories.d.ts +1 -0
- package/dist/stories/grid-setting.stories.js +21 -25
- package/dist/stories/grid-setting.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/src/data-grid/data-grid-header.ts +1 -1
- package/src/data-grist.ts +58 -36
- 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 +15 -5
- package/src/types.ts +12 -0
- package/stories/grid-setting.stories.ts +22 -25
@@ -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
|
+
}
|
@@ -30,8 +30,6 @@ export class OxGristPersonalizer extends LitElement {
|
|
30
30
|
`
|
31
31
|
]
|
32
32
|
|
33
|
-
@property({ type: String }) page!: string
|
34
|
-
@property({ type: String }) element!: string
|
35
33
|
@property({ type: Boolean, attribute: true }) debug: boolean = false
|
36
34
|
|
37
35
|
@state() private preference?: PersonalGristPreference
|
@@ -42,7 +40,7 @@ export class OxGristPersonalizer extends LitElement {
|
|
42
40
|
@click=${(e: MouseEvent) => {
|
43
41
|
const grist = this.closest('ox-grist') as DataGrist
|
44
42
|
|
45
|
-
const { config, compiledConfig } = grist
|
43
|
+
const { config, compiledConfig, sorters, pagination, mode } = grist
|
46
44
|
const { columns: compiledColumns } = compiledConfig
|
47
45
|
|
48
46
|
const columns = compiledColumns
|
@@ -59,7 +57,13 @@ export class OxGristPersonalizer extends LitElement {
|
|
59
57
|
hidden: column.hidden,
|
60
58
|
width: column.width
|
61
59
|
}
|
62
|
-
})
|
60
|
+
}),
|
61
|
+
sorters,
|
62
|
+
pagination: {
|
63
|
+
...pagination,
|
64
|
+
limit: grist.getCurrentLimit()
|
65
|
+
},
|
66
|
+
mode
|
63
67
|
}
|
64
68
|
|
65
69
|
const template = html`
|
@@ -68,7 +72,13 @@ export class OxGristPersonalizer extends LitElement {
|
|
68
72
|
style="margin-left: auto;"
|
69
73
|
@click=${async (e: MouseEvent) => {
|
70
74
|
if (grist.personalConfigProvider) {
|
71
|
-
|
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
|
+
})
|
72
82
|
}
|
73
83
|
popup.close()
|
74
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,19 +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
|
-
}
|
414
|
+
return personalConfig
|
408
415
|
},
|
409
416
|
async save(preference: PersonalGristPreference) {
|
410
417
|
await sleep(1000)
|
411
|
-
|
418
|
+
personalConfig = {
|
419
|
+
...personalConfig,
|
420
|
+
...preference
|
421
|
+
}
|
422
|
+
console.log('saving preference', personalConfig)
|
412
423
|
return preference
|
413
424
|
},
|
414
425
|
async reset() {
|
426
|
+
personalConfig = {}
|
415
427
|
await sleep(1000)
|
416
428
|
}
|
417
429
|
}}
|
@@ -420,24 +432,9 @@ const Template: Story<ArgTypes> = ({
|
|
420
432
|
>
|
421
433
|
<div slot="headroom">
|
422
434
|
<div id="filters">
|
423
|
-
<ox-filters-form autofocus
|
424
|
-
|
425
|
-
|
426
|
-
<div id="sorters">
|
427
|
-
Sort
|
428
|
-
<md-icon
|
429
|
-
@click=${(e: Event) => {
|
430
|
-
const target = e.currentTarget as HTMLElement
|
431
|
-
;(target.closest('#sorters')!.querySelector('#sorter-control') as any).open({
|
432
|
-
right: 0,
|
433
|
-
top: target.offsetTop + target.offsetHeight
|
434
|
-
})
|
435
|
-
}}
|
436
|
-
>expand_more</md-icon
|
437
|
-
>
|
438
|
-
<ox-popup id="sorter-control">
|
439
|
-
<ox-sorters-control> </ox-sorters-control>
|
440
|
-
</ox-popup>
|
435
|
+
<ox-filters-form autofocus>
|
436
|
+
<ox-grist-filter-personalizer slot="setting"></ox-grist-filter-personalizer>
|
437
|
+
</ox-filters-form>
|
441
438
|
</div>
|
442
439
|
|
443
440
|
<div id="modes">
|