Skip to content
Merged
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
35 changes: 18 additions & 17 deletions web/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
edgeSlotWriteTooltip as edgeOnboardSlotWriteTooltip,
emptyEdgeOnboardProfileState,
friendlyEdgeSlotsError,
isEdgeSlotsAgentRequiredMessage,
isEdgeTargetController,
shouldReadEdgeOnboardProfiles,
shouldResetEdgeOnboardProfiles
Expand Down Expand Up @@ -787,7 +788,8 @@
let requestedView: AppView | null = null;
const guardBounceMessages: Partial<Record<AppView, string>> = {
tuning: 'Tuning opens once a controller is connected.',
advancedButtonMapping: 'Button mapping needs a game selected in Tuning first.'
advancedButtonMapping: 'Button mapping needs a game selected in Tuning first.',
advancedEdgeSlots: 'Edge onboard slots need a DualSense Edge as the Target Controller.'
};

const syncViewFromHash = () => {
Expand All @@ -801,7 +803,12 @@
};

const navigateToView = (view: AppView) => {
view = guardView(view, { tuningReady, buttonMappingReady, edgeSlotsReady });
const requested = view;
view = guardView(requested, { tuningReady, buttonMappingReady, edgeSlotsReady });
if (view !== requested) {
const message = guardBounceMessages[requested];
if (message) showToast(message, 'info');
}
activeView = view;
// An explicit navigation always wins over a parked deep-link intent.
requestedView = null;
Expand Down Expand Up @@ -1541,7 +1548,7 @@
await loadEdgeProfiles(controller.id, true);
} catch (caught) {
edgeProfilesError = friendlyEdgeSlotsError(caught, 'Unable to write Edge onboard slot.');
showToast(edgeProfilesError, 'error');
showToast(edgeProfilesError, isEdgeSlotsAgentRequiredMessage(edgeProfilesError) ? 'info' : 'error');
} finally {
edgeProfilesBusySlot = '';
}
Expand Down Expand Up @@ -2288,9 +2295,9 @@
<button class="solid-action compact" type="button" onclick={refresh}>Retry</button>
</section>
{:else if snapshot}
<!-- Utility row: cross-view context that has no single page home the
target controller for writes, the web UI bind address, the Forza glyph
override, and a compact system readout. -->
<!-- Utility row: cross-view context that has no single page home: the
target controller for writes, the web UI bind address, and a compact
system readout. -->
<section class="app-toolbar" class:open={toolbarOpen} aria-label="Controller and display options">
<button
class="app-toolbar-disclosure"
Expand Down Expand Up @@ -2337,17 +2344,6 @@
<option value="lan">LAN Access</option>
</select>
</label>
<button
class="app-toolbar-toggle"
class:active={glyphOverrideEnabled}
type="button"
disabled={appSettingsBusy}
aria-pressed={glyphOverrideEnabled}
title={forzaGlyphs?.lastStatus ?? glyphInstallPath}
onclick={() => void updateForzaGlyphOverride()}
>
Controller Glyphs: {glyphOverrideEnabled ? 'PlayStation Icons' : 'Game Default'}
</button>
<div class="app-toolbar-spacer"></div>
<div
class="app-toolbar-readout"
Expand Down Expand Up @@ -2488,6 +2484,10 @@
onSetStickDeadzone={setStickDeadzone}
onStartInputBridge={startControllerInputBridge}
onStopInputBridge={stopControllerInputBridge}
{glyphOverrideEnabled}
glyphOverrideBusy={appSettingsBusy}
glyphOverrideTitle={forzaGlyphs?.lastStatus ?? glyphInstallPath}
onToggleGlyphOverride={updateForzaGlyphOverride}
{supportBundleBusy}
onDownloadSupportBundle={exportSupportBundle}
/>
Expand All @@ -2501,6 +2501,7 @@
{edgeProfilesLoading}
{edgeProfilesBusySlot}
{edgeProfilesError}
edgeProfilesAgentRequired={isEdgeSlotsAgentRequiredMessage(edgeProfilesError)}
{edgeSlotsReadTooltip}
edgeSlotWriteLabel={edgeSlotWriteLabel()}
onRefreshEdgeProfiles={() => controller && void loadEdgeProfiles(controller.id, true)}
Expand Down
7 changes: 6 additions & 1 deletion web/src/app/edgeOnboardProfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,15 @@ export function edgeSlotWriteLabel(edgeProfiles: EdgeProfilesResponse | null): s
* helper handles both the read and the write path, so the copy can't claim a
* direction.
*/
export const EDGE_SLOTS_AGENT_REQUIRED_MESSAGE = 'Onboard slots need DSCC running.';

