You've already forked pigallery2
mirror of
https://github.com/bpatrik/pigallery2.git
synced 2025-11-23 22:24:44 +02:00
Add metadata filters and always show options to media buttons #743
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pigallery2-extension-kit",
|
||||
"version": "2.5.0-edge8",
|
||||
"version": "2.6.0-edge",
|
||||
"description": "Interfaces for developing extensions for pigallery2",
|
||||
"author": "Patrik J. Braun",
|
||||
"homepage": "https://github.com/bpatrik/pigallery2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pigallery2",
|
||||
"version": "2.5.0-edge",
|
||||
"version": "2.6.0-edge",
|
||||
"description": "This is a photo gallery optimised for running low resource servers (especially on raspberry pi)",
|
||||
"author": "Patrik J. Braun",
|
||||
"homepage": "https://github.com/bpatrik/pigallery2",
|
||||
|
||||
@@ -237,10 +237,10 @@ export interface IUIExtension<C> {
|
||||
* Adds a new button on to UI to all media (photo, video).
|
||||
* Implement the server-side click action in the serverSB function.
|
||||
* @param buttonConfig
|
||||
* @param serverSB
|
||||
* @param serverSB If not set the button will be a fake button (i.e.: only show up not clickable)
|
||||
*/
|
||||
|
||||
addMediaButton(buttonConfig: IClientMediaButtonConfig, serverSB: (params: ParamsDictionary, body: any, user: UserDTO, media: MediaEntity, repository: Repository<MediaEntity>) => Promise<void>): void;
|
||||
addMediaButton(buttonConfig: IClientMediaButtonConfig, serverSB?: (params: ParamsDictionary, body: any, user: UserDTO, media: MediaEntity, repository: Repository<MediaEntity>) => Promise<void>): void;
|
||||
}
|
||||
|
||||
export interface IExtensionConfigInit<C> {
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface IClientSVGIconConfig {
|
||||
|
||||
export interface IClientMediaButtonPopupFields {
|
||||
/**
|
||||
* Id of the field. This how it will listed in the body.
|
||||
* Id of the field. This how it will be listed in the body.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
@@ -70,20 +70,35 @@ export interface IClientMediaButtonConfig {
|
||||
name: string;
|
||||
/**
|
||||
* Icon of the button. This will be shown in the gallery and also in the lightbox.
|
||||
* You can look for icons here: https://fontawesome.com
|
||||
*/
|
||||
svgIcon: IClientSVGIconConfig;
|
||||
/**
|
||||
* If true, the button will be hidden if on photos
|
||||
* If true, the button will be hidden on photos
|
||||
*/
|
||||
skipPhotos?: boolean;
|
||||
/**
|
||||
* If true, the button will be hidden if on videos
|
||||
* If true, the button will be hidden on videos
|
||||
*/
|
||||
skipVideos?: boolean;
|
||||
/**
|
||||
* Path to the server side function that will be called when the button is clicked.
|
||||
* If set, the button will only be shown if the metadata matches this filter.
|
||||
* example values:
|
||||
* [
|
||||
* {field: 'rating', comparator: '==', value: 4}, // matches if rating is 4
|
||||
* {field: 'size.width', comparator: '>=', value: 400} // matches if width is >= 400px
|
||||
* ]
|
||||
*/
|
||||
apiPath: string;
|
||||
metadataFilter?: { field: string, comparator: '>=' | '<=' | '==', value: string | number }[];
|
||||
/**
|
||||
* If true, the button will always be visible, not only on hover.
|
||||
*/
|
||||
alwaysVisible?: boolean;
|
||||
/**
|
||||
* Path to the server side function that will be called when the button is clicked.
|
||||
* If it is not set, it will be a fake button. (i.e.: only shows up but does not do anything)
|
||||
*/
|
||||
apiPath?: string;
|
||||
/**
|
||||
* Popup config. IF you want conformation before calling the apiPath or want to change media metedata, set this.
|
||||
*/
|
||||
|
||||
@@ -44,6 +44,9 @@ export class MediaButtonModalService {
|
||||
|
||||
async executeButtonAction(button: IClientMediaButtonConfigWithBaseApiPath, media: GridMedia, formData?: any): Promise<void> {
|
||||
try {
|
||||
if (!button.apiPath) {
|
||||
return; // this is a fake button, nothing to call
|
||||
}
|
||||
// Construct the full API path using base path and button's API path
|
||||
const apiPath = Utils.concatUrls(button.extensionBasePath, button.apiPath);
|
||||
|
||||
|
||||
@@ -194,11 +194,15 @@ a {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.photo-container:hover .media-buttons {
|
||||
.photo-container:hover .media-button {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.media-buttons {
|
||||
.media-button {
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease-out;
|
||||
}
|
||||
|
||||
.media-button.always-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<div class="media-buttons" *ngIf="mediaButtons.length > 0">
|
||||
<button *ngFor="let button of mediaButtons"
|
||||
class="media-button"
|
||||
[class.always-visible]="button.alwaysVisible"
|
||||
[title]="button.name"
|
||||
(click)="onMediaButtonClick(button, $event)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -109,8 +109,51 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
|
||||
if (this.gridMedia.isPhoto() && button.skipPhotos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check metadataFilter
|
||||
if (button.metadataFilter && button.metadataFilter.length > 0) {
|
||||
return this.matchesMetadataFilter(button.metadataFilter);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// move always visible buttons to the front
|
||||
this.mediaButtons = [...this.mediaButtons.filter(b => b.alwaysVisible), ...this.mediaButtons.filter(b => !b.alwaysVisible)];
|
||||
}
|
||||
|
||||
matchesMetadataFilter(filters: { field: string, comparator: '>=' | '<=' | '==', value: string | number }[]): boolean {
|
||||
const metadata = this.gridMedia.media.metadata;
|
||||
|
||||
// All filters must match (AND logic)
|
||||
return filters.every(filter => {
|
||||
// Get the value from metadata using the field path (e.g., 'rating' or 'size.width')
|
||||
const fieldParts = filter.field.split('.');
|
||||
let fieldValue: any = metadata;
|
||||
|
||||
for (const part of fieldParts) {
|
||||
if (fieldValue === undefined || fieldValue === null) {
|
||||
return false;
|
||||
}
|
||||
fieldValue = fieldValue[part];
|
||||
}
|
||||
|
||||
if (fieldValue === undefined || fieldValue === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare based on comparator
|
||||
switch (filter.comparator) {
|
||||
case '>=':
|
||||
return fieldValue >= filter.value;
|
||||
case '<=':
|
||||
return fieldValue <= filter.value;
|
||||
case '==':
|
||||
return fieldValue == filter.value; // Use == for loose equality
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -223,6 +266,10 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if(!button.apiPath){
|
||||
return; // this is a fake button, nothing to call
|
||||
}
|
||||
|
||||
if (button.popup) {
|
||||
this.modalService.showModal(button, this.gridMedia);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user