Skip to content
Merged
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
27 changes: 1 addition & 26 deletions apps/files/lib/Controller/ViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
*/
namespace OCA\Files\Controller;

use OCA\Files\Activity\Helper;
use OCA\Files\AppInfo\Application;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files\Event\LoadSearchPlugins;
Expand All @@ -59,59 +58,48 @@
use OCP\Files\NotFoundException;
use OCP\Files\Template\ITemplateManager;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Share\IManager;

/**
* @package OCA\Files\Controller
*/
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
class ViewController extends Controller {
private IURLGenerator $urlGenerator;
private IL10N $l10n;
private IConfig $config;
private IEventDispatcher $eventDispatcher;
private IUserSession $userSession;
private IAppManager $appManager;
private IRootFolder $rootFolder;
private Helper $activityHelper;
private IInitialState $initialState;
private ITemplateManager $templateManager;
private IManager $shareManager;
private UserConfig $userConfig;
private ViewConfig $viewConfig;

public function __construct(string $appName,
IRequest $request,
IURLGenerator $urlGenerator,
IL10N $l10n,
IConfig $config,
IEventDispatcher $eventDispatcher,
IUserSession $userSession,
IAppManager $appManager,
IRootFolder $rootFolder,
Helper $activityHelper,
IInitialState $initialState,
ITemplateManager $templateManager,
IManager $shareManager,
UserConfig $userConfig,
ViewConfig $viewConfig
ViewConfig $viewConfig,
) {
parent::__construct($appName, $request);
$this->urlGenerator = $urlGenerator;
$this->l10n = $l10n;
$this->config = $config;
$this->eventDispatcher = $eventDispatcher;
$this->userSession = $userSession;
$this->appManager = $appManager;
$this->rootFolder = $rootFolder;
$this->activityHelper = $activityHelper;
$this->initialState = $initialState;
$this->templateManager = $templateManager;
$this->shareManager = $shareManager;
$this->userConfig = $userConfig;
$this->viewConfig = $viewConfig;
}
Expand Down Expand Up @@ -201,18 +189,6 @@ public function index($dir = '', $view = '', $fileid = null) {

$userId = $this->userSession->getUser()->getUID();

// Get all the user favorites to create a submenu
try {
$userFolder = $this->rootFolder->getUserFolder($userId);
$favElements = $this->activityHelper->getFavoriteNodes($userId, true);
$favElements = array_map(fn (Folder $node) => [
'fileid' => $node->getId(),
'path' => $userFolder->getRelativePath($node->getPath()),
], $favElements);
} catch (\RuntimeException $e) {
$favElements = [];
}

// If the file doesn't exists in the folder and
// exists in only one occurrence, redirect to that file
// in the correct folder
Expand Down Expand Up @@ -240,7 +216,6 @@ public function index($dir = '', $view = '', $fileid = null) {
$this->initialState->provideInitialState('storageStats', $storageInfo);
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
$this->initialState->provideInitialState('viewConfigs', $this->viewConfig->getConfigs());
$this->initialState->provideInitialState('favoriteFolders', $favElements);

// File sorting user config
$filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);
Expand Down
2 changes: 1 addition & 1 deletion apps/files/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { entry as newFolderEntry } from './newMenu/newFolder.ts'
import { entry as newTemplatesFolder } from './newMenu/newTemplatesFolder.ts'
import { registerTemplateEntries } from './newMenu/newFromTemplate.ts'

import registerFavoritesView from './views/favorites'
import { registerFavoritesView } from './views/favorites.ts'
import registerRecentView from './views/recent'
import registerPersonalFilesView from './views/personal-files'
import registerFilesView from './views/files'
Expand Down
96 changes: 69 additions & 27 deletions apps/files/src/views/favorites.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,42 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import { basename } from 'path'

import type { Folder as CFolder, Navigation } from '@nextcloud/files'

import { expect } from '@jest/globals'
import { Folder, Navigation, getNavigation } from '@nextcloud/files'
import * as filesUtils from '@nextcloud/files'
import { CancelablePromise } from 'cancelable-promise'
import eventBus from '@nextcloud/event-bus'
import * as eventBus from '@nextcloud/event-bus'
import * as initialState from '@nextcloud/initial-state'
import { basename } from 'path'

import { action } from '../actions/favoriteAction'
import * as favoritesService from '../services/Favorites'
import registerFavoritesView from './favorites'
import { registerFavoritesView } from './favorites'

const { Folder, getNavigation } = filesUtils

jest.mock('@nextcloud/axios', () => ({
post: jest.fn(),
}))

jest.mock('webdav/dist/node/request.js', () => ({
request: jest.fn(),
}))

global.window.OC = {
jest.mock('@nextcloud/files', () => ({
__esModule: true,
...jest.requireActual('@nextcloud/files'),
}))

jest.mock('@nextcloud/event-bus', () => ({
__esModule: true,
...jest.requireActual('@nextcloud/event-bus'),
}))

window.OC = {
...window.OC,
TAG_FAVORITE: '_$!<Favorite>!$_',
}

Expand All @@ -56,11 +76,12 @@ describe('Favorites view definition', () => {
delete window._nc_navigation
})

test('Default empty favorite view', () => {
test('Default empty favorite view', async () => {
jest.spyOn(eventBus, 'subscribe')
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))

registerFavoritesView()
await registerFavoritesView()
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')

Expand All @@ -83,16 +104,31 @@ describe('Favorites view definition', () => {
expect(favoritesView?.getContents).toBeDefined()
})

test('Default with favorites', () => {
test('Default with favorites', async () => {
const favoriteFolders = [
{ fileid: 1, path: '/foo' },
{ fileid: 2, path: '/bar' },
{ fileid: 3, path: '/foo/bar' },
new Folder({
id: 1,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo',
owner: 'admin',
}),
new Folder({
id: 2,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/bar',
owner: 'admin',
}),
new Folder({
id: 3,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/foo/bar',
owner: 'admin',
}),
]
jest.spyOn(initialState, 'loadState').mockReturnValue(favoriteFolders)
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders))
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))

registerFavoritesView()
await registerFavoritesView()
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')

Expand All @@ -110,7 +146,7 @@ describe('Favorites view definition', () => {
expect(favoriteView?.order).toBe(index)
expect(favoriteView?.params).toStrictEqual({
dir: folder.path,
fileid: folder.fileid.toString(),
fileid: String(folder.fileid),
view: 'favorites',
})
expect(favoriteView?.parent).toBe('favorites')
Expand All @@ -132,10 +168,10 @@ describe('Dynamic update of favourite folders', () => {

test('Add a favorite folder creates a new entry in the navigation', async () => {
jest.spyOn(eventBus, 'emit')
jest.spyOn(initialState, 'loadState').mockReturnValue([])
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))

registerFavoritesView()
await registerFavoritesView()
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')

Expand All @@ -161,10 +197,17 @@ describe('Dynamic update of favourite folders', () => {
test('Remove a favorite folder remove the entry from the navigation column', async () => {
jest.spyOn(eventBus, 'emit')
jest.spyOn(eventBus, 'subscribe')
jest.spyOn(initialState, 'loadState').mockReturnValue([{ fileid: 42, path: '/Foo/Bar' }])
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))

registerFavoritesView()
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([
new Folder({
id: 42,
root: '/files/admin',
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
owner: 'admin',
}),
]))
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))

await registerFavoritesView()
let favoritesView = Navigation.views.find(view => view.id === 'favorites')
let favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')

Expand Down Expand Up @@ -201,11 +244,10 @@ describe('Dynamic update of favourite folders', () => {

test('Renaming a favorite folder updates the navigation', async () => {
jest.spyOn(eventBus, 'emit')
jest.spyOn(initialState, 'loadState').mockReturnValue([])
jest.spyOn(favoritesService, 'getContents')
.mockReturnValue(CancelablePromise.resolve({ folder: {} as Folder, contents: [] }))
jest.spyOn(filesUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
jest.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))

registerFavoritesView()
await registerFavoritesView()
const favoritesView = Navigation.views.find(view => view.id === 'favorites')
const favoriteFoldersViews = Navigation.views.filter(view => view.parent === 'favorites')

Expand Down
35 changes: 13 additions & 22 deletions apps/files/src/views/favorites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,27 @@
import type { Folder, Node } from '@nextcloud/files'

import { subscribe } from '@nextcloud/event-bus'
import { FileType, View, getNavigation } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { FileType, View, getFavoriteNodes, getNavigation } from '@nextcloud/files'
import { getLanguage, translate as t } from '@nextcloud/l10n'
import { basename } from 'path'
import { client } from '../services/WebdavClient.ts'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'
import StarSvg from '@mdi/svg/svg/star.svg?raw'

import { getContents } from '../services/Favorites'
import { hashCode } from '../utils/hashUtils'
import logger from '../logger'

// The return type of the initial state
interface IFavoriteFolder {
fileid: number
path: string
}

export const generateFavoriteFolderView = function(folder: IFavoriteFolder, index = 0): View {
const generateFavoriteFolderView = function(folder: Folder, index = 0): View {
return new View({
id: generateIdFromPath(folder.path),
name: basename(folder.path),
name: folder.displayname,

icon: FolderSvg,
order: index,

params: {
dir: folder.path,
fileid: folder.fileid.toString(),
fileid: String(folder.fileid),
view: 'favorites',
},

Expand All @@ -60,16 +54,11 @@ export const generateFavoriteFolderView = function(folder: IFavoriteFolder, inde
})
}

export const generateIdFromPath = function(path: string): string {
const generateIdFromPath = function(path: string): string {
return `favorite-${hashCode(path)}`
}

export default () => {
// Load state in function for mock testing purposes
const favoriteFolders = loadState<IFavoriteFolder[]>('files', 'favoriteFolders', [])
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
logger.debug('Generating favorites view', { favoriteFolders })

export const registerFavoritesView = async () => {
const Navigation = getNavigation()
Navigation.register(new View({
id: 'favorites',
Expand All @@ -87,6 +76,9 @@ export default () => {
getContents,
}))

const favoriteFolders = (await getFavoriteNodes(client)).filter(node => node.type === FileType.Folder) as Folder[]
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
logger.debug('Generating favorites view', { favoriteFolders })
favoriteFoldersViews.forEach(view => Navigation.register(view))

/**
Expand Down Expand Up @@ -154,16 +146,15 @@ export default () => {

// Add a folder to the favorites paths array and update the views
const addToFavorites = function(node: Folder) {
const newFavoriteFolder: IFavoriteFolder = { path: node.path, fileid: node.fileid! }
const view = generateFavoriteFolderView(newFavoriteFolder)
const view = generateFavoriteFolderView(node)

// Skip if already exists
if (favoriteFolders.find((folder) => folder.path === node.path)) {
return
}

// Update arrays
favoriteFolders.push(newFavoriteFolder)
favoriteFolders.push(node)
favoriteFoldersViews.push(view)

// Update and sort views
Expand Down
Loading
Loading