From 576559b64a70b63406cb48010b157ceb25b615c2 Mon Sep 17 00:00:00 2001 From: hudson-newey Date: Tue, 14 Jan 2025 09:43:21 +1000 Subject: [PATCH] Add shift symbol and variant enum Add inline variant for shorcut template Use shortcut template in verification button --- .../advanced-shortcuts/advanced-shortcuts.ts | 6 +- .../decision/classification/classification.ts | 128 ++++++++++++------ .../decision/verification/verification.ts | 7 +- src/templates/keyboardShortcut.ts | 53 +++++++- 4 files changed, 136 insertions(+), 58 deletions(-) diff --git a/src/components/bootstrap-modal/slides/advanced-shortcuts/advanced-shortcuts.ts b/src/components/bootstrap-modal/slides/advanced-shortcuts/advanced-shortcuts.ts index d7d13177..0d21fa7d 100644 --- a/src/components/bootstrap-modal/slides/advanced-shortcuts/advanced-shortcuts.ts +++ b/src/components/bootstrap-modal/slides/advanced-shortcuts/advanced-shortcuts.ts @@ -1,5 +1,5 @@ import { html } from "lit"; -import { KeyboardShortcut, keyboardShortcutTemplate } from "../../../../templates/keyboardShortcut"; +import { KeyboardShortcut, keyboardShortcutTemplate, shiftSymbol } from "../../../../templates/keyboardShortcut"; import { BootstrapSlide } from "../bootstrapSlide"; export function advancedShortcutsSlide(): BootstrapSlide { @@ -12,8 +12,8 @@ export function advancedShortcutsSlide(): BootstrapSlide { { keys: ["Ctrl", "a"], description: "Select all" }, { keys: ["Esc"], description: "Deselect all" }, { keys: ["Ctrl"], description: "Toggle selection", hasMouse: true }, - { keys: ["Shift"], description: "Exclusive range selection", hasMouse: true }, - { keys: ["Ctrl", "Shift"], description: "Additive range selection", hasMouse: true }, + { keys: [shiftSymbol], description: "Exclusive range selection", hasMouse: true }, + { keys: ["Ctrl", shiftSymbol], description: "Additive range selection", hasMouse: true }, ] satisfies KeyboardShortcut[]; const slideTemplate = html` diff --git a/src/components/decision/classification/classification.ts b/src/components/decision/classification/classification.ts index 60a1f743..a3929042 100644 --- a/src/components/decision/classification/classification.ts +++ b/src/components/decision/classification/classification.ts @@ -8,7 +8,13 @@ import { DecisionOptions } from "../../../models/decisions/decision"; import { html, nothing, TemplateResult, unsafeCSS } from "lit"; import { classMap } from "lit/directives/class-map.js"; import { map } from "lit/directives/map.js"; -import { KeyboardShortcut, shiftSymbol } from "../../../templates/keyboardShortcut"; +import { + KeyboardShortcut, + KeyboardShortcutKey, + keyboardShortcutTemplate, + shiftSymbol, + ShiftSymbolVariant, +} from "../../../templates/keyboardShortcut"; import classificationStyles from "./css/style.css?inline"; /** @@ -57,47 +63,41 @@ export class ClassificationComponent extends DecisionComponent { private _decisionModels: Partial> = {}; - protected get derivedTrueShortcut(): string | undefined { - if (this.trueShortcut) { - return this.trueShortcut; - } else if (!this.falseShortcut) { - // if there is no false shortcut to derive a shortcut from, then we need - // cannot derive a true shortcut - return; - } + private get trueKeyboardShortcut(): KeyboardShortcut | undefined { + const shortcut = this.deriveTrueShortcutKey(); + if (shortcut) { + const keys: KeyboardShortcutKey[] = [shortcut]; + if (this.isShortcutUppercase(shortcut)) { + keys.unshift(shiftSymbol); + } - return this.falseShortcut === this.falseShortcut.toUpperCase() - ? this.falseShortcut.toLowerCase() - : this.falseShortcut.toUpperCase(); + return { keys, description: `Is ${this.tag.text}` }; + } } - protected get derivedFalseShortcut(): string | undefined { - if (this.falseShortcut) { - return this.falseShortcut; - } else if (!this.trueShortcut) { - return; - } + private get falseKeyboardShortcut(): KeyboardShortcut | undefined { + const shortcut = this.deriveFalseShortcutKey(); + if (shortcut) { + const keys: KeyboardShortcutKey[] = [shortcut]; + if (this.isShortcutUppercase(shortcut)) { + keys.unshift(shiftSymbol); + } - return this.trueShortcut === this.trueShortcut.toUpperCase() - ? this.trueShortcut.toLowerCase() - : this.trueShortcut.toUpperCase(); + return { keys, description: `Is ${this.tag.text}` }; + } } public override shortcutKeys(): KeyboardShortcut[] { const shortcuts: KeyboardShortcut[] = []; - if (this.derivedTrueShortcut) { - shortcuts.push({ - keys: [this.derivedTrueShortcut], - description: `Is ${this.tag.text}`, - }); + const trueShortcut = this.trueKeyboardShortcut; + if (trueShortcut) { + shortcuts.push(trueShortcut); } - if (this.derivedFalseShortcut) { - shortcuts.push({ - keys: [this.derivedFalseShortcut], - description: `Not ${this.tag.text}`, - }); + const falseShortcut = this.falseKeyboardShortcut; + if (falseShortcut) { + shortcuts.push(falseShortcut); } return shortcuts; @@ -120,27 +120,64 @@ export class ClassificationComponent extends DecisionComponent { return this.isTrueShortcutKey(event) || this.isFalseShortcutKey(event); } - private isTrueShortcutKey(event: KeyboardEvent): boolean { - if (!this.derivedTrueShortcut) { - return false; + /** + * Returns a true shortcut key from the "true-shortcut" attribute. + * If there is no true shortcut, this function will derive a shortcut key from + * the false shortcut key if a false shortcut key is defined without a true + * shortcut key. + */ + private deriveTrueShortcutKey(): string | undefined { + if (this.trueShortcut) { + return this.trueShortcut; + } else if (!this.falseShortcut) { + // if there is no false shortcut to derive a shortcut from, then we need + // cannot derive a true shortcut + return; } - return event.key === this.derivedTrueShortcut; + return this.falseShortcut === this.falseShortcut.toUpperCase() + ? this.falseShortcut.toLowerCase() + : this.falseShortcut.toUpperCase(); } - private isFalseShortcutKey(event: KeyboardEvent): boolean { - if (!this.derivedFalseShortcut) { + /** + * Returns a false shortcut key from the "false-shortcut" attribute. + * If there is no true shortcut, this function will derive a shortcut key from + * the true shortcut key if a true shortcut key is defined without a false + * shortcut key. + */ + private deriveFalseShortcutKey(): string | undefined { + if (this.falseShortcut) { + return this.falseShortcut; + } else if (!this.trueShortcut) { + return; + } + + return this.trueShortcut === this.trueShortcut.toUpperCase() + ? this.trueShortcut.toLowerCase() + : this.trueShortcut.toUpperCase(); + } + + private isShortcutUppercase(key: string) { + return key === key.toUpperCase(); + } + + private isTrueShortcutKey(event: KeyboardEvent): boolean { + const shortcut = this.deriveTrueShortcutKey(); + if (!shortcut) { return false; } - return event.key === this.derivedFalseShortcut; + return event.key === shortcut; } - private keyboardShortcutTemplate(shortcut: string): TemplateResult { - const isShortcutUpperCase = shortcut === shortcut.toUpperCase(); - const shiftKey = isShortcutUpperCase ? shiftSymbol : ""; + private isFalseShortcutKey(event: KeyboardEvent): boolean { + const shortcut = this.deriveFalseShortcutKey(); + if (!shortcut) { + return false; + } - return html` ${shiftKey}${shortcut} `; + return event.key === shortcut; } private decisionButtonTemplate(decision: DecisionOptions): TemplateResult { @@ -150,7 +187,7 @@ export class ClassificationComponent extends DecisionComponent { "oe-btn-secondary": decision === DecisionOptions.UNSURE || decision === DecisionOptions.SKIP, }); - const shortcut = decision === DecisionOptions.TRUE ? this.derivedTrueShortcut : this.derivedFalseShortcut; + const shortcut = decision === DecisionOptions.TRUE ? this.trueKeyboardShortcut : this.falseKeyboardShortcut; const decisionModel = new Classification(decision, this.tag); const color = this.injector.colorService(decisionModel); @@ -161,7 +198,6 @@ export class ClassificationComponent extends DecisionComponent { id="${decision}-decision-button" class="decision-button ${buttonClasses}" part="${decision}-decision-button" - title="Shortcut: ${shortcut}" style="--ripple-color: var(${color})" aria-label="${decision} decision for ${this.tag.text}" aria-disabled="${this.disabled}" @@ -170,7 +206,9 @@ export class ClassificationComponent extends DecisionComponent {
${decision}
- ${!this.isMobile && shortcut ? this.keyboardShortcutTemplate(shortcut) : nothing} +
+ ${!this.isMobile && shortcut ? keyboardShortcutTemplate(shortcut, ShiftSymbolVariant.inline) : nothing} +
`; } diff --git a/src/components/decision/verification/verification.ts b/src/components/decision/verification/verification.ts index b2590f33..b40b5447 100644 --- a/src/components/decision/verification/verification.ts +++ b/src/components/decision/verification/verification.ts @@ -7,7 +7,7 @@ import { html, nothing } from "lit"; import { classMap } from "lit/directives/class-map.js"; import { DecisionOptions } from "../../../models/decisions/decision"; import { enumConverter, tagArrayConverter } from "../../../helpers/attributes"; -import { KeyboardShortcut } from "../../../templates/keyboardShortcut"; +import { KeyboardShortcut, keyboardShortcutTemplate } from "../../../templates/keyboardShortcut"; import { Tag } from "../../../models/tag"; /** @@ -103,7 +103,6 @@ export class VerificationComponent extends DecisionComponent { id="decision-button" class="oe-btn-primary decision-button ${buttonClasses}" part="decision-button" - title="Shortcut: ${this.shortcut}" style="--ripple-color: var(${color})" aria-disabled="${this.disabled}" @click="${() => this.handleDecision()}" @@ -116,7 +115,9 @@ export class VerificationComponent extends DecisionComponent {
${this.additionalTagsTemplate()}
-
${!this.isMobile ? html`${this.shortcut}` : nothing}
+
+ ${!this.isMobile ? keyboardShortcutTemplate({ keys: [this.shortcut] }) : nothing} +
`; } diff --git a/src/templates/keyboardShortcut.ts b/src/templates/keyboardShortcut.ts index a426a527..9c3982d9 100644 --- a/src/templates/keyboardShortcut.ts +++ b/src/templates/keyboardShortcut.ts @@ -2,25 +2,64 @@ import { html, HTMLTemplateResult } from "lit"; import { when } from "lit/directives/when.js"; import { loop } from "../helpers/directives"; +export type KeyboardShortcutKey = string | typeof shiftSymbol; + export interface KeyboardShortcut { - keys: string[]; + keys: KeyboardShortcutKey[]; description?: string; hasMouse?: boolean; } -export const shiftSymbol = "⇧" as const; +export enum ShiftSymbolVariant { + "inline", + "short", + "long", +} + +export const shiftSymbol = Symbol("shift"); /** * @description * A standardized template to display keyboard shortcuts and shortcut * combinations */ -export function keyboardShortcutTemplate(shortcut: KeyboardShortcut): HTMLTemplateResult { +export function keyboardShortcutTemplate( + shortcut: KeyboardShortcut, + shiftSymbolVariant: ShiftSymbolVariant = ShiftSymbolVariant.long, +): HTMLTemplateResult { + const shortShiftCharacter = "⇧"; + let skipNext = false; + return html` - ${loop( - shortcut.keys, - (key, { last }) => html`${key.toLocaleUpperCase()} ${when(!last || shortcut.hasMouse, () => "+")}`, - )} + ${loop(shortcut.keys, (key, { last, index }) => { + if (skipNext) { + return; + } + + if (key === shiftSymbol) { + switch (shiftSymbolVariant) { + case ShiftSymbolVariant.inline: { + skipNext = true; + if (last) { + return html`${shortShiftCharacter}`; + } + + const nextKey = shortcut.keys[index + 1]; + return html`${shortShiftCharacter}${nextKey}`; + } + + case ShiftSymbolVariant.short: { + return html`${shortShiftCharacter} ${when(!last || shortcut.hasMouse, () => "+")}`; + } + + case ShiftSymbolVariant.long: { + return html`Shift ${when(!last || shortcut.hasMouse, () => "+")}`; + } + } + } + + return html`${key.toLocaleUpperCase()} ${when(!last || shortcut.hasMouse, () => "+")}`; + })} ${when(shortcut.hasMouse, () => html``)} `; }