Added ThemeSwitch Plugin

This commit is contained in:
Elkorol 2024-02-16 21:07:40 +00:00
parent e6ec1b007c
commit 102cfd0512
6 changed files with 22293 additions and 0 deletions

View File

@ -0,0 +1,57 @@
# Theme Switch
## Information about plugin
Adds a button that shows a menu, allowing you to change CSS Themes and apply various CSS snippets for Stash App. The CSS isn't made by myself and in the CSS files authors where known are referenced.
Only the CSS of one theme can be applied at a time, and any amount of snippets can be applied.
Clicking on any radio/checkbox to apply the CSS, saves the CSS content of the Stylesheet in the browsers local storage, with an applied flag, eitehr true or false which allows the script upon page navigation and refresh to apply previously selected stylesheets
Under Change Order of Menu Bar, you can drag the elements to reorder the main menu and upon dropping it will generate the required CSS. It should work on touchscreens also, tested on Chrome IOS.
![Theme Plugin 1](https://github.com/elkorol/Stash-App-Theme-Switch-Plugin/blob/193f54fce3914991440027c4b98fd30aa9402d29/images/1.png)
![Theme Plugin 2](https://github.com/elkorol/Stash-App-Theme-Switch-Plugin/blob/193f54fce3914991440027c4b98fd30aa9402d29/images/2.png)
## Other Requirements
## Usage
1. Copy repository into Stash plugins folder.
2. Reload plugins from settings and enable
## Credit
The CSS code used is provided by [Stash Community Themes](https://docs.stashapp.cc/user-interface-ui/themes) and [Stash Community Custom CSS Snippets](https://docs.stashapp.cc/user-interface-ui/custom-css-snippets).
## Adding your own CSS
To add your own CSS open the themeSwitchCSS.js file. CSS is defined in const variables. They are ordered in categories. You can make your own categories too. Just define a variable to hold your css and put the css inbetween backticks ``.
Then look for the variable themeSwitchCSS at the bottom of the file. If adding to an existing category to just add a new number at the end. Or if you are reordering the CSS or themes. Be sure to update the numbering and there is no duplicate numbers within a theme or some won't render. It's stuctured like but can be seen better within the file itself.
const themeSwitchCSS = {
Theme: {
1: {
displayName: "Default",
styles: null,
key: "themeSwitchPlugin-theme-default",
version: null,
},
2: {
displayName: "Black Hole",
styles: blackHole,
key: "themeSwitchPlugin-theme-blackHole",
version: "2.0",
}
YourCategoryHere: {
1: {
displayName: "Your CSS Name Here",
styles: Your-CSS-Variable-Name-Here,
key: "themeSwitchPlugin-YourCategoryHere-AnIDforYourCSS",
version: VersionNumberOrNull,
}
}
}
}

View File

@ -0,0 +1,12 @@
name: Theme Switch
description: Theme and CSS script manager located in main menu bar top right.
url:
version: 2.1
ui:
requires:
- stashUserscriptLibrary
javascript:
- themeSwitchMain.js
- themeSwitchCSS.js
css:
- themeSwtichDefault.css

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,684 @@
(function () {
const svgChevDN =
'<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-down" class="svg-inline--fa fa-chevron-down fa-icon collapse-icon fa-fw" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M201.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 338.7 54.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"></path></svg>';
const svgChevUP =
'<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="chevron-right" class="svg-inline--fa fa-chevron-right fa-icon collapse-icon fa-fw" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"></path></svg>';
const svgBTN =
'<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 102 123" width="20" height="20"><title>paint-icon-svg</title><style> .s0 { fill: #fafafa } .s1 { fill: #efaa3a } .s2 { fill: #25a700 } .s3 { fill: #e22952 } .s4 { fill: #42a3cc } </style><path id="Layer" class="s0" d="m42.6 114.5q3 0.4 6.1 0.4 3 0 6-0.4 3-0.4 6-1.2 2.9-0.8 5.7-1.9 1.1-0.5 2.2-1 1.1-0.5 2.1-1 1.1-0.6 2.1-1.2 1-0.6 2-1.2l0.7 6.9q-0.8 0.5-1.6 0.9-0.9 0.4-1.7 0.8-0.8 0.5-1.7 0.8-0.9 0.4-1.7 0.8-3.2 1.3-6.5 2.2-3.4 0.9-6.8 1.3-3.4 0.5-6.9 0.5-3.4-0.1-6.8-0.5-3.7-0.5-7.2-1.7-3.6-1.1-6.9-2.8-3.3-1.6-6.3-3.8-3-2.2-5.6-4.9-2.8-2.8-5.1-6-2.3-3.1-4.2-6.6-1.8-3.4-3.2-7.1-1.3-3.7-2.1-7.6-1-4.7-1.2-9.5-0.1-4.8 0.6-9.5 0.8-4.7 2.3-9.3 1.6-4.5 4-8.7 2.3-4.2 5.4-7.9 3.1-3.8 6.8-6.9 3.7-3.1 7.9-5.6 4.1-2.4 8.7-4 1.5-0.6 3-1 1.5-0.4 3.1-0.7 1.6-0.2 3.2-0.2 1.5-0.1 3.1 0.1 1.4 0.2 2.7 0.6 1.3 0.4 2.6 1.1 1.2 0.7 2.2 1.6 1 1 1.9 2.1 0 0 0.1 0.1 0 0.1 0 0.2 0.1 0 0.1 0.1 0.1 0.1 0.1 0.2c2.9 6.6-0.6 10.8-4 14.8-2 2.3-3.8 4.5-2.7 6.6 0.4 0.9 1.6 1.5 3.2 2q1 0.2 2 0.3 1 0.1 2 0.1 1 0 2 0 1-0.1 2-0.3c2.5-0.4 5.2-1 7.8-1.5 1.5-0.4 3-0.7 4.5-1l-2 6.8-1.3 0.3c-2.5 0.5-5.1 1.1-8.1 1.6q-1.3 0.2-2.6 0.3-1.3 0.1-2.6 0.1-1.3 0-2.7-0.2-1.3-0.2-2.6-0.5c-3.3-0.8-5.9-2.6-7.2-5.1-3-5.9 0.1-9.6 3.5-13.6 2-2.4 4.2-5 3.2-7.9q-0.5-0.6-1-1-0.6-0.5-1.2-0.8-0.7-0.3-1.4-0.5-0.6-0.2-1.4-0.3-1.2-0.1-2.4-0.1-1.2 0.1-2.4 0.2-1.2 0.2-2.3 0.5-1.2 0.3-2.3 0.8-4 1.5-7.7 3.6-3.7 2.2-7 4.9-3.2 2.8-6 6.1-2.7 3.3-4.8 7-2 3.7-3.4 7.7-1.4 4-2 8.2-0.7 4.2-0.6 8.4 0.2 4.3 1 8.4 0.7 3.4 1.9 6.7 1.2 3.3 2.9 6.3 1.6 3.1 3.6 5.9 2.1 2.8 4.5 5.3 2.3 2.3 4.9 4.2 2.5 1.9 5.4 3.4 2.8 1.4 5.9 2.4 3 0.9 6.2 1.4z"/><path id="Layer" fill-rule="evenodd" class="s1" d="m22.1 55.7c1.3 3.4 4.6 5.6 8.2 5.7 3.6 0.1 6.9-2 8.4-5.3 1.5-3.4 0.8-7.2-1.7-9.9-2.5-2.6-6.3-3.5-9.7-2.2-1.1 0.4-2.2 1.1-3 1.9-0.9 0.8-1.6 1.8-2.1 2.9-0.4 1.1-0.7 2.2-0.7 3.4 0 1.2 0.2 2.4 0.6 3.5z"/><path id="Layer" fill-rule="evenodd" class="s2" d="m16.8 82.5c1.3 3.4 4.5 5.7 8.2 5.8 3.7 0.1 7.1-2 8.6-5.3 1.5-3.4 0.8-7.3-1.7-10-2.6-2.7-6.5-3.6-9.9-2.3-1.1 0.5-2.1 1.1-3 1.9-0.9 0.9-1.6 1.9-2 2.9-0.5 1.1-0.8 2.3-0.8 3.5-0.1 1.2 0.2 2.4 0.6 3.5z"/><path id="Layer" fill-rule="evenodd" class="s3" d="m62.1 81.7q0.6 1.6 1.7 2.9 1.1 1.3 2.6 2.2 1.4 0.9 3.1 1.3 1.7 0.3 3.4 0.2l-2.1-20.7q-0.4 0-0.7 0.1-0.4 0-0.7 0.1-0.4 0.1-0.7 0.2-0.3 0.1-0.7 0.2c-1.2 0.5-2.4 1.2-3.4 2.2-1 0.9-1.8 2-2.4 3.3-0.5 1.2-0.8 2.6-0.8 4-0.1 1.3 0.1 2.7 0.6 4z"/><path id="Layer" fill-rule="evenodd" class="s0" d="m100.3 17.7c2.4 7.9 1.5 18.1-6.8 21.8q-0.8 0.4-1.6 0.6-0.9 0.2-1.7 0.2-0.8 0.1-1.7 0-0.8-0.1-1.6-0.4-1.4-0.4-2.7-1.1-1.3-0.8-2.4-1.8-1-0.9-1.9-2.1-0.9-1.2-1.5-2.5c-5.6-12.7 10.4-20.2 11.1-32.4 3.6 4.2 8.5 10.5 10.8 17.7zm-0.1 45.2l-4 54.6q0 0.5-0.1 1-0.1 0.5-0.3 1-0.2 0.5-0.4 1-0.3 0.5-0.6 0.9c-0.7 0.6-1.4 1-2.3 1.3-0.8 0.2-1.7 0.2-2.6 0-0.8-0.2-1.6-0.6-2.3-1.2-0.6-0.6-1.1-1.3-1.4-2.1q-0.1-0.4-0.2-0.9-0.1-0.4-0.2-0.8 0-0.4-0.1-0.8 0-0.5 0-0.9l-5.4-52.9q2.4 0.5 4.9 0.7 2.5 0.3 5 0.3 2.5 0 5-0.3 2.5-0.3 5-0.9zm-5.5-17.8c2.5 7.5 2.8 8.2 6 14.5-6.5 1.8-13.4 1.5-20.4 0l4.3-14.5c3.2 1.6 7.7 1 10.2 0z"/><path id="Layer" fill-rule="evenodd" class="s4" d="m40.8 101.6c1.3 3.4 4.5 5.7 8.2 5.8 3.7 0.1 7.1-2 8.6-5.4 1.5-3.3 0.8-7.3-1.7-9.9-2.6-2.7-6.5-3.6-9.9-2.3-1.1 0.5-2.1 1.1-3 1.9-0.9 0.9-1.6 1.8-2 2.9-0.5 1.1-0.8 2.3-0.8 3.5-0.1 1.2 0.2 2.4 0.6 3.5z"/></svg>';
let menuCreated = false;
function menuOrderListReset(element) {
const defaultOrder = [
"Scenes",
"Images",
"Movies",
"Markers",
"Galleries",
"Performers",
"Studios",
"Tags",
];
const ul = element.querySelector("ul");
const lis = Array.from(ul.querySelectorAll("li"));
// Sort the li elements based on their text content
lis.sort((a, b) => {
const aIndex = defaultOrder.indexOf(a.textContent);
const bIndex = defaultOrder.indexOf(b.textContent);
return aIndex - bIndex;
});
// Append the sorted li elements to the ul in their new order
lis.forEach((li) => ul.appendChild(li));
}
function menuOrderResetButton(elementToAppend) {
if (!document.getElementById("themeSwitchPlugin-menuOrderReset")) {
const resetButton = document.createElement("button");
resetButton.id = "themeSwitchPlugin-menuOrderReset";
resetButton.className = "btn btn-primary";
resetButton.innerHTML = "Reset Menu Order";
resetButton.addEventListener("click", function () {
localStorage.removeItem(
"themeSwitchPlugin-menu-changeOrderOfNavButtons"
);
document
.getElementById("themeSwitchPlugin-menu-changeOrderOfNavButtons")
.remove();
menuOrderListReset(elementToAppend);
resetButton.remove();
});
elementToAppend.appendChild(resetButton);
}
}
function buldDragCSS(themename, key) {
const container = document.querySelector(".draggable-ul-container");
const list = container.querySelectorAll("li");
let css = "";
for (let i = 0; i < list.length; i++) {
const item = list[i];
const key = item.getAttribute("data-rb-event-key");
const order = i - list.length;
css += `div.nav-link[data-rb-event-key="${key}"] { order: ${order}; }`;
}
const style = document.createElement("style");
style.type = "text/css";
style.id = key;
style.innerHTML = `nav .navbar-nav:first-child {
display: flex;
flex-direction: row;
}
${css}`;
const existing = document.getElementById(key);
if (existing) {
existing.remove();
}
document.getElementsByTagName("head")[0].appendChild(style);
const data = {
name: themename,
css: css,
key: key,
id: key,
applied: "true",
};
localStorage.setItem(key, JSON.stringify(data));
menuOrderResetButton(container.parentElement);
}
function enableDragSort(listClass) {
const sortableLists = document.getElementsByClassName(listClass);
Array.prototype.map.call(sortableLists, (list) => {
enableDragList(list);
});
}
function enableDragList(list) {
Array.prototype.map.call(list.children, (item) => {
enableDragItem(item);
});
}
function enableDragItem(item) {
item.setAttribute("draggable", true);
item.addEventListener("touchstart", handleTouchStart, { passive: true });
item.addEventListener("touchmove", handleTouchMove, { passive: true });
item.addEventListener("touchend", handleTouchEnd, { passive: true });
item.ondrag = handleDrag;
item.ondragend = handleDrop;
}
let touchEndY = null;
function handleTouchStart(event) {
event.preventDefault();
touchStartY = event.touches[0].clientY;
}
function handleTouchMove(event) {
event.preventDefault();
touchEndY = event.changedTouches[0].clientY;
const selectedItem = event.target,
list = selectedItem.parentNode;
selectedItem.classList.add("drag-sort-active");
let swapItem =
document.elementFromPoint(
selectedItem.getBoundingClientRect().x,
touchEndY
) === null
? selectedItem
: document.elementFromPoint(
selectedItem.getBoundingClientRect().x,
touchEndY
);
if (list === swapItem.parentNode) {
swapItem =
swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling;
list.insertBefore(selectedItem, swapItem);
}
}
function handleTouchEnd(event) {
event.preventDefault();
touchEndY = event.changedTouches[0].clientY;
handleDragTouch(event.target);
}
function handleDragTouch(item) {
const selectedItem = item,
list = selectedItem.parentNode;
selectedItem.classList.remove("drag-sort-active");
let swapItem =
document.elementFromPoint(
selectedItem.getBoundingClientRect().x,
touchEndY
) === null
? selectedItem
: document.elementFromPoint(
selectedItem.getBoundingClientRect().x,
touchEndY
);
if (list === swapItem.parentNode) {
swapItem =
swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling;
list.insertBefore(selectedItem, swapItem);
}
setTimeout(() => {
buldDragCSS(
"Change the order of navigation bar buttons",
"themeSwitchPlugin-menu-changeOrderOfNavButtons"
);
}, 100);
}
function handleDrag(item) {
const selectedItem = item.target,
list = selectedItem.parentNode,
x = event.clientX,
y = event.clientY;
selectedItem.classList.add("drag-sort-active");
let swapItem =
document.elementFromPoint(x, y) === null
? selectedItem
: document.elementFromPoint(x, y);
if (list === swapItem.parentNode) {
swapItem =
swapItem !== selectedItem.nextSibling ? swapItem : swapItem.nextSibling;
list.insertBefore(selectedItem, swapItem);
}
}
function handleDrop(item) {
item.target.classList.remove("drag-sort-active");
setTimeout(() => {
buldDragCSS(
"Change the order of navigation bar buttons",
"themeSwitchPlugin-menu-changeOrderOfNavButtons"
);
}, 100);
}
(() => {
enableDragSort("drag-sort-enable");
})();
waitForElementClass("draggable-ul-container", function () {
enableDragSort("draggable-ul-container");
});
function setObject(key, category, active) {
return new Promise((resolve, reject) => {
try {
localStorage.setItem(
key,
JSON.stringify({ category: category, active: active })
);
resolve();
} catch (error) {
reject(error);
}
});
}
function checkActive(key) {
let object = JSON.parse(localStorage.getItem(key));
if (object && object.active === true) {
return true;
} else {
return false;
}
}
function applyStyleToHead(key, css) {
const styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css");
const cssTextNode = document.createTextNode(css);
styleElement.id = key;
styleElement.appendChild(cssTextNode);
document.getElementsByTagName("head")[0].appendChild(styleElement);
}
function applyCSS(category, key, css, set) {
if (category === "Themes") {
// Turn Off old Theme
let regex = /(themeSwitchPlugin-theme-.*)/;
for (let i = 0; i < localStorage.length; i++) {
let storageKey = localStorage.key(i);
let match = storageKey.match(regex);
if (
match &&
storageKey !== key &&
JSON.parse(localStorage.getItem(storageKey)).active === true
) {
setObject(storageKey, category, false);
let element = document.getElementById(storageKey);
if (element) {
element.remove();
}
}
}
const theme = JSON.parse(localStorage.getItem(key));
if (!theme && key != "themeSwitchPlugin-theme-default") {
setObject(key, category, true).then(() => {
applyStyleToHead(key, css);
});
} else if (!theme && key === "themeSwitchPlugin-theme-default") {
setObject(key, category, true);
} else if (theme && key === "themeSwitchPlugin-theme-default") {
setObject(key, category, true);
} else if (theme && key !== "themeSwitchPlugin-theme-default") {
setObject(key, category, true).then(() => {
applyStyleToHead(key, css);
});
}
} else {
// CSS Other than themes
const storageObject = JSON.parse(localStorage.getItem(key));
if (!storageObject || storageObject.active === false) {
setObject(key, category, true).then(() => {
applyStyleToHead(key, css);
});
} else if (storageObject.active && !document.getElementById(key)) {
applyStyleToHead(key, css);
} else {
setObject(key, category, false).then(() => {
document.getElementById(key).remove();
});
}
}
}
function expandCollapse(category, name, span, collapse) {
let categorySpanClick = document.getElementById(span);
let categoryCollapseClick = document.getElementById(collapse);
let expanded =
categoryCollapseClick.getAttribute("aria-expanded") === "true";
if (expanded) {
categoryCollapseClick.setAttribute("aria-expanded", "false");
categoryCollapseClick.classList.add("expanding");
setTimeout(function () {
categoryCollapseClick.classList.remove("show");
categoryCollapseClick.classList.remove("expanding");
categorySpanClick.innerHTML = svgChevUP + category;
}, 300);
} else {
categoryCollapseClick.setAttribute("aria-expanded", "true");
categoryCollapseClick.classList.add("expanding");
setTimeout(function () {
categoryCollapseClick.classList.add("show");
categoryCollapseClick.classList.remove("expanding");
categorySpanClick.innerHTML = svgChevDN + category;
}, 300);
}
}
function createBTNMenu() {
return new Promise(function (resolve, reject) {
waitForElementClass("top-nav", function () {
if (!document.getElementById("themeSwitchPlugin")) {
const pluginDiv = document.createElement("div");
pluginDiv.className = "mr-2 dropdown";
pluginDiv.innerHTML =
'<button id="themeSwitchPlugin" aria-haspopup="true" aria-expanded="false" type="button" class="dropdown-toggle minimal d-flex align-items-center h-100 btn btn-primary" title="Theme Switcher">' +
svgBTN +
"</button>";
const themesDiv = document.createElement("div");
themesDiv.className = "dropdown-menu theme-plugin-menu";
document.addEventListener("click", function (event) {
const isClickInside =
pluginDiv.contains(event.target) ||
themesDiv.contains(event.target);
const isClickInsideThemesDiv = themesDiv.contains(event.target);
const themeSwitchPlugin =
document.getElementById("themeSwitchPlugin");
const expanded =
themeSwitchPlugin.getAttribute("aria-expanded") === "true";
if (expanded && !isClickInsideThemesDiv) {
themeSwitchPlugin.setAttribute("aria-expanded", "false");
themesDiv.classList.remove("show");
themesDiv.style =
"position: absolute; to0px; left: 0px; margin: 0px; opacity: 0; pointer-events: none;";
} else if (!expanded && isClickInside) {
themeSwitchPlugin.setAttribute("aria-expanded", "true");
themesDiv.classList.add("show");
themesDiv.style =
"position: absolute; top: 133%; left: 0px; margin: 0px; opacity: 1; pointer-events: auto; width: max-content; min-width: 20rem; left: -575%;";
} else if (!isClickInside && !isClickInsideThemesDiv) {
themeSwitchPlugin.setAttribute("aria-expanded", "false");
themesDiv.classList.remove("show");
themesDiv.style =
"position: absolute; top: 0px; left: 0px; margin: 0px; opacity: 0; pointer-events: none;";
}
});
const accordion = document.createElement("div");
accordion.className = "criterion-list accordion";
accordion.style = "max-width: 20rem !important;";
const header = document.createElement("div");
header.innerHTML = "Theme/CSS Switcher";
header.className = "modal-header";
accordion.append(header);
Object.entries(themeSwitchCSS).forEach(
([category, themesInCategory], i) => {
const categoryDiv = document.createElement("div");
categoryDiv.className = "card";
categoryDiv.setAttribute("data-type", category);
categoryDiv.setAttribute(
"id",
"themeSwitchPlugin-card-" + category
);
categoryDiv.style = "padding: 2px !important;";
const categoryHeader = document.createElement("div");
categoryHeader.className = "card-header";
categoryHeader.style = "padding: 0.375rem 0.625rem !important;";
const categorySpan = document.createElement("span");
categorySpan.innerHTML = svgChevUP + category;
categorySpan.setAttribute(
"id",
"themeSwitchPlugin-span-" + category
);
const collapseDiv = document.createElement("div");
collapseDiv.className = "collapse";
collapseDiv.setAttribute("aria-expanded", "false");
collapseDiv.setAttribute(
"id",
"themeSwitchPlugin-collapse-" + category
);
categoryDiv.addEventListener(
"click",
(function () {
return function (event) {
if (!event.target.closest(".collapse")) {
expandCollapse(
category,
"themeSwitchPlugin-card-" + category,
"themeSwitchPlugin-span-" + category,
"themeSwitchPlugin-collapse-" + category
);
}
};
})(i),
{ capture: true }
);
const cardBody = document.createElement("div");
cardBody.className = "card-body";
const editorDiv = document.createElement("div");
editorDiv.className = "criterion-editor";
const blankDiv = document.createElement("div");
const optionListFilter = document.createElement("div");
optionListFilter.className = "option-list-filter";
blankDiv.append(optionListFilter);
editorDiv.append(blankDiv);
cardBody.append(editorDiv);
collapseDiv.append(cardBody);
categoryHeader.append(categorySpan, collapseDiv);
const fieldset = document.createElement("fieldset");
fieldset.className = "checkbox-switch";
// Loop over themes in each category
Object.entries(themesInCategory).forEach(([themeId, theme]) => {
if (category === "Navigation") {
// menuOrder(category, theme.key, optionListFilter);
} else {
const forRow = document.createElement("div");
forRow.className = "checkbox-switch-form-row";
const legend = document.createElement("legend");
legend.innerHTML = theme.displayName;
legend.className = "legend-right";
const input = document.createElement("input");
if (category === "Themes") {
input.type = "radio";
input.name = "themeGroup";
const active = checkActive(theme.key);
input.checked = active;
} else {
input.type = "checkbox";
const active = checkActive(theme.key);
input.checked = active;
}
const themeData = {
category: category,
key: theme.key,
css: theme.styles,
};
input.setAttribute("id", category + "-" + theme.key);
input.addEventListener(
"click",
(function (category, key, css) {
return function () {
applyCSS(category, key, css);
};
})(themeData.category, themeData.key, themeData.css),
false
);
const label = document.createElement("label");
label.setAttribute("for", category + "-" + theme.key);
label.title = "Turn " + theme.displayName + " on/off";
label.className = "checkbox-right";
// Append the legend, input, and label elements to the fieldset element
forRow.appendChild(legend);
forRow.appendChild(input);
forRow.appendChild(label);
fieldset.appendChild(forRow);
optionListFilter.appendChild(fieldset);
categoryDiv.append(categoryHeader);
accordion.append(categoryDiv);
}
});
Object.entries(themesInCategory).forEach(([themeId, theme]) => {
if (category === "Navigation") {
const forRow = document.createElement("div");
forRow.className = "switch-form-row";
const legend = document.createElement("legend");
legend.innerHTML = theme.displayName;
legend.className = "legend-left";
const draggableStylesheetContent = localStorage.getItem(
"themeSwitchPlugin-menu-changeOrderOfNavButtons"
);
if (draggableStylesheetContent) {
// Read the stylesheet content and create an array called "elements"
var reset = true;
var navMenuItems = [];
const css = JSON.parse(draggableStylesheetContent).css;
const regex = /data-rb-event-key="\/([^"]+)"/g;
let match;
while ((match = regex.exec(css))) {
const key = match[1];
if (match[1] === "scenes/markers") {
const name = "Markers";
navMenuItems.push({ name, key });
} else {
const name =
match[1].charAt(0).toUpperCase() + match[1].slice(1);
navMenuItems.push({ name, key });
}
}
navMenuItems.sort((a, b) => a.order - b.order);
} else {
var navMenuItems = [
{ name: "Scenes", key: "scenes" },
{ name: "Images", key: "images" },
{ name: "Movies", key: "movies" },
{ name: "Markers", key: "scenes/markers" },
{ name: "Galleries", key: "galleries" },
{ name: "Performers", key: "performers" },
{ name: "Studios", key: "studios" },
{ name: "Tags", key: "tags" },
];
}
// const label = document.createElement("label");
legend.setAttribute("for", category + "-" + theme.key);
optionListFilter.appendChild(legend);
const formCheck = document.createElement("ul");
formCheck.className = "draggable-ul-container";
for (let i = 0; i < navMenuItems.length; i++) {
const li = document.createElement("li");
li.className = "draggable-li";
li.setAttribute(
"data-rb-event-key",
"/" + navMenuItems[i].key
);
li.setAttribute("draggable", true);
const newSpan = document.createElement("span");
newSpan.className = "grippy";
li.appendChild(newSpan);
const textNode = document.createTextNode(
navMenuItems[i].name
);
li.appendChild(textNode);
formCheck.appendChild(li);
}
optionListFilter.appendChild(formCheck);
if (reset) {
menuOrderResetButton(optionListFilter);
}
fieldset.appendChild(forRow);
optionListFilter.appendChild(fieldset);
categoryDiv.append(categoryHeader);
accordion.append(categoryDiv);
}
});
}
);
themesDiv.append(accordion);
pluginDiv.append(themesDiv);
waitForElementClass("navbar-buttons", function () {
const mainDiv =
document.getElementsByClassName("navbar-buttons")[0];
const secondLastChild =
mainDiv.childNodes[mainDiv.childNodes.length - 4];
mainDiv.insertBefore(pluginDiv, secondLastChild);
// Resolving the promise once everything is set up
menuCreated = true;
resolve();
});
} else {
// Rejecting the promise if the element already exists
reject(new Error("Element already exists"));
}
});
});
}
function returnCSS(key) {
for (const [, categoryThemes] of Object.entries(themeSwitchCSS)) {
for (const [, theme] of Object.entries(categoryThemes)) {
if (key === theme.key) {
return theme.styles;
}
}
}
}
function init() {
// Apply active Themes and stylesheets
let selectedTheme;
const regex = /(themeSwitchPlugin(?!-theme-default).*)/;
const defaultRegex = /(themeSwitchPlugin-theme-default)/;
let defaultFound = false;
let appliedThemeOtherThanDefault = [];
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
let defaultMatch = key.match(defaultRegex);
if (defaultMatch) {
defaultFound = true;
}
let match = key.match(regex);
if (match) {
selectedTheme = JSON.parse(localStorage.getItem(key));
if (selectedTheme.active === true) {
appliedThemeOtherThanDefault.push("True");
const css = returnCSS(key);
applyCSS(selectedTheme.category, key, css, true);
}
}
}
if (!defaultFound) {
let setDefaultActive;
if (appliedThemeOtherThanDefault.length > 0) {
setDefaultActive = false;
} else {
setDefaultActive = true;
}
setObject("themeSwitchPlugin-theme-default", "Themes", setDefaultActive);
}
}
const StashPages = [
"stash:page:scenes",
"stash:page:scene",
"stash:page:images",
"stash:page:image",
"stash:page:movies",
"stash:page:movie",
"stash:page:markers",
"stash:page:galleries",
"stash:page:performers",
"stash:page:performer",
"stash:page:studios",
"stash:page:studio",
"stash:page:tags",
"stash:page:tag",
"stash:page:settings",
"stash:page:stats",
"stash:page:home",
];
function createMenuAndInit() {
if (!menuCreated) {
createBTNMenu()
.then(function () {
console.log("Theme Plugin: Menu created successfully");
})
.catch(function (error) {
console.error("Theme Plugin: Error creating menu:", error.message);
});
menuCreated = true;
}
init();
}
for (var i = 0; i < StashPages.length; i++) {
const page = StashPages[i];
stash.addEventListener(page, createMenuAndInit);
}
// Reset menuCreated flag on hard refresh
window.addEventListener("beforeunload", function () {
menuCreated = false;
});
})();

