mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Nested Menus (#1554)
* added initial submenu * added submenu - needs a bit of more work * removed admin and nonadmin action split * the whole menu is build under the resetactions function * removed download from seriesAction * changed submenu layout changed submenu toggle icon fix for the hovering of submenu toggle * moved the cdMarkForCheck in the subscribe block
This commit is contained in:
parent
dec6802f88
commit
3cdf8df1db
6
UI/Web/package-lock.json
generated
6
UI/Web/package-lock.json
generated
@ -2637,9 +2637,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/cdk": {
|
"@angular/cdk": {
|
||||||
"version": "13.2.2",
|
"version": "13.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.9.tgz",
|
||||||
"integrity": "sha512-cT5DIaz+NI9IGb3X61Wh26+L6zdRcOXT1BP37iRbK2Qa2qM8/0VNeK6hrBBIblyoHKR/WUmRlS8XYf6mmArpZw==",
|
"integrity": "sha512-XCuCbeuxWFyo3EYrgEYx7eHzwl76vaWcxtWXl00ka8d+WAOtMQ6Tf1D98ybYT5uwF9889fFpXAPw98mVnlo3MA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"parse5": "^5.0.0",
|
"parse5": "^5.0.0",
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
|
@ -8,6 +8,8 @@ import { Volume } from '../_models/volume';
|
|||||||
import { AccountService } from './account.service';
|
import { AccountService } from './account.service';
|
||||||
|
|
||||||
export enum Action {
|
export enum Action {
|
||||||
|
AddTo = -2,
|
||||||
|
Others = -1,
|
||||||
/**
|
/**
|
||||||
* Mark entity as read
|
* Mark entity as read
|
||||||
*/
|
*/
|
||||||
@ -83,13 +85,13 @@ export interface ActionItem<T> {
|
|||||||
action: Action;
|
action: Action;
|
||||||
callback: (action: Action, data: T) => void;
|
callback: (action: Action, data: T) => void;
|
||||||
requiresAdmin: boolean;
|
requiresAdmin: boolean;
|
||||||
|
children: Array<ActionItem<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ActionFactoryService {
|
export class ActionFactoryService {
|
||||||
|
|
||||||
libraryActions: Array<ActionItem<Library>> = [];
|
libraryActions: Array<ActionItem<Library>> = [];
|
||||||
|
|
||||||
seriesActions: Array<ActionItem<Series>> = [];
|
seriesActions: Array<ActionItem<Series>> = [];
|
||||||
@ -108,7 +110,7 @@ export class ActionFactoryService {
|
|||||||
hasDownloadRole = false;
|
hasDownloadRole = false;
|
||||||
|
|
||||||
constructor(private accountService: AccountService) {
|
constructor(private accountService: AccountService) {
|
||||||
this.accountService.currentUser$.subscribe(user => {
|
this.accountService.currentUser$.subscribe((user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.isAdmin = this.accountService.hasAdminRole(user);
|
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||||
this.hasDownloadRole = this.accountService.hasDownloadRole(user);
|
this.hasDownloadRole = this.accountService.hasDownloadRole(user);
|
||||||
@ -118,243 +120,282 @@ export class ActionFactoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._resetActions();
|
this._resetActions();
|
||||||
|
|
||||||
if (this.isAdmin) {
|
|
||||||
this.collectionTagActions.push({
|
|
||||||
action: Action.Edit,
|
|
||||||
title: 'Edit',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.Scan,
|
|
||||||
title: 'Scan Series',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.RefreshMetadata,
|
|
||||||
title: 'Refresh Covers',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.AnalyzeFiles,
|
|
||||||
title: 'Analyze Files',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.Delete,
|
|
||||||
title: 'Delete',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.AddToCollection,
|
|
||||||
title: 'Add to Collection',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.seriesActions.push({
|
|
||||||
action: Action.Edit,
|
|
||||||
title: 'Edit',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.libraryActions.push({
|
|
||||||
action: Action.Scan,
|
|
||||||
title: 'Scan Library',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.libraryActions.push({
|
|
||||||
action: Action.RefreshMetadata,
|
|
||||||
title: 'Refresh Covers',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.libraryActions.push({
|
|
||||||
action: Action.AnalyzeFiles,
|
|
||||||
title: 'Analyze Files',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: true
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chapterActions.push({
|
|
||||||
action: Action.Edit,
|
|
||||||
title: 'Details',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hasDownloadRole || this.isAdmin) {
|
|
||||||
this.volumeActions.push({
|
|
||||||
action: Action.Download,
|
|
||||||
title: 'Download',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chapterActions.push({
|
|
||||||
action: Action.Download,
|
|
||||||
title: 'Download',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibraryActions(callback: (action: Action, library: Library) => void) {
|
getLibraryActions(callback: (action: Action, library: Library) => void) {
|
||||||
const actions = this.libraryActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.libraryActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeriesActions(callback: (action: Action, series: Series) => void) {
|
getSeriesActions(callback: (action: Action, series: Series) => void) {
|
||||||
const actions = this.seriesActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.seriesActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getVolumeActions(callback: (action: Action, volume: Volume) => void) {
|
getVolumeActions(callback: (action: Action, volume: Volume) => void) {
|
||||||
const actions = this.volumeActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.volumeActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getChapterActions(callback: (action: Action, chapter: Chapter) => void) {
|
getChapterActions(callback: (action: Action, chapter: Chapter) => void) {
|
||||||
const actions = this.chapterActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.chapterActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCollectionTagActions(callback: (action: Action, collectionTag: CollectionTag) => void) {
|
getCollectionTagActions(callback: (action: Action, collectionTag: CollectionTag) => void) {
|
||||||
const actions = this.collectionTagActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.collectionTagActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadingListActions(callback: (action: Action, readingList: ReadingList) => void) {
|
getReadingListActions(callback: (action: Action, readingList: ReadingList) => void) {
|
||||||
const actions = this.readingListActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.readingListActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBookmarkActions(callback: (action: Action, series: Series) => void) {
|
getBookmarkActions(callback: (action: Action, series: Series) => void) {
|
||||||
const actions = this.bookmarkActions.map(a => {return {...a}});
|
return this.applyCallbackToList(this.bookmarkActions, callback);
|
||||||
actions.forEach(action => action.callback = callback);
|
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dummyCallback(action: Action, data: any) {}
|
dummyCallback(action: Action, data: any) {}
|
||||||
|
|
||||||
_resetActions() {
|
_resetActions() {
|
||||||
this.libraryActions = [];
|
this.libraryActions = [
|
||||||
|
{
|
||||||
|
action: Action.Scan,
|
||||||
|
title: 'Scan Library',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.Others,
|
||||||
|
title: 'Others',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.RefreshMetadata,
|
||||||
|
title: 'Refresh Covers',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.AnalyzeFiles,
|
||||||
|
title: 'Analyze Files',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.collectionTagActions = [
|
||||||
|
{
|
||||||
|
action: Action.Edit,
|
||||||
|
title: 'Edit',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
this.collectionTagActions = [];
|
|
||||||
|
|
||||||
this.seriesActions = [
|
this.seriesActions = [
|
||||||
{
|
{
|
||||||
action: Action.MarkAsRead,
|
action: Action.MarkAsRead,
|
||||||
title: 'Mark as Read',
|
title: 'Mark as Read',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.MarkAsUnread,
|
action: Action.MarkAsUnread,
|
||||||
title: 'Mark as Unread',
|
title: 'Mark as Unread',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
},
|
children: [],
|
||||||
{
|
|
||||||
action: Action.AddToReadingList,
|
|
||||||
title: 'Add to Reading List',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.AddToWantToReadList,
|
action: Action.AddTo,
|
||||||
title: 'Add to Want To Read',
|
title: 'Add to',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.AddToWantToReadList,
|
||||||
|
title: 'Add to Want To Read',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.RemoveFromWantToReadList,
|
||||||
|
title: 'Remove from Want To Read',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.AddToReadingList,
|
||||||
|
title: 'Add to Reading List',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.AddToCollection,
|
||||||
|
title: 'Add to Collection',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.RemoveFromWantToReadList,
|
action: Action.Scan,
|
||||||
title: 'Remove from Want To Read',
|
title: 'Scan Series',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
}
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.Edit,
|
||||||
|
title: 'Edit',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.Others,
|
||||||
|
title: 'Others',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.RefreshMetadata,
|
||||||
|
title: 'Refresh Covers',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.AnalyzeFiles,
|
||||||
|
title: 'Analyze Files',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.Delete,
|
||||||
|
title: 'Delete',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: true,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.volumeActions = [
|
this.volumeActions = [
|
||||||
|
{
|
||||||
|
action: Action.IncognitoRead,
|
||||||
|
title: 'Read Incognito',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.MarkAsRead,
|
action: Action.MarkAsRead,
|
||||||
title: 'Mark as Read',
|
title: 'Mark as Read',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.MarkAsUnread,
|
action: Action.MarkAsUnread,
|
||||||
title: 'Mark as Unread',
|
title: 'Mark as Unread',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
},
|
children: [],
|
||||||
{
|
|
||||||
action: Action.AddToReadingList,
|
|
||||||
title: 'Add to Reading List',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: Action.IncognitoRead,
|
|
||||||
title: 'Read Incognito',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
requiresAdmin: false
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: Action.AddTo,
|
||||||
|
title: 'Add to',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.AddToReadingList,
|
||||||
|
title: 'Add to Reading List',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.Edit,
|
action: Action.Edit,
|
||||||
title: 'Details',
|
title: 'Details',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
}
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.Download,
|
||||||
|
title: 'Download',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.chapterActions = [
|
this.chapterActions = [
|
||||||
|
{
|
||||||
|
action: Action.IncognitoRead,
|
||||||
|
title: 'Read Incognito',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.MarkAsRead,
|
action: Action.MarkAsRead,
|
||||||
title: 'Mark as Read',
|
title: 'Mark as Read',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.MarkAsUnread,
|
action: Action.MarkAsUnread,
|
||||||
title: 'Mark as Unread',
|
title: 'Mark as Unread',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: Action.AddTo,
|
||||||
|
title: 'Add to',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.AddToReadingList,
|
||||||
|
title: 'Add to Reading List',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.IncognitoRead,
|
action: Action.Edit,
|
||||||
title: 'Read Incognito',
|
title: 'Details',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
|
// RBS will handle rendering this, so non-admins with download are appicable
|
||||||
{
|
{
|
||||||
action: Action.AddToReadingList,
|
action: Action.Download,
|
||||||
title: 'Add to Reading List',
|
title: 'Download',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -363,13 +404,15 @@ export class ActionFactoryService {
|
|||||||
action: Action.Edit,
|
action: Action.Edit,
|
||||||
title: 'Edit',
|
title: 'Edit',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.Delete,
|
action: Action.Delete,
|
||||||
title: 'Delete',
|
title: 'Delete',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -378,20 +421,41 @@ export class ActionFactoryService {
|
|||||||
action: Action.ViewSeries,
|
action: Action.ViewSeries,
|
||||||
title: 'View Series',
|
title: 'View Series',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.DownloadBookmark,
|
action: Action.DownloadBookmark,
|
||||||
title: 'Download',
|
title: 'Download',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: Action.Delete,
|
action: Action.Delete,
|
||||||
title: 'Clear',
|
title: 'Clear',
|
||||||
callback: this.dummyCallback,
|
callback: this.dummyCallback,
|
||||||
requiresAdmin: false
|
requiresAdmin: false,
|
||||||
|
children: [],
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyCallback(action: ActionItem<any>, callback: (action: Action, data: any) => void) {
|
||||||
|
action.callback = callback;
|
||||||
|
|
||||||
|
if (action.children === null || action.children?.length === 0) return;
|
||||||
|
|
||||||
|
action.children?.forEach((childAction) => {
|
||||||
|
this.applyCallback(childAction, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyCallbackToList(list: Array<ActionItem<any>>, callback: (action: Action, data: any) => void): Array<ActionItem<any>> {
|
||||||
|
const actions = list.map((a) => {
|
||||||
|
return { ...a };
|
||||||
|
});
|
||||||
|
actions.forEach((action) => this.applyCallback(action, callback));
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ export class CardDetailDrawerComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this))
|
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this))
|
||||||
.filter(item => item.action !== Action.Edit);
|
.filter(item => item.action !== Action.Edit);
|
||||||
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false});
|
this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false, children: []});
|
||||||
|
|
||||||
this.libraryService.getLibraryType(this.libraryId).subscribe(type => {
|
this.libraryService.getLibraryType(this.libraryId).subscribe(type => {
|
||||||
this.libraryType = type;
|
this.libraryType = type;
|
||||||
|
@ -2,10 +2,22 @@
|
|||||||
<div ngbDropdown container="body" class="d-inline-block">
|
<div ngbDropdown container="body" class="d-inline-block">
|
||||||
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle (click)="preventClick($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
<button [disabled]="disabled" class="btn {{btnClass}}" id="actions-{{labelBy}}" ngbDropdownToggle (click)="preventClick($event)"><i class="fa {{iconClass}}" aria-hidden="true"></i></button>
|
||||||
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
|
<div ngbDropdownMenu attr.aria-labelledby="actions-{{labelBy}}">
|
||||||
<button ngbDropdownItem *ngFor="let action of nonAdminActions" (click)="performAction($event, action)">{{action.title}}</button>
|
<ng-container *ngTemplateOutlet="submenu; context: { list: actions }"></ng-container>
|
||||||
<div class="dropdown-divider" *ngIf="nonAdminActions.length > 1 && adminActions.length > 1"></div>
|
|
||||||
<button ngbDropdownItem *ngFor="let action of adminActions" (click)="performAction($event, action)">{{action.title}}</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- IDEA: If we are not on desktop, then let's open a bottom drawer instead-->
|
<ng-template #submenu let-list="list">
|
||||||
|
<ng-container *ngFor="let action of list">
|
||||||
|
<ng-container *ngIf="action.children === undefined || action?.children?.length === 0 else submenuDropdown">
|
||||||
|
<button ngbDropdownItem *ngIf="willRenderAction(action)" (click)="performAction($event, action)">{{action.title}}</button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #submenuDropdown>
|
||||||
|
<div ngbDropdown #subMenuHover="ngbDropdown" placement="right" (mouseover)="preventClick($event); openSubmenu(action.title, subMenuHover)" (mouseleave)="preventClick($event)">
|
||||||
|
<button id="actions-{{action.title}}" class="submenu-toggle" ngbDropdownToggle>{{action.title}} <i class="fa-solid fa-angle-right submenu-icon"></i></button>
|
||||||
|
<div ngbDropdownMenu attr.aria-labelledby="actions-{{action.title}}">
|
||||||
|
<ng-container *ngTemplateOutlet="submenu; context: { list: action.children }"></ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
@ -1,3 +1,28 @@
|
|||||||
.dropdown-toggle:after {
|
.dropdown-toggle:after {
|
||||||
content: none !important;
|
content: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submenu-toggle {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: inherit;
|
||||||
|
border: 0;
|
||||||
|
color: var(--dropdown-item-text-color);
|
||||||
|
background-color: var(--dropdown-item-bg-color);
|
||||||
|
&:hover {
|
||||||
|
color: var(--dropdown-item-text-color);
|
||||||
|
background-color: var(--dropdown-item-hover-bg-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:focus-visible {
|
||||||
|
color: var(--dropdown-item-text-color);
|
||||||
|
background-color: var(--dropdown-item-hover-bg-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-icon {
|
||||||
|
float: right;
|
||||||
|
padding: var(--bs-dropdown-item-padding-y) 0;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { ActionItem } from 'src/app/_services/action-factory.service';
|
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { take } from 'rxjs';
|
||||||
|
import { AccountService } from 'src/app/_services/account.service';
|
||||||
|
import { Action, ActionItem } from 'src/app/_services/action-factory.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-card-actionables',
|
selector: 'app-card-actionables',
|
||||||
@ -16,16 +19,19 @@ export class CardActionablesComponent implements OnInit {
|
|||||||
@Input() disabled: boolean = false;
|
@Input() disabled: boolean = false;
|
||||||
@Output() actionHandler = new EventEmitter<ActionItem<any>>();
|
@Output() actionHandler = new EventEmitter<ActionItem<any>>();
|
||||||
|
|
||||||
adminActions: ActionItem<any>[] = [];
|
isAdmin: boolean = false;
|
||||||
nonAdminActions: ActionItem<any>[] = [];
|
canDownload: boolean = false;
|
||||||
|
submenu: {[key: string]: NgbDropdown} = {};
|
||||||
|
|
||||||
|
constructor(private readonly cdRef: ChangeDetectorRef, private accountService: AccountService) { }
|
||||||
constructor(private readonly cdRef: ChangeDetectorRef) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.nonAdminActions = this.actions.filter(item => !item.requiresAdmin);
|
this.accountService.currentUser$.pipe(take(1)).subscribe((user) => {
|
||||||
this.adminActions = this.actions.filter(item => item.requiresAdmin);
|
if (!user) return;
|
||||||
this.cdRef.markForCheck();
|
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||||
|
this.canDownload = this.accountService.hasDownloadRole(user);
|
||||||
|
this.cdRef.markForCheck();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
preventClick(event: any) {
|
preventClick(event: any) {
|
||||||
@ -41,4 +47,23 @@ export class CardActionablesComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
willRenderAction(action: ActionItem<any>): boolean {
|
||||||
|
return (action.requiresAdmin && this.isAdmin)
|
||||||
|
|| (action.action === Action.Download && (this.canDownload || this.isAdmin))
|
||||||
|
|| (!action.requiresAdmin && action.action !== Action.Download)
|
||||||
|
}
|
||||||
|
|
||||||
|
openSubmenu(actionTitle: string, subMenu: NgbDropdown) {
|
||||||
|
// We keep track when we open and when we get a request to open, if we have other keys, we close them and clear their keys
|
||||||
|
if (Object.keys(this.submenu).length > 0) {
|
||||||
|
const keys = Object.keys(this.submenu).filter(k => k !== actionTitle);
|
||||||
|
keys.forEach(key => {
|
||||||
|
this.submenu[key].close();
|
||||||
|
delete this.submenu[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.submenu[actionTitle] = subMenu;
|
||||||
|
subMenu.open();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -482,7 +482,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy, AfterContentChe
|
|||||||
|
|
||||||
this.seriesActions = this.actionFactoryService.getSeriesActions(this.handleSeriesActionCallback.bind(this))
|
this.seriesActions = this.actionFactoryService.getSeriesActions(this.handleSeriesActionCallback.bind(this))
|
||||||
.filter(action => action.action !== Action.Edit);
|
.filter(action => action.action !== Action.Edit);
|
||||||
this.seriesActions.push({action: Action.Download, callback: this.seriesActions[0].callback, requiresAdmin: false, title: 'Download'});
|
|
||||||
|
|
||||||
this.volumeActions = this.actionFactoryService.getVolumeActions(this.handleVolumeActionCallback.bind(this));
|
this.volumeActions = this.actionFactoryService.getVolumeActions(this.handleVolumeActionCallback.bind(this));
|
||||||
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user