1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-15 03:05:26 +02:00

Hookable upgrades to InputfieldImage, including the ability to add thumbnail icon actions, the ability to add new buttons in image-edit mode, and full working examples in the phpdoc, plus add an EXIF working example for the image actions dropdown. This originated from PR #251 which added a Download button. But I decided we didn't really need a download button (since you can right click on image in image-edit mode and "Save as...") and instead took the additions from the PR and turned it into a hookable feature so you can add any needed actions relatively easily, whether download or something else.

Co-authored-by: JanRomero <JanRomero@users.noreply.github.com>
This commit is contained in:
Ryan Cramer
2023-01-27 10:58:20 -05:00
parent 474e31b2be
commit 3e81b0fc4d
3 changed files with 139 additions and 29 deletions

File diff suppressed because one or more lines are too long

View File

@@ -47,6 +47,8 @@
* @method array buildTooltipData(Pageimage $pagefile) * @method array buildTooltipData(Pageimage $pagefile)
* @method array getFileActions(Pagefile $pagefile) * @method array getFileActions(Pagefile $pagefile)
* @method bool|null processUnknownFileAction(Pageimage $pagefile, $action, $label) * @method bool|null processUnknownFileAction(Pageimage $pagefile, $action, $label)
* @method array getImageEditButtons($pagefile, $id, $n, $buttonClass) 3.0.212+
* @method array getImageThumbnailActions($pagefile, $id, $n, $class) 3.0.212+
* *
* *
*/ */
@@ -57,7 +59,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
return array( return array(
'title' => __('Images', __FILE__), // Module Title 'title' => __('Images', __FILE__), // Module Title
'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary 'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary
'version' => 125, 'version' => 126,
'permanent' => true, 'permanent' => true,
); );
} }
@@ -682,13 +684,18 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
$error = str_replace('{out}', $sanitizer->entities($thumb['error']), $this->themeSettings['error']); $error = str_replace('{out}', $sanitizer->entities($thumb['error']), $this->themeSettings['error']);
} }
$labels = $this->labels; $labels = $this->labels;
$thumbnailActions = array();
if($this->hasHook('getImageThumbnailActions()')) {
$thumbnailActions = $this->getImageThumbnailActions($pagefile, $id, $n, 'gridImage__btn');
}
$thumbnailActions = implode('', $thumbnailActions);
$out .= " $out .= "
<div class='gridImage__hover'> <div class='gridImage__hover'>
<div class='gridImage__inner'> <div class='gridImage__inner'>
<label for='' class='gridImage__trash'> <label for='' class='gridImage__btn gridImage__trash'>
<input class='gridImage__deletebox' type='checkbox' name='delete_$id' value='1' title='$labels[delete]' /> <input class='gridImage__deletebox' type='checkbox' name='delete_$id' value='1' title='$labels[delete]' />
<span class='fa fa-trash-o'></span> <span class='fa fa-trash-o'></span>
</label> </label>$thumbnailActions
<a class='gridImage__edit'> <a class='gridImage__edit'>
<span>$labels[edit]</span> <span>$labels[edit]</span>
</a> </a>
@@ -729,6 +736,37 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
return $out; return $out;
} }
/**
* Get the image thumbnail icon actions/links/buttons
*
* These are icon-only actions/links displayed next to the trash icon when hovering over an image preview.
* They are also displayed as icons on the far right side of a image when in full list mode.
*
* Example:
* ~~~~~
* $wire->addHookAfter('InputfieldImage::getImageThumbnailActions', function(HookEvent $event) {
* $image = $event->arguments(0); // Pageimage
* $class = $event->arguments(3); // class to use on all returned actions
* $a = $event->return; // array
* $a['download'] = "<a class='$class' href='$pagefile->url' download><span class='fa fa-download'></span></a>";
* $event->return = $a;
* });
* ~~~~~
*
* #pw-hooker
*
* @param Pageimage $pagefile
* @param string $id Image id string
* @param int $n Image index number
* @param string $class Class that should appear on all returned actions/links/buttons
* @return array
* @since 3.0.212
*
*/
protected function ___getImageThumbnailActions($pagefile, $id, $n, $class) {
return array();
}
/** /**
* Render a Pageimage item * Render a Pageimage item
* *
@@ -807,40 +845,70 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
* *
*/ */
protected function ___renderButtons($pagefile, $id, $n) { protected function ___renderButtons($pagefile, $id, $n) {
if(!$this->useImageEditor) return ''; if(!$this->useImageEditor) return '';
$buttonClass = $this->themeSettings['buttonClass'];
if($n) {} // ignore, $n is for hooks $buttons = $this->getImageEditButtons($pagefile, $id, $n, $buttonClass);
return implode('', $buttons);
}
/**
* Get array of buttons for image edit mode
*
* Hook after this to add or remove image edit buttons/actions.
*
* ~~~~~
* // Example of adding a download button
* $wire->addHookAfter('InputfieldImage::getImageEditButtons', function(HookEvent $event) {
* $image = $event->arguments(0); // Pageimage
* $class = $event->arguments(3);
* $buttons = $event->return; // array
* $icon = wireIconMarkup('download');
* $buttons['download'] = "<button class='$class'><a download href='$image->url'>$icon Download</a></button>";
* $event->return = $buttons;
* });
* ~~~~~
*
* #pw-hooker
*
* @param Pagefile|Pageimage $pagefile Image that buttons are for
* @param string $id Image/file id
* @param int $n Index of image/file (i.e 0=first)
* @param string $buttonClass Class attribute additions that should appear on all returned <button> elements
* @return array Array of <button> elements indexed by button name
* @since 3.0.212
*
*/
protected function ___getImageEditButtons($pagefile, $id, $n, $buttonClass) {
$buttons = array();
$pageID = $pagefile->pagefiles->page->id; $pageID = $pagefile->pagefiles->page->id;
$variationCount = $pagefile->variations()->count(); $variationCount = $pagefile->variations()->count();
$editUrl = $this->getEditUrl($pagefile, $pageID); $editUrl = $this->getEditUrl($pagefile, $pageID);
$variationUrl = $this->getVariationUrl($pagefile, $id); $variationUrl = $this->getVariationUrl($pagefile, $id);
$buttonClass = $this->themeSettings['buttonClass'];
$modalButtonClass = trim("$buttonClass $this->modalClass pw-modal"); $modalButtonClass = trim("$buttonClass $this->modalClass pw-modal");
$modalAttrs = "data-buttons='#non_rte_dialog_buttons button' data-autoclose='1' data-close='#non_rte_cancel'"; $modalAttrs = "data-buttons='#non_rte_dialog_buttons button' data-autoclose='1' data-close='#non_rte_cancel'";
$labels = $this->labels; $labels = $this->labels;
$out = '';
// Crop // Crop
$icon = wireIconMarkup('crop'); $icon = wireIconMarkup('crop');
$buttonText = str_replace('{out}', "$icon $labels[crop]", $this->themeSettings['buttonText']); $buttonText = str_replace('{out}', "$icon $labels[crop]", $this->themeSettings['buttonText']);
$out .= "<button type='button' data-href='$editUrl' class='InputfieldImageButtonCrop $modalButtonClass' $modalAttrs>$buttonText</button>"; $buttons['crop'] = "<button type='button' data-href='$editUrl' class='InputfieldImageButtonCrop $modalButtonClass' $modalAttrs>$buttonText</button>";
// Focus // Focus
if($this->focusMode && $this->focusMode != 'off') { if($this->focusMode && $this->focusMode != 'off') {
$iconA = $pagefile->hasFocus ? 'fa-check-circle-o' : 'fa-circle-o'; $iconA = $pagefile->hasFocus ? 'fa-check-circle-o' : 'fa-circle-o';
$iconB = $pagefile->hasFocus ? 'fa-check-circle' : 'fa-dot-circle-o'; $iconB = $pagefile->hasFocus ? 'fa-check-circle' : 'fa-dot-circle-o';
$buttonText = str_replace('{out}', "<i class='fa $iconA' data-toggle='$iconA $iconB'></i> $labels[focus]", $this->themeSettings['buttonText']); $buttonText = str_replace('{out}', "<i class='fa $iconA' data-toggle='$iconA $iconB'></i> $labels[focus]", $this->themeSettings['buttonText']);
$out .= "<button type='button' class='InputfieldImageButtonFocus $buttonClass'>$buttonText</button>"; $buttons['focus'] = "<button type='button' class='InputfieldImageButtonFocus $buttonClass'>$buttonText</button>";
} }
// Variations // Variations
$icon = wireIconMarkup('files-o'); $icon = wireIconMarkup('files-o');
$buttonText = "$icon $labels[variations] <span class='ui-priority-secondary'>($variationCount)</span>"; $buttonText = "$icon $labels[variations] <span class='ui-priority-secondary'>($variationCount)</span>";
$buttonText = str_replace('{out}', $buttonText, $this->themeSettings['buttonText']); $buttonText = str_replace('{out}', $buttonText, $this->themeSettings['buttonText']);
$out .= "<button type='button' data-href='$variationUrl' class='$modalButtonClass' data-buttons='button'>$buttonText</button>"; $buttons['variations'] = "<button type='button' data-href='$variationUrl' class='$modalButtonClass' data-buttons='button'>$buttonText</button>";
return $out; return $buttons;
} }
/** /**
@@ -888,7 +956,32 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
} }
/** /**
* Get array of actions available for given Pagefile * Get array of actions (displayed in select dropdown) available for given Pagefile
*
* #pw-hooker
*
* ~~~~~
* // Example of adding an “Get EXIF data” action
* $wire->addHookAfter('InputfieldImage::getFileActions', function(HookEvent $event) {
* $image = $event->arguments(0); // Pageimage
* if($image->ext == 'jpg' || $image->ext == 'jpeg') {
* $actions = $event->return; // array
* $actions['exif'] = 'Get EXIF data';
* $event->return = $actions;
* }
* });
*
* // Example of handling an “Get EXIF data” action
* $wire->addHookAfter('InputfieldImage::processUnknownFileAction', function(HookEvent $event) {
* $image = $event->arguments(0);
* $action = $event->arguments(1);
* if($action === 'exif') {
* $exif = exif_read_data($image->filename);
* $event->warning([ "EXIF data for $image->name" => $exif ], 'icon-photo nogroup');
* $event->return = true;
* }
* });
* ~~~~~
* *
* @param Pagefile|Pageimage $pagefile * @param Pagefile|Pageimage $pagefile
* @return array Associative array of ('action_name' => 'Action Label') * @return array Associative array of ('action_name' => 'Action Label')
@@ -1353,7 +1446,12 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
} }
/** /**
* Called when an action was received that InputfieldImage does not recognize (for hooking purposes) * Called when a select dropdown action was received that InputfieldImage does not recognize (for hooking purposes)
*
* This is what should be hooked to provide the processing for a custom action added from a hook.
* See the ___getFileActions() method documentation for full example including both hooks.
*
* #pw-hooker
* *
* @param Pageimage $pagefile Image file to process * @param Pageimage $pagefile Image file to process
* @param string $action Action to execute * @param string $action Action to execute

View File

@@ -210,15 +210,18 @@ $focusPointCircleSize: 40px;
} }
} }
&__trash { // &__trash {
position: absolute; &__btn {
// position: absolute;
position: relative;
float: left;
background: white; background: white;
padding: .2em .6em; padding: .2em .6em;
color: #aaa; color: #aaa;
z-index: 2; z-index: 2;
cursor: pointer; cursor: pointer;
@at-root label#{&} { @at-root .gridImage #{&} {
display: none; display: none;
} }
@@ -235,7 +238,7 @@ $focusPointCircleSize: 40px;
display: block; display: block;
} }
@at-root .gridImage:hover label#{&}:hover { @at-root .gridImage:hover #{&}:hover {
// overwrite a:hover // overwrite a:hover
display: block; display: block;
color: $deleteColor; color: $deleteColor;
@@ -349,7 +352,7 @@ $focusPointCircleSize: 40px;
background: rgba($activeColor, .5); background: rgba($activeColor, .5);
} }
& .gridImage__trash { & .gridImage__btn {
background-color: $activeColor; background-color: $activeColor;
color: white; color: white;
@@ -358,7 +361,7 @@ $focusPointCircleSize: 40px;
} }
} }
&:hover .gridImage__trash:hover { &:hover .gridImage__btn:hover {
background-color: $activeColor; background-color: $activeColor;
} }
} }
@@ -390,6 +393,7 @@ $focusPointCircleSize: 40px;
&:hover .gridImage__trash:hover { &:hover .gridImage__trash:hover {
background-color: $deleteColor; background-color: $deleteColor;
color: white;
} }
&:hover .gridImage__inner { &:hover .gridImage__inner {
@@ -566,6 +570,9 @@ $focusPointCircleSize: 40px;
margin: 1em 0 0.5em 0; margin: 1em 0 0.5em 0;
button { button {
margin-bottom: 0.5em; margin-bottom: 0.5em;
a, a:hover {
text-decoration: none;
}
} }
} }
@@ -828,11 +835,13 @@ $focusPointCircleSize: 40px;
} }
} }
&__trash { &__btn {
z-index: 5; z-index: 5;
display: block !important; display: block !important;
top: 0; top: 0;
right: 0; right: 0;
float: right;
background: transparent;
} }
&__edit { &__edit {
@@ -874,7 +883,10 @@ $focusPointCircleSize: 40px;
display: none; display: none;
} }
&:hover .gridImage__trash:hover { .gridImage__btn {
color: white;
}
&:hover .gridImage__btn:hover {
color: white; color: white;
background-color: $deleteColor; background-color: $deleteColor;
} }
@@ -934,4 +946,4 @@ $focusPointCircleSize: 40px;
.gridImage__edit { display:block !important; } .gridImage__edit { display:block !important; }
.gridImage__edit span { display:none; } .gridImage__edit span { display:none; }
.gridImage__edit:hover span { display:inline; } .gridImage__edit:hover span { display:inline; }
*/ */