Skip to content
1 change: 1 addition & 0 deletions lib/Constants/ViewUpdatableParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum ViewUpdatableParameters: string {
case TECHNICAL_NAME = 'technicalName';
case SIDEBAR_ORDER = 'sidebarOrder';
case TYPE = 'type';
case GRID = 'grid';
case LAYOUT = 'layout';
case VIEW_SETTINGS = 'viewSettings';
}
19 changes: 19 additions & 0 deletions lib/Db/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
* @method setTableId(?int $tableId)
* @method getType(): string
* @method setType(string $type)
* @method getGrid(): ?string
* @method setGrid(?string $grid)
* @method getColumns(): string
* @method setColumns(string $columns)
* @method getColumnSettings(): string
Expand Down Expand Up @@ -101,6 +103,7 @@ class View extends EntitySuper implements JsonSerializable {
protected ?string $sort = null; // json
protected ?string $filter = null; // json
protected ?string $type = null;
protected ?string $grid = null; // json
protected ?string $layout = null;
protected ?string $viewSettings = null; // json

Expand Down Expand Up @@ -231,6 +234,21 @@ public function setFilterArray(array $array):void {
$this->setFilter(\json_encode($array));
}

/**
* The widgets of a grid view and where they sit, as GridWidgetTypes stored them.
*
* @return array{widgets: list<array<array-key, mixed>>, layout: list<array<array-key, mixed>>}
*/
public function getGridArray(): array {
$grid = $this->getArray($this->getGrid());
$list = static fn (mixed $items): array => array_values(array_filter(is_array($items) ? $items : [], is_array(...)));
return ['widgets' => $list($grid['widgets'] ?? null), 'layout' => $list($grid['layout'] ?? null)];
}

public function setGridArray(array $array): void {
$this->setGrid(\json_encode($array, JSON_THROW_ON_ERROR));
}

/**
* @return 'table'|'grid'
*/
Expand Down Expand Up @@ -297,6 +315,7 @@ public function jsonSerialize(): array {
'isFederated' => $this->isFederated(),
'sidebarOrder' => $this->sidebarOrder,
'type' => $this->getTypeOrDefault(),
'grid' => $this->getGridArray(),
'layout' => $this->getLayoutNormalized(),
'viewSettings' => $this->getViewSettingsObject()->jsonSerialize(),
];
Expand Down
40 changes: 40 additions & 0 deletions lib/Migration/Version2400Date20261012010000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?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;

/**
* Grid views store their widgets and layout as JSON.
*/
class Version2400Date20261012010000 extends SimpleMigrationStep {
#[Override]
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('grid')) {
$table->addColumn('grid', Types::TEXT, [
'notnull' => false,
'default' => null,
]);
}
}

return $schema;
}
}
23 changes: 21 additions & 2 deletions lib/Model/ViewUpdateInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function __construct(
protected readonly ?SortRuleSet $sortRuleSet = null,
protected readonly ?int $sidebarOrder = null,
protected readonly ?string $type = null,
protected readonly ?array $grid = null,
protected readonly ?string $layout = null,
protected readonly ?ViewSettings $viewSettings = null,
) {
Expand Down Expand Up @@ -68,6 +69,9 @@ public function updateDetail(): Generator {
if ($this->type !== null) {
yield ViewUpdatableParameters::TYPE => $this->type;
}
if ($this->grid !== null) {
yield ViewUpdatableParameters::GRID => $this->grid;
}
if ($this->layout !== null) {
yield ViewUpdatableParameters::LAYOUT => $this->layout;
}
Expand All @@ -89,12 +93,13 @@ public function updateDetail(): Generator {
* viewSettings?: array{cardBackgroundSource?: int|null, cardTitleSource?: int|null}|string,
* filter?: list<list<array{columnId: int, operator: 'begins-with'|'ends-with'|'contains'|'does-not-contain'|'contains-item'|'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|list<array{id: int, label: string, uuid?: string}>}>>,
* sidebarOrder?: int,
* type?: 'table'|'grid'
* type?: 'table'|'grid',
* grid?: array{widgets?: list<array<string, mixed>>, layout?: list<array<string, mixed>>}
* } $data
* @param array $columnsMap
*/
public static function fromInputArray(array $data, array $columnsMap = []): self {
$data = self::transformJsonToArrayInPayload($data, ['columnSettings', 'filter', 'sort', 'viewSettings']);
$data = self::transformJsonToArrayInPayload($data, ['columnSettings', 'filter', 'sort', 'grid', 'viewSettings']);

if (isset($data['columns']) && !isset($data['columnSettings'])) {
$logger = Server::get(LoggerInterface::class);
Expand Down Expand Up @@ -122,6 +127,7 @@ public static function fromInputArray(array $data, array $columnsMap = []): self
sortRuleSet: isset($data['sort']) ? SortRuleSet::createFromInputArray($data['sort'], $columnsMap) : null,
sidebarOrder: (array_key_exists('sidebarOrder', $data) && $data['sidebarOrder'] !== null) ? (int)$data['sidebarOrder'] : null,
type: self::optionalType($data),
grid: self::optionalArray($data, 'grid'),
layout: $layout,
viewSettings: $viewSettings,
);
Expand Down Expand Up @@ -151,6 +157,19 @@ private static function optionalType(array $data): ?string {
return $type;
}

/**
* @throws InvalidArgumentException
*/
private static function optionalArray(array $data, string $key): ?array {
if (!isset($data[$key])) {
return null;
}
if (!is_array($data[$key])) {
throw new InvalidArgumentException('The view property "' . $key . '" must be an object.');
}
return $data[$key];
}

private static function createViewSettingsFromInputData(array $data, array $columnsMap = []): ?ViewSettings {
if (array_key_exists('viewSettings', $data)) {
if ($data['viewSettings'] === null) {
Expand Down
20 changes: 20 additions & 0 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@
namespace OCA\Tables;

/**
* @psalm-type TablesGridWidgetValue = string|bool|int|array{type: 'table'|'view', id: int}|null
*
* @psalm-type TablesGridWidget = array{
* id: string,
* type: string,
* configuration: array<string, TablesGridWidgetValue>,
* content: array<string, TablesGridWidgetValue>,
* broken?: bool,
* }
*
* @psalm-type TablesGridLayoutItem = array{
* id: string,
* widgetId: string,
* gridX: int,
* gridY: int,
* gridWidth: int,
* gridHeight: int,
* }
*
* @psalm-type TablesView = array{
* id: int,
* uuid: string,
Expand Down Expand Up @@ -48,6 +67,7 @@
* isFederated: bool,
* sidebarOrder: int|null,
* type: 'table'|'grid',
* grid: array{widgets: list<TablesGridWidget>, layout: list<TablesGridLayoutItem>},
* }
*
* @psalm-type TablesTable = array{
Expand Down
Loading
Loading