Skip to content

Commit 7500831

Browse files
committed
feat: Reorder views
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent 461f96e commit 7500831

9 files changed

Lines changed: 132 additions & 5 deletions

File tree

lib/Constants/ViewUpdatableParameters.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ enum ViewUpdatableParameters: string {
1717
case FILTER = 'filter';
1818
case COLUMN_SETTINGS = 'columns';
1919
case TECHNICAL_NAME = 'technicalName';
20+
case SIDEBAR_ORDER = 'sidebarOrder';
2021
}

lib/Db/View.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,15 @@
7070
* @method setExternalId(?int $externalId)
7171
* @method getShareToken(): ?string
7272
* @method setShareToken(?string $shareToken)
73+
* @method getSidebarOrder(): ?int
74+
* @method setSidebarOrder(?int $sidebarOrder)
7375
*/
7476
class View extends EntitySuper implements JsonSerializable {
7577
protected ?string $uuid = null;
7678
protected ?string $title = null;
7779
protected ?string $technicalName = null;
7880
protected ?int $tableId = null;
81+
protected ?int $sidebarOrder = null;
7982
protected ?string $createdBy = null;
8083
protected ?string $createdAt = null;
8184
protected ?string $lastEditBy = null;
@@ -104,6 +107,7 @@ public function __construct() {
104107
$this->addType('id', 'integer');
105108
$this->addType('uuid', 'string');
106109
$this->addType('tableId', 'integer');
110+
$this->addType('sidebarOrder', 'integer');
107111
}
108112

109113
public function setter(string $name, array $args): void {
@@ -239,6 +243,7 @@ public function jsonSerialize(): array {
239243
'rowsCount' => $this->rowsCount ?: 0,
240244
'ownerDisplayName' => $this->ownerDisplayName,
241245
'isFederated' => $this->isFederated(),
246+
'sidebarOrder' => $this->sidebarOrder,
242247
];
243248
$serialisedJson['filter'] = $this->getFilterArray();
244249

lib/Db/ViewMapper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ public function findAll(?int $tableId = null): array {
139139
if ($tableId !== null) {
140140
$qb->where($qb->expr()->eq('v.table_id', $qb->createNamedParameter($tableId, IQueryBuilder::PARAM_INT)));
141141
}
142+
143+
$qb->addOrderBy('v.sidebar_order', 'ASC');
144+
$qb->addOrderBy('v.id', 'ASC');
145+
142146
return $this->findEntities($qb);
143147
}
144148

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Tables\Migration;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\IOutput;
16+
use OCP\Migration\SimpleMigrationStep;
17+
use Override;
18+
19+
class Version2210Date20260709000000 extends SimpleMigrationStep {
20+
#[Override]
21+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
22+
/** @var ISchemaWrapper $schema */
23+
$schema = $schemaClosure();
24+
25+
if (!$schema->hasTable('tables_views')) {
26+
return null;
27+
}
28+
29+
$table = $schema->getTable('tables_views');
30+
if (!$table->hasColumn('sidebar_order')) {
31+
$table->addColumn('sidebar_order', Types::BIGINT, [
32+
'notnull' => false,
33+
'default' => null,
34+
]);
35+
}
36+
37+
return $schema;
38+
}
39+
}

lib/Model/ViewUpdateInput.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@ public function __construct(
3030
protected readonly ?ColumnSettings $columnSettings = null,
3131
protected readonly ?FilterSet $filterSet = null,
3232
protected readonly ?SortRuleSet $sortRuleSet = null,
33+
protected readonly ?int $sidebarOrder = null,
3334
) {
3435
}
3536

3637
public function updateDetail(): Generator {
38+
if ($this->sidebarOrder !== null) {
39+
yield ViewUpdatableParameters::SIDEBAR_ORDER => $this->sidebarOrder;
40+
}
3741
if ($this->title) {
3842
yield ViewUpdatableParameters::TITLE => $this->title;
3943
}
@@ -66,7 +70,8 @@ public function updateDetail(): Generator {
6670
* columns?: list<int>,
6771
* columnSettings?: list<array{columnId?: int, order?: int, readonly?: bool, mandatory?: bool}>,
6872
* sort?: list<array{columnId: int, mode: 'ASC'|'DESC'}>,
69-
* filter?: list<list<array{columnId: int, operator: 'begins-with'|'ends-with'|'contains'|'does-not-contain'|'is-equal'|'is-not-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal'|'is-empty', value: string|int|float}>>
73+
* filter?: list<list<array{columnId: int, operator: 'begins-with'|'ends-with'|'contains'|'does-not-contain'|'is-equal'|'is-not-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal'|'is-empty', value: string|int|float}>>,
74+
* sidebarOrder?: int
7075
* } $data
7176
* @param array $columnsMap
7277
*/
@@ -94,6 +99,7 @@ public static function fromInputArray(array $data, array $columnsMap = []): self
9499
columnSettings: isset($data['columnSettings']) ? ColumnSettings::createViewSettingsFromInputArray($data['columnSettings'], $columnsMap) : null,
95100
filterSet: isset($data['filter']) ? FilterSet::createFromInputArray($data['filter'], $columnsMap) : null,
96101
sortRuleSet: isset($data['sort']) ? SortRuleSet::createFromInputArray($data['sort'], $columnsMap) : null,
102+
sidebarOrder: (array_key_exists('sidebarOrder', $data) && $data['sidebarOrder'] !== null) ? (int)$data['sidebarOrder'] : null,
97103
);
98104
}
99105

