1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-11-25 22:32:52 +02:00

Add metadata filters and always show options to media buttons #743

This commit is contained in:
Patrik J. Braun
2025-10-24 00:25:55 +02:00
parent 89bf9929e4
commit 2addbdeefc
8 changed files with 81 additions and 11 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "pigallery2-extension-kit", "name": "pigallery2-extension-kit",
"version": "2.5.0-edge8", "version": "2.6.0-edge",
"description": "Interfaces for developing extensions for pigallery2", "description": "Interfaces for developing extensions for pigallery2",
"author": "Patrik J. Braun", "author": "Patrik J. Braun",
"homepage": "https://github.com/bpatrik/pigallery2", "homepage": "https://github.com/bpatrik/pigallery2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "pigallery2", "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)", "description": "This is a photo gallery optimised for running low resource servers (especially on raspberry pi)",
"author": "Patrik J. Braun", "author": "Patrik J. Braun",
"homepage": "https://github.com/bpatrik/pigallery2", "homepage": "https://github.com/bpatrik/pigallery2",

View File

@@ -237,10 +237,10 @@ export interface IUIExtension<C> {
* Adds a new button on to UI to all media (photo, video). * Adds a new button on to UI to all media (photo, video).
* Implement the server-side click action in the serverSB function. * Implement the server-side click action in the serverSB function.
* @param buttonConfig * @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> { export interface IExtensionConfigInit<C> {

View File

@@ -18,7 +18,7 @@ export interface IClientSVGIconConfig {
export interface IClientMediaButtonPopupFields { 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; id: string;
/** /**
@@ -70,20 +70,35 @@ export interface IClientMediaButtonConfig {
name: string; name: string;
/** /**
* Icon of the button. This will be shown in the gallery and also in the lightbox. * 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; svgIcon: IClientSVGIconConfig;
/** /**
* If true, the button will be hidden if on photos * If true, the button will be hidden on photos
*/ */
skipPhotos?: boolean; skipPhotos?: boolean;
/** /**
* If true, the button will be hidden if on videos * If true, the button will be hidden on videos
*/ */
skipVideos?: boolean; 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. * Popup config. IF you want conformation before calling the apiPath or want to change media metedata, set this.
*/ */

View File

@@ -44,6 +44,9 @@ export class MediaButtonModalService {
async executeButtonAction(button: IClientMediaButtonConfigWithBaseApiPath, media: GridMedia, formData?: any): Promise<void> { async executeButtonAction(button: IClientMediaButtonConfigWithBaseApiPath, media: GridMedia, formData?: any): Promise<void> {
try { 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 // Construct the full API path using base path and button's API path
const apiPath = Utils.concatUrls(button.extensionBasePath, button.apiPath); const apiPath = Utils.concatUrls(button.extensionBasePath, button.apiPath);

View File

@@ -194,11 +194,15 @@ a {
fill: white; fill: white;
} }
.photo-container:hover .media-buttons { .photo-container:hover .media-button {
opacity: 1; opacity: 1;
} }
.media-buttons { .media-button {
opacity: 0; opacity: 0;
transition: opacity .3s ease-out; transition: opacity .3s ease-out;
} }
.media-button.always-visible {
opacity: 1;
}

View File

@@ -22,6 +22,7 @@
<div class="media-buttons" *ngIf="mediaButtons.length > 0"> <div class="media-buttons" *ngIf="mediaButtons.length > 0">
<button *ngFor="let button of mediaButtons" <button *ngFor="let button of mediaButtons"
class="media-button" class="media-button"
[class.always-visible]="button.alwaysVisible"
[title]="button.name" [title]="button.name"
(click)="onMediaButtonClick(button, $event)"> (click)="onMediaButtonClick(button, $event)">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"

View File

@@ -109,8 +109,51 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
if (this.gridMedia.isPhoto() && button.skipPhotos) { if (this.gridMedia.isPhoto() && button.skipPhotos) {
return false; return false;
} }
// Check metadataFilter
if (button.metadataFilter && button.metadataFilter.length > 0) {
return this.matchesMetadataFilter(button.metadataFilter);
}
return true; 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 { ngOnInit(): void {
@@ -223,6 +266,10 @@ export class GalleryPhotoComponent implements IRenderable, OnInit, OnDestroy {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if(!button.apiPath){
return; // this is a fake button, nothing to call
}
if (button.popup) { if (button.popup) {
this.modalService.showModal(button, this.gridMedia); this.modalService.showModal(button, this.gridMedia);
} else { } else {