From 627c6a07dfcfbb181f664c47c4e5fc5b979a5dcd Mon Sep 17 00:00:00 2001 From: ValkyrJS Date: Fri, 21 Jun 2024 11:05:40 +0100 Subject: [PATCH] release: PDE v0.3.0 --- .../PerformerDetailsExtended.css | 2 +- .../PerformerDetailsExtended.js | 645 +++++++++++------- .../PerformerDetailsExtended.yml | 40 +- 3 files changed, 414 insertions(+), 273 deletions(-) diff --git a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.css b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.css index d039830..c8430a6 100644 --- a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.css +++ b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.css @@ -1 +1 @@ -.performer-details-extended{padding-top:0;.detail-item-value .additional-data{opacity:.65;&:before{content:" ("}&:after{content:")"}}.hoverable{border-bottom:1px dotted #fff}.inner-wrapper{align-items:center;display:inline-flex}.separator{margin:0 5px}}.full-width .performer-details-extended{.detail-item{.detail-item-title{width:180px}&.detail-item-wide{display:inline-flex;.detail-item-title,.detail-item-value{width:100%}}}@media (min-width:1080px){display:grid;grid-template-columns:repeat(2,1fr);.detail-item.most-common-tags{grid-column:span 2}}@media (min-width:1360px){grid-template-columns:repeat(3,1fr);.detail-item.most-common-tags{grid-column:span 3}}@media (min-width:1620px){grid-template-columns:repeat(4,1fr)}@media (min-width:1980px){grid-template-columns:repeat(5,1fr)}@media (min-width:2200px){grid-template-columns:repeat(6,1fr)}} \ No newline at end of file +.performer-details-extended{padding-top:0;.detail-item-value .additional-data{opacity:.65;&:before{content:" ("}&:after{content:")"}}.hoverable{border-bottom:1px dotted #fff}.inner-wrapper{align-items:center;display:inline-flex}.separator{margin:0 5px}}.full-width .performer-details-extended{.detail-item{.detail-item-title{width:180px}&.detail-item-wide{display:inline-flex;.detail-item-title,.detail-item-value{width:100%}}}@media (min-width:1080px){display:grid;grid-template-columns:repeat(2,1fr);.detail-item.top-tags{grid-column:span 2}}@media (min-width:1360px){grid-template-columns:repeat(3,1fr);.detail-item.top-tags{grid-column:span 3}}@media (min-width:1620px){grid-template-columns:repeat(4,1fr)}@media (min-width:1980px){grid-template-columns:repeat(5,1fr)}@media (min-width:2200px){grid-template-columns:repeat(6,1fr)}} \ No newline at end of file diff --git a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.js b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.js index be852df..31973d4 100644 --- a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.js +++ b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.js @@ -88,6 +88,140 @@ var DetailItem = function (_a) { exports["default"] = DetailItem; +/***/ }), + +/***/ 327: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +var helpers_1 = __webpack_require__(730); +var constants_1 = __webpack_require__(333); +var DetailItem_1 = __importDefault(__webpack_require__(645)); +var React = window.PluginApi.React; +var makePerformerScenesUrl = window.PluginApi.utils.NavUtils.makePerformerScenesUrl; +var ItemAppearsMostWith = function (_a) { + var performer = _a.performer, props = __rest(_a, ["performer"]); + var _b = props.pluginConfig, appearsMostWithGendered = _b.appearsMostWithGendered, maximumTops = _b.maximumTops, minimumAppearances = _b.minimumAppearances; + // Create an array of performer data from all scenes + var partners = []; + // Check each scene + props.scenesQueryResult.scenes.forEach(function (sc) { + // Check each performer in the scene + sc.performers.forEach(function (pf) { + // Check this is not the featured performer + if (pf.id !== performer.id) { + // Check if the performer already exists in the array + var perfomersIndex = partners.findIndex(function (ptnr) { return ptnr.data.id === pf.id; }); + if (perfomersIndex !== -1) { + // Increase the performer count + partners[perfomersIndex].count++; + } + else { + // Add the performer to the array + partners.push({ + count: 1, + data: pf, + }); + } + } + }); + }); + // Sort count from highest to lowest + partners.sort(function (a, b) { return b.count - a.count; }); + // If the top partner's count is less than the minimum required, don't return + // a component. + if (partners.length === 0 || partners[0].count < minimumAppearances) + return null; + if (appearsMostWithGendered) { + return constants_1.GENDERS.map(function (g) { + var genderedPartners = partners.filter(function (p) { return p.data.gender === g; }); + // If there is no partner for the current gender or the top partner's + // count is less than the minimum required, don't return a component. + if (genderedPartners.length === 0 || + genderedPartners[0].count < minimumAppearances) + return null; + var topPartners = genderedPartners + .filter(function (p) { return p.count === genderedPartners[0].count; }) + .sort(function (a, b) { return a.data.name.localeCompare(b.data.name, "en"); }); + var topPartnersData = topPartners.map(function (p) { + var scenesLink = makePerformerScenesUrl(performer, { + id: p.data.id, + label: p.data.name, + }); + return { name: p.data.name, scenesLink: scenesLink }; + }); + var genderWord = " (" + (0, helpers_1.getGenderFromEnum)(g) + ")"; + var id = genderWord.toLowerCase().split(" ").join("-"); + var scenesText = topPartners[0].count + + " " + + (topPartners[0].count === 1 ? "scene" : "scenes"); + var maxLinks = topPartners.length < maximumTops ? topPartners.length : maximumTops; + var links = []; + for (var i = 0; i < maxLinks; i++) { + links.push(React.createElement("a", { href: topPartnersData[i].scenesLink }, topPartnersData[i].name)); + if (i !== maxLinks - 1) + links.push(" / "); + } + if (topPartners.length > maxLinks) { + var names = topPartners.map(function (p) { return p.data.name; }); + var title = (0, helpers_1.createOverflowText)(names, maxLinks); + links.push(React.createElement(React.Fragment, null, + " ", + React.createElement("span", { className: "top-meta-overflow hoverable", title: title }, + "and ", + topPartners.length - maxLinks, + " more"))); + } + var value = React.createElement.apply(React, __spreadArray([React.Fragment, null], links, false)); + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "appears-most-with-" + id, title: "Appears Most With " + genderWord, value: value, wide: true, additionalData: { + id: "scene-count", + value: scenesText, + } })); + }); + } + var topPartner = partners[0]; + // If the top partner's count is less than the minimum required, don't return + // a component. + if (topPartner.count < minimumAppearances) + return null; + var scenesText = topPartner.count + " " + (topPartner.count === 1 ? "scene" : "scenes"); + var scenesLink = makePerformerScenesUrl(performer, { + id: topPartner.data.id, + label: topPartner.data.name, + }); + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "appears-most-with", title: "Appears Most With", value: React.createElement("a", { href: scenesLink }, topPartner.data.name), wide: true, additionalData: { + id: "scene-count", + value: scenesText, + } })); +}; +exports["default"] = ItemAppearsMostWith; + + /***/ }), /***/ 138: @@ -134,79 +268,43 @@ exports["default"] = ItemAverageRating; /***/ }), -/***/ 790: +/***/ 806: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); var DetailItem_1 = __importDefault(__webpack_require__(645)); -var TagItem_1 = __importDefault(__webpack_require__(718)); var React = window.PluginApi.React; -var ItemMostCommonTags = function (_a) { - var performer = _a.performer, props = __rest(_a, ["performer"]); - var _b = props.pluginConfig, mostCommonTagsCount = _b.mostCommonTagsCount, mostCommonTagsOn = _b.mostCommonTagsOn; - // Do not render the item if the user has turned it off in the config. - if (!mostCommonTagsOn) +var ItemOCount = function (props) { + var total_o_count = props.statsQueryResult.total_o_count; + // Don't return a component if there is no O count across all performers + if (total_o_count === 0) return null; - // Create an array of tag data from all scenes - var tags = []; - // Check each scene + var oCount = 0; props.scenesQueryResult.scenes.forEach(function (sc) { - // Check each tag in the scene - sc.tags.forEach(function (tag) { - // Check if the tag already exists in the array - var tagIndex = tags.findIndex(function (t) { return t.data.id === tag.id; }); - if (tagIndex !== -1) { - // Increase the tag count - tags[tagIndex].count++; - } - else { - // Add the tag to the array - tags.push({ - count: 1, - data: tag, - }); - } - }); + if (typeof sc.o_counter !== "undefined") + oCount += sc.o_counter || 0; }); - // Sort count from highest to lowest - tags.sort(function (a, b) { return b.count - a.count; }); - // Return null if there are no tags - if (!tags.length) + // Don't return a component if there is no O count for this performer + if (oCount === 0) return null; - // Return the tags with the highest overall count, up to the tagCount - var maxTags = tags.length < mostCommonTagsCount ? tags.length : mostCommonTagsCount; - var value = []; - for (var i = 0; i < maxTags; i++) { - var tagCount = tags[i].count; - var tagData = tags[i].data; - var link = "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"".concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")&c=(\"type\":\"tags\",\"value\":(\"items\":%5B(\"id\":\"").concat(tagData.id, "\",\"label\":\"").concat(encodeURIComponent(tagData.name), "\")%5D,\"excluded\":%5B%5D,\"depth\":0),\"modifier\":\"INCLUDES\")"); - value.push(React.createElement(TagItem_1.default, { link: link, title: "".concat(tagData.name, " (").concat(tagCount, " ").concat(tagCount === 1 ? "scene" : "scenes", ")") })); - } - var title = "Most Common Tag" + (mostCommonTagsCount === 1 ? "" : "s"); - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "most-common-tags", title: title, value: value, wide: true })); + var percentage = Math.round((oCount / total_o_count + Number.EPSILON) * 10000) / 100; + var additionalValue = "".concat(percentage, "% of ").concat(total_o_count, " total"); + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "o-count", title: "O Count", value: oCount, wide: true, additionalData: { + id: "o-count-of-total", + value: additionalValue, + } })); }; -exports["default"] = ItemMostCommonTags; +exports["default"] = ItemOCount; /***/ }), -/***/ 92: +/***/ 740: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; @@ -215,22 +313,67 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -var helpers_1 = __webpack_require__(730); var DetailItem_1 = __importDefault(__webpack_require__(645)); var React = window.PluginApi.React; -var ItemContentSize = function (props) { - var _a = props.scenesQueryResult, duration = _a.duration, filesize = _a.filesize; - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "content-size", title: "Total Content", value: (0, helpers_1.createDuration)(duration), wide: true, additionalData: { - id: "content-filesize", - value: (0, helpers_1.createFilesize)(filesize), +var ItemScenesOrganized = function (props) { + var scenes = props.scenesQueryResult.scenes; + var totalScenes = scenes.length; + if (totalScenes < 1) + return null; + var organizedScenes = scenes.filter(function (sc) { return sc.organized; }).length; + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "scenes-organized", title: "Scenes Organized", value: Math.round((organizedScenes / totalScenes + Number.EPSILON) * 100) + "%", wide: true, additionalData: { + id: "scenes-organized-number", + value: organizedScenes + " of " + totalScenes, } })); }; -exports["default"] = ItemContentSize; +exports["default"] = ItemScenesOrganized; + + +/***/ }), + +/***/ 222: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +"use strict"; + +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +var DetailItem_1 = __importDefault(__webpack_require__(645)); +var PluginApi = window.PluginApi; +var React = PluginApi.React; +var ItemScenesTimespan = function (_a) { + var _b, _c; + var collapsed = _a.collapsed, scenesQueryResult = _a.scenesQueryResult; + // Wait for PluginApi components to load before rendering. + var componentsLoading = PluginApi.hooks.useLoadComponents([ + PluginApi.loadableComponents.SceneCard, + ]); + if (componentsLoading) + return null; + var _d = PluginApi.components, HoverPopover = _d.HoverPopover, SceneCard = _d.SceneCard; + // Filter out scenes with no date + var scenes = scenesQueryResult.scenes; + var datedScenes = scenes.filter(function (sc) { return typeof sc.date !== "undefined"; }); + // Require a minimum of two scenes to render this item. + if (datedScenes.length < 2) + return null; + var earliestScene = datedScenes[0]; + var latestScene = datedScenes[datedScenes.length - 1]; + return (React.createElement(DetailItem_1.default, { collapsed: collapsed, id: "scenes-timespan", title: "Scenes Timespan", value: React.createElement("div", { className: "inner-wrapper" }, + React.createElement(HoverPopover, { placement: "bottom", content: React.createElement(SceneCard, { scene: earliestScene, compact: true }), leaveDelay: 100 }, + React.createElement("span", { className: "hoverable" }, (_b = earliestScene.date) === null || _b === void 0 ? void 0 : _b.split("-").join("/"))), + React.createElement("span", { className: "separator" }, "\u2013"), + React.createElement(HoverPopover, { placement: "bottom", content: React.createElement(SceneCard, { scene: latestScene, compact: true }), leaveDelay: 100 }, + React.createElement("span", { className: "hoverable" }, (_c = latestScene.date) === null || _c === void 0 ? void 0 : _c.split("-").join("/")))), wide: true })); +}; +exports["default"] = ItemScenesTimespan; /***/ }), -/***/ 682: +/***/ 291: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; @@ -246,15 +389,25 @@ var __rest = (this && this.__rest) || function (s, e) { } return t; }; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +var helpers_1 = __webpack_require__(730); var DetailItem_1 = __importDefault(__webpack_require__(645)); var React = window.PluginApi.React; -var ItemMostFeaturedOn = function (_a) { +var ItemTopStudio = function (_a) { var performer = _a.performer, props = __rest(_a, ["performer"]); - var mostFeaturedNetworkOn = props.pluginConfig.mostFeaturedNetworkOn; + var _b = props.pluginConfig, maximumTops = _b.maximumTops, minimumAppearances = _b.minimumAppearances, topNetworkOn = _b.topNetworkOn; var scenes = props.scenesQueryResult.scenes; if (scenes.length === 0) return null; @@ -279,13 +432,41 @@ var ItemMostFeaturedOn = function (_a) { }); // Sort count from highest to lowest number of scenes. var sortHighToLow = function (a, b) { return b.count - a.count; }; - studios.sort(sortHighToLow); - var topStudio = studios[0]; - var additionalDataValue = topStudio.count + (topStudio.count === 1 ? " scene" : " scenes"); - var linkToStudio = "/studios/".concat(topStudio.data.id, "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"").concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")"); + var sortedStudios = studios.sort(sortHighToLow); + // If there are no studios or the top studio's count is less than the minimum + // required, don't return a component. + if (sortedStudios.length === 0 || sortedStudios[0].count < minimumAppearances) + return null; + var highestValue = sortedStudios[0].count; + var topStudios = sortedStudios + .filter(function (st) { return st.count === highestValue; }) + .sort(function (a, b) { return a.data.name.localeCompare(b.data.name, "en"); }); + var topStudioData = topStudios.map(function (st) { + var scenesLink = "/studios/".concat(st.data.id, "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"").concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")"); + return { scenesLink: scenesLink, name: st.data.name }; + }); + var scenesText = highestValue + (highestValue === 1 ? " scene" : " scenes"); + var maxLinks = topStudioData.length < maximumTops ? topStudioData.length : maximumTops; + var links = []; + for (var i = 0; i < maxLinks; i++) { + links.push(React.createElement("a", { href: topStudioData[i].scenesLink }, topStudioData[i].name)); + if (i !== maxLinks - 1) + links.push(" / "); + } + if (topStudioData.length > maxLinks) { + var names = topStudioData.map(function (st) { return st.name; }); + var title = (0, helpers_1.createOverflowText)(names, maxLinks); + links.push(React.createElement(React.Fragment, null, + " ", + React.createElement("span", { className: "top-meta-overflow hoverable", title: title }, + "and ", + topStudioData.length - maxLinks, + " more"))); + } + var value = React.createElement.apply(React, __spreadArray([React.Fragment, null], links, false)); /* ------------------------------ Network data ------------------------------ */ - var itemMostFeaturedNetwork = null; - if (mostFeaturedNetworkOn) { + var itemTopNetworkOn = null; + if (topNetworkOn) { // Create an array of network data from all scenes var networks_1 = []; // If the scene studio has a network, use it. Otherwise treat the studio as @@ -305,7 +486,7 @@ var ItemMostFeaturedOn = function (_a) { * can be streamlined to use that data. */ var network = getNetworkData_1((_a = sc.studio) === null || _a === void 0 ? void 0 : _a.id); - // If network is undefined or null, skip + // If network is undefined or null, or exists in the top studio list, skip it if (!network) return; // Check if the scene network already exists in the array @@ -321,32 +502,64 @@ var ItemMostFeaturedOn = function (_a) { }); if (networks_1.length > 0) { // Sort count from highest to lowest number of scenes. - networks_1.sort(sortHighToLow); - var topNetwork = networks_1[0]; - var additionalNetworkDataValue = topNetwork.count + (topNetwork.count === 1 ? " scene" : " scenes"); - var linkToNetwork = "/studios/".concat(topNetwork.data.id, "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"").concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")"); - // Don't return the network unless it is different from the top studio. - if (topNetwork.data.id !== topStudio.data.id) { - itemMostFeaturedNetwork = (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "most-featured-network", title: "Most Featured On (Network)", value: React.createElement("a", { href: linkToNetwork }, topNetwork.data.name), wide: true, additionalData: { - id: "featured-network-scenes", - value: additionalNetworkDataValue, + var sortedNetworks = networks_1.sort(sortHighToLow); + // If there are no networks or the top networks's count is less than the + // minimum required, don't return a component. + if (sortedNetworks.length === 0 || + sortedNetworks[0].count < minimumAppearances) + return null; + var highestNwValue_1 = sortedNetworks[0].count; + var topNetworks = sortedNetworks + .filter(function (st) { return st.count === highestNwValue_1; }) + .sort(function (a, b) { return a.data.name.localeCompare(b.data.name, "en"); }); + var topNetworkData = topNetworks.map(function (st) { + var scenesLink = "/studios/".concat(st.data.id, "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"").concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")"); + return { scenesLink: scenesLink, name: st.data.name }; + }); + var nwScenesText = highestNwValue_1 + (highestNwValue_1 === 1 ? " scene" : " scenes"); + var nwMaxLinks = topNetworkData.length < maximumTops + ? topNetworkData.length + : maximumTops; + var nwLinks = []; + for (var i = 0; i < nwMaxLinks; i++) { + nwLinks.push(React.createElement("a", { href: topNetworkData[i].scenesLink }, topNetworkData[i].name)); + if (i !== nwMaxLinks - 1) + nwLinks.push(" / "); + } + if (topNetworkData.length > nwMaxLinks) { + var names = topNetworkData.map(function (nw) { return nw.name; }); + var title = (0, helpers_1.createOverflowText)(names, maxLinks); + nwLinks.push(React.createElement(React.Fragment, null, + " ", + React.createElement("span", { className: "top-meta-overflow hoverable", title: title }, + "and ", + topNetworkData.length - maxLinks, + " more"))); + } + var nwValue = React.createElement.apply(React, __spreadArray([React.Fragment, null], nwLinks, false)); + // Don't return the network unless it is different from the top studio and + // its count is at least the minimum required. + if (!(highestNwValue_1 < minimumAppearances)) { + itemTopNetworkOn = (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "top-network", title: "Top Network" + (topNetworkData.length > 1 ? "s" : ""), value: nwValue, wide: true, additionalData: { + id: "top-network-scenes", + value: nwScenesText, } })); } } } return (React.createElement(React.Fragment, null, - React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "most-featured-on", title: "Most Featured On", value: React.createElement("a", { href: linkToStudio }, topStudio.data.name), wide: true, additionalData: { - id: "featured-studio-scenes", - value: additionalDataValue, + React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "top-studio", title: "Top Studio" + (topStudioData.length > 1 ? "s" : ""), value: value, wide: true, additionalData: { + id: "top-studio-scenes", + value: scenesText, } }), - itemMostFeaturedNetwork)); + itemTopNetworkOn)); }; -exports["default"] = ItemMostFeaturedOn; +exports["default"] = ItemTopStudio; /***/ }), -/***/ 231: +/***/ 182: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; @@ -366,116 +579,59 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -var helpers_1 = __webpack_require__(730); -var constants_1 = __webpack_require__(333); var DetailItem_1 = __importDefault(__webpack_require__(645)); +var TagItem_1 = __importDefault(__webpack_require__(718)); var React = window.PluginApi.React; -var makePerformerScenesUrl = window.PluginApi.utils.NavUtils.makePerformerScenesUrl; -var ItemMostWorkedWith = function (_a) { +var ItemTopTags = function (_a) { var performer = _a.performer, props = __rest(_a, ["performer"]); - var mostWorkedWithGendered = props.pluginConfig.mostWorkedWithGendered; - // Create an array of performer data from all scenes - var partners = []; + var _b = props.pluginConfig, topTagsCount = _b.topTagsCount, topTagsOn = _b.topTagsOn; + // Do not render the item if the user has turned it off in the config. + if (!topTagsOn) + return null; + // Create an array of tag data from all scenes + var tags = []; // Check each scene props.scenesQueryResult.scenes.forEach(function (sc) { - // Check each performer in the scene - sc.performers.forEach(function (pf) { - // Check this is not the featured performer - if (pf.id !== performer.id) { - // Check if the performer already exists in the array - var perfomersIndex = partners.findIndex(function (ptnr) { return ptnr.data.id === pf.id; }); - if (perfomersIndex !== -1) { - // Increase the performer count - partners[perfomersIndex].count++; - } - else { - // Add the performer to the array - partners.push({ - count: 1, - data: pf, - }); - } + // Check each tag in the scene + sc.tags.forEach(function (tag) { + // Check if the tag already exists in the array + var tagIndex = tags.findIndex(function (t) { return t.data.id === tag.id; }); + if (tagIndex !== -1) { + // Increase the tag count + tags[tagIndex].count++; + } + else { + // Add the tag to the array + tags.push({ + count: 1, + data: tag, + }); } }); }); // Sort count from highest to lowest - partners.sort(function (a, b) { return b.count - a.count; }); - if (!partners) + tags.sort(function (a, b) { return b.count - a.count; }); + // Return null if there are no tags + if (!tags.length) return null; - if (mostWorkedWithGendered) { - return constants_1.GENDERS.map(function (g) { - var topPartnerIndex = partners.findIndex(function (p) { return p.data.gender === g; }); - // If there is no partner for the current gender, don't return a - // component. - if (topPartnerIndex === -1) - return null; - var topPartner = partners[topPartnerIndex]; - var scenesText = topPartner.count + " " + (topPartner.count === 1 ? "scene" : "scenes"); - var scenesLink = makePerformerScenesUrl(performer, { - id: topPartner.data.id, - label: topPartner.data.name, - }); - var genderWord = " (" + (0, helpers_1.getGenderFromEnum)(g) + ")"; - var id = genderWord.toLowerCase().split(" ").join("-"); - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "most-worked-with-" + id, title: "Most Worked With " + genderWord, value: React.createElement("a", { href: scenesLink }, topPartner.data.name), wide: true, additionalData: { - id: "scene-count", - value: scenesText, - } })); - }); + // Return the tags with the highest overall count, up to the tagCount + var maxTags = tags.length < topTagsCount ? tags.length : topTagsCount; + var value = []; + for (var i = 0; i < maxTags; i++) { + var tagCount = tags[i].count; + var tagData = tags[i].data; + var link = "/scenes?c=(\"type\":\"performers\",\"value\":(\"items\":%5B(\"id\":\"".concat(performer.id, "\",\"label\":\"").concat(encodeURIComponent(performer.name), "\")%5D,\"excluded\":%5B%5D),\"modifier\":\"INCLUDES\")&c=(\"type\":\"tags\",\"value\":(\"items\":%5B(\"id\":\"").concat(tagData.id, "\",\"label\":\"").concat(encodeURIComponent(tagData.name), "\")%5D,\"excluded\":%5B%5D,\"depth\":0),\"modifier\":\"INCLUDES\")"); + value.push(React.createElement(TagItem_1.default, { link: link, title: "".concat(tagData.name, " (").concat(tagCount, " ").concat(tagCount === 1 ? "scene" : "scenes", ")") })); } - var topPartner = partners[0]; - var scenesText = topPartner.count + " " + (topPartner.count === 1 ? "scene" : "scenes"); - var scenesLink = makePerformerScenesUrl(performer, { - id: topPartner.data.id, - label: topPartner.data.name, - }); - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "most-worked-with", title: "Most Worked With", value: React.createElement("a", { href: scenesLink }, topPartner.data.name), wide: true, additionalData: { - id: "scene-count", - value: scenesText, - } })); + var title = "Top Tag" + (topTagsCount === 1 ? "" : "s"); + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "top-tags", title: title, value: value, wide: true })); }; -exports["default"] = ItemMostWorkedWith; +exports["default"] = ItemTopTags; /***/ }), -/***/ 806: -/***/ (function(__unused_webpack_module, exports, __webpack_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -var DetailItem_1 = __importDefault(__webpack_require__(645)); -var React = window.PluginApi.React; -var ItemOCount = function (props) { - var total_o_count = props.statsQueryResult.total_o_count; - // Don't return a component if there is no O count across all performers - if (total_o_count === 0) - return null; - var oCount = 0; - props.scenesQueryResult.scenes.forEach(function (sc) { - if (typeof sc.o_counter !== "undefined") - oCount += sc.o_counter || 0; - }); - // Don't return a component if there is no O count for this performer - if (oCount === 0) - return null; - var percentage = Math.round((oCount / total_o_count + Number.EPSILON) * 10000) / 100; - var additionalValue = "".concat(percentage, "% of ").concat(total_o_count, " total"); - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "o-count", title: "O Count", value: oCount, wide: true, additionalData: { - id: "o-count-of-total", - value: additionalValue, - } })); -}; -exports["default"] = ItemOCount; - - -/***/ }), - -/***/ 740: +/***/ 959: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; @@ -484,68 +640,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); +var helpers_1 = __webpack_require__(730); var DetailItem_1 = __importDefault(__webpack_require__(645)); var React = window.PluginApi.React; -var ItemScenesOrganized = function (props) { - var scenes = props.scenesQueryResult.scenes; - var totalScenes = scenes.length; - if (totalScenes < 1) - return null; - var organizedScenes = scenes.filter(function (sc) { return sc.organized; }).length; - return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "scenes-organized", title: "Scenes Organized", value: Math.round((organizedScenes / totalScenes + Number.EPSILON) * 100) + "%", wide: true, additionalData: { - id: "scenes-organized-number", - value: organizedScenes + " of " + totalScenes, +var ItemTotalContent = function (props) { + var _a = props.scenesQueryResult, duration = _a.duration, filesize = _a.filesize; + return (React.createElement(DetailItem_1.default, { collapsed: props.collapsed, id: "total-content", title: "Total Content", value: (0, helpers_1.createDuration)(duration), wide: true, additionalData: { + id: "total-filesize", + value: (0, helpers_1.createFilesize)(filesize), } })); }; -exports["default"] = ItemScenesOrganized; - - -/***/ }), - -/***/ 222: -/***/ (function(__unused_webpack_module, exports, __webpack_require__) { - -"use strict"; - -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -var DetailItem_1 = __importDefault(__webpack_require__(645)); -var PluginApi = window.PluginApi; -var React = PluginApi.React; -var ItemScenesTimespan = function (_a) { - var _b, _c; - var collapsed = _a.collapsed, scenesQueryResult = _a.scenesQueryResult; - // Wait for PluginApi components to load before rendering. - var componentsLoading = PluginApi.hooks.useLoadComponents([ - PluginApi.loadableComponents.SceneCard, - ]); - if (componentsLoading) - return null; - var _d = PluginApi.components, HoverPopover = _d.HoverPopover, SceneCard = _d.SceneCard; - // Filter out scenes with no date - var scenes = scenesQueryResult.scenes; - var datedScenes = scenes.filter(function (sc) { return typeof sc.date !== "undefined"; }); - // Require a minimum of two scenes to render this item. - if (datedScenes.length < 2) - return null; - var earliestScene = datedScenes[0]; - var latestScene = datedScenes[datedScenes.length - 1]; - return (React.createElement(React.Fragment, null, - React.createElement(DetailItem_1.default, { collapsed: collapsed, id: "scenes-timespan", title: "Scenes Timespan", value: React.createElement("div", { className: "inner-wrapper" }, - React.createElement(HoverPopover, { placement: "bottom", content: React.createElement(SceneCard, { scene: earliestScene, compact: true }), leaveDelay: 100 }, - React.createElement("span", { className: "hoverable" }, (_b = earliestScene.date) === null || _b === void 0 ? void 0 : _b.split("-").join("/"))), - React.createElement("span", { className: "separator" }, "\u2013"), - React.createElement(HoverPopover, { placement: "bottom", content: React.createElement(SceneCard, { scene: latestScene, compact: true }), leaveDelay: 100 }, - React.createElement("span", { className: "hoverable" }, (_c = latestScene.date) === null || _c === void 0 ? void 0 : _c.split("-").join("/")))), wide: true }))); -}; -exports["default"] = ItemScenesTimespan; +exports["default"] = ItemTotalContent; /***/ }), -/***/ 197: +/***/ 632: /***/ (function(__unused_webpack_module, exports, __webpack_require__) { "use strict"; @@ -557,8 +667,8 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); var helpers_1 = __webpack_require__(730); var DetailItem_1 = __importDefault(__webpack_require__(645)); var React = window.PluginApi.React; -/** "Scene Play Count" item component. */ -var ItemWatchedFor = function (_a) { +/** "Total Play Duration" item component. */ +var ItemTotalPlayDuration = function (_a) { var collapsed = _a.collapsed, scenesQueryResult = _a.scenesQueryResult; var playCount = 0; var playDuration = 0; @@ -567,12 +677,12 @@ var ItemWatchedFor = function (_a) { playCount += (scene === null || scene === void 0 ? void 0 : scene.play_count) || 0; playDuration += scene.play_duration || 0; } - return (React.createElement(DetailItem_1.default, { collapsed: collapsed, id: "watched-for", title: "Scenes Watched For", value: (0, helpers_1.createDuration)(playDuration), wide: true, additionalData: { + return (React.createElement(DetailItem_1.default, { collapsed: collapsed, id: "total-play-duration", title: "Total Play Duration", value: (0, helpers_1.createDuration)(playDuration), wide: true, additionalData: { id: "total-play-count", value: "".concat(playCount, " ").concat(playCount === 1 ? "play" : "plays"), } })); }; -exports["default"] = ItemWatchedFor; +exports["default"] = ItemTotalPlayDuration; /***/ }), @@ -606,7 +716,7 @@ exports["default"] = TagItem; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getGenderFromEnum = exports.createFilesize = exports.createDuration = void 0; +exports.createOverflowText = exports.getGenderFromEnum = exports.createFilesize = exports.createDuration = void 0; /** Converts the given seconds into a uniform string showing an amount of time. * */ var createDuration = function (seconds) { @@ -614,7 +724,7 @@ var createDuration = function (seconds) { var inHours = Math.floor(inMinutes / 60); var inDays = Math.floor(inHours / 24); var output = ""; - var totalHours = inHours % 60; + var totalHours = inHours % 24; var totalMinutes = inMinutes % 60; var totalSeconds = seconds % 60; if (inDays > 0) @@ -670,6 +780,27 @@ exports.getGenderFromEnum = getGenderFromEnum; var roundToTwo = function (number) { return Math.round((number + Number.EPSILON) * 100) / 100; }; +/** Creates a string list of names from an array of data which overflowa an item + * */ +var createOverflowText = function (arr, overflowAt) { + var overflow = ""; + for (var i = overflowAt; i < arr.length; i++) { + overflow += arr[i]; + var isOneBeforeLast = i === arr.length - 2; + var isAnyBeforeLast = i < arr.length - 1; + if (arr.length - overflowAt === 2) { + overflow += isOneBeforeLast ? " and " : ""; + } + else { + if (isAnyBeforeLast) + overflow += ", "; + if (isOneBeforeLast) + overflow += "and "; + } + } + return overflow; +}; +exports.createOverflowText = createOverflowText; /***/ }), @@ -685,14 +816,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", ({ value: true })); var DetailGroup_1 = __importDefault(__webpack_require__(687)); var ItemAverageRating_1 = __importDefault(__webpack_require__(138)); -var ItemContentSize_1 = __importDefault(__webpack_require__(92)); -var ItemCommonTags_1 = __importDefault(__webpack_require__(790)); -var ItemMostFeaturedOn_1 = __importDefault(__webpack_require__(682)); -var ItemMostWorkedWith_1 = __importDefault(__webpack_require__(231)); +var ItemAppearsMostWith_1 = __importDefault(__webpack_require__(327)); var ItemOCount_1 = __importDefault(__webpack_require__(806)); var ItemScenesOrganized_1 = __importDefault(__webpack_require__(740)); var ItemScenesTimespan_1 = __importDefault(__webpack_require__(222)); -var ItemWatchedFor_1 = __importDefault(__webpack_require__(197)); +var ItemTopStudio_1 = __importDefault(__webpack_require__(291)); +var ItemTopTags_1 = __importDefault(__webpack_require__(182)); +var ItemTotalContent_1 = __importDefault(__webpack_require__(959)); +var ItemTotalPlayDuration_1 = __importDefault(__webpack_require__(632)); __webpack_require__(680); var PluginApi = window.PluginApi; var GQL = PluginApi.GQL, React = PluginApi.React; @@ -704,7 +835,7 @@ PluginApi.patch.after("PerformerDetailsPanel.DetailGroup", function (_a) { var performerID = performer.id; var qScenes = GQL.useFindScenesQuery({ variables: { - filter: { per_page: -1 }, + filter: { per_page: -1, sort: "date" }, scene_filter: { performers: { modifier: "INCLUDES" /* CriterionModifier.Includes */, @@ -740,12 +871,14 @@ PluginApi.patch.after("PerformerDetailsPanel.DetailGroup", function (_a) { var userConfig = configurationQueryResult.plugins.PerformerDetailsExtended; // Compile the user's config with config defaults var pluginConfig = { - // For mostCommonTagsCount, set to 3 if the value is undefined or 0. - mostCommonTagsCount: (userConfig === null || userConfig === void 0 ? void 0 : userConfig.mostCommonTagsCount) || 3, - mostCommonTagsOn: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.mostCommonTagsOn, true), - mostFeaturedNetworkOn: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.mostFeaturedNetworkOn, true), - mostWorkedWithGendered: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.mostCommonTagsOn, true), + // For topTagsCount, set to 3 if the value is undefined or 0. + appearsMostWithGendered: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.appearsMostWithGendered, true), + maximumTops: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.maximumTops, 3), + minimumAppearances: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.minimumAppearances, 2), showWhenCollapsed: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.showWhenCollapsed, showAllDetails || false), + topNetworkOn: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.topNetworkOn, true), + topTagsCount: (userConfig === null || userConfig === void 0 ? void 0 : userConfig.topTagsCount) || 3, + topTagsOn: getConfigProp(userConfig === null || userConfig === void 0 ? void 0 : userConfig.topTagsOn, true), }; var showDetails = !collapsed || pluginConfig.showWhenCollapsed; if (showDetails) { @@ -754,12 +887,12 @@ PluginApi.patch.after("PerformerDetailsPanel.DetailGroup", function (_a) { React.createElement(DetailGroup_1.default, null, children), React.createElement(DetailGroup_1.default, { id: "pde__entities", className: "performer-details-extended" }, React.createElement(ItemAverageRating_1.default, { collapsed: collapsed, configurationQueryResult: configurationQueryResult, performer: performer, scenesQueryResult: scenesQueryResult }), - React.createElement(ItemMostWorkedWith_1.default, { collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult }), - React.createElement(ItemMostFeaturedOn_1.default, { allStudiosQueryResult: allStudiosQueryResult, collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult }), - React.createElement(ItemCommonTags_1.default, { collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult })), + React.createElement(ItemAppearsMostWith_1.default, { collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult }), + React.createElement(ItemTopStudio_1.default, { allStudiosQueryResult: allStudiosQueryResult, collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult }), + React.createElement(ItemTopTags_1.default, { collapsed: collapsed, performer: performer, pluginConfig: pluginConfig, scenesQueryResult: scenesQueryResult })), React.createElement(DetailGroup_1.default, { id: "pde__numbers", className: "performer-details-extended" }, - React.createElement(ItemContentSize_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), - React.createElement(ItemWatchedFor_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), + React.createElement(ItemTotalContent_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), + React.createElement(ItemTotalPlayDuration_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), React.createElement(ItemScenesTimespan_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), React.createElement(ItemScenesOrganized_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult }), React.createElement(ItemOCount_1.default, { collapsed: collapsed, scenesQueryResult: scenesQueryResult, statsQueryResult: statsQueryResult }))), diff --git a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.yml b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.yml index 3912e40..afab30d 100644 --- a/plugins/PerformerDetailsExtended/PerformerDetailsExtended.yml +++ b/plugins/PerformerDetailsExtended/PerformerDetailsExtended.yml @@ -1,30 +1,38 @@ name: Performer Details Extended description: Displays metadata about frequent scene partners, most prominent tags, etc. in your library, on performer pages. url: https://github.com/Valkyr-JS/performer-details-extended -version: 0.2.1 +version: 0.3.0 ui: javascript: - PerformerDetailsExtended.js css: - PerformerDetailsExtended.css settings: - mostCommonTagsCount: - displayName: Number of Common Tags - description: The number of tags to show under "Most Common Tags". Default is 3. - type: NUMBER - mostCommonTagsOn: - displayName: Show "Most Common Tags" item - description: Toggle displaying the "Most Common Tags" item on or off. Default is on. - type: BOOLEAN - mostFeaturedNetworkOn: - displayName: Show "Most Featured On (Network)" item - description: Toggle displaying the "Most Featured On (Network)" item on or off. Default is on. - type: BOOLEAN - mostWorkedWithGendered: - displayName: '"Most Worked With" by gender' - description: Show a "Most Worked With" metadata item for each gender that the performer has worked with. If false, only one item showing the most worked with performer overall will be displayed. + appearsMostWithGendered: + displayName: '"Appears Most With" by gender' + description: Show an "Appears Most With" metadata item for each gender that the performer has worked with. If false, only one item showing the performer they work with most overall will be displayed. type: BOOLEAN + maximumTops: + displayName: Top appearances before overflow listed + description: The maximum number of results that are displayed as a performer's top studios/networks/partners before they overflow to a hover popover. The default value is 3. + type: NUMBER + minimumAppearances: + displayName: Minimum appearances required + description: The minimum number of appearances a performer needs to have had with a partner/studio/network in order to show the data. The default value is 2. + type: NUMBER showWhenCollapsed: displayName: Show when collapsed description: When enabled, the plugin data will always be displayed, irrelevant of whether the performer details panel is collapsed or not. If disabled, it will follow the same settings as Interface > Detail Page > Show all details. type: BOOLEAN + topNetworkOn: + displayName: Show "Top Network" item + description: Toggle displaying the "Top Network" item on or off. Default is on. + type: BOOLEAN + topTagsCount: + displayName: Number of top tags + description: The number of tags to show under "Top Tags". The default value is 3. + type: NUMBER + topTagsOn: + displayName: Show "Top Tags" item + description: When enabled, the "Top Tags" item will be displayed. Default is on. + type: BOOLEAN