Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • dad/cines-vitamui
1 result
Show changes
Commits on Source (29)
Showing
with 406 additions and 287 deletions
......@@ -11,7 +11,7 @@
"start": "ng serve --aot --proxy-config proxy.conf.json --disable-host-check --ssl --ssl-key $npm_package_pki_path/$npm_package_pki_asset.key --ssl-cert $npm_package_pki_path/$npm_package_pki_asset.crt",
"build": "ng build --prod --build-optimizer=false --optimization=false",
"build:dev": "ng build --prod --sourceMap=false --build-optimizer=false --optimization=false",
"build:prod": "set NODE_OPTIONS=--max_old_space_size=4096; ng build --prod",
"build:prod": "export NODE_OPTIONS=--max_old_space_size=4096; ng build --prod",
"analyze": "ng build --prod --stats-json ; webpack-bundle-analyzer dist/ui-frontend-common/stats.json",
"test": "ng test --watch=false",
"test:conf-ci": "ng test --watch=false --karma-config=karma.conf.ci.js",
......
......@@ -87,5 +87,77 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>standalone</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-vitamui-common-configuration-model</id>
<phase>initialize</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>src/app/modules/models/</outputDirectory>
<resources>
<resource>
<directory>../ui-pastis/src/main/resources/standalone/</directory>
<includes>
<include>app.configuration.interface.ts</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-vitamui-common-startup.service.ts</id>
<phase>initialize</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>src/app/modules/</outputDirectory>
<resources>
<resource>
<directory>../ui-pastis/src/main/resources/standalone/</directory>
<includes>
<include>startup.service.ts</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-vitamui-common-theme.service.ts</id>
<phase>initialize</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>src/app/modules/</outputDirectory>
<resources>
<resource>
<directory>../ui-pastis/src/main/resources/standalone/</directory>
<includes>
<include>theme.service.ts</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
......@@ -15453,7 +15453,7 @@
},
"ui-frontend-common": {
"version": "file:../ui-frontend-common/ui-frontend-common-2.1.0.tgz",
"integrity": "sha512-0onrnkRt0/D4bb58U+vB6f0FBSfYwZTTNLcWn6mfc09K4iL34wfiBSznbeHhuC2U1qRA/m2p597ETqOyQNJz0g==",
"integrity": "sha512-RmA3J6YeB2ZLmOI+ts0WUb7zcGmP0HWhUo7JvjIw4PBO7+fLFN920rA/Vnc1sIDnl68AxuaerUZ1hyWS7dnjIw==",
"requires": {
"@angular/material-moment-adapter": "^10.2.3",
"@ngx-translate/core": "^12.0.0",
......
......@@ -131,5 +131,60 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>standalone</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>copy-pastis-standalone-angular.json</id>
<phase>initialize</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<overwrite>true</overwrite>
<outputDirectory>.</outputDirectory>
<resources>
<resource>
<directory>../ui-pastis/src/main/resources/standalone/</directory>
<includes>
<include>angular.json</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-resources-package.json</id>
<phase>initialize</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>.</outputDirectory>
<overwrite>true</overwrite>
<resources>
<resource>
<directory>../ui-pastis/src/main/resources/standalone/</directory>
<includes>
<include>package.json</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<vitamui-common-header></vitamui-common-header>
<vitamui-common-header *ngIf="!isStandalone"></vitamui-common-header>
<mat-toolbar color="primary" class="header" *ngIf="isStandalone"></mat-toolbar>
<vitamui-common-body>
<router-outlet></router-outlet>
</vitamui-common-body>
<vitamui-common-footer></vitamui-common-footer>
<vitamui-common-subrogation-banner></vitamui-common-subrogation-banner>
\ No newline at end of file
<vitamui-common-footer *ngIf="!isStandalone"></vitamui-common-footer>
<vitamui-common-subrogation-banner *ngIf="!isStandalone"></vitamui-common-subrogation-banner>
@import "~ui-frontend-common/sass/mixins/elevation";
mat-toolbar {
@include elevation-4;
z-index: 10;
}
.header {
background-color: var(--vitamui-primary);
height: 72px;
position: fixed;
top: 0;
display: flex;
}
/*
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
[dad@cines.fr]
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
profiles based on the french SEDA standard
(https://redirect.francearchives.fr/seda/).
This software is governed by the CeCILL-C license under French law and
abiding by the rules of distribution of free software. You can use,
abiding by the rules of distribution of free software. You can use,
modify and/ or redistribute the software under the terms of the CeCILL-C
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".
"http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors have only limited
liability.
liability.
In this respect, the user's attention is drawn to the risks associated
with loading, using, modifying and/or developing or reproducing the
......@@ -28,14 +28,15 @@ that may mean that it is complicated to manipulate, and that also
therefore means that it is reserved for developers and experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
The fact that you are presently reading this means that you have had
knowledge of the CeCILL-C license and that you accept its terms.
*/
import { Component } from '@angular/core';
import { environment } from '../environments/environment';
@Component({
selector: 'app-root',
......@@ -47,8 +48,9 @@ export class AppComponent {
title= 'Pastis App';
subrogating = true;
isStandalone: boolean = environment.standalone;
constructor() {
}
}
......@@ -37,7 +37,7 @@ knowledge of the CeCILL-C license and that you accept its terms.
*/
import {Injectable} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {BehaviorSubject, Observable} from 'rxjs';
import {BehaviorSubject, ReplaySubject} from 'rxjs';
import {SedaCardinalityConstants, SedaData, SedaElementConstants} from '../../profile/edit-profile/classes/seda-data';
import {FileNode, TypeConstants} from '../../profile/edit-profile/classes/file-node';
import {PastisDialogConfirmComponent} from '../../shared/pastis-dialog/pastis-dialog-confirm/pastis-dialog-confirm.component';
......@@ -45,8 +45,8 @@ import {ProfileService} from './profile.service';
import {PastisDialogData} from 'projects/pastis/src/app/shared/pastis-dialog/classes/pastis-dialog-data';
import {SedaService} from './seda.service';
import {FileTreeMetadataService} from '../../profile/edit-profile/file-tree-metadata/file-tree-metadata.service';
import {PuaService} from './pua.service';
import {ComponentType} from '@angular/cdk/portal';
import { NoticeProfile, ProfileResponse } from '../../profile/edit-profile/classes/profile-response';
@Injectable({
providedIn: 'root'
......@@ -54,65 +54,51 @@ import {ComponentType} from '@angular/cdk/portal';
export class FileService {
dataChange = new BehaviorSubject<FileNode[]>([]);
profile = new BehaviorSubject<any>([]);
notice = new BehaviorSubject<any>([]);
currentTree = new ReplaySubject<FileNode[]>();
notice = new BehaviorSubject<NoticeProfile>(null);
nodeChange = new BehaviorSubject<FileNode>(null);
allData = new BehaviorSubject<FileNode[]>([]);
currentTreeLoaded: boolean = false;
collectionName = new BehaviorSubject<string>(null);
rootTabMetadataName = new BehaviorSubject<string>(null);
tabRootNode = new BehaviorSubject<FileNode>(null);
filteredNode = new BehaviorSubject<FileNode>(null);
tabChildrenRulesChange = new BehaviorSubject<string[][]>([]);
parentNodeMap = new Map<FileNode, FileNode>();
sedaDataArchiveUnit : SedaData;
constructor(private profileService: ProfileService, private fileMetadataService: FileTreeMetadataService,
private dialog: MatDialog, private sedaService: SedaService, private puaService : PuaService) { }
private dialog: MatDialog, private sedaService: SedaService) { }
getCurrentFileTree(): Observable<FileNode[]> {
console.log("On file service : ", this.dataChange.getValue())
return this.dataChange;
}
reinitialisaDataChange() {
this.dataChange = new BehaviorSubject<FileNode[]>([]);
/**
* Update the tree with the profile provided
* @param profileResponse profileResponse sent from backend
*/
updateTreeWithProfile(profileResponse: ProfileResponse) {
this.profileService.profileMode = profileResponse.type;
let sedaDataArray: SedaData[] = [this.sedaService.sedaRules[0]];
this.linkFileNodeToSedaData(null, [profileResponse.profile], sedaDataArray);
this.currentTree.next([profileResponse.profile]);
this.currentTreeLoaded = true;
if(profileResponse.notice){
this.notice.next(profileResponse.notice);
}
}
addSedaMetadataToFileTree(id:number){
/**
* Get profile from backend with id
* @param id id of profile to get
*/
getProfileAndUpdateTree(id:number){
this.profileService.getProfile(id).subscribe((response) => {
this.profile.next(response);
let profile = this.puaService.puaToFileNode(response);
this.sedaService.getSedaRules().subscribe((sedaData: any) => {
let sedaDataArray: SedaData[] = [sedaData[0]];
this.linkFileNodeToSedaData(null, profile, sedaDataArray);
this.dataChange.next(profile);
})
if(response.notice){
this.notice.next(JSON.parse(response.notice));
}
this.updateTreeWithProfile(response);
});
return this.dataChange;
}
addSedaMetadataFromFileToFileTree(noSedaProfile:any){
this.sedaService.getSedaRules().subscribe((sedaData: any) => {
let sedaDataArray: SedaData[] = [sedaData[0]];
this.linkFileNodeToSedaData(null, noSedaProfile, sedaDataArray);
this.dataChange.next(noSedaProfile);
})
return this.dataChange;
}
sedaDataArchiveUnit : SedaData;
/**
* Relie chaque FileNode a sa définition Seda
*
......@@ -138,18 +124,16 @@ export class FileService {
//sedaDataMatch = this.sedaService.getSedaNodeRecursively(sedaData[0],nodeName)
}
if (!sedaDataMatch){
// Sometimes,the sedaData has no property children, but many symblins (e.g. elements on the same level of the tree)
// Sometimes,the sedaData has no property children, but many siblings (e.g. elements on the same level of the tree)
// In this case, the recursivity must be used on each symbling since the service getSedaNodeRecursively
// expects a tree root element with Children key
for (const element of sedaData) {
let resultRecursity = this.sedaService.getSedaNodeRecursively(element,nodeName);
if (resultRecursity) {
console.error("Found result : ", resultRecursity);
sedaDataMatch = resultRecursity;
break;
}
}
} else {
// Si le node en cours est l'achive ArchiveUnit mère, on la sauvegarde pour l'utiliser pour les ArchivesUnit enfants
if (sedaDataMatch.Name === 'ArchiveUnit' && this.sedaDataArchiveUnit == undefined){
......@@ -165,20 +149,11 @@ export class FileService {
});
}
updateFileTree(newData: FileNode[]): Observable<FileNode[]> {
this.sedaService.getSedaRules().subscribe((sedaData) => {
let sedaDataArray: SedaData[] = [sedaData[0]];
this.linkFileNodeToSedaData(null, newData, sedaDataArray);
this.dataChange.next(newData);
})
return this.dataChange;
}
/**
* Update the children of a node, based on given list of nodes
* @param parentNode
* @param newChildrenNodes
*/
/**
* Update the children of a node, based on given list of nodes
* @param parentNode
* @param newChildrenNodes
*/
updateNodeChildren(parentNode: FileNode, newChildrenNodes:FileNode[]) {
for (let idx in parentNode.children) {
let childFromNewChildren = newChildrenNodes.find(newChild => newChild.id == parentNode.children[idx].id);
......@@ -190,7 +165,6 @@ export class FileService {
sendNode(node:FileNode) {
this.nodeChange.next(node);
console.log("Node on file file service : ", this.nodeChange.getValue());
}
openPopup(popData: PastisDialogData){
......@@ -234,7 +208,6 @@ export class FileService {
this.rootTabMetadataName.next(rootTabMetadataName);
}
openDialogWithTemplateRef(templateRef: ComponentType<unknown>) {
this.dialog.open(templateRef);
}
......@@ -260,32 +233,26 @@ export class FileService {
fileNode.children = fileNode.children.filter(c=>c.type!==TypeConstants.attribute);
}
/** Update an item Tree node */
updateItem(node: FileNode) {
this.dataChange.next(node[0]);
console.log("Node updated to : ", this.dataChange.getValue())
}
removeItem(nodesToBeDeleted: FileNode[], root: FileNode) {
if (nodesToBeDeleted.length) {
for (let node of nodesToBeDeleted) {
let nodeToBeDeleted = this.getFileNodeByName(root,node.name);
//Check if node exists in the file tree
if (nodeToBeDeleted) {
const parentNode = nodeToBeDeleted.parent;
console.log("On removeItem with node : ", nodeToBeDeleted, "and parent : ", parentNode);
const index = parentNode.children.indexOf(nodeToBeDeleted);
if (index !== -1) {
parentNode.children.splice(index, 1);
this.parentNodeMap.delete(nodeToBeDeleted);
}
console.log("Deleted node : ", nodeToBeDeleted, "and his parent : ", parentNode);
removeItem(nodesToBeDeleted: FileNode[], root: FileNode) {
if (nodesToBeDeleted.length) {
for (let node of nodesToBeDeleted) {
let nodeToBeDeleted = this.getFileNodeByName(root,node.name);
//Check if node exists in the file tree
if (nodeToBeDeleted) {
const parentNode = nodeToBeDeleted.parent;
console.log("On removeItem with node : ", nodeToBeDeleted, "and parent : ", parentNode);
const index = parentNode.children.indexOf(nodeToBeDeleted);
if (index !== -1) {
parentNode.children.splice(index, 1);
//Refacto TODO
this.parentNodeMap.delete(nodeToBeDeleted);
}
console.log("Deleted node : ", nodeToBeDeleted, "and his parent : ", parentNode);
}
}
console.log("No nodes will be deleted")
}
console.log("No nodes will be deleted")
}
/** Find a parent tree node */
findParent(id: number, node: FileNode): FileNode {
......@@ -315,7 +282,7 @@ export class FileService {
console.log("Node clicked : ", node, "...with tab rules from service : ", rulesFromService);
console.log("The found node on filetree : ", node.sedaData)
this.sedaService.selectedSedaNode.next(node.sedaData);
this.dataChange.next(node[0]);
this.currentTree.next([node]);
this.sendNode(node);
let dataTable = this.fileMetadataService.fillDataTable(node.sedaData, node, tabChildrenToInclude, tabChildrenToExclude);
console.log("Data revtried on click : ", dataTable);
......@@ -325,17 +292,17 @@ export class FileService {
getFileNodeByName(fileTree:FileNode, nodeNameToFind:string):FileNode {
if (fileTree){
if (fileTree.name === nodeNameToFind) {
return fileTree;
}
for (const child of fileTree.children) {
const res = this.getFileNodeByName(child, nodeNameToFind);
if (res) {
return res;
if (fileTree.name === nodeNameToFind) {
return fileTree;
}
for (const child of fileTree.children) {
const res = this.getFileNodeByName(child, nodeNameToFind);
if (res) {
return res;
}
}
}
}
}
getFileNodeById(fileTree:FileNode, nodeIdToFind:number): any {
if (fileTree){
......
/*
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
[dad@cines.fr]
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
profiles based on the french SEDA standard
(https://redirect.francearchives.fr/seda/).
This software is governed by the CeCILL-C license under French law and
abiding by the rules of distribution of free software. You can use,
abiding by the rules of distribution of free software. You can use,
modify and/ or redistribute the software under the terms of the CeCILL-C
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".
"http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors have only limited
liability.
liability.
In this respect, the user's attention is drawn to the risks associated
with loading, using, modifying and/or developing or reproducing the
......@@ -28,16 +28,16 @@ that may mean that it is complicated to manipulate, and that also
therefore means that it is reserved for developers and experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
The fact that you are presently reading this means that you have had
knowledge of the CeCILL-C license and that you accept its terms.
*/
import { HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable, isDevMode } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Observable } from 'rxjs';
import { FileNode } from '../../profile/edit-profile/classes/file-node';
import { PastisApiService } from './api.pastis.service';
import { PastisConfiguration } from '../classes/pastis-configuration';
......@@ -45,13 +45,14 @@ import { environment} from '../../../environments/environment'
import { cloneDeep } from 'lodash';
import { ProfileDescription } from 'projects/pastis/src/app/profile/list-profile/models/profile-description.model';
import { NoticeProfile, ProfileResponse } from 'projects/pastis/src/app/profile/edit-profile/classes/profile-response';
@Injectable({
providedIn: 'root'
})
export class ProfileService {
private apiServerPath: string;
public profileMode = new BehaviorSubject(null);
public profileMode : string;
constructor(private apiService: PastisApiService, private pastisConfig: PastisConfiguration) {
this.apiServerPath = isDevMode() ? environment.apiServerUrl : pastisConfig.apiPastisUrl;
......@@ -67,12 +68,12 @@ export class ProfileService {
getProfile(id:number): Observable<ProfileResponse> {
let parameters = new HttpParams().set('id', id.toString());
return this.apiService.post(this.pastisConfig.editProfileUrl,{},{params:parameters})
return this.apiService.post<ProfileResponse>(this.pastisConfig.editProfileUrl,{},{params:parameters})
}
// Upload a RNG or a JSON file (PA or PUA, respectively) to the server
// Response : a JSON object
uploadProfile(profile: FormData): Observable<FileNode[]> {
uploadProfile(profile: FormData): Observable<ProfileResponse> {
return this.apiService.post(this.pastisConfig.uploadProfileUrl, profile);
}
......@@ -82,8 +83,8 @@ export class ProfileService {
return this.apiService.get(this.apiServerPath+this.pastisConfig.getFileUrl, options);
}
// Send the modified tree as post,
// Expects a RNG or a JSON file depending on the profile type
// Send the modified tree as post,
// Expects a RNG or a JSON file depending on the profile type
uploadFile(file: FileNode[],notice: NoticeProfile ,profileType:string): Observable<Blob> {
const httpOptions = {
headers: new HttpHeaders({
......@@ -92,29 +93,21 @@ export class ProfileService {
responseType: 'blob'
};
let profile: any = cloneDeep(file[0]);
let endPointUrl = profileType === "PA" ? this.pastisConfig.savePAasFileUrl : this.pastisConfig.savePUAasFileUrl
this.fixCircularReference(profile);
console.log("Data to")
if(profileType === "PUA"){
profile = {"elementProperties": profile, "notice": notice};
}
return this.apiService.post(endPointUrl, profile, httpOptions);
}
}
fixCircularReference(node: FileNode){
node.parent=null;
node.sedaData=null;
node.children.forEach(child=>{this.fixCircularReference(child);});
}
getProfileMode(){
this.profileMode.getValue();
}
setProfileMode(profileType: string){
this.profileMode.next(profileType)
}
}
import { TestBed } from '@angular/core/testing';
import { PuaService } from './pua.service';
describe('PuaService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: PuaService = TestBed.get(PuaService);
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { FileNode } from 'projects/pastis/src/app/profile/edit-profile/classes/file-node';
@Injectable({
providedIn: 'root'
})
export class PuaService {
constructor() { }
puaToFileNode(profileResponse:any){
let profile: any = JSON.parse(profileResponse.profile);
if (profile.notice) {
let puaProfile: FileNode = JSON.parse(profile.notice.profile);
return puaProfile;
} else {
return profile;
}
}
exportPUA(){
}
}
/*
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
[dad@cines.fr]
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
profiles based on the french SEDA standard
(https://redirect.francearchives.fr/seda/).
This software is governed by the CeCILL-C license under French law and
abiding by the rules of distribution of free software. You can use,
abiding by the rules of distribution of free software. You can use,
modify and/ or redistribute the software under the terms of the CeCILL-C
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".
"http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors have only limited
liability.
liability.
In this respect, the user's attention is drawn to the risks associated
with loading, using, modifying and/or developing or reproducing the
......@@ -28,39 +28,32 @@ that may mean that it is complicated to manipulate, and that also
therefore means that it is reserved for developers and experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
The fact that you are presently reading this means that you have had
knowledge of the CeCILL-C license and that you accept its terms.
*/
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { CardinalityConstants, FileNode } from '../../profile/edit-profile/classes/file-node';
import { SedaData} from '../../profile/edit-profile/classes/seda-data';
import { CardinalityValues } from '../classes/models';
import { PastisApiService } from './api.pastis.service';
import sedaRulesFile from '../../../assets/seda.json';
@Injectable({
providedIn: 'root'
})
export class SedaService {
private getSedaUrl = './assets/seda.json';
selectedSedaNode = new BehaviorSubject<SedaData>(null);
selectedSedaNodeParent = new BehaviorSubject<SedaData>(null);
sedaTabNodeRootToSearch = new BehaviorSubject<SedaData>(null);
private sedaRulesTemp: any = sedaRulesFile;
public sedaRules: SedaData = this.sedaRulesTemp;
constructor(private pastisAPI : PastisApiService) {}
getSedaRules(): Observable<SedaData>{
return this.pastisAPI.getLocally<SedaData>(this.getSedaUrl);
}
constructor() { }
getSedaNode(currentNode:SedaData, nameNode:string):SedaData {
if (currentNode && nameNode) {
......@@ -118,7 +111,7 @@ export class SedaService {
//Get the seda node based on collection name and a node name.
// Since the SEDA 2.1 model does not contain unique names,
// the function will search the whole file and return a single metadata based on
// a node name and a collection name;
// a node name and a collection name;
getSedaNodeCollection(sedaNode:SedaData, nodeName:string, collectionName:string):SedaData {
if (sedaNode){
if (sedaNode.Collection === collectionName && sedaNode.Name === nodeName) {
......@@ -131,28 +124,28 @@ export class SedaService {
}
}
}
}
// For all correspondent values beetween seda and tree elements,
// return a SedaData array of elements that does not have
// For all correspondent values beetween seda and tree elements,
// return a SedaData array of elements that does not have
// an optional (0-1) or an obligatory (1) cardinality.
// If an element have an 'n' cardinality (e.g. 0-N), the element will
// aways be included in the list
findSelectableElementList(sedaNode:SedaData, fileNode:FileNode): SedaData[] {
let fileNodesNames = fileNode.children.map(e=>e.name);
let allowedSelectableList = sedaNode.Children.filter(x => (!fileNodesNames.includes(x.Name) &&
let allowedSelectableList = sedaNode.Children.filter(x => (!fileNodesNames.includes(x.Name) &&
x.Cardinality !== CardinalityConstants.Obligatoire.valueOf())
||
(fileNodesNames.includes(x.Name) &&
||
(fileNodesNames.includes(x.Name) &&
(x.Cardinality === CardinalityConstants["Zero or More"].valueOf())
))
return allowedSelectableList;
return allowedSelectableList;
}
findCardinalityName(clickedNode:FileNode, cardlinalityValues: CardinalityValues[]):string{
if(!clickedNode.cardinality){
return "1"
if(!clickedNode.cardinality){
return "1"
} else {
return cardlinalityValues.find(c=>c.value == clickedNode.cardinality).value
}
......@@ -164,7 +157,7 @@ export class SedaService {
*/
getAttributes(sedaNode:SedaData, collection:string):SedaData[] {
//if (!sedaNode) return;
return sedaNode.Children.filter(children=>children.Element=="Attribute"
return sedaNode.Children.filter(children=>children.Element=="Attribute"
&& sedaNode.Collection === collection);
}
......@@ -181,9 +174,22 @@ export class SedaService {
}
}
isDuplicated(nomDuChamp: string, sedaParent: SedaData) {
if (sedaParent.Name == nomDuChamp) {
return sedaParent.Cardinality.includes('N');
}
if (sedaParent){
for (let child of sedaParent.Children) {
if (child.Name == nomDuChamp){
return child.Cardinality.includes('N');
}
}
}
}
checkSedaElementType(nodeName:string, sedaNode:SedaData):string{
if (sedaNode.Name === nodeName) return sedaNode.Element;
let node = sedaNode.Children.find(c=>c.Name==nodeName);
if (node) {
return node.Element
......@@ -202,17 +208,17 @@ export class SedaService {
let sedaRootNodeSearch = this.getSedaNodeRecursively(this.selectedSedaNode.getValue(),sedaNodeName)
this.sedaTabNodeRootToSearch.next(sedaRootNodeSearch);
}
// Returns a list of cardinalities of a given a fileNode's children
// If an attributte child doesn't not have a cardinality
// then the seda child's cardinality will be added by default;
getCardinalitiesOfSedaChildrenAttributes(fileNode:FileNode,sedaNode:SedaData):string[]{
let cardinalities : string[] = []
let cardinalities : string[] = []
for (let fileChild of fileNode.children){
for (let sedaChild of sedaNode.Children){
if (fileChild.name === sedaChild.Name){
fileChild.cardinality ?
cardinalities.push(fileChild.cardinality) :
fileChild.cardinality ?
cardinalities.push(fileChild.cardinality) :
cardinalities.push(sedaChild.Cardinality);
}
}
......
declare module "*.json" {
const value: any;
export default value;
}
\ No newline at end of file
......@@ -16,11 +16,12 @@
<mat-sidenav-content >
<div class="pastis-entete-bandeau"></div>
<div>
<pastis-file-tree-metadata style="text-align: center;" *ngIf="!noticeDisplay"
<pastis-file-tree-metadata *ngIf="fileService.currentTreeLoaded && !noticeDisplay"
(insertItem)="insertionItem($event)"
(addNode)="addNode($event)"
(insertAttributes)="insertAttribute($event)"
(removeNode)="removeNode($event)">
(removeNode)="removeNode($event)"
(duplicateNode)="duplicateNode($event)">
</pastis-file-tree-metadata>
<pastis-notice *ngIf="noticeDisplay"></pastis-notice>
......
......@@ -37,12 +37,14 @@ knowledge of the CeCILL-C license and that you accept its terms.
*/
import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {CdkTextareaAutosize} from '@angular/cdk/text-field';
import {ActivatedRoute} from '@angular/router';
import {ActivatedRoute, Router} from '@angular/router';
import {ToggleSidenavService} from '../core/services/toggle-sidenav.service';
import {ToastContainerDirective, ToastrService} from 'ngx-toastr';
import {Subscription} from 'rxjs';
import {EditProfileComponent} from '../profile/edit-profile/edit-profile.component';
import {FileNode, FileNodeInsertAttributeParams, FileNodeInsertParams} from "../profile/edit-profile/classes/file-node";
import { FileService } from '../core/services/file.service';
import { ProfileResponse } from '../profile/edit-profile/classes/profile-response';
@Component({
selector: 'app-home',
......@@ -65,11 +67,16 @@ export class MainComponent implements OnInit, OnDestroy {
noticeDisplay: boolean;
noticeDisplaySub: Subscription;
constructor(private route: ActivatedRoute,private sideNavService : ToggleSidenavService, private toastrService: ToastrService) {
uploadedProfileResponse: ProfileResponse;
constructor(private route: ActivatedRoute,private sideNavService : ToggleSidenavService, private toastrService: ToastrService,
public fileService: FileService, private router: Router) {
this.uploadedProfileResponse = this.router.getCurrentNavigation().extras.state as ProfileResponse;
this.sideNavService.isOpened.subscribe(status=>{
this.opened = status;
})
this.noticeDisplaySub = this.sideNavService.noticeSelected.subscribe(statusNotice => {
this.noticeDisplay = statusNotice;
console.log('consolee ======> status'+ this.noticeDisplay)
......@@ -79,7 +86,16 @@ export class MainComponent implements OnInit, OnDestroy {
ngOnInit() {
this.toastrService.overlayContainer = this.toastContainer;
this.route.params.subscribe(params => {
this.profileId = params['id']
this.profileId = params['id'];
// If a profileId has been defined, it is retrieved from backend
if (this.profileId != undefined) {
this.fileService.getProfileAndUpdateTree(this.profileId);
} else {
// Otherwise we must have an user uploaded profile
this.profileId = this.uploadedProfileResponse.id;
this.fileService.updateTreeWithProfile(this.uploadedProfileResponse);
}
});
this.opened = true;
}
......@@ -89,7 +105,6 @@ export class MainComponent implements OnInit, OnDestroy {
this.sideNavService.show();
}
insertionItem($event: FileNodeInsertParams) {
this.editProfileComponent.fileTreeComponent.insertItem($event.node, $event.elementsToAdd);
console.log("Params : ", $event);
......@@ -109,7 +124,14 @@ export class MainComponent implements OnInit, OnDestroy {
this.editProfileComponent.fileTreeComponent.remove($event);
}
duplicateNode($event: FileNode) {
this.editProfileComponent.fileTreeComponent.duplicate($event);
}
ngOnDestroy(): void {
this.noticeDisplaySub.unsubscribe();
}
}
<mat-sidenav-container [autosize]="true" [hasBackdrop]="false">
<mat-sidenav-content >
<div>
<!--<pastis-file-tree-metadata style="text-align: center;"></pastis-file-tree-metadata>-->
<!--Top panels container-->
<div class="pastis-metadata-option-container">
<!-- Top left panel container -->
<div class="pastis-metadata-option-entete-1">
<!--Arrow button conainer-->
<!-- <div class="pastis-position-btn-arrow-back">
<button class="btn btn-circle primary small" tooltip="Retour vers la liste des profils" placement="bottom"
show-delay="0" tooltip-class="pastis-tooltip-class" (click)="goBack()">
<i class="material-icons">arrow_back</i>
</button>
</div> -->
<!--Panel content separator-->
<!-- <div class="pastis-entete-1-separator"></div>-->
<!--Text cotopntainer-->
<div class="pastis-entete-1-text">
<div class="pastis-entete-1-text-titre">{{keys[0]}}</div>
<div class="pastis-entete-1-text-body-1">
Créer et gérer un profil d'unité archivistique<mat-icon style="color: #474D4A" class="pastis-ico-arrow-right">arrow_right_alt</mat-icon>
<span class="pastis-entete-1-text-body-2" *ngIf="notice">{{notice.Name}}</span>
</div>
</div>
</div>
<!--Top right panel container-->
<div class="pastis-metadata-option-entete-2">
<pastis-title-breadcrumb *ngIf="!isStandalone" class="breadcrumbTop"
[data]="breadcrumbDataTop"
(selected)="navigate($event)">
</pastis-title-breadcrumb>
<!--Button save-->
<div class="panel-buttons" tooltip="Enregistrer le profil" placement="top" show-delay="0"
tooltip-class="pastis-tooltip-class">
<pastis-user-action-save-profile></pastis-user-action-save-profile>
</div>
<!--Button setting-->
<div class="panel-buttons">
<pastis-user-action-download-doc (click)="openChoicePopup()"></pastis-user-action-download-doc>
<div class="vitamui-pastis-choice-language">
<pastis-popup-metadata-language *ngIf="languagePopup"
[docPath]="docPath" (click)="changeSedaLanguage()"></pastis-popup-metadata-language>
</div>
<div class="pastis-metadata-option-container">
<!-- Top left panel container -->
<div class="pastis-metadata-option-entete-1">
<h5><i class="vitamui-icon vitamui-icon-dossier-physique"></i>{{profileModeLabel}}</h5>
</div>
<!--Top right panel container-->
<div class="pastis-metadata-option-entete-2">
<!--Button open-->
<!-- <div class="panel-buttons" tooltip="Importer un profil" placement="top" show-delay="0"
tooltip-class="pastis-tooltip-class">
<pastis-user-action-upload></pastis-user-action-upload>
</div> -->
<!--Button save-->
<div class="panel-buttons" tooltip="Enregistrer le profil" placement="top" show-delay="0"
tooltip-class="pastis-tooltip-class">
<pastis-user-action-save-profile></pastis-user-action-save-profile>
</div>
<!--Button setting-->
<div class="panel-buttons" i18n-tooltip="Télécharger le manuel d'utilisation de PASTIS@@telechargerManuel" tooltip="Télécharger le manuel d'utilisation de PASTIS" placement="top"
show-delay="0" tooltip-class="pastis-tooltip-class">
<pastis-user-action-download-doc (click)="openChoicePopup()"></pastis-user-action-download-doc>
<div class="vitamui-pastis-choice-language">
<pastis-popup-metadata-language *ngIf="languagePopup"
[docPath]="docPath" (click)="changeSedaLanguage()"></pastis-popup-metadata-language>
</div>
</div>
</div>
<!--Check font.scss-->
</div>
<!--Check font.scss-->
<div class="pastis-notice-container">
<div class="pastis-notice-container-title" i18n="Onglet Notice@@ongletNotice">
......@@ -54,7 +42,8 @@
<div>
<div class="pastis-notice-container-id">
<div class="pastis-notice-id-label"><p class="text caption bold" style="color:var(--vitamui-primary)" i18n="Onglet Notice type@@ongletNoticeType">Type</p></div>
<div class="pastis-notice-id-value"><p class="text text-medium bold">{{puaMode ? 'Profil d\'unité archivistique' : 'Profil d\'archivage'}}</p></div>
<div class="pastis-notice-id-value"><p class="text text-medium bold">{{profileService.profileMode==='PUA' ? 'Profil d\'unité archivistique' : 'Profil d\'archivage'}}</p></div>
</div>
<div class="pastis-notice-container-id">
<div class="pastis-notice-id-label"><p class="text caption bold" style="color:var(--vitamui-primary)" i18n="Onglet Notice identifiant@@ongletNoticeIdentifiant">Identifiant</p></div>
......@@ -70,7 +59,7 @@
<vitamui-common-textarea *ngIf="notice" [(ngModel)]="notice.Description" i18n-placeholder="Onglet Notice placeholder Description@@ongletNoticeDescription" placeholder="Description" (change)="changeNotice()">
</vitamui-common-textarea>
</div>
<div class="pastis-notice-footer" *ngIf="puaMode">
<div class="pastis-notice-footer" *ngIf="profileService.profileMode==='PUA'">
<vitamui-common-slide-toggle [checked]="false" [disabled]="false" style="margin-top: 10px;"></vitamui-common-slide-toggle>
<div class="pastis-notice-container-chart">
<div class="pastis-notice-chart" i18n="Pastis notice chart part 1@@pastisNoticeChartPartOne">
......
......@@ -42,9 +42,12 @@ import {Subscription} from 'rxjs';
import {ToggleSidenavService} from '../core/services/toggle-sidenav.service';
import {FormBuilder, FormControl, FormGroup} from '@angular/forms';
import {NoticeProfile} from "../profile/edit-profile/classes/profile-response";
import {NoticeService} from '../core/services/notice.service'
import { ProfileService } from '../core/services/profile.service';
import { PastisPopupMetadataLanguageService } from '../shared/pastis-popup-metadata-language/pastis-popup-metadata-language.service';
import { FileService } from '../core/services/file.service';
import { BreadcrumbDataTop } from '../profile/edit-profile/classes/breadcrumb';
import { StartupService } from 'ui-frontend-common';
import { environment } from '../../environments/environment';
@Component({
selector: 'pastis-notice',
templateUrl: './notice.component.html',
......@@ -63,29 +66,22 @@ export class NoticeComponent implements OnInit, OnDestroy {
options: FormGroup;
hideRequiredControl = new FormControl(false);
floatLabelControl = new FormControl('auto');
puaMode: boolean;
puaSub: Subscription;
docPath: string;
sedaLanguage: boolean;
languagePopup: boolean;
breadcrumbDataTop: Array<BreadcrumbDataTop>;
profileModeLabel: string;
isStandalone: boolean = environment.standalone;
constructor(private route: ActivatedRoute, private sideNavService: ToggleSidenavService, private fb: FormBuilder,
private router: Router, private noticeService: NoticeService, private profileService: ProfileService,
private metadataLanguageService: PastisPopupMetadataLanguageService) {
private router: Router,private startupService: StartupService, public profileService: ProfileService, private fileService: FileService, private metadataLanguageService: PastisPopupMetadataLanguageService) {
this.options = this.fb.group({
hideRequired: this.hideRequiredControl,
floatLabel: this.floatLabelControl,
});
this.puaSub = this.profileService.profileMode.subscribe((status) => {
status === "PUA" ? this.puaMode = true : this.puaMode = false;
},
(error) => {
console.log(error);
}
);
this.newComponent = (this.route.snapshot.url[0].path === "new");
if(this.newComponent){
this.noticeSub = this.noticeService.notice.subscribe(
this.noticeSub = this.fileService.notice.subscribe(
(value: any) => {
console.log(value)
this.notice = value;
......@@ -95,9 +91,9 @@ export class NoticeComponent implements OnInit, OnDestroy {
}
);
}else{
this.noticeSub = this.noticeService.getNotice().subscribe(
this.noticeSub = this.fileService.notice.subscribe(
(value: any) => {
if(value && this.puaMode){
if(value && this.profileService.profileMode==='PUA'){
this.notice = value;
}else {
const notice: NoticeProfile = {
......@@ -128,6 +124,9 @@ export class NoticeComponent implements OnInit, OnDestroy {
ngOnInit() {
this.languagePopup = false;
this.docPath = 'assets/doc/VITAM UI - Documentation APP - PASTIS.pdf';
this.profileModeLabel = this.profileService.profileMode==='PUA'?'Profil d\'Unité Archivistique':'Profil d\'Archivage';
this.breadcrumbDataTop = [{ label: 'Portail', url: this.startupService.getPortalUrl(), external: true},{ label: 'Créer et gérer un profil d\'archivage', url: '/'}, { label: this.profileModeLabel }];
this.openedSub = this.sideNavService.isOpened.subscribe((status) => {
this.opened = status;
},
......@@ -138,12 +137,21 @@ export class NoticeComponent implements OnInit, OnDestroy {
this.tabLabels.push('NOTICE', 'UNITÉ D\'ARCHIVES');
}
goBack(){
this.router.navigate(['/'],{skipLocationChange: false});
}
navigate(d: BreadcrumbDataTop){
if (d.external){
window.location.assign(d.url);
} else {
this.router.navigate([d.url],{skipLocationChange: false});
}
}
changeNotice(){
this.noticeService.notice.next(this.notice);
this.fileService.notice.next(this.notice);
}
changeSedaLanguage(){
......@@ -164,7 +172,6 @@ export class NoticeComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.openedSub.unsubscribe();
this.noticeSub.unsubscribe();
this.puaSub.unsubscribe();
}
......
import { FileNode } from "./file-node";
export interface BreadcrumbDataTop {
label: string;
url?: string;
external?: boolean;
}
export interface BreadcrumbDataMetadata {
label: string;
node?: FileNode;
}
/*
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
Copyright © CINES - Centre Informatique National pour l'Enseignement Supérieur (2020)
[dad@cines.fr]
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
This software is a computer program whose purpose is to provide
a web application to create, edit, import and export archive
profiles based on the french SEDA standard
(https://redirect.francearchives.fr/seda/).
This software is governed by the CeCILL-C license under French law and
abiding by the rules of distribution of free software. You can use,
abiding by the rules of distribution of free software. You can use,
modify and/ or redistribute the software under the terms of the CeCILL-C
license as circulated by CEA, CNRS and INRIA at the following URL
"http://www.cecill.info".
"http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors have only limited
liability.
liability.
In this respect, the user's attention is drawn to the risks associated
with loading, using, modifying and/or developing or reproducing the
......@@ -28,9 +28,9 @@ that may mean that it is complicated to manipulate, and that also
therefore means that it is reserved for developers and experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
requirements in conditions enabling the security of their systems and/or
data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
The fact that you are presently reading this means that you have had
knowledge of the CeCILL-C license and that you accept its terms.
......@@ -94,10 +94,11 @@ export enum ValueOrDataConstants {
}
export interface FileNode {
[key: string]: any;
id: number;
parentId: number;
name: string;
groupOrChoice: string;
choices: string;
valueOrData: ValueOrDataConstants;
value: string;
type: TypeConstants;
......@@ -120,3 +121,11 @@ export interface FileNodeInsertAttributeParams {
node: FileNode;
elementsToAdd:FileNode[];
}
export enum nodeNameToLabel {
'notice' = 'Notice',
'ArchiveTransfer' = 'Entête',
'ManagementMetadata' = 'Règles',
'DescriptiveMetadata' = 'Unités d\'archives',
'DataObjectPackage' = 'Objets'
}
import { FileNode } from "./file-node";
export interface ProfileResponse{
id:number;
profile: string;
notice?:string;
profile: FileNode;
notice?: NoticeProfile;
type: string;
}
export interface NoticeProfile{
Description: string;
......