Skip to content

Commit

Permalink
Add shift symbol and variant enum
Browse files Browse the repository at this point in the history
Add inline variant for shorcut template

Use shortcut template in verification button
  • Loading branch information
hudson-newey committed Jan 14, 2025
1 parent 477f231 commit 576559b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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`
Expand Down
128 changes: 83 additions & 45 deletions src/components/decision/classification/classification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -57,47 +63,41 @@ export class ClassificationComponent extends DecisionComponent {

private _decisionModels: Partial<DecisionModels<Classification>> = {};

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;
Expand All @@ -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` <kbd class="shortcut-legend">${shiftKey}${shortcut}</kbd> `;
return event.key === shortcut;
}

private decisionButtonTemplate(decision: DecisionOptions): TemplateResult {
Expand All @@ -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);

Expand All @@ -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}"
Expand All @@ -170,7 +206,9 @@ export class ClassificationComponent extends DecisionComponent {
<span class="oe-pill decision-color-pill" style="background-color: var(${color})"></span>
<div class="button-text">${decision}</div>
${!this.isMobile && shortcut ? this.keyboardShortcutTemplate(shortcut) : nothing}
<div>
${!this.isMobile && shortcut ? keyboardShortcutTemplate(shortcut, ShiftSymbolVariant.inline) : nothing}
</div>
</button>
`;
}
Expand Down
7 changes: 4 additions & 3 deletions src/components/decision/verification/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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()}"
Expand All @@ -116,7 +115,9 @@ export class VerificationComponent extends DecisionComponent {
<div class="additional-tags">${this.additionalTagsTemplate()}</div>
<div>${!this.isMobile ? html`<kbd class="shortcut-legend">${this.shortcut}</kbd>` : nothing}</div>
<div class="shortcut-legend">
${!this.isMobile ? keyboardShortcutTemplate({ keys: [this.shortcut] }) : nothing}
</div>
</button>
`;
}
Expand Down
53 changes: 46 additions & 7 deletions src/templates/keyboardShortcut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`<kbd>${key.toLocaleUpperCase()}</kbd> ${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`<kbd>${shortShiftCharacter}</kbd>`;
}
const nextKey = shortcut.keys[index + 1];
return html`<kbd>${shortShiftCharacter}${nextKey}</kbd>`;
}
case ShiftSymbolVariant.short: {
return html`<kbd>${shortShiftCharacter}</kbd> ${when(!last || shortcut.hasMouse, () => "+")}`;
}
case ShiftSymbolVariant.long: {
return html`<kbd>Shift</kbd> ${when(!last || shortcut.hasMouse, () => "+")}`;
}
}
}
return html`<kbd>${key.toLocaleUpperCase()}</kbd> ${when(!last || shortcut.hasMouse, () => "+")}`;
})}
${when(shortcut.hasMouse, () => html`<sl-icon name="mouse" class="inline-icon xl-icon"></sl-icon>`)}
`;
}

0 comments on commit 576559b

Please sign in to comment.