diff --git a/src/modals/SelectModal.ts b/src/modals/SelectModal.ts index 35b278b..de2cdc3 100644 --- a/src/modals/SelectModal.ts +++ b/src/modals/SelectModal.ts @@ -1,5 +1,6 @@ import {App, Modal, Setting} from 'obsidian'; import {SelectModalElement} from './SelectModalElement'; +import {mod} from '../utils/Utils'; export abstract class SelectModal extends Modal { allowMultiSelect: boolean; @@ -22,6 +23,17 @@ export abstract class SelectModal extends Modal { this.elements = elements; this.selectModalElements = []; + + this.scope.register([], 'ArrowUp', () => { + this.highlightUp(); + }); + this.scope.register([], 'ArrowDown', () => { + this.highlightDown(); + }); + this.scope.register([], 'ArrowRight', () => { + this.activateHighlighted(); + }); + this.scope.register([], 'Enter', () => this.submit()); } abstract renderElement(value: T, el: HTMLElement): any; @@ -38,6 +50,14 @@ export abstract class SelectModal extends Modal { } } + deHighlightAllOtherElements(elementId: number) { + for (const selectModalElement of this.selectModalElements) { + if (selectModalElement.id !== elementId) { + selectModalElement.setHighlighted(false); + } + } + } + async onOpen() { const {contentEl} = this; @@ -72,4 +92,54 @@ export abstract class SelectModal extends Modal { } bottomSetting.addButton(btn => btn.setButtonText('Ok').setCta().onClick(() => this.submit())); } + + activateHighlighted() { + for (const selectModalElement of this.selectModalElements) { + if (selectModalElement.isHighlighted()) { + selectModalElement.setActive(!selectModalElement.isActive()); + if (!this.allowMultiSelect) { + this.disableAllOtherElements(selectModalElement.id); + } + } + } + } + + highlightUp() { + for (const selectModalElement of this.selectModalElements) { + if (selectModalElement.isHighlighted()) { + this.getPreviousSelectModalElement(selectModalElement).setHighlighted(true); + return; + } + } + + // nothing is highlighted + this.selectModalElements.last().setHighlighted(true); + } + + highlightDown() { + for (const selectModalElement of this.selectModalElements) { + if (selectModalElement.isHighlighted()) { + this.getNextSelectModalElement(selectModalElement).setHighlighted(true); + return; + } + } + + // nothing is highlighted + this.selectModalElements.first().setHighlighted(true); + } + + private getNextSelectModalElement(selectModalElement: SelectModalElement): SelectModalElement { + let nextId = selectModalElement.id + 1; + nextId = mod(nextId, this.selectModalElements.length); + + return this.selectModalElements.filter(x => x.id === nextId).first(); + } + + private getPreviousSelectModalElement(selectModalElement: SelectModalElement): SelectModalElement { + let nextId = selectModalElement.id - 1; + nextId = mod(nextId, this.selectModalElements.length); + + return this.selectModalElements.filter(x => x.id === nextId).first(); + } + } diff --git a/src/modals/SelectModalElement.ts b/src/modals/SelectModalElement.ts index 8ef9a74..4aadb9e 100644 --- a/src/modals/SelectModalElement.ts +++ b/src/modals/SelectModalElement.ts @@ -9,6 +9,7 @@ export class SelectModalElement { activeClass: string; hoverClass: string; private active: boolean; + private highlighted: boolean; constructor(value: T, parentElement: HTMLElement, id: number, selectModal: SelectModal, active: boolean = false) { this.value = value; @@ -29,21 +30,35 @@ export class SelectModalElement { } }); this.element.on('mouseenter', '#' + this.getHTMLId(), () => { - this.addClass(this.hoverClass); + this.setHighlighted(true); }); this.element.on('mouseleave', '#' + this.getHTMLId(), () => { - this.removeClass(this.hoverClass); + this.setHighlighted(false); }); } - isActive(): boolean { - return this.active; - } - getHTMLId(): string { return `media-db-plugin-select-element-${this.id}`; } + isHighlighted(): boolean { + return this.highlighted; + } + + setHighlighted(value: boolean) { + this.highlighted = value; + if (this.highlighted) { + this.addClass(this.hoverClass); + this.selectModal.deHighlightAllOtherElements(this.id); + } else { + this.removeClass(this.hoverClass); + } + } + + isActive(): boolean { + return this.active; + } + setActive(active: boolean): void { this.active = active; this.update(); diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index a4b3cdb..d6768a4 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -164,3 +164,8 @@ export class UserSkipError extends Error { } } +// js can't even implement modulo correctly... +export function mod(n: number, m: number): number { + return ((n % m) + m) % m; +} + diff --git a/src/utils/YAMLConverter.ts b/src/utils/YAMLConverter.ts index 7cec238..f9e013f 100644 --- a/src/utils/YAMLConverter.ts +++ b/src/utils/YAMLConverter.ts @@ -10,6 +10,10 @@ export class YAMLConverter { } private static toYamlString(value: any, indentation: number): string { + if (value == null) { + return 'null'; + } + if (typeof value === 'boolean') { return value ? 'true' : 'false'; } else if (typeof value === 'number') {