keyboard keymap also

This commit is contained in:
Matt Hill
2026-01-20 14:24:45 -07:00
parent 0a0f0850d7
commit 99727e132c
6 changed files with 63 additions and 57 deletions

View File

@@ -82,7 +82,7 @@ export default class KeyboardPage {
this.stateService.language as LanguageCode,
)
selected =
this.keyboards.find(k => k.code === this.stateService.keyboard) ||
this.keyboards.find(k => k.layout === this.stateService.keyboard) ||
this.keyboards[0]!
readonly saving = signal(false)
@@ -95,13 +95,14 @@ export default class KeyboardPage {
try {
// Send keyboard to backend
await this.api.setKeyboard({
layout: this.selected.code,
layout: this.selected.layout,
keymap: this.selected.keymap,
model: null,
variant: null,
options: [],
})
this.stateService.keyboard = this.selected.code
this.stateService.keyboard = this.selected.layout
await this.navigateToNextStep()
} finally {
this.saving.set(false)

View File

@@ -1,9 +1,14 @@
import { LanguageCode } from './languages'
/**
* Keyboard layout codes
* Keyboard layout codes (X11/Wayland)
*/
export type KeyboardCode = 'us' | 'gb' | 'es' | 'latam' | 'de' | 'fr' | 'pl'
export type KeyboardLayout = 'us' | 'gb' | 'es' | 'latam' | 'de' | 'fr' | 'pl'
/**
* Keyboard keymap codes (console/TTY)
*/
export type KeyboardKeymap = 'us' | 'uk' | 'es' | 'la' | 'de' | 'fr' | 'pl'
/**
* Keyboard layout display names
@@ -18,10 +23,11 @@ export type KeyboardName =
| 'Polish'
/**
* Keyboard layout definition
* Keyboard definition with layout and keymap
*/
export interface Keyboard {
code: KeyboardCode
layout: KeyboardLayout
keymap: KeyboardKeymap
name: KeyboardName
}
@@ -29,7 +35,8 @@ export interface Keyboard {
* Full keyboard configuration for backend API
*/
export interface FullKeyboard {
layout: string
layout: KeyboardLayout
keymap: KeyboardKeymap
model: string | null
variant: string | null
options: string[]
@@ -40,29 +47,29 @@ export interface FullKeyboard {
*/
export const KEYBOARDS_BY_LANGUAGE: Record<LanguageCode, Keyboard[]> = {
en: [
{ code: 'us', name: 'US English' },
{ code: 'gb', name: 'UK English' },
{ layout: 'us', keymap: 'us', name: 'US English' },
{ layout: 'gb', keymap: 'uk', name: 'UK English' },
],
es: [
{ code: 'es', name: 'Spanish' },
{ code: 'latam', name: 'Latin American' },
{ layout: 'es', keymap: 'es', name: 'Spanish' },
{ layout: 'latam', keymap: 'la', name: 'Latin American' },
],
de: [{ code: 'de', name: 'German' }],
fr: [{ code: 'fr', name: 'French' }],
pl: [{ code: 'pl', name: 'Polish' }],
de: [{ layout: 'de', keymap: 'de', name: 'German' }],
fr: [{ layout: 'fr', keymap: 'fr', name: 'French' }],
pl: [{ layout: 'pl', keymap: 'pl', name: 'Polish' }],
}
/**
* All available keyboard layouts
*/
export const ALL_KEYBOARDS: Keyboard[] = [
{ code: 'us', name: 'US English' },
{ code: 'gb', name: 'UK English' },
{ code: 'es', name: 'Spanish' },
{ code: 'latam', name: 'Latin American' },
{ code: 'de', name: 'German' },
{ code: 'fr', name: 'French' },
{ code: 'pl', name: 'Polish' },
{ layout: 'us', keymap: 'us', name: 'US English' },
{ layout: 'gb', keymap: 'uk', name: 'UK English' },
{ layout: 'es', keymap: 'es', name: 'Spanish' },
{ layout: 'latam', keymap: 'la', name: 'Latin American' },
{ layout: 'de', keymap: 'de', name: 'German' },
{ layout: 'fr', keymap: 'fr', name: 'French' },
{ layout: 'pl', keymap: 'pl', name: 'Polish' },
]
/**
@@ -71,20 +78,20 @@ export const ALL_KEYBOARDS: Keyboard[] = [
*/
export function getAllKeyboardsSorted(languageCode: LanguageCode): Keyboard[] {
const languageKeyboards = KEYBOARDS_BY_LANGUAGE[languageCode]
const languageKeyboardCodes = new Set(languageKeyboards.map(kb => kb.code))
const languageLayouts = new Set(languageKeyboards.map(kb => kb.layout))
const otherKeyboards = ALL_KEYBOARDS.filter(
kb => !languageKeyboardCodes.has(kb.code),
kb => !languageLayouts.has(kb.layout),
).sort((a, b) => a.name.localeCompare(b.name))
return [...languageKeyboards, ...otherKeyboards]
}
/**
* Get the display name for a keyboard code.
* Get the display name for a keyboard layout.
*/
export function getKeyboardName(
code: KeyboardCode | string,
layout: KeyboardLayout | string,
): KeyboardName | string {
const keyboard = ALL_KEYBOARDS.find(kb => kb.code === code)
const keyboard = ALL_KEYBOARDS.find(kb => kb.layout === layout)
if (keyboard) return keyboard.name
return code // fallback to the code itself if not found
return layout // fallback to the layout itself if not found
}

View File

@@ -18,7 +18,7 @@ import {
i18nPipe,
i18nService,
Keyboard,
KeyboardCode,
KeyboardLayout,
Language,
LANGUAGES,
LANGUAGE_TO_CODE,
@@ -317,29 +317,30 @@ export default class SystemGeneralComponent {
if (!server) return
const keyboards = getAllKeyboardsSorted(LANGUAGE_TO_CODE[server.language])
const currentKeyboard = (server.keyboard?.layout as KeyboardCode) || null
const currentLayout = (server.keyboard?.layout as KeyboardLayout) || null
this.dialog
.openComponent<KeyboardCode | null>(
.openComponent<Keyboard | null>(
new PolymorpheusComponent(KeyboardSelectComponent, this.injector),
{
label: 'Select Keyboard Layout',
size: 's',
data: { keyboards, currentKeyboard },
data: { keyboards, currentLayout },
},
)
.pipe(filter((code): code is KeyboardCode => code !== null))
.subscribe(keyboardCode => {
this.saveKeyboard(keyboardCode)
.pipe(filter((keyboard): keyboard is Keyboard => keyboard !== null))
.subscribe(keyboard => {
this.saveKeyboard(keyboard)
})
}
private async saveKeyboard(keyboardCode: KeyboardCode) {
private async saveKeyboard(keyboard: Keyboard) {
const loader = this.loader.open('Saving').subscribe()
try {
await this.api.setKeyboard({
layout: keyboardCode,
layout: keyboard.layout,
keymap: keyboard.keymap,
model: null,
variant: null,
options: [],
@@ -457,17 +458,17 @@ export default class SystemGeneralComponent {
private promptKeyboardSelection(keyboards: Keyboard[]) {
this.dialog
.openComponent<KeyboardCode | null>(
.openComponent<Keyboard | null>(
new PolymorpheusComponent(KeyboardSelectComponent, this.injector),
{
label: 'Select Keyboard Layout',
size: 's',
data: { keyboards, currentKeyboard: null },
data: { keyboards, currentLayout: null },
},
)
.pipe(filter((code): code is KeyboardCode => code !== null))
.subscribe(keyboardCode => {
this.enableKioskWithKeyboard(keyboardCode)
.pipe(filter((keyboard): keyboard is Keyboard => keyboard !== null))
.subscribe(keyboard => {
this.enableKioskWithKeyboard(keyboard)
})
}
@@ -484,12 +485,13 @@ export default class SystemGeneralComponent {
}
}
private async enableKioskWithKeyboard(keyboardCode: KeyboardCode) {
private async enableKioskWithKeyboard(keyboard: Keyboard) {
const loader = this.loader.open('Enabling').subscribe()
try {
await this.api.setKeyboard({
layout: keyboardCode,
layout: keyboard.layout,
keymap: keyboard.keymap,
model: null,
variant: null,
options: [],

View File

@@ -1,6 +1,6 @@
import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { i18nPipe, Keyboard, KeyboardCode } from '@start9labs/shared'
import { i18nPipe, Keyboard, KeyboardLayout } from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiTextfield } from '@taiga-ui/core'
import { TuiChevron, TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
@@ -29,7 +29,7 @@ import { injectContext } from '@taiga-ui/polymorpheus'
</button>
<button
tuiButton
[disabled]="!selected || selected.code === initialCode"
[disabled]="!selected || selected.layout === initialLayout"
(click)="confirm()"
>
{{ 'Confirm' | i18n }}
@@ -61,16 +61,16 @@ export class KeyboardSelectComponent {
private readonly context =
injectContext<
TuiDialogContext<
KeyboardCode | null,
{ keyboards: Keyboard[]; currentKeyboard: KeyboardCode | null }
Keyboard | null,
{ keyboards: Keyboard[]; currentLayout: KeyboardLayout | null }
>
>()
protected readonly mobile = inject(TUI_IS_MOBILE)
readonly keyboards = this.context.data.keyboards
readonly initialCode = this.context.data.currentKeyboard
readonly initialLayout = this.context.data.currentLayout
selected =
this.keyboards.find(kb => kb.code === this.initialCode) ||
this.keyboards.find(kb => kb.layout === this.initialLayout) ||
this.keyboards[0]!
readonly stringify = (kb: Keyboard) => kb.name
@@ -80,6 +80,6 @@ export class KeyboardSelectComponent {
}
confirm() {
this.context.completeWith(this.selected.code)
this.context.completeWith(this.selected)
}
}

View File

@@ -460,12 +460,7 @@ export class MockApiService extends ApiService {
{
op: PatchOp.REPLACE,
path: '/serverInfo/keyboard',
value: {
layout: params.layout,
model: params.model,
variant: params.variant,
options: params.options,
},
value: params,
},
]
this.mockRevision(patch)

View File

@@ -222,6 +222,7 @@ export const mockPatchData: DataModel = {
language: 'en_US',
keyboard: {
layout: 'us',
keymap: 'us',
model: null,
variant: null,
options: [],