lib/ResponseDefinitions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* hasShares: bool,
4545
* rowsCount: int,
4646
* isFederated: bool,
47+
* sidebarOrder: int|null,
4748
* }
4849
*
4950
* @psalm-type TablesTable = array{

lib/Service/ViewService.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ public function update(int $id, ViewUpdateInput $data, ?string $userId = null, b
258258
}
259259

260260
foreach ($data->updateDetail() as $parameter => $value) {
261+
$insertableValue = null;
262+
261263
if ($parameter === ViewUpdatableParameters::COLUMN_SETTINGS
262264
&& $value instanceof ColumnSettings
263265
) {

src/modules/navigation/partials/NavigationTableItem.vue

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,14 @@
135135
</NcActionButton>
136136
</template>
137137
<ul>
138-
<NavigationViewItem v-for="view in getViews" :key="'view' + view.id" :view="view"
139-
:show-share-sender="false" />
138+
<NavigationViewItem v-for="(view, index) in orderedViews" :key="'view' + view.id" :view="view"
139+
:show-share-sender="false"
140+
:draggable="canReorderViews"
141+
:class="{ 'view-drop-target': dragOverIndex === index }"
142+
@dragstart.native="onViewDragStart(index)"
143+
@dragover.native.prevent="onViewDragOver(index)"
144+
@drop.native.prevent="onViewDrop"
145+
@dragend.native="onViewDragEnd" />
140146
</ul>
141147
</NcAppNavigationItem>
142148
</template>
@@ -206,6 +212,9 @@ export default {
206212
data() {
207213
return {
208214
isParentOfActiveView: false,
215+
orderedViews: [],
216+
draggedIndex: null,
217+
dragOverIndex: null,
209218
}
210219
},
211220
@@ -218,13 +227,28 @@ export default {
218227
return getCurrentUser().uid
219228
},
220229
getViews() {
221-
return this.views.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
230+
return this.views
231+
.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
232+
.sort((a, b) => {
233+
const orderA = a.sidebarOrder ?? Number.MAX_SAFE_INTEGER
234+
const orderB = b.sidebarOrder ?? Number.MAX_SAFE_INTEGER
235+
return orderA - orderB || a.id - b.id
236+
})
222237
},
223238
hasViews() {
224239
return this.getViews.length > 0
225240
},
241+
canReorderViews() {
242+
return this.canManageElement(this.table) && !this.filterString && this.orderedViews.length > 1
243+
},
226244
},
227245
watch: {
246+
getViews: {
247+
handler(views) {
248+
this.orderedViews = [...views]
249+
},
250+
immediate: true,
251+
},
228252
activeView: {
229253
immediate: true,
230254
handler() {
@@ -240,8 +264,47 @@ export default {
240264
},
241265
},
242266
methods: {
243-
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable']),
267+
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable', 'updateView']),
244268
emit,
269+
onViewDragStart(index) {
270+
if (!this.canReorderViews) {
271+
return
272+
}
273+
this.draggedIndex = index
274+
},
275+
onViewDragOver(index) {
276+
if (this.draggedIndex === null || this.draggedIndex === index) {
277+
return
278+
}
279+
const moved = this.orderedViews.splice(this.draggedIndex, 1)[0]
280+
this.orderedViews.splice(index, 0, moved)
281+
this.draggedIndex = index
282+
this.dragOverIndex = index
283+
},
284+
onViewDrop() {
285+
this.persistViewOrder()
286+
},
287+
onViewDragEnd() {
288+
this.persistViewOrder()
289+
},
290+
async persistViewOrder() {
291+
if (this.draggedIndex === null) {
292+
this.dragOverIndex = null
293+
return
294+
}
295+
this.draggedIndex = null
296+
this.dragOverIndex = null
297+
298+
const updates = []
299+
this.orderedViews.forEach((view, index) => {
300+
if (view.sidebarOrder !== index) {
301+
updates.push(this.updateView({ id: view.id, data: { data: { sidebarOrder: index } } }))
302+
}
303+
})
304+
if (updates.length) {
305+
await Promise.all(updates)
306+
}
307+
},
245308
deleteTable() {
246309
emit('tables:table:delete', this.table)
247310
},
@@ -348,4 +411,8 @@ export default {
348411
display: inline;
349412
}
350413
}
414+
415+
.view-drop-target {
416+
border-top: 2px solid var(--color-primary-element);
417+
}
351418
</style>

src/types/openapi/openapi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,8 @@ export type components = {
14031403
/** Format: int64 */
14041404
readonly rowsCount: number;
14051405
readonly isFederated: boolean;
1406+
/** Format: int64 */
1407+
readonly sidebarOrder: number | null;
14061408
};
14071409
};
14081410
responses: never;

0 commit comments

Comments
 (0)