Skip to content
Snippets Groups Projects
math-editor.module.ts 6.04 KiB
Newer Older
rhenck's avatar
rhenck committed
// eslint-disable-next-line max-classes-per-file
import {
  NgModule, CUSTOM_ELEMENTS_SCHEMA,
  Component, AfterViewInit, ViewChild, ElementRef, Input, OnChanges, SimpleChanges
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { MathfieldElement, VirtualKeyboardDefinition } from 'mathlive';
import { VirtualKeyboardLayer } from 'mathlive/dist/public/options';
import { MatButtonToggleChange, MatButtonToggleModule } from '@angular/material/button-toggle';

@Component({
  selector: 'aspect-mathlive-math-field',
  template: `
    <mat-button-toggle-group *ngIf="enableModeSwitch"
rhenck's avatar
rhenck committed
                             [value]="mathFieldElement.mode"
                             (change)="setParseMode($event)">
rhenck's avatar
rhenck committed
      <mat-button-toggle value="math">Formel</mat-button-toggle>
      <mat-button-toggle value="text">Text</mat-button-toggle>
    </mat-button-toggle-group>
    <div #mathfield [class.read-only]="readonly">
rhenck's avatar
rhenck committed
    </div>
  `,
  styles: [`
    mat-button-toggle-group {
      height: auto;
    }
    :host ::ng-deep .read-only math-field {
      outline: unset; border: unset;
    }
    :host ::ng-deep .mat-button-toggle-label-content {
      line-height: unset;
    }`
rhenck's avatar
rhenck committed
  ]
})
export class MathInputComponent implements AfterViewInit, OnChanges {
  @Input() value!: string;
  @Input() readonly: boolean = false;
rhenck's avatar
rhenck committed
  @Input() enableModeSwitch: boolean = false;
  @ViewChild('mathfield') mathfieldRef!: ElementRef;

  mathFieldElement: MathfieldElement = new MathfieldElement({
    virtualKeyboardMode: 'onfocus',
    customVirtualKeyboardLayers: MathInputComponent.setupKeyboadLayer(),
    customVirtualKeyboards: MathInputComponent.setupKeyboard(),
    virtualKeyboards: 'aspect-keyboard roman greek',
    keypressSound: null,
    plonkSound: null,
    decimalSeparator: ',',
    smartFence: false
rhenck's avatar
rhenck committed
    // defaultMode: 'math'
  });

  ngAfterViewInit(): void {
    this.setupMathfield();
  }

  setupMathfield(): void {
    this.mathfieldRef.nativeElement.appendChild(this.mathFieldElement);
    this.mathFieldElement.value = this.value;
    this.mathFieldElement.readOnly = this.readonly;
rhenck's avatar
rhenck committed
  }

  static setupKeyboard(): Record<string, VirtualKeyboardDefinition> {
    return {
      'aspect-keyboard': {
        label: 'Formel', // Label displayed in the Virtual Keyboard Switcher
        tooltip: 'Zahlen & Formeln', // Tooltip when hovering over the label
        layer: 'aspect-keyboard-layer'
      }
    };
  }

  static setupKeyboadLayer(): Record<string, string | Partial<VirtualKeyboardLayer>> {
    return {
      'aspect-keyboard-layer': {
        styles: '',
        rows: [
          [
            { label: '7', key: '7' },
            { label: '8', key: '8' },
            { label: '9', key: '9' },
            { class: 'separator w5' },
            { latex: '+' },
            { class: 'separator w5' },
            { latex: '<' },
            { latex: '>' },
            { latex: '\\ne' },
rhenck's avatar
rhenck committed
            { class: 'separator w5' },
            { latex: '' },
            { class: 'separator w5' },
            { label: '<span><i>x</i>&thinsp;²</span>', insert: '$$#@^{2}$$' },
            { latex: '$$#@^{#?}' },
            { latex: '$$#@_{#?}' }
          ],
          [
            { label: '4', latex: '4' },
            { label: '5', key: '5' },
            { label: '6', key: '6' },
            { class: 'separator w5' },
            { latex: '-' },
            { class: 'separator w5' },
            { latex: '\\le' },
            { latex: '\\ge' },
            { latex: '\\approx' },
            { class: 'separator w5' },
            { latex: '\\%' },
            { class: 'separator w5' },
            { class: 'small', latex: '\\frac{#0}{#0}' },
            { latex: '\\sqrt{#0}', insert: '$$\\sqrt{#0}$$' },
            { class: 'separator' }
          ],
          [
            { label: '1', key: '1' },
            { label: '2', key: '2' },
            { label: '3', key: '3' },
            { class: 'separator w5' },
            { latex: '\\times' },
            { class: 'separator w5' },
            { latex: '(' },
            { latex: ')' },
            { latex: '\\Rightarrow' },
            { class: 'separator w5' },
            { latex: '°' },
            { class: 'separator w5' },
            { latex: '\\overline' },
            { class: 'separator' },
            { class: 'separator' }
          ],
          [
            { label: '0', key: '0' },
            { latex: ',' },
            { latex: '=' },
            { class: 'separator w5' },
            { latex: '\\div' },
            { class: 'separator w5' },
            { latex: '[' },
            { latex: ']' },
            { latex: '\\Leftrightarrow' },
            { class: 'separator w5' },
            { latex: '\\mid' },
            { class: 'separator w5' },
            {
              class: 'action',
              label: "<svg><use xlink:href='#svg-arrow-left' /></svg>",
              command: ['performWithFeedback', 'moveToPreviousChar']
rhenck's avatar
rhenck committed
            },
            {
              class: 'action',
              label: "<svg><use xlink:href='#svg-arrow-right' /></svg>",
              command: ['performWithFeedback', 'moveToNextChar']
rhenck's avatar
rhenck committed
            },
            {
              class: 'action font-glyph bottom right',
              label: '&#x232b;',
              command: ['performWithFeedback', 'deleteBackward']
            }
          ]
        ]
      }
    };
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.value) {
      this.mathFieldElement.setValue(changes.value.currentValue, { mode: 'text' });
    }
rhenck's avatar
rhenck committed
  }

  setParseMode(event: MatButtonToggleChange) {
    // TODO Keyboard moving up and down on focus loss may be avoided by using useSharedVirtualKeyboard
    this.mathFieldElement.mode = event.value;
    (this.mathfieldRef.nativeElement.childNodes[0] as HTMLElement).focus();
  }

  getMathMLValue(): string {
    return this.mathFieldElement.getValue('math-ml');
  }
rhenck's avatar
rhenck committed
}

@NgModule({
  declarations: [
    MathInputComponent
  ],
  imports: [
    CommonModule,
    MatButtonToggleModule
  ],
  exports: [
    MathInputComponent
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class MathEditorModule {}