mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[ACA-1078] next/prev navigation for the Viewer (#187)
* basic navigation through the folder * carousel mode for libraries * code polishing * save and restore sorting settings * get user sorting preferences into account * sorting fixes * full test coverage
This commit is contained in:
committed by
Cilibiu Bogdan
parent
34e322e9f6
commit
3cff48cac5
@@ -25,7 +25,7 @@
|
||||
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { AlfrescoApiService } from '@alfresco/adf-core';
|
||||
import { AlfrescoApiService, UserPreferencesService, ObjectUtils } from '@alfresco/adf-core';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
|
||||
@Component({
|
||||
@@ -38,47 +38,281 @@ import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
})
|
||||
export class PreviewComponent implements OnInit {
|
||||
|
||||
private node: MinimalNodeEntryEntity;
|
||||
private previewLocation: string = null;
|
||||
private routesSkipNavigation = [ '/shared', '/recent-files', '/favorites' ];
|
||||
nodeId: string = null;
|
||||
node: MinimalNodeEntryEntity;
|
||||
previewLocation: string = null;
|
||||
routesSkipNavigation = [ 'shared', 'recent-files', 'favorites' ];
|
||||
navigateSource: string = null;
|
||||
navigationSources = ['favorites', 'libraries', 'personal-files', 'recent-files', 'shared'];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: AlfrescoApiService) {
|
||||
this.previewLocation = this.router.url.substr(0, this.router.url.indexOf('/', 1));
|
||||
}
|
||||
folderId: string = null;
|
||||
nodeId: string = null;
|
||||
previousNodeId: string;
|
||||
nextNodeId: string;
|
||||
navigateMultiple = false;
|
||||
|
||||
constructor(private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: AlfrescoApiService,
|
||||
private preferences: UserPreferencesService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.previewLocation = this.router.url
|
||||
.substr(0, this.router.url.indexOf('/', 1))
|
||||
.replace(/\//g, '');
|
||||
|
||||
const routeData = this.route.snapshot.data;
|
||||
|
||||
if (routeData.navigateMultiple) {
|
||||
this.navigateMultiple = true;
|
||||
}
|
||||
|
||||
if (routeData.navigateSource) {
|
||||
const source = routeData.navigateSource.toLowerCase();
|
||||
if (this.navigationSources.includes(source)) {
|
||||
this.navigateSource = routeData.navigateSource;
|
||||
}
|
||||
}
|
||||
|
||||
this.route.params.subscribe(params => {
|
||||
this.folderId = params.folderId;
|
||||
const id = params.nodeId;
|
||||
if (id) {
|
||||
this.apiService.getInstance().nodes.getNodeInfo(id).then(
|
||||
(node) => {
|
||||
this.node = node;
|
||||
|
||||
if (node && node.isFile) {
|
||||
this.nodeId = id;
|
||||
return;
|
||||
}
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
},
|
||||
() => this.router.navigate([this.previewLocation, id])
|
||||
);
|
||||
this.displayNode(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onShowChange(isVisible) {
|
||||
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
|
||||
/**
|
||||
* Loads the particular node into the Viewer
|
||||
* @param id Unique identifier for the Node to display
|
||||
*/
|
||||
async displayNode(id: string) {
|
||||
if (id) {
|
||||
try {
|
||||
this.node = await this.apiService.nodesApi.getNodeInfo(id);
|
||||
if (this.node && this.node.isFile) {
|
||||
const nearest = await this.getNearestNodes(this.node.id, this.node.parentId);
|
||||
|
||||
if (!isVisible) {
|
||||
if ( !shouldSkipNavigation ) {
|
||||
this.router.navigate([this.previewLocation, this.node.parentId ]);
|
||||
} else {
|
||||
this.router.navigate([this.previewLocation]);
|
||||
this.previousNodeId = nearest.left;
|
||||
this.nextNodeId = nearest.right;
|
||||
this.nodeId = this.node.id;
|
||||
return;
|
||||
}
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
} catch {
|
||||
this.router.navigate([this.previewLocation, id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the visibility change of the Viewer component.
|
||||
* @param isVisible Indicator whether Viewer is visible or hidden.
|
||||
*/
|
||||
onVisibilityChanged(isVisible: boolean): void {
|
||||
const shouldSkipNavigation = this.routesSkipNavigation.includes(this.previewLocation);
|
||||
|
||||
if (!isVisible) {
|
||||
const route = [this.previewLocation];
|
||||
|
||||
if ( !shouldSkipNavigation && this.folderId ) {
|
||||
route.push(this.folderId);
|
||||
}
|
||||
|
||||
this.router.navigate(route);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles navigation to a previous document */
|
||||
onNavigateBefore(): void {
|
||||
if (this.previousNodeId) {
|
||||
this.router.navigate(
|
||||
this.getPreviewPath(this.folderId, this.previousNodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles navigation to a next document */
|
||||
onNavigateNext(): void {
|
||||
if (this.nextNodeId) {
|
||||
this.router.navigate(
|
||||
this.getPreviewPath(this.folderId, this.nextNodeId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a node preview route based on folder and node IDs.
|
||||
* @param folderId Folder ID
|
||||
* @param nodeId Node ID
|
||||
*/
|
||||
getPreviewPath(folderId: string, nodeId: string): any[] {
|
||||
const route = [this.previewLocation];
|
||||
|
||||
if (folderId) {
|
||||
route.push(folderId);
|
||||
}
|
||||
|
||||
if (nodeId) {
|
||||
route.push('preview', nodeId);
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves nearest node information for the given node and folder.
|
||||
* @param nodeId Unique identifier of the document node
|
||||
* @param folderId Unique identifier of the containing folder node.
|
||||
*/
|
||||
async getNearestNodes(nodeId: string, folderId: string): Promise<{ left: string, right: string }> {
|
||||
const empty = {
|
||||
left: null,
|
||||
right: null
|
||||
};
|
||||
|
||||
if (nodeId && folderId) {
|
||||
try {
|
||||
const ids = await this.getFileIds(this.navigateSource, folderId);
|
||||
const idx = ids.indexOf(nodeId);
|
||||
|
||||
if (idx >= 0) {
|
||||
return {
|
||||
left: ids[idx - 1] || null,
|
||||
right: ids[idx + 1] || null
|
||||
};
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
} catch {
|
||||
return empty;
|
||||
}
|
||||
} else {
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of node identifiers for the folder and data source.
|
||||
* @param source Data source name. Allowed values are: personal-files, libraries, favorites, shared, recent-files.
|
||||
* @param folderId Containing folder node identifier for 'personal-files' and 'libraries' sources.
|
||||
*/
|
||||
async getFileIds(source: string, folderId?: string): Promise<string[]> {
|
||||
if ((source === 'personal-files' || source === 'libraries') && folderId) {
|
||||
const sortKey = this.preferences.get('personal-files.sorting.key') || 'modifiedAt';
|
||||
const sortDirection = this.preferences.get('personal-files.sorting.direction') || 'desc';
|
||||
const nodes = await this.apiService.nodesApi.getNodeChildren(folderId, {
|
||||
// orderBy: `${sortKey} ${sortDirection}`,
|
||||
fields: ['id', this.getRootField(sortKey)],
|
||||
where: '(isFile=true)'
|
||||
});
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortKey, sortDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
if (source === 'favorites') {
|
||||
const nodes = await this.apiService.favoritesApi.getFavorites('-me-', {
|
||||
where: '(EXISTS(target/file))',
|
||||
fields: ['target']
|
||||
});
|
||||
|
||||
const sortKey = this.preferences.get('favorites.sorting.key') || 'modifiedAt';
|
||||
const sortDirection = this.preferences.get('favorites.sorting.direction') || 'desc';
|
||||
const files = nodes.list.entries.map(obj => obj.entry.target.file);
|
||||
this.sort(files, sortKey, sortDirection);
|
||||
|
||||
return files.map(f => f.id);
|
||||
}
|
||||
|
||||
if (source === 'shared') {
|
||||
const sortingKey = this.preferences.get('shared.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection = this.preferences.get('shared.sorting.direction') || 'desc';
|
||||
|
||||
const nodes = await this.apiService.sharedLinksApi.findSharedLinks({
|
||||
fields: ['nodeId', this.getRootField(sortingKey)]
|
||||
});
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.nodeId);
|
||||
}
|
||||
|
||||
if (source === 'recent-files') {
|
||||
const person = await this.apiService.peopleApi.getPerson('-me-');
|
||||
const username = person.entry.id;
|
||||
const sortingKey = this.preferences.get('recent-files.sorting.key') || 'modifiedAt';
|
||||
const sortingDirection = this.preferences.get('recent-files.sorting.direction') || 'desc';
|
||||
|
||||
const nodes = await this.apiService.searchApi.search({
|
||||
query: {
|
||||
query: '*',
|
||||
language: 'afts'
|
||||
},
|
||||
filterQueries: [
|
||||
{ query: `cm:modified:[NOW/DAY-30DAYS TO NOW/DAY+1DAY]` },
|
||||
{ query: `cm:modifier:${username} OR cm:creator:${username}` },
|
||||
{ query: `TYPE:"content" AND -TYPE:"app:filelink" AND -TYPE:"fm:post"` }
|
||||
],
|
||||
fields: ['id', this.getRootField(sortingKey)],
|
||||
sort: [{
|
||||
type: 'FIELD',
|
||||
field: 'cm:modified',
|
||||
ascending: false
|
||||
}]
|
||||
});
|
||||
|
||||
const entries = nodes.list.entries.map(obj => obj.entry);
|
||||
this.sort(entries, sortingKey, sortingDirection);
|
||||
|
||||
return entries.map(obj => obj.id);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private sort(items: any[], key: string, direction: string) {
|
||||
const options: Intl.CollatorOptions = {};
|
||||
|
||||
if (key.includes('sizeInBytes') || key === 'name') {
|
||||
options.numeric = true;
|
||||
}
|
||||
|
||||
items.sort((a: any, b: any) => {
|
||||
let left = ObjectUtils.getValue(a, key);
|
||||
if (left) {
|
||||
left = (left instanceof Date) ? left.valueOf().toString() : left.toString();
|
||||
} else {
|
||||
left = '';
|
||||
}
|
||||
|
||||
let right = ObjectUtils.getValue(b, key);
|
||||
if (right) {
|
||||
right = (right instanceof Date) ? right.valueOf().toString() : right.toString();
|
||||
} else {
|
||||
right = '';
|
||||
}
|
||||
|
||||
return direction === 'asc'
|
||||
? left.localeCompare(right, undefined, options)
|
||||
: right.localeCompare(left, undefined, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root field name from the property path.
|
||||
* Example: 'property1.some.child.property' => 'property1'
|
||||
* @param path Property path
|
||||
*/
|
||||
getRootField(path: string) {
|
||||
if (path) {
|
||||
return path.split('.')[0];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user