Move adapter-wide controls into a settings dialog

This commit is contained in:
Joey Yakimowich-Payne 2026-09-10 07:58:35 -06:00
commit c7261c9338
4 changed files with 60 additions and 28 deletions

View file

@ -267,7 +267,9 @@ The Pico 2 W onboard LED reports the overall Bluetooth state:
### Joy-Con 2 player arrangement
In Controller Studio, use **Joy-Con 2 default mode** and **Apply default mode**.
In Controller Studio, open **Adapter settings** in the top bar, then use
**Joy-Con 2 default mode** and **Apply default mode**. Closing the settings
dialog preserves unapplied selections and does not change your profile draft.
This is a real adapter-wide setting, separate from the artwork preview:
- **Paired:** ready opposite halves form one player and use their L+R bank.

View file

@ -600,10 +600,14 @@ input[type="range"] { width: 100%; accent-color: var(--blue); }
.curve-controls output { float: right; color: var(--ink); }
.curve-controls input[type="range"] { display: block; margin-top: 8px; }
.copy-side { min-height: 34px; margin-top: auto; padding-block: 6px; }
.copy-dialog { width: min(440px, calc(100% - 28px)); padding: 24px; color: var(--ink); border: 1px solid var(--line); border-radius: 18px; background: var(--surface-solid); box-shadow: var(--shadow); }
.copy-dialog::backdrop { background: rgba(2, 5, 13, 0.76); }
.copy-dialog, .adapter-settings-dialog { width: min(440px, calc(100% - 28px)); padding: 24px; color: var(--ink); border: 1px solid var(--line); border-radius: 18px; background: var(--surface-solid); box-shadow: var(--shadow); }
.copy-dialog::backdrop, .adapter-settings-dialog::backdrop { background: rgba(2, 5, 13, 0.76); }
.copy-dialog h3 { margin-bottom: 18px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 22px; }
.adapter-settings-dialog { width: min(580px, calc(100% - 28px)); max-height: calc(100dvh - 28px); overflow-y: auto; }
.adapter-settings-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.adapter-settings-heading .button { flex: 0 0 auto; min-height: 44px; }
.adapter-settings-dialog .joycon-mode { margin-top: 20px; padding-bottom: 0; border-bottom: 0; }
@media (max-width: 1120px) {
.workspace { grid-template-columns: 250px minmax(0, 1fr); }
.profile-list { grid-template-columns: 1fr; }
@ -615,8 +619,10 @@ input[type="range"] { width: 100%; accent-color: var(--blue); }
.editor-header { align-items: flex-start; }
}
@media (max-width: 780px) {
.topbar { position: relative; min-height: 76px; }
.topbar { position: relative; min-height: 76px; flex-wrap: wrap; gap: 12px; }
.topbar-meta > p { display: none; }
.topbar-meta { width: 100%; justify-content: space-between; gap: 12px; }
.topbar-meta .adapter-settings-button { flex: 0 0 auto; min-height: 44px; }
.connection { max-width: 52%; }
.workspace { display: block; width: min(100% - 24px, 700px); margin-top: 14px; }
.sidebar { position: relative; top: auto; margin-bottom: 18px; max-height: none; overflow: visible; scrollbar-gutter: auto; }

View file

@ -23,6 +23,7 @@
<span class="connection-dot"></span>
<span id="connectionText">Connecting to adapter…</span>
</div>
<button class="button button-secondary adapter-settings-button" id="adapterSettingsButton" type="button" aria-haspopup="dialog" aria-controls="adapterSettingsDialog">Adapter settings</button>
</div>
</header>
@ -39,30 +40,6 @@
<select id="identitySelect" class="select"></select>
<p class="field-help">The default applies when a controller has no dedicated layout.</p>
<section class="joycon-mode" aria-labelledby="joyconModeTitle">
<p class="eyebrow">Adapter-wide setting</p>
<h3 id="joyconModeTitle">Joy-Con 2 default mode</h3>
<label class="field-label" for="joyconModeSelect">Default arrangement</label>
<select id="joyconModeSelect" class="select" aria-describedby="joyconModeHelp joyconModeStatus" disabled>
<option value="paired">Paired · L + R together</option>
<option value="individual">Individual · separate players</option>
</select>
<p class="field-help">Saved on the adapter; changing it applies live and clears temporary joins/splits. Not an artwork preview.</p>
<details class="joycon-mode-help">
<summary>Controller shortcut &amp; profile banks</summary>
<div id="joyconModeHelp">
<p><strong>Paired:</strong> L + R share one player and a separate L+R profile bank. Unmatched halves stay separate.</p>
<p><strong>Individual:</strong> each half is a sideways player using its own solo bank. Choose the L or R profile owner to edit it.</p>
<p><strong>Join / split this pair:</strong> hold <strong>ZL + Minus</strong> on the left and <strong>ZR + Plus</strong> on the right for <strong>2 seconds</strong>. A short pulse confirms success. Release all four buttons before using it again.</p>
<p>The shortcut affects only those halves and overrides this default until either disconnects. Other Joy-Cons are unchanged. Each half's complete two-button chord is reserved until both buttons release, even while waiting for the other half. Ambiguous solo selections do not join.</p>
<p>All saved banks and unsaved profile drafts are kept. Your chosen owner does not change automatically.</p>
<p>Saved preference is not a live pairing report. A pair waits for its L+R bank to be ready; the diagram and live playtest report the actual arrangement.</p>
</div>
</details>
<button class="button button-secondary" id="applyJoyconModeButton" type="button" disabled>Apply default mode</button>
<p class="joycon-mode-status" id="joyconModeStatus" data-state="loading" role="status" aria-live="polite">Reading saved preference…</p>
</section>
<div class="identity-tools">
<label class="field-label" for="controllerAlias">Controller alias</label>
<input class="number-input" id="controllerAlias" maxlength="31" placeholder="Optional nickname">
@ -454,6 +431,37 @@
</main>
<form id="macroCaptureSettings" hidden></form>
<dialog class="adapter-settings-dialog" id="adapterSettingsDialog" aria-labelledby="adapterSettingsTitle" aria-describedby="adapterSettingsDescription">
<div class="adapter-settings-heading">
<h2 id="adapterSettingsTitle">Adapter settings</h2>
<button class="button button-ghost" id="closeAdapterSettingsButton" type="button" autofocus>Close</button>
</div>
<p class="field-help" id="adapterSettingsDescription">These settings apply to the whole adapter, not the selected profile. Use Apply to save changes; closing keeps unapplied selections.</p>
<section class="joycon-mode" aria-labelledby="joyconModeTitle">
<p class="eyebrow">Adapter-wide setting</p>
<h3 id="joyconModeTitle">Joy-Con 2 default mode</h3>
<label class="field-label" for="joyconModeSelect">Default arrangement</label>
<select id="joyconModeSelect" class="select" aria-describedby="joyconModeHelp joyconModeStatus" disabled>
<option value="paired">Paired · L + R together</option>
<option value="individual">Individual · separate players</option>
</select>
<p class="field-help">Saved on the adapter; changing it applies live and clears temporary joins/splits. Not an artwork preview.</p>
<details class="joycon-mode-help">
<summary>Controller shortcut &amp; profile banks</summary>
<div id="joyconModeHelp">
<p><strong>Paired:</strong> L + R share one player and a separate L+R profile bank. Unmatched halves stay separate.</p>
<p><strong>Individual:</strong> each half is a sideways player using its own solo bank. Choose the L or R profile owner to edit it.</p>
<p><strong>Join / split this pair:</strong> hold <strong>ZL + Minus</strong> on the left and <strong>ZR + Plus</strong> on the right for <strong>2 seconds</strong>. A short pulse confirms success. Release all four buttons before using it again.</p>
<p>The shortcut affects only those halves and overrides this default until either disconnects. Other Joy-Cons are unchanged. Each half's complete two-button chord is reserved until both buttons release, even while waiting for the other half. Ambiguous solo selections do not join.</p>
<p>All saved banks and unsaved profile drafts are kept. Your chosen owner does not change automatically.</p>
<p>Saved preference is not a live pairing report. A pair waits for its L+R bank to be ready; the diagram and live playtest report the actual arrangement.</p>
</div>
</details>
<button class="button button-secondary" id="applyJoyconModeButton" type="button" disabled>Apply default mode</button>
<p class="joycon-mode-status" id="joyconModeStatus" data-state="loading" role="status" aria-live="polite">Reading saved preference…</p>
</section>
</dialog>
<dialog class="copy-dialog" id="copyDialog">
<form method="dialog">
<p class="eyebrow">Duplicate profile</p>

View file

@ -88,6 +88,9 @@ const elements = {
connection: document.querySelector("#connectionState"),
connectionText: document.querySelector("#connectionText"),
identity: document.querySelector("#identitySelect"),
adapterSettingsButton: document.querySelector("#adapterSettingsButton"),
adapterSettingsDialog: document.querySelector("#adapterSettingsDialog"),
closeAdapterSettings: document.querySelector("#closeAdapterSettingsButton"),
joyconMode: document.querySelector("#joyconModeSelect"),
applyJoyconMode: document.querySelector("#applyJoyconModeButton"),
joyconModeStatus: document.querySelector("#joyconModeStatus"),
@ -2523,6 +2526,19 @@ elements.refresh.addEventListener("click", async () => {
await loadLibrary(true);
});
elements.adapterSettingsButton.addEventListener("click", () => {
if (!elements.adapterSettingsDialog.open) {
renderJoyconMode();
elements.adapterSettingsDialog.showModal();
}
});
elements.closeAdapterSettings.addEventListener("click", () => {
elements.adapterSettingsDialog.close();
});
elements.adapterSettingsDialog.addEventListener("close", () => {
elements.adapterSettingsButton.focus();
});
elements.joyconMode.addEventListener("change", () => {
if (elements.joyconMode.disabled) return;
joyconMode.selected = elements.joyconMode.value;