Initial commit — ProxMorph with TheRaiwy Dark cyber theme
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* ProxMorph PDM Theme Selector Patch
|
||||
* Version: 1.0.0
|
||||
*
|
||||
* Injects ProxMorph theme options into PDM's native Theme dialog dropdown.
|
||||
* Uses MutationObserver to detect when the dialog opens and adds custom
|
||||
* theme entries alongside the built-in Desktop/Crisp options.
|
||||
*
|
||||
* Theme guard: only runs if proxmorph-theme CSS links are present.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (!document.querySelector('link.proxmorph-theme')) return;
|
||||
|
||||
var themeLinks = document.querySelectorAll('link.proxmorph-theme');
|
||||
var themes = [];
|
||||
for (var i = 0; i < themeLinks.length; i++) {
|
||||
var href = themeLinks[i].getAttribute('href');
|
||||
var filename = href.split('/').pop();
|
||||
var name = filename.replace('theme-', '').replace('.css', '')
|
||||
.split('-').map(function(w) { return w.charAt(0).toUpperCase() + w.slice(1); }).join(' ');
|
||||
themes.push({ filename: filename, display: 'PM: ' + name, link: themeLinks[i] });
|
||||
}
|
||||
|
||||
function activateTheme(filename) {
|
||||
for (var i = 0; i < themeLinks.length; i++) {
|
||||
themeLinks[i].setAttribute('disabled', '');
|
||||
}
|
||||
if (filename) {
|
||||
localStorage.setItem('proxmorph-theme', filename);
|
||||
var base = document.querySelector('link.proxmorph-base');
|
||||
if (base) base.removeAttribute('disabled');
|
||||
for (var j = 0; j < themeLinks.length; j++) {
|
||||
if (themeLinks[j].href.indexOf(filename) !== -1) {
|
||||
themeLinks[j].removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem('proxmorph-theme');
|
||||
var base = document.querySelector('link.proxmorph-base');
|
||||
if (base) base.setAttribute('disabled', '');
|
||||
}
|
||||
}
|
||||
|
||||
function deactivateProxmorph() {
|
||||
for (var i = 0; i < themeLinks.length; i++) {
|
||||
themeLinks[i].setAttribute('disabled', '');
|
||||
}
|
||||
var base = document.querySelector('link.proxmorph-base');
|
||||
if (base) base.setAttribute('disabled', '');
|
||||
localStorage.removeItem('proxmorph-theme');
|
||||
}
|
||||
|
||||
var patched = false;
|
||||
|
||||
function setupObserver() {
|
||||
var observer = new MutationObserver(function() {
|
||||
if (patched) return;
|
||||
tryInject();
|
||||
});
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', setupObserver);
|
||||
} else {
|
||||
setupObserver();
|
||||
}
|
||||
|
||||
function tryInject() {
|
||||
var dialog = document.querySelector('dialog');
|
||||
if (!dialog) { patched = false; return; }
|
||||
|
||||
var combo = dialog.querySelector('[aria-label="Select Theme"]');
|
||||
if (!combo) return;
|
||||
|
||||
/* Early combo fix: override value as soon as dialog appears, before dropdown is expanded */
|
||||
var currentThemeEarly = localStorage.getItem('proxmorph-theme');
|
||||
if (currentThemeEarly && combo.tagName === 'INPUT' && !combo.__proxmorphPatched) {
|
||||
for (var ei = 0; ei < themes.length; ei++) {
|
||||
if (themes[ei].filename === currentThemeEarly) {
|
||||
var origDesc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
|
||||
origDesc.set.call(combo, themes[ei].display);
|
||||
Object.defineProperty(combo, 'value', {
|
||||
get: function() { return origDesc.get.call(this); },
|
||||
set: function(v) {
|
||||
var active = localStorage.getItem('proxmorph-theme');
|
||||
if (active && (v === 'Desktop' || v === 'Crisp' || v === 'Material')) {
|
||||
return;
|
||||
}
|
||||
origDesc.set.call(this, v);
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
combo.__proxmorphPatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tables = dialog.querySelectorAll('.pwt-datatable-content');
|
||||
var themeTable = null;
|
||||
|
||||
for (var t = 0; t < tables.length; t++) {
|
||||
var rows = tables[t].querySelectorAll('tr[role="row"]');
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
var cellText = rows[r].textContent.trim();
|
||||
if (cellText === 'Desktop' || cellText === 'Crisp' || cellText === 'Material') {
|
||||
themeTable = tables[t];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (themeTable) break;
|
||||
}
|
||||
|
||||
if (!themeTable) return;
|
||||
if (themeTable.querySelector('[data-proxmorph-theme]')) { patched = true; return; }
|
||||
|
||||
var nativeRows = themeTable.querySelectorAll('tr[role="row"]');
|
||||
var existingCount = nativeRows.length;
|
||||
|
||||
var idPrefix = 'ProxMorph';
|
||||
if (nativeRows.length > 0 && nativeRows[0].id) {
|
||||
idPrefix = nativeRows[0].id.split('-item-')[0];
|
||||
}
|
||||
|
||||
var sepRow = document.createElement('tr');
|
||||
sepRow.setAttribute('role', 'none');
|
||||
sepRow.className = 'proxmorph-separator';
|
||||
var sepTd = document.createElement('td');
|
||||
sepTd.setAttribute('role', 'none');
|
||||
sepTd.setAttribute('colspan', '2');
|
||||
sepTd.style.cssText = 'padding:4px 0;border-top:1px solid var(--pwt-color-neutral-40,#555);';
|
||||
var sepDiv = document.createElement('div');
|
||||
sepDiv.style.cssText = 'font-size:11px;color:var(--pwt-color-neutral-60,#999);padding:2px 8px;';
|
||||
sepDiv.textContent = 'ProxMorph Themes';
|
||||
sepTd.appendChild(sepDiv);
|
||||
sepRow.appendChild(sepTd);
|
||||
themeTable.appendChild(sepRow);
|
||||
|
||||
var currentTheme = localStorage.getItem('proxmorph-theme');
|
||||
|
||||
for (var i = 0; i < themes.length; i++) {
|
||||
var theme = themes[i];
|
||||
var rowIndex = existingCount + i + 1;
|
||||
var isSelected = currentTheme === theme.filename;
|
||||
|
||||
var row = document.createElement('tr');
|
||||
row.setAttribute('role', 'row');
|
||||
row.setAttribute('aria-rowindex', String(rowIndex));
|
||||
row.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
||||
row.id = idPrefix + '-item-PM-' + theme.filename;
|
||||
row.className = isSelected ? 'row-cursor selected' : '';
|
||||
row.setAttribute('data-proxmorph-theme', theme.filename);
|
||||
|
||||
var td = document.createElement('td');
|
||||
td.setAttribute('role', 'gridcell');
|
||||
td.setAttribute('data-column-num', '0');
|
||||
td.setAttribute('tabindex', '-1');
|
||||
td.className = 'pwt-datatable-cell pwt-pointer' + (isSelected ? ' cell-cursor' : '');
|
||||
td.style.cssText = 'vertical-align:baseline;text-align:start;';
|
||||
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute('role', 'none');
|
||||
div.textContent = theme.display;
|
||||
td.appendChild(div);
|
||||
|
||||
var td2 = document.createElement('td');
|
||||
td2.setAttribute('role', 'none');
|
||||
td2.style.cssText = 'vertical-align:top;height:22px;';
|
||||
|
||||
row.appendChild(td);
|
||||
row.appendChild(td2);
|
||||
themeTable.appendChild(row);
|
||||
|
||||
(function(themeObj, rowEl) {
|
||||
rowEl.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var allRows = themeTable.querySelectorAll('tr[role="row"]');
|
||||
for (var k = 0; k < allRows.length; k++) {
|
||||
allRows[k].className = '';
|
||||
allRows[k].setAttribute('aria-selected', 'false');
|
||||
var cells = allRows[k].querySelectorAll('.pwt-datatable-cell');
|
||||
for (var c = 0; c < cells.length; c++) {
|
||||
cells[c].classList.remove('cell-cursor');
|
||||
}
|
||||
}
|
||||
rowEl.className = 'row-cursor selected';
|
||||
rowEl.setAttribute('aria-selected', 'true');
|
||||
var myCell = rowEl.querySelector('.pwt-datatable-cell');
|
||||
if (myCell) myCell.classList.add('cell-cursor');
|
||||
|
||||
activateTheme(themeObj.filename);
|
||||
|
||||
var combo = dialog.querySelector('[aria-label="Select Theme"]');
|
||||
if (combo) {
|
||||
if (combo.tagName === 'INPUT') {
|
||||
/* Remove override temporarily to set the new value */
|
||||
var origDesc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
|
||||
delete combo.value;
|
||||
origDesc.set.call(combo, themeObj.display);
|
||||
/* Re-install override to block PWT resets */
|
||||
Object.defineProperty(combo, 'value', {
|
||||
get: function() { return origDesc.get.call(this); },
|
||||
set: function(v) {
|
||||
var active = localStorage.getItem('proxmorph-theme');
|
||||
if (active && (v === 'Desktop' || v === 'Crisp' || v === 'Material')) {
|
||||
return;
|
||||
}
|
||||
origDesc.set.call(this, v);
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
} else {
|
||||
var valueSpan = combo.querySelector('.pwt-text-truncate') || combo;
|
||||
if (valueSpan) valueSpan.textContent = themeObj.display;
|
||||
}
|
||||
}
|
||||
});
|
||||
})(theme, row);
|
||||
}
|
||||
|
||||
/* If a ProxMorph theme is active, deselect native rows and update combo */
|
||||
if (currentTheme) {
|
||||
for (var d = 0; d < nativeRows.length; d++) {
|
||||
nativeRows[d].className = '';
|
||||
nativeRows[d].setAttribute('aria-selected', 'false');
|
||||
var dCells = nativeRows[d].querySelectorAll('.pwt-datatable-cell');
|
||||
for (var dc = 0; dc < dCells.length; dc++) {
|
||||
dCells[dc].classList.remove('cell-cursor');
|
||||
}
|
||||
}
|
||||
/* Update combo display text to active PM theme name */
|
||||
var activeCombo = dialog.querySelector('[aria-label="Select Theme"]');
|
||||
if (activeCombo) {
|
||||
for (var at = 0; at < themes.length; at++) {
|
||||
if (themes[at].filename === currentTheme) {
|
||||
if (activeCombo.tagName === 'INPUT') {
|
||||
activeCombo.value = themes[at].display;
|
||||
/* Override value setter to prevent PWT from resetting to native theme name */
|
||||
var pmDisplay = themes[at].display;
|
||||
var origDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
|
||||
Object.defineProperty(activeCombo, 'value', {
|
||||
get: function() { return origDescriptor.get.call(this); },
|
||||
set: function(v) {
|
||||
var active = localStorage.getItem('proxmorph-theme');
|
||||
if (active && (v === 'Desktop' || v === 'Crisp' || v === 'Material')) {
|
||||
return;
|
||||
}
|
||||
origDescriptor.set.call(this, v);
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
} else {
|
||||
var valSpan = activeCombo.querySelector('.pwt-text-truncate') || activeCombo;
|
||||
if (valSpan) valSpan.textContent = themes[at].display;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var nr = 0; nr < nativeRows.length; nr++) {
|
||||
(function(nativeRow) {
|
||||
nativeRow.addEventListener('click', function() {
|
||||
deactivateProxmorph();
|
||||
/* Remove value override so PWT can set native theme name */
|
||||
var cmb = dialog.querySelector('[aria-label="Select Theme"]');
|
||||
if (cmb && cmb.tagName === 'INPUT') {
|
||||
delete cmb.value;
|
||||
}
|
||||
var pmRows = themeTable.querySelectorAll('[data-proxmorph-theme]');
|
||||
for (var p = 0; p < pmRows.length; p++) {
|
||||
pmRows[p].className = '';
|
||||
pmRows[p].setAttribute('aria-selected', 'false');
|
||||
var cells = pmRows[p].querySelectorAll('.pwt-datatable-cell');
|
||||
for (var c = 0; c < cells.length; c++) {
|
||||
cells[c].classList.remove('cell-cursor');
|
||||
}
|
||||
}
|
||||
});
|
||||
})(nativeRows[nr]);
|
||||
}
|
||||
|
||||
patched = true;
|
||||
|
||||
var closeCheck = setInterval(function() {
|
||||
if (!document.contains(themeTable)) {
|
||||
patched = false;
|
||||
clearInterval(closeCheck);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user