diff --git a/apps/octra/src/app/app.module.ts b/apps/octra/src/app/app.module.ts index 6572f8c56..008692d05 100644 --- a/apps/octra/src/app/app.module.ts +++ b/apps/octra/src/app/app.module.ts @@ -98,7 +98,7 @@ import * as fromUser from './core/store/user/user.reducer'; !environment.production ? StoreDevtoolsModule.instrument({ trace: !environment.production, - maxAge: 50, + maxAge: 200, logOnly: !environment.production, connectInZone: true, }) diff --git a/apps/octra/src/app/core/component/octra-dropzone/octra-dropzone.component.ts b/apps/octra/src/app/core/component/octra-dropzone/octra-dropzone.component.ts index 1f2fd3dfe..8c4a53079 100644 --- a/apps/octra/src/app/core/component/octra-dropzone/octra-dropzone.component.ts +++ b/apps/octra/src/app/core/component/octra-dropzone/octra-dropzone.component.ts @@ -1,4 +1,11 @@ -import { Component, Input, ViewChild } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + Output, + ViewChild, +} from '@angular/core'; +import { Store } from '@ngrx/store'; import { AnnotationLevelType, Converter, @@ -18,6 +25,8 @@ import { ImportOptionsModalComponent } from '../../modals/import-options-modal/i import { OctraModalService } from '../../modals/octra-modal.service'; import { SupportedFilesModalComponent } from '../../modals/supportedfiles-modal/supportedfiles-modal.component'; import { FileProgress } from '../../obj/objects'; +import { LoginMode, RootState } from '../../store'; +import { LoginModeActions } from '../../store/login-mode'; import { DefaultComponent } from '../default.component'; import { DropZoneComponent } from '../drop-zone'; @@ -29,6 +38,8 @@ import { DropZoneComponent } from '../drop-zone'; export class OctraDropzoneComponent extends DefaultComponent { @ViewChild('dropzone', { static: true }) dropzone!: DropZoneComponent; @Input() height = '250px'; + @Output() filesAdded = new EventEmitter(); + private _audioManager?: AudioManager; get AppInfo(): AppInfo { @@ -63,7 +74,10 @@ export class OctraDropzoneComponent extends DefaultComponent { return this._status; } - constructor(private modService: OctraModalService) { + constructor( + private modService: OctraModalService, + private store: Store + ) { super(); } @@ -359,6 +373,7 @@ export class OctraDropzoneComponent extends DefaultComponent { this.dropzone.clicklocked = false; }); } + this.filesAdded.emit(this.dropzone.files); } private resetFormatFileProgresses() { @@ -389,6 +404,7 @@ export class OctraDropzoneComponent extends DefaultComponent { this._status = 'invalid'; } } + this.filesAdded.emit(this.dropzone.files); } private decodeArrayBuffer( @@ -489,5 +505,21 @@ export class OctraDropzoneComponent extends DefaultComponent { if (result.action === 'apply') { fileProgress.options = result.result; } + + const importOptions = {}; + importOptions[fileProgress.converter.name] = fileProgress.options; + + this.store.dispatch( + LoginModeActions.setImportConverter.do({ + mode: LoginMode.LOCAL, + importConverter: fileProgress.converter.name + }) + ); + this.store.dispatch( + LoginModeActions.changeImportOptions.do({ + mode: LoginMode.LOCAL, + importOptions, + }) + ); } } diff --git a/apps/octra/src/app/core/modals/overview-modal/overview-modal.component.ts b/apps/octra/src/app/core/modals/overview-modal/overview-modal.component.ts index 09bccbe61..5e160810c 100644 --- a/apps/octra/src/app/core/modals/overview-modal/overview-modal.component.ts +++ b/apps/octra/src/app/core/modals/overview-modal/overview-modal.component.ts @@ -20,9 +20,6 @@ import { import { AnnotationStoreService } from '../../store/login-mode/annotation/annotation.store.service'; import { ShortcutService } from '../../shared/service/shortcut.service'; -declare let validateAnnotation: (transcript: string, guidelines: any) => any; -declare let tidyUpAnnotation: (transcript: string, guidelines: any) => any; - @Component({ selector: 'octra-overview-modal', templateUrl: './overview-modal.component.html', diff --git a/apps/octra/src/app/core/obj/Settings/project-configuration.ts b/apps/octra/src/app/core/obj/Settings/project-configuration.ts index 954fdb696..1d6813a81 100644 --- a/apps/octra/src/app/core/obj/Settings/project-configuration.ts +++ b/apps/octra/src/app/core/obj/Settings/project-configuration.ts @@ -10,10 +10,6 @@ export interface ProjectSettings { interfaces: boolean; help_url: string; }; - responsive: { - enabled: boolean; - fixedwidth: number; - }; agreement: { enabled: boolean; text: any; @@ -33,6 +29,7 @@ export interface ProjectSettings { sendValidatedTranscriptionOnly?: boolean; showOverviewIfTranscriptNotValid?: boolean; theme?: string; + importOptions?: Record; }; guidelines: any; } diff --git a/apps/octra/src/app/core/pages/intern/transcription/transcription.component.html b/apps/octra/src/app/core/pages/intern/transcription/transcription.component.html index b974b9e00..0e4a4aef4 100644 --- a/apps/octra/src/app/core/pages/intern/transcription/transcription.component.html +++ b/apps/octra/src/app/core/pages/intern/transcription/transcription.component.html @@ -45,7 +45,7 @@ overview: 'g.overview' | transloco, help: 'g.help' | transloco }" - [responsive]="responsive" + [responsive]="true" >
@@ -128,9 +128,7 @@ > @if (!appStorage.easyMode) { - + {{ 'g.quit' | transloco }} } @@ -143,9 +141,7 @@ > @if (!appStorage.easyMode) { - + {{ 'g.export data' | transloco }} } @@ -157,8 +153,7 @@ type="button" > @if (!appStorage.easyMode) { - {{ 'transcription.send' | transloco }} diff --git a/apps/octra/src/app/core/pages/intern/transcription/transcription.component.ts b/apps/octra/src/app/core/pages/intern/transcription/transcription.component.ts index 79e7f3c83..0fcc912ea 100644 --- a/apps/octra/src/app/core/pages/intern/transcription/transcription.component.ts +++ b/apps/octra/src/app/core/pages/intern/transcription/transcription.component.ts @@ -311,10 +311,6 @@ export class TranscriptionComponent return this.settingsService.projectsettings!; } - get responsive(): boolean { - return this.settingsService.responsive.enabled; - } - private _currentEditor!: ComponentRef; get currentEditor(): ComponentRef { @@ -467,19 +463,9 @@ export class TranscriptionComponent } }, }); - /* - this.subscribe( - this.transcrService.alertTriggered,(alertConfig) => { - this.alertService.showAlert( - alertConfig.type, - alertConfig.data, - alertConfig.unique, - alertConfig.duration - ); - }) - ); - */ + + this.annotationStoreService.overwriteTidyUpAnnotation(); this.navbarServ.interfaces = this.projectsettings.interfaces; this.shortcutService.registerGeneralShortcutGroup( diff --git a/apps/octra/src/app/core/pages/login/login.component.html b/apps/octra/src/app/core/pages/login/login.component.html index d71a3e86d..2648a33ba 100644 --- a/apps/octra/src/app/core/pages/login/login.component.html +++ b/apps/octra/src/app/core/pages/login/login.component.html @@ -14,7 +14,7 @@ }
- @if (compatibleBrowser === true) { + @if (compatibleBrowser !== false) {
{{ 'login.local mode' | transloco }}
- } @else if(compatibleBrowser === false) { + } @else { } diff --git a/apps/octra/src/app/core/shared/functions.ts b/apps/octra/src/app/core/shared/functions.ts index 65aa846d0..58c3e8e3a 100644 --- a/apps/octra/src/app/core/shared/functions.ts +++ b/apps/octra/src/app/core/shared/functions.ts @@ -150,7 +150,10 @@ export const isValidAnnotation = (io: TaskInputOutputDto, audiofile: any) => { ); if (result?.annotjson) { - return result.annotjson; + return { + annotjson: result.annotjson, + converter: converter.name + }; } else if ( converter.name === 'AnnotJSON' && /_annot\.json$/g.exec(io.filename) !== null diff --git a/apps/octra/src/app/core/shared/octra-database.ts b/apps/octra/src/app/core/shared/octra-database.ts index 3a0df1819..6919f3d78 100644 --- a/apps/octra/src/app/core/shared/octra-database.ts +++ b/apps/octra/src/app/core/shared/octra-database.ts @@ -466,6 +466,7 @@ export interface IIDBModeOptions { transcriptID?: string | null; feedback?: any; sessionfile?: any; + importConverter?: string; currentEditor?: string | null; currentLevel?: number | null; logging?: boolean | null; diff --git a/apps/octra/src/app/core/shared/service/idb.service.ts b/apps/octra/src/app/core/shared/service/idb.service.ts index cf21b13e7..f74cca113 100644 --- a/apps/octra/src/app/core/shared/service/idb.service.ts +++ b/apps/octra/src/app/core/shared/service/idb.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; -import { ConsoleEntry, ConsoleGroupEntry } from './bug-report.service'; +import { IAnnotJSON, OAnnotJSON } from '@octra/annotation'; +import { from, map, Observable, throwError } from 'rxjs'; +import { LoginMode } from '../../store'; import { DefaultModeOptions, IDBApplicationOptionName, @@ -7,9 +9,7 @@ import { IIDBModeOptions, OctraDatabase, } from '../octra-database'; -import { LoginMode } from '../../store'; -import { IAnnotJSON, OAnnotJSON } from '@octra/annotation'; -import { from, map, Observable, throwError } from 'rxjs'; +import { ConsoleEntry, ConsoleGroupEntry } from './bug-report.service'; @Injectable({ providedIn: 'root', @@ -163,6 +163,14 @@ export class IDBService { ); } + public loadImportOptions(mode: LoginMode): Observable { + return this.database.loadDataOfMode( + mode, + 'importOptions', + undefined + ); + } + /** * save one log item. */ @@ -177,6 +185,13 @@ export class IDBService { return this.database.saveModeData(mode, 'annotation', annotation, true); } + /** + * save converter options. + */ + public saveImportOptions(mode: LoginMode, options: Record) { + return this.database.saveModeData(mode, 'importOptions', options, true); + } + /** * clears logging data */ diff --git a/apps/octra/src/app/core/shared/service/settings.service.ts b/apps/octra/src/app/core/shared/service/settings.service.ts index f2c8381df..0cc3b23db 100644 --- a/apps/octra/src/app/core/shared/service/settings.service.ts +++ b/apps/octra/src/app/core/shared/service/settings.service.ts @@ -13,25 +13,6 @@ import { Subscription } from 'rxjs'; export class SettingsService { private subscrmanager: SubscriptionManager; - get responsive(): { - enabled: boolean; - fixedwidth: number; - } { - if ( - this.projectsettings !== undefined && - this.projectsettings.responsive !== undefined - ) { - return this.projectsettings.responsive; - } else { - return this.appSettings?.octra.responsive !== undefined - ? this.appSettings?.octra.responsive - : { - enabled: true, - fixedwidth: 1079, - }; - } - } - get projectsettings(): ProjectSettings | undefined { return getModeState(this.appStorage.snapshot)?.projectConfig; } diff --git a/apps/octra/src/app/core/store/application/application.effects.ts b/apps/octra/src/app/core/store/application/application.effects.ts index 49654d20d..99e7a694c 100644 --- a/apps/octra/src/app/core/store/application/application.effects.ts +++ b/apps/octra/src/app/core/store/application/application.effects.ts @@ -1,9 +1,12 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { getBrowserLang, TranslocoService } from '@jsverse/transloco'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Action, Store } from '@ngrx/store'; +import { uniqueHTTPRequest } from '@octra/ngx-utilities'; +import { isNumber } from '@octra/utilities'; +import { findElements, getAttr } from '@octra/web-media'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; -import { ApplicationActions } from '../application/application.actions'; -import { LoginModeActions } from '../login-mode'; import { catchError, exhaustMap, @@ -16,32 +19,29 @@ import { tap, withLatestFrom, } from 'rxjs'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { APIActions } from '../api'; -import { getBrowserLang, TranslocoService } from '@jsverse/transloco'; -import { uniqueHTTPRequest } from '@octra/ngx-utilities'; -import { ConfigurationService } from '../../shared/service/configuration.service'; -import { AppConfigSchema } from '../../schemata/appconfig.schema'; +import X2JS from 'x2js'; +import { environment } from '../../../../environments/environment'; import { AppInfo } from '../../../app.info'; +import { ErrorModalComponent } from '../../modals/error-modal/error-modal.component'; +import { OctraModalService } from '../../modals/octra-modal.service'; +import { AppSettings, ASRSettings } from '../../obj'; +import { AppConfigSchema } from '../../schemata/appconfig.schema'; +import { isIgnoredAction, isIgnoredConsoleAction } from '../../shared'; +import { SettingsService } from '../../shared/service'; +import { AppStorageService } from '../../shared/service/appstorage.service'; import { BugReportService, ConsoleType, } from '../../shared/service/bug-report.service'; -import { AppSettings, ASRSettings } from '../../obj'; +import { ConfigurationService } from '../../shared/service/configuration.service'; +import { RoutingService } from '../../shared/service/routing.service'; +import { APIActions } from '../api'; +import { ApplicationActions } from '../application/application.actions'; +import { AuthenticationActions } from '../authentication'; import { IDBActions } from '../idb/idb.actions'; -import { AppStorageService } from '../../shared/service/appstorage.service'; -import { SettingsService } from '../../shared/service'; import { getModeState, LoginMode, RootState } from '../index'; -import { AuthenticationActions } from '../authentication'; -import { RoutingService } from '../../shared/service/routing.service'; +import { LoginModeActions } from '../login-mode'; import { AnnotationActions } from '../login-mode/annotation/annotation.actions'; -import { OctraModalService } from '../../modals/octra-modal.service'; -import { ErrorModalComponent } from '../../modals/error-modal/error-modal.component'; -import { environment } from '../../../../environments/environment'; -import { findElements, getAttr } from '@octra/web-media'; -import X2JS from 'x2js'; -import { isNumber } from '@octra/utilities'; -import { isIgnoredAction, isIgnoredConsoleAction } from '../../shared'; @Injectable({ providedIn: 'root', @@ -825,10 +825,6 @@ export class ApplicationEffects { tap(([a, state]: [Action, RootState]) => { this.bugService.addEntriesFromDB(this.appStorage.consoleEntries); - if (!this.settingsService.responsive.enabled) { - this.setFixedWidth(); - } - // define languages const languages = state.application.appConfiguration!.octra.languages; const browserLang = @@ -1173,16 +1169,6 @@ export class ApplicationEffects { } } - private setFixedWidth() { - // set fixed width - const head = document.head || document.getElementsByTagName('head')[0]; - const style = document.createElement('style'); - style.type = 'text/css'; - style.innerText = - '.container {width:' + this.settingsService.responsive.fixedwidth + 'px}'; - head.appendChild(style); - } - private getParameterByName(name: string, url?: string) { if (!url) { url = document.location.href; diff --git a/apps/octra/src/app/core/store/idb/idb-effects.service.ts b/apps/octra/src/app/core/store/idb/idb-effects.service.ts index 8840d6f1b..e3f3e6d3a 100644 --- a/apps/octra/src/app/core/store/idb/idb-effects.service.ts +++ b/apps/octra/src/app/core/store/idb/idb-effects.service.ts @@ -1,10 +1,21 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { Action, Store } from '@ngrx/store'; +import { + ASRContext, + IAnnotJSON, + OAnnotJSON, + OctraAnnotation, + OctraAnnotationSegment, +} from '@octra/annotation'; +import { hasProperty } from '@octra/utilities'; +import { SessionStorageService } from 'ngx-webstorage'; import { catchError, exhaustMap, filter, forkJoin, + from, map, mergeAll, mergeMap, @@ -15,33 +26,23 @@ import { timer, withLatestFrom, } from 'rxjs'; -import { Action, Store } from '@ngrx/store'; -import { IDBService } from '../../shared/service/idb.service'; -import { getModeState, LoginMode, RootState } from '../index'; -import { - ASRContext, - IAnnotJSON, - OAnnotJSON, - OctraAnnotation, - OctraAnnotationSegment, -} from '@octra/annotation'; -import { SessionStorageService } from 'ngx-webstorage'; -import { ConsoleEntry } from '../../shared/service/bug-report.service'; -import { AnnotationActions } from '../login-mode/annotation/annotation.actions'; -import { IDBActions } from './idb.actions'; -import { ApplicationActions } from '../application/application.actions'; -import { ASRActions } from '../asr/asr.actions'; -import { UserActions } from '../user/user.actions'; -import { LoginModeActions } from '../login-mode/login-mode.actions'; import { IIDBApplicationOptions, IIDBModeOptions, } from '../../shared/octra-database'; -import { hasProperty } from '@octra/utilities'; -import { AuthenticationActions } from '../authentication'; -import { AnnotationState } from '../login-mode/annotation'; import { AudioService } from '../../shared/service'; +import { ConsoleEntry } from '../../shared/service/bug-report.service'; +import { IDBService } from '../../shared/service/idb.service'; import { RoutingService } from '../../shared/service/routing.service'; +import { ApplicationActions } from '../application/application.actions'; +import { ASRActions } from '../asr/asr.actions'; +import { AuthenticationActions } from '../authentication'; +import { getModeState, LoginMode, RootState } from '../index'; +import { AnnotationState } from '../login-mode/annotation'; +import { AnnotationActions } from '../login-mode/annotation/annotation.actions'; +import { LoginModeActions } from '../login-mode/login-mode.actions'; +import { UserActions } from '../user/user.actions'; +import { IDBActions } from './idb.actions'; @Injectable({ providedIn: 'root', @@ -56,6 +57,26 @@ export class IDBEffects { .initialize(state.application.appConfiguration!.octra.database.name) .pipe( map(() => { + this.store.dispatch( + IDBActions.loadImportOptions.do({ + mode: LoginMode.LOCAL, + }) + ); + this.store.dispatch( + IDBActions.loadImportOptions.do({ + mode: LoginMode.ONLINE, + }) + ); + this.store.dispatch( + IDBActions.loadImportOptions.do({ + mode: LoginMode.DEMO, + }) + ); + this.store.dispatch( + IDBActions.loadImportOptions.do({ + mode: LoginMode.URL, + }) + ); return forkJoin< [ IIDBApplicationOptions, @@ -436,23 +457,26 @@ export class IDBEffects { LoginModeActions.startAnnotation.success, ApplicationActions.changeApplicationOption.do, LoginModeActions.endTranscription.do, - AnnotationActions.setLevelIndex.do + AnnotationActions.setLevelIndex.do, + LoginModeActions.setImportConverter.do ), withLatestFrom(this.store), - exhaustMap(([action, appState]) => { + mergeMap(([action, appState]) => { const modeState = this.getModeStateFromString( appState, (action as any).mode ); + if (modeState) { + return this.idbService .saveModeOptions((action as any).mode, { sessionfile: - modeState && - modeState.sessionFile && + modeState?.sessionFile && Object.keys(modeState.sessionFile).length > 0 ? modeState.sessionFile.toAny() : null, + importConverter: modeState.importConverter, currentEditor: modeState.currentEditor ?? null, currentLevel: modeState.transcript?.selectedLevelIndex ?? null, logging: modeState.logging.enabled ?? null, @@ -675,9 +699,7 @@ export class IDBEffects { saveASRSettings$ = createEffect(() => this.actions$.pipe( - ofType( - ASRActions.setASRSettings.do - ), + ofType(ASRActions.setASRSettings.do), withLatestFrom(this.store), mergeMap(([, state]) => this.idbService.saveOption('asr', state.asr.settings).pipe( @@ -864,6 +886,22 @@ export class IDBEffects { ) ); + loadImportOptions$ = createEffect(() => + this.actions$.pipe( + ofType(IDBActions.loadImportOptions.do), + mergeMap((action) => { + return from(this.idbService.loadImportOptions(action.mode)).pipe( + map((importOptions) => + IDBActions.loadImportOptions.success({ + mode: action.mode, + importOptions, + }) + ) + ); + }) + ) + ); + saveConsoleEntries$ = createEffect( () => this.actions$.pipe( @@ -896,6 +934,17 @@ export class IDBEffects { { dispatch: false } ); + saveImportOptions$ = createEffect(() => + this.actions$.pipe( + ofType(LoginModeActions.changeImportOptions.do), + exhaustMap((action) => + this.idbService + .saveImportOptions(action.mode, action.importOptions) + .pipe(map(() => IDBActions.saveImportOptions.success())) + ) + ) + ); + clearAllData$ = createEffect( () => this.actions$.pipe( diff --git a/apps/octra/src/app/core/store/idb/idb.actions.ts b/apps/octra/src/app/core/store/idb/idb.actions.ts index aed3249a7..73158507b 100644 --- a/apps/octra/src/app/core/store/idb/idb.actions.ts +++ b/apps/octra/src/app/core/store/idb/idb.actions.ts @@ -1,14 +1,15 @@ import { createActionGroup, emptyProps, props } from '@ngrx/store'; -import { ConsoleEntry } from '../../shared/service/bug-report.service'; -import { - IIDBApplicationOptions, - IIDBModeOptions, -} from '../../shared/octra-database'; import { ASRContext, OctraAnnotation, OctraAnnotationSegment, } from '@octra/annotation'; +import { + IIDBApplicationOptions, + IIDBModeOptions, +} from '../../shared/octra-database'; +import { ConsoleEntry } from '../../shared/service/bug-report.service'; +import { LoginMode } from '../index'; export class IDBActions { static loadOptions = createActionGroup({ @@ -20,6 +21,7 @@ export class IDBActions { onlineOptions: IIDBModeOptions; demoOptions: IIDBModeOptions; urlOptions: IIDBModeOptions; + importOptions?: Record; }>(), fail: props<{ error: string; @@ -432,4 +434,30 @@ export class IDBActions { }>(), }, }); + + static saveImportOptions = createActionGroup({ + source: 'IDB/save import options', + events: { + success: emptyProps(), + fail: props<{ + error: string; + }>(), + }, + }); + + static loadImportOptions = createActionGroup({ + source: 'IDB/load import options', + events: { + do: props<{ + mode: LoginMode; + }>(), + success: props<{ + mode: LoginMode; + importOptions?: Record; + }>(), + fail: props<{ + error: string; + }>(), + }, + }); } diff --git a/apps/octra/src/app/core/store/login-mode/annotation/annotation.effects.ts b/apps/octra/src/app/core/store/login-mode/annotation/annotation.effects.ts index 7e78eeb5e..befffaeea 100644 --- a/apps/octra/src/app/core/store/login-mode/annotation/annotation.effects.ts +++ b/apps/octra/src/app/core/store/login-mode/annotation/annotation.effects.ts @@ -1,32 +1,8 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { TranslocoService } from '@jsverse/transloco'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { OctraAPIService } from '@octra/ngx-octra-api'; -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { TranslocoService } from '@jsverse/transloco'; -import { - catchError, - exhaustMap, - forkJoin, - interval, - map, - Observable, - of, - Subscription, - tap, - timer, - withLatestFrom, -} from 'rxjs'; -import { getModeState, LoginMode, RootState } from '../../index'; -import { OctraModalService } from '../../../modals/octra-modal.service'; -import { RoutingService } from '../../../shared/service/routing.service'; -import { AnnotationActions } from './annotation.actions'; -import { - AlertService, - AudioService, - UserInteractionsService, -} from '../../../shared/service'; -import { AppInfo } from '../../../../app.info'; import { AnnotJSONConverter, ImportResult, @@ -38,7 +14,6 @@ import { OLabel, PraatTextgridConverter, } from '@octra/annotation'; -import { AppStorageService } from '../../../shared/service/appstorage.service'; import { TaskDto, TaskInputOutputCreatorType, @@ -46,14 +21,27 @@ import { TaskStatus, ToolConfigurationAssetDto, } from '@octra/api-types'; -import { AnnotationState, GuidelinesItem } from './index'; -import { LoginModeActions } from '../login-mode.actions'; -import { AuthenticationActions } from '../../authentication'; -import { TranscriptionSendingModalComponent } from '../../../modals/transcription-sending-modal/transcription-sending-modal.component'; -import { NgbModalWrapper } from '../../../modals/ng-modal-wrapper'; -import { ApplicationActions } from '../../application/application.actions'; -import { ErrorModalComponent } from '../../../modals/error-modal/error-modal.component'; +import { SampleUnit } from '@octra/media'; +import { OctraAPIService } from '@octra/ngx-octra-api'; import { hasProperty, SubscriptionManager } from '@octra/utilities'; +import { + catchError, + exhaustMap, + forkJoin, + interval, + map, + Observable, + of, + Subscription, + tap, + timer, + withLatestFrom, +} from 'rxjs'; +import { AppInfo } from '../../../../app.info'; +import { ErrorModalComponent } from '../../../modals/error-modal/error-modal.component'; +import { NgbModalWrapper } from '../../../modals/ng-modal-wrapper'; +import { OctraModalService } from '../../../modals/octra-modal.service'; +import { TranscriptionSendingModalComponent } from '../../../modals/transcription-sending-modal/transcription-sending-modal.component'; import { createSampleProjectDto, createSampleTask, @@ -62,13 +50,25 @@ import { isValidAnnotation, StatisticElem, } from '../../../shared'; +import { + AlertService, + AudioService, + UserInteractionsService, +} from '../../../shared/service'; +import { AppStorageService } from '../../../shared/service/appstorage.service'; +import { RoutingService } from '../../../shared/service/routing.service'; +import { ApplicationActions } from '../../application/application.actions'; +import { AuthenticationActions } from '../../authentication'; import { checkAndThrowError } from '../../error.handlers'; -import { SampleUnit } from '@octra/media'; +import { getModeState, LoginMode, RootState } from '../../index'; +import { LoginModeActions } from '../login-mode.actions'; +import { AnnotationActions } from './annotation.actions'; +import { AnnotationState, GuidelinesItem } from './index'; -import { MaintenanceAPI } from '../../../component/maintenance/maintenance-api'; +import { FileInfo } from '@octra/web-media'; import { DateTime } from 'luxon'; +import { MaintenanceAPI } from '../../../component/maintenance/maintenance-api'; import { FeedBackForm } from '../../../obj/FeedbackForm/FeedBackForm'; -import { FileInfo } from '@octra/web-media'; import { ASRQueueItemType } from '../../asr'; declare const BUILD: { @@ -294,6 +294,15 @@ export class AnnotationEffects { ); } + if (a.mode !== LoginMode.LOCAL) { + this.store.dispatch( + LoginModeActions.changeImportOptions.do({ + mode: a.mode, + importOptions: a.projectSettings.octra.importOptions, + }) + ); + } + this.store.dispatch( AnnotationActions.initTranscriptionService.do({ mode: a.mode }) ); @@ -782,8 +791,8 @@ export class AnnotationEffects { inputs = [ { id: Date.now().toString(), - filename: state.localMode.sessionFile!.name, - fileType: state.localMode.sessionFile!.type, + filename: state.localMode.sessionFile?.name, + fileType: state.localMode.sessionFile?.type, chain_position: 0, type: 'input', creator_type: TaskInputOutputCreatorType.user, @@ -1011,7 +1020,13 @@ export class AnnotationEffects { transcript = transcript!.removeItemByIndex( i - 1, '', - false + false, + (transcript: string) => { + return tidyUpAnnotation( + transcript, + modeState.guidelines.selected.json + ); + } ); currentLevel = transcript.currentLevel as any; i--; @@ -1494,22 +1509,32 @@ export class AnnotationEffects { }) ); - const serverTranscript = task - ? findCompatibleFileFromIO( - task, - 'transcript', - (io: TaskInputOutputDto) => { - return isValidAnnotation( - io, - this.audio.audioManager.resource.getOAudioFile() - ); - } - ) + const importResult = task + ? findCompatibleFileFromIO< + | { + annotjson: OAnnotJSON; + converter?: string; + } + | undefined + >(task, 'transcript', (io: TaskInputOutputDto) => { + return isValidAnnotation( + io, + this.audio.audioManager.resource.getOAudioFile() + ); + }) : undefined; - if (serverTranscript) { + if (importResult?.annotjson) { // import server transcript - newAnnotation = OctraAnnotation.deserialize(serverTranscript); + this.store.dispatch( + LoginModeActions.setImportConverter.do({ + mode: rootState.application.mode, + importConverter: importResult?.converter, + }) + ); + newAnnotation = OctraAnnotation.deserialize( + importResult?.annotjson + ); } if (newAnnotation.levels.length === 0) { diff --git a/apps/octra/src/app/core/store/login-mode/annotation/annotation.reducer.ts b/apps/octra/src/app/core/store/login-mode/annotation/annotation.reducer.ts index a3e401ad5..00f6579eb 100644 --- a/apps/octra/src/app/core/store/login-mode/annotation/annotation.reducer.ts +++ b/apps/octra/src/app/core/store/login-mode/annotation/annotation.reducer.ts @@ -346,7 +346,13 @@ export class AnnotationStateReducers { .removeItemById( item.id, removeOptions?.silenceCode, - removeOptions?.mergeTranscripts + removeOptions?.mergeTranscripts, + (transcript: string) => { + return tidyUpAnnotation( + transcript, + state.guidelines?.selected?.json + ); + } ); } else if (item.index !== undefined && item.index !== null) { state.transcript = state.transcript @@ -354,7 +360,13 @@ export class AnnotationStateReducers { .removeItemByIndex( item.index, removeOptions?.silenceCode, - removeOptions?.mergeTranscripts + removeOptions?.mergeTranscripts, + (transcript: string) => { + return tidyUpAnnotation( + transcript, + state.guidelines?.selected?.json + ); + } ); } else { console.error( diff --git a/apps/octra/src/app/core/store/login-mode/annotation/annotation.store.service.ts b/apps/octra/src/app/core/store/login-mode/annotation/annotation.store.service.ts index 8624920c1..0a7ecbce5 100644 --- a/apps/octra/src/app/core/store/login-mode/annotation/annotation.store.service.ts +++ b/apps/octra/src/app/core/store/login-mode/annotation/annotation.store.service.ts @@ -22,7 +22,7 @@ import { SubscriptionManager, TsWorkerJob, } from '@octra/utilities'; -import { map, Observable } from 'rxjs'; +import { BehaviorSubject, map, Observable } from 'rxjs'; import { OLog, OLogging } from '../../../obj/Settings/logging'; import { KeyStatisticElem } from '../../../obj/statistics/KeyStatisticElem'; import { MouseStatisticElem } from '../../../obj/statistics/MouseStatisticElem'; @@ -35,9 +35,6 @@ import { getModeState, LoginMode, RootState } from '../../index'; import { LoginModeActions } from '../login-mode.actions'; import { AnnotationActions } from './annotation.actions'; -declare let validateAnnotation: (transcript: string, guidelines: any) => any; -declare let tidyUpAnnotation: (transcript: string, guidelines: any) => any; - @Injectable({ providedIn: 'root', }) @@ -48,13 +45,11 @@ export class AnnotationStoreService { return this._feedback; } - get silencePlaceholder(): string { + get silencePlaceholder(): string | undefined { if (this.guidelines?.markers) { - return ( - this.guidelines.markers.find((a) => a.type === 'break')?.code ?? '

' - ); + return this.guidelines.markers.find((a) => a.type === 'break')?.code; } - return '

'; + return undefined; } get transcript(): @@ -239,6 +234,13 @@ export class AnnotationStoreService { ) ); + importOptions$ = new BehaviorSubject | undefined>( + undefined + ); + importConverter$ = new BehaviorSubject( + undefined + ); + public set comment(value: string | undefined) { this.changeComment(value ?? ''); } @@ -303,6 +305,18 @@ export class AnnotationStoreService { }, }) ); + + this.store + .select((state: RootState) => { + return getModeState(state)?.importOptions; + }) + .subscribe(this.importOptions$); + + this.store + .select((state: RootState) => { + return getModeState(state)?.importConverter; + }) + .subscribe(this.importConverter$); } quit(clearSession: boolean, freeTask: boolean, redirectToProjects = false) { @@ -391,10 +405,10 @@ export class AnnotationStoreService { const results = validateAnnotation(rawText, this.guidelines); // check if selection is in the raw text - const sPos = rawText.indexOf('[[[sel-start/]]]'); - const sLen = '[[[sel-start/]]]'.length; - const ePos = rawText.indexOf('[[[sel-end/]]]'); - const eLen = '[[[sel-end/]]]'.length; + const sPos = rawText.indexOf('βœ‰βœ‰βœ‰sel-start/πŸ“©πŸ“©πŸ“©'); + const sLen = 'βœ‰βœ‰βœ‰sel-start/βœ‰βœ‰βœ‰'.length; + const ePos = rawText.indexOf('βœ‰βœ‰βœ‰sel-end/πŸ“©πŸ“©πŸ“©'); + const eLen = 'βœ‰βœ‰βœ‰sel-end/πŸ“©πŸ“©πŸ“©'.length; // look for segment boundaries like {23423424} const segRegex = new RegExp(/{[0-9]+}/g); @@ -436,7 +450,7 @@ export class AnnotationStoreService { public replaceSingleTags(html: string) { html = html.replace(/(<)([^<>]+)(>)/g, (g0, g1, g2) => { - return `[[[${g2}]]]`; + return `βœ‰βœ‰βœ‰${g2}πŸ“©πŸ“©πŸ“©`; }); html = html.replace(/([<>])/g, (g0, g1) => { @@ -446,8 +460,8 @@ export class AnnotationStoreService { return '>'; }); - html = html.replace(/((?:\[\[\[)|(?:]]]))/g, (g0, g1) => { - if (g1 === '[[[') { + html = html.replace(/((?:βœ‰βœ‰βœ‰)|(?:πŸ“©πŸ“©πŸ“©))/g, (g0, g1) => { + if (g1 === 'βœ‰βœ‰βœ‰') { return '<'; } @@ -537,13 +551,13 @@ export class AnnotationStoreService { g2 === 'u' && g2 === 's' ) { - return `[[[${g2}]]]`; + return `βœ‰βœ‰βœ‰${g2}πŸ“©πŸ“©πŸ“©`; } // check if it's a marker for (const marker of markers) { if (`${g1}${g2}${g3}` === marker.code) { - return `[[[${g2}]]]`; + return `βœ‰βœ‰βœ‰${g2}πŸ“©πŸ“©πŸ“©`; } } @@ -560,7 +574,7 @@ export class AnnotationStoreService { return '>'; }); - result = result.replace(/(\[\[\[)|(]]])/g, (g0, g1, g2) => { + result = result.replace(/(βœ‰βœ‰βœ‰)|(πŸ“©πŸ“©πŸ“©)/g, (g0, g1, g2) => { if (g2 === undefined && g1 !== undefined) { return '<'; } else { @@ -650,8 +664,8 @@ export class AnnotationStoreService { let result = rawtext; try { - const sPos = rawtext.indexOf('[[[sel-start/]]]'); - const sLen = '[[[sel-start/]]]'.length; + const sPos = rawtext.indexOf('βœ‰βœ‰βœ‰sel-start/πŸ“©πŸ“©πŸ“©'); + const sLen = 'βœ‰βœ‰βœ‰sel-start/πŸ“©πŸ“©πŸ“©'.length; interface Pos { start: number; @@ -684,16 +698,16 @@ export class AnnotationStoreService { ? validationElement.start : sPos + sLen + validationElement.start, puffer: - "[[[span class='val-error' data-errorcode='" + + "βœ‰βœ‰βœ‰span class='val-error' data-errorcode='" + validationElement.code + - "']]]", + "'πŸ“©πŸ“©πŸ“©", }; insertions.push(insertStart); } else { insertStart.puffer += - "[[[span class='val-error' data-errorcode='" + + "βœ‰βœ‰βœ‰span class='val-error' data-errorcode='" + validationElement.code + - "']]]"; + "'πŸ“©πŸ“©πŸ“©"; } let insertEnd = insertions.find((val) => { @@ -707,10 +721,10 @@ export class AnnotationStoreService { start: insertStart.start + validationElement.length, puffer: '', }; - insertEnd.puffer = '[[[/span]]]'; + insertEnd.puffer = 'βœ‰βœ‰βœ‰/spanπŸ“©πŸ“©πŸ“©'; insertions.push(insertEnd); } else { - insertEnd.puffer = '[[[/span]]]' + insertEnd.puffer; + insertEnd.puffer = 'βœ‰βœ‰βœ‰/spanπŸ“©πŸ“©πŸ“©' + insertEnd.puffer; } } } @@ -952,4 +966,44 @@ export class AnnotationStoreService { }) ); } + + overwriteTidyUpAnnotation() { + const tidyUp = (window as any).tidyUpAnnotation; + + (window as any).tidyUpAnnotation = (transcript, guidelines) => { + transcript = tidyUp(transcript, guidelines); + + // make sure there is only one speaker label for each unit if exists + if ( + this.importOptions$.value && this.importConverter$.value === "SRT" && + this.importOptions$.value['SRT']?.speakerIdentifierPattern + ) { + const pattern = + this.importOptions$.value['SRT'].speakerIdentifierPattern; + const regex = new RegExp(pattern, 'g'); + const matches: RegExpExecArray[] = []; + let match = regex.exec(transcript); + + while (match) { + matches.push(match); + match = regex.exec(transcript); + } + + for (let i = matches.length - 1; i > 0; i--) { + match = matches[i]; + transcript = + transcript.substring(0, match.index) + + transcript.substring(match.index + match[0].length); + match = regex.exec(transcript); + } + } + return transcript; + }; + } + + setImportConverter(mode: LoginMode, importConverter: string) { + this.store.dispatch( + LoginModeActions.setImportConverter.do({ mode, importConverter }) + ); + } } diff --git a/apps/octra/src/app/core/store/login-mode/annotation/index.ts b/apps/octra/src/app/core/store/login-mode/annotation/index.ts index b1a2d8480..79f8fff37 100644 --- a/apps/octra/src/app/core/store/login-mode/annotation/index.ts +++ b/apps/octra/src/app/core/store/login-mode/annotation/index.ts @@ -1,9 +1,3 @@ -import { pipe } from 'rxjs'; -import { getModeState, RootState } from '../../index'; -import { Histories, UndoRedoState } from 'ngrx-wieder'; -import { ILog } from '../../../obj/Settings/logging'; -import { ProjectSettings } from '../../../obj'; -import { ProjectDto, TaskDto, TaskInputOutputDto } from '@octra/api-types'; import { ASRContext, OctraAnnotation, @@ -11,9 +5,15 @@ import { OSegment, SegmentWithContext, } from '@octra/annotation'; +import { ProjectDto, TaskDto, TaskInputOutputDto } from '@octra/api-types'; import { OctraGuidelines } from '@octra/assets'; +import { Histories, UndoRedoState } from 'ngrx-wieder'; +import { pipe } from 'rxjs'; +import { ProjectSettings } from '../../../obj'; import { FeedBackForm } from '../../../obj/FeedbackForm/FeedBackForm'; import { SessionFile } from '../../../obj/SessionFile'; +import { ILog } from '../../../obj/Settings/logging'; +import { getModeState, RootState } from '../../index'; export interface GuidelinesItem { filename: string; @@ -50,6 +50,8 @@ export interface AnnotationState extends UndoRedoState { savingNeeded: boolean; isSaving: boolean; currentEditor?: string; + importOptions?: Record; + importConverter?: string; previousCurrentLevel?: number; audio: { loaded: boolean; diff --git a/apps/octra/src/app/core/store/login-mode/login-mode.actions.ts b/apps/octra/src/app/core/store/login-mode/login-mode.actions.ts index 5be31d298..7d08a1794 100644 --- a/apps/octra/src/app/core/store/login-mode/login-mode.actions.ts +++ b/apps/octra/src/app/core/store/login-mode/login-mode.actions.ts @@ -1,8 +1,14 @@ -import { AnnotationActions } from './annotation/annotation.actions'; -import { Action, createAction, createActionGroup, props } from '@ngrx/store'; -import { LoginMode } from '../index'; -import { CurrentAccountDto, ProjectDto, TaskDto } from '@octra/api-types'; import { HttpErrorResponse } from '@angular/common/http'; +import { + Action, + createAction, + createActionGroup, + emptyProps, + props, +} from '@ngrx/store'; +import { CurrentAccountDto, ProjectDto, TaskDto } from '@octra/api-types'; +import { LoginMode } from '../index'; +import { AnnotationActions } from './annotation/annotation.actions'; export class LoginModeActions extends AnnotationActions { public static setFeedback = createAction( @@ -26,6 +32,21 @@ export class LoginModeActions extends AnnotationActions { }, }); + static setImportConverter = createActionGroup({ + source: 'annotation/set import converter', + events: { + do: props<{ + mode: LoginMode; + importConverter: string; + }>(), + success: emptyProps(), + fail: props<{ + error: string; + }>(), + } + }); + + static clearOnlineSession = createActionGroup({ source: `annotation/ clear online session`, events: { @@ -87,4 +108,18 @@ export class LoginModeActions extends AnnotationActions { do: props<{ clearSession: boolean; mode: LoginMode }>(), }, }); + + static changeImportOptions = createActionGroup({ + source: 'annotation/change converter options', + events: { + do: props<{ + mode: LoginMode; + importOptions?: Record; + }>(), + success: emptyProps(), + fail: props<{ + error: string; + }>(), + }, + }); } diff --git a/apps/octra/src/app/core/store/login-mode/login-mode.reducer.ts b/apps/octra/src/app/core/store/login-mode/login-mode.reducer.ts index 7ff94b400..59ad7aad2 100644 --- a/apps/octra/src/app/core/store/login-mode/login-mode.reducer.ts +++ b/apps/octra/src/app/core/store/login-mode/login-mode.reducer.ts @@ -1,26 +1,26 @@ import { Action, ActionReducer, on } from '@ngrx/store'; -import * as fromAnnotation from './annotation/annotation.reducer'; -import { AnnotationStateReducers } from './annotation/annotation.reducer'; +import { + ASRContext, + OctraAnnotation, + OctraAnnotationSegment, +} from '@octra/annotation'; +import { getProperties } from '@octra/utilities'; import { undoRedo } from 'ngrx-wieder'; -import { AnnotationActions } from './annotation/annotation.actions'; -import { LoginModeActions } from './login-mode.actions'; -import { IDBActions } from '../idb/idb.actions'; +import { ProjectSettings } from '../../obj'; +import { SessionFile } from '../../obj/SessionFile'; import { DefaultModeOptions, IIDBModeOptions, } from '../../shared/octra-database'; -import { getProperties } from '@octra/utilities'; +import { ApplicationActions } from '../application/application.actions'; import { AuthenticationActions } from '../authentication'; +import { IDBActions } from '../idb/idb.actions'; import { LoginMode } from '../index'; -import { ProjectSettings } from '../../obj'; -import { ApplicationActions } from '../application/application.actions'; import { AnnotationState } from './annotation'; -import { - ASRContext, - OctraAnnotation, - OctraAnnotationSegment, -} from '@octra/annotation'; -import { SessionFile } from '../../obj/SessionFile'; +import { AnnotationActions } from './annotation/annotation.actions'; +import * as fromAnnotation from './annotation/annotation.reducer'; +import { AnnotationStateReducers } from './annotation/annotation.reducer'; +import { LoginModeActions } from './login-mode.actions'; export const initialState: AnnotationState = { ...fromAnnotation.initialState, @@ -101,7 +101,7 @@ export class LoginModeReducers { on( LoginModeActions.setFeedback, (state: AnnotationState, { feedback, mode }) => { - if (mode === mode) { + if (mode === this.mode) { return { ...state, currentSession: { @@ -313,6 +313,34 @@ export class LoginModeReducers { return state; } ), + on( + LoginModeActions.changeImportOptions.do, + IDBActions.loadImportOptions.success, + (state: AnnotationState, { mode, importOptions }) => { + if (mode === this.mode) { + return { + ...state, + importOptions, + }; + } + return state; + } + ), + on( + LoginModeActions.setImportConverter.do, + (state: AnnotationState, { mode, importConverter }) => { + if (mode === this.mode) { + return { + ...state, + importConverter, + currentSession: { + ...state.currentSession + }, + }; + } + return state; + } + ), on( ApplicationActions.setAppLanguage, (state: AnnotationState, { language }) => { @@ -383,6 +411,12 @@ export class LoginModeReducers { sessionFile: SessionFile.fromAny(value), }; break; + case 'importConverter': + state = { + ...state, + importConverter: value, + }; + break; } return state; diff --git a/apps/octra/src/app/editors/2D-editor/2D-editor.component.html b/apps/octra/src/app/editors/2D-editor/2D-editor.component.html index f6566c0e2..adee517a3 100644 --- a/apps/octra/src/app/editors/2D-editor/2D-editor.component.html +++ b/apps/octra/src/app/editors/2D-editor/2D-editor.component.html @@ -16,7 +16,7 @@ [audioChunk]="audioChunkLines" [easyMode]="appStorage.easyMode" [playbackRate]="appStorage.audioSpeed" - [responsive]="settingsService.responsive.enabled" + [responsive]="true" [volume]="appStorage.audioVolume" > diff --git a/apps/octra/src/app/editors/2D-editor/2D-editor.component.ts b/apps/octra/src/app/editors/2D-editor/2D-editor.component.ts index 46a70fc4c..123348514 100644 --- a/apps/octra/src/app/editors/2D-editor/2D-editor.component.ts +++ b/apps/octra/src/app/editors/2D-editor/2D-editor.component.ts @@ -539,6 +539,14 @@ export class TwoDEditorComponent this.openSegment(segnumber); } ); + + this.subscribe(this.annotationStoreService.importOptions$, { + next: (importOptions) => { + if (importOptions && Object.keys(importOptions).includes("SRT")) { + this.viewer.settings.speakerPattern = importOptions["SRT"]["speakerIdentifierPattern"]; + } + }, + }); } override ngOnDestroy() { diff --git a/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.html b/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.html index 92950f279..fd9d50740 100644 --- a/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.html +++ b/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.html @@ -103,7 +103,7 @@ [audioChunk]="audiochunk" [easyMode]="easyMode" [playbackRate]="appStorage.audioSpeed" - [responsive]="settingsService.responsive.enabled" + [responsive]="true" [volume]="appStorage.audioVolume" > - @if (responsive || easyMode) { - + @if (easyMode) { + & } @if (!easyMode) { - + {{ 'g.save' | transloco }} & {{ 'segments.previous' | transloco }} @@ -156,12 +156,12 @@ (click)="save(); close()" class="btn btn-info w-100 rounded-0" > - @if (responsive || easyMode) { - + @if (easyMode) { + } @if (!easyMode) { - + {{ 'g.save' | transloco }} & {{ 'g.close' | transloco }} @@ -174,13 +174,13 @@ class="btn btn-info w-100 rounded-0" (click)="save(); doDirectionAction('right')" > - @if (responsive || easyMode) { - + @if (easyMode) { + & } @if (!easyMode) { - + {{ 'g.save' | transloco }} & {{ 'segments.next' | transloco }} @@ -194,12 +194,12 @@ (click)="save(); close(); openOverview()" class="btn btn-info w-100 rounded-0" > - @if (responsive || easyMode) { - + @if (easyMode) { + & } @if (!easyMode) { - + {{ 'g.save' | transloco }} & {{ 'g.overview' | transloco }} diff --git a/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.ts b/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.ts index 17097195b..0c436c273 100644 --- a/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.ts +++ b/apps/octra/src/app/editors/2D-editor/transcr-window/transcr-window.component.ts @@ -112,10 +112,6 @@ export class TranscrWindowComponent return this.settingsService.projectsettings; } - get responsive(): boolean { - return this.settingsService.responsive.enabled; - } - get audioManager(): AudioManager { return this.audiochunk.audioManager; } @@ -443,7 +439,7 @@ export class TranscrWindowComponent this.editor.settings.markers = this.annotationStoreService.guidelines?.markers ?? []; - this.editor.settings.responsive = this.settingsService.responsive.enabled; + this.editor.settings.responsive = true; this.editor.settings.specialMarkers.boundary = true; this.loupe.name = 'transcr-window viewer'; this.loupe.settings.margin.top = 5; diff --git a/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.html b/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.html index 84523736f..39fc1a9e4 100644 --- a/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.html +++ b/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.html @@ -6,7 +6,7 @@ [audioChunk]="audiochunk" [easyMode]="appStorage.easyMode" [playbackRate]="appStorage.audioSpeed" - [responsive]="settingsService.responsive.enabled" + [responsive]="true" [volume]="appStorage.audioVolume" > diff --git a/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.ts b/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.ts index 2472fa86d..b60bc961d 100644 --- a/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.ts +++ b/apps/octra/src/app/editors/dictaphone-editor/dictaphone-editor.component.ts @@ -209,7 +209,7 @@ export class DictaphoneEditorComponent this.audiochunk = this.audioManager.mainchunk.clone(); this.editor.settings.markers = this.annotationStoreService.guidelines?.markers ?? []; - this.editor.settings.responsive = this.settingsService.responsive.enabled; + this.editor.settings.responsive = true; this.editor.settings.specialMarkers.boundary = true; this.editor.settings.highlightingEnabled = true; diff --git a/apps/octra/src/app/editors/linear-editor/linear-editor.component.html b/apps/octra/src/app/editors/linear-editor/linear-editor.component.html index 658858d56..cd702c101 100644 --- a/apps/octra/src/app/editors/linear-editor/linear-editor.component.html +++ b/apps/octra/src/app/editors/linear-editor/linear-editor.component.html @@ -31,7 +31,7 @@ [audioChunk]="selectedAudioChunk" [easyMode]="appStorage.easyMode" [playbackRate]="appStorage.audioSpeed" - [responsive]="settingsService.responsive.enabled" + [responsive]="true" [volume]="appStorage.audioVolume" > diff --git a/apps/octra/src/app/editors/linear-editor/linear-editor.component.ts b/apps/octra/src/app/editors/linear-editor/linear-editor.component.ts index 340025f78..d0455a453 100644 --- a/apps/octra/src/app/editors/linear-editor/linear-editor.component.ts +++ b/apps/octra/src/app/editors/linear-editor/linear-editor.component.ts @@ -505,7 +505,7 @@ export class LinearEditorComponent this.editorSettings.markers = this.annotationStoreService.guidelines?.markers ?? []; - this.editorSettings.responsive = this.settingsService.responsive.enabled; + this.editorSettings.responsive = true; this.editorSettings.disabledKeys.push('SHIFT + SPACE'); this.subscribe(this.annotationStoreService.currentLevel$, ($event) => { diff --git a/apps/octra/src/app/editors/trn-editor/trn-editor.component.html b/apps/octra/src/app/editors/trn-editor/trn-editor.component.html index 0a309bf99..e7b0c3b0b 100644 --- a/apps/octra/src/app/editors/trn-editor/trn-editor.component.html +++ b/apps/octra/src/app/editors/trn-editor/trn-editor.component.html @@ -164,7 +164,7 @@ [easyMode]="appStorage.easyMode" [playbackRate]="appStorage.audioSpeed" [audioChunk]="textEditor.audiochunk" - [responsive]="settingsService.responsive.enabled" + [responsive]="true" [volume]="appStorage.audioVolume" > @if (!showSignalDisplay) { diff --git a/apps/octra/src/app/editors/trn-editor/trn-editor.component.ts b/apps/octra/src/app/editors/trn-editor/trn-editor.component.ts index 8400ef546..1f05fb199 100644 --- a/apps/octra/src/app/editors/trn-editor/trn-editor.component.ts +++ b/apps/octra/src/app/editors/trn-editor/trn-editor.component.ts @@ -7,34 +7,17 @@ import { OnInit, ViewChild, } from '@angular/core'; -import { - AlertService, - AudioService, - SettingsService, - UserInteractionsService, -} from '../../core/shared/service'; -import { AppStorageService } from '../../core/shared/service/appstorage.service'; -import { OCTRAEditor, OctraEditorRequirements } from '../octra-editor'; -import { TranscrEditorComponent } from '../../core/component/transcr-editor'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; -import { ValidationPopoverComponent } from '../../core/component/transcr-editor/validation-popover/validation-popover.component'; -import { AudioViewerComponent, AudioviewerConfig } from '@octra/ngx-components'; +import { TranslocoService } from '@jsverse/transloco'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AnnotationLevelType, ASRContext, OctraAnnotationAnyLevel, OctraAnnotationSegment, } from '@octra/annotation'; -import { - ContextMenuAction, - ContextMenuComponent, -} from '../../core/component/context-menu/context-menu.component'; -import { TranslocoService } from '@jsverse/transloco'; -import { PermutationsReplaceModalComponent } from './modals/permutations-replace-modal/permutations-replace-modal.component'; -import { timer } from 'rxjs'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { OctraGuidelines } from '@octra/assets'; -import { AnnotationStoreService } from '../../core/store/login-mode/annotation/annotation.store.service'; +import { AudioViewerComponent, AudioviewerConfig } from '@octra/ngx-components'; import { AudioChunk, AudioManager, @@ -43,8 +26,23 @@ import { ShortcutEvent, ShortcutGroup, } from '@octra/web-media'; - -declare let validateAnnotation: any; +import { timer } from 'rxjs'; +import { + ContextMenuAction, + ContextMenuComponent, +} from '../../core/component/context-menu/context-menu.component'; +import { TranscrEditorComponent } from '../../core/component/transcr-editor'; +import { ValidationPopoverComponent } from '../../core/component/transcr-editor/validation-popover/validation-popover.component'; +import { + AlertService, + AudioService, + SettingsService, + UserInteractionsService, +} from '../../core/shared/service'; +import { AppStorageService } from '../../core/shared/service/appstorage.service'; +import { AnnotationStoreService } from '../../core/store/login-mode/annotation/annotation.store.service'; +import { OCTRAEditor, OctraEditorRequirements } from '../octra-editor'; +import { PermutationsReplaceModalComponent } from './modals/permutations-replace-modal/permutations-replace-modal.component'; @Component({ selector: 'octra-trn-editor', @@ -745,79 +743,9 @@ export class TrnEditorComponent sel?.addRange(range); } - sanitizeHTML(str: string): SafeHtml { - return this.sanitizer.bypassSecurityTrustHtml(str); - } - - getShownSegment( - startSamples: number, - segment: OctraAnnotationSegment, - i: number - ) { - /* TODO implement - const obj: ShownSegment = { - start: startSamples, - end: segment.time.samples, - label: segment.getLabel('Speaker')!.value, - id: segment.id, - isSelected: false, - transcription: { - text: segment.getFirstLabelWithoutName('Speaker')?.value ?? '', - safeHTML: undefined as any, - }, - validation: '', - }; - - let html = segment.getFirstLabelWithoutName('Speaker')?.value ?? ''; - if (this.appStorage.useMode !== LoginMode.URL) { - if ( - typeof validateAnnotation !== 'undefined' && - typeof validateAnnotation === 'function' && - this.transcrService.validationArray[i] !== undefined - ) { - if (obj.transcription.text !== '') { - html = this.transcrService.underlineTextRed( - obj.transcription.text, - this.transcrService.validationArray[i].validation - ); - } - } - - html = this.transcrService.rawToHTML(html); - html = html.replace(/((?:\[\[\[)|(?:]]]))/g, (g0, g1) => { - if (g1 === '[[[') { - return '<'; - } - return '>'; - }); - } else { - html = this.transcrService.rawToHTML(html); - html = html.replace(/((?:\[\[\[)|(?:]]]))/g, (g0, g1) => { - if (g1 === '[[[') { - return '<'; - } - return '>'; - }); - } - - html = html.replace(/(

)|(<\/p>)/g, ''); - obj.transcription.safeHTML = this.sanitizeHTML(html); - - return obj; - */ - } - onKeyUp($event: KeyboardEvent, i: number) { if ($event.code === 'Enter') { - this.saveAndCloseTranscrEditor().then(() => { - /* - this.uiService.addElementFromEvent('segment', { - value: 'updated' - }, Date.now(), undefined, undefined, undefined, { - start: startSample, - length: segment.time.samples - startSample - }, 'overview'); */ - }); + this.saveAndCloseTranscrEditor(); } else if ($event.code === 'Escape') { // close without saving this._textEditor.audiochunk.stopPlayback(); diff --git a/apps/octra/src/config/appconfig_sample.json b/apps/octra/src/config/appconfig_sample.json index f8a8133f2..66cd82235 100644 --- a/apps/octra/src/config/appconfig_sample.json +++ b/apps/octra/src/config/appconfig_sample.json @@ -13,38 +13,22 @@ }, "showdetails": true, "supportEmail": "no-email-provided@email.com", - "responsive": { - "enabled": true, - "fixedwidth": 1200 - }, "plugins": {}, "allowed_browsers": [ { "name": "Chrome", "version": "" }, - { - "name": "Chrome Mobile", - "version": "" - }, { "name": "Firefox", "version": "" }, - { - "name": "Firefox Mobile", - "version": "" - }, { "name": "Opera", "version": "" }, { - "name": "Opera Mini", - "version": "" - }, - { - "name": "Opera Mobile", + "name": "Microsoft Edge", "version": "" } ], diff --git a/apps/octra/src/config/localmode/functions.js b/apps/octra/src/config/localmode/functions.js index 271b8bd25..1894d730b 100644 --- a/apps/octra/src/config/localmode/functions.js +++ b/apps/octra/src/config/localmode/functions.js @@ -47,19 +47,10 @@ function validateAnnotation(annotation, guidelines) { function tidyUpAnnotation(annotation, guidelines) { var result = annotation; - result = result.replace(/<[~^a-z0-9]+>/g, function (x) { - return ' ' + x + ' '; - }); - //set whitespaces before * - result = result.replace(/(\w|Γ€|ΓΌ|ΓΆ|ß|Ü|Γ–|Γ„)\*(\w|Γ€|ΓΌ|ΓΆ|ß|Ü|Γ–|Γ„)/g, '$1 *$2'); - //set whitespaces before and after ** - result = result.replace(/(\*\*)|(\s\*\*)|(\*\*\s)/g, ' ** '); - - //replace all numbers of whitespaces to one + // replace all numbers of whitespaces to one result = result.replace(/\s+/g, ' '); - //replace whitespaces at start an end - result = result.replace(/^\s+/g, ''); - result = result.replace(/\s$/g, ''); + // replace whitespaces at start an end + result = result.replace(/(^\s+)|(\s+$)/g, ''); return result; } diff --git a/apps/octra/src/config/localmode/projectconfig.json b/apps/octra/src/config/localmode/projectconfig.json index 2e5b23fd5..1e6ba293d 100644 --- a/apps/octra/src/config/localmode/projectconfig.json +++ b/apps/octra/src/config/localmode/projectconfig.json @@ -21,9 +21,9 @@ }, "languages": ["de", "en"], "interfaces": [ + "2D-Editor", "Dictaphone Editor", "Linear Editor", - "2D-Editor", "TRN-Editor" ], "feedback_form": [ diff --git a/apps/octra/src/types/index.d.ts b/apps/octra/src/types/index.d.ts new file mode 100644 index 000000000..b83e1b4b3 --- /dev/null +++ b/apps/octra/src/types/index.d.ts @@ -0,0 +1,20 @@ +import { OctraGuidelines } from '@octra/assets'; + +export interface OctraValidationItem { + start: number; + length: number; + code: string; +} + +export {}; + +declare global { + export const validateAnnotation: ( + transcript: string, + guidelines: OctraGuidelines + ) => OctraValidationItem[]; + export const tidyUpAnnotation: ( + transcript: string, + guidelines: OctraGuidelines + ) => string; +} diff --git a/apps/octra/tsconfig.json b/apps/octra/tsconfig.json index 620957bbb..283749ecf 100644 --- a/apps/octra/tsconfig.json +++ b/apps/octra/tsconfig.json @@ -9,7 +9,8 @@ "noImplicitReturns": false, "noFallthroughCasesInSwitch": false, "allowSyntheticDefaultImports": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "typeRoots": ["./src/types"] }, "files": [], "include": [], diff --git a/libs/annotation/src/lib/annotation.ts b/libs/annotation/src/lib/annotation.ts index 3103e2c4e..8edc45db9 100644 --- a/libs/annotation/src/lib/annotation.ts +++ b/libs/annotation/src/lib/annotation.ts @@ -196,18 +196,6 @@ export class OctraAnnotation< } } - combineSegments( - segmentIndexStart: number, - segmentIndexEnd: number, - breakMarker: string - ) { - for (let i = segmentIndexStart; i < segmentIndexEnd; i++) { - this.removeItemByIndex(i); - i--; - segmentIndexEnd--; - } - } - removeLevel(id: number) { const index = this.levels.findIndex((a) => a.id === id); @@ -377,7 +365,8 @@ export class OctraAnnotation< removeItemByIndex( index: number, silenceValue?: string, - mergeTranscripts?: boolean + mergeTranscripts?: boolean, + changeTranscript?: (transcript: string) => string ) { if (!this.currentLevel) { throw new Error('Current level is undefined'); @@ -396,7 +385,7 @@ export class OctraAnnotation< const nextSegment = this.currentLevel.items[ index + 1 ] as OctraAnnotationSegment; - const transcription = ( + let transcript = ( this.currentLevel.items[index] as OctraAnnotationSegment ).getFirstLabelWithoutName('Speaker')?.value; @@ -404,28 +393,35 @@ export class OctraAnnotation< !silenceValue || (nextSegment.getFirstLabelWithoutName('Speaker')?.value !== silenceValue && - transcription !== silenceValue && + transcript !== silenceValue && mergeTranscripts) ) { // concat transcripts if ( nextSegment.getFirstLabelWithoutName('Speaker') && - transcription !== '' + transcript !== '' ) { - nextSegment.changeFirstLabelWithoutName( - 'Speaker', - transcription + - ' ' + - nextSegment.getFirstLabelWithoutName('Speaker')?.value - ); + transcript = + transcript + + ' ' + + nextSegment.getFirstLabelWithoutName('Speaker')?.value; + + if (changeTranscript) { + transcript = changeTranscript(transcript); + } + + nextSegment.changeFirstLabelWithoutName('Speaker', transcript); } else if ( !nextSegment.getFirstLabelWithoutName('Speaker') && - transcription !== '' + transcript !== '' ) { - nextSegment.changeFirstLabelWithoutName( - 'Speaker', - transcription ?? '' - ); + transcript = transcript ?? ''; + + if (changeTranscript) { + transcript = changeTranscript(transcript); + } + + nextSegment.changeFirstLabelWithoutName('Speaker', transcript); } } else if ( silenceValue && @@ -433,10 +429,12 @@ export class OctraAnnotation< silenceValue ) { // delete pause - nextSegment.changeFirstLabelWithoutName( - 'Speaker', - transcription ?? '' - ); + transcript = transcript ?? ''; + + if (changeTranscript) { + transcript = changeTranscript(transcript); + } + nextSegment.changeFirstLabelWithoutName('Speaker', transcript); } this.currentLevel.changeItem(nextSegment as any); } @@ -445,16 +443,25 @@ export class OctraAnnotation< ...this.currentLevel.items.slice(0, index), ...this.currentLevel.items.slice(index + 1), ] as any); - const t = ''; } return this; } - removeItemById(id: number, silenceCode?: string, mergeTranscripts?: boolean) { + removeItemById( + id: number, + silenceCode?: string, + mergeTranscripts?: boolean, + changeTranscript?: (transcript: string) => string + ) { const index = this.currentLevel?.items.findIndex((a) => a.id === id); if (index !== undefined && index > -1) { - return this.removeItemByIndex(index, silenceCode, mergeTranscripts); + return this.removeItemByIndex( + index, + silenceCode, + mergeTranscripts, + changeTranscript + ); } else { throw new Error(`Can't find item with id ${id}`); } diff --git a/libs/assets/src/lib/schemata/projectconfig.schema.json b/libs/assets/src/lib/schemata/projectconfig.schema.json index b793d5247..a44084096 100644 --- a/libs/assets/src/lib/schemata/projectconfig.schema.json +++ b/libs/assets/src/lib/schemata/projectconfig.schema.json @@ -1,9 +1,7 @@ { "$id": "v0.0.2", "type": "object", - "required": [ - "version" - ], + "required": ["version"], "properties": { "version": { "type": "string", @@ -23,17 +21,6 @@ }, "type": "object" }, - "responsive": { - "properties": { - "enabled": { - "type": "boolean" - }, - "fixedwidth": { - "type": "number" - } - }, - "type": "object" - }, "agreement": { "properties": { "enabled": { @@ -110,26 +97,11 @@ "showdetails": { "type": "boolean" }, - "responsive": { - "$id": "/properties/octra/responsive", - "properties": { - "enabled": { - "type": "boolean" - }, - "fixedwidth": { - "type": "number" - } - }, - "type": "object" - }, "tools": { "type": "array", "items": { "type": "string", - "enum": [ - "cut-audio", - "combine-phrases" - ] + "enum": ["cut-audio", "combine-phrases"] } }, "bugreport": { @@ -170,6 +142,9 @@ "type": "array" } }, + "importOptions": { + "type": "object" + }, "type": "object" } }, diff --git a/libs/assets/src/lib/schemata/projectconfig.schema.ts b/libs/assets/src/lib/schemata/projectconfig.schema.ts index c6a134ec8..dc84e6f50 100644 --- a/libs/assets/src/lib/schemata/projectconfig.schema.ts +++ b/libs/assets/src/lib/schemata/projectconfig.schema.ts @@ -169,6 +169,9 @@ export const OctraProjectConfigJSONSchema = { type: 'array', }, }, + importOptions: { + type: "object", + }, type: 'object', }, }, diff --git a/libs/ngx-components/src/lib/components/audio/audio-viewer/audio-viewer.service.ts b/libs/ngx-components/src/lib/components/audio/audio-viewer/audio-viewer.service.ts index 45c841368..126437659 100644 --- a/libs/ngx-components/src/lib/components/audio/audio-viewer/audio-viewer.service.ts +++ b/libs/ngx-components/src/lib/components/audio/audio-viewer/audio-viewer.service.ts @@ -3799,10 +3799,11 @@ export class AudioViewerService { index: number, silenceCode: string | undefined, mergeTranscripts: boolean, - triggerChange = true + triggerChange = true, + changeTranscript?: (transcript: string) => string ) { if (this.annotation?.currentLevel) { - this.annotation?.removeItemByIndex(index, silenceCode, mergeTranscripts); + this.annotation?.removeItemByIndex(index, silenceCode, mergeTranscripts, changeTranscript); if (triggerChange) { this.currentLevelChange.emit({ type: 'remove',