mirror of
https://github.com/MichaelScottsman/LiquidAss-Frame.git
synced 2026-10-23 16:01:22 +02:00
164 lines
8.3 KiB
JavaScript
164 lines
8.3 KiB
JavaScript
// @lgs-flag wp.c6b
|
|
/* Glass Shell C6b (SET §4.10, §9.5; contracts/daemon.md §6): SteamVR's own
|
|
settings page in vr:systemui (#vrsettingspanel). T2 markup for
|
|
theme/vr/30-settings.css, keyed by SteamVR's own localised strings, never
|
|
by position:
|
|
- data-lgs-switch on two-option .DualValue groups of the detail pane whose
|
|
texts are SteamVR's "Off" then "On" (#settings_togglebutton_off/_on):
|
|
the T1 switch; data-lgs-exempt="E-SWITCH" on the group (PLAN §1.16);
|
|
- data-lgs-sec on each sidebar row (#settings_sectiontitle_*): icon circle;
|
|
- data-lgs-title / data-lgs-sec on .SettingsPageContainer: the section hero
|
|
(the active row's own text);
|
|
- the Back circle (.lgs-c6b-back, a new node in the sidebar's non-scrolling
|
|
parent): its click switches the dashboard frame back to Steam's page with
|
|
the same FrameStore call settings_open.js uses (SwitchToPage).
|
|
A MutationObserver (childList only) re-applies after React re-renders,
|
|
before the next paint. Nothing here changes a setting. remove() takes
|
|
every attribute and node away. */
|
|
(function (ctx) {
|
|
const SEC = {
|
|
general: '#settings_sectiontitle_general',
|
|
playarea: '#settings_sectiontitle_playarea',
|
|
dashboard: '#settings_sectiontitle_dashboard',
|
|
controller: '#settings_sectiontitle_controller',
|
|
video: '#settings_sectiontitle_video',
|
|
camera: '#settings_sectiontitle_camera',
|
|
startupshutdown: '#settings_sectiontitle_startupshutdown',
|
|
developer: '#settings_sectiontitle_developer',
|
|
};
|
|
const norm = (s) => String(s || '').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
const loc = (t) => {
|
|
try {
|
|
const L = window.LocalizationManager;
|
|
const v = L && L.LocalizeString ? L.LocalizeString(t) : null;
|
|
return v && v !== t ? v : null;
|
|
} catch (e) { return null; }
|
|
};
|
|
let secMap = null; // label -> key
|
|
let offOn = null; // [off, on]
|
|
let backLabel = 'Back';
|
|
const build = () => {
|
|
const m = new Map();
|
|
for (const [k, t] of Object.entries(SEC)) { const v = loc(t); if (v) m.set(norm(v), k); }
|
|
if (m.size) secMap = m;
|
|
const off = loc('#settings_togglebutton_off'); const on = loc('#settings_togglebutton_on');
|
|
if (off && on) offOn = [norm(off), norm(on)];
|
|
backLabel = loc('#back') || 'Back';
|
|
};
|
|
const ATTRS = ['data-lgs-switch', 'data-lgs-exempt', 'data-lgs-sec', 'data-lgs-title'];
|
|
// ARIA we add (SteamVR sets none on these nodes): removed by remove()
|
|
const OWN_ARIA = [['.SettingsSidebar > .SettingsSidebarButton', ['role', 'aria-selected']],
|
|
['.SegmentedControlGroup[data-lgs-switch]', ['role', 'aria-checked', 'aria-label']]];
|
|
const setAttr = (el, a, v) => { if (el.getAttribute(a) !== v) el.setAttribute(a, v); };
|
|
let runs = 0; let switches = 0; let backClicks = 0; let lastErr = null;
|
|
|
|
const steamPage = () => {
|
|
const fs = window.FrameStore && window.FrameStore.frames;
|
|
if (!fs) return null;
|
|
const f = fs.find((x) => x.pages && x.pages.some((p) => p.m_sSummonOverlayKey === 'system.settings'));
|
|
if (!f) return null;
|
|
const p = f.pages.find((x) => x.m_sSummonOverlayKey === 'valve.steam.gamepadui.main');
|
|
return p ? { f, id: p.pageID ?? p.m_unPageID } : null;
|
|
};
|
|
const onBack = (ev) => {
|
|
ev.preventDefault(); ev.stopPropagation();
|
|
backClicks++;
|
|
try { const s = steamPage(); if (s) s.f.SwitchToPage(s.id); } catch (e) { lastErr = String(e); }
|
|
};
|
|
const ensureBack = (cont) => {
|
|
let b = cont.querySelector(':scope > .lgs-c6b-back');
|
|
if (!b) {
|
|
b = document.createElement('div');
|
|
b.className = 'lgs-c6b-back';
|
|
b.setAttribute('role', 'button');
|
|
b.setAttribute('data-lgs-c6b', '');
|
|
b.addEventListener('click', onBack);
|
|
cont.appendChild(b);
|
|
}
|
|
setAttr(b, 'aria-label', backLabel);
|
|
setAttr(b, 'title', backLabel);
|
|
};
|
|
|
|
const apply = () => {
|
|
const panel = document.querySelector('.DashboardPanel.Settings');
|
|
if (!panel) return;
|
|
runs++;
|
|
if (!secMap || !offOn) build();
|
|
const cont = panel.querySelector('.SettingsSidebarPageContainer');
|
|
if (cont && steamPage()) ensureBack(cont);
|
|
// sidebar rows and the hero
|
|
let active = null;
|
|
for (const b of panel.querySelectorAll('.SettingsSidebar > .SettingsSidebarButton')) {
|
|
const lab = b.querySelector('.Label');
|
|
const key = secMap && lab ? secMap.get(norm(lab.textContent)) : null;
|
|
if (key) setAttr(b, 'data-lgs-sec', key); else if (b.hasAttribute('data-lgs-sec')) b.removeAttribute('data-lgs-sec');
|
|
const on = b.classList.contains('Active');
|
|
setAttr(b, 'role', 'tab'); setAttr(b, 'aria-selected', on ? 'true' : 'false');
|
|
if (on) active = { lab, key };
|
|
}
|
|
const page = panel.querySelector('.SettingsPageContainer');
|
|
if (page) {
|
|
const t = active && active.lab ? active.lab.textContent.trim() : '';
|
|
if (t) setAttr(page, 'data-lgs-title', t); else if (page.hasAttribute('data-lgs-title')) page.removeAttribute('data-lgs-title');
|
|
if (active && active.key) setAttr(page, 'data-lgs-sec', active.key); else if (page.hasAttribute('data-lgs-sec')) page.removeAttribute('data-lgs-sec');
|
|
// Off / On pairs -> switches
|
|
let n = 0;
|
|
if (offOn) for (const g of page.querySelectorAll('.SettingsItem.SegmentedControl .SegmentedControlGroup.DualValue')) {
|
|
const o = g.children;
|
|
const ok = o.length === 2 && norm(o[0].textContent) === offOn[0] && norm(o[1].textContent) === offOn[1];
|
|
if (ok) {
|
|
setAttr(g, 'data-lgs-switch', ''); setAttr(g, 'data-lgs-exempt', 'E-SWITCH');
|
|
setAttr(g, 'role', 'switch'); setAttr(g, 'aria-checked', o[1].classList.contains('Active') ? 'true' : 'false');
|
|
const lab = g.closest('.SettingsItem') && g.closest('.SettingsItem').querySelector(':scope > .Label');
|
|
if (lab) setAttr(g, 'aria-label', lab.textContent.trim());
|
|
n++;
|
|
} else if (g.hasAttribute('data-lgs-switch')) { for (const a of ['data-lgs-switch', 'data-lgs-exempt', 'role', 'aria-checked', 'aria-label']) g.removeAttribute(a); }
|
|
}
|
|
switches = n;
|
|
}
|
|
};
|
|
const safe = () => { try { apply(); } catch (e) { lastErr = String(e && e.stack || e).slice(0, 300); } };
|
|
const mo = new MutationObserver(safe);
|
|
mo.observe(document.body, { childList: true, subtree: true });
|
|
// the active row and a switch's state change by class only: watch class changes in the panel too
|
|
const mo2 = new MutationObserver(safe);
|
|
let watched = null;
|
|
const watchSidebar = () => {
|
|
const sb = document.querySelector('.DashboardPanel.Settings');
|
|
if (sb && sb !== watched) { mo2.disconnect(); mo2.observe(sb, { attributes: true, attributeFilter: ['class'], subtree: true }); watched = sb; }
|
|
};
|
|
const mo3 = new MutationObserver(watchSidebar);
|
|
mo3.observe(document.body, { childList: true, subtree: true });
|
|
watchSidebar();
|
|
safe();
|
|
|
|
return {
|
|
remove() {
|
|
mo.disconnect(); mo2.disconnect(); mo3.disconnect();
|
|
for (const b of document.querySelectorAll('.lgs-c6b-back')) { b.removeEventListener('click', onBack); b.remove(); }
|
|
for (const [sel, as] of OWN_ARIA) for (const el of document.querySelectorAll('.DashboardPanel.Settings ' + sel)) for (const a of as) el.removeAttribute(a);
|
|
for (const a of ATTRS) for (const el of document.querySelectorAll('.DashboardPanel.Settings [' + a + ']')) el.removeAttribute(a);
|
|
},
|
|
status() {
|
|
return { name: ctx && ctx.name, runs, switches, backClicks, sections: secMap ? secMap.size : 0, offOn: !!offOn, back: !!document.querySelector('.lgs-c6b-back'), err: lastErr };
|
|
},
|
|
test: {
|
|
apply: safe,
|
|
steamPage,
|
|
// lab only (hold the Steam lab lock): show SteamVR's page for ms, then Steam's page again.
|
|
// Navigation only, the same FrameStore calls as theme/vr/pre/settings_open.js.
|
|
async open(ms) {
|
|
const s = steamPage();
|
|
if (!s) return 'no frame';
|
|
const t = s.f.pages.find((p) => p.m_sSummonOverlayKey === 'system.settings');
|
|
if (window.__LGS_SET_REVERT) clearTimeout(window.__LGS_SET_REVERT);
|
|
s.f.SwitchToPage(t.pageID ?? t.m_unPageID);
|
|
window.__LGS_SET_REVERT = setTimeout(() => { window.__LGS_SET_REVERT = 0; s.f.SwitchToPage(s.id); }, Math.min(Number(ms) || 9000, 120000));
|
|
for (let i = 0; i < 25 && !document.querySelector('.SettingsMainPanel'); i++) await new Promise((r) => setTimeout(r, 200));
|
|
await new Promise((r) => setTimeout(r, 1200));
|
|
return 'settings shown, back to Steam in ' + ms + ' ms';
|
|
},
|
|
},
|
|
};
|
|
})
|