Skip to content

Commit 90b85a1

Browse files
committed
Add emoji emotion markers (#22)
1 parent ccb1359 commit 90b85a1

13 files changed

Lines changed: 176 additions & 21 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
namespace PrompterOne.Shared.Contracts;
2+
3+
public static class TpsEmotionMarkers
4+
{
5+
public static string ResolveMarker(string? emotion) =>
6+
Normalize(emotion) switch
7+
{
8+
"warm" => "😊",
9+
"concerned" => "😟",
10+
"focused" => "🎯",
11+
"motivational" => "💪",
12+
"urgent" => "🚨",
13+
"happy" => "😄",
14+
"excited" => "🚀",
15+
"sad" => "😢",
16+
"calm" => "😌",
17+
"energetic" => "⚡",
18+
"professional" => "💼",
19+
"neutral" => "😐",
20+
_ => string.Empty
21+
};
22+
23+
private static string Normalize(string? emotion) =>
24+
string.IsNullOrWhiteSpace(emotion)
25+
? string.Empty
26+
: emotion.Trim().ToLowerInvariant();
27+
}

src/PrompterOne.Shared/Contracts/UiDataAttributes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public static class UiDataAttributes
44
{
55
public static class Editor
66
{
7+
public const string EmotionMarker = "data-editor-emotion-marker";
78
public const string IconShape = "data-editor-icon-shape";
89
public const string IconShapeDot = "dot";
910
public const string IconShapeGlyph = "glyph";
@@ -20,6 +21,7 @@ public static class Teleprompter
2021
public const string CardState = "data-reader-card-state";
2122
public const string DurationMilliseconds = "data-ms";
2223
public const string EffectiveWordsPerMinute = "data-effective-wpm";
24+
public const string EmotionMarker = "data-reader-emotion-marker";
2325
public const string OriginalText = "data-original-text";
2426
public const string PauseMilliseconds = "data-pause-ms";
2527
public const string Pronunciation = "data-pronunciation";

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public static class Teleprompter
8080
public static string CardWord(int cardIndex, int groupIndex, int wordIndex) =>
8181
$"teleprompter-card-word-{cardIndex}-{groupIndex}-{wordIndex}";
8282

83+
public static string CardWordEmotionMarker(int cardIndex, int groupIndex, int wordIndex) =>
84+
$"teleprompter-emotion-marker-{cardIndex}-{groupIndex}-{wordIndex}";
85+
86+
public static string CardWordEmotionMarkerPrefix(int cardIndex) => $"teleprompter-emotion-marker-{cardIndex}-";
87+
8388
public static string CardWordPrefix(int cardIndex) => $"teleprompter-card-word-{cardIndex}-";
8489

8590
public static string CardWordSpeedCueLabel(int cardIndex, int groupIndex, int wordIndex) =>

src/PrompterOne.Shared/Editor/Components/EditorActionContentModels.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,9 @@ public static EditorActionContentDescriptor Emotion(
140140
string metaText,
141141
EditorActionContentTone tone) =>
142142
new(
143-
LeadingText: "●",
143+
LeadingText: metaText,
144144
LeadingTone: tone,
145145
Label: label,
146-
MetaText: metaText,
147146
LeadingBold: true);
148147

149148
public static EditorActionContentDescriptor LabelTrigger(

src/PrompterOne.Shared/Editor/Components/EditorSourcePanel.razor.css

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -425,41 +425,62 @@ body.theme-light .ed-main ::deep .monaco-editor .line-numbers.active-line-number
425425
color:#796B57 !important;
426426
}
427427
.ed-main ::deep .mk-emo-warm,
428-
.ed-main ::deep .po-inline-emotion-warm{color:#FFB840;}
428+
.ed-main ::deep .po-inline-emotion-warm{--ed-emotion-marker:"😊";color:#FFB840;}
429429
.ed-main ::deep .po-tag-emotion-warm{--ed-tag-color:#FFB840;}
430430
.ed-main ::deep .mk-emo-concerned,
431-
.ed-main ::deep .po-inline-emotion-concerned{color:#FF7A7A;}
431+
.ed-main ::deep .po-inline-emotion-concerned{--ed-emotion-marker:"😟";color:#FF7A7A;}
432432
.ed-main ::deep .po-tag-emotion-concerned{--ed-tag-color:#FF7A7A;}
433433
.ed-main ::deep .mk-emo-focused,
434-
.ed-main ::deep .po-inline-emotion-focused{color:#4AE0A0;}
434+
.ed-main ::deep .po-inline-emotion-focused{--ed-emotion-marker:"🎯";color:#4AE0A0;}
435435
.ed-main ::deep .po-tag-emotion-focused{--ed-tag-color:#4AE0A0;}
436436
.ed-main ::deep .mk-emo-motivational,
437-
.ed-main ::deep .po-inline-emotion-motivational{color:#C88AFF;}
437+
.ed-main ::deep .po-inline-emotion-motivational{--ed-emotion-marker:"💪";color:#C88AFF;}
438438
.ed-main ::deep .po-tag-emotion-motivational{--ed-tag-color:#C88AFF;}
439439
.ed-main ::deep .mk-emo-neutral,
440-
.ed-main ::deep .po-inline-emotion-neutral{color:#8ECFFF;}
440+
.ed-main ::deep .po-inline-emotion-neutral{--ed-emotion-marker:"😐";color:#8ECFFF;}
441441
.ed-main ::deep .po-tag-emotion-neutral{--ed-tag-color:#8ECFFF;}
442442
.ed-main ::deep .mk-emo-urgent,
443-
.ed-main ::deep .po-inline-emotion-urgent{color:#FF6060;text-shadow:0 0 8px rgba(255,96,96,.18);}
443+
.ed-main ::deep .po-inline-emotion-urgent{--ed-emotion-marker:"🚨";color:#FF6060;text-shadow:0 0 8px rgba(255,96,96,.18);}
444444
.ed-main ::deep .po-tag-emotion-urgent{--ed-tag-color:#FF6060;}
445445
.ed-main ::deep .mk-emo-happy,
446-
.ed-main ::deep .po-inline-emotion-happy{color:#FFE87A;}
446+
.ed-main ::deep .po-inline-emotion-happy{--ed-emotion-marker:"😄";color:#FFE87A;}
447447
.ed-main ::deep .po-tag-emotion-happy{--ed-tag-color:#FFE87A;}
448448
.ed-main ::deep .mk-emo-excited,
449-
.ed-main ::deep .po-inline-emotion-excited{color:#FF8AC8;}
449+
.ed-main ::deep .po-inline-emotion-excited{--ed-emotion-marker:"🚀";color:#FF8AC8;}
450450
.ed-main ::deep .po-tag-emotion-excited{--ed-tag-color:#FF8AC8;}
451451
.ed-main ::deep .mk-emo-sad,
452-
.ed-main ::deep .po-inline-emotion-sad{color:#A0A8FF;}
452+
.ed-main ::deep .po-inline-emotion-sad{--ed-emotion-marker:"😢";color:#A0A8FF;}
453453
.ed-main ::deep .po-tag-emotion-sad{--ed-tag-color:#A0A8FF;}
454454
.ed-main ::deep .mk-emo-calm,
455-
.ed-main ::deep .po-inline-emotion-calm{color:#5EECC2;}
455+
.ed-main ::deep .po-inline-emotion-calm{--ed-emotion-marker:"😌";color:#5EECC2;}
456456
.ed-main ::deep .po-tag-emotion-calm{--ed-tag-color:#5EECC2;}
457457
.ed-main ::deep .mk-emo-energetic,
458-
.ed-main ::deep .po-inline-emotion-energetic{color:#FFA050;}
458+
.ed-main ::deep .po-inline-emotion-energetic{--ed-emotion-marker:"⚡";color:#FFA050;}
459459
.ed-main ::deep .po-tag-emotion-energetic{--ed-tag-color:#FFA050;}
460460
.ed-main ::deep .mk-emo-professional,
461-
.ed-main ::deep .po-inline-emotion-professional{color:#80B8FF;}
461+
.ed-main ::deep .po-inline-emotion-professional{--ed-emotion-marker:"💼";color:#80B8FF;}
462462
.ed-main ::deep .po-tag-emotion-professional{--ed-tag-color:#80B8FF;}
463+
.ed-main ::deep .mk-emo-warm{--ed-emotion-marker:"😊";}
464+
.ed-main ::deep .mk-emo-concerned{--ed-emotion-marker:"😟";}
465+
.ed-main ::deep .mk-emo-focused{--ed-emotion-marker:"🎯";}
466+
.ed-main ::deep .mk-emo-motivational{--ed-emotion-marker:"💪";}
467+
.ed-main ::deep .mk-emo-neutral{--ed-emotion-marker:"😐";}
468+
.ed-main ::deep .mk-emo-urgent{--ed-emotion-marker:"🚨";}
469+
.ed-main ::deep .mk-emo-happy{--ed-emotion-marker:"😄";}
470+
.ed-main ::deep .mk-emo-excited{--ed-emotion-marker:"🚀";}
471+
.ed-main ::deep .mk-emo-sad{--ed-emotion-marker:"😢";}
472+
.ed-main ::deep .mk-emo-calm{--ed-emotion-marker:"😌";}
473+
.ed-main ::deep .mk-emo-energetic{--ed-emotion-marker:"⚡";}
474+
.ed-main ::deep .mk-emo-professional{--ed-emotion-marker:"💼";}
475+
.ed-main ::deep [class*="mk-emo-"]::before,
476+
.ed-main ::deep [class*="po-inline-emotion-"]::before{
477+
content:var(--ed-emotion-marker);
478+
display:inline-block;
479+
margin-inline-end:.24em;
480+
font-size:.9em;
481+
line-height:1;
482+
vertical-align:-.05em;
483+
}
463484
.ed-main ::deep [data-tps-volume],
464485
.ed-main ::deep [data-tps-delivery],
465486
.ed-main ::deep [data-tps-articulation],

src/PrompterOne.Shared/Editor/Rendering/EditorMarkupRenderer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,11 @@ public void AppendHtmlAttributes(StringBuilder builder, params string[] extraCla
593593
if (!string.IsNullOrWhiteSpace(EmotionValue))
594594
{
595595
AppendAttribute(builder, TpsVisualCueContracts.EmotionAttributeName, EmotionValue);
596+
var marker = TpsEmotionMarkers.ResolveMarker(EmotionValue);
597+
if (!string.IsNullOrWhiteSpace(marker))
598+
{
599+
AppendAttribute(builder, UiDataAttributes.Editor.EmotionMarker, marker);
600+
}
596601
}
597602

598603
if (!string.IsNullOrWhiteSpace(VolumeValue))

src/PrompterOne.Shared/Teleprompter/Pages/TeleprompterPage.ReaderContent.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private static void AppendReaderCardSeeds(
9090
DisplayName: string.IsNullOrWhiteSpace(block.Name) ? segment.Name : block.Name,
9191
EmotionKey: emotionKey,
9292
EmotionLabel: FormatEmotionLabel(emotionKey),
93+
EmotionMarker: TpsEmotionMarkers.ResolveMarker(emotionKey),
9394
BackgroundClass: ResolveReaderBackgroundClass(emotionKey),
9495
AccentColor: segmentAccent,
9596
TargetWpm: targetWpm,
@@ -158,6 +159,7 @@ private static List<ReaderCardViewModel> BuildReaderCardViewModels(IReadOnlyList
158159
DisplayName: seed.DisplayName,
159160
EmotionKey: seed.EmotionKey,
160161
EmotionLabel: seed.EmotionLabel,
162+
EmotionMarker: seed.EmotionMarker,
161163
BackgroundClass: seed.BackgroundClass,
162164
AccentColor: seed.AccentColor,
163165
TargetWpm: seed.TargetWpm,
@@ -277,6 +279,7 @@ private static IReadOnlyList<ReaderChunkViewModel> BuildReaderChunks(IEnumerable
277279
var effectiveWpm = ResolveEffectiveWpm(word.Metadata, targetWpm);
278280
var speedCueValue = ResolveReaderSpeedCueValue(targetWpm, effectiveWpm);
279281
var pronunciationGuide = ResolveReaderPronunciationGuide(word.Metadata);
282+
var emotionCueValue = ResolveEmotionKey(word.Metadata?.InlineEmotionHint, string.Empty);
280283
var wordDurationMs = Math.Max(MinimumReaderWordDurationMilliseconds, (int)Math.Round(word.DisplayDuration.TotalMilliseconds));
281284
currentGroup.Add(new ReaderWordViewModel(
282285
Text: pronunciationGuide ?? word.CleanText,
@@ -290,6 +293,8 @@ private static IReadOnlyList<ReaderChunkViewModel> BuildReaderChunks(IEnumerable
290293
wordDurationMs),
291294
OriginalText: pronunciationGuide is null ? null : word.CleanText,
292295
PronunciationGuide: pronunciationGuide,
296+
EmotionMarker: TpsEmotionMarkers.ResolveMarker(emotionCueValue),
297+
EmotionLabel: string.IsNullOrWhiteSpace(emotionCueValue) ? null : FormatEmotionLabel(emotionCueValue),
293298
EffectiveWpm: effectiveWpm,
294299
Attributes: BuildReaderWordAttributes(word.Metadata, speedCueValue)));
295300

src/PrompterOne.Shared/Teleprompter/Pages/TeleprompterPage.ReaderModels.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ private sealed record ReaderCardSeed(
99
string DisplayName,
1010
string EmotionKey,
1111
string EmotionLabel,
12+
string EmotionMarker,
1213
string BackgroundClass,
1314
string AccentColor,
1415
int TargetWpm,
@@ -23,6 +24,7 @@ private sealed record ReaderCardViewModel(
2324
string DisplayName,
2425
string EmotionKey,
2526
string EmotionLabel,
27+
string EmotionMarker,
2628
string BackgroundClass,
2729
string AccentColor,
2830
int TargetWpm,
@@ -52,6 +54,8 @@ private sealed record ReaderWordViewModel(
5254
string? Style = null,
5355
string? OriginalText = null,
5456
string? PronunciationGuide = null,
57+
string? EmotionMarker = null,
58+
string? EmotionLabel = null,
5559
int? EffectiveWpm = null,
5660
IReadOnlyDictionary<string, object>? Attributes = null);
5761

src/PrompterOne.Shared/Teleprompter/Pages/TeleprompterPage.ReaderRendering.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ private IReadOnlyDictionary<string, object> BuildReaderWordDataAttributes(
401401
attributes,
402402
UiDataAttributes.Teleprompter.EffectiveWordsPerMinute,
403403
word.EffectiveWpm);
404+
AddOptionalDataAttribute(
405+
attributes,
406+
UiDataAttributes.Teleprompter.EmotionMarker,
407+
word.EmotionMarker);
404408
AddOptionalDataAttribute(
405409
attributes,
406410
UiDataAttributes.Teleprompter.OriginalText,
@@ -441,6 +445,13 @@ private IReadOnlyDictionary<string, object> BuildReaderSpeedCueLabelDataAttribut
441445
[UiDataAttributes.Teleprompter.SpeedCueDisplayMode] = BuildReaderSpeedCueDisplayModeDataAttribute()
442446
};
443447

448+
private static IReadOnlyDictionary<string, object> BuildReaderEmotionMarkerDataAttributes(
449+
ReaderWordViewModel word) =>
450+
new Dictionary<string, object>(StringComparer.Ordinal)
451+
{
452+
[UiDataAttributes.Teleprompter.EmotionMarker] = word.EmotionMarker ?? string.Empty
453+
};
454+
444455
private string ResolveReaderCardCssClass(int index) =>
445456
index == _activeReaderCardIndex
446457
? ReaderCardActiveCssClass

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
<div class="rd-cluster rd-cluster-active"
101101
data-test="@UiTestIds.Teleprompter.CardCluster(entry.index)">
102102
<div class="rd-cluster-emotion">
103-
<span class="rd-emo-dot" style="background:@entry.card.AccentColor"></span>
103+
<span class="rd-emo-marker"
104+
aria-label="@entry.card.EmotionLabel">@entry.card.EmotionMarker</span>
104105
<span class="rd-emo-name">@entry.card.EmotionLabel</span>
105106
<span class="rd-emo-wpm">@entry.card.TargetWpm <small>@Text(UiTextKey.CommonWpm)</small></span>
106107
</div>
@@ -130,7 +131,16 @@
130131
id="@UiDomIds.Teleprompter.CardWord(entry.index, chunkIndex, wordIndex)"
131132
@attributes="BuildReaderWordDataAttributes(word, entry.card.TargetWpm, entry.index, chunkIndex, wordIndex)"
132133
style="@word.Style"
133-
data-test="@BuildReaderWordTestId(entry.index, chunkIndex, wordIndex)">@word.Text</span>
134+
data-test="@BuildReaderWordTestId(entry.index, chunkIndex, wordIndex)">
135+
@if (!string.IsNullOrWhiteSpace(word.EmotionMarker))
136+
{
137+
<span class="rd-emotion-marker"
138+
aria-label="@word.EmotionLabel"
139+
@attributes="BuildReaderEmotionMarkerDataAttributes(word)"
140+
data-test="@UiTestIds.Teleprompter.CardWordEmotionMarker(entry.index, chunkIndex, wordIndex)"></span>
141+
}
142+
@word.Text
143+
</span>
134144
@if (!string.IsNullOrWhiteSpace(speedCueLabel))
135145
{
136146
<span class="rd-speed-cue-label"

0 commit comments

Comments
 (0)