import { Navigate } from '@ngxs/router-plugin' ; import { Action , NgxsOnInit , Selector , State , StateContext } from '@ngxs/store' ; import { throwError } from 'rxjs' ; import { catchError , finalize , tap } from 'rxjs/operators' ; import { Logout } from '../../auth/stores/auth.state' ; import { errorToMessage } from '../../shared/util/api-error' ; import { Locale } from '../models/locale' ; import { ProjectLocale } from '../models/project-locale' ; import { Translation } from '../models/translation' ; import { ProjectTranslationsService } from '../services/translations.service' ; import { ClearCurrentProject , RefreshProjectStats } from './projects.state' ; import { LabelTranslation , UnlabelTranslation } from './project-label.state' ; import { Injectable } from '@angular/core' ; import { sortBy } from 'lodash' ; export class ClearMessages { static readonly type = '[Translations] Clear messages' ; } export class GetKnownLocales { static readonly type = '[Translations] Get known locales' ; } export class GetProjectLocales { static readonly type = '[Translations] Get project locales' ; constructor ( public projectId ) { } } export class AddProjectLocale { static readonly type = '[Translations] Add project locale' ; constructor ( public projectId , public localeCode ) { } } export class DeleteProjectLocale { static readonly type = '[Translations] Delete project locale' ; constructor ( public projectId , public localeCode ) { } } export class GetTranslations { static readonly type = '[Translations] Get translation' ; constructor ( public projectId , public localeCode ) { } } export class UpdateTranslation { static readonly type = '[Translations] Update translation' ; constructor ( public projectId , public localeCode , public termId , public value ) { } } export interface TranslationsStateModel { projectLocales : ProjectLocale [ ] ; knownLocales : Locale [ ] ; translations : { [ localeCode ] : Translation [ ] ; } ; isLoading : boolean ; errorMessage : string | undefined ; } const stateDefaults = { projectLocales : [ ] , knownLocales : [ ] , translations : { } , isLoading : false , errorMessage : undefined , } ; @ State < TranslationsStateModel > ( { name : 'translations' , defaults : stateDefaults , } ) @ Injectable ( { providedIn : 'root' } ) export class TranslationsState implements NgxsOnInit { constructor ( private translationService ) { } @ Selector ( ) static isLoading ( state ) { return state . isLoading ; } @ Selector ( ) static projectLocales ( state ) { return state . projectLocales ; } @ Selector ( ) static projectTranslations ( state ) { return state . translations ; } @ Selector ( ) static knownLocales ( state ) { return state . knownLocales ; } ngxsOnInit ( ctx ) { } @ Action ( Logout ) logout ( ctx , action ) { ctx . setState ( stateDefaults ) ; } @ Action ( GetKnownLocales ) getKnownLocales ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . loadKnownLocales ( ) . pipe ( tap ( knownLocales => ctx . patchState ( { knownLocales } ) ) , catchError ( error => { ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( GetProjectLocales ) getProjectLocales ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . findProjectLocales ( action . projectId ) . pipe ( tap ( projectLocales => ctx . patchState ( { projectLocales : sortBy ( projectLocales , v => v . locale . code ) } ) ) , catchError ( error => { ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( AddProjectLocale ) addProjectLocale ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . addProjectLocale ( action . projectId , action . localeCode ) . pipe ( tap ( projectLocale => ctx . patchState ( { projectLocales : [ ... ctx . getState ( ) . projectLocales , projectLocale ] } ) ) , catchError ( error => { ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , tap ( ( ) => ctx . dispatch ( new RefreshProjectStats ( ) ) ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( DeleteProjectLocale ) deleteProjectLocale ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . deleteProjectLocale ( action . projectId , action . localeCode ) . pipe ( tap ( ( ) => ctx . patchState ( { projectLocales : ctx . getState ( ) . projectLocales . filter ( x => x . locale . code !== action . localeCode ) , } ) ) , tap ( ( ) => ctx . dispatch ( new RefreshProjectStats ( ) ) ) , catchError ( error => { ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( GetTranslations ) getTranslations ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . findProjectTranslation ( action . projectId , action . localeCode ) . pipe ( tap ( translation => ctx . patchState ( { translations : { ... ctx . getState ( ) . translations , [ action . localeCode ] : translation } , } ) ) , catchError ( error => { if ( error . status === 404 ) { ctx . dispatch ( new Navigate ( [ '404' ] ) ) ; } ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( UpdateTranslation ) updateTranslation ( ctx , action ) { ctx . patchState ( { isLoading : true } ) ; return this . translationService . updateTranslation ( action . projectId , action . localeCode , action . termId , action . value ) . pipe ( tap ( translation => { const storeTranslations = ctx . getState ( ) . translations ; let found = false ; let previousValue ; const forLocale = storeTranslations [ action . localeCode ] . map ( tr => { if ( tr . termId === translation . termId ) { found = true ; previousValue = tr . value ; return translation ; } return tr ; } ) ; if ( ! found ) { forLocale . push ( translation ) ; } const translations = { ... storeTranslations , [ action . localeCode ] : forLocale } ; ctx . patchState ( { translations } ) ; const newValueIsEmpty = ! translation . value || translation . value === '' ; const previousValueIsEmpty = ! previousValue || previousValue === '' ; const isAddition = previousValueIsEmpty && ! newValueIsEmpty ; const isRemoval = ! previousValueIsEmpty && newValueIsEmpty ; if ( isAddition || isRemoval ) { ctx . dispatch ( new RefreshProjectStats ( ) ) ; } } ) , catchError ( error => { ctx . patchState ( { errorMessage : errorToMessage ( error ) } ) ; return throwError ( error ) ; } ) , finalize ( ( ) => ctx . patchState ( { isLoading : false } ) ) ) ; } @ Action ( LabelTranslation ) labelTranslation ( ctx , action ) { const translations = ctx . getState ( ) . translations ; const forLocale = translations [ action . localeCode ] ; if ( forLocale ) { const updated = forLocale . map ( v => { if ( v . termId === action . termId ) { return { ... v , labels : [ ... v . labels , action . label ] } ; } return v ; } ) ; ctx . patchState ( { translations : { ... translations , [ action . localeCode ] : updated } } ) ; } } @ Action ( UnlabelTranslation ) unlabelTranslation ( ctx , action ) { const translations = ctx . getState ( ) . translations ; const forLocale = translations [ action . localeCode ] ; if ( forLocale ) { const updated = forLocale . map ( v => { if ( v . termId === action . termId ) { return { ... v , labels : v . labels . filter ( t => t . id !== action . label . id ) } ; } return v ; } ) ; ctx . patchState ( { translations : { ... translations , [ action . localeCode ] : updated } } ) ; } } @ Action ( ClearMessages ) clearMessages ( ctx ) { ctx . patchState ( { errorMessage : undefined } ) ; } @ Action ( ClearCurrentProject ) clearCurrentProject ( ctx ) { ctx . patchState ( { projectLocales : [ ] , translations : { } , errorMessage : undefined } ) ; } }