From 23f62f741d625446878c93cf6c77246bda10939f Mon Sep 17 00:00:00 2001 From: CJ <72030708+cj12312021@users.noreply.github.com> Date: Sat, 7 Jun 2025 19:28:03 -0700 Subject: [PATCH 1/3] Secondary Performer Image Plugin --- plugins/SecondaryPerformerImage/README.md | 12 ++++++++++++ .../SecondaryPerformerImage.css | 1 + .../SecondaryPerformerImage.js | 1 + .../SecondaryPerformerImage.yml | 16 ++++++++++++++++ 4 files changed, 30 insertions(+) create mode 100644 plugins/SecondaryPerformerImage/README.md create mode 100644 plugins/SecondaryPerformerImage/SecondaryPerformerImage.css create mode 100644 plugins/SecondaryPerformerImage/SecondaryPerformerImage.js create mode 100644 plugins/SecondaryPerformerImage/SecondaryPerformerImage.yml diff --git a/plugins/SecondaryPerformerImage/README.md b/plugins/SecondaryPerformerImage/README.md new file mode 100644 index 00000000..6fa71932 --- /dev/null +++ b/plugins/SecondaryPerformerImage/README.md @@ -0,0 +1,12 @@ +# Secondart Performer Images + +This plugin adds support for a secondary performer image on the perform details page, enabling more customization. + +## 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 1 to enable the secondary image 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. \ No newline at end of file diff --git a/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css new file mode 100644 index 00000000..ad23211e --- /dev/null +++ b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css @@ -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} \ No newline at end of file diff --git a/plugins/SecondaryPerformerImage/SecondaryPerformerImage.js b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.js new file mode 100644 index 00000000..58e4cf26 --- /dev/null +++ b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.js @@ -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)()})()})(); \ No newline at end of file diff --git a/plugins/SecondaryPerformerImage/SecondaryPerformerImage.yml b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.yml new file mode 100644 index 00000000..b6ddc7dd --- /dev/null +++ b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.yml @@ -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: + /: . From 7ac361d8d6d59f3ba373c1e7f618fef9c49a8647 Mon Sep 17 00:00:00 2001 From: CJ <72030708+cj12312021@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:35:20 -0500 Subject: [PATCH 2/3] Update README.md --- plugins/SecondaryPerformerImage/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/SecondaryPerformerImage/README.md b/plugins/SecondaryPerformerImage/README.md index 6fa71932..acecdf4d 100644 --- a/plugins/SecondaryPerformerImage/README.md +++ b/plugins/SecondaryPerformerImage/README.md @@ -1,6 +1,6 @@ -# Secondart Performer Images +# Secondary Performer Images -This plugin adds support for a secondary performer image on the perform details page, enabling more customization. +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. @@ -9,4 +9,4 @@ The plugin offers three different display modes that can be set for the use of t 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 1 to enable the secondary image 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. \ No newline at end of file +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. From e18756e9cdbb35d021af061de5cd8535f01d52bd Mon Sep 17 00:00:00 2001 From: CJ <72030708+cj12312021@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:47:32 -0500 Subject: [PATCH 3/3] patch CSS in light of other changes merged to develop branch --- plugins/SecondaryPerformerImage/SecondaryPerformerImage.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css index ad23211e..28348c20 100644 --- a/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css +++ b/plugins/SecondaryPerformerImage/SecondaryPerformerImage.css @@ -1 +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} \ No newline at end of file +#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}