Skip to content

Commit 3e8a0f9

Browse files
committed
Fix reader controls and editor sidebars (#24 #29)
1 parent fad2da7 commit 3e8a0f9

19 files changed

Lines changed: 312 additions & 113 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ Rule format:
8686
- Teleprompter `Read Width` must map honestly to the visible reading lane: at `100%` it must not keep extra internal container padding or shrink-to-content gutters that make the text block visibly narrower than the width guides.
8787
- Teleprompter reader media controls must keep background media and recording capture as separate concepts: background camera or video-file playback is stage scenery, while recording explicitly captures microphone-only or camera-plus-microphone from user-selected devices that default to Settings selections.
8888
- Teleprompter reader background-media controls must look visually distinct from recording capture controls; background media is the layer for camera preview, video files, or video URLs, while recording camera controls choose what gets captured.
89+
- Teleprompter reader background-media controls must not use picture-in-picture/card-in-card, file-only, or recording-camera icons; use a distinct background media source/layer icon so the affordance reads as choosing a background source that may be live camera, local video file, or URL/video media.
8990
- Teleprompter reader recording must show a live, unobtrusive audio-level indicator while armed or recording so the user can confirm microphone input is present before and during rehearsal capture.
9091
- Teleprompter reader recording controls must stay compact in the transport bar: use icon-sized buttons/dropdowns for record mode and capture devices, and surface active capture with a small live indicator instead of wide text-heavy controls.
9192
- Browser media settings must expose supported audio/video capture options used by the app and LiveKit path, including device selection and audio processing controls such as echo cancellation, noise suppression, automatic gain control, voice isolation when available, channel count, sample rate, and sample size where the browser/runtime supports them.

src/PrompterOne.Shared/Components/UiIcon.razor

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
<circle cx="18" cy="16" r="3" />
2222
</svg>
2323
break;
24+
case UiIconKind.BackgroundMedia:
25+
<svg class="@CssClassValue" width="@Size" height="@Size" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="@StrokeWidth" stroke-linecap="round" stroke-linejoin="round" aria-hidden="@AriaHidden">
26+
<rect x="3" y="5" width="18" height="14" rx="3" />
27+
<path d="M6.5 16.5 10 13l2.5 2.5 2-2 3 3" />
28+
<polygon points="10.5,8.5 15,11 10.5,13.5" />
29+
</svg>
30+
break;
2431
case UiIconKind.BookOpen:
2532
<svg class="@CssClassValue" width="@Size" height="@Size" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="@StrokeWidth" aria-hidden="@AriaHidden">
2633
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />

src/PrompterOne.Shared/Components/UiIconKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public enum UiIconKind
66
ArrowLeft,
77
ArrowRight,
88
AudioNotes,
9+
BackgroundMedia,
910
BookOpen,
1011
Broadcast,
1112
Camera,

src/PrompterOne.Shared/Contracts/UiTestIds.Editor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ public static class Editor
161161
public const string StatusSegments = "editor-status-segments";
162162
public const string StatusVersion = "editor-status-version";
163163
public const string StatusWords = "editor-status-words";
164+
public const string StructureSidebar = "editor-structure-sidebar";
165+
public const string StructureSidebarToggle = "editor-structure-sidebar-toggle";
164166
public const string SourceTab = "editor-source-tab";
165167
public const string SplitSegment = "editor-split-segment";
166168
public const string SplitHint = "editor-split-hint";

src/PrompterOne.Shared/Editor/Components/EditorMetadataRail.razor

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@
5757
aria-controls="@UiDomIds.Editor.MetadataRailBody"
5858
aria-expanded="@GetExpandedAttributeValue()"
5959
aria-label="@ToggleLabel"
60-
data-chevron-direction="@GetToggleChevronDirection()"
60+
title="@ToggleLabel"
61+
data-sidebar-icon="right"
6162
@onclick="() => ToggleRequested.InvokeAsync()"
6263
data-test="@UiTestIds.Editor.MetadataRailToggle">
63-
<UiIcon Kind="@(IsCollapsed ? UiIconKind.ChevronLeft : UiIconKind.ChevronRight)"
64-
Size="14"
65-
StrokeWidth="2.2m" />
64+
<UiIcon Kind="UiIconKind.PanelRight"
65+
Size="16"
66+
StrokeWidth="2.1m" />
6667
</button>
6768
</div>
6869

@@ -198,8 +199,6 @@
198199

199200
[Parameter] public EventCallback<int> XslowOffsetChanged { get; set; }
200201

201-
private const string LeftChevronDirection = "left";
202-
private const string RightChevronDirection = "right";
203202
private EditorMetadataRailTab _selectedTab = EditorMetadataRailTab.Metadata;
204203

205204
protected override void OnParametersSet()
@@ -215,8 +214,6 @@
215214

216215
private string GetExpandedAttributeValue() => IsCollapsed ? "false" : "true";
217216

218-
private string GetToggleChevronDirection() => IsCollapsed ? LeftChevronDirection : RightChevronDirection;
219-
220217
private async Task SelectTabAsync(EditorMetadataRailTab tab)
221218
{
222219
if (_selectedTab == tab)

src/PrompterOne.Shared/Editor/Components/EditorStructureSidebar.razor

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,60 @@
44
@using PrompterOne.Shared.Localization
55
@inject IStringLocalizer<SharedResource> Localizer
66

7-
<div class="ed-sidebar">
8-
<div class="ed-structure">
9-
<div class="ed-section-label">@Text(UiTextKey.EditorStructureSection)</div>
10-
<div class="ed-tree">
11-
@foreach (var segment in Segments)
12-
{
13-
var segmentIsActive = segment.Index == ActiveSegmentIndex;
14-
<button class="@GetSegmentCss(segment, segmentIsActive)"
15-
data-nav="seg-@segment.Index"
16-
data-active="@(segmentIsActive ? ActiveStateValue : InactiveStateValue)"
17-
data-test="@UiTestIds.Editor.SegmentNavigation(segment.Index)"
18-
type="button"
19-
@onclick="() => OnNavigate.InvokeAsync(new EditorNavigationTarget(segment.Index, null, segment.StartIndex, segment.EndIndex))">
20-
<div class="ed-tree-dot"></div>
21-
<span class="ed-tree-seg__name" title="@segment.Name">@segment.Name</span>
22-
<small title="@segment.EmotionLabel">@segment.EmotionLabel</small>
23-
</button>
24-
<div class="ed-tree-children">
25-
@foreach (var block in segment.Blocks)
26-
{
27-
var blockIsActive = segment.Index == ActiveSegmentIndex && block.Index == ActiveBlockIndex;
28-
<button class="ed-tree-block @(blockIsActive ? "active" : null)"
29-
data-nav="blk-@segment.Index-@block.Index"
30-
data-active="@(blockIsActive ? ActiveStateValue : InactiveStateValue)"
31-
data-test="@UiTestIds.Editor.BlockNavigation(segment.Index, block.Index)"
32-
type="button"
33-
@onclick="() => OnNavigate.InvokeAsync(new EditorNavigationTarget(segment.Index, block.Index, block.StartIndex, block.EndIndex))">
34-
<span class="ed-tree-block__name" title="@block.Name">@block.Name</span>
35-
<code>@block.TargetWpmLabel</code>
36-
</button>
37-
}
38-
</div>
39-
}
40-
</div>
7+
<div class="ed-sidebar"
8+
data-collapsed="@(IsCollapsed ? ActiveStateValue : InactiveStateValue)"
9+
data-test="@UiTestIds.Editor.StructureSidebar">
10+
<div class="ed-sidebar-topbar">
11+
<button type="button"
12+
class="ed-sidebar-toggle"
13+
aria-controls="editor-structure-sidebar-body"
14+
aria-expanded="@(!IsCollapsed ? "true" : "false")"
15+
aria-label="@ToggleLabel"
16+
title="@ToggleLabel"
17+
data-sidebar-icon="left"
18+
data-test="@UiTestIds.Editor.StructureSidebarToggle"
19+
@onclick="() => ToggleRequested.InvokeAsync()">
20+
<UiIcon Kind="UiIconKind.PanelLeft" Size="16" StrokeWidth="2.1m" />
21+
</button>
4122
</div>
4223

24+
@if (!IsCollapsed)
25+
{
26+
<div id="editor-structure-sidebar-body" class="ed-structure">
27+
<div class="ed-section-label">@Text(UiTextKey.EditorStructureSection)</div>
28+
<div class="ed-tree">
29+
@foreach (var segment in Segments)
30+
{
31+
var segmentIsActive = segment.Index == ActiveSegmentIndex;
32+
<button class="@GetSegmentCss(segment, segmentIsActive)"
33+
data-nav="seg-@segment.Index"
34+
data-active="@(segmentIsActive ? ActiveStateValue : InactiveStateValue)"
35+
data-test="@UiTestIds.Editor.SegmentNavigation(segment.Index)"
36+
type="button"
37+
@onclick="() => OnNavigate.InvokeAsync(new EditorNavigationTarget(segment.Index, null, segment.StartIndex, segment.EndIndex))">
38+
<div class="ed-tree-dot"></div>
39+
<span class="ed-tree-seg__name" title="@segment.Name">@segment.Name</span>
40+
<small title="@segment.EmotionLabel">@segment.EmotionLabel</small>
41+
</button>
42+
<div class="ed-tree-children">
43+
@foreach (var block in segment.Blocks)
44+
{
45+
var blockIsActive = segment.Index == ActiveSegmentIndex && block.Index == ActiveBlockIndex;
46+
<button class="ed-tree-block @(blockIsActive ? "active" : null)"
47+
data-nav="blk-@segment.Index-@block.Index"
48+
data-active="@(blockIsActive ? ActiveStateValue : InactiveStateValue)"
49+
data-test="@UiTestIds.Editor.BlockNavigation(segment.Index, block.Index)"
50+
type="button"
51+
@onclick="() => OnNavigate.InvokeAsync(new EditorNavigationTarget(segment.Index, block.Index, block.StartIndex, block.EndIndex))">
52+
<span class="ed-tree-block__name" title="@block.Name">@block.Name</span>
53+
<code>@block.TargetWpmLabel</code>
54+
</button>
55+
}
56+
</div>
57+
}
58+
</div>
59+
</div>
60+
}
4361
</div>
4462

4563
@code {
@@ -50,10 +68,16 @@
5068

5169
[Parameter] public int ActiveSegmentIndex { get; set; }
5270

71+
[Parameter] public bool IsCollapsed { get; set; }
72+
5373
[Parameter] public EventCallback<EditorNavigationTarget> OnNavigate { get; set; }
5474

5575
[Parameter] public IReadOnlyList<EditorOutlineSegmentViewModel> Segments { get; set; } = [];
5676

77+
[Parameter] public string ToggleLabel { get; set; } = string.Empty;
78+
79+
[Parameter] public EventCallback ToggleRequested { get; set; }
80+
5781
private static string GetEmotionClass(EditorOutlineSegmentViewModel segment) =>
5882
segment.EmotionKey switch
5983
{

src/PrompterOne.Shared/Editor/Pages/EditorPage.Layout.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,27 @@ public partial class EditorPage
99
[Inject] private IStringLocalizer<SharedResource> Localizer { get; set; } = null!;
1010

1111
private bool _isMetadataRailCollapsed;
12+
private bool _isStructureSidebarCollapsed;
1213

1314
private Task OnMetadataRailToggleAsync()
1415
{
1516
_isMetadataRailCollapsed = !_isMetadataRailCollapsed;
1617
return Task.CompletedTask;
1718
}
1819

20+
private Task OnStructureSidebarToggleAsync()
21+
{
22+
_isStructureSidebarCollapsed = !_isStructureSidebarCollapsed;
23+
return Task.CompletedTask;
24+
}
25+
1926
private string GetMetadataRailToggleLabel() =>
2027
_isMetadataRailCollapsed
2128
? Localizer[UiTextKey.EditorMetadataToggleExpand.ToString()]
2229
: Localizer[UiTextKey.EditorMetadataToggleCollapse.ToString()];
30+
31+
private string GetStructureSidebarToggleLabel() =>
32+
_isStructureSidebarCollapsed
33+
? Localizer[UiTextKey.LibraryOpenSidebar.ToString()]
34+
: Localizer[UiTextKey.LibraryCloseSidebar.ToString()];
2335
}

src/PrompterOne.Shared/Editor/Pages/EditorPage.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
<EditorStructureSidebar Segments="@_segments"
2020
ActiveSegmentIndex="@_activeSegmentIndex"
2121
ActiveBlockIndex="@_activeBlockIndex"
22+
IsCollapsed="@_isStructureSidebarCollapsed"
23+
ToggleLabel="@GetStructureSidebarToggleLabel()"
24+
ToggleRequested="OnStructureSidebarToggleAsync"
2225
OnNavigate="OnNavigateAsync" />
2326
}
2427

src/PrompterOne.Shared/Teleprompter/Pages/TeleprompterPage.razor

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,10 @@
5858
FontValue="@_readerFontSize" FocalMax="@ReaderMaxFocalPointPercent" FocalMin="@ReaderMinFocalPointPercent"
5959
FocalTitle="@ReaderFocalSliderTitle" FocalValue="@_readerFocalPointPercent" OnFontInput="HandleReaderFontSizeInputAsync"
6060
OnFocalInput="HandleReaderFocalPointInputAsync" OnWidthInput="HandleReaderTextWidthInputAsync"
61-
OnSpeedDialInput="HandleReaderSpeedDialInputAsync"
6261
OnSetSpeedCueDisplayMultiplier="@(() => SetReaderSpeedCueDisplayModeAsync(ReaderSpeedCueDisplayMode.Multiplier))"
6362
OnSetSpeedCueDisplayWpm="@(() => SetReaderSpeedCueDisplayModeAsync(ReaderSpeedCueDisplayMode.WordsPerMinute))"
6463
SpeedCueDisplayMode="@_readerSpeedCueDisplayMode" SpeedCueDisplayMultiplierTitle="@ReaderSpeedCueDisplayMultiplierTitle"
6564
SpeedCueDisplayTitle="@ReaderSpeedCueDisplayTitle" SpeedCueDisplayWpmTitle="@ReaderSpeedCueDisplayWpmTitle"
66-
SpeedDialMax="@ReaderSpeedDialMaximum" SpeedDialMin="@ReaderSpeedDialMinimum"
67-
SpeedDialTitle="@ReaderSpeedDialTitle" SpeedDialValue="@BuildReaderSpeedDialValue()"
68-
SpeedDialValueLabel="@BuildReaderSpeedDialValueLabel()"
6965
TooltipCssClassFactory="BuildRailTooltipCssClass" TooltipDomIdFactory="BuildRailTooltipDomId"
7066
TooltipFocusIn="HandleRailTooltipFocusIn" TooltipFocusOut="HandleRailTooltipFocusOut"
7167
TooltipKeyDown="HandleRailTooltipKeyDown" TooltipPointerDown="HandleRailTooltipPointerDown"
@@ -232,7 +228,10 @@
232228
RecordingStatusLabel="@ReaderRecordingStatusLabel" RecordingStopLabel="@ReaderRecordingStopLabel"
233229
RecordingToggleTooltip="@ReaderRecordingToggleTooltip" SelectedRecordingCameraId="@_readerRecordingCameraId"
234230
SelectedRecordingMicrophoneId="@_readerRecordingMicrophoneId"
235-
SpeedUpTooltip="@ReaderSpeedUpTooltip" SpeedValueLabel="@BuildReaderSpeedLabel()"
231+
SpeedDialMax="@ReaderSpeedDialMaximum" SpeedDialMin="@ReaderSpeedDialMinimum"
232+
SpeedDialTitle="@ReaderSpeedDialTitle" SpeedDialValue="@BuildReaderSpeedDialValue()"
233+
SpeedDialValueLabel="@BuildReaderSpeedDialValueLabel()" SpeedUpTooltip="@ReaderSpeedUpTooltip"
234+
SpeedValueLabel="@BuildReaderSpeedLabel()" OnSpeedDialInput="HandleReaderSpeedDialInputAsync"
236235
TimeDataAttributes="@BuildReaderTimeDataAttributes()" />
237236
</div>
238237
</div>

src/PrompterOne.Shared/Teleprompter/Pages/TeleprompterReaderSliders.razor

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -85,45 +85,6 @@
8585
role="tooltip"
8686
data-test="@BuildTooltipTestId(UiTestIds.Teleprompter.AlignmentTooltipWidthKey)">@WidthTitle</span>
8787
</div>
88-
<div class="rd-rail-tip-anchor"
89-
@onmouseenter="@(() => HandleTooltipPointerEnter(UiTestIds.Teleprompter.SpeedDialTooltipKey))"
90-
@onmouseleave="@(() => HandleTooltipPointerLeave(UiTestIds.Teleprompter.SpeedDialTooltipKey))"
91-
@onfocusin="@(() => HandleTooltipFocusIn(UiTestIds.Teleprompter.SpeedDialTooltipKey))"
92-
@onfocusout="@(() => HandleTooltipFocusOut(UiTestIds.Teleprompter.SpeedDialTooltipKey))"
93-
@onpointerdown="@(() => HandleTooltipPointerDown(UiTestIds.Teleprompter.SpeedDialTooltipKey))"
94-
@onkeydown="@((KeyboardEventArgs args) => HandleTooltipKeyDown(UiTestIds.Teleprompter.SpeedDialTooltipKey, args))">
95-
<div class="rd-slider-group rd-speed-dial-group">
96-
<svg class="rd-slider-icon"
97-
aria-hidden="true"
98-
width="14"
99-
height="14"
100-
viewBox="0 0 24 24"
101-
fill="none"
102-
stroke="currentColor"
103-
stroke-width="2">
104-
<path d="M12 15l3-3" />
105-
<path d="M4 14a8 8 0 1 1 16 0" />
106-
<path d="M5 19h14" />
107-
</svg>
108-
<input type="range"
109-
class="rd-vslider rd-speed-dial"
110-
id="@UiDomIds.Teleprompter.SpeedDial"
111-
min="@SpeedDialMin"
112-
max="@SpeedDialMax"
113-
value="@SpeedDialValue"
114-
@oninput="OnSpeedDialInput"
115-
@onkeydown:stopPropagation="true"
116-
aria-label="@SpeedDialTitle"
117-
data-test="@UiTestIds.Teleprompter.SpeedDial">
118-
<span class="rd-slider-val"
119-
id="@UiDomIds.Teleprompter.SpeedDialValue"
120-
data-test="@UiTestIds.Teleprompter.SpeedDialValue">@SpeedDialValueLabel</span>
121-
</div>
122-
<span id="@BuildTooltipDomId(UiTestIds.Teleprompter.SpeedDialTooltipKey)"
123-
class="@BuildTooltipCssClass(UiTestIds.Teleprompter.SpeedDialTooltipKey, false)"
124-
role="tooltip"
125-
data-test="@BuildTooltipTestId(UiTestIds.Teleprompter.SpeedDialTooltipKey)">@SpeedDialTitle</span>
126-
</div>
12788
<div class="rd-rail-tip-anchor"
12889
@onmouseenter="@(() => HandleTooltipPointerEnter(UiTestIds.Teleprompter.SpeedCueDisplayTooltipKey))"
12990
@onmouseleave="@(() => HandleTooltipPointerLeave(UiTestIds.Teleprompter.SpeedCueDisplayTooltipKey))"
@@ -174,17 +135,11 @@
174135
[Parameter, EditorRequired] public EventCallback<ChangeEventArgs> OnFocalInput { get; set; }
175136
[Parameter, EditorRequired] public EventCallback OnSetSpeedCueDisplayMultiplier { get; set; }
176137
[Parameter, EditorRequired] public EventCallback OnSetSpeedCueDisplayWpm { get; set; }
177-
[Parameter, EditorRequired] public EventCallback<ChangeEventArgs> OnSpeedDialInput { get; set; }
178138
[Parameter, EditorRequired] public EventCallback<ChangeEventArgs> OnWidthInput { get; set; }
179139
[Parameter] public ReaderSpeedCueDisplayMode SpeedCueDisplayMode { get; set; } = ReaderSettingsDefaults.SpeedCueDisplayMode;
180140
[Parameter, EditorRequired] public string SpeedCueDisplayMultiplierTitle { get; set; } = string.Empty;
181141
[Parameter, EditorRequired] public string SpeedCueDisplayTitle { get; set; } = string.Empty;
182142
[Parameter, EditorRequired] public string SpeedCueDisplayWpmTitle { get; set; } = string.Empty;
183-
[Parameter] public int SpeedDialMax { get; set; }
184-
[Parameter] public int SpeedDialMin { get; set; }
185-
[Parameter] public int SpeedDialValue { get; set; }
186-
[Parameter, EditorRequired] public string SpeedDialTitle { get; set; } = string.Empty;
187-
[Parameter, EditorRequired] public string SpeedDialValueLabel { get; set; } = string.Empty;
188143
[Parameter] public Action<string>? TooltipFocusIn { get; set; }
189144
[Parameter] public Action<string>? TooltipFocusOut { get; set; }
190145
[Parameter] public Action<string, KeyboardEventArgs>? TooltipKeyDown { get; set; }

0 commit comments

Comments
 (0)