@globalbrain/sefirot 4.41.2 → 4.43.0
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/lib/blocks/lens/components/LensCatalog.vue +60 -5
- package/lib/blocks/lens/components/LensCatalogControl.vue +2 -0
- package/lib/blocks/lens/components/LensTable.vue +11 -4
- package/lib/components/SInputSegments.vue +4 -4
- package/lib/components/SInputSegmentsOption.vue +19 -53
- package/package.json +1 -1
|
@@ -61,7 +61,18 @@ export interface Props {
|
|
|
61
61
|
// Whether to hide the condition blocks.
|
|
62
62
|
hideConditions?: boolean
|
|
63
63
|
|
|
64
|
-
// Field name to be used as
|
|
64
|
+
// Field name to be used as the row identifier. When set, the field is
|
|
65
|
+
// automatically included in the request `select` so every row carries
|
|
66
|
+
// its value, and the corresponding column is hidden from the rendered
|
|
67
|
+
// table — *unless* the caller explicitly listed it in `select`, in
|
|
68
|
+
// which case the caller's intent wins and the column is shown
|
|
69
|
+
// normally. The value remains accessible via `record[indexField]` in
|
|
70
|
+
// `cell-clicked` events and through the `selected` model regardless.
|
|
71
|
+
//
|
|
72
|
+
// If the caller's `select` is empty or `null` (i.e. "use server
|
|
73
|
+
// defaults"), the index field is **not** auto-appended — that would
|
|
74
|
+
// narrow the result to just that single column on backends that treat
|
|
75
|
+
// an empty `select` as "use defaults".
|
|
65
76
|
indexField?: string
|
|
66
77
|
|
|
67
78
|
// Fields that are clickable to emit `cell-clicked` event when clicked.
|
|
@@ -125,6 +136,11 @@ const hasInitialResults = ref(false)
|
|
|
125
136
|
let prevFetchInput: LensQuery | null = null
|
|
126
137
|
let prevFetchResult: LensResult | null = null
|
|
127
138
|
|
|
139
|
+
// `_select` carries the caller's intent. The index field is preserved
|
|
140
|
+
// here if it was listed explicitly so the corresponding column gets
|
|
141
|
+
// rendered, and is only added to the request payload separately (see
|
|
142
|
+
// `withIndexField`). When `_select` is empty, the auto-fetched index
|
|
143
|
+
// field is stripped out of the table's column list further down.
|
|
128
144
|
const _select = ref(props.select ?? [])
|
|
129
145
|
const _selectable = ref(props.selectable ?? props.select ?? [])
|
|
130
146
|
|
|
@@ -152,7 +168,7 @@ const perPage = ref(100)
|
|
|
152
168
|
const { data: result, execute: refresh, loading } = useQuery(async (http) => {
|
|
153
169
|
const input = {
|
|
154
170
|
entity: props.entity ?? '__no_entity__',
|
|
155
|
-
select: _select.value,
|
|
171
|
+
select: withIndexField(_select.value),
|
|
156
172
|
filters: createInputFilters(queryFilter.value, _filters.value),
|
|
157
173
|
sort: _sort.value.length > 0 ? _sort.value : defaultSort.value ?? [],
|
|
158
174
|
page: page.value,
|
|
@@ -231,19 +247,55 @@ const tableMaxHeight = computed(() => {
|
|
|
231
247
|
return `--table-max-height: calc(${props.height} - ${controlHeight} - ${conditionBlocksHeight.value} - ${columnsHeight} - ${footerHeight})`
|
|
232
248
|
})
|
|
233
249
|
|
|
234
|
-
// Initial setup when the result is loaded for the first time.
|
|
250
|
+
// Initial setup when the result is loaded for the first time. When the
|
|
251
|
+
// caller didn't pass a `select`, we initialise from the response, but
|
|
252
|
+
// we strip out the index field — anything pulled in by `withIndexField`
|
|
253
|
+
// on the way out shouldn't pretend to be caller-declared on the way
|
|
254
|
+
// back in.
|
|
235
255
|
watch(result, (res) => {
|
|
236
256
|
if (!hasInitialResults.value && res!.data.length > 0) {
|
|
237
257
|
hasInitialResults.value = true
|
|
238
258
|
}
|
|
239
259
|
if (_select.value.length === 0) {
|
|
240
|
-
_select.value = res!.query.select
|
|
260
|
+
_select.value = withoutIndexField(res!.query.select)
|
|
241
261
|
}
|
|
242
262
|
if (_selectable.value.length === 0) {
|
|
243
|
-
_selectable.value = res!.query.select
|
|
263
|
+
_selectable.value = withoutIndexField(res!.query.select)
|
|
244
264
|
}
|
|
245
265
|
}, { once: true })
|
|
246
266
|
|
|
267
|
+
// Columns to render in the table. We always defer to `_select` (caller
|
|
268
|
+
// or user intent) once we have it; before the first response we use the
|
|
269
|
+
// raw response select with the index field filtered out so an
|
|
270
|
+
// auto-fetched index field doesn't accidentally appear as a column.
|
|
271
|
+
const tableSelect = computed(() => {
|
|
272
|
+
if (_select.value.length > 0) {
|
|
273
|
+
return _select.value
|
|
274
|
+
}
|
|
275
|
+
return withoutIndexField(result.value?.query.select ?? [])
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
// The `indexField` is appended to the request `select` so the server
|
|
279
|
+
// returns its value on every row, but it is kept out of the internal
|
|
280
|
+
// `_select` / `_selectable` state so the caller-facing concept of
|
|
281
|
+
// "selected columns" stays clean (the index field is a row identifier,
|
|
282
|
+
// not a column the user picked). The corresponding column is also
|
|
283
|
+
// hidden from the rendered table by `LensTable`.
|
|
284
|
+
//
|
|
285
|
+
// When the caller has no concrete select list, the index field is
|
|
286
|
+
// *not* added either — leaving the request empty lets the server use
|
|
287
|
+
// its own defaults. See `indexField` prop docs above.
|
|
288
|
+
function withIndexField(fields: string[]): string[] {
|
|
289
|
+
if (fields.length === 0) { return [] }
|
|
290
|
+
if (!props.indexField || fields.includes(props.indexField)) { return [...fields] }
|
|
291
|
+
return [...fields, props.indexField]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function withoutIndexField(fields: string[]): string[] {
|
|
295
|
+
if (!props.indexField) { return [...fields] }
|
|
296
|
+
return fields.filter((f) => f !== props.indexField)
|
|
297
|
+
}
|
|
298
|
+
|
|
247
299
|
// Create lens filters option by combining query (free search) filters,
|
|
248
300
|
// user selected filters, and fixed filters.
|
|
249
301
|
function createInputFilters(queryFilters: any[], filters: any[]) {
|
|
@@ -313,6 +365,8 @@ function onResetSorts() {
|
|
|
313
365
|
}
|
|
314
366
|
|
|
315
367
|
function onViewUpdated(newSelect: string[], newSelectable: string[], overrides: Record<string, Partial<FieldData>>) {
|
|
368
|
+
// Treat updates from the view form as deliberate user intent: if the
|
|
369
|
+
// user picked the index field on purpose, surface its column.
|
|
316
370
|
_select.value = newSelect
|
|
317
371
|
_selectable.value = newSelectable
|
|
318
372
|
_overrides.value = overrides
|
|
@@ -423,6 +477,7 @@ defineExpose({
|
|
|
423
477
|
:result
|
|
424
478
|
:loading
|
|
425
479
|
:overrides="_overrides"
|
|
480
|
+
:select="tableSelect"
|
|
426
481
|
:index-field
|
|
427
482
|
:selected
|
|
428
483
|
:clickable-fields
|
|
@@ -84,6 +84,7 @@ function createFilterPresetOptions(): ActionList {
|
|
|
84
84
|
<div class="LensCatalogControl">
|
|
85
85
|
<template v-if="!selected || selected.length === 0">
|
|
86
86
|
<div class="main">
|
|
87
|
+
<slot name="main-left" />
|
|
87
88
|
<SInputText
|
|
88
89
|
v-if="showQuery"
|
|
89
90
|
class="s-w-320"
|
|
@@ -113,6 +114,7 @@ function createFilterPresetOptions(): ActionList {
|
|
|
113
114
|
:label="t.a_view"
|
|
114
115
|
@click="$emit('view')"
|
|
115
116
|
/>
|
|
117
|
+
<slot name="main-right" />
|
|
116
118
|
</div>
|
|
117
119
|
<div class="sub">
|
|
118
120
|
<slot name="sub-left" />
|
|
@@ -14,6 +14,11 @@ const props = defineProps<{
|
|
|
14
14
|
result?: LensResult
|
|
15
15
|
overrides?: Record<string, Partial<FieldData>>
|
|
16
16
|
loading: boolean
|
|
17
|
+
// The list of field keys to actually render as columns, in order.
|
|
18
|
+
// Falls back to `result.query.select` if not provided. The catalog
|
|
19
|
+
// uses this to control whether an auto-fetched `indexField` shows up
|
|
20
|
+
// as a column.
|
|
21
|
+
select?: string[]
|
|
17
22
|
selected?: any[]
|
|
18
23
|
indexField?: string
|
|
19
24
|
}>()
|
|
@@ -29,8 +34,10 @@ const fieldFactory = useFieldFactory()
|
|
|
29
34
|
|
|
30
35
|
const records = computed(() => props.result?.data ?? [])
|
|
31
36
|
|
|
37
|
+
const columnKeys = computed(() => props.select ?? props.result?.query.select ?? [])
|
|
38
|
+
|
|
32
39
|
const orders = computed(() => [
|
|
33
|
-
...
|
|
40
|
+
...columnKeys.value,
|
|
34
41
|
'__last_empty__'
|
|
35
42
|
])
|
|
36
43
|
|
|
@@ -48,9 +55,9 @@ const columns = computedAsync(async () => {
|
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
// Build the
|
|
52
|
-
for (const i in
|
|
53
|
-
const key =
|
|
58
|
+
// Build the list of columns based on the resolved column key list.
|
|
59
|
+
for (const i in columnKeys.value) {
|
|
60
|
+
const key = columnKeys.value[i]
|
|
54
61
|
|
|
55
62
|
const _fieldData = cloneDeep(r.fields[key])
|
|
56
63
|
|
|
@@ -85,7 +85,7 @@ function onSelect(value: T) {
|
|
|
85
85
|
</SInputBase>
|
|
86
86
|
</template>
|
|
87
87
|
|
|
88
|
-
<style scoped
|
|
88
|
+
<style scoped>
|
|
89
89
|
.SInputSegments.sm,
|
|
90
90
|
.SInputSegments.mini {
|
|
91
91
|
.box {
|
|
@@ -129,9 +129,9 @@ function onSelect(value: T) {
|
|
|
129
129
|
|
|
130
130
|
.box {
|
|
131
131
|
display: inline-flex;
|
|
132
|
-
border: 1px solid var(--
|
|
133
|
-
border-radius:
|
|
134
|
-
background-color: var(--
|
|
132
|
+
border: 1px solid var(--c-border);
|
|
133
|
+
border-radius: 8px;
|
|
134
|
+
background-color: var(--c-bg-2);
|
|
135
135
|
transition: border-color 0.25s;
|
|
136
136
|
}
|
|
137
137
|
</style>
|
|
@@ -35,7 +35,7 @@ function onClick() {
|
|
|
35
35
|
</button>
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
|
-
<style scoped
|
|
38
|
+
<style scoped>
|
|
39
39
|
.SInputSegmentsOption {
|
|
40
40
|
position: relative;
|
|
41
41
|
display: block;
|
|
@@ -43,11 +43,10 @@ function onClick() {
|
|
|
43
43
|
justify-content: center;
|
|
44
44
|
align-items: center;
|
|
45
45
|
border: 1px solid transparent;
|
|
46
|
-
border-radius:
|
|
46
|
+
border-radius: 6px;
|
|
47
47
|
width: 100%;
|
|
48
48
|
height: 100%;
|
|
49
49
|
text-align: center;
|
|
50
|
-
font-size: 14px;
|
|
51
50
|
color: var(--c-text-2);
|
|
52
51
|
white-space: nowrap;
|
|
53
52
|
transition: border-color 0.25s, color 0.25s, background-color 0.25s;
|
|
@@ -60,76 +59,43 @@ function onClick() {
|
|
|
60
59
|
color: var(--c-text-3);
|
|
61
60
|
cursor: not-allowed;
|
|
62
61
|
}
|
|
63
|
-
|
|
64
|
-
.SInputSegmentsOption + &::before {
|
|
65
|
-
position: absolute;
|
|
66
|
-
left: -1px;
|
|
67
|
-
display: block;
|
|
68
|
-
width: 1px;
|
|
69
|
-
height: 16px;
|
|
70
|
-
border-radius: 4px;
|
|
71
|
-
background-color: var(--c-divider);
|
|
72
|
-
content: "";
|
|
73
|
-
transition: opacity 0.25s;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.SInputSegmentsOption.active + &::before,
|
|
77
|
-
&.active::before {
|
|
78
|
-
opacity: 0;
|
|
79
|
-
}
|
|
80
62
|
}
|
|
81
63
|
|
|
82
|
-
.SInputSegmentsOption.sm,
|
|
83
|
-
.SInputSegmentsOption.mini {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.label {
|
|
89
|
-
padding: 0 12px;
|
|
90
|
-
}
|
|
64
|
+
.SInputSegmentsOption.sm .label,
|
|
65
|
+
.SInputSegmentsOption.mini .label {
|
|
66
|
+
padding: 0 12px;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
font-weight: 500;
|
|
91
69
|
}
|
|
92
70
|
|
|
93
|
-
.SInputSegmentsOption.md {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.label {
|
|
99
|
-
padding: 0 12px;
|
|
100
|
-
}
|
|
71
|
+
.SInputSegmentsOption.md .label {
|
|
72
|
+
padding: 0 12px;
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
font-weight: 500;
|
|
101
75
|
}
|
|
102
76
|
|
|
103
|
-
.SInputSegmentsOption.small {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
.label {
|
|
109
|
-
padding: 0 12px;
|
|
110
|
-
}
|
|
77
|
+
.SInputSegmentsOption.small .label {
|
|
78
|
+
padding: 0 12px;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
font-weight: 500;
|
|
111
81
|
}
|
|
112
82
|
|
|
113
|
-
.SInputSegmentsOption.medium {
|
|
114
|
-
|
|
115
|
-
top: 10px;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.label {
|
|
119
|
-
padding: 0 16px;
|
|
120
|
-
}
|
|
83
|
+
.SInputSegmentsOption.medium .label {
|
|
84
|
+
padding: 0 16px;
|
|
121
85
|
}
|
|
122
86
|
|
|
123
87
|
.SInputSegmentsOption.default.active {
|
|
124
88
|
border-color: var(--button-fill-default-border-color);
|
|
125
89
|
color: var(--button-fill-default-text-color);
|
|
126
90
|
background-color: var(--button-fill-default-bg-color);
|
|
91
|
+
box-shadow: var(--shadow-depth-1);
|
|
127
92
|
}
|
|
128
93
|
|
|
129
94
|
.SInputSegmentsOption.mute.active {
|
|
130
95
|
border-color: var(--button-fill-mute-border-color);
|
|
131
96
|
color: var(--button-fill-mute-text-color);
|
|
132
97
|
background-color: var(--button-fill-mute-bg-color);
|
|
98
|
+
box-shadow: var(--shadow-depth-1);
|
|
133
99
|
}
|
|
134
100
|
|
|
135
101
|
.SInputSegmentsOption.neutral.active {
|