Skip to content

Commit 49dedce

Browse files
committed
feat: Reorder views
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
1 parent 6eafd63 commit 49dedce

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
@@ -16,4 +16,5 @@ enum ViewUpdatableParameters: string {
1616
case SORT = 'sort';
1717
case FILTER = 'filter';
1818
case COLUMN_SETTINGS = 'columns';
19+
case SIDEBAR_ORDER = 'sidebarOrder';
1920
}

lib/Db/View.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,13 @@
6161
* @method setOwnerDisplayName(string $ownerDisplayName)
6262
* @method getOwnership(): ?string
6363
* @method setOwnership(string $ownership)
64+
* @method getSidebarOrder(): ?int
65+
* @method setSidebarOrder(?int $sidebarOrder)
6466
*/
6567
class View extends EntitySuper implements JsonSerializable {
6668
protected ?string $title = null;
6769
protected ?int $tableId = null;
70+
protected ?int $sidebarOrder = null;
6871
protected ?string $createdBy = null;
6972
protected ?string $createdAt = null;
7073
protected ?string $lastEditBy = null;
@@ -89,6 +92,7 @@ class View extends EntitySuper implements JsonSerializable {
8992
public function __construct() {
9093
$this->addType('id', 'integer');
9194
$this->addType('tableId', 'integer');
95+
$this->addType('sidebarOrder', 'integer');
9296
}
9397

9498
/**
@@ -199,6 +203,7 @@ public function jsonSerialize(): array {
199203
'hasShares' => (bool)$this->hasShares,
200204
'rowsCount' => $this->rowsCount ?: 0,
201205
'ownerDisplayName' => $this->ownerDisplayName,
206+
'sidebarOrder' => $this->sidebarOrder,
202207
];
203208
$serialisedJson['filter'] = $this->getFilterArray();
204209

lib/Db/ViewMapper.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ public function findAll(?int $tableId = null): array {
111111
if ($tableId !== null) {
112112
$qb->where($qb->expr()->eq('v.table_id', $qb->createNamedParameter($tableId, IQueryBuilder::PARAM_INT)));
113113
}
114+
115+
$qb->addOrderBy('v.sidebar_order', 'ASC');
116+
$qb->addOrderBy('v.id', 'ASC');
117+
114118
return $this->findEntities($qb);
115119
}
116120

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
@@ -29,10 +29,14 @@ public function __construct(
2929
protected readonly ?ColumnSettings $columnSettings = null,
3030
protected readonly ?FilterSet $filterSet = null,
3131
protected readonly ?SortRuleSet $sortRuleSet = null,
32+
protected readonly ?int $sidebarOrder = null,
3233
) {
3334
}
3435

3536
public function updateDetail(): Generator {
37+
if ($this->sidebarOrder !== null) {
38+
yield ViewUpdatableParameters::SIDEBAR_ORDER => $this->sidebarOrder;
39+
}
3640
if ($this->title) {
3741
yield ViewUpdatableParameters::TITLE => $this->title;
3842
}
@@ -61,7 +65,8 @@ public function updateDetail(): Generator {
6165
* columns?: list<int>,
6266
* columnSettings?: list<array{columnId?: int, order?: int, readonly?: bool, mandatory?: bool}>,
6367
* sort?: list<array{columnId: int, mode: 'ASC'|'DESC'}>,
64-
* 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}>>
68+
* 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}>>,
69+
* sidebarOrder?: int
6570
* } $data
6671
*/
6772
public static function fromInputArray(array $data): self {
@@ -87,6 +92,7 @@ public static function fromInputArray(array $data): self {
8792
columnSettings: ($data['columnSettings'] ?? null) ? ColumnSettings::createViewSettingsFromInputArray($data['columnSettings']) : null,
8893
filterSet: ($data['filter'] ?? null) ? FilterSet::createFromInputArray($data['filter']) : null,
8994
sortRuleSet: ($data['sort'] ?? null) ? SortRuleSet::createFromInputArray($data['sort']) : null,
95+
sidebarOrder: (array_key_exists('sidebarOrder', $data) && $data['sidebarOrder'] !== null) ? (int)$data['sidebarOrder'] : null,
9096
);
9197
}
9298

lib/ResponseDefinitions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* },
4242
* hasShares: bool,
4343
* rowsCount: int,
44+
* sidebarOrder: int|null,
4445
* }
4546
*
4647
* @psalm-type TablesTable = array{

lib/Service/ViewService.php

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

247247
foreach ($data->updateDetail() as $parameter => $value) {
248+
$insertableValue = null;
249+
248250
if ($parameter === ViewUpdatableParameters::COLUMN_SETTINGS
249251
&& $value instanceof ColumnSettings
250252
) {

src/modules/navigation/partials/NavigationTableItem.vue

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,14 @@
128128
</NcActionButton>
129129
</template>
130130
<ul>
131-
<NavigationViewItem v-for="view in getViews" :key="'view' + view.id" :view="view"
132-
:show-share-sender="false" />
131+
<NavigationViewItem v-for="(view, index) in orderedViews" :key="'view' + view.id" :view="view"
132+
:show-share-sender="false"
133+
:draggable="canReorderViews"
134+
:class="{ 'view-drop-target': dragOverIndex === index }"
135+
@dragstart.native="onViewDragStart(index)"
136+
@dragover.native.prevent="onViewDragOver(index)"
137+
@drop.native.prevent="onViewDrop"
138+
@dragend.native="onViewDragEnd" />
133139
</ul>
134140
</NcAppNavigationItem>
135141
</template>
@@ -207,6 +213,9 @@ export default {
207213
data() {
208214
return {
209215
isParentOfActiveView: false,
216+
orderedViews: [],
217+
draggedIndex: null,
218+
dragOverIndex: null,
210219
}
211220
},
212221
@@ -219,13 +228,28 @@ export default {
219228
return getCurrentUser().uid
220229
},
221230
getViews() {
222-
return this.views.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
231+
return this.views
232+
.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
233+
.sort((a, b) => {
234+
const orderA = a.sidebarOrder ?? Number.MAX_SAFE_INTEGER
235+
const orderB = b.sidebarOrder ?? Number.MAX_SAFE_INTEGER
236+
return orderA - orderB || a.id - b.id
237+
})
223238
},
224239
hasViews() {
225240
return this.getViews.length > 0
226241
},
242+
canReorderViews() {
243+
return this.canManageElement(this.table) && !this.filterString && this.orderedViews.length > 1
244+
},
227245
},
228246
watch: {
247+
getViews: {
248+
handler(views) {
249+
this.orderedViews = [...views]
250+
},
251+
immediate: true,
252+
},
229253
activeView() {
230254
if (!this.isParentOfActiveView && this.activeView?.tableId === this.table?.id) {
231255
this.isParentOfActiveView = true
@@ -238,8 +262,47 @@ export default {
238262
},
239263
},
240264
methods: {
241-
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable']),
265+
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable', 'updateView']),
242266
emit,
267+
onViewDragStart(index) {
268+
if (!this.canReorderViews) {
269+
return
270+
}
271+
this.draggedIndex = index
272+
},
273+
onViewDragOver(index) {
274+
if (this.draggedIndex === null || this.draggedIndex === index) {
275+
return
276+
}
277+
const moved = this.orderedViews.splice(this.draggedIndex, 1)[0]
278+
this.orderedViews.splice(index, 0, moved)
279+
this.draggedIndex = index
280+
this.dragOverIndex = index
281+
},
282+
onViewDrop() {
283+
this.persistViewOrder()
284+
},
285+
onViewDragEnd() {
286+
this.persistViewOrder()
287+
},
288+
async persistViewOrder() {
289+
if (this.draggedIndex === null) {
290+
this.dragOverIndex = null
291+
return
292+
}
293+
this.draggedIndex = null
294+
this.dragOverIndex = null
295+
296+
const updates = []
297+
this.orderedViews.forEach((view, index) => {
298+
if (view.sidebarOrder !== index) {
299+
updates.push(this.updateView({ id: view.id, data: { data: { sidebarOrder: index } } }))
300+
}
301+
})
302+
if (updates.length) {
303+
await Promise.all(updates)
304+
}
305+
},
243306
deleteTable() {
244307
emit('tables:table:delete', this.table)
245308
},
@@ -343,4 +406,8 @@ export default {
343406
display: inline;
344407
}
345408
}
409+
410+
.view-drop-target {
411+
border-top: 2px solid var(--color-primary-element);
412+
}
346413
</style>

src/types/openapi/openapi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,8 @@ export type components = {
12381238
readonly hasShares: boolean;
12391239
/** Format: int64 */
12401240
readonly rowsCount: number;
1241+
/** Format: int64 */
1242+
readonly sidebarOrder: number | null;
12411243
};
12421244
};
12431245
responses: never;

0 commit comments

Comments
 (0)