export const isEdgeSlotsAgentRequiredMessage = (message: string): boolean =>
message === EDGE_SLOTS_AGENT_REQUIRED_MESSAGE;

export const friendlyEdgeSlotsError = (caught: unknown, fallback: string): string => {
const message = caught instanceof Error ? caught.message : fallback;
return message.includes('requires the real DSCC agent')
? 'Onboard slots need DSCC running.'
? EDGE_SLOTS_AGENT_REQUIRED_MESSAGE
: message;
};

Expand Down
8 changes: 5 additions & 3 deletions web/src/components/AppSidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
const itemTooltip = (id: AppView): string => {
if (id === 'tuning' && !readiness.tuningReady) return 'Select a controller before tuning haptics.';
if (id === 'advancedButtonMapping' && !readiness.buttonMappingReady) {
return 'Select a game or local app scope before editing mappings.';
return 'Pick a supported game in Tuning to map its buttons.';
}
if (id === 'advancedEdgeSlots' && !readiness.edgeSlotsReady) {
return 'Onboard slots are available when the Target Controller is a DualSense Edge.';
Expand All @@ -50,9 +50,10 @@
<button
class="sidebar-item"
class:active={view === item.id}
class:disabled={itemDisabled(item.id)}
type="button"
title={itemTooltip(item.id)}
disabled={itemDisabled(item.id)}
aria-disabled={itemDisabled(item.id) ? 'true' : undefined}
aria-current={view === item.id ? 'page' : undefined}
onclick={() => onNavigate(item.id)}
>{item.label}</button>
Expand All @@ -68,9 +69,10 @@
<button
class="sidebar-item sidebar-sub"
class:active={view === item.id}
class:disabled={itemDisabled(item.id)}
type="button"
title={itemTooltip(item.id)}
disabled={itemDisabled(item.id)}
aria-disabled={itemDisabled(item.id) ? 'true' : undefined}
aria-current={view === item.id ? 'page' : undefined}
onclick={() => onNavigate(item.id)}
>{item.label}</button>
Expand Down
23 changes: 23 additions & 0 deletions web/src/lib/features/controllers/ControllersView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
export let onSetStickDeadzone: (side: 'left' | 'right', value: number) => void | Promise<void> = () => {};
export let onStartInputBridge: () => void | Promise<void> = () => {};
export let onStopInputBridge: () => void | Promise<void> = () => {};
export let glyphOverrideEnabled = false;
export let glyphOverrideBusy = false;
export let glyphOverrideTitle = '';
export let onToggleGlyphOverride: () => void | Promise<void> = () => {};
export let supportBundleBusy: 'copy' | 'download' | '' = '';
export let onDownloadSupportBundle: () => void | Promise<void> = () => {};

Expand Down Expand Up @@ -614,6 +618,25 @@
</div>
{/if}

<div class="ctl-group narrow">
<div class="lbl">Button icons</div>
<div class="ctl-setting-row">
<div>
<strong>Forza button icons</strong>
<span>Show PlayStation icons where DSCC manages the supported Forza icon files.</span>
</div>
<button
type="button"
class="ctl-button"
class:active={glyphOverrideEnabled}
disabled={glyphOverrideBusy}
aria-pressed={glyphOverrideEnabled}
title={glyphOverrideTitle}
onclick={() => void onToggleGlyphOverride()}
>{glyphOverrideEnabled ? 'PlayStation icons' : 'Game default'}</button>
</div>
</div>

<div class="ctl-group narrow">
<div class="lbl">Support</div>
<div class="ctl-support">
Expand Down
15 changes: 13 additions & 2 deletions web/src/lib/features/controllers/EdgeSlotsView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
export let edgeProfilesLoading = false;
export let edgeProfilesBusySlot = '';
export let edgeProfilesError = '';
export let edgeProfilesAgentRequired = false;
export let edgeSlotsReadTooltip = '';
export let edgeSlotWriteLabel = 'Write';
export let onRefreshEdgeProfiles: () => void | Promise<void> = () => {};
Expand All @@ -25,7 +26,7 @@
$: alias = controller?.name || controller?.family || 'No controller';
</script>

<section class="ctl-view" aria-label="Edge onboard slots">
<section class="ctl-view edge-slots-view" aria-label="Edge onboard slots">
<div class="ctl-head">
<h1 class="ctl-title">Edge onboard slots</h1>
<span class="ctl-sub">{alias} &middot; profiles stored on the controller itself, for checking, not for everyday tuning</span>
Expand All @@ -49,7 +50,17 @@
</div>

{#if edgeProfilesError}
<p class="ctl-note error">{edgeProfilesError}</p>
{#if edgeProfilesAgentRequired}
<p class="ctl-note status info">
<span class="ctl-note-label">Info</span>
<span>{edgeProfilesError} Start DSCC to read onboard slots.</span>
</p>
{:else}
<p class="ctl-note status error" role="alert">
<span class="ctl-note-label">Error</span>
<span>{edgeProfilesError}</span>
</p>
{/if}
{:else if edgeProfiles?.warning}
<p class="ctl-note">{edgeProfiles.warning}</p>
{/if}
Expand Down
3 changes: 2 additions & 1 deletion web/src/lib/features/tuning/TuningHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,10 @@
aria-haspopup="menu"
aria-expanded={profileMenuOpen}
disabled={!profiles.length}
title="Choose the saved profile to edit in this tuning scope."
onclick={toggleProfileMenu}
>
<span class="tuning-profile-label">Profile:</span>
<span class="tuning-profile-label">Saved profile:</span>
<strong>{profileName}</strong>
<span class="tuning-caret" aria-hidden="true">▾</span>
</button>
Expand Down
64 changes: 64 additions & 0 deletions web/src/styles/controllers.css
Original file line number Diff line number Diff line change
Expand Up @@ -318,16 +318,61 @@
color: var(--ink-muted);
}

.ctl-note.status {
display: flex;
align-items: flex-start;
gap: 6px;
}

.ctl-note-label {
flex: none;
font-weight: 700;
color: var(--ink);
}

.ctl-note.error {
color: var(--danger);
}

.ctl-note.error .ctl-note-label {
color: var(--danger);
}

.ctl-suggestions {
display: grid;
gap: 6px;
margin-top: 8px;
}

.ctl-setting-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}

.ctl-setting-row > div {
display: grid;
gap: 2px;
min-width: 0;
}

.ctl-setting-row strong {
font-size: 12px;
font-weight: 600;
color: var(--ink);
}

.ctl-setting-row span {
font-size: 11px;
line-height: 1.45;
color: var(--ink-muted);
}

.ctl-setting-row .ctl-button {
flex: none;
}

/* Support group */
.ctl-support {
display: grid;
Expand All @@ -337,6 +382,25 @@
}

/* Edge onboard slots */
.edge-slots-view {
max-width: 980px;
margin-inline: auto;
}

@media (min-width: 1800px) {
.edge-slots-view {
padding-top: 44px;
}

.edge-slots-view .ctl-groups {
max-width: 980px;
}

.edge-slots-view .ctl-group {
max-width: 560px;
}
}

.edge-slot-list {
display: grid;
}
Expand Down
10 changes: 9 additions & 1 deletion web/src/styles/shell-v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ html {
background: color-mix(in srgb, var(--surface-raised) 60%, transparent);
}

.sidebar-item.disabled,
.sidebar-item.disabled:hover {
color: var(--ink-muted);
background: none;
cursor: not-allowed;
opacity: 0.48;
}

.sidebar-item.active {
color: var(--ink);
background: var(--surface-raised);
Expand Down Expand Up @@ -125,7 +133,7 @@ html {
}

/* Utility row above the views — cross-view context (target controller, web UI
bind address, glyph override, system readout). */
bind address, system readout). */
.app-toolbar {
display: flex;
flex-direction: column;
Expand Down
16 changes: 16 additions & 0 deletions web/src/styles/status.css
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,19 @@
min-height: calc(100dvh - 130px); /* viewport minus measured app chrome */
}
}

/* Very large windows: keep the user's wide/tall centering choice through the
1200-1600px band, then cap the dead-band so Status stays near the toolbar. */
@media (min-width: 1800px) and (min-height: 900px) {
.status-view {
justify-content: flex-start;
min-height: auto;
max-width: 1180px;
margin-inline: auto;
padding-top: 56px;
}

.status-groups {
max-width: 1180px;
}
}
Loading