Skip to content

Commit

Permalink
Merge pull request #1295 from nextcloud/nav-display-frontend
Browse files Browse the repository at this point in the history
feat: Add UI elements to modify navigation display
  • Loading branch information
blizzz authored Jan 9, 2025
2 parents 84e26e5 + 4d5a063 commit 76b51d8
Show file tree
Hide file tree
Showing 16 changed files with 336 additions and 84 deletions.
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@
['name' => 'search#all', 'url' => '/search/all', 'verb' => 'GET'],
],
'ocs' => [
['name' => 'navigation#getAppsNavigation', 'url' => '/navigation', 'verb' => 'GET'],

// API v2
['name' => 'ApiGeneral#index', 'url' => '/api/2/init', 'verb' => 'GET'],
// -> tables
Expand Down
1 change: 0 additions & 1 deletion cypress/e2e/context.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ describe('Manage a context', () => {
cy.login(nonLocalUser)
cy.visit('apps/tables')
cy.loadContext(contextTitle)
cy.contains('header .app-menu-entry', contextTitle).should('exist')
cy.contains('h1', contextTitle).should('exist')
cy.contains('h1', tableTitle).should('exist')
cy.get('button').contains('Create row').click()
Expand Down
43 changes: 43 additions & 0 deletions lib/Controller/NavigationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

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

namespace OCA\Tables\Controller;

use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\DataResponse;
use OCP\INavigationManager;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;

/**
* This is a workaround until https://github.com/nextcloud/server/pull/49904 is
* settled in all covered NC versions; expected >= 31.
*/
class NavigationController extends \OC\Core\Controller\NavigationController {
public function __construct(
protected ContextService $contextService,
protected IUserSession $userSession,
string $appName,
IRequest $request,
INavigationManager $navigationManager,
IURLGenerator $urlGenerator
) {
parent::__construct($appName, $request, $navigationManager, $urlGenerator);
}

#[NoAdminRequired]
#[NoCSRFRequired]
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
public function getAppsNavigation(bool $absolute = false): DataResponse {
$this->contextService->addToNavigation($this->userSession->getUser()?->getUID());
return parent::getAppsNavigation($absolute);
}
}
3 changes: 3 additions & 0 deletions lib/Db/ContextMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ protected function formatResultRows(array $rows, ?string $userId) {
'display_mode_default' => (int)$item['display_mode_default'],
];
if ($userId !== null) {
if ($item['display_mode'] === null) {
$item['display_mode'] = $item['display_mode_default'];
}
$carry[$item['share_id']]['display_mode'] = (int)$item['display_mode'];
}
return $carry;
Expand Down
60 changes: 60 additions & 0 deletions lib/Db/ContextNavigationMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
namespace OCA\Tables\Db;

use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
Expand Down Expand Up @@ -41,4 +42,63 @@ public function setDisplayModeByShareId(int $shareId, int $displayMode, string $

return $this->insertOrUpdate($entity);
}

// we have to overwrite QBMapper`s insert() because we do not have
// an id column in this table. Sad.
public function insert(Entity $entity): Entity {
// get updated fields to save, fields have to be set using a setter to
// be saved
$properties = $entity->getUpdatedFields();

$qb = $this->db->getQueryBuilder();
$qb->insert($this->tableName);

// build the fields
foreach ($properties as $property => $updated) {
$column = $entity->propertyToColumn($property);
$getter = 'get' . ucfirst($property);
$value = $entity->$getter();

$type = $this->getParameterTypeForProperty($entity, $property);
$qb->setValue($column, $qb->createNamedParameter($value, $type));
}

$qb->executeStatement();

return $entity;
}

// we have to overwrite QBMapper`s update() because we do not have
// an id column in this table. Sad.
public function update(Entity $entity): ContextNavigation {
if (!$entity instanceof ContextNavigation) {
throw new \LogicException('Can only update context navigation entities');
}

// if entity wasn't changed it makes no sense to run a db query
$properties = $entity->getUpdatedFields();
if (\count($properties) === 0) {
return $entity;
}

$qb = $this->db->getQueryBuilder();
$qb->update($this->tableName);

// build the fields
foreach ($properties as $property => $updated) {
$column = $entity->propertyToColumn($property);
$getter = 'get' . ucfirst($property);
$value = $entity->$getter();

$type = $this->getParameterTypeForProperty($entity, $property);
$qb->set($column, $qb->createNamedParameter($value, $type));
}

$qb->where($qb->expr()->eq('share_id', $qb->createNamedParameter($entity->getShareId(), IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($entity->getUserId(), IQueryBuilder::PARAM_STR)));

$qb->executeStatement();

return $entity;
}
}
32 changes: 3 additions & 29 deletions lib/Listener/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,19 @@

namespace OCA\Tables\Listener;

use OCA\Tables\AppInfo\Application;
use OCA\Tables\Service\ContextService;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserSession;

/**
* @template-implements IEventListener<Event|BeforeTemplateRenderedEvent>
*/
class BeforeTemplateRenderedListener implements IEventListener {
public function __construct(
protected INavigationManager $navigationManager,
protected IURLGenerator $urlGenerator,
protected IUserSession $userSession,
protected ContextService $contextService,
protected IUserSession $userSession,
protected ContextService $contextService,
) {
}

Expand All @@ -41,27 +36,6 @@ public function handle(Event $event): void {
return;
}

$contexts = $this->contextService->findForNavigation($user->getUID());
foreach ($contexts as $context) {
$this->navigationManager->add(function () use ($context) {
$iconRelPath = 'material/' . $context->getIcon() . '.svg';
if (file_exists(__DIR__ . '/../../img/' . $iconRelPath)) {
$iconUrl = $this->urlGenerator->imagePath(Application::APP_ID, $iconRelPath);
} else {
$iconUrl = $this->urlGenerator->imagePath('core', 'places/default-app-icon.svg');
}

$contextUrl = $this->urlGenerator->linkToRoute('tables.page.context', ['contextId' => $context->getId()]);

return [
'id' => Application::APP_ID . '_application_' . $context->getId(),
'name' => $context->getName(),
'href' => $contextUrl,
'icon' => $iconUrl,
'order' => 500,
'type' => 'link',
];
});
}
$this->contextService->addToNavigation($user->getUID());
}
}
75 changes: 41 additions & 34 deletions lib/Service/ContextService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,49 +27,31 @@
use OCP\DB\Exception;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\INavigationManager;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Log\Audit\CriticalActionPerformedEvent;
use Psr\Log\LoggerInterface;

class ContextService {

private ContextMapper $contextMapper;
private bool $isCLI;
private LoggerInterface $logger;
private ContextNodeRelationMapper $contextNodeRelMapper;
private PageMapper $pageMapper;
private PageContentMapper $pageContentMapper;
private PermissionsService $permissionsService;
private IUserManager $userManager;
private IEventDispatcher $eventDispatcher;
private IDBConnection $dbc;
private ShareService $shareService;

public function __construct(
ContextMapper $contextMapper,
ContextNodeRelationMapper $contextNodeRelationMapper,
PageMapper $pageMapper,
PageContentMapper $pageContentMapper,
LoggerInterface $logger,
PermissionsService $permissionsService,
IUserManager $userManager,
IEventDispatcher $eventDispatcher,
IDBConnection $dbc,
ShareService $shareService,
bool $isCLI,
private ContextMapper $contextMapper,
private ContextNodeRelationMapper $contextNodeRelMapper,
private PageMapper $pageMapper,
private PageContentMapper $pageContentMapper,
private LoggerInterface $logger,
private PermissionsService $permissionsService,
private IUserManager $userManager,
private IEventDispatcher $eventDispatcher,
private IDBConnection $dbc,
private ShareService $shareService,
private bool $isCLI,
protected INavigationManager $navigationManager,
protected IURLGenerator $urlGenerator,
) {
$this->contextMapper = $contextMapper;
$this->isCLI = $isCLI;
$this->logger = $logger;
$this->contextNodeRelMapper = $contextNodeRelationMapper;
$this->pageMapper = $pageMapper;
$this->pageContentMapper = $pageContentMapper;
$this->permissionsService = $permissionsService;
$this->userManager = $userManager;
$this->eventDispatcher = $eventDispatcher;
$this->dbc = $dbc;
$this->shareService = $shareService;
}

use TTransactional;

/**
Expand All @@ -93,6 +75,31 @@ public function findForNavigation(string $userId): array {
return $this->contextMapper->findForNavBar($userId);
}

public function addToNavigation(string $userId): void {
$contexts = $this->findForNavigation($userId);
foreach ($contexts as $context) {
$this->navigationManager->add(function () use ($context) {
$iconRelPath = 'material/' . $context->getIcon() . '.svg';
if (file_exists(__DIR__ . '/../../img/' . $iconRelPath)) {
$iconUrl = $this->urlGenerator->imagePath(Application::APP_ID, $iconRelPath);
} else {
$iconUrl = $this->urlGenerator->imagePath('core', 'places/default-app-icon.svg');
}

$contextUrl = $this->urlGenerator->linkToRoute('tables.page.context', ['contextId' => $context->getId()]);

return [
'id' => Application::APP_ID . '_application_' . $context->getId(),
'name' => $context->getName(),
'href' => $contextUrl,
'icon' => $iconUrl,
'order' => 500,
'type' => 'link',
];
});
}
}

/**
* @throws Exception
* @throws InternalError
Expand Down
2 changes: 1 addition & 1 deletion lib/Service/ShareService.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public function updateDisplayMode(int $shareId, int $displayMode, string $userId
}
} else {
// setting user display mode override only requires access
if (!$this->permissionsService->canAccessContextById($item->getId())) {
if (!$this->permissionsService->canAccessContextById($item->getNodeId(), $userId)) {
throw new PermissionError(sprintf('PermissionError: can not update share with id %d', $shareId));
}
}
Expand Down
7 changes: 6 additions & 1 deletion openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10074,5 +10074,10 @@
}
}
},
"tags": []
"tags": [
{
"name": "navigation",
"description": "This is a workaround until https://github.com/nextcloud/server/pull/49904 is settled in all covered NC versions; expected >= 31."
}
]
}
Loading

0 comments on commit 76b51d8

Please sign in to comment.