For me it was always challenging to manage installed modules because even if I filter them by ‘Owned’ on plugins page, it a) still mixes in modules I don’t have installed, and b) I can’t see image of the module, so it’s always back and forth between library and module page to confirm I want to keep module or not.
I created a script which updates plugins page structure and injects thumbnails of modules that I own. Rest of the controls stay the same, so I can see all modules and remove them from one page.
This is how it looks:
Here’s the script:
Code
/*
* VCV Library owned-module gallery — no-filter version
*
* Usage:
* 1. Log in at https://library.vcvrack.com/plugins
* 2. Open the browser developer console.
* 3. Paste this entire file and press Enter.
*
* The script changes only the current page presentation. Individual removals
* are still handled by VCV's original Remove buttons. Reload the page, or use
* the "Exit gallery" button, to remove the gallery UI.
*/
(async function installVcvOwnedGalleryWithoutFilter() {
"use strict";
const HOST = "library.vcvrack.com";
const GLOBAL_KEY = "__vcvOwnedGallery";
const ROOT_CLASS = "codex-vcv-owned-gallery";
const STYLE_ID = "codex-vcv-owned-gallery-style";
const PANEL_ID = "codex-vcv-owned-gallery-panel";
const COUNT_ID = "codex-vcv-owned-count";
const CARD_CLASS = "codex-vcv-owned-card";
const EMPTY_ROW_CLASS = "codex-vcv-empty-row";
const EMPTY_HEADER_CLASS = "codex-vcv-empty-header";
if (window.location.hostname !== HOST || window.location.pathname !== "/plugins") {
throw new Error("Open https://library.vcvrack.com/plugins before running this script.");
}
if (window[GLOBAL_KEY] && typeof window[GLOBAL_KEY].destroy === "function") {
window[GLOBAL_KEY].destroy();
}
const delay = (milliseconds) => new Promise((resolve) => {
window.setTimeout(resolve, milliseconds);
});
const normalizedButtonText = (button) => button.textContent.replace(/\s+/g, " ").trim();
const findButton = (label) => Array.from(document.querySelectorAll("button"))
.find((button) => normalizedButtonText(button) === label);
// The button reads "Show all" when the owned-only filter is already active.
const showOwnedButton = findButton("Show owned");
if (showOwnedButton) {
showOwnedButton.click();
await delay(400);
}
const expandAllButton = findButton("Expand all");
if (expandAllButton) {
expandAllButton.click();
await delay(400);
}
const pluginTable = document.querySelector("table");
if (!pluginTable) {
throw new Error("The VCV plugin table was not found. Make sure the page has finished loading.");
}
document.documentElement.classList.add(ROOT_CLASS);
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = `
html.${ROOT_CLASS} tr.library-plugins-modules ul {
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
align-items: stretch;
gap: 16px;
margin: 0;
padding: 18px 8px 26px;
list-style: none;
}
html.${ROOT_CLASS} tr.library-plugins-modules li {
display: none !important;
min-width: 0;
}
html.${ROOT_CLASS} tr.library-plugins-modules li.${CARD_CLASS} {
display: block !important;
}
html.${ROOT_CLASS} tr.${EMPTY_ROW_CLASS},
html.${ROOT_CLASS} tr.${EMPTY_HEADER_CLASS} {
display: none !important;
}
html.${ROOT_CLASS} p.library-plugins-module {
box-sizing: border-box;
display: flex !important;
flex-direction: column;
gap: 8px;
height: 100%;
margin: 0;
padding: 12px;
border: 1px solid #39404c;
border-radius: 9px;
background: #171a20;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.2);
}
html.${ROOT_CLASS} .codex-vcv-module-image-link {
order: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 260px;
padding: 8px;
border-radius: 6px;
background: #0d0f13;
overflow: hidden;
}
html.${ROOT_CLASS} .codex-vcv-module-image {
display: block;
width: 100%;
height: 260px;
object-fit: contain;
}
html.${ROOT_CLASS} .codex-vcv-module-image-link.codex-vcv-image-missing::after {
color: #929baa;
content: "Screenshot unavailable";
text-align: center;
}
html.${ROOT_CLASS} .codex-vcv-module-image-link.codex-vcv-image-missing img {
display: none;
}
html.${ROOT_CLASS} p.library-plugins-module > a:not(.codex-vcv-module-image-link) {
order: 1;
display: block;
min-height: 2.4em;
color: #e8edf4 !important;
font-size: 15px;
font-weight: 700;
line-height: 1.2;
text-align: center;
}
html.${ROOT_CLASS} p.library-plugins-module > .library-remove {
order: 2;
width: 100%;
margin-top: auto;
}
html.${ROOT_CLASS} #${PANEL_ID} {
position: fixed;
top: 12px;
right: 12px;
z-index: 10000;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: min(410px, calc(100vw - 24px));
padding: 12px;
border: 1px solid #3f526a;
border-radius: 8px;
background: rgba(20, 25, 32, 0.97);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
color: #e8edf4;
}
html.${ROOT_CLASS} #${COUNT_ID} {
color: #fff;
font-size: 14px;
}
html.${ROOT_CLASS} .codex-vcv-exit-gallery {
padding: 6px 9px;
border: 1px solid #58677a;
border-radius: 5px;
background: #252b34;
color: #e8edf4;
cursor: pointer;
white-space: nowrap;
}
`;
document.head.appendChild(style);
const panel = document.createElement("div");
panel.id = PANEL_ID;
const countLabel = document.createElement("strong");
countLabel.id = COUNT_ID;
const exitButton = document.createElement("button");
exitButton.type = "button";
exitButton.className = "codex-vcv-exit-gallery";
exitButton.textContent = "Exit gallery";
panel.append(countLabel, exitButton);
document.body.appendChild(panel);
let observer;
let refreshTimer;
function addModuleImage(paragraph, removeButton) {
if (paragraph.querySelector(".codex-vcv-module-image-link")) {
return;
}
const pluginSlug = removeButton.dataset.plugin || "";
const moduleSlug = removeButton.dataset.module || "";
const titleLink = paragraph.querySelector("a[href^='/']");
const title = ((titleLink && titleLink.textContent) || moduleSlug).trim();
const imageLink = document.createElement("a");
imageLink.className = "codex-vcv-module-image-link";
imageLink.href = (titleLink && titleLink.getAttribute("href"))
|| `/${pluginSlug}/${moduleSlug}`;
imageLink.title = `${title} — open module page`;
const image = document.createElement("img");
image.className = "codex-vcv-module-image";
image.src = `/screenshots/400/${encodeURIComponent(pluginSlug)}/${encodeURIComponent(moduleSlug)}.webp`;
image.alt = `${title} module panel`;
image.loading = "lazy";
image.decoding = "async";
image.addEventListener("error", () => {
imageLink.classList.add("codex-vcv-image-missing");
}, { once: true });
imageLink.appendChild(image);
paragraph.insertBefore(imageLink, paragraph.firstChild);
}
function updatePluginRows() {
document.querySelectorAll("tr.library-plugins-modules").forEach((moduleRow) => {
const hasOwnedCard = Boolean(moduleRow.querySelector(`li.${CARD_CLASS}`));
moduleRow.classList.toggle(EMPTY_ROW_CLASS, !hasOwnedCard);
const headerRow = moduleRow.previousElementSibling;
if (headerRow && headerRow.matches("tr")) {
headerRow.classList.toggle(EMPTY_HEADER_CLASS, !hasOwnedCard);
}
});
}
function refresh() {
let ownedCount = 0;
document.querySelectorAll("p.library-plugins-module").forEach((paragraph) => {
const item = paragraph.closest("li");
const removeButton = paragraph.querySelector("button.library-remove:not(.hidden)");
if (!item) {
return;
}
item.classList.toggle(CARD_CLASS, Boolean(removeButton));
if (!removeButton) {
return;
}
addModuleImage(paragraph, removeButton);
ownedCount += 1;
});
updatePluginRows();
countLabel.textContent = `${ownedCount} owned modules`;
}
function scheduleRefresh() {
window.clearTimeout(refreshTimer);
refreshTimer = window.setTimeout(refresh, 250);
}
function destroy() {
window.clearTimeout(refreshTimer);
if (observer) {
observer.disconnect();
}
document.documentElement.classList.remove(ROOT_CLASS);
document.getElementById(STYLE_ID)?.remove();
document.getElementById(PANEL_ID)?.remove();
document.querySelectorAll(".codex-vcv-module-image-link").forEach((element) => element.remove());
document.querySelectorAll(`.${CARD_CLASS}`).forEach((element) => {
element.classList.remove(CARD_CLASS);
});
document.querySelectorAll(`tr.${EMPTY_ROW_CLASS}`).forEach((element) => {
element.classList.remove(EMPTY_ROW_CLASS);
});
document.querySelectorAll(`tr.${EMPTY_HEADER_CLASS}`).forEach((element) => {
element.classList.remove(EMPTY_HEADER_CLASS);
});
delete window[GLOBAL_KEY];
console.info("VCV owned-module gallery removed. Reloading the page also restores the original view.");
}
exitButton.addEventListener("click", destroy);
refresh();
observer = new MutationObserver((mutations) => {
const ownershipChanged = mutations.some((mutation) => {
if (mutation.type === "childList") {
return true;
}
return mutation.target instanceof Element
&& mutation.target.matches("button.library-remove, button.library-add");
});
if (ownershipChanged) {
scheduleRefresh();
}
});
observer.observe(pluginTable, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["class"]
});
window[GLOBAL_KEY] = {
destroy,
refresh
};
console.info(
`VCV owned-module gallery enabled: ${document.querySelectorAll(`li.${CARD_CLASS}`).length} modules. `
+ "Use window.__vcvOwnedGallery.destroy() to restore the original presentation."
);
})();
How to use:
- Go to VCV Library - Plugins
- Open developers tools or javascript console, paste code into it and press enter
Or create bookmark with following url, and click on it while being on plugins page:
Bookmarklet
javascript:void%20(async%20function%20installVcvOwnedGalleryWithoutFilter()%20%7B%0A%20%20%22use%20strict%22%3B%0A%0A%20%20const%20HOST%20%3D%20%22library.vcvrack.com%22%3B%0A%20%20const%20GLOBAL_KEY%20%3D%20%22__vcvOwnedGallery%22%3B%0A%20%20const%20ROOT_CLASS%20%3D%20%22codex-vcv-owned-gallery%22%3B%0A%20%20const%20STYLE_ID%20%3D%20%22codex-vcv-owned-gallery-style%22%3B%0A%20%20const%20PANEL_ID%20%3D%20%22codex-vcv-owned-gallery-panel%22%3B%0A%20%20const%20COUNT_ID%20%3D%20%22codex-vcv-owned-count%22%3B%0A%20%20const%20CARD_CLASS%20%3D%20%22codex-vcv-owned-card%22%3B%0A%20%20const%20EMPTY_ROW_CLASS%20%3D%20%22codex-vcv-empty-row%22%3B%0A%20%20const%20EMPTY_HEADER_CLASS%20%3D%20%22codex-vcv-empty-header%22%3B%0A%0A%20%20if%20(window.location.hostname%20!%3D%3D%20HOST%20%7C%7C%20window.location.pathname%20!%3D%3D%20%22%2Fplugins%22)%20%7B%0A%20%20%20%20throw%20new%20Error(%22Open%20https%3A%2F%2Flibrary.vcvrack.com%2Fplugins%20before%20running%20this%20script.%22)%3B%0A%20%20%7D%0A%0A%20%20if%20(window%5BGLOBAL_KEY%5D%20%26%26%20typeof%20window%5BGLOBAL_KEY%5D.destroy%20%3D%3D%3D%20%22function%22)%20%7B%0A%20%20%20%20window%5BGLOBAL_KEY%5D.destroy()%3B%0A%20%20%7D%0A%0A%20%20const%20delay%20%3D%20(milliseconds)%20%3D%3E%20new%20Promise((resolve)%20%3D%3E%20%7B%0A%20%20%20%20window.setTimeout(resolve%2C%20milliseconds)%3B%0A%20%20%7D)%3B%0A%0A%20%20const%20normalizedButtonText%20%3D%20(button)%20%3D%3E%20button.textContent.replace(%2F%5Cs%2B%2Fg%2C%20%22%20%22).trim()%3B%0A%0A%20%20const%20findButton%20%3D%20(label)%20%3D%3E%20Array.from(document.querySelectorAll(%22button%22))%0A%20%20%20%20.find((button)%20%3D%3E%20normalizedButtonText(button)%20%3D%3D%3D%20label)%3B%0A%0A%20%20%2F%2F%20The%20button%20reads%20%22Show%20all%22%20when%20the%20owned-only%20filter%20is%20already%20active.%0A%20%20const%20showOwnedButton%20%3D%20findButton(%22Show%20owned%22)%3B%0A%20%20if%20(showOwnedButton)%20%7B%0A%20%20%20%20showOwnedButton.click()%3B%0A%20%20%20%20await%20delay(400)%3B%0A%20%20%7D%0A%0A%20%20const%20expandAllButton%20%3D%20findButton(%22Expand%20all%22)%3B%0A%20%20if%20(expandAllButton)%20%7B%0A%20%20%20%20expandAllButton.click()%3B%0A%20%20%20%20await%20delay(400)%3B%0A%20%20%7D%0A%0A%20%20const%20pluginTable%20%3D%20document.querySelector(%22table%22)%3B%0A%20%20if%20(!pluginTable)%20%7B%0A%20%20%20%20throw%20new%20Error(%22The%20VCV%20plugin%20table%20was%20not%20found.%20Make%20sure%20the%20page%20has%20finished%20loading.%22)%3B%0A%20%20%7D%0A%0A%20%20document.documentElement.classList.add(ROOT_CLASS)%3B%0A%0A%20%20const%20style%20%3D%20document.createElement(%22style%22)%3B%0A%20%20style.id%20%3D%20STYLE_ID%3B%0A%20%20style.textContent%20%3D%20%60%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20tr.library-plugins-modules%20ul%20%7B%0A%20%20%20%20%20%20display%3A%20grid%20!important%3B%0A%20%20%20%20%20%20grid-template-columns%3A%20repeat(auto-fill%2C%20minmax(170px%2C%201fr))%3B%0A%20%20%20%20%20%20align-items%3A%20stretch%3B%0A%20%20%20%20%20%20gap%3A%2016px%3B%0A%20%20%20%20%20%20margin%3A%200%3B%0A%20%20%20%20%20%20padding%3A%2018px%208px%2026px%3B%0A%20%20%20%20%20%20list-style%3A%20none%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20tr.library-plugins-modules%20li%20%7B%0A%20%20%20%20%20%20display%3A%20none%20!important%3B%0A%20%20%20%20%20%20min-width%3A%200%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20tr.library-plugins-modules%20li.%24%7BCARD_CLASS%7D%20%7B%0A%20%20%20%20%20%20display%3A%20block%20!important%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20tr.%24%7BEMPTY_ROW_CLASS%7D%2C%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20tr.%24%7BEMPTY_HEADER_CLASS%7D%20%7B%0A%20%20%20%20%20%20display%3A%20none%20!important%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20p.library-plugins-module%20%7B%0A%20%20%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20%20%20display%3A%20flex%20!important%3B%0A%20%20%20%20%20%20flex-direction%3A%20column%3B%0A%20%20%20%20%20%20gap%3A%208px%3B%0A%20%20%20%20%20%20height%3A%20100%25%3B%0A%20%20%20%20%20%20margin%3A%200%3B%0A%20%20%20%20%20%20padding%3A%2012px%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20%2339404c%3B%0A%20%20%20%20%20%20border-radius%3A%209px%3B%0A%20%20%20%20%20%20background%3A%20%23171a20%3B%0A%20%20%20%20%20%20box-shadow%3A%200%203px%2012px%20rgba(0%2C%200%2C%200%2C%200.2)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20.codex-vcv-module-image-link%20%7B%0A%20%20%20%20%20%20order%3A%200%3B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20justify-content%3A%20center%3B%0A%20%20%20%20%20%20min-height%3A%20260px%3B%0A%20%20%20%20%20%20padding%3A%208px%3B%0A%20%20%20%20%20%20border-radius%3A%206px%3B%0A%20%20%20%20%20%20background%3A%20%230d0f13%3B%0A%20%20%20%20%20%20overflow%3A%20hidden%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20.codex-vcv-module-image%20%7B%0A%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20width%3A%20100%25%3B%0A%20%20%20%20%20%20height%3A%20260px%3B%0A%20%20%20%20%20%20object-fit%3A%20contain%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20.codex-vcv-module-image-link.codex-vcv-image-missing%3A%3Aafter%20%7B%0A%20%20%20%20%20%20color%3A%20%23929baa%3B%0A%20%20%20%20%20%20content%3A%20%22Screenshot%20unavailable%22%3B%0A%20%20%20%20%20%20text-align%3A%20center%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20.codex-vcv-module-image-link.codex-vcv-image-missing%20img%20%7B%0A%20%20%20%20%20%20display%3A%20none%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20p.library-plugins-module%20%3E%20a%3Anot(.codex-vcv-module-image-link)%20%7B%0A%20%20%20%20%20%20order%3A%201%3B%0A%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20min-height%3A%202.4em%3B%0A%20%20%20%20%20%20color%3A%20%23e8edf4%20!important%3B%0A%20%20%20%20%20%20font-size%3A%2015px%3B%0A%20%20%20%20%20%20font-weight%3A%20700%3B%0A%20%20%20%20%20%20line-height%3A%201.2%3B%0A%20%20%20%20%20%20text-align%3A%20center%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20p.library-plugins-module%20%3E%20.library-remove%20%7B%0A%20%20%20%20%20%20order%3A%202%3B%0A%20%20%20%20%20%20width%3A%20100%25%3B%0A%20%20%20%20%20%20margin-top%3A%20auto%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20%23%24%7BPANEL_ID%7D%20%7B%0A%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20top%3A%2012px%3B%0A%20%20%20%20%20%20right%3A%2012px%3B%0A%20%20%20%20%20%20z-index%3A%2010000%3B%0A%20%20%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20justify-content%3A%20space-between%3B%0A%20%20%20%20%20%20gap%3A%2012px%3B%0A%20%20%20%20%20%20width%3A%20min(410px%2C%20calc(100vw%20-%2024px))%3B%0A%20%20%20%20%20%20padding%3A%2012px%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20%233f526a%3B%0A%20%20%20%20%20%20border-radius%3A%208px%3B%0A%20%20%20%20%20%20background%3A%20rgba(20%2C%2025%2C%2032%2C%200.97)%3B%0A%20%20%20%20%20%20box-shadow%3A%200%204px%2016px%20rgba(0%2C%200%2C%200%2C%200.35)%3B%0A%20%20%20%20%20%20color%3A%20%23e8edf4%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20%23%24%7BCOUNT_ID%7D%20%7B%0A%20%20%20%20%20%20color%3A%20%23fff%3B%0A%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20html.%24%7BROOT_CLASS%7D%20.codex-vcv-exit-gallery%20%7B%0A%20%20%20%20%20%20padding%3A%206px%209px%3B%0A%20%20%20%20%20%20border%3A%201px%20solid%20%2358677a%3B%0A%20%20%20%20%20%20border-radius%3A%205px%3B%0A%20%20%20%20%20%20background%3A%20%23252b34%3B%0A%20%20%20%20%20%20color%3A%20%23e8edf4%3B%0A%20%20%20%20%20%20cursor%3A%20pointer%3B%0A%20%20%20%20%20%20white-space%3A%20nowrap%3B%0A%20%20%20%20%7D%0A%20%20%60%3B%0A%20%20document.head.appendChild(style)%3B%0A%0A%20%20const%20panel%20%3D%20document.createElement(%22div%22)%3B%0A%20%20panel.id%20%3D%20PANEL_ID%3B%0A%0A%20%20const%20countLabel%20%3D%20document.createElement(%22strong%22)%3B%0A%20%20countLabel.id%20%3D%20COUNT_ID%3B%0A%0A%20%20const%20exitButton%20%3D%20document.createElement(%22button%22)%3B%0A%20%20exitButton.type%20%3D%20%22button%22%3B%0A%20%20exitButton.className%20%3D%20%22codex-vcv-exit-gallery%22%3B%0A%20%20exitButton.textContent%20%3D%20%22Exit%20gallery%22%3B%0A%0A%20%20panel.append(countLabel%2C%20exitButton)%3B%0A%20%20document.body.appendChild(panel)%3B%0A%0A%20%20let%20observer%3B%0A%20%20let%20refreshTimer%3B%0A%0A%20%20function%20addModuleImage(paragraph%2C%20removeButton)%20%7B%0A%20%20%20%20if%20(paragraph.querySelector(%22.codex-vcv-module-image-link%22))%20%7B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20const%20pluginSlug%20%3D%20removeButton.dataset.plugin%20%7C%7C%20%22%22%3B%0A%20%20%20%20const%20moduleSlug%20%3D%20removeButton.dataset.module%20%7C%7C%20%22%22%3B%0A%20%20%20%20const%20titleLink%20%3D%20paragraph.querySelector(%22a%5Bhref%5E%3D'%2F'%5D%22)%3B%0A%20%20%20%20const%20title%20%3D%20((titleLink%20%26%26%20titleLink.textContent)%20%7C%7C%20moduleSlug).trim()%3B%0A%0A%20%20%20%20const%20imageLink%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20imageLink.className%20%3D%20%22codex-vcv-module-image-link%22%3B%0A%20%20%20%20imageLink.href%20%3D%20(titleLink%20%26%26%20titleLink.getAttribute(%22href%22))%0A%20%20%20%20%20%20%7C%7C%20%60%2F%24%7BpluginSlug%7D%2F%24%7BmoduleSlug%7D%60%3B%0A%20%20%20%20imageLink.title%20%3D%20%60%24%7Btitle%7D%20%E2%80%94%20open%20module%20page%60%3B%0A%0A%20%20%20%20const%20image%20%3D%20document.createElement(%22img%22)%3B%0A%20%20%20%20image.className%20%3D%20%22codex-vcv-module-image%22%3B%0A%20%20%20%20image.src%20%3D%20%60%2Fscreenshots%2F400%2F%24%7BencodeURIComponent(pluginSlug)%7D%2F%24%7BencodeURIComponent(moduleSlug)%7D.webp%60%3B%0A%20%20%20%20image.alt%20%3D%20%60%24%7Btitle%7D%20module%20panel%60%3B%0A%20%20%20%20image.loading%20%3D%20%22lazy%22%3B%0A%20%20%20%20image.decoding%20%3D%20%22async%22%3B%0A%20%20%20%20image.addEventListener(%22error%22%2C%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20imageLink.classList.add(%22codex-vcv-image-missing%22)%3B%0A%20%20%20%20%7D%2C%20%7Bonce%3A%20true%7D)%3B%0A%0A%20%20%20%20imageLink.appendChild(image)%3B%0A%20%20%20%20paragraph.insertBefore(imageLink%2C%20paragraph.firstChild)%3B%0A%20%20%7D%0A%0A%20%20function%20updatePluginRows()%20%7B%0A%20%20%20%20document.querySelectorAll(%22tr.library-plugins-modules%22).forEach((moduleRow)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20hasOwnedCard%20%3D%20Boolean(moduleRow.querySelector(%60li.%24%7BCARD_CLASS%7D%60))%3B%0A%20%20%20%20%20%20moduleRow.classList.toggle(EMPTY_ROW_CLASS%2C%20!hasOwnedCard)%3B%0A%0A%20%20%20%20%20%20const%20headerRow%20%3D%20moduleRow.previousElementSibling%3B%0A%20%20%20%20%20%20if%20(headerRow%20%26%26%20headerRow.matches(%22tr%22))%20%7B%0A%20%20%20%20%20%20%20%20headerRow.classList.toggle(EMPTY_HEADER_CLASS%2C%20!hasOwnedCard)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%20%20%7D%0A%0A%20%20function%20refresh()%20%7B%0A%20%20%20%20let%20ownedCount%20%3D%200%3B%0A%0A%20%20%20%20document.querySelectorAll(%22p.library-plugins-module%22).forEach((paragraph)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20item%20%3D%20paragraph.closest(%22li%22)%3B%0A%20%20%20%20%20%20const%20removeButton%20%3D%20paragraph.querySelector(%22button.library-remove%3Anot(.hidden)%22)%3B%0A%0A%20%20%20%20%20%20if%20(!item)%20%7B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20item.classList.toggle(CARD_CLASS%2C%20Boolean(removeButton))%3B%0A%20%20%20%20%20%20if%20(!removeButton)%20%7B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20addModuleImage(paragraph%2C%20removeButton)%3B%0A%20%20%20%20%20%20ownedCount%20%2B%3D%201%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20updatePluginRows()%3B%0A%20%20%20%20countLabel.textContent%20%3D%20%60%24%7BownedCount%7D%20owned%20modules%20with%20screenshots%60%3B%0A%20%20%7D%0A%0A%20%20function%20scheduleRefresh()%20%7B%0A%20%20%20%20window.clearTimeout(refreshTimer)%3B%0A%20%20%20%20refreshTimer%20%3D%20window.setTimeout(refresh%2C%20250)%3B%0A%20%20%7D%0A%0A%20%20function%20destroy()%20%7B%0A%20%20%20%20window.clearTimeout(refreshTimer)%3B%0A%20%20%20%20if%20(observer)%20%7B%0A%20%20%20%20%20%20observer.disconnect()%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20document.documentElement.classList.remove(ROOT_CLASS)%3B%0A%20%20%20%20document.getElementById(STYLE_ID)%3F.remove()%3B%0A%20%20%20%20document.getElementById(PANEL_ID)%3F.remove()%3B%0A%20%20%20%20document.querySelectorAll(%22.codex-vcv-module-image-link%22).forEach((element)%20%3D%3E%20element.remove())%3B%0A%20%20%20%20document.querySelectorAll(%60.%24%7BCARD_CLASS%7D%60).forEach((element)%20%3D%3E%20%7B%0A%20%20%20%20%20%20element.classList.remove(CARD_CLASS)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%20%20document.querySelectorAll(%60tr.%24%7BEMPTY_ROW_CLASS%7D%60).forEach((element)%20%3D%3E%20%7B%0A%20%20%20%20%20%20element.classList.remove(EMPTY_ROW_CLASS)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%20%20document.querySelectorAll(%60tr.%24%7BEMPTY_HEADER_CLASS%7D%60).forEach((element)%20%3D%3E%20%7B%0A%20%20%20%20%20%20element.classList.remove(EMPTY_HEADER_CLASS)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20delete%20window%5BGLOBAL_KEY%5D%3B%0A%20%20%20%20console.info(%22VCV%20owned-module%20gallery%20removed.%20Reloading%20the%20page%20also%20restores%20the%20original%20view.%22)%3B%0A%20%20%7D%0A%0A%20%20exitButton.addEventListener(%22click%22%2C%20destroy)%3B%0A%20%20refresh()%3B%0A%0A%20%20observer%20%3D%20new%20MutationObserver((mutations)%20%3D%3E%20%7B%0A%20%20%20%20const%20ownershipChanged%20%3D%20mutations.some((mutation)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(mutation.type%20%3D%3D%3D%20%22childList%22)%20%7B%0A%20%20%20%20%20%20%20%20return%20true%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20return%20mutation.target%20instanceof%20Element%0A%20%20%20%20%20%20%20%20%26%26%20mutation.target.matches(%22button.library-remove%2C%20button.library-add%22)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20if%20(ownershipChanged)%20%7B%0A%20%20%20%20%20%20scheduleRefresh()%3B%0A%20%20%20%20%7D%0A%20%20%7D)%3B%0A%20%20observer.observe(pluginTable%2C%20%7B%0A%20%20%20%20subtree%3A%20true%2C%0A%20%20%20%20childList%3A%20true%2C%0A%20%20%20%20attributes%3A%20true%2C%0A%20%20%20%20attributeFilter%3A%20%5B%22class%22%5D%0A%20%20%7D)%3B%0A%0A%20%20window%5BGLOBAL_KEY%5D%20%3D%20%7B%0A%20%20%20%20destroy%2C%0A%20%20%20%20refresh%0A%20%20%7D%3B%0A%0A%20%20console.info(%0A%20%20%20%20%60VCV%20owned-module%20gallery%20enabled%3A%20%24%7Bdocument.querySelectorAll(%60li.%24%7BCARD_CLASS%7D%60).length%7D%20modules.%20%60%0A%20%20%20%20%20%20%2B%20%22Use%20window.__vcvOwnedGallery.destroy()%20to%20restore%20the%20original%20presentation.%22%0A%20%20)%3B%0A%7D)()%3B
Hope you’ll find it helpful.


