Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion plugins/themeSwitch/assets/snippets/global/blur-nsfw-images.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,24 @@ img.performer,
.scene-details .pre,
#scene-tabs-tabpane-scene-file-info-panel span.col-8.text-truncate > a,
.gallery .flexbin img,
.movie-details .logo {
.movie-details .logo,
.group-card-image,
.studio-card-image,
.image-image,
.gallery-image {
filter: blur(12px);
}

/* Scene player (video.js, used since Stash v0.17) and its scrubber thumbnails.
!important: the player's own styles and themes can otherwise win. */
.video-js video,
.video-js .vjs-tech,
.video-js .vjs-poster,
.video-js .vjs-poster img,
.scrubber-item {
filter: blur(20px) !important;
}

.scene-card-video {
filter: blur(13px);
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/themeSwitch/themeSwitch.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Theme Switch
# requires: CommunityScriptsUILibrary
description: Theme and CSS script manager located in main menu bar top right.
version: 2.1.2
version: 2.1.3
url: https://discourse.stashapp.cc/t/theme-switch/1414
ui:
requires:
Expand Down
145 changes: 96 additions & 49 deletions plugins/themeSwitch/themeSwitchMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@
}
}

// Bump with the plugin version so browsers fetch updated snippets instead
// of reusing a cached copy (Stash sends no cache headers for plugin assets).
const ASSET_VERSION = "2.1.3";

function addStyleSheet(key, path) {
console.log(key, path);
const styleSheet = document.createElement("link");
Expand All @@ -256,67 +260,102 @@
document.querySelector("base")?.getAttribute("href") ?? "/";
styleSheet.setAttribute(
"href",
`${serverURL}plugin/themeSwitch/assets${path}`
`${serverURL}plugin/themeSwitch/assets${path}?v=${ASSET_VERSION}`
);
styleSheet.setAttribute("rel", "stylesheet");
styleSheet.setAttribute("type", "text/css");
styleSheet.id = key;
document.getElementsByTagName("head")[0].appendChild(styleSheet);
}

async function applyCSS(category, key, path, pluginId, pluginSrc) {
// Picking a theme enables exactly that theme's plugin and disables every
// other theme plugin in the list, including ones enabled from the Plugins
// page, so themes never stack. On page load only path-based themes need
// re-attaching; plugin themes load themselves.
async function applyCSS(category, key, path, pluginId, pluginSrc, picked) {
if (category === "Themes") {
// Turn Off old Theme
let regex = /(themeSwitchPlugin-theme-.*)/;
if (!picked) {
// Page load: re-attach a path-based theme; plugin themes load themselves.
if (path && !document.getElementById(key)) addStyleSheet(key, path);
return;
}

// Turn off the old theme in storage and drop any path-based sheet.
const 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 && !pluginId) {
element.remove();
} else {
const oldThemePluginId = getDataFromKey(storageKey, "pluginId");
if (oldThemePluginId) {
await enablePlugin(oldThemePluginId, false);
setTimeout(() => {
location.reload();
}, 1000);
}
}
const storageKey = localStorage.key(i);
if (storageKey.match(regex) && storageKey !== key) {
await setObject(storageKey, category, false);
document.getElementById(storageKey)?.remove();
}
}
await setObject(key, category, true);
if (path && !document.getElementById(key)) addStyleSheet(key, path);

let installed = false;
if (pluginId && !(await isPluginInstalled(pluginId))) {
// installPackages runs as a job; wait for the plugin to register.
await installPlugin(pluginId, pluginSrc);
for (let i = 0; i < 30 && !(await isPluginInstalled(pluginId)); i++) {
await new Promise((r) => setTimeout(r, 500));
}
installed = true;
}

const theme = JSON.parse(localStorage.getItem(key));

if (!theme && key != "themeSwitchPlugin-theme-default") {
setObject(key, category, true).then(() => {
addStyleSheet(key, path);
});
} 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(async () => {
if (pluginId) {
if (!(await isPluginInstalled(pluginId))) {
await installPlugin(pluginId, pluginSrc);
}
await enablePlugin(pluginId, true);
setTimeout(() => {
location.reload();
}, 1000);
} else if (path) {
addStyleSheet(key, path);
// Exactly one theme plugin on: the chosen one (none for Default or a
// path-based theme).
const data = await csLib
.callGQL({
query: `query Plugins{plugins{id enabled paths{css javascript}}}`,
})
.catch((err) => console.error(err));
const plugins = new Map((data?.plugins ?? []).map((p) => [p.id, p]));
const enabledMap = {};
let needsReload = installed;
for (const theme of window.themeSwitchCSS.Themes) {
const plugin = plugins.get(theme.pluginId);
if (!plugin) continue;
const want = theme.pluginId === pluginId;
if (plugin.enabled !== want) enabledMap[theme.pluginId] = want;
// A theme's script can't be unloaded or loaded late: switching away
// from or to a theme that ships JavaScript still needs a reload.
if (plugin.paths?.javascript?.length && plugin.enabled !== want) {
needsReload = true;
}
}
if (Object.keys(enabledMap).length > 0) {
await csLib
.callGQL({
query:
"mutation SetPluginsEnabled($enabledMap: BoolMap!) { setPluginsEnabled(enabledMap: $enabledMap) }",
variables: { enabledMap },
})
.catch((err) => console.error(err));
}
if (needsReload) {
location.reload();
return;
}
// CSS-only themes switch in place: drop the other themes' stylesheets
// and add the chosen one's, with a cache-busting query so a stale copy
// is never reused.
for (const theme of window.themeSwitchCSS.Themes) {
const plugin = plugins.get(theme.pluginId);
if (!plugin) continue;
const marker = `/plugin/${theme.pluginId}/`;
const links = [...document.querySelectorAll("link[rel=stylesheet]")]
.filter((l) => l.href.includes(marker));
if (theme.pluginId !== pluginId) {
links.forEach((l) => l.remove());
} else if (!links.length) {
for (const href of plugin.paths?.css ?? []) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = `${href}${href.includes("?") ? "&" : "?"}t=${Date.now()}`;
document.head.appendChild(link);
}
});
}
}
} else {
// CSS Other than themes
Expand Down Expand Up @@ -547,7 +586,8 @@
themeData.key,
themeData.path,
themeData.pluginId,
themeData.pluginSrc
themeData.pluginSrc,
true
);
};
})(themeData),
Expand Down Expand Up @@ -701,7 +741,14 @@
selectedTheme = JSON.parse(localStorage.getItem(key));
if (selectedTheme.active === true) {
appliedThemeOtherThanDefault.push("True");
applyCSS(selectedTheme.category, key, getDataFromKey(key, "path"));
// init() runs on every stash:location event (page changes, filter
// changes). Only attach what is missing: going through applyCSS
// here toggled an already-attached snippet back off.
if (selectedTheme.category === "Themes") {
applyCSS(selectedTheme.category, key, getDataFromKey(key, "path"));
} else if (!document.getElementById(key)) {
addStyleSheet(key, getDataFromKey(key, "path"));
}
}
}
}
Expand Down