Skip to content
Snippets Groups Projects
Commit 7d47bfa3 authored by jojohoch's avatar jojohoch
Browse files

Implement basic functionality for range marking

parent 0c63959d
No related branches found
No related tags found
No related merge requests found
Showing
with 180 additions and 13 deletions
......@@ -3,7 +3,7 @@ import {
} from '@angular/core';
import { TextElement } from 'common/models/elements/text/text';
import { BehaviorSubject } from 'rxjs';
import { MarkingData } from 'common/models/marking-data';
import { MarkingRange, MarkingData } from 'common/models/marking-data';
import { ValueChangeElement } from 'common/interfaces';
import { ElementComponent } from '../../directives/element-component.directive';
......@@ -18,7 +18,7 @@ import { ElementComponent } from '../../directives/element-component.directive';
elementModel.highlightableOrange"
[sticky]="true"
[selectedColor]="selectedColor.value || 'none'"
[hasDeleteButton]="elementModel.markingMode === 'selection'"
[hasDeleteButton]="elementModel.markingMode !== 'word'"
[elementModel]="elementModel"
(markingDataChanged)="selectedColor.next($event.colorName); markingDataChanged.emit($event)">
</aspect-text-marking-bar>
......@@ -66,11 +66,14 @@ export class TextComponent extends ElementComponent implements OnInit {
@Output() textSelectionStart = new EventEmitter<PointerEvent>();
@Output() markingDataChanged = new EventEmitter<MarkingData>();
markingRange!: BehaviorSubject<MarkingRange | null> | null;
selectedColor: BehaviorSubject<string | undefined> = new BehaviorSubject<string | undefined>(undefined);
@ViewChild('textContainerRef') textContainerRef!: ElementRef;
ngOnInit(): void {
this.markingRange = this.elementModel.markingMode === 'range' ?
new BehaviorSubject<MarkingRange | null>(null) : null;
this.selectedColor.subscribe(color => this.selectedColorChanged.emit(color));
}
......
......@@ -9,3 +9,8 @@ export interface MarkingPanelMarkingData {
id: string,
markingData: MarkingData
}
export interface MarkingRange {
first: number | null;
second: number | null;
}
......@@ -84,7 +84,7 @@ import { DialogService } from '../../../../../services/dialog.service';
!combinedProperties.highlightableTurquoise &&
!combinedProperties.highlightableOrange)"
(selectionChange)="updateModel.emit({ property: 'markingMode', value: $event.value })">
<mat-option *ngFor="let option of ['selection', 'word']"
<mat-option *ngFor="let option of ['selection', 'word', 'range']"
[value]="option">
{{ 'propertiesPanel.markingMode-' + option | translate }}
</mat-option>
......
......@@ -65,6 +65,7 @@ export class MarkableSupport {
});
componentRef.instance.markables = markablesContainer.markables;
componentRef.instance.selectedColor = elementComponent.selectedColor;
componentRef.instance.markingRange = elementComponent.markingRange;
componentRef.instance.markablesChange.subscribe(() => {
elementComponent.elementValueChanged.emit(
{
......
......@@ -119,7 +119,7 @@ export class InteractiveGroupElementComponent
.subscribe(markingColor => {
if (markingColor.markingPanels.includes(this.elementModel.id)) {
this.selectedColor = markingColor.color;
this.hasDeleteButton = (markingColor.markingMode === 'selection');
this.hasDeleteButton = (markingColor.markingMode !== 'word');
}
});
}
......
<span [class.is-active]="!!(markColor && markColor !== 'none')"
[style.background-color]="color ? color : 'transparent'"
(click)="toggleMarked()">{{text}}</span>
[class.is-selected-yellow]="markColor === 'yellow' &&
markingRange?.value?.first === id &&
markingRange?.value?.second === null"
[class.is-selected-turquoise]="markColor === 'turquoise' &&
markingRange?.value?.first === id &&
markingRange?.value?.second === null"
[class.is-selected-orange]="markColor === 'orange' &&
markingRange?.value?.first === id &&
markingRange?.value?.second === null"
[class.is-selected-delete]="markColor === 'delete' &&
markingRange?.value?.first === id &&
markingRange?.value?.second === null"
[style.background-color]="color ? color : null"
(click)="onWordClick()">{{text}}</span>
.is-active {
cursor: pointer;
}
@mixin selectionAnimation($animation) {
animation: $animation 1s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-play-state: running;
}
@keyframes yellowColorChange {
80% { background: #f9f871; }
}
@keyframes turquoiseColorChange {
80% { background: #9de8eb; }
}
@keyframes orangeColorChange {
80% { background: #ffa06a; }
}
@keyframes deleteColorChange {
80% { background: lightgrey; }
}
.is-selected-yellow {
@include selectionAnimation(yellowColorChange);
}
.is-selected-turquoise {
@include selectionAnimation(turquoiseColorChange);
}
.is-selected-orange {
@include selectionAnimation(orangeColorChange);
}
.is-selected-delete {
@include selectionAnimation(deleteColorChange);
}
import {
Component, EventEmitter, Input, Output
Component, EventEmitter, Input, OnDestroy, OnInit, Output
} from '@angular/core';
import { TextElement } from 'common/models/elements/text/text';
import { BehaviorSubject, Subject } from 'rxjs';
import { MarkingRange } from 'common/models/marking-data';
import { NativeEventService } from 'player/src/app/services/native-event.service';
import { first, takeUntil } from 'rxjs/operators';
@Component({
selector: 'aspect-markable-word',
......@@ -10,21 +15,116 @@ import { TextElement } from 'common/models/elements/text/text';
templateUrl: './markable-word.component.html',
styleUrl: './markable-word.component.scss'
})
export class MarkableWordComponent {
export class MarkableWordComponent implements OnInit, OnDestroy {
@Input() id!: number;
@Input() text = '';
@Input() markingRange!: BehaviorSubject<MarkingRange | null> | null;
@Input() color!: string | null;
@Input() markColor!: string | undefined;
@Output() colorChange = new EventEmitter<string | null>();
toggleMarked(): void {
private ngUnsubscribe = new Subject<void>();
constructor(private nativeEventService: NativeEventService) {}
ngOnInit(): void {
if (this.markingRange) {
this.markingRange.subscribe(() => this.applyRangeColor());
}
}
onWordClick(): void {
if (!this.markColor || this.markColor === 'none') {
return;
}
if (this.color && this.color === TextElement.selectionColors[this.markColor]) {
this.color = null;
if (!this.markingRange) {
this.toggleMarked(this.markColor);
} else {
this.toggleRange(this.markColor);
}
}
private applyRangeColor(): void {
if (!this.markColor || this.markColor === 'none') {
return;
}
const range = this.getRange();
if (range && this.id >= range.start && this.id <= range.end) {
this.toggleRangedMarked(this.markColor);
}
}
private getRange(): { start: number, end: number } | null {
if (this.markingRange?.value) {
const firstWord = this.markingRange.value.first;
const secondWord = this.markingRange.value.second;
if (firstWord !== null && secondWord !== null) {
return {
start: Math.min(firstWord, secondWord),
end: Math.max(firstWord, secondWord)
};
}
}
return null;
}
private toggleRange(markColor: string): void {
const actualValue = this.markingRange?.value as MarkingRange | null;
if (actualValue === null) {
if (this.color && this.color === TextElement.selectionColors[markColor]) {
this.unmark();
this.markingRange?.next(null);
} else {
this.subscribeForPointerUp();
this.markingRange?.next({ first: this.id, second: null });
}
} else {
this.markingRange?.next({ ...actualValue, second: this.id });
}
}
private subscribeForPointerUp(): void {
this.nativeEventService.pointerUp
.pipe(takeUntil(this.ngUnsubscribe), first())
.subscribe(() => this.cleanMarking());
}
private cleanMarking(): void {
setTimeout(() => {
if (this.markingRange?.value) {
this.markingRange.next(null);
}
});
}
private toggleRangedMarked(markColor: string): void {
if (this.markColor === 'delete') {
this.unmark();
} else {
this.color = TextElement.selectionColors[this.markColor];
this.mark(markColor);
}
}
private toggleMarked(markColor: string): void {
if (this.color && this.color === TextElement.selectionColors[markColor]) {
this.unmark();
} else {
this.mark(markColor);
}
}
private mark(markColor: string): void {
this.color = TextElement.selectionColors[markColor];
this.colorChange.emit(this.color);
}
private unmark(): void {
this.color = null;
this.colorChange.emit(this.color);
}
ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}
}
......@@ -4,6 +4,8 @@
}
@if (markable.word) {
<aspect-markable-word class="prevent-select"
[id]="markable.id"
[markingRange]="markingRange"
[text]="markable.word"
[markColor]="selectedColor.value"
[(color)]="markable.color"
......
......@@ -4,12 +4,16 @@ import {
import { MarkableWordComponent } from 'player/src/app/components/markable-word/markable-word.component';
import { Markable } from 'player/src/app/models/markable.interface';
import { BehaviorSubject } from 'rxjs';
import { JsonPipe } from '@angular/common';
import { MarkingRange } from 'common/models/marking-data';
@Component({
selector: 'aspect-markables-container',
standalone: true,
imports: [
MarkableWordComponent
MarkableWordComponent,
JsonPipe
],
templateUrl: './markables-container.component.html',
styleUrl: './markables-container.component.scss'
......@@ -17,6 +21,7 @@ import { BehaviorSubject } from 'rxjs';
export class MarkablesContainerComponent {
@Input() selectedColor!: BehaviorSubject<string | undefined>;
@Input() markables!: Markable[];
@Input() markingRange!: BehaviorSubject<MarkingRange | null> | null;
@Input() markablesChange: EventEmitter<void> = new EventEmitter<void>();
onColorChange() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment