253 lines
6.4 KiB
Vue
253 lines
6.4 KiB
Vue
<template>
|
|
<IconLabel class="user-input-label keyboard-lock-notice" v-if="displayKeyboardLockNotice" :icon="'Info'" :title="keyboardLockInfoMessage" />
|
|
<LayoutRow class="user-input-label" v-else>
|
|
<template v-for="(keysWithLabels, i) in keysWithLabelsGroups" :key="i">
|
|
<span class="group-gap" v-if="i > 0"></span>
|
|
<template v-for="(keyInfo, j) in keyTextOrIconList(keysWithLabels)" :key="j">
|
|
<span class="input-key" :class="keyInfo.width">
|
|
<IconLabel v-if="keyInfo.icon" :icon="keyInfo.icon" />
|
|
<template v-else-if="keyInfo.label !== undefined">{{ keyInfo.label }}</template>
|
|
</span>
|
|
</template>
|
|
</template>
|
|
<span class="input-mouse" v-if="mouseMotion">
|
|
<IconLabel :icon="mouseHintIcon(mouseMotion)" />
|
|
</span>
|
|
<span class="hint-text" v-if="hasSlotContent">
|
|
<slot></slot>
|
|
</span>
|
|
</LayoutRow>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.user-input-label {
|
|
flex: 0 0 auto;
|
|
height: 100%;
|
|
align-items: center;
|
|
white-space: nowrap;
|
|
|
|
.group-gap {
|
|
width: 4px;
|
|
}
|
|
|
|
.input-key,
|
|
.input-mouse {
|
|
& + .input-key,
|
|
& + .input-mouse {
|
|
margin-left: 2px;
|
|
}
|
|
}
|
|
|
|
.input-key {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
font-family: "Inconsolata", monospace;
|
|
font-weight: 400;
|
|
text-align: center;
|
|
height: 16px;
|
|
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height, so moving it up 1 pixel by using 15px makes them agree
|
|
line-height: 15px;
|
|
box-sizing: border-box;
|
|
border: 1px solid;
|
|
border-radius: 4px;
|
|
border-color: var(--color-7-middlegray);
|
|
color: var(--color-e-nearwhite);
|
|
|
|
&.width-1 {
|
|
width: 16px;
|
|
}
|
|
|
|
&.width-2 {
|
|
width: 24px;
|
|
}
|
|
|
|
&.width-3 {
|
|
width: 32px;
|
|
}
|
|
|
|
&.width-4 {
|
|
width: 40px;
|
|
}
|
|
|
|
&.width-5 {
|
|
width: 48px;
|
|
}
|
|
|
|
.icon-label {
|
|
margin: 1px;
|
|
}
|
|
}
|
|
|
|
.input-mouse {
|
|
.bright {
|
|
fill: var(--color-e-nearwhite);
|
|
}
|
|
|
|
.dim {
|
|
fill: var(--color-7-middlegray);
|
|
}
|
|
}
|
|
|
|
.hint-text {
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.floating-menu-content & {
|
|
.input-key {
|
|
border-color: var(--color-4-dimgray);
|
|
color: var(--color-8-uppergray);
|
|
}
|
|
|
|
.input-key .icon-label svg,
|
|
&.keyboard-lock-notice.keyboard-lock-notice svg,
|
|
.input-mouse .bright {
|
|
fill: var(--color-8-uppergray);
|
|
}
|
|
|
|
.input-mouse .dim {
|
|
fill: var(--color-4-dimgray);
|
|
}
|
|
}
|
|
|
|
.floating-menu-content .row:hover > & {
|
|
.input-key {
|
|
border-color: var(--color-7-middlegray);
|
|
color: var(--color-9-palegray);
|
|
}
|
|
|
|
.input-key .icon-label svg,
|
|
&.keyboard-lock-notice.keyboard-lock-notice svg,
|
|
.input-mouse .bright {
|
|
fill: var(--color-9-palegray);
|
|
}
|
|
|
|
.input-mouse .dim {
|
|
fill: var(--color-7-middlegray);
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, type PropType } from "vue";
|
|
|
|
import { type IconName } from "@/utility-functions/icons";
|
|
import { platformIsMac } from "@/utility-functions/platform";
|
|
import { type KeyRaw, type KeysGroup, type Key, type MouseMotion } from "@/wasm-communication/messages";
|
|
|
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|
|
|
type LabelData = { label?: string; icon?: IconName; width: string };
|
|
|
|
// Keys that become icons if they are listed here with their units of width
|
|
const ICON_WIDTHS_MAC = {
|
|
Shift: 2,
|
|
Control: 2,
|
|
Option: 2,
|
|
Command: 2,
|
|
};
|
|
const ICON_WIDTHS = {
|
|
ArrowUp: 1,
|
|
ArrowRight: 1,
|
|
ArrowDown: 1,
|
|
ArrowLeft: 1,
|
|
Backspace: 2,
|
|
Enter: 2,
|
|
Tab: 2,
|
|
Space: 3,
|
|
...(platformIsMac() ? ICON_WIDTHS_MAC : {}),
|
|
};
|
|
|
|
export default defineComponent({
|
|
inject: ["fullscreen"],
|
|
props: {
|
|
keysWithLabelsGroups: { type: Array as PropType<KeysGroup[]>, default: () => [] },
|
|
mouseMotion: { type: String as PropType<MouseMotion | undefined>, required: false },
|
|
requiresLock: { type: Boolean as PropType<boolean>, default: false },
|
|
},
|
|
computed: {
|
|
hasSlotContent(): boolean {
|
|
return Boolean(this.$slots.default);
|
|
},
|
|
keyboardLockInfoMessage(): string {
|
|
const RESERVED = "This hotkey is reserved by the browser. ";
|
|
const USE_FULLSCREEN = "It is made available in fullscreen mode.";
|
|
const USE_SECURE_CTX = "It is made available in fullscreen mode when this website is served from a secure context (https or localhost).";
|
|
const SWITCH_BROWSER = "Use a Chromium-based browser (like Chrome or Edge) in fullscreen mode to directly use the shortcut.";
|
|
|
|
if (this.fullscreen.keyboardLockApiSupported) return `${RESERVED} ${USE_FULLSCREEN}`;
|
|
if (!("chrome" in window)) return `${RESERVED} ${SWITCH_BROWSER}`;
|
|
if (!window.isSecureContext) return `${RESERVED} ${USE_SECURE_CTX}`;
|
|
return RESERVED;
|
|
},
|
|
displayKeyboardLockNotice(): boolean {
|
|
return this.requiresLock && !this.fullscreen.state.keyboardLocked;
|
|
},
|
|
},
|
|
methods: {
|
|
keyTextOrIconList(keyGroup: KeysGroup): LabelData[] {
|
|
return keyGroup.map((key) => this.keyTextOrIcon(key));
|
|
},
|
|
keyTextOrIcon(keyWithLabel: Key): LabelData {
|
|
// `key` is the name of the `Key` enum in Rust, while `label` is the localized string to display (if it doesn't become an icon)
|
|
let key = keyWithLabel.key;
|
|
const label = keyWithLabel.label;
|
|
|
|
// Replace Alt and Accel keys with their Mac-specific equivalents
|
|
if (platformIsMac()) {
|
|
if (key === "Alt") key = "Option";
|
|
if (key === "Accel") key = "Command";
|
|
}
|
|
|
|
// Either display an icon...
|
|
// @ts-expect-error We want undefined if it isn't in the object
|
|
const iconWidth: number | undefined = ICON_WIDTHS[key];
|
|
const icon = iconWidth !== undefined && iconWidth > 0 && (this.keyboardHintIcon(key) || false);
|
|
if (icon) return { icon, width: `width-${iconWidth}` };
|
|
|
|
// ...or display text
|
|
return { label, width: `width-${label.length}` };
|
|
},
|
|
mouseHintIcon(input?: MouseMotion): IconName {
|
|
return `MouseHint${input}` as IconName;
|
|
},
|
|
keyboardHintIcon(input: KeyRaw): IconName | undefined {
|
|
switch (input) {
|
|
case "ArrowDown":
|
|
return "KeyboardArrowDown";
|
|
case "ArrowLeft":
|
|
return "KeyboardArrowLeft";
|
|
case "ArrowRight":
|
|
return "KeyboardArrowRight";
|
|
case "ArrowUp":
|
|
return "KeyboardArrowUp";
|
|
case "Backspace":
|
|
return "KeyboardBackspace";
|
|
case "Command":
|
|
return "KeyboardCommand";
|
|
case "Control":
|
|
return "KeyboardControl";
|
|
case "Enter":
|
|
return "KeyboardEnter";
|
|
case "Option":
|
|
return "KeyboardOption";
|
|
case "Shift":
|
|
return "KeyboardShift";
|
|
case "Space":
|
|
return "KeyboardSpace";
|
|
case "Tab":
|
|
return "KeyboardTab";
|
|
default:
|
|
return undefined;
|
|
}
|
|
},
|
|
},
|
|
components: {
|
|
IconLabel,
|
|
LayoutRow,
|
|
},
|
|
});
|
|
</script>
|