Secondary Performer Image Plugin (#576)

This commit is contained in:
CJ 2025-10-20 23:39:07 -05:00 committed by GitHub
parent 55e64832fc
commit 50f8b7f7ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# Secondary Performer Images
This plugin adds support for a secondary performer image on the performer details page, enabling more customization. A new `Set secondary image...` button will be available on the performer edit page to provide this new image. Performers without a secondary image will continue to behave as they've always done.
## Modes
The plugin offers three different display modes that can be set for the use of the secondary image. The modes range from 0-2, with 0 being the default mode. Any values that fall outside this range will be processed as the default mode.
0 - Specify 0 to enable the secondary image to be used in the expanded view. You might consider using this mode if your performer image currently consists of headshots and you want to provide fuller body images for the expanded view.
1 - Specify 1 to enable the secondary image to be used in the collapsed view. You might consider using this mode if your performer image currently consists of body shots and you want to provide headshots for the collapsed view.
2 - Specify 2 to enable a button to be used to flip between the primary and secondary image. This mode gives you the option to provide whatever combination of images you want to use together. Some might consider using this option to provide front and back images of the performer.

View File

@ -0,0 +1 @@
#performer-page .detail-header:not(.edit) .perf-images{position:relative;z-index:1}#performer-page .detail-header:not(.edit) .perf-images button.btn.btn-link{padding:0;position:relative;transition:all .3s;z-index:1}#performer-page .detail-header:not(.edit) .perf-images .active button{z-index:2}#performer-page .detail-header:not(.edit) .perf-images .inactive img{display:none}#performer-page .detail-header:not(.edit) .perf-images .inactive .detail-header-image{padding:0}#performer-page .detail-header:not(.edit) .perf-images .inactive button{opacity:.5;padding:0;transform:rotateY(180deg)}#performer-page .detail-header:not(.edit) .perf-images button.flip{align-items:center;border-radius:50%;bottom:-5px;display:flex;font-size:20px;height:40px;justify-content:center;padding:0;position:absolute;right:-5px;width:40px;z-index:2}#performer-page:has(button.flip) .perf-images{display:flex;float:left;height:auto;justify-content:center}.edit .secondary-image{display:none}.secondary-image-popover{padding:5px}.secondary-image-popover .secondary-image-thumbnail{max-width:22rem;object-fit:cover;object-position:top}.performer-head .custom-fields .detail-item.alt-image{display:none}

View File

@ -0,0 +1 @@
(()=>{"use strict";var e={264:(e,t,a)=>{a.r(t)},577:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0});const{PluginApi:a}=window,{React:n}=a,r=(e,t)=>{const a=new FileReader;a.onloadend=()=>{a.error||t(a.result)},a.readAsDataURL(e)},o={onImageChange:(e,t)=>{var a,n;const o=null===(n=null===(a=null==e?void 0:e.currentTarget)||void 0===a?void 0:a.files)||void 0===n?void 0:n[0];o&&r(o,t)},usePasteImage:(e,t=!0)=>{const a=n.useCallback((t=>{e(t)}),[e]);return n.useEffect((()=>{const e=e=>((e,t)=>{var a;const n=null===(a=null==e?void 0:e.clipboardData)||void 0===a?void 0:a.files;if(!(null==n?void 0:n.length))return;const o=n[0];r(o,t)})(e,a);return t&&document.addEventListener("paste",e),()=>document.removeEventListener("paste",e)}),[t,a]),!1},imageToDataURL:async e=>{const t=await fetch(e),a=await t.blob();return new Promise(((e,t)=>{const n=new FileReader;n.onloadend=()=>{e(n.result)},n.onerror=t,n.readAsDataURL(a)}))}};t.default=o},604:function(e,t,a){var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.handlePerformerHeaderImagePatch=function(){o.patch.instead("PerformerHeaderImage",(function(e,t,a){var n,r;const{encodingImage:s,collapsed:u,activeImage:m,lightboxImages:d,performer:g}=e,{Button:v}=o.libraries.Bootstrap,{Icon:f}=o.components,{faRefresh:p}=o.libraries.FontAwesomeSolid,[y,h]=l.useState(!0),E=i.useConfigurationQuery(),[b]=c.usePerformerUpdate();void 0===(null===(n=g.custom_fields)||void 0===n?void 0:n.alt_image)&&b({variables:{input:{id:g.id,custom_fields:{full:{alt_image:""}}}}});const I=a({...e});return!E.loading&&(null===(r=g.custom_fields)||void 0===r?void 0:r.alt_image)?l.createElement(l.Fragment,null,function(){var e,t;const n=a({encodingImage:s,activeImage:null===(e=g.custom_fields)||void 0===e?void 0:e.alt_image,lightboxImages:d,performer:g}),r=E.data.configuration.plugins.SecondaryPerformerImage;let o=null!==(t=null==r?void 0:r.imageMode)&&void 0!==t?t:0;if((o<0||o>2)&&(o=0),2==o)return l.createElement("div",{className:"perf-images"},l.createElement("div",{className:"primary-image "+(y?"active":"inactive")},I),l.createElement("div",{className:"secondary-image "+(y?"inactive":"active")},n),l.createElement(v,{className:"flip",onClick:()=>h(!y)},l.createElement(f,{icon:p})));{let e=0==o?u:!u;return l.createElement("div",{className:"perf-images"},l.createElement("div",{className:"primary-image "+(e?"active":"inactive")},I),l.createElement("div",{className:"secondary-image "+(e?"inactive":"active")},n))}}()):l.createElement(l.Fragment,null,I)})),o.patch.instead("ImageInput",(function(e,t,a){var n;const{isEditing:o,text:i,onImageChange:c,onImageURL:s,acceptSVG:u}=e,m=document.querySelector("#performer-page"),d=null===(n=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,"value"))||void 0===n?void 0:n.set,g=document.querySelector("div.custom-fields-input > button"),v=document.querySelector("div.custom-fields-input > div"),f=document.querySelector('input[placeholder="alt_image"]');async function p(e){if(!e||!f||!d)return;var t;"collapse"==v.getAttribute("class")&&(g.focus(),g.click(),await(t=200,new Promise((e=>setTimeout(e,t)))),f.focus()),null==d||d.call(f,e);const a=new Event("change",{bubbles:!0});f.dispatchEvent(a),f.focus()}const y=a({...e}),h=a({isEditing:o,text:"Set secondary image...",onImageChange:function(e){r.default.onImageChange(e,p)},onImageURL:p,acceptSVG:u});return m?l.createElement(l.Fragment,null,y,h):l.createElement(l.Fragment,null,y)})),o.patch.instead("CustomFieldInput",(function(e,t,a){const{field:n,value:r,onChange:i,isNew:c=!1,error:s}=e,{HoverPopover:u}=o.components,m=l.useMemo((()=>l.createElement("div",{className:"secondary-image-popover"},l.createElement("img",{className:"secondary-image-thumbnail",alt:n,src:r}))),[r]),d=a({...e});return"alt_image"===n&&r?l.createElement(u,{className:"scene-card__performer",placement:"top",content:m,leaveDelay:100},d):l.createElement(l.Fragment,null,d)})),o.patch.instead("CustomFields",(function(e,t,a){const{values:n}=e;if(Object.keys(n).length<=1)return l.createElement(l.Fragment,null);const r=a({...e});return l.createElement(l.Fragment,null,r)}))};const r=n(a(577)),{PluginApi:o}=window,{GQL:i,React:l}=o,{StashService:c}=window.PluginApi.utils}},t={};function a(n){var r=t[n];if(void 0!==r)return r.exports;var o=t[n]={exports:{}};return e[n].call(o.exports,o,o.exports,a),o.exports}a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};(()=>{const e=a(604);a(264),(0,e.handlePerformerHeaderImagePatch)()})()})();

View File

@ -0,0 +1,16 @@
name: Add secondary perfomrer image.
description: Adds support for a secondary perfomrer image on the perform details page.
url: https://github.com/stashapp/CommunityScripts
version: 1.0
settings:
imageMode:
displayName: Image Mode
description: Mode at which to display the performer image. There are only 3 valid values. Specify 0 to enable the secondary image to be used in the expanded view. Specify 1 to enable the secondary image to be used in the collapsed view. Specify 2 to enable a button to be used to flip between the primary and secondary image.
type: NUMBER
ui:
javascript:
- SecondaryPerformerImage.js
css:
- SecondaryPerformerImage.css
assets:
/: .