diff --git a/projects/aas-lib/src/lib/aas-table/aas-table-api.service.ts b/projects/aas-lib/src/lib/aas-table/aas-table-api.service.ts deleted file mode 100644 index 054296c0..00000000 --- a/projects/aas-lib/src/lib/aas-table/aas-table-api.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { AASCursor, AASDocumentNode, AASPage, aas } from 'common'; -import { encodeBase64Url } from '../convert'; - -@Injectable({ - providedIn: 'root' -}) -export class AASTableApiService { - constructor(private readonly http: HttpClient) { - } - - /** - * Returns a page of documents from the specified cursor. - * @param cursor The current cursor. - * @param filter A filter expression. - * @param language The language to used for the filter. - * @returns The document page. - */ - public getDocuments(cursor: AASCursor, filter?: string, language?: string): Observable { - let url = `/api/v1/documents?cursor=${encodeBase64Url(JSON.stringify(cursor))}`; - if (filter) { - url += `&filter=${encodeBase64Url(filter)}`; - if (language) { - url += `&language=${language}`; - } - } - return this.http.get(url); - } - - /** - * Loads the element structure of the specified document. - * @param endpointName The URL of the container. - * @param id The identification of the AAS document. - * @returns The root of the element structure. - */ - public getContent(endpointName: string, id: string): Observable { - return this.http.get( - `/api/v1/containers/${encodeBase64Url(endpointName)}/documents/${encodeBase64Url(id)}/content`); - } - - /** - * - * @param endpointName - * @param id - * @returns - */ - public getHierarchy(endpointName: string, id: string): Observable { - return this.http.get( - `/api/v1/containers/${encodeBase64Url(endpointName)}/documents/${encodeBase64Url(id)}/hierarchy`); - } -} \ No newline at end of file diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.actions.ts b/projects/aas-lib/src/lib/aas-table/aas-table.actions.ts index 84683493..9d663f81 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.actions.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.actions.ts @@ -7,79 +7,40 @@ *****************************************************************************/ import { createAction, props } from '@ngrx/store'; -import { AASDocument, AASDocumentNode, AASPage, aas } from 'common'; -import { AASTableRow } from './aas-table.state'; import { TypedAction } from '@ngrx/store/src/models'; +import { AASDocument } from 'common'; +import { AASTableRow } from './aas-table.state'; export enum AASTableActionType { - INIT_LIST_VIEW = '[AASTable] init list view', - GET_FIRST_PAGE = '[AASTable] get first page', - GET_NEXT_PAGE = '[AASTable] get next page', - GET_PREVIOUS_PAGE = '[AASTable] previous next page', SET_PAGE = '[AASTable] set page', - GET_LAST_PAGE = '[AASTable] get last page', - SET_CONTENT = '[AASTable] set content', - UPDATE_ROWS = '[AASTable] update Rows', EXPAND = '[AASTable] expand', COLLAPSE = '[AASTable] collapse', TOGGLE_SELECTED = '[AASTable] toggle selected', TOGGLE_SELECTIONS = '[AASTable] toggle selections', - ADD_ROOT = '[AASTable] add root', - INIT_TREE_VIEW = '[AASTable] init tree view', SET_SELECTIONS = '[AASTable] set selections', + UPDATE_LIST_VIEW = '[AASTable] update list view', + UPDATE_TREE_VIEW = '[AASTable] update tree view', } -export interface GetFirstPageAction extends TypedAction { - limit: number; - filter?: string; -} - -export interface GetLastPageAction extends TypedAction { - limit: number; - filter?: string; +export interface UpdateListViewAction extends TypedAction { + documents: AASDocument[]; } -export interface GetPreviousPageAction extends TypedAction { - limit: number; - filter?: string; +export interface UpdateTreeViewAction extends TypedAction { + documents: AASDocument[]; } -export interface GetNextPageAction extends TypedAction { - limit: number; - filter?: string; -} - -export const getFirstPage = createAction( - AASTableActionType.GET_FIRST_PAGE, - props<{ limit: number, filter?: string }>()); - -export const getNextPage = createAction( - AASTableActionType.GET_NEXT_PAGE, - props<{ limit: number, filter?: string }>()); +export const updateListView = createAction( + AASTableActionType.UPDATE_LIST_VIEW, + props<{ documents: AASDocument[] }>()); -export const getPreviousPage = createAction( - AASTableActionType.GET_PREVIOUS_PAGE, - props<{ limit: number, filter?: string }>()); +export const updateTreeView = createAction( + AASTableActionType.UPDATE_TREE_VIEW, + props<{ documents: AASDocument[] }>()); -export const getLastPage = createAction( - AASTableActionType.GET_LAST_PAGE, - props<{ limit: number, filter?: string }>()); - -export const setPage = createAction( +export const setRows = createAction( AASTableActionType.SET_PAGE, - props<{ page: AASPage }>()); - -export const setContent = createAction( - AASTableActionType.SET_CONTENT, - props<{ document: AASDocument, content: aas.Environment }>()); - -export const expandRow = createAction( - AASTableActionType.EXPAND, - props<{ row: AASTableRow }>()); - -export const collapseRow = createAction( - AASTableActionType.COLLAPSE, - props<{ row: AASTableRow }>()); + props<{ rows: AASTableRow[] }>()); export const toggleSelected = createAction( AASTableActionType.TOGGLE_SELECTED, @@ -88,13 +49,14 @@ export const toggleSelected = createAction( export const toggleSelections = createAction( AASTableActionType.TOGGLE_SELECTIONS); -export const addRoot = createAction( - AASTableActionType.ADD_ROOT, - props<{ nodes: AASDocumentNode[] }>()); - -export const initTreeView = createAction( - AASTableActionType.INIT_TREE_VIEW); - export const setSelections = createAction( AASTableActionType.SET_SELECTIONS, - props<{ documents: AASDocument[] }>()); \ No newline at end of file + props<{ documents: AASDocument[] }>()); + +export const expandRow = createAction( + AASTableActionType.EXPAND, + props<{ row: AASTableRow }>()); + +export const collapseRow = createAction( + AASTableActionType.COLLAPSE, + props<{ row: AASTableRow }>()); diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.component.html b/projects/aas-lib/src/lib/aas-table/aas-table.component.html index df8a0518..474c787e 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.component.html +++ b/projects/aas-lib/src/lib/aas-table/aas-table.component.html @@ -6,7 +6,7 @@ ! !----------------------------------------------------------------------------> - +
- + diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.component.ts b/projects/aas-lib/src/lib/aas-table/aas-table.component.ts index 83680edd..d90965c7 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.component.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.component.ts @@ -10,17 +10,15 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, S import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { AASDocument, equalArray } from 'common'; -import { Observable, Subscription, first, mergeMap } from 'rxjs'; +import { Observable, Subscription, map, mergeMap } from 'rxjs'; -import { ViewMode } from '../types/view-mode'; import { AASTableRow, AASTableFeatureState } from './aas-table.state'; import * as AASTableSelectors from './aas-table.selectors'; import * as AASTableActions from './aas-table.actions'; import { AASQuery } from '../types/aas-query-params'; import { ClipboardService } from '../clipboard.service'; import { WindowService } from '../window.service'; -import { AuthService } from '../auth/auth.service'; -import { NotifyService } from '../notify/notify.service'; +import { ViewMode } from '../types/view-mode'; @Component({ selector: 'fhg-aas-table', @@ -30,9 +28,6 @@ import { NotifyService } from '../notify/notify.service'; export class AASTableComponent implements OnInit, OnChanges, OnDestroy { private readonly store: Store; private readonly subscription: Subscription = new Subscription(); - private _filter = ''; - private _limit = 10; - private _viewMode = ViewMode.List; private _selected: AASDocument[] = []; private shiftKey = false; private altKey = false; @@ -40,16 +35,11 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { constructor( private readonly router: Router, store: Store, - private readonly auth: AuthService, private readonly clipboard: ClipboardService, - private readonly notify: NotifyService, private readonly window: WindowService ) { this.store = store as Store; this.rows = this.store.select(AASTableSelectors.selectRows); - this.isFirstPage = this.store.select(AASTableSelectors.selectIsFirstPage); - this.isLastPage = this.store.select(AASTableSelectors.selectIsLastPage); - this.totalCount = this.store.select(AASTableSelectors.selectTotalCount); this.everySelected = this.store.select(AASTableSelectors.selectEverySelected); this.someSelected = this.store.select(AASTableSelectors.selectSomeSelected); @@ -58,26 +48,10 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { } @Input() - public get viewMode(): ViewMode { - return this._viewMode; - } - - public set viewMode(value: ViewMode) { - if (value !== this._viewMode) { - this._viewMode = value; - if (this._viewMode === ViewMode.List) { - this.store.dispatch(AASTableActions.getFirstPage({ limit: this._limit })); - } else { - this.store.dispatch(AASTableActions.initTreeView()) - } - } - } - - @Input() - public filter: Observable | null = null; + public viewMode: Observable | null = null; @Input() - public limit: Observable | null = null; + public documents: Observable | null = null; @Output() public selectedChange = new EventEmitter(); @@ -94,15 +68,9 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { } } - public someSelected: Observable; + public readonly someSelected: Observable; - public everySelected: Observable; - - public readonly isFirstPage: Observable; - - public readonly isLastPage: Observable; - - public readonly totalCount: Observable; + public readonly everySelected: Observable; public readonly rows: Observable; @@ -112,35 +80,11 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { this._selected = documents; this.selectedChange.emit(documents); })); - - this.store.select(AASTableSelectors.selectViewMode).pipe( - first(viewMode => viewMode === ViewMode.Undefined), - mergeMap(() => this.auth.ready), - first(ready => ready), - first(), - ).subscribe({ - next: () => this.store.dispatch(AASTableActions.getFirstPage({ limit: this._limit })), - error: error => this.notify.error(error), - }); } public ngOnChanges(changes: SimpleChanges): void { - if (changes['filter'] && this.filter) { - this.subscription.add(this.filter.subscribe(filter => { - if (filter !== this._filter) { - this._filter = filter; - this.store.dispatch(AASTableActions.getFirstPage({ filter, limit: this._limit })); - } - })); - } - - if (changes['limit'] && this.limit) { - this.subscription.add(this.limit.subscribe(limit => { - if (limit !== this._limit) { - this._limit = limit; - this.store.dispatch(AASTableActions.getFirstPage({ filter: this._filter, limit })); - } - })); + if (changes['viewMode'] || changes['documents']) { + this.initialize(); } } @@ -150,32 +94,12 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { this.window.removeEventListener('keydown', this.keydown); } - public firstPage(): void { - this.store.dispatch(AASTableActions.getFirstPage({ filter: this._filter, limit: this._limit })); - } - - public previousPage(): void { - this.store.dispatch(AASTableActions.getPreviousPage({ filter: this._filter, limit: this._limit })) - } - - public nextPage(): void { - this.store.dispatch(AASTableActions.getNextPage({ filter: this._filter, limit: this._limit })) - } - - public lastPage(): void { - this.store.dispatch(AASTableActions.getLastPage({ filter: this._filter, limit: this._limit })) - } - public expand(row: AASTableRow): void { - if (this.viewMode === ViewMode.Tree) { - this.store.dispatch(AASTableActions.expandRow({ row })); - } + this.store.dispatch(AASTableActions.expandRow({ row })); } public collapse(row: AASTableRow): void { - if (this.viewMode === ViewMode.Tree) { - this.store.dispatch(AASTableActions.collapseRow({ row })); - } + this.store.dispatch(AASTableActions.collapseRow({ row })); } public open(row: AASTableRow): void { @@ -184,10 +108,6 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { name: row.document.endpoint, }; - if (this._filter) { - query.search = this._filter.replace(/&&/, '||'); - } - this.clipboard.set('AASQuery', query); this.router.navigateByUrl('/aas?format=AASQuery', { skipLocationChange: true }); } @@ -204,6 +124,21 @@ export class AASTableComponent implements OnInit, OnChanges, OnDestroy { this.store.dispatch(AASTableActions.toggleSelections()); } + private initialize(): void { + if (this.viewMode != null && this.documents != null) { + const viewMode = this.viewMode; + this.subscription.add(this.documents.pipe( + mergeMap(documents => viewMode.pipe( + map(viewMode => ({ viewMode, documents }))))).subscribe(tuple => { + if (tuple.viewMode === ViewMode.List) { + this.store.dispatch(AASTableActions.updateListView({ documents: tuple.documents })); + } else { + this.store.dispatch(AASTableActions.updateTreeView({ documents: tuple.documents })); + } + })); + } + } + private keyup = () => { this.shiftKey = false; this.altKey = false; diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.effects.ts b/projects/aas-lib/src/lib/aas-table/aas-table.effects.ts index 47ebc47b..a0aceaa9 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.effects.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.effects.ts @@ -8,15 +8,14 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { Action, Store } from '@ngrx/store'; -import { AASDocument, AASDocumentId, AASDocumentNode, AASPage } from 'common'; -import { EMPTY, exhaustMap, map, mergeMap, first, concat, of, from, Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { exhaustMap, map, first } from 'rxjs'; +import { findLastIndex } from 'lodash-es'; +import { AASDocument } from 'common'; import * as AASTableActions from './aas-table.actions'; import * as AASTableSelectors from './aas-table.selectors'; -import { AASTableApiService } from './aas-table-api.service'; -import { AASTableFeatureState } from './aas-table.state'; -import { TranslateService } from '@ngx-translate/core'; +import { AASTableFeatureState, AASTableRow } from './aas-table.state'; @Injectable() export class AASTableEffects { @@ -25,99 +24,136 @@ export class AASTableEffects { constructor( private readonly actions: Actions, store: Store, - private readonly translate: TranslateService, - private readonly api: AASTableApiService, ) { this.store = store as Store; } - public getFirstPage = createEffect(() => { + public updateListView = createEffect(() => { return this.actions.pipe( - ofType(AASTableActions.AASTableActionType.GET_FIRST_PAGE), - exhaustMap(action => this.api.getDocuments( - { previous: null, limit: action.limit }, - action.filter, - this.translate.currentLang).pipe( - mergeMap(page => this.setPageAndLoadContents(page))))); - }); - - public getLastPage = createEffect(() => { - return this.actions.pipe( - ofType(AASTableActions.AASTableActionType.GET_LAST_PAGE), - exhaustMap(action => this.api.getDocuments( - { next: null, limit: action.limit }, - action.filter, - this.translate.currentLang).pipe( - mergeMap(page => this.setPageAndLoadContents(page))))); - }); - - public getNextPage = createEffect(() => { - return this.actions.pipe( - ofType(AASTableActions.AASTableActionType.GET_NEXT_PAGE), - exhaustMap(action => this.store.select(AASTableSelectors.selectState).pipe( + ofType(AASTableActions.AASTableActionType.UPDATE_LIST_VIEW), + exhaustMap(action => this.store.select(AASTableSelectors.selectRows).pipe( first(), - mergeMap(state => { - if (state.rows.length === 0) return EMPTY; - - return this.api.getDocuments( - { - next: this.getId(state.rows[state.rows.length - 1].document), - limit: action.limit - }, - action.filter, - this.translate.currentLang); - }), - mergeMap(page => this.setPageAndLoadContents(page))))); + map(state => { + const rows = this.bla(state, action.documents); + return AASTableActions.setRows({ rows }); + }) + ))); }); - public getPreviousPage = createEffect(() => { + public updateTreeView = createEffect(() => { return this.actions.pipe( - ofType(AASTableActions.AASTableActionType.GET_PREVIOUS_PAGE), - exhaustMap(action => this.store.select(AASTableSelectors.selectState).pipe( + ofType(AASTableActions.AASTableActionType.UPDATE_TREE_VIEW), + exhaustMap(action => this.store.select(AASTableSelectors.selectRows).pipe( first(), - mergeMap(state => { - if (state.rows.length === 0) return EMPTY; - - return this.api.getDocuments( - { - previous: this.getId(state.rows[0].document), - limit: action.limit - }, - action.filter, - this.translate.currentLang); - }), - mergeMap(page => this.setPageAndLoadContents(page))))); + map(state => { + const rows = this.wupp(state, action.documents); + return AASTableActions.setRows({ rows }); + }) + ))); }); - public initTreeView = createEffect(() => { - return this.actions.pipe( - ofType(AASTableActions.AASTableActionType.INIT_TREE_VIEW), - exhaustMap(() => this.store.select(AASTableSelectors.selectSelectedDocuments).pipe( - first(), - mergeMap(documents => from(documents).pipe( - mergeMap(document => this.api.getHierarchy(document.endpoint, document.id)), - mergeMap(nodes => this.addRootAndLoadContents(nodes))))))); - }); + private bla(state: AASTableRow[], documents: AASDocument[]): AASTableRow[] { + const map = new Map(state.map(row => [`${row.endpoint}:${row.id}`, row])); + const rows = documents.map(document => { + return map.get(`${document.endpoint}:${document.id}`) ?? new AASTableRow(document, false, false, false, -1, -1, -1); + }); + + return rows; + } + + private wupp(state: AASTableRow[], documents: AASDocument[]): AASTableRow[] { + const map = new Map(state.map(item => [`${item.endpoint}:${item.id}`, item])); + const rows: AASTableRow[] = []; + const nodes: AASDocument[] = []; + documents.forEach(document => { + const row = map.get(`${document.endpoint}:${document.id}`); + if (row) { + rows.push(row); + } else { + nodes.push(document); + } + }); + + if (nodes.length > 0) { + this.addRoot(nodes, rows); + } - private getId(document: AASDocument): AASDocumentId { - return { id: document.id, endpoint: document.endpoint }; + return rows; } - private setPageAndLoadContents(page: AASPage): Observable { - return concat( - of(AASTableActions.setPage({ page })), - from(page.documents).pipe( - mergeMap(document => this.api.getContent(document.endpoint, document.id).pipe( - map(content => AASTableActions.setContent({ document, content })) - )))); + private addRoot(nodes: AASDocument[], rows: AASTableRow[]): AASTableRow[] { + const root = nodes.find(node => node.parent === null); + const index = findLastIndex(rows, row => row.level === 0); + if (root) { + const children = nodes.filter(node => this.isChild(root, node)); + const rootRow = new AASTableRow( + root, + false, + false, + children.length === 0, + 0, + rows.length + 1, + -1 + ); + + rows.push(rootRow) + + if (index >= 0) { + const previous = this.clone(rows[index]); + previous.nextSibling = rows.length - 1; + rows[index] = previous; + } + + this.traverse(root, nodes, rows, 1); + } + + return rows; + } + + private traverse(parent: AASDocument, nodes: AASDocument[], rows: AASTableRow[], level: number): void { + let previous: AASTableRow | null = null; + const children = nodes.filter(node => this.isChild(parent, node)); + for (const child of children) { + const row = new AASTableRow( + child, + false, + false, + children.length === 0, + level, + -1, + -1 + ); + + rows.push(row); + if (previous) { + previous.nextSibling = rows.length - 1; + } + + if (children.length > 0) { + row.firstChild = rows.length; + this.traverse(child, nodes, rows, level + 1); + } + + previous = row; + } + } + + private isChild(parent: AASDocument, node: AASDocument): boolean { + if (!node.parent) { + return false; + } + + return node.parent.endpoint === parent.endpoint && node.parent.id === parent.id; } - private addRootAndLoadContents(nodes: AASDocumentNode[]): Observable { - return concat( - of(AASTableActions.addRoot({ nodes })), - from(nodes).pipe( - mergeMap(document => this.api.getContent(document.endpoint, document.id).pipe( - map(content => AASTableActions.setContent({ document, content })) - )))); + private clone(row: AASTableRow): AASTableRow { + return new AASTableRow( + row.document, + row.selected, + row.expanded, + row.isLeaf, + row.level, + row.firstChild, + row.nextSibling) } } diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.reducer.ts b/projects/aas-lib/src/lib/aas-table/aas-table.reducer.ts index e257a6c8..f889cc94 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.reducer.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.reducer.ts @@ -7,17 +7,11 @@ *****************************************************************************/ import { createReducer, on } from '@ngrx/store'; -import { findLastIndex } from 'lodash-es' -import { AASDocument, AASDocumentNode, AASPage, aas } from 'common'; +import { AASDocument } from 'common'; import * as AASTableActions from './aas-table.actions'; import { AASTableRow, AASTableState } from './aas-table.state'; -import { ViewMode } from '../types/view-mode'; const initialState: AASTableState = { - isFirstPage: false, - isLastPage: false, - totalCount: 0, - viewMode: ViewMode.Undefined, rows: [], }; @@ -32,12 +26,8 @@ export const aasTableReducer = createReducer( (state, { row }) => expandRow(state, row) ), on( - AASTableActions.setPage, - (state, { page }) => setPage(state, page) - ), - on( - AASTableActions.setContent, - (state, { document, content }) => setContent(state, document, content) + AASTableActions.setRows, + (state, { rows }) => setRows(state, rows) ), on( AASTableActions.toggleSelected, @@ -47,93 +37,14 @@ export const aasTableReducer = createReducer( AASTableActions.toggleSelections, (state) => toggleSelections(state) ), - on( - AASTableActions.addRoot, - (state, { nodes }) => addRoot(state, nodes) - ), on( AASTableActions.setSelections, (state, { documents }) => setSelections(state, documents) ), ); -function setPage(state: AASTableState, page: AASPage): AASTableState { - const rows: AASTableRow[] = []; - for (const document of page.documents) { - if (document.content !== undefined) { - const row = new AASTableRow( - document, - false, - false, - false, - 0, - -1, - -1); - - rows.push(row); - } - } - - return { - ...state, - viewMode: ViewMode.List, - rows, - isFirstPage: page.previous === null, - isLastPage: page.next === null, - totalCount: page.totalCount, - }; -} - -function setContent(state: AASTableState, document: AASDocument, content: aas.Environment): AASTableState { - const rows = [...state.rows]; - const index = rows.findIndex(row => row.endpoint === document.endpoint && row.id === document.id); - if (index >= 0) { - const row = rows[index]; - rows[index] = clone(row, { ...document, content }) - } - +function setRows(state: AASTableState, rows: AASTableRow[]): AASTableState { return { ...state, rows }; - - function clone(row: AASTableRow, document: AASDocument): AASTableRow { - return new AASTableRow( - document, - row.selected, - row.expanded, - row.isLeaf, - row.level, - row.firstChild, - row.nextSibling) - } -} - -function addRoot(state: AASTableState, nodes: AASDocumentNode[]): AASTableState { - const rows: AASTableRow[] = state.viewMode === ViewMode.Tree ? [...state.rows] : []; - const root = nodes.find(node => node.parent === null); - const index = findLastIndex(rows, row => row.level === 0); - if (root) { - const children = nodes.filter(node => isChild(root, node)); - const rootRow = new AASTableRow( - root, - false, - false, - children.length === 0, - 0, - state.rows.length + 1, - -1 - ); - - rows.push(rootRow) - - if (index >= 0) { - const previous = clone(rows[index]); - previous.nextSibling = rows.length - 1 - rows[index] = previous; - } - - traverse(root, nodes, rows, 1); - } - - return { ...state, rows, viewMode: ViewMode.Tree }; } function setSelections(state: AASTableState, documents: AASDocument[]): AASTableState { @@ -151,72 +62,6 @@ function setSelections(state: AASTableState, documents: AASDocument[]): AASTable return { ...state, rows }; } -function isChild(parent: AASDocumentNode, node: AASDocumentNode): boolean { - if (!node.parent) { - return false; - } - - return node.parent.endpoint === parent.endpoint && node.parent.id === parent.id; -} - -function traverse(parent: AASDocumentNode, nodes: AASDocumentNode[], rows: AASTableRow[], level: number): void { - let previous: AASTableRow | null = null; - const children = nodes.filter(node => isChild(parent, node)); - for (const child of children) { - const row = new AASTableRow( - child, - false, - false, - children.length === 0, - level, - -1, - -1 - ); - - rows.push(row); - if (previous) { - previous.nextSibling = rows.length - 1; - } - - if (children.length > 0) { - row.firstChild = rows.length; - traverse(child, nodes, rows, level + 1); - } - - previous = row; - } -} - -function expandRow(state: AASTableState, row: AASTableRow): AASTableState { - const rows = [...state.rows]; - const index = rows.indexOf(row); - rows[index] = new AASTableRow( - row.document, - false, - true, - row.isLeaf, - row.level, - row.firstChild, - row.nextSibling); - - return { ...state, rows }; -} - -function collapseRow(state: AASTableState, row: AASTableRow): AASTableState { - const rows = [...state.rows]; - const index = rows.indexOf(row); - rows[index] = new AASTableRow( - row.document, - false, - false, - row.isLeaf, - row.level, - row.firstChild, - row.nextSibling); - - return { ...state, rows }; -} - function toggleSelected( state: AASTableState, row: AASTableRow, @@ -291,3 +136,33 @@ function clone(row: AASTableRow, selected?: boolean): AASTableRow { row.firstChild, row.nextSibling) } + +function expandRow(state: AASTableState, row: AASTableRow): AASTableState { + const rows = [...state.rows]; + const index = rows.indexOf(row); + rows[index] = new AASTableRow( + row.document, + false, + true, + row.isLeaf, + row.level, + row.firstChild, + row.nextSibling); + + return { ...state, rows }; +} + +function collapseRow(state: AASTableState, row: AASTableRow): AASTableState { + const rows = [...state.rows]; + const index = rows.indexOf(row); + rows[index] = new AASTableRow( + row.document, + false, + false, + row.isLeaf, + row.level, + row.firstChild, + row.nextSibling); + + return { ...state, rows }; +} diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.selectors.ts b/projects/aas-lib/src/lib/aas-table/aas-table.selectors.ts index 6734e6c1..518d6996 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.selectors.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.selectors.ts @@ -38,18 +38,3 @@ export const selectRows = createSelector(getState, state => { return state.rows; }); -export const selectIsFirstPage = createSelector( - getState, - state => state.isFirstPage); - -export const selectIsLastPage = createSelector( - getState, - state => state.isLastPage); - -export const selectViewMode = createSelector( - getState, - state => state.viewMode); - -export const selectTotalCount = createSelector( - getState, - state => state.totalCount); diff --git a/projects/aas-lib/src/lib/aas-table/aas-table.state.ts b/projects/aas-lib/src/lib/aas-table/aas-table.state.ts index 72a20fa6..278a88e9 100644 --- a/projects/aas-lib/src/lib/aas-table/aas-table.state.ts +++ b/projects/aas-lib/src/lib/aas-table/aas-table.state.ts @@ -7,7 +7,6 @@ *****************************************************************************/ import { AASDocument } from 'common'; -import { ViewMode } from '../types/view-mode'; export class AASTableRow { constructor( @@ -75,10 +74,6 @@ export class AASTableRow { } export interface AASTableState { - isFirstPage: boolean; - isLastPage: boolean; - totalCount: number; - viewMode: ViewMode; rows: AASTableRow[]; } diff --git a/projects/aas-portal/src/app/app.module.ts b/projects/aas-portal/src/app/app.module.ts index 2e0a9847..b561cace 100644 --- a/projects/aas-portal/src/app/app.module.ts +++ b/projects/aas-portal/src/app/app.module.ts @@ -38,6 +38,7 @@ import { httpInterceptorProviders } from './index'; import { EffectsModule } from '@ngrx/effects'; import { AASEffects } from './aas/aas.effects'; import { ViewEffects } from './view/view.effects'; +import { StartEffects } from './start/start.effects'; @NgModule({ declarations: [ @@ -68,7 +69,7 @@ import { ViewEffects } from './view/view.effects'; view: viewReducer, dashboard: dashboardReducer, }), - EffectsModule.forRoot([AASEffects, ViewEffects]), + EffectsModule.forRoot([StartEffects, AASEffects, ViewEffects]), TranslateModule.forRoot({ defaultLanguage: 'en-us', loader: { diff --git a/projects/aas-portal/src/app/start/start-api.service.ts b/projects/aas-portal/src/app/start/start-api.service.ts index cdbad884..b53e8d69 100644 --- a/projects/aas-portal/src/app/start/start-api.service.ts +++ b/projects/aas-portal/src/app/start/start-api.service.ts @@ -8,8 +8,8 @@ import { Injectable } from '@angular/core'; import { HttpClient, } from '@angular/common/http'; -import { AASEndpoint } from 'common'; import { Observable } from 'rxjs'; +import { AASCursor, AASDocument, AASEndpoint, AASPage, aas } from 'common'; import { encodeBase64Url } from 'projects/aas-lib/src/public-api'; /** The client side AAS provider service. */ @@ -60,4 +60,45 @@ export class StartApiService { return this.http.delete( `/api/v1/containers/${encodeBase64Url(url)}/packages/${encodeBase64Url(id)}`); } + + /** + * Returns a page of documents from the specified cursor. + * @param cursor The current cursor. + * @param filter A filter expression. + * @param language The language to used for the filter. + * @returns The document page. + */ + public getPage(cursor: AASCursor, filter?: string, language?: string): Observable { + let url = `/api/v1/documents?cursor=${encodeBase64Url(JSON.stringify(cursor))}`; + if (filter) { + url += `&filter=${encodeBase64Url(filter)}`; + if (language) { + url += `&language=${language}`; + } + } + + return this.http.get(url); + } + + /** + * Loads the element structure of the specified document. + * @param endpointName The URL of the container. + * @param id The identification of the AAS document. + * @returns The root of the element structure. + */ + public getContent(endpointName: string, id: string): Observable { + return this.http.get( + `/api/v1/containers/${encodeBase64Url(endpointName)}/documents/${encodeBase64Url(id)}/content`); + } + + /** + * + * @param endpointName + * @param id + * @returns + */ + public getHierarchy(endpointName: string, id: string): Observable { + return this.http.get( + `/api/v1/containers/${encodeBase64Url(endpointName)}/documents/${encodeBase64Url(id)}/hierarchy`); + } } \ No newline at end of file diff --git a/projects/aas-portal/src/app/start/start.actions.ts b/projects/aas-portal/src/app/start/start.actions.ts index 0185ed0a..af630cbf 100644 --- a/projects/aas-portal/src/app/start/start.actions.ts +++ b/projects/aas-portal/src/app/start/start.actions.ts @@ -7,23 +7,60 @@ *****************************************************************************/ import { createAction, props } from '@ngrx/store'; -import { ViewMode } from 'projects/aas-lib/src/public-api'; +import { AASDocument, AASPage, aas } from 'common'; +import { TypedAction } from '@ngrx/store/src/models'; export enum StartActionType { - SET_VIEW_MODE = '[Start] set View Mode', + SET_DOCUMENTS = '[Start] set documents', + APPEND_DOCUMENTS = '[Start] append documents', APPLY_FILTER = '[Start] apply filter', - SET_FILTER = '[Start] set filter', - SET_LIMIT = '[Start] set limit', + GET_FIRST_PAGE = '[Start] get first page', + GET_NEXT_PAGE = '[Start] get next page', + GET_PREVIOUS_PAGE = '[Start] previous next page', + GET_LAST_PAGE = '[Start] get last page', + SET_PAGE = '[Start] set page', + SET_CONTENT = '[Start] set content', + GET_HIERARCHY = '[Start] get hierarchy' } -export const setViewMode = createAction( - StartActionType.SET_VIEW_MODE, - props<{ viewMode: ViewMode }>()); +export interface GetFirstPageAction extends TypedAction { + limit: number | undefined; + filter: string | undefined; +} + +export interface GetHierarchyAction extends TypedAction { + roots: AASDocument[]; +} + +export const getHierarchy = createAction( + StartActionType.GET_HIERARCHY, + props<{ roots: AASDocument[] }>()); + +export const setDocuments = createAction( + StartActionType.SET_DOCUMENTS, + props<{ documents: AASDocument[] }>()); + +export const appendDocuments = createAction( + StartActionType.APPEND_DOCUMENTS, + props<{ documents: AASDocument[] }>()); + +export const getFirstPage = createAction( + StartActionType.GET_FIRST_PAGE, + props<{ limit?: number, filter?: string }>()); + +export const getNextPage = createAction( + StartActionType.GET_NEXT_PAGE); + +export const getPreviousPage = createAction( + StartActionType.GET_PREVIOUS_PAGE); + +export const getLastPage = createAction( + StartActionType.GET_LAST_PAGE); -export const setFilter = createAction( - StartActionType.SET_FILTER, - props<{ filter: string }>()); +export const setPage = createAction( + StartActionType.SET_PAGE, + props<{ page: AASPage, limit: number | undefined, filter: string | undefined }>()); -export const setLimit = createAction( - StartActionType.SET_LIMIT, - props<{ limit: number }>()); \ No newline at end of file +export const setContent = createAction( + StartActionType.SET_CONTENT, + props<{ document: AASDocument, content: aas.Environment }>()); diff --git a/projects/aas-portal/src/app/start/start.component.html b/projects/aas-portal/src/app/start/start.component.html index def6e946..b94d3b4c 100644 --- a/projects/aas-portal/src/app/start/start.component.html +++ b/projects/aas-portal/src/app/start/start.component.html @@ -7,26 +7,21 @@ !---------------------------------------------------------------------------->
- +
-
- - - -
@@ -55,12 +50,12 @@
+ (change)="setViewMode('list')" [checked]="(viewMode | async) === 'list'"> + (change)="setViewMode('tree')" [checked]="(viewMode | async) === 'tree'" [disabled]="selected.length === 0"> @@ -77,10 +72,10 @@
-
+
+ (keydown.enter)="setFilter(textInput.value)" [disabled]="(viewMode | async) === 'tree'">
\ No newline at end of file diff --git a/projects/aas-portal/src/app/start/start.component.ts b/projects/aas-portal/src/app/start/start.component.ts index 7da089b6..6f287b6b 100644 --- a/projects/aas-portal/src/app/start/start.component.ts +++ b/projects/aas-portal/src/app/start/start.component.ts @@ -13,14 +13,14 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { aas, AASDocument, AASEndpoint, stringFormat } from 'common'; import * as lib from 'projects/aas-lib/src/public-api'; -import { BehaviorSubject, EMPTY, from, map, mergeMap, Observable, of, Subscription } from 'rxjs'; +import { BehaviorSubject, EMPTY, first, from, map, mergeMap, Observable, of, Subscription } from 'rxjs'; import { AddEndpointFormComponent } from './add-endpoint-form/add-endpoint-form.component'; import { EndpointSelect, RemoveEndpointFormComponent } from './remove-endpoint-form/remove-endpoint-form.component'; import * as StartActions from './start.actions'; +import * as StartSelectors from './start.selectors'; import { StartFeatureState } from './start.state'; import { UploadFormComponent } from './upload-form/upload-form.component'; -import { selectFilter, selectViewMode, selectLimit, selectDocuments } from './start.selectors'; import { ToolbarService } from '../toolbar.service'; import { StartApiService } from './start-api.service'; @@ -49,27 +49,33 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { private readonly clipboard: lib.ClipboardService ) { this.store = store as Store; - this.filter = this.store.select(selectFilter); - this.limit = this.store.select(selectLimit); - this.documents = this.store.select(selectDocuments); + this.filter = this.store.select(StartSelectors.selectFilter); + this.limit = this.store.select(StartSelectors.selectLimit); + this.isFirstPage = this.store.select(StartSelectors.selectIsFirstPage); + this.isLastPage = this.store.select(StartSelectors.selectIsLastPage); + this.documents = this.store.select(StartSelectors.selectDocuments); + this.viewMode = this.store.select(StartSelectors.selectViewMode); + this.endpoints = this.api.getEndpoints(); } @ViewChild('startToolbar', { read: TemplateRef }) public startToolbar: TemplateRef | null = null; - public viewMode: lib.ViewMode = lib.ViewMode.List; + public readonly viewMode: Observable; public readonly filter: Observable; public readonly limit: Observable; - public allAvailable = true; + public readonly isFirstPage: Observable; - public readonly endpoints = this.api.getEndpoints(); + public readonly isLastPage: Observable; + + public readonly endpoints: Observable; public documents: Observable; - public get selected(): AASDocument[]{ + public get selected(): AASDocument[] { return this._selected; } @@ -79,13 +85,15 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { } public ngOnInit(): void { - this.subscription.add( - this.store - .select(selectViewMode).pipe() - .subscribe((value) => { - this.viewMode = value; - }) - ); + this.store.select(StartSelectors.selectViewMode).pipe( + first(viewMode => viewMode === lib.ViewMode.Undefined), + mergeMap(() => this.auth.ready), + first(ready => ready), + first(), + ).subscribe({ + next: () => this.store.dispatch(StartActions.getFirstPage({})), + error: error => this.notify.error(error), + }); } public ngAfterViewInit(): void { @@ -100,7 +108,11 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { } public setViewMode(viewMode: string | lib.ViewMode): void { - this.store.dispatch(StartActions.setViewMode({ viewMode: viewMode as lib.ViewMode })); + if (viewMode === lib.ViewMode.List) { + this.store.dispatch(StartActions.getFirstPage({})); + } else { + this.store.dispatch(StartActions.getHierarchy({ roots: this._selected })); + } } public addEndpoint(): void { @@ -254,11 +266,27 @@ export class StartComponent implements OnInit, OnDestroy, AfterViewInit { } public setFilter(filter: string): void { - this.store.dispatch(StartActions.setFilter({ filter })); + this.store.dispatch(StartActions.getFirstPage({ filter })); } public setLimit(limit: number): void { - this.store.dispatch(StartActions.setLimit({ limit })); + this.store.dispatch(StartActions.getFirstPage({ limit })); + } + + public firstPage(): void { + this.store.dispatch(StartActions.getFirstPage({})); + } + + public previousPage(): void { + this.store.dispatch(StartActions.getPreviousPage()) + } + + public nextPage(): void { + this.store.dispatch(StartActions.getNextPage()) + } + + public lastPage(): void { + this.store.dispatch(StartActions.getLastPage()) } private selectSubmodels(document: AASDocument, semanticId: string): aas.Submodel[] { diff --git a/projects/aas-portal/src/app/start/start.effects.ts b/projects/aas-portal/src/app/start/start.effects.ts new file mode 100644 index 00000000..699e4543 --- /dev/null +++ b/projects/aas-portal/src/app/start/start.effects.ts @@ -0,0 +1,134 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2023 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { Injectable } from '@angular/core'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { Action, Store } from '@ngrx/store'; +import { AASDocument, AASDocumentId, AASPage } from 'common'; +import { EMPTY, exhaustMap, map, mergeMap, first, concat, of, from, Observable } from 'rxjs'; + +import * as StartActions from './start.actions'; +import * as StartSelectors from './start.selectors'; +import { TranslateService } from '@ngx-translate/core'; +import { StartFeatureState } from './start.state'; +import { StartApiService } from './start-api.service'; + +@Injectable() +export class StartEffects { + private readonly store: Store; + + constructor( + private readonly actions: Actions, + store: Store, + private readonly translate: TranslateService, + private readonly api: StartApiService, + ) { + this.store = store as Store; + } + + public getFirstPage = createEffect(() => { + return this.actions.pipe( + ofType(StartActions.StartActionType.GET_FIRST_PAGE), + exhaustMap(action => this.store.select(StartSelectors.selectState).pipe( + first(), + mergeMap(state => { + return this.api.getPage({ + previous: null, + limit: action.limit ?? state.limit + }, + action.filter ?? state.filter, + this.translate.currentLang); + }), + mergeMap(page => this.setPageAndLoadContents(page, action.limit, action.filter))))); + }); + + public getLastPage = createEffect(() => { + return this.actions.pipe( + ofType(StartActions.StartActionType.GET_LAST_PAGE), + exhaustMap(() => this.store.select(StartSelectors.selectState).pipe( + first(), + mergeMap(state => { + return this.api.getPage({ + next: null, + limit: state.limit + }, + state.filter, + this.translate.currentLang); + }), + mergeMap(page => this.setPageAndLoadContents(page))))); + }); + + public getNextPage = createEffect(() => { + return this.actions.pipe( + ofType(StartActions.StartActionType.GET_NEXT_PAGE), + exhaustMap(() => this.store.select(StartSelectors.selectState).pipe( + first(), + mergeMap(state => { + if (state.documents.length === 0) return EMPTY; + + return this.api.getPage( + { + next: this.getId(state.documents[state.documents.length - 1]), + limit: state.limit + }, + state.filter, + this.translate.currentLang); + }), + mergeMap(page => this.setPageAndLoadContents(page))))); + }); + + public getPreviousPage = createEffect(() => { + return this.actions.pipe( + ofType(StartActions.StartActionType.GET_PREVIOUS_PAGE), + exhaustMap(() => this.store.select(StartSelectors.selectState).pipe( + first(), + mergeMap(state => { + if (state.documents.length === 0) return EMPTY; + + return this.api.getPage( + { + previous: this.getId(state.documents[0]), + limit: state.limit + }, + state.filter, + this.translate.currentLang); + }), + mergeMap(page => this.setPageAndLoadContents(page))))); + }); + + public getHierarchy = createEffect(() => { + return this.actions.pipe( + ofType(StartActions.StartActionType.GET_HIERARCHY), + exhaustMap(action => concat(of(StartActions.setDocuments({ documents: [] })), of(action.roots).pipe( + mergeMap(documents => from(documents).pipe( + mergeMap(document => this.api.getHierarchy(document.endpoint, document.id)), + mergeMap(nodes => this.addHierarchyAndLoadContents(nodes)))))))); + }); + + private getId(document: AASDocument): AASDocumentId { + return { id: document.id, endpoint: document.endpoint }; + } + + private setPageAndLoadContents(page: AASPage, limit?: number, filter?: string): Observable { + return concat( + of(StartActions.setPage({ page, limit, filter })), + from(page.documents).pipe( + mergeMap(document => this.api.getContent(document.endpoint, document.id).pipe( + map(content => StartActions.setContent({ document, content })) + )))); + } + + private addHierarchyAndLoadContents(documents: AASDocument[]): Observable { + return concat( + of(StartActions.appendDocuments({ documents })), + from(documents).pipe( + mergeMap(document => this.api.getContent(document.endpoint, document.id).pipe( + map(content => StartActions.setContent({ document, content })) + )))); + } +} diff --git a/projects/aas-portal/src/app/start/start.reducer.ts b/projects/aas-portal/src/app/start/start.reducer.ts index 6f41d3d1..65c50d83 100644 --- a/projects/aas-portal/src/app/start/start.reducer.ts +++ b/projects/aas-portal/src/app/start/start.reducer.ts @@ -8,40 +8,67 @@ import { createReducer, on } from '@ngrx/store'; import { ViewMode } from 'projects/aas-lib/src/public-api'; +import { AASDocument, AASPage, aas } from 'common'; import * as StartActions from './start.actions'; import { StartState } from './start.state'; const initialState: StartState = { - viewMode: ViewMode.List, + viewMode: ViewMode.Undefined, limit: 10, filter: '', documents: [], + isFirstPage: false, + isLastPage: false, + totalCount: 0, }; export const startReducer = createReducer( initialState, on( - StartActions.setFilter, - (state, { filter }) => setFilter(state, filter) + StartActions.setDocuments, + (state, { documents }) => setDocuments(state, documents) ), on( - StartActions.setLimit, - (state, { limit }) => setLimit(state, limit) + StartActions.appendDocuments, + (state, { documents }) => appendDocuments(state, documents) ), on( - StartActions.setViewMode, - (state, { viewMode }) => setViewMode(state, viewMode) - ) + StartActions.setPage, + (state, { page, limit, filter }) => setPage(state, page, limit, filter) + ), + on( + StartActions.setContent, + (state, { document, content }) => setContent(state, document, content) + ), ) -function setViewMode(state: StartState, viewMode: ViewMode): StartState { - return { ...state, viewMode }; +function setDocuments(state: StartState, documents: AASDocument[]): StartState { + return { ...state, documents, viewMode: ViewMode.Tree }; } -function setFilter(state: StartState, filter: string): StartState { - return { ...state, filter }; +function appendDocuments(state: StartState, documents: AASDocument[]): StartState { + return { ...state, documents: [...state.documents, ...documents] }; } -function setLimit(state: StartState, limit: number): StartState { - return { ...state, limit }; -} \ No newline at end of file +function setPage(state: StartState, page: AASPage, limit: number | undefined, filter: string | undefined): StartState { + return { + ...state, + viewMode: ViewMode.List, + limit: limit ?? state.limit, + filter: filter ?? state.filter, + documents: page.documents, + isFirstPage: page.previous === null, + isLastPage: page.next === null, + totalCount: page.totalCount, + }; +} + +function setContent(state: StartState, document: AASDocument, content: aas.Environment): StartState { + const documents = [...state.documents]; + const index = documents.findIndex(item => item.endpoint === document.endpoint && item.id === document.id); + if (index >= 0) { + documents[index] = { ...document, content } + } + + return { ...state, documents }; +} diff --git a/projects/aas-portal/src/app/start/start.selectors.ts b/projects/aas-portal/src/app/start/start.selectors.ts index 21690cf6..eb7638cf 100644 --- a/projects/aas-portal/src/app/start/start.selectors.ts +++ b/projects/aas-portal/src/app/start/start.selectors.ts @@ -10,11 +10,14 @@ import { createSelector } from '@ngrx/store'; import { StartFeatureState } from './start.state' import { ViewMode } from 'projects/aas-lib/src/public-api'; +const getState = (state: StartFeatureState) => state.start; const getFilter = (state: StartFeatureState) => state.start.filter; const getViewMode = (state: StartFeatureState) => state.start.viewMode; const getLimit = (state: StartFeatureState) => state.start.limit; const getDocuments = (state: StartFeatureState) => state.start.documents; +export const selectState = createSelector(getState, state => state); + export const selectFilter = createSelector(getFilter, filter => filter); export const selectViewMode = createSelector(getViewMode, viewMode => viewMode); @@ -26,3 +29,11 @@ export const selectIsViewModeTree = createSelector(getViewMode, viewMode => view export const selectLimit = createSelector(getLimit, limit => limit); export const selectDocuments = createSelector(getDocuments, documents => documents); + +export const selectIsFirstPage = createSelector( + getState, + state => state.isFirstPage); + +export const selectIsLastPage = createSelector( + getState, + state => state.isLastPage); diff --git a/projects/aas-portal/src/app/start/start.state.ts b/projects/aas-portal/src/app/start/start.state.ts index 40fb015c..b574e95c 100644 --- a/projects/aas-portal/src/app/start/start.state.ts +++ b/projects/aas-portal/src/app/start/start.state.ts @@ -13,6 +13,9 @@ export interface StartState { viewMode: ViewMode; filter: string; limit: number; + isFirstPage: boolean; + isLastPage: boolean; + totalCount: number; documents: AASDocument[]; } diff --git a/projects/aas-server/src/app/aas-provider/aas-provider.ts b/projects/aas-server/src/app/aas-provider/aas-provider.ts index 6b8ae246..48baae9f 100644 --- a/projects/aas-server/src/app/aas-provider/aas-provider.ts +++ b/projects/aas-server/src/app/aas-provider/aas-provider.ts @@ -38,7 +38,6 @@ import { AASResourceScanFactory } from './aas-resource-scan-factory.js'; import { Variable } from '../variable.js'; import { WSServer } from '../ws-server.js'; import { ERRORS } from '../errors.js'; -import { AASDocumentNode } from 'common/src/lib/types.js'; @singleton() export class AASProvider { @@ -363,10 +362,10 @@ export class AASProvider { * @param id The AAS identifier. * @returns */ - public async getHierarchyAsync(endpoint: string, id: string): Promise { + public async getHierarchyAsync(endpoint: string, id: string): Promise { const document: AASDocument = await this.index.get(endpoint, id); - const root: AASDocumentNode = { ...document, parent: null, content: null }; - const nodes: AASDocumentNode[] = [root]; + const root: AASDocument = { ...document, parent: null, content: null }; + const nodes: AASDocument[] = [root]; await this.collectDescendants(root, nodes); return nodes; } @@ -513,7 +512,7 @@ export class AASProvider { }; } - private async collectDescendants(parent: AASDocumentNode, nodes: AASDocumentNode[]): Promise { + private async collectDescendants(parent: AASDocument, nodes: AASDocument[]): Promise { const content = await this.getDocumentContentAsync(parent); for (const reference of this.whereReferenceElement(content.submodels)) { const childId = reference.value.keys[0].value; @@ -525,7 +524,7 @@ export class AASProvider { } if (child) { - const node: AASDocumentNode = { ...child, parent: { ...parent }, content: null }; + const node: AASDocument = { ...child, parent: { ...parent }, content: null }; nodes.push(node); await this.collectDescendants(node, nodes); } diff --git a/projects/aas-server/src/app/controller/containers-controller.ts b/projects/aas-server/src/app/controller/containers-controller.ts index 73a6dabc..be223b35 100644 --- a/projects/aas-server/src/app/controller/containers-controller.ts +++ b/projects/aas-server/src/app/controller/containers-controller.ts @@ -9,7 +9,7 @@ import { inject, injectable } from 'tsyringe'; import fs from 'fs'; import { Body, Delete, Get, OperationId, Path, Post, Put, Queries, Route, Security, Tags, UploadedFile, UploadedFiles } from 'tsoa'; -import { AASDocument, AASDocumentNode, aas } from 'common'; +import { AASDocument, aas } from 'common'; import { AASProvider } from '../aas-provider/aas-provider.js'; import { AuthService } from '../auth/auth-service.js'; @@ -227,7 +227,7 @@ export class ContainersController extends AASController { @Get('{endpoint}/documents/{id}/hierarchy') @Security('bearerAuth', ['guest']) @OperationId('getHierarchy') - public async getHierarchy(@Path() endpoint: string, @Path() id: string): Promise { + public async getHierarchy(@Path() endpoint: string, @Path() id: string): Promise { try { this.logger.start('getHierarchy'); return await this.aasProvider.getHierarchyAsync(decodeBase64Url(endpoint), decodeBase64Url(id)); diff --git a/projects/common/src/lib/types.ts b/projects/common/src/lib/types.ts index 20d2a52c..dde7b2ad 100644 --- a/projects/common/src/lib/types.ts +++ b/projects/common/src/lib/types.ts @@ -86,11 +86,8 @@ export interface AASDocument extends AASDocumentId { /** The root element of the AAS structure (content), `null` if the content is not loaded or * `undefined` if the content is not available. */ content?: aas.Environment | null; -} - -/** Node in an AAS hierarchy. */ -export interface AASDocumentNode extends AASDocument { - parent: AASDocumentNode | null; + /** The parent AAS in a hierarchy. */ + parent?: AASDocument | null; } /** Represents a page of AAS documents from the total set. */
@@ -53,7 +53,7 @@
@@ -82,6 +82,7 @@
+
{{row.endpoint}}
{{row.id | max:80}}