1
0
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:
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",
"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",

View File

@@ -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",

View File

@@ -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> {

View File

@@ -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.
*/

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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 {