Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/Constants/ViewUpdatableParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ enum ViewUpdatableParameters: string {
case SORT = 'sort';
case FILTER = 'filter';
case COLUMN_SETTINGS = 'columns';
case SIDEBAR_ORDER = 'sidebarOrder';
}
5 changes: 5 additions & 0 deletions lib/Db/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@
* @method setOwnerDisplayName(string $ownerDisplayName)
* @method getOwnership(): ?string
* @method setOwnership(string $ownership)
* @method getSidebarOrder(): ?int
* @method setSidebarOrder(?int $sidebarOrder)
*/
class View extends EntitySuper implements JsonSerializable {
protected ?string $title = null;
protected ?int $tableId = null;
protected ?int $sidebarOrder = null;
protected ?string $createdBy = null;
protected ?string $createdAt = null;
protected ?string $lastEditBy = null;
Expand All @@ -89,6 +92,7 @@ class View extends EntitySuper implements JsonSerializable {
public function __construct() {
$this->addType('id', 'integer');
$this->addType('tableId', 'integer');
$this->addType('sidebarOrder', 'integer');
}

/**
Expand Down Expand Up @@ -199,6 +203,7 @@ public function jsonSerialize(): array {
'hasShares' => (bool)$this->hasShares,
'rowsCount' => $this->rowsCount ?: 0,
'ownerDisplayName' => $this->ownerDisplayName,
'sidebarOrder' => $this->sidebarOrder,
];
$serialisedJson['filter'] = $this->getFilterArray();

Expand Down
4 changes: 4 additions & 0 deletions lib/Db/ViewMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ public function findAll(?int $tableId = null): array {
if ($tableId !== null) {
$qb->where($qb->expr()->eq('v.table_id', $qb->createNamedParameter($tableId, IQueryBuilder::PARAM_INT)));
}

$qb->addOrderBy('v.sidebar_order', 'ASC');
$qb->addOrderBy('v.id', 'ASC');

return $this->findEntities($qb);
}

Expand Down
39 changes: 39 additions & 0 deletions lib/Migration/Version2210Date20260709000000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

class Version2210Date20260709000000 extends SimpleMigrationStep {
#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

if (!$schema->hasTable('tables_views')) {
return null;
}

$table = $schema->getTable('tables_views');
if (!$table->hasColumn('sidebar_order')) {
$table->addColumn('sidebar_order', Types::BIGINT, [
'notnull' => false,
'default' => null,
]);
}

return $schema;
}
}
8 changes: 7 additions & 1 deletion lib/Model/ViewUpdateInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ public function __construct(
protected readonly ?ColumnSettings $columnSettings = null,
protected readonly ?FilterSet $filterSet = null,
protected readonly ?SortRuleSet $sortRuleSet = null,
protected readonly ?int $sidebarOrder = null,
) {
}

public function updateDetail(): Generator {
if ($this->sidebarOrder !== null) {
yield ViewUpdatableParameters::SIDEBAR_ORDER => $this->sidebarOrder;
}
if ($this->title) {
yield ViewUpdatableParameters::TITLE => $this->title;
}
Expand Down Expand Up @@ -61,7 +65,8 @@ public function updateDetail(): Generator {
* columns?: list<int>,
* columnSettings?: list<array{columnId?: int, order?: int, readonly?: bool, mandatory?: bool}>,
* sort?: list<array{columnId: int, mode: 'ASC'|'DESC'}>,
* 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}>>
* 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}>>,
* sidebarOrder?: int
* } $data
*/
public static function fromInputArray(array $data): self {
Expand All @@ -87,6 +92,7 @@ public static function fromInputArray(array $data): self {
columnSettings: ($data['columnSettings'] ?? null) ? ColumnSettings::createViewSettingsFromInputArray($data['columnSettings']) : null,
filterSet: ($data['filter'] ?? null) ? FilterSet::createFromInputArray($data['filter']) : null,
sortRuleSet: ($data['sort'] ?? null) ? SortRuleSet::createFromInputArray($data['sort']) : null,
sidebarOrder: (array_key_exists('sidebarOrder', $data) && $data['sidebarOrder'] !== null) ? (int)$data['sidebarOrder'] : null,
);
}

Expand Down
1 change: 1 addition & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
* },
* hasShares: bool,
* rowsCount: int,
* sidebarOrder: int|null,
* }
*
* @psalm-type TablesTable = array{
Expand Down
2 changes: 2 additions & 0 deletions lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ public function update(int $id, ViewUpdateInput $data, ?string $userId = null, b
}

foreach ($data->updateDetail() as $parameter => $value) {
$insertableValue = null;

if ($parameter === ViewUpdatableParameters::COLUMN_SETTINGS
&& $value instanceof ColumnSettings
) {
Expand Down
75 changes: 71 additions & 4 deletions src/modules/navigation/partials/NavigationTableItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,14 @@
</NcActionButton>
</template>
<ul>
<NavigationViewItem v-for="view in getViews" :key="'view' + view.id" :view="view"
:show-share-sender="false" />
<NavigationViewItem v-for="(view, index) in orderedViews" :key="'view' + view.id" :view="view"
:show-share-sender="false"
:draggable="canReorderViews"
:class="{ 'view-drop-target': dragOverIndex === index }"
@dragstart.native="onViewDragStart(index)"
@dragover.native.prevent="onViewDragOver(index)"
@drop.native.prevent="onViewDrop"
@dragend.native="onViewDragEnd" />
</ul>
</NcAppNavigationItem>
</template>
Expand Down Expand Up @@ -207,6 +213,9 @@ export default {
data() {
return {
isParentOfActiveView: false,
orderedViews: [],
draggedIndex: null,
dragOverIndex: null,
}
},

Expand All @@ -219,13 +228,28 @@ export default {
return getCurrentUser().uid
},
getViews() {
return this.views.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
return this.views
.filter(v => v.tableId === this.table.id && v.title.toLowerCase().includes(this.filterString.toLowerCase()))
.sort((a, b) => {
const orderA = a.sidebarOrder ?? Number.MAX_SAFE_INTEGER
const orderB = b.sidebarOrder ?? Number.MAX_SAFE_INTEGER
return orderA - orderB || a.id - b.id
})
},
hasViews() {
return this.getViews.length > 0
},
canReorderViews() {
return this.canManageElement(this.table) && !this.filterString && this.orderedViews.length > 1
},
},
watch: {
getViews: {
handler(views) {
this.orderedViews = [...views]
},
immediate: true,
},
activeView() {
if (!this.isParentOfActiveView && this.activeView?.tableId === this.table?.id) {
this.isParentOfActiveView = true
Expand All @@ -238,8 +262,47 @@ export default {
},
},
methods: {
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable']),
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable', 'updateView']),
emit,
onViewDragStart(index) {
if (!this.canReorderViews) {
return
}
this.draggedIndex = index
},
onViewDragOver(index) {
if (this.draggedIndex === null || this.draggedIndex === index) {
return
}
const moved = this.orderedViews.splice(this.draggedIndex, 1)[0]
this.orderedViews.splice(index, 0, moved)
this.draggedIndex = index
this.dragOverIndex = index
},
onViewDrop() {
this.persistViewOrder()
},
onViewDragEnd() {
this.persistViewOrder()
},
async persistViewOrder() {
if (this.draggedIndex === null) {
this.dragOverIndex = null
return
}
this.draggedIndex = null
this.dragOverIndex = null

const updates = []
this.orderedViews.forEach((view, index) => {
if (view.sidebarOrder !== index) {
updates.push(this.updateView({ id: view.id, data: { data: { sidebarOrder: index } } }))
}
})
if (updates.length) {
await Promise.all(updates)
}
},
deleteTable() {
emit('tables:table:delete', this.table)
},
Expand Down Expand Up @@ -343,4 +406,8 @@ export default {
display: inline;
}
}

.view-drop-target {
border-top: 2px solid var(--color-primary-element);
}
</style>
2 changes: 2 additions & 0 deletions src/types/openapi/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,8 @@ export type components = {
readonly hasShares: boolean;
/** Format: int64 */
readonly rowsCount: number;
/** Format: int64 */
readonly sidebarOrder: number | null;
};
};
responses: never;
Expand Down
Loading