From fc8e718519cc1ddcb41b490e36d06c9322f86e96 Mon Sep 17 00:00:00 2001 From: HandyRandyx Date: Fri, 26 Jul 2024 09:24:13 -0300 Subject: [PATCH 01/22] [Hot Cards] Add utility files and update CDNs (#373) --- plugins/hotCards/hotCards.js | 10 +--- plugins/hotCards/hotCards.yml | 10 ++-- plugins/hotCards/utils/fetchInterceptor.js | 31 +++++++++++ plugins/hotCards/utils/helpers.js | 37 +++++++++++++ .../utils/registerPathChangeListener.js | 24 ++++++++ plugins/hotCards/utils/stashHandler.js | 55 +++++++++++++++++++ 6 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 plugins/hotCards/utils/fetchInterceptor.js create mode 100644 plugins/hotCards/utils/helpers.js create mode 100644 plugins/hotCards/utils/registerPathChangeListener.js create mode 100644 plugins/hotCards/utils/stashHandler.js diff --git a/plugins/hotCards/hotCards.js b/plugins/hotCards/hotCards.js index f7cf7bf7..19052bd2 100644 --- a/plugins/hotCards/hotCards.js +++ b/plugins/hotCards/hotCards.js @@ -579,7 +579,7 @@ gradientAnimation = "", filter = "" ) { - const opacity = getBackgroundOpacity(cardOptions.opacity); + const opacity = getFixedBackgroundOpacity(cardOptions.opacity); const fill = /true/i.test(cardOptions.fill); const gradientAnimationStr = gradientAnimation ? `animation: move ${gradientAnimation};` @@ -675,14 +675,6 @@ animate(); } - function getRandomInt(max) { - return Math.floor(Math.random() * max); - } - - function getBackgroundOpacity(opacity) { - return parseFloat((1 - opacity / 100).toFixed(1)); - } - function createCardStyle( hoverColor, hoverAnimation, diff --git a/plugins/hotCards/hotCards.yml b/plugins/hotCards/hotCards.yml index 1103a3fe..30d095e4 100644 --- a/plugins/hotCards/hotCards.yml +++ b/plugins/hotCards/hotCards.yml @@ -1,16 +1,16 @@ name: Hot Cards description: Adds custom styling to card elements that match a Tag ID or a Rating Threshold. -version: 1.1.6 +version: 1.1.7 url: https://github.com/stashapp/CommunityScripts/tree/main/plugins/hotCards # requires: CommunityScriptsUILibrary ui: requires: - CommunityScriptsUILibrary javascript: - - https://cdn.jsdelivr.net/gh/HandyRandyx/stash-plugins@main/utils/fetchInterceptor.js - - https://cdn.jsdelivr.net/gh/HandyRandyx/stash-plugins@main/utils/stashHandler.js - - https://cdn.jsdelivr.net/gh/HandyRandyx/stash-plugins@main/utils/registerPathChangeListener.js - - https://cdn.jsdelivr.net/gh/HandyRandyx/stash-plugins@main/utils/waitForClass.js + - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/fetchInterceptor.js + - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/stashHandler.js + - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/registerPathChangeListener.js + - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/helpers.js - hotCards.js css: - hotCards.css diff --git a/plugins/hotCards/utils/fetchInterceptor.js b/plugins/hotCards/utils/fetchInterceptor.js new file mode 100644 index 00000000..951da4d8 --- /dev/null +++ b/plugins/hotCards/utils/fetchInterceptor.js @@ -0,0 +1,31 @@ +(() => { + if (window.stashListener) return; + + const { fetch: originalFetch } = window; + const stashListener = new EventTarget(); + + window.fetch = async (...args) => { + let [resource, config] = args; + const response = await originalFetch(resource, config); + const contentType = response.headers.get("content-type"); + + if ( + typeof resource === "string" && + contentType && + contentType.indexOf("application/json") !== -1 && + resource.endsWith("/graphql") + ) { + try { + const data = await response.clone().json(); + stashListener.dispatchEvent( + new CustomEvent("response", { detail: data }) + ); + } catch (e) { + console.error("Error parsing JSON:", e); + } + } + return response; + }; + + window.stashListener = stashListener; +})(); diff --git a/plugins/hotCards/utils/helpers.js b/plugins/hotCards/utils/helpers.js new file mode 100644 index 00000000..5c7d5863 --- /dev/null +++ b/plugins/hotCards/utils/helpers.js @@ -0,0 +1,37 @@ +/** General */ + +function getRandomInt(max) { + return Math.floor(Math.random() * max); +} + +function getFixedBackgroundOpacity(opacity) { + return parseFloat((1 - opacity / 100).toFixed(1)); +} + +/** Elements */ + +function waitForClass(className, callback) { + const checkInterval = 100; // ms + const maxRetries = 30; // Timeout after 3 seconds + let retryCount = 0; + + const intervalId = setInterval(() => { + const elements = document.getElementsByClassName(className); + if (elements.length > 0) { + clearInterval(intervalId); + callback(); + } else if (retryCount >= maxRetries) { + clearInterval(intervalId); + console.info( + `Element with class ${className} not found within timeout period` + ); + } + retryCount++; + }, checkInterval); +} + +function waitForImageLoad(selector, callback) { + var imgEl = document.querySelector(selector); + if (imgEl?.complete) return callback(imgEl); + setTimeout(waitForImageLoad, 100, selector, callback); +} diff --git a/plugins/hotCards/utils/registerPathChangeListener.js b/plugins/hotCards/utils/registerPathChangeListener.js new file mode 100644 index 00000000..8afd6ee8 --- /dev/null +++ b/plugins/hotCards/utils/registerPathChangeListener.js @@ -0,0 +1,24 @@ +function registerPathChangeListener(pattern, callback) { + const regex = new RegExp(pattern); + + function checkURL() { + const currentPathName = window.location.pathname; + if (regex.test(currentPathName)) callback(); + } + + // Listen to popstate event for back/forward navigation + window.addEventListener("popstate", checkURL); + + // Hijack pushState and replaceState methods + ["pushState", "replaceState"].forEach((method) => { + const original = history[method]; + history[method] = function () { + const result = original.apply(this, arguments); + checkURL(); + return result; + }; + }); + + // Initial check + checkURL(); +} diff --git a/plugins/hotCards/utils/stashHandler.js b/plugins/hotCards/utils/stashHandler.js new file mode 100644 index 00000000..25cc801b --- /dev/null +++ b/plugins/hotCards/utils/stashHandler.js @@ -0,0 +1,55 @@ +const stash = { + galleries: {}, + images: {}, + movies: {}, + performers: {}, + scenes: {}, + studios: {}, +}; + +stashListener.addEventListener("response", (event) => { + const dataProcessors = { + galleries: processData("findGalleries", "galleries"), + images: processData("findImages", "images"), + movies: processData("findMovies", "movies"), + performers: processData("findPerformers", "performers"), + scenes: processData("findScenes", "scenes"), + studios: processData("findStudios", "studios"), + }; + + for (const key in dataProcessors) { + dataProcessors[key](event.detail); + } + + processOtherData(event.detail); +}); + +function processData(findKey, dataKey) { + return function (data) { + if (data.data[findKey]?.[dataKey]) { + for (const item of data.data[findKey][dataKey]) { + stash[dataKey][item.id] = item; + } + } + }; +} + +function processOtherData(data) { + const otherDataMappings = [ + { findKey: "findScene", key: "movies", nested: true }, + { findKey: "findScene", key: "galleries", nested: false }, + { findKey: "findScene", key: "performers", nested: false }, + { findKey: "findImage", key: "performers", nested: false }, + { findKey: "findGallery", key: "performers", nested: false }, + { findKey: "findGallery", key: "scenes", nested: false }, + ]; + + for (const mapping of otherDataMappings) { + if (data.data[mapping.findKey]?.[mapping.key]) { + for (const item of data.data[mapping.findKey][mapping.key]) { + const value = mapping.nested ? item[mapping.key.slice(0, -1)] : item; + stash[mapping.key][value.id] = value; + } + } + } +} From 62cb8ecb3fd4558a64bd4a82dc1f843448aa6e47 Mon Sep 17 00:00:00 2001 From: HandyRandyx Date: Tue, 30 Jul 2024 00:27:29 -0300 Subject: [PATCH 02/22] [Hot Cards] Replace CDNs with local files (#374) --- plugins/hotCards/hotCards.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/hotCards/hotCards.yml b/plugins/hotCards/hotCards.yml index 30d095e4..9e96262b 100644 --- a/plugins/hotCards/hotCards.yml +++ b/plugins/hotCards/hotCards.yml @@ -7,10 +7,10 @@ ui: requires: - CommunityScriptsUILibrary javascript: - - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/fetchInterceptor.js - - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/stashHandler.js - - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/registerPathChangeListener.js - - https://cdn.jsdelivr.net/gh/stashapp/CommunityScripts@main/plugins/hotCards/utils/helpers.js + - utils/fetchInterceptor.js + - utils/stashHandler.js + - utils/registerPathChangeListener.js + - utils/helpers.js - hotCards.js css: - hotCards.css From a5df45ba48a8393e3dd6e4bee49df4884ea0dc6a Mon Sep 17 00:00:00 2001 From: HandyRandyx Date: Tue, 30 Jul 2024 00:32:09 -0300 Subject: [PATCH 03/22] [Hot Cards] Fix Holo style to work with any type of card (#376) --- plugins/hotCards/README.md | 92 ++++++++++++------------- plugins/hotCards/assets/holo.png | Bin 195019 -> 347445 bytes plugins/hotCards/hotCards.css | 6 +- plugins/hotCards/hotCards.js | 114 ++++++++++++++++++++++--------- plugins/hotCards/hotCards.yml | 2 +- 5 files changed, 130 insertions(+), 84 deletions(-) diff --git a/plugins/hotCards/README.md b/plugins/hotCards/README.md index efe9e451..61a3f66f 100644 --- a/plugins/hotCards/README.md +++ b/plugins/hotCards/README.md @@ -25,14 +25,14 @@ After installation, you can configure the plugin to suit your needs. Set a desir _[criterion]\_[value]\_[style]\_[gradient-opts]\_[hover-opts]\_[card-opts]_ -| Parameter | Description | Details | -| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `` | Defines the basis for applying styles. Use `t` for tag-based criteria, `r` for rating-based criteria, or `d` to disable. | If left empty, it will default to the global _Tag ID_ or _Rating Threshold_ as configured. If both options are enabled and unspecified, ~~the Tag ID will be used by default~~ both criteria will be used. | -| `` | Specifies the exact value for the Tag ID or Rating Threshold to be used.

_Multiple values can be specified using a comma-separated list and slashes to delimit each set of criteria:_ `,.../,.../...` or `//...` | Defaults to the global _Tag ID_ or _Rating Threshold_ value.

**Important**: When dealing with tenths precision (e.g. 4.8, 3.25), map these to the 6-100 range and set the _Rating Threshold_ in that range. Thus, 4.8 would be 96, 3.25 would be 65, and so on.

See [this additional information](#regarding-multiple-values) on multiple values. | -| `