picocrank 1.32.0 → 1.34.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/docs/filter-query-v1.md +5 -1
- package/docs/table.md +99 -0
- package/package.json +1 -1
- package/router.js +33 -0
- package/styles/picocrank-extensions.css +23 -2
- package/vue/components/Accordion.spec.js +48 -0
- package/vue/components/Accordion.vue +46 -0
- package/vue/components/AccordionItem.vue +103 -0
- package/vue/components/FieldValue.spec.js +88 -0
- package/vue/components/FieldValue.vue +222 -0
- package/vue/components/FormField.spec.js +1 -0
- package/vue/components/FormField.vue +38 -10
- package/vue/components/Header.vue +24 -2
- package/vue/components/NotificationBlock.vue +25 -0
- package/vue/components/ReorderList.spec.js +318 -0
- package/vue/components/ReorderList.vue +614 -0
- package/vue/components/Table.spec.js +490 -1
- package/vue/components/Table.vue +1038 -111
- package/vue/components/TableColumnFilterPopover.vue +63 -19
- package/vue/components/TableColumnOptionsPopover.vue +248 -273
- package/vue/components/TopBar.vue +6 -2
- package/vue/components/accordionContext.js +1 -0
- package/vue/composables/tableColumnPresets.js +33 -0
- package/vue/composables/tableFilters.js +6 -1
- package/vue/composables/tableGroupBy.js +173 -0
- package/vue/composables/tableGroupBy.spec.js +69 -0
- package/vue/composables/tableViewSettings.js +34 -0
- package/vue/composables/tableViewSettings.spec.js +21 -0
- package/vue/composables/useHeaderTopBarFit.js +91 -0
- package/vue/composables/useResponsiveNav.js +5 -3
- package/vue/data/navSectionHubs.js +16 -1
- package/vue/examples/AccordionExample.vue +33 -0
- package/vue/examples/FieldValueExample.vue +143 -0
- package/vue/examples/FormExample.vue +11 -6
- package/vue/examples/ReorderListExample.vue +236 -0
- package/vue/examples/TableExample.vue +5 -1
- package/vue/examples/TableRemoteExample.vue +28 -4
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Field value"
|
|
4
|
+
:icon="Edit03Icon"
|
|
5
|
+
subtitle="A displayed value with read-only, inline edit, or outline edit."
|
|
6
|
+
>
|
|
7
|
+
<p>
|
|
8
|
+
Pass an <code>#edit</code> template to edit in place. Without that template, set
|
|
9
|
+
<code>to</code> or <code>href</code> so the icon opens another page. Otherwise the
|
|
10
|
+
value is read-only. Use the <code>#title</code> slot on <code>Section</code> for an
|
|
11
|
+
editable heading.
|
|
12
|
+
</p>
|
|
13
|
+
</Section>
|
|
14
|
+
|
|
15
|
+
<Section subtitle="FieldValue in the Section title slot — click the heading to rename">
|
|
16
|
+
<template #title>
|
|
17
|
+
<FieldValue v-model="sectionTitle">
|
|
18
|
+
<template #edit="{ value, update, commit, cancel }">
|
|
19
|
+
<input
|
|
20
|
+
:value="value"
|
|
21
|
+
type="text"
|
|
22
|
+
class="field-value-title-input"
|
|
23
|
+
@input="update($event.target.value)"
|
|
24
|
+
@keyup.enter="commit"
|
|
25
|
+
@keyup.escape="cancel"
|
|
26
|
+
@blur="commit"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
29
|
+
</FieldValue>
|
|
30
|
+
</template>
|
|
31
|
+
<p>
|
|
32
|
+
The value above is the section heading. It uses the same inline edit pattern as
|
|
33
|
+
definition-list fields below.
|
|
34
|
+
</p>
|
|
35
|
+
</Section>
|
|
36
|
+
|
|
37
|
+
<Section title="Read only" subtitle="No edit template and no outline target">
|
|
38
|
+
<dl class="field-value-dl">
|
|
39
|
+
<dt>Created</dt>
|
|
40
|
+
<dd>
|
|
41
|
+
<FieldValue model-value="2 hours ago" />
|
|
42
|
+
</dd>
|
|
43
|
+
</dl>
|
|
44
|
+
</Section>
|
|
45
|
+
|
|
46
|
+
<Section title="Inline edit" subtitle="An edit template replaces the value until commit or cancel">
|
|
47
|
+
<dl class="field-value-dl">
|
|
48
|
+
<dt>Name</dt>
|
|
49
|
+
<dd>
|
|
50
|
+
<FieldValue v-model="name" @commit="lastCommit = $event">
|
|
51
|
+
<template #edit="{ value, update, commit, cancel }">
|
|
52
|
+
<input
|
|
53
|
+
:value="value"
|
|
54
|
+
type="text"
|
|
55
|
+
@input="update($event.target.value)"
|
|
56
|
+
@keyup.enter="commit"
|
|
57
|
+
@keyup.escape="cancel"
|
|
58
|
+
@blur="commit"
|
|
59
|
+
/>
|
|
60
|
+
</template>
|
|
61
|
+
</FieldValue>
|
|
62
|
+
</dd>
|
|
63
|
+
|
|
64
|
+
<dt>Active</dt>
|
|
65
|
+
<dd>
|
|
66
|
+
<FieldValue v-model="active">
|
|
67
|
+
<template #default>{{ active === '1' ? 'Yes' : 'No' }}</template>
|
|
68
|
+
<template #edit="{ value, update, commit }">
|
|
69
|
+
<input
|
|
70
|
+
type="checkbox"
|
|
71
|
+
:checked="value === '1'"
|
|
72
|
+
@change="event => {
|
|
73
|
+
update(event.target.checked ? '1' : '0')
|
|
74
|
+
commit()
|
|
75
|
+
}"
|
|
76
|
+
/>
|
|
77
|
+
</template>
|
|
78
|
+
</FieldValue>
|
|
79
|
+
</dd>
|
|
80
|
+
</dl>
|
|
81
|
+
<p v-if="lastCommit" class="subtle">Last saved name: {{ lastCommit }}</p>
|
|
82
|
+
</Section>
|
|
83
|
+
|
|
84
|
+
<Section title="Outline edit" subtitle="The icon navigates; the value itself stays read-only">
|
|
85
|
+
<dl class="field-value-dl">
|
|
86
|
+
<dt>Website</dt>
|
|
87
|
+
<dd>
|
|
88
|
+
<FieldValue
|
|
89
|
+
model-value="example.com"
|
|
90
|
+
to="/form-example"
|
|
91
|
+
edit-label="Edit on the form page"
|
|
92
|
+
/>
|
|
93
|
+
</dd>
|
|
94
|
+
|
|
95
|
+
<dt>Docs</dt>
|
|
96
|
+
<dd>
|
|
97
|
+
<FieldValue
|
|
98
|
+
model-value="femtocrank"
|
|
99
|
+
href="https://github.com/jamesread/femtocrank"
|
|
100
|
+
edit-label="Open documentation"
|
|
101
|
+
/>
|
|
102
|
+
</dd>
|
|
103
|
+
</dl>
|
|
104
|
+
</Section>
|
|
105
|
+
</template>
|
|
106
|
+
|
|
107
|
+
<script setup>
|
|
108
|
+
import { ref } from 'vue'
|
|
109
|
+
import { Edit03Icon } from '@hugeicons/core-free-icons'
|
|
110
|
+
import Section from '../components/Section.vue'
|
|
111
|
+
import FieldValue from '../components/FieldValue.vue'
|
|
112
|
+
|
|
113
|
+
const sectionTitle = ref('Project settings')
|
|
114
|
+
const name = ref('Ada Lovelace')
|
|
115
|
+
const active = ref('1')
|
|
116
|
+
const lastCommit = ref('')
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<style scoped>
|
|
120
|
+
.field-value-dl {
|
|
121
|
+
grid-template-columns: 12rem minmax(0, 1fr);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.field-value-dl dt {
|
|
125
|
+
grid-column: 1;
|
|
126
|
+
box-sizing: border-box;
|
|
127
|
+
min-width: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.field-value-dl dd {
|
|
131
|
+
grid-column: 2;
|
|
132
|
+
box-sizing: border-box;
|
|
133
|
+
min-width: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.field-value-title-input {
|
|
137
|
+
font: inherit;
|
|
138
|
+
font-weight: inherit;
|
|
139
|
+
color: inherit;
|
|
140
|
+
min-width: 12rem;
|
|
141
|
+
max-width: 100%;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -101,12 +101,17 @@
|
|
|
101
101
|
/>
|
|
102
102
|
</FormField>
|
|
103
103
|
|
|
104
|
-
<FormField label="Is Administrator?">
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
<FormField label="Is Administrator?" component-has-label>
|
|
105
|
+
<div>
|
|
106
|
+
<label>
|
|
107
|
+
<input
|
|
108
|
+
id="is-admin"
|
|
109
|
+
v-model="formData.isAdmin"
|
|
110
|
+
type="checkbox"
|
|
111
|
+
/>
|
|
112
|
+
<span>Administrator</span>
|
|
113
|
+
</label>
|
|
114
|
+
</div>
|
|
110
115
|
</FormField>
|
|
111
116
|
|
|
112
117
|
<FormField
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Section
|
|
3
|
+
title="Reorder list"
|
|
4
|
+
:icon="DragDropVerticalIcon"
|
|
5
|
+
subtitle="Drag the handle, or use the arrows, to change the order of any components shown as rows."
|
|
6
|
+
>
|
|
7
|
+
<p>
|
|
8
|
+
<code>ReorderList</code> is a vertical list, separate from <code>Table</code>.
|
|
9
|
+
Each row’s body is the <code>#item</code> slot, so the reordered content can be
|
|
10
|
+
any component. The list emits <code>update:modelValue</code> with a new array.
|
|
11
|
+
While dragging, a line marks the gap where the row will be inserted (native HTML5 drag-and-drop).
|
|
12
|
+
<code>showMoveButtons</code>, <code>showDragHandles</code>, and <code>dragWholeRow</code>
|
|
13
|
+
control how rows are reordered.
|
|
14
|
+
</p>
|
|
15
|
+
</Section>
|
|
16
|
+
|
|
17
|
+
<Section title="Checklist" subtitle="Status cards in a row list">
|
|
18
|
+
<template #toolbar>
|
|
19
|
+
<label class="reorder-example-toggle">
|
|
20
|
+
<input v-model="showMoveButtons" type="checkbox" />
|
|
21
|
+
Move buttons
|
|
22
|
+
</label>
|
|
23
|
+
<label class="reorder-example-toggle">
|
|
24
|
+
<input v-model="showDragHandles" type="checkbox" />
|
|
25
|
+
Drag handles
|
|
26
|
+
</label>
|
|
27
|
+
<label class="reorder-example-toggle">
|
|
28
|
+
<input v-model="dragWholeRow" type="checkbox" />
|
|
29
|
+
Whole row drag
|
|
30
|
+
</label>
|
|
31
|
+
<button type="button" class="neutral" @click="resetTasks">
|
|
32
|
+
Reset order
|
|
33
|
+
</button>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<ReorderList
|
|
37
|
+
v-model="tasks"
|
|
38
|
+
item-key="id"
|
|
39
|
+
aria-label="Checklist order"
|
|
40
|
+
:show-move-buttons="showMoveButtons"
|
|
41
|
+
:show-drag-handles="showDragHandles"
|
|
42
|
+
:drag-whole-row="dragWholeRow"
|
|
43
|
+
>
|
|
44
|
+
<template #item="{ item }">
|
|
45
|
+
<div class="task-row">
|
|
46
|
+
<div class="task-copy">
|
|
47
|
+
<strong>{{ item.title }}</strong>
|
|
48
|
+
<p class="subtle">{{ item.detail }}</p>
|
|
49
|
+
</div>
|
|
50
|
+
<span class="tag">{{ item.tag }}</span>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
</ReorderList>
|
|
54
|
+
</Section>
|
|
55
|
+
|
|
56
|
+
<Section title="Current order" subtitle="Bound array after each move">
|
|
57
|
+
<ol class="order-readout">
|
|
58
|
+
<li v-for="task in tasks" :key="task.id">{{ task.title }}</li>
|
|
59
|
+
</ol>
|
|
60
|
+
</Section>
|
|
61
|
+
|
|
62
|
+
<Section
|
|
63
|
+
title="Nested lists"
|
|
64
|
+
subtitle="Reorder phases, then reorder steps inside each phase — each list is its own ReorderList."
|
|
65
|
+
>
|
|
66
|
+
<template #toolbar>
|
|
67
|
+
<button type="button" class="neutral" @click="resetPhases">
|
|
68
|
+
Reset nested demo
|
|
69
|
+
</button>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<ReorderList
|
|
73
|
+
v-model="phases"
|
|
74
|
+
item-key="id"
|
|
75
|
+
aria-label="Release phases"
|
|
76
|
+
:show-drag-handles="true"
|
|
77
|
+
>
|
|
78
|
+
<template #item="{ item }">
|
|
79
|
+
<div class="nested-phase">
|
|
80
|
+
<p class="nested-phase-title">{{ item.title }}</p>
|
|
81
|
+
<ReorderList
|
|
82
|
+
v-model="item.steps"
|
|
83
|
+
item-key="id"
|
|
84
|
+
class="nested-phase-steps"
|
|
85
|
+
:aria-label="`${item.title} steps`"
|
|
86
|
+
drag-whole-row
|
|
87
|
+
>
|
|
88
|
+
<template #item="{ item: step }">
|
|
89
|
+
<span class="nested-step-label">{{ step.label }}</span>
|
|
90
|
+
</template>
|
|
91
|
+
</ReorderList>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
</ReorderList>
|
|
95
|
+
</Section>
|
|
96
|
+
|
|
97
|
+
<Section title="Nested order" subtitle="Phases and their step arrays after moves">
|
|
98
|
+
<ul class="nested-readout">
|
|
99
|
+
<li v-for="phase in phases" :key="phase.id">
|
|
100
|
+
<strong>{{ phase.title }}</strong>
|
|
101
|
+
<ol>
|
|
102
|
+
<li v-for="step in phase.steps" :key="step.id">{{ step.label }}</li>
|
|
103
|
+
</ol>
|
|
104
|
+
</li>
|
|
105
|
+
</ul>
|
|
106
|
+
</Section>
|
|
107
|
+
</template>
|
|
108
|
+
|
|
109
|
+
<script setup>
|
|
110
|
+
import { ref } from 'vue'
|
|
111
|
+
import { DragDropVerticalIcon } from '@hugeicons/core-free-icons'
|
|
112
|
+
import Section from '../components/Section.vue'
|
|
113
|
+
import ReorderList from '../components/ReorderList.vue'
|
|
114
|
+
|
|
115
|
+
const initialTasks = [
|
|
116
|
+
{ id: 'deploy', title: 'Deploy service', detail: 'Production rollout window', tag: 'Ops' },
|
|
117
|
+
{ id: 'review', title: 'Review access', detail: 'Quarterly IAM check', tag: 'Security' },
|
|
118
|
+
{ id: 'backup', title: 'Verify backups', detail: 'Nightly snapshot', tag: 'Ops' },
|
|
119
|
+
{ id: 'notes', title: 'Publish notes', detail: 'Changelog for 2.4', tag: 'Docs' },
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
const tasks = ref(initialTasks.map((task) => ({ ...task })))
|
|
123
|
+
const showMoveButtons = ref(false)
|
|
124
|
+
const showDragHandles = ref(true)
|
|
125
|
+
const dragWholeRow = ref(false)
|
|
126
|
+
|
|
127
|
+
function resetTasks() {
|
|
128
|
+
tasks.value = initialTasks.map((task) => ({ ...task }))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const initialPhases = [
|
|
132
|
+
{
|
|
133
|
+
id: 'build',
|
|
134
|
+
title: 'Build',
|
|
135
|
+
steps: [
|
|
136
|
+
{ id: 'build-lint', label: 'Run linters' },
|
|
137
|
+
{ id: 'build-test', label: 'Unit tests' },
|
|
138
|
+
{ id: 'build-image', label: 'Container image' },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'staging',
|
|
143
|
+
title: 'Staging',
|
|
144
|
+
steps: [
|
|
145
|
+
{ id: 'staging-deploy', label: 'Deploy to staging' },
|
|
146
|
+
{ id: 'staging-smoke', label: 'Smoke tests' },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'production',
|
|
151
|
+
title: 'Production',
|
|
152
|
+
steps: [
|
|
153
|
+
{ id: 'prod-approve', label: 'Change approval' },
|
|
154
|
+
{ id: 'prod-deploy', label: 'Rolling deploy' },
|
|
155
|
+
{ id: 'prod-verify', label: 'Post-deploy checks' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
const phases = ref(clonePhases(initialPhases))
|
|
161
|
+
|
|
162
|
+
function clonePhases(source) {
|
|
163
|
+
return source.map((phase) => ({
|
|
164
|
+
...phase,
|
|
165
|
+
steps: phase.steps.map((step) => ({ ...step })),
|
|
166
|
+
}))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function resetPhases() {
|
|
170
|
+
phases.value = clonePhases(initialPhases)
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<style scoped>
|
|
175
|
+
.task-row {
|
|
176
|
+
display: flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
gap: 0.75rem;
|
|
179
|
+
min-width: 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.task-copy {
|
|
183
|
+
flex: 1 1 auto;
|
|
184
|
+
min-width: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.task-copy p {
|
|
188
|
+
margin: 0.15rem 0 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.order-readout {
|
|
192
|
+
margin: 0;
|
|
193
|
+
padding-left: 1.25rem;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.reorder-example-toggle {
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 0.4em;
|
|
200
|
+
margin-right: 0.75em;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
user-select: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.nested-phase {
|
|
206
|
+
flex: 1 1 auto;
|
|
207
|
+
min-width: 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.nested-phase-title {
|
|
211
|
+
margin: 0 0 0.5rem;
|
|
212
|
+
font-weight: bold;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.nested-phase-steps {
|
|
216
|
+
margin-top: 0.25rem;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.nested-phase-steps :deep(.reorder-list-row) {
|
|
220
|
+
padding: 0.4rem 0.55rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.nested-step-label {
|
|
224
|
+
display: block;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.nested-readout {
|
|
228
|
+
margin: 0;
|
|
229
|
+
padding-left: 1.25rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.nested-readout ol {
|
|
233
|
+
margin: 0.35rem 0 0.75rem;
|
|
234
|
+
padding-left: 1.25rem;
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<Section
|
|
3
3
|
:title="tableSectionTitle"
|
|
4
4
|
:icon="TableIcon"
|
|
5
|
-
subtitle="Sortable columns, pagination,
|
|
5
|
+
subtitle="Sortable columns, pagination, column filters, and group-by. Right-click or long-press a column header to filter or group. Use Column options for group-by as well."
|
|
6
6
|
:padding="false"
|
|
7
7
|
>
|
|
8
8
|
<template v-if="multiSelectEnabled" #toolbar>
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
:sticky-cols="stickyCols"
|
|
67
67
|
v-model:column-visibility="columnVisibility"
|
|
68
68
|
v-model:layout-label="tableLayoutLabel"
|
|
69
|
+
v-model:group-by="tableGroupBy"
|
|
69
70
|
:default-column-visibility="defaultColumnVisibility"
|
|
70
71
|
v-bind="tableListeners"
|
|
71
72
|
>
|
|
@@ -163,6 +164,7 @@
|
|
|
163
164
|
<strong>Query:</strong>
|
|
164
165
|
page {{ lastQuery.page }},
|
|
165
166
|
sort {{ lastQuery.sortBy ?? 'none' }} {{ lastQuery.sortDir }},
|
|
167
|
+
group {{ lastQuery.groupBy ?? 'none' }},
|
|
166
168
|
{{ activeFilterCount }} active filter(s)
|
|
167
169
|
</p>
|
|
168
170
|
<p v-else class="event-line subtle">Query: waiting for the first table update…</p>
|
|
@@ -256,6 +258,7 @@ const lastBulkAction = ref(null)
|
|
|
256
258
|
const lastContextMenuAction = ref(null)
|
|
257
259
|
const selectedKeys = ref([])
|
|
258
260
|
const tableLayoutLabel = ref('')
|
|
261
|
+
const tableGroupBy = ref(null)
|
|
259
262
|
const rowDialogRef = ref(null)
|
|
260
263
|
|
|
261
264
|
const multiSelectEnabled = computed(() => enabledFeatures.value.includes('multiSelect'))
|
|
@@ -471,6 +474,7 @@ function resetControls() {
|
|
|
471
474
|
lastBulkAction.value = null
|
|
472
475
|
lastContextMenuAction.value = null
|
|
473
476
|
selectedKeys.value = []
|
|
477
|
+
tableGroupBy.value = null
|
|
474
478
|
if (rowDialogRef.value?.open) {
|
|
475
479
|
rowDialogRef.value.close()
|
|
476
480
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<Section
|
|
3
3
|
title="Remote table"
|
|
4
4
|
:icon="CloudServerIcon"
|
|
5
|
-
subtitle="Server-style pagination with fetchRows. Filter queries, sort, and page changes are sent to the callback.
|
|
5
|
+
subtitle="Server-style pagination with fetchRows. Filter queries, sort, and page changes are sent to the callback. Saved views use callbackControlled view settings (not localStorage)."
|
|
6
6
|
:padding="false"
|
|
7
7
|
>
|
|
8
8
|
<p v-if="lastQuerySummary" class="subtle query-summary">{{ lastQuerySummary }}</p>
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
row-key="name"
|
|
12
12
|
:headers="headers"
|
|
13
13
|
:fetch-rows="fetchRows"
|
|
14
|
-
|
|
14
|
+
view-control-mode="callbackControlled"
|
|
15
|
+
:view-settings="viewSettings"
|
|
15
16
|
:show-pagination="true"
|
|
16
17
|
:remote-debounce-ms="250"
|
|
17
18
|
/>
|
|
@@ -61,7 +62,7 @@ function createRemotePresetId() {
|
|
|
61
62
|
return `remote-preset-${remotePresetCounter}`
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
const
|
|
65
|
+
const viewSettings = {
|
|
65
66
|
async list() {
|
|
66
67
|
return {
|
|
67
68
|
presets: remoteLayoutStore.value.presets.map((preset) => ({ ...preset })),
|
|
@@ -105,6 +106,29 @@ const layoutPresets = {
|
|
|
105
106
|
}
|
|
106
107
|
},
|
|
107
108
|
|
|
109
|
+
async overwrite({ presetId, state }) {
|
|
110
|
+
const index = remoteLayoutStore.value.presets.findIndex((item) => item.id === presetId)
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
return { ok: false, error: 'Preset not found.' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const preset = {
|
|
116
|
+
...remoteLayoutStore.value.presets[index],
|
|
117
|
+
savedAt: new Date().toISOString(),
|
|
118
|
+
state: JSON.parse(JSON.stringify(state)),
|
|
119
|
+
}
|
|
120
|
+
remoteLayoutStore.value.presets = remoteLayoutStore.value.presets.map((item, i) => (
|
|
121
|
+
i === index ? preset : item
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
console.info('[remote table] overwrote layout preset', preset)
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
preset,
|
|
128
|
+
defaultPresetId: remoteLayoutStore.value.defaultPresetId,
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
108
132
|
async load(presetId) {
|
|
109
133
|
const preset = remoteLayoutStore.value.presets.find((item) => item.id === presetId)
|
|
110
134
|
if (!preset) {
|
|
@@ -171,7 +195,7 @@ async function fetchRows(query, { signal } = {}) {
|
|
|
171
195
|
const start = (query.page - 1) * query.pageSize
|
|
172
196
|
const rows = sorted.slice(start, start + query.pageSize)
|
|
173
197
|
|
|
174
|
-
lastQuerySummary.value = `Last query: page ${query.page}, ${countActiveFilterQuery(query.filterQuery, headers.value, allRows)} active filter(s), ${sorted.length} matching row(s).`
|
|
198
|
+
lastQuerySummary.value = `Last query: page ${query.page}, group ${query.groupBy ?? 'none'}, ${countActiveFilterQuery(query.filterQuery, headers.value, allRows)} active filter(s), ${sorted.length} matching row(s).`
|
|
175
199
|
|
|
176
200
|
return {
|
|
177
201
|
rows,
|