View File

@ -0,0 +1,233 @@
.draggable-ul-container {
border: 1px solid black;
margin: 0;
padding: 0;
}
.draggable-li {
padding: 0 20px;
height: 40px;
line-height: 40px;
border-radius: 3px;
background: #137cbd; /* fallback for old browsers */
background: -webkit-linear-gradient(
to right,
#137cbd,
#0b4970
); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(
to right,
#137cbd,
#0b4970
); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
border-bottom: 1px solid black;
cursor: move;
color: #fff;
list-style: none;
}
li.drag-sort-active {
background: transparent;
opacity: 0.5;
border: 1px solid #4ca1af;
}
span.grippy {
content: "....";
width: 10px;
height: 20px;
display: inline-block;
overflow: hidden;
line-height: 5px;
padding: 3px 4px;
cursor: move;
vertical-align: middle;
margin-top: -0.7em;
margin-right: 0.3em;
font-size: 12px;
font-family: sans-serif;
letter-spacing: 2px;
color: #cccccc;
text-shadow: 1px 0 1px black;
}
span.grippy::after {
content: ".. .. .. ..";
}
.dragging {
opacity: 0.5;
}
li.drag-sort-active {
background: transparent;
color: transparent;
border: 1px solid #4ca1af;
}
span.drag-sort-active {
background: transparent;
color: transparent;
}
.dropdown-menu {
max-height: 500px !important;
}
#plugin_theme-menuOrderReset {
margin-top: 10px;
}
.dropdown-menu {
line-height: 1;
}
.theme-plugin-menu {
z-index: 9999999 !important;
}
/* CHECKBOX */
.checkbox-switch input[type="checkbox"],
.checkbox-switch input[type="radio"] {
width: 0;
height: 0;
top: -9999px;
right: -9999px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.checkbox-switch {
margin: 0;
padding: 0;
border: 0;
display: flex;
flex-direction: collumn;
}
.checkbox-switch:after {
content: "";
display: table;
clear: both;
}
.checkbox-right {
margin-bottom: unset !important;
display: flex !important;
flex-direction: column;
box-sizing: unset;
justify-content: flex-end;
transform: translate(8px, 14px);
position: unset;
}
.legend-left {
clear: both; /* fix for IE */
margin: 0;
padding: 0;
font-size: unset;
padding-inline-start: unset;
padding-inline-end: unset;
border-width: unset;
border-style: unset;
border-color: unset;
border-image: unset;
width: unset;
padding: 0;
font-size: unset;
margin-bottom: 10px;
font-weight: unset;
display: flex;
flex-direction: row;
align-items: flex-end;
flex-grow: 1;
}
.legend-right {
clear: both; /* fix for IE */
margin: 0;
padding: 0;
font-size: unset;
padding-inline-start: unset;
padding-inline-end: unset;
border-width: unset;
border-style: unset;
border-color: unset;
border-image: unset;
width: unset;
padding: 0;
font-size: unset;
margin-bottom: unset;
font-weight: unset;
display: flex;
flex-direction: row-reverse;
align-items: flex-end;
flex-grow: 1;
}
.checkbox-switch input[type="checkbox"] + label,
.checkbox-switch input[type="radio"] + label {
user-select: none;
}
.checkbox-switch input[type="checkbox"] + label:before,
.checkbox-switch input[type="radio"] + label:before {
width: 2.3rem;
height: 1.3rem;
font-family: Arial, sans-serif;
content: "•";
transition: all 0.2s ease;
text-align: left;
font-size: 2.25rem;
line-height: 1rem;
overflow: hidden;
color: black;
border: 0.1949902505rem solid black;
border-radius: 0.65rem;
margin: auto 0;
float: right;
transform: translate(0px, -15px);
background-color: #808b8d;
}
.checkbox-switch input[type="checkbox"] + label:after,
.checkbox-switch input[type="radio"] + label:after {
display: none;
}
.checkbox-switch input[type="checkbox"]:checked + label:before,
.checkbox-switch input[type="radio"]:checked + label:before {
border-color: #00ac64;
background: #00ac64;
text-align: right;
color: white;
}
.checkbox-switch input[type="checkbox"] + label:active:before,
.checkbox-switch input[type="checkbox"]:checked + label:active:before,
.checkbox-switch input[type="radio"] + label:active:before,
.checkbox-switch input[type="radio"]:checked + label:active:before {
border-color: #808b8d;
background: #808b8d !important;
color: white;
}
.checkbox-switch fieldset {
margin: 0;
padding: 0;
border: 0;
padding: 1rem;
display: flex;
align-items: center;
}
.checkbox-switch a {
text-decoration: none;
font-weight: 500;
color: inherit;
transition: all 0.2s ease;
}
.checkbox-switch a:hover {
text-decoration: underline;
color: #00ac64;
}
/* Overides */
.checkbox-switch {
margin-inline-start: unset !important;
margin-inline-end: unset !important;
padding-inline-start: unset !important;
padding-inline-end: unset !important;
padding-block-end: unset !important;
min-inline-size: unset !important;
display: flex;
flex-direction: column;
}
.checkbox-switch-form-row {
align-items: center;
display: flex;
flex-direction: row;
margin-bottom: 10px;
justify-content: right;
}
.top-nav:has(.dropdown-menu.theme-plugin-menu.show) {
overflow: visible !important;
}

5
themes/README.md Normal file
View File

@ -0,0 +1,5 @@
# Adding Theme plugins as part of Theme Switch plugin
On developing theme plugins, consider adding your theme to plugins/themeSwitch. While some users may prefer to have individual themes installed requiring to so to the settings > plugin page each time to change the theme, other users may prefer a Theme Switch manager always available on the nav bar.
There is instructions on how to your theme to the plugin in the folders README.md