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
5 changes: 4 additions & 1 deletion lib/Db/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
* @method setCreatedAt(string $createdAt)
* @method getFilter(): string
* @method setFilter(string $filter)
* @method getLastEditBy(): string
* @method getViewGroup(): ?string
* @method setViewGroup(?string $viewGroup)
* @method setLastEditBy(string $lastEditBy)
* @method getLastEditAt(): string
* @method setLastEditAt(string $lastEditAt)
Expand Down Expand Up @@ -72,6 +73,7 @@ class View extends EntitySuper implements JsonSerializable {
protected ?string $columns = null; // json
protected ?string $sort = null; // json
protected ?string $filter = null; // json
protected ?string $viewGroup = null;

// virtual properties
protected ?bool $isShared = null;
Expand Down Expand Up @@ -207,6 +209,7 @@ public function jsonSerialize(): array {
'ownerDisplayName' => $this->ownerDisplayName,
];
$serialisedJson['filter'] = $this->getFilterArray();
$serialisedJson['viewGroup'] = $this->viewGroup;

return $serialisedJson;
}
Expand Down
40 changes: 40 additions & 0 deletions lib/Migration/Version001000Date20250122000000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/** @noinspection PhpUnused */
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 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;

class Version001000Date20250122000000 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

if ($schema->hasTable('tables_views')) {
$table = $schema->getTable('tables_views');
if (!$table->hasColumn('view_group')) {
$table->addColumn('view_group', Types::STRING, [
'notnull' => false,
'default' => null,
'length' => 255,
]);
}
}

return $schema;
}
}
2 changes: 1 addition & 1 deletion lib/Service/ViewService.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ public function update(int $id, array $data, ?string $userId = null, bool $skipT
throw new PermissionError('PermissionError: can not update view with id ' . $id);
}

$updatableParameter = ['title', 'emoji', 'description', 'sort', 'filter', 'columns', 'columnSettings'];
$updatableParameter = ['title', 'emoji', 'description', 'sort', 'filter', 'columns', 'columnSettings', 'viewGroup'];

foreach ($data as $key => $value) {
if (!in_array($key, $updatableParameter)) {
Expand Down
14 changes: 14 additions & 0 deletions src/modules/modals/ViewSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
<TableDescription :description.sync="description" />
</div>
</div>
<div class="row">
<div class="col-4 space-T">
{{ t('tables', 'Group') }}
</div>
<div class="col-4">
<input v-model="viewGroup"
type="text"
:placeholder="t('tables', 'Group name (optional)')">
</div>
</div>
</NcAppSettingsSection>
<!--columns & order-->
<NcAppSettingsSection v-if="columns != null" id="columns-and-order" :name="t('tables', 'Columns')">
Expand Down Expand Up @@ -129,6 +139,7 @@ export default {
open: false,
title: '',
description: '',
viewGroup: '',
icon: '',
errorTitle: false,
selectedColumns: [],
Expand Down Expand Up @@ -294,6 +305,7 @@ export default {
title: this.title,
description: this.description,
emoji: this.icon,
viewGroup: this.viewGroup,
}
const res = await this.insertNewView({ data })
if (res) {
Expand All @@ -316,6 +328,7 @@ export default {
title: this.title,
description: this.description,
emoji: this.icon,
viewGroup: this.viewGroup,
columnSettings: JSON.stringify(newColumnSettings),
},
}
Expand Down Expand Up @@ -344,6 +357,7 @@ export default {
this.title = this.mutableView.title ?? ''
this.description = this.mutableView.description ?? ''
this.icon = this.mutableView.emoji ?? this.loadEmoji()
this.viewGroup = this.mutableView.viewGroup ?? ''
this.errorTitle = false
this.selectedColumns = this.mutableView.columnSettings ? this.mutableView.columnSettings.map(item => item.columnId) : null
this.allColumns = []
Expand Down
76 changes: 71 additions & 5 deletions src/modules/navigation/partials/NavigationTableItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,23 @@
</template>
</NcActionButton>
</template>
<ul>
<NavigationViewItem v-for="view in getViews" :key="'view' + view.id" :view="view"
:show-share-sender="false" />
</ul>
<ul>
<!-- Ungrouped views -->
<NavigationViewItem v-for="view in groupedViews.ungrouped" :key="'view' + view.id" :view="view"
:show-share-sender="false" />
<!-- Grouped views -->
<li v-for="(views, groupName) in groupedViews.groups" :key="'group-' + groupName" class="view-group">
<div class="view-group-header" @click="toggleGroup(groupName)">
<span class="view-group-icon">{{ collapsedGroups[groupName] ? "▶" : "▼" }}</span>
<span class="view-group-name">{{ groupName }}</span>
<span class="view-group-count">({{ views.length }})</span>
</div>
<ul v-show="!collapsedGroups[groupName]" class="view-group-items">
<NavigationViewItem v-for="view in views" :key="'view' + view.id" :view="view"
:show-share-sender="false" />
</ul>
</li>
</ul>
</NcAppNavigationItem>
</template>

Expand Down Expand Up @@ -205,6 +218,7 @@ export default {
data() {
return {
isParentOfActiveView: false,
collapsedGroups: {},
}
},

Expand All @@ -222,7 +236,25 @@ export default {
hasViews() {
return this.getViews.length > 0
},
},
groupedViews() {
const groups = {}
const ungrouped = []
this.getViews.forEach(view => {
if (view.viewGroup) {
if (!groups[view.viewGroup]) {
groups[view.viewGroup] = []
}
groups[view.viewGroup].push(view)
} else {
ungrouped.push(view)
}
})
return { groups, ungrouped }
},
hasGroups() {
return Object.keys(this.groupedViews.groups).length > 0
},
},
watch: {
activeView() {
if (!this.isParentOfActiveView && this.activeView?.tableId === this.table?.id) {
Expand All @@ -238,6 +270,9 @@ export default {
methods: {
...mapActions(useTablesStore, ['favoriteTable', 'removeFavoriteTable', 'updateTable']),
emit,
toggleGroup(groupName) {
this.$set(this.collapsedGroups, groupName, !this.collapsedGroups[groupName])
},
deleteTable() {
emit('tables:table:delete', this.table)
},
Expand Down Expand Up @@ -341,4 +376,35 @@ export default {
display: inline;
}
}
.view-group {
list-style: none;
padding: 0;
margin: 0;
}
.view-group-header {
display: flex;
align-items: center;
padding: 8px 8px 8px 32px;
cursor: pointer;
font-weight: 500;
color: var(--color-text-maxcontrast);
&:hover {
background-color: var(--color-background-hover);
}
}
.view-group-icon {
width: 16px;
margin-right: 8px;
font-size: 10px;
}
.view-group-name {
flex: 1;
}
.view-group-count {
font-size: 12px;
color: var(--color-text-lighter);
}
.view-group-items {
padding-left: 16px;
}
</style>