<commit_before>import {Observer} from 'rxjs/Observer';
import {Observable} from 'rxjs/Observable';
import {IdaiFieldDocument} from 'idai-components-2/field';
import {IdaiFieldDocumentReadDatastore} from '../../../core/datastore/field/idai-field-document-read-datastore';
import {ObserverUtil} from '../../../util/observer-util';
import {ObjectUtil} from '../../../util/object-util';
import {ResourcesStateManager} from './resources-state-manager';
import {NavigationPath} from './state/navigation-path';


/**
 * @author <NAME>
 * @author <NAME>
 */
export class NavigationPathManager {

    private navigationPathObservers: Array<Observer<NavigationPath>> = [];

    constructor(private resourcesState: ResourcesStateManager,
                private datastore: IdaiFieldDocumentReadDatastore) {}


    public navigationPathNotifications = (): Observable<NavigationPath> =>
        ObserverUtil.register(this.navigationPathObservers);


    public setDisplayHierarchy(displayHierarchy: boolean) {

        this.resourcesState.setDisplayHierarchy(displayHierarchy);
        this.notify();
    }


    public setBypassOperationTypeSelection(bypassOperationTypeSelection: boolean) {

        this.resourcesState.setBypassOperationTypeSelection(bypassOperationTypeSelection);
        this.notify();
    }


    public async moveInto(document: IdaiFieldDocument|undefined) {

        const invalidSegment = await NavigationPath.findInvalidSegment(
            this.resourcesState.getMainTypeDocumentResourceId(),
            this.resourcesState.getNavigationPath(),
            async (resourceId: string) => (await this.datastore.find({ q: '',
                        constraints: { 'id:match': resourceId }})).totalCount !== 0);

        const validatedNavigationPath = invalidSegment
            ? NavigationPath.shorten(this.resourcesState.getNavigationPath(), invalidSegment)
            : this.resourcesState.getNavigationPath();

        const updatedNavigationPath = NavigationPath.setNewSelectedSegmentDoc(validatedNavigationPath, document);
        this.resourcesState.setNavigationPath(updatedNavigationPath);
        this.notify();
    }


    public async rebuildNavigationPath() {

        const segment = NavigationPath.getSelectedSegment(this.getNavigationPath());
        await this.moveInto(segment ? segment.document : undefined);
    }


    public setMainTypeDocument(selectedMainTypeDocumentResourceId: string|undefined) {

        if (!this.resourcesState.getDisplayHierarchy()) this.resourcesState.setBypassOperationTypeSelection(false);
        if (selectedMainTypeDocumentResourceId) this.resourcesState.setMainTypeDocument(selectedMainTypeDocumentResourceId);
        this.notify();
    }


    public async updateNavigationPathForDocument(document: IdaiFieldDocument) {

        if (!NavigationPath.isPartOfNavigationPath(document, this.getNavigationPath(), this.resourcesState.getMainTypeDocumentResourceId())) {
            await this.createNavigationPathForDocument(document);
        }
    }


    // TODO unit test that it returns a clone
    public getNavigationPath(): NavigationPath {

        if (this.resourcesState.isInOverview()) return NavigationPath.empty();
        if (!this.resourcesState.getMainTypeDocumentResourceId()) return NavigationPath.empty();

        return ObjectUtil.cloneObject(this.resourcesState.getNavigationPath());
    }


    private notify() {

        ObserverUtil.notify(this.navigationPathObservers, this.getNavigationPath());
    }


    private async createNavigationPathForDocument(document: IdaiFieldDocument) {

        const segments = await NavigationPath.makeSegments(document, resourceId => this.datastore.get(resourceId));
        if (segments.length == 0) return await this.moveInto(undefined);

        const navPath = NavigationPath.replaceSegmentsIfNecessary(
            this.resourcesState.getNavigationPath(), segments, segments[segments.length - 1].document.resource.id);

        this.resourcesState.setNavigationPath(navPath);
        this.notify();
    }
}
<commit_msg>switch to hierarchy mode if the document to select is not shown in the current flat hierarchy list<commit_after>import {Observer} from 'rxjs/Observer';
import {Observable} from 'rxjs/Observable';
import {IdaiFieldDocument} from 'idai-components-2/field';
import {IdaiFieldDocumentReadDatastore} from '../../../core/datastore/field/idai-field-document-read-datastore';
import {ObserverUtil} from '../../../util/observer-util';
import {ObjectUtil} from '../../../util/object-util';
import {ResourcesStateManager} from './resources-state-manager';
import {NavigationPath} from './state/navigation-path';


/**
 * @author <NAME>
 * @author <NAME>
 */
export class NavigationPathManager {

    private navigationPathObservers: Array<Observer<NavigationPath>> = [];

    constructor(private resourcesState: ResourcesStateManager,
                private datastore: IdaiFieldDocumentReadDatastore) {}


    public navigationPathNotifications = (): Observable<NavigationPath> =>
        ObserverUtil.register(this.navigationPathObservers);


    public setDisplayHierarchy(displayHierarchy: boolean) {

        this.resourcesState.setDisplayHierarchy(displayHierarchy);
        this.notify();
    }


    public setBypassOperationTypeSelection(bypassOperationTypeSelection: boolean) {

        this.resourcesState.setBypassOperationTypeSelection(bypassOperationTypeSelection);
        this.notify();
    }


    public async moveInto(document: IdaiFieldDocument|undefined) {

        const invalidSegment = await NavigationPath.findInvalidSegment(
            this.resourcesState.getMainTypeDocumentResourceId(),
            this.resourcesState.getNavigationPath(),
            async (resourceId: string) => (await this.datastore.find({ q: '',
                        constraints: { 'id:match': resourceId }})).totalCount !== 0);

        const validatedNavigationPath = invalidSegment
            ? NavigationPath.shorten(this.resourcesState.getNavigationPath(), invalidSegment)
            : this.resourcesState.getNavigationPath();

        const updatedNavigationPath = NavigationPath.setNewSelectedSegmentDoc(validatedNavigationPath, document);
        this.resourcesState.setNavigationPath(updatedNavigationPath);
        this.notify();
    }


    public async rebuildNavigationPath() {

        const segment = NavigationPath.getSelectedSegment(this.getNavigationPath());
        await this.moveInto(segment ? segment.document : undefined);
    }


    public setMainTypeDocument(selectedMainTypeDocumentResourceId: string|undefined) {

        if (!this.resourcesState.getDisplayHierarchy()) this.resourcesState.setBypassOperationTypeSelection(false);
        if (selectedMainTypeDocumentResourceId) this.resourcesState.setMainTypeDocument(selectedMainTypeDocumentResourceId);
        this.notify();
    }


    public async updateNavigationPathForDocument(document: IdaiFieldDocument) {

        this.resourcesState.setDisplayHierarchy(true);

        if (!NavigationPath.isPartOfNavigationPath(document, this.getNavigationPath(),
                this.resourcesState.getMainTypeDocumentResourceId())) {
            await this.createNavigationPathForDocument(document);
        }
    }


    // TODO unit test that it returns a clone
    public getNavigationPath(): NavigationPath {

        if (this.resourcesState.isInOverview()) return NavigationPath.empty();
        if (!this.resourcesState.getMainTypeDocumentResourceId()) return NavigationPath.empty();

        return ObjectUtil.cloneObject(this.resourcesState.getNavigationPath());
    }


    private notify() {

        ObserverUtil.notify(this.navigationPathObservers, this.getNavigationPath());
    }


    private async createNavigationPathForDocument(document: IdaiFieldDocument) {

        const segments = await NavigationPath.makeSegments(document, resourceId => this.datastore.get(resourceId));
        if (segments.length == 0) return await this.moveInto(undefined);

        const navPath = NavigationPath.replaceSegmentsIfNecessary(
            this.resourcesState.getNavigationPath(), segments, segments[segments.length - 1].document.resource.id);

        this.resourcesState.setNavigationPath(navPath);
        this.notify();
    }
}

{'fraction_non_alphanumeric': 0.1027471862519666, 'fraction_numerical': 0.0009681713663318407, 'mean_word_length': 3.960384153661465, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1458803', 'n_tokens_mistral': 2125, 'n_tokens_neox': 2034, 'n_words': 368}