new public branch

parents
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
/.gradle/
/.gradle/buildOutputCleanup/buildOutputCleanup.lock
/.gradle/checksums/checksums.lock
/.gradle/6.7/executionHistory/executionHistory.bin
/.gradle/6.7/executionHistory/executionHistory.lock
/.gradle/6.7/fileHashes/fileHashes.bin
/.gradle/6.7/fileHashes/fileHashes.lock
/.gradle/checksums/md5-checksums.bin
/.gradle/checksums/sha1-checksums.bin
/build/tmp/war/MANIFEST.MF
/build/
/.gradle/
/.angular/
/.angular/cache/13.2.6/babel-webpack/
/.angular/cache/13.2.6/angular-webpack/
/.angular/
#Fri Feb 17 18:28:47 CET 2023
gradle.version=7.6
# IpmworksWeb
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.0.2.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": false
},
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ipmworks-web": {
"i18n": {
"sourceLocale": "en",
"locales": {
"sr": {
"translation": "src/locale/messages.sr.xlf"
},
"it": {
"translation": "src/locale/messages.it.xlf"
},
"pt": {
"translation": "src/locale/messages.pt.xlf"
},
"pl": {
"translation": "src/locale/messages.pl.xlf"
},
"fi": {
"translation": "src/locale/messages.fi.xlf"
},
"fr": {
"translation": "src/locale/messages.fr.xlf"
},
"el": {
"translation": "src/locale/messages.el.xlf"
},
"sl": {
"translation": "src/locale/messages.sl.xlf"
},
"es": {
"translation": "src/locale/messages.es.xlf"
},
"nl": {
"translation": "src/locale/messages.nl.xlf"
},
"da": {
"translation": "src/locale/messages.da.xlf"
},
"de": {
"translation": "src/locale/messages.de.xlf"
}
}
},
"projectType": "application",
"schematics": {
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"localize": true,
"aot": true,
"outputPath": "dist/ipmworks-web",
"index": "src/index.html",
"baseHref": "/toolbox/",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css",
"./node_modules/primeicons/primeicons.css",
"./node_modules/primeng/resources/themes/mdc-light-indigo/theme.css",
"./node_modules/primeng/resources/primeng.css",
"./node_modules/primeflex/primeflex.css",
"./node_modules/quill/dist/quill.core.css",
"./node_modules/quill/dist/quill.snow.css"
],
"scripts": []
},
"configurations": {
"production": {
"buildOptimizer": true,
"optimization": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "10mb",
"maximumError": "10mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "10mb",
"maximumError": "10mb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": true,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "ipmworks-web:build:production"
},
"development": {
"browserTarget": "ipmworks-web:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ipmworks-web:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
}
}
}
},
"defaultProject": "ipmworks-web"
}
plugins {
id 'java'
id "com.github.node-gradle.node" version "2.2.4"
id 'war'
}
repositories {
mavenCentral()
}
node {
version = '18.13.0'
npmVersion = '9.4.0'
download = true
}
war.dependsOn 'npm_run_build'
war {
from 'dist/ipmworks-web'
enabled = true
}
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/ipmworks-web'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "ipmworks-web",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.2.6",
"@angular/common": "~13.2.6",
"@angular/compiler": "~13.2.6",
"@angular/core": "~13.2.6",
"@angular/forms": "~13.2.6",
"@angular/platform-browser": "~13.2.6",
"@angular/platform-browser-dynamic": "~13.2.6",
"@angular/router": "~13.2.6",
"@ngxs/storage-plugin": "^3.5.0",
"@ngxs/store": "^3.5.0",
"lazysizes": "^5.3.2",
"ng-lazyload-image": "^9.1.2",
"ngx-clipboard": "~15.1.0",
"primeflex": "~3.1.3",
"primeicons": "~5.0.0",
"primeng": "~13.2.1",
"quill": "^1.3.7",
"rxjs": "~6.6.0",
"tslib": "^2.1.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^13.2.6",
"@angular/cdk": "~13.2.6",
"@angular/cli": "~13.2.6",
"@angular/compiler-cli": "~13.2.6",
"@angular/localize": "~13.2.6",
"@types/jasmine": "~3.10.3",
"@types/node": "^14.0.0",
"jasmine-core": "~4.0.1",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"lite-server": "^2.6.1",
"typescript": "~4.4.2"
}
}
pluginManagement {
repositories {
gradlePluginPortal ()
}
}
rootProject.name = 'ipmworks-web'
import {NgModule, OnInit} from '@angular/core';
import {ActivatedRoute, Router, RouterModule, Routes} from '@angular/router';
import {HomeComponent} from './component/home/home.component';
import {ResourceComponent} from "./component/resource/resource.component";
import {AddResourceComponent} from "./component/add-resource/add-resource.component";
import {ApproveResourceComponent} from "./component/approve-resource/approve-resource.component";
import {LoginComponent} from "./component/login/login.component";
import {AuthGuard} from "./guard/auth.guard";
import {ChangePasswordComponent} from "./component/change-password/change-password.component";
import {HelpComponent} from "./component/help/help.component";
import {AppComponent} from "./app.component";
const routes: Routes = [
{path: '', component: HomeComponent, pathMatch: 'full'},
{path: 'export', component: HomeComponent, pathMatch: 'full',data:{header:"none"}},
{path: 'home', component: HomeComponent, pathMatch: 'full'},
{path:'resource/approve', component:ApproveResourceComponent, pathMatch:'full',canActivate:[AuthGuard]},
{path:'resource/add', component:AddResourceComponent, pathMatch:'full'},
{path:'resource/edit/:id',component:AddResourceComponent,pathMatch:'full',canActivate:[AuthGuard]},
{path:'resource/:id/:lang', component:ResourceComponent, pathMatch:'full'},
{path:'resource/:id', component:ResourceComponent, pathMatch:'full'},
{path:'login', component:LoginComponent, pathMatch:'full'},
{path:'help', component:HelpComponent, pathMatch:'full'},
{path:'changePassword/:token', component:ChangePasswordComponent, pathMatch:'full'}
];
@NgModule({
imports: [RouterModule.forRoot(routes,{onSameUrlNavigation: 'reload', scrollPositionRestoration: 'enabled'})],
exports: [RouterModule]
})
export class AppRoutingModule{
}
<app-header-menu [hideheader]="hideheader"> </app-header-menu>
<div class="container-fluid py-4">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'ipmworks-web'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('ipmworks-web');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('ipmworks-web app is running!');
});
});
import {Component, OnDestroy, OnInit} from "@angular/core";
import {ActivatedRoute, NavigationEnd, Router} from "@angular/router";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
title = 'ipmworks-web';
hideheader?:string
constructor(public router:Router,public route:ActivatedRoute) {
}
ngOnInit(): void {
this.router.events.subscribe(e => {
this.hideheader = this.route.root.firstChild?.snapshot.data['header'];
});
}
}
import {LOCALE_ID, NgModule} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {MenubarModule} from 'primeng/menubar';
import {MenuModule} from 'primeng/menu';
import {ButtonModule} from 'primeng/button';
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {HomeComponent} from './component/home/home.component';
import {AccordionModule} from 'primeng/accordion';
import {MessageService, SharedModule} from "primeng/api";
import {CheckboxModule} from 'primeng/checkbox';
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {DataViewModule} from "primeng/dataview";
import {RatingModule} from "primeng/rating";
import {HTTP_INTERCEPTORS, HttpClientModule} from "@angular/common/http";
import {MultiSelectModule} from 'primeng/multiselect';
import {DropdownModule} from "primeng/dropdown";
import {InputTextModule} from "primeng/inputtext";
import {TooltipModule} from "primeng/tooltip";
import {CardModule} from "primeng/card";
import {TagModule} from "primeng/tag";
import {ResourceComponent } from './component/resource/resource.component';
import {AddResourceComponent } from './component/add-resource/add-resource.component';
import {InputTextareaModule} from "primeng/inputtextarea";
import {RadioButtonModule} from "primeng/radiobutton";
import {FileUploadModule} from "primeng/fileupload";
import {ChipsModule} from 'primeng/chips';
import {MessagesModule} from 'primeng/messages';
import {MessageModule} from 'primeng/message';
import {ToastModule} from 'primeng/toast';
import {PanelModule} from "primeng/panel";
import {HashLocationStrategy, LocationStrategy} from "@angular/common";
import {CascadeSelectModule} from 'primeng/cascadeselect';
import {DialogModule} from 'primeng/dialog';
import { ApproveResourceComponent } from './component/approve-resource/approve-resource.component';
import {TableModule} from "primeng/table";
import { LoginComponent } from './component/login/login.component';
import {NgxsStoragePluginModule} from '@ngxs/storage-plugin';
import {NgxsModule} from '@ngxs/store';
import {UserState} from "./states/user.state";
import {environment} from "../environments/environment";
import {AuthInterceptor} from "./guard/auth.interceptor";
import { HeaderMenuComponent } from './component/header-menu/header-menu.component';
import {ImageModule} from "primeng/image";
import {ConfirmPopupModule} from 'primeng/confirmpopup';
import {ConfirmationService} from 'primeng/api';
import {ConfirmDialogModule} from "primeng/confirmdialog";
import {LazyLoadImageModule} from 'ng-lazyload-image';
import {FooterComponent } from './component/footer/footer.component';
import {ChangePasswordComponent } from './component/change-password/change-password.component';
import {CheckPasswordDirective } from './validator/check-password.directive';
import {BadgeModule} from "primeng/badge";
import {ClipboardModule } from 'ngx-clipboard';
import {AvatarModule} from 'primeng/avatar';
import {AvatarGroupModule} from 'primeng/avatargroup';
import {HelpComponent } from './component/help/help.component';
import {DividerModule} from 'primeng/divider';
import {ProgressSpinner, ProgressSpinnerModule} from "primeng/progressspinner";
import {TabViewModule} from 'primeng/tabview';
import {EditorModule} from "primeng/editor";
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ResourceComponent,
AddResourceComponent,
ApproveResourceComponent,
LoginComponent,
HeaderMenuComponent,
FooterComponent,
ChangePasswordComponent,
CheckPasswordDirective,
HelpComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
MenubarModule,
ButtonModule,
MenuModule,
AccordionModule,
SharedModule,
FormsModule,
DataViewModule,
RatingModule,
HttpClientModule,
MultiSelectModule,
DropdownModule,
InputTextModule,
TooltipModule,
CardModule,
TagModule,
InputTextModule,
CheckboxModule,
ButtonModule,
RadioButtonModule,
InputTextareaModule,
FileUploadModule,
ChipsModule,
MessagesModule,
MessageModule,
ToastModule,
PanelModule,
CascadeSelectModule,
DialogModule,
TableModule,
NgxsModule.forRoot([UserState], {developmentMode: !environment.production}),
NgxsStoragePluginModule.forRoot(),
ReactiveFormsModule,
ToastModule,
ImageModule,
ConfirmPopupModule,
ConfirmDialogModule,
LazyLoadImageModule,
BadgeModule,
ClipboardModule,
AvatarModule,
AvatarGroupModule,
DividerModule,
ProgressSpinnerModule,
TabViewModule,
EditorModule
],
providers: [MessageService,ConfirmationService,
{provide: LocationStrategy, useClass: HashLocationStrategy},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
.imageSizeHome{
max-width: 100%;
max-height: 120px;
display: block;
margin-left: auto;
margin-right: auto;
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddResourceComponent } from './add-resource.component';
describe('AddResourceComponent', () => {
let component: AddResourceComponent;
let fixture: ComponentFixture<AddResourceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AddResourceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddResourceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Inject, LOCALE_ID, OnInit} from '@angular/core';
import {ResourceType, Language, Region, Sector, Project, Resource, Pest, Crop} from "../../model/resource";
import {ResourceService} from "../../service/resource.service";
import {ConfirmationService, MessageService} from 'primeng/api';
import {ActivatedRoute, Router} from "@angular/router";
import {FormControl} from "@angular/forms";
import {environment} from "../../../environments/environment";
@Component({
selector: 'app-add-resource',
templateUrl: './add-resource.component.html',
styleUrls: ['./add-resource.component.css']
})
export class AddResourceComponent implements OnInit {
sectors: Sector[] = [];
regions: Region[] = [];
resourceTypes: ResourceType[] = [];
languages: Language[] = [];
projects: Project[] = [];
pests:Pest[] = [];
crops: Crop [] = [];
resource: Resource = {};
image: any;
resourceContent: File[] = [];
displayPestModal=false;
displayCropModal=false;
newPest:Pest = {};
newCrop: Crop = {};
resourceControl = new FormControl('');
resourceId?:string;
edit:boolean = false;
imageExist:boolean = false;
imagePath = environment.baseUrl+"/resource/image/raw";
showOldImg = true;
logo: any;
showOldLogo = true;
logoExist:boolean = false;
displayTerms = false;
termChecked = false;
constructor(private resourceService:ResourceService, private route: ActivatedRoute, private messageService: MessageService, private router: Router, @Inject(LOCALE_ID) public locale: string, private confirmationService: ConfirmationService) { }
ngOnInit(): void {
this.resourceService.getAllSectors().subscribe((sectors: Sector[]) => {
this.sectors = sectors;});
this.resourceService.getAllRegions().subscribe((regions: Region[]) => {
this.regions = regions;});
this.resourceService.getAllResourceTypes().subscribe((contentTypes: ResourceType[]) => {
this.resourceTypes = contentTypes;});
this.resourceService.getAllLanguages().subscribe((languages: Language[]) => {
this.languages = languages;});
this.resourceService.getAllProjects().subscribe((projects: Project[]) => {
this.projects = projects;});
this.resourceService.getAllPests().subscribe((pests: Pest[]) => {
this.pests = pests;});
this.resourceService.getAllCrops().subscribe((crops: Crop[]) => {
this.crops = crops;});
// @ts-ignore
this.resourceId = this.route.snapshot.paramMap.get('id');
if(this.resourceId != undefined){
// @ts-ignore
this.resourceService.getResource(this.resourceId).subscribe(data =>{
this.resource = data;
this.edit = true;
});
this.resourceService.hasImage(this.resourceId,"image").subscribe( data =>{
this.imageExist = data
});
this.resourceService.hasImage(this.resourceId,"logo").subscribe( data =>{
this.logoExist = data
});
}
}
saveResource(){
this.resource.canEdit=true;
this.resourceService.saveResource(this.resource,this.edit).subscribe((data: Resource) => {
this.resource = data;
this.resourceService.addFiles(this.image, this.logo, this.resourceContent, this.resource.idResource).subscribe(data => {
if(!this.edit){
this.messageService.add({severity: 'success', detail: 'Resource is saved, but need to be approved by administrator!'});
this.router.navigate(['/home']);
}else{
this.messageService.add({severity: 'success', detail: 'Resource is updated!'});
this.router.navigate(['/resource/'+this.resourceId]);
}
}, error => {
this.messageService.add({severity: 'error', detail: 'Some error occurred!'})
});
},error => {
//TODO obrisati sve fajlove koji su se mozda sacuvali ako je doslo do greske
this.messageService.add({severity: 'error', detail: 'Some error occurred!'})
});
}
public fileUploader(type:string,event:any){
if(type === 'image'){
this.image = event.files[0]
}
else if(type === 'logo'){
this.logo = event.files[0]
}
else{
this.resourceContent = event.files;
}
}
public openPestModal(){
// @ts-ignore
if (this.resource.pests.filter(e => e.commonName === 'Other').length > 0) {
this.displayPestModal=true;
}
}
public addPest(){
// @ts-ignore
var selectedPests = this.resource.pests.filter(function(el) { return el.commonName != "Other"; });
this.resource.pests = selectedPests;
this.resource.pests.push(this.newPest);
this.pests.push(this.newPest);
this.newPest={};
this.displayPestModal=false;
}
public openCropModal(){
// @ts-ignore
if (this.resource.crops.filter(e => e.commonName === 'Other').length > 0) {
this.displayCropModal=true;
}
}
public addCrop(){
// @ts-ignore
var selectedCrops = this.resource.crops.filter(function(el) { return el.commonName != "Other"; });
this.resource.crops = selectedCrops;
this.resource.crops.push(this.newCrop);
this.crops.push(this.newCrop);
this.newCrop={};
this.displayCropModal=false;
}
hideOldImage(){
this.showOldImg = false;
}
removeImage(){
this.image=undefined;
this.showOldImg = true;
}
hideOldLogo(){
this.showOldLogo = false;
}
removeLogo(){
this.logo=undefined;
this.showOldLogo = true;
}
showTerms(){
this.displayTerms = true;
}
delete(fileId?: String) {
this.confirmationService.confirm({
message: 'Are you sure that you want to delete resource?',
icon: 'pi pi-exclamation-triangle',
accept: () => {
// @ts-ignore
this.resourceService.deleteFile(fileId).subscribe(r => {
this.messageService.add({severity: 'success', detail: 'Resource has been deleted.'});
this.router.navigate(['/resource/edit/'+this.resource.idResource]);
}, error => {
this.messageService.add({severity: 'error', detail: 'Some error occurred!'});
}
);
}
});
}
downloadFile(fileName: any): void {
this.resourceService.downloadFile(fileName).subscribe((file: any) => {
const blob = new Blob([file], {type: file.type});
var url = window.URL.createObjectURL(blob);
/*const link = document.createElement('a');
link.href = this.url;
link.target="_blank";
link.download = fileName;*/
window.open(url,"_blank")
}, error => {
console.log(error)
});
}
}
<div class="card pt-6 pl-6 pr-6">
<p class="text-3xl" i18n> Approve resources</p>
<p-table [value]="resources" [paginator]="true" [rows]="10" >
<ng-template pTemplate="header">
<tr>
<th i18n>Resource name</th>
<th i18n>Resource type</th>
<th i18n>Creation date</th>
<th i18n>How resource added</th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-resource>
<tr>
<td>{{resource.resourceName}}</td>
<td>{{resource.resourceType?.name}}</td>
<td>{{resource.creationDate| date:"d.M.yyyy, h:mm a"}}</td>
<td i18n>Manually</td>
<td>
<button pButton type="button" routerLink="/resource/{{resource.idResource}}" icon="pi pi-pencil"></button>
</td>
</tr>
</ng-template>
</p-table>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ApproveResourceComponent } from './approve-resource.component';
describe('ApproveResourceComponent', () => {
let component: ApproveResourceComponent;
let fixture: ComponentFixture<ApproveResourceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ApproveResourceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ApproveResourceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {TableModule} from 'primeng/table';
import {PageableResource, Resource} from "../../model/resource";
import {ResourceService} from "../../service/resource.service";
@Component({
selector: 'app-approve-resource',
templateUrl: './approve-resource.component.html',
styleUrls: ['./approve-resource.component.css']
})
export class ApproveResourceComponent implements OnInit {
resources: Resource[] = [];
constructor(private resourceService:ResourceService) { }
ngOnInit(): void {
this.resourceService.getResourcesForApproval().subscribe((resources: Resource[]) => {
// @ts-ignore
this.resources = resources});
}
}
<div class="p-fluid grid flex justify-content-center col-12">
<div class="pt-8 w-3" *ngIf="show">
<form [formGroup]="changeForm" #formDir="ngForm">
<div [hidden]="formDir.submitted">
<div class="field">
<span class="p-float-label">
<input type="password" id="password" pInputText formControlName="password">
<label for="password" i18n="@@{new_password}">Enter new password...</label>
</span>
</div>
<div class="field">
<span class="p-float-label">
<input type="password" id="confirmedPassword" pInputText formControlName="confirmedPassword">
<label for="confirmedPassword" i18n="@@{retype_pass}">Retype password again...</label>
</span>
</div>
<div *ngIf="changeForm.errors?.['notSame'] && (changeForm.touched || changeForm.dirty)" class="text-xs p-error " i18n="@@{pass_error}">
Passwords does not match!
</div>
</div>
<div class="field flex justify-content-center pt-4">
<p-button class="pr-4" type="submit" [disabled]="changeForm.invalid" (click)="changePassword()" i18n="@@{change_password}">Change password</p-button>
</div>
</form>
</div>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangePasswordComponent } from './change-password.component';
describe('ChangePasswordComponent', () => {
let component: ChangePasswordComponent;
let fixture: ComponentFixture<ChangePasswordComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ChangePasswordComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ChangePasswordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute, Router} from "@angular/router";
import {LoginService} from "../../service/login.service";
import {MessageService} from "primeng/api";
import {FormControl, FormGroup} from "@angular/forms";
import {checkValidator} from "../../validator/check-password.directive";
@Component({
selector: 'app-change-password',
templateUrl: './change-password.component.html',
styleUrls: ['./change-password.component.css']
})
export class ChangePasswordComponent implements OnInit {
show:boolean=false;
username?:string;
changeForm!: FormGroup;
constructor(private route: ActivatedRoute,private loginService: LoginService,private messageService: MessageService,private router:Router) {
}
ngOnInit(): void {
this.changeForm = new FormGroup({
password: new FormControl(),
confirmedPassword: new FormControl()
},{ validators: checkValidator })
var token = this.route.snapshot.paramMap.get('token');
if(token != undefined){
this.loginService.checkToken(token).subscribe(data =>{
this.show = true;
this.username = data.username;
},
error => {
this.show = false;
this.messageService.add({severity: 'error', detail: 'This change password request has expired! Try again!'});
this.router.navigate(['/login']);
})
}
}
changePassword(){
// @ts-ignore
this.loginService.changePassword(this.username,this.password.value).subscribe(data =>{
this.router.navigate(['/login']);
},error => {
this.messageService.add({severity: 'error', detail: 'Something went wrong...'});
}
)
}
get password() { return this.changeForm.get('password')!; }
get confirmedPassword() { return this.changeForm.get('confirmedPassword')!; }
}
.footer{
background-image: url('/toolbox/assets/footer.jpg');
height: 415px;
background-position: center;
padding: 0;
}
.footer-ribbon{
background-color: #00662e;
color:white;
}
.footer-white{
background-color: white;
align-self: center;
width: 100%;
}
<footer class="footer flex align-items-end col-12">
<div class="footer-white flex flex-row">
<div>
<img src="">
</div>
<div>
<img src="">
</div>
<div>
<img src="">
</div>
</div>
<div class="footer-ribbon flex col-12 align-items-center">
<div class="pl-8 pt-2">
<img src="assets/EUFlag.png"/>
</div>
<div>
<p class="pl-5 pt-2 align-self-center" i18n="@@{project_ack}">
This project has received funding from the European Union’s Horizon 2020 research and innovation programme under grant agreement No. 101000339.
</p>
</div>
</div>
</footer>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ FooterComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.css']
})
export class FooterComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
@media only screen and (min-width: 60.0px) {
.flag{
width: 45px;
}
}
:host ::ng-deep .p-dropdown{
border:0px !important;
}
:host ::ng-deep .p-dropdown:not(.p-disabled).p-focus {
box-shadow: none;
}
:host ::ng-deep .p-menubar-end{
align-self: auto !important;
}
:host ::ng-deep .p-dropdown-trigger {
display: none !important;
}
<p-menubar [model]="items" [ngStyle]="{'display': hideheader}">
<ng-template pTemplate="start">
<a href="http://ipmworks.net">
<img src="assets/logo.jpg" height="97" class="pl-6 mr-8" alt="brand logo">
</a>
</ng-template>
<ng-template pTemplate="end">
<p-dropdown [options]="languages" [(ngModel)]="selectedLanguage" optionLabel="name" class="col-4" (onChange)="reloadTranslate($event)" appendTo="body">
<ng-template pTemplate="selectedItem">
<div *ngIf="selectedLanguage">
<img src="assets/languages/{{locale}}.png" class="flag pr-2"/>
</div>
</ng-template>
<ng-template let-language pTemplate="item">
<div class="flex">
<img src="assets/languages/{{language.code}}.png" class="flag pr-2"/>
<div>{{language.name}}</div>
</div>
</ng-template>
</p-dropdown>
</ng-template>
</p-menubar>
<p-toast></p-toast>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderMenuComponent } from './header-menu.component';
describe('HeaderMenuComponent', () => {
let component: HeaderMenuComponent;
let fixture: ComponentFixture<HeaderMenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderMenuComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HeaderMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {MenuItem, MessageService, PrimeNGConfig} from "primeng/api";
import {Observable, Subject} from "rxjs";
import {User} from "../../model/user";
import {LogoutAction, UserState} from "../../states/user.state";
import {Select, Store} from "@ngxs/store";
import {ActivatedRoute, NavigationEnd, Router, RouterEvent} from "@angular/router";
import {filter, takeUntil} from "rxjs/operators";
import { LOCALE_ID, Inject } from '@angular/core';
export class LanguageTrans{
public name?: string;
public code?:string;
}
@Component({
selector: 'app-header-menu',
templateUrl: './header-menu.component.html',
styleUrls: ['./header-menu.component.css']
})
export class HeaderMenuComponent implements OnInit,OnDestroy{
items: MenuItem[] = [] ;
public destroyed = new Subject<any>();
// @ts-ignore
public loggeduser: Observable<User> = this.store.select(UserState.userDetails);
logged?:boolean
selectedLanguage?:LanguageTrans;
languages :LanguageTrans[] = [];
@Input() hideheader?:string|null;
constructor(private route:ActivatedRoute,private primengConfig: PrimeNGConfig, private store: Store, private router: Router,private messageService: MessageService, @Inject(LOCALE_ID) public locale: string) {}
initialise() {
this.logged = this.isLogged();
// @ts-ignore
this.items = [
{
label: $localize `Home`,
routerLink:'/home',
styleClass:"pr-2 ",
},
{
label: $localize `Add resource`,
url:'#/resource/add',
styleClass:"pr-2",
target:"_blank"
},
{
label: $localize `Approve`,
routerLink:'/resource/approve',
styleClass:"pr-2",
visible:this.isUserAdmin()
},
{
label:$localize `IPMWORKS Project`,
url:'http://ipmworks.net',
styleClass:"pr-2",
target:"_blank"
},
{
label: $localize`Help and Support`,
routerLink:'/help',
styleClass:"pr-2",
target:"_blank"
},
{
label: $localize `Logout`,
command:(event) => {this.logout()},
styleClass:"pr-2",
visible:this.logged
},
{
label: $localize `Login`,
routerLink:'/login',
styleClass:"pr-2",
visible:!this.logged
}
]
}
ngOnInit(): void {
this.languages = [
{ name: $localize `Dutch`, code: "nl" },
{ name: $localize `Danish`, code: "da" },
{ name: $localize `English`, "code": "en" },
{ name: $localize `Finnish`, code: "fi" },
{ name: $localize `French`, code: "fr" },
{ name: $localize `German`, code: "de" },
{ name: $localize `Greek`, code: "el" },
{ name: $localize `Italian`, code: "it" },
{ name: $localize `Polish`, code: "pl" },
{ name: $localize `Portuguese`, code: "pt" },
{ name: $localize `Serbian`, "code": "sr" },
{ name: $localize `Slovenian`, code: "sl" },
{ name: $localize `Spanish`, code: "es" }
]
this.findSelectedLanguage();
this.router.events.pipe(filter((event) => event instanceof NavigationEnd),
takeUntil(this.destroyed)
).subscribe(() => {
this.router.routeReuseStrategy.shouldReuseRoute = function() { return false; };
this.initialise();
});
}
public findSelectedLanguage(){
console.log("locale:"+this.locale)
var selected = this.languages.filter(l=>l.code===this.locale);
if(selected.length !== 0){
this.selectedLanguage =this.languages.filter(l=>l.code===this.locale)[0];
}
}
public isUserAdmin() {
var admin = false;
this.loggeduser?.subscribe(user => {
if(user == undefined || user.token == undefined){
admin=false;
}else{
// @ts-ignore
admin=user?.roles.includes("ADMIN")
}
});
return admin;
}
public isLogged() {
var logged = false;
this.loggeduser?.subscribe(user => {
if(user == undefined || user.token == undefined){
logged=false;
}else{
logged = true;
}
});
return logged;
}
ngOnDestroy(): void {
this.destroyed.next();
this.destroyed.complete();
}
public logout(): void {
this.store.dispatch(new LogoutAction());
this.router.navigate(['/home']);
this.messageService.add({severity:"success", detail:"Goodbye!"})
}
public reloadTranslate(event:any):void{
window.location.href= "/toolbox/"+this.selectedLanguage?.code;
}
}
<div class="card pt-6 pl-6 pr-6">
<p-panel class="col-12 p-d-block" header="Useful material" *ngIf="(helpFilesDesc != undefined)">
<div *ngFor="let file of helpFilesDesc" class="flex">
<div class="col-5 mb-2 md:col-11 md:mb-0">
<i class="pi pi-file p-mr-2" style="font-size: 2rem"></i>
<button pButton type="button" label="{{file.description}}" class="p-button-link button-link" (click)="downloadFile(file.fileIdentifier)"></button>
</div>
<div class="col-1 md:col-1" *ngIf="(loggeduser| async)?.roles?.includes('ADMIN')">
<button pButton pRipple type="submit" label="Delete" icon="pi pi-trash" (click)="delete(file.fileIdentifier)" [loading]="false" class="p-button-danger"></button>
</div>
</div>
</p-panel>
<div class="col-12 pt-3 flex justify-content-center">
<div class="col-1 md:col-1" *ngIf="(loggeduser| async)?.roles?.includes('ADMIN')">
<p-button label="Add new file" [loading]="false" type="submit" (click)="displayFileModal=true"></p-button>
</div>
</div>
</div>
<p-toast position="bottom-center" key="bc"></p-toast>
<p-confirmDialog></p-confirmDialog>
<p-dialog id="addFileModal" name="addFileModal" header="Add new file" [style]="{width: '50vw'}" [(visible)]="displayFileModal">
<div class="field">
<label>File title</label>
<input id="fileDescription" type="text" pInputText [(ngModel)]="newFile.description" />
</div>
<div class="field">
<label>Select file</label>
<p-fileUpload name="help" [auto]=true (onRemove) = removeFile() [maxFileSize]=5242880 [auto]=true [showUploadButton]=false [customUpload]=true (uploadHandler)="fileUploader($event)"></p-fileUpload>
</div>
<div class="field button-center">
<button pButton type="button" class="p-button-help mr-3" label="Cancel" (click)="displayFileModal=false" ></button>
<button pButton type="button" label="Submit" (click)="addFile()" [disabled]="isEmpty()"></button>
</div>
</p-dialog>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HelpComponent } from './help.component';
describe('HelpComponent', () => {
let component: HelpComponent;
let fixture: ComponentFixture<HelpComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HelpComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HelpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {ResourceService} from "../../service/resource.service";
import {FileDesc} from "../../model/resource";
import {Observable} from "rxjs";
import {User} from "../../model/user";
import {UserState} from "../../states/user.state";
import {Store} from "@ngxs/store";
import {ConfirmationService, MessageService} from "primeng/api";
import {Router} from "@angular/router";
@Component({
selector: 'app-help',
templateUrl: './help.component.html',
styleUrls: ['./help.component.css']
})
export class HelpComponent implements OnInit {
helpFilesDesc: FileDesc[] = [];
url: any;
newFile: FileDesc = {};
displayFileModal = false;
fileContent: any;
// @ts-ignore
public loggeduser: Observable<User> = this.store.select(UserState.userDetails);
constructor(private resourceService: ResourceService, private router: Router, private store: Store, private messageService: MessageService, private confirmationService: ConfirmationService) {
}
ngOnInit(): void {
this.resourceService.getAllHelpFilesDesc().subscribe((files: FileDesc[]) => {
this.helpFilesDesc = files;
});
}
downloadFile(fileId: any): void {
this.resourceService.downloadFileById(fileId).subscribe((file: any) => {
const blob = new Blob([file], {type: file.type});
this.url = window.URL.createObjectURL(blob);
/*const link = document.createElement('a');
link.href = this.url;
link.target="_blank";
link.download = fileName;*/
window.open(this.url, "_blank")
}, error => {
console.log(error)
});
}
delete(fileId?: String) {
this.confirmationService.confirm({
message: 'Are you sure that you want to delete resource?',
icon: 'pi pi-exclamation-triangle',
accept: () => {
// @ts-ignore
this.resourceService.deleteFile(fileId).subscribe(r => {
this.messageService.add({severity: 'success', detail: 'Resource has been deleted.'});
this.router.navigate(['/help']);
}, error => {
this.messageService.add({severity: 'error', detail: 'Some error occurred!'});
}
);
}
});
}
public fileUploader(event: any) {
this.fileContent = event.files[0];
}
public addFile() {
// @ts-ignore
this.resourceService.addFile(this.fileContent, this.newFile.description, "help").subscribe(data => {
this.messageService.add({severity: 'success', detail: 'File is added!'});
this.router.navigate(['/help']);
}, error => {
this.messageService.add({severity: 'error', detail: 'Some error occurred!'})
});
}
public removeFile(){
this.fileContent=undefined;
}
public isEmpty(){
if (this.newFile.description && this.fileContent){
return false;
}else{
return true;
}
}
}
.imageSizeHome{
max-width: 100%;
max-height: 120px;
display: block;
margin-left: auto;
margin-right: auto;
}
.card {
padding: 2rem;
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
border-radius: 4px;
margin-bottom: 2rem;
justify-content: space-between;
}
.button-center{
display: flex;
justify-content: space-around;
}
.button-right{
display: flex;
justify-content: flex-end;
}
.flag{
width:25px;
height:25px;
position:absolute;
left:10px;
}
.sector{
width:25px;
height:25px;
}
.horizontal{
padding:5px;
display: inline-block;
}
.hoverResource:hover{
background: rgba(63, 81, 181, 0.04);
cursor: pointer;
}
<p-messages severity="info" [enableService]="false" *ngIf="!close">
<ng-template pTemplate>
<span class="p-message-icon pi pi-info-circle"></span>
<span class="p-message-summary" i18n>Disclaimer</span>
<span class="p-message-detail" i18n>This IPMWORKS Resource Toolbox is a repository for IPM resource developed by the EU IPMWORKS project (101000339). The cooperating partners have no economic responsibility whatsoever for losses due to using this service. In continuing to use the IPMWorks Resource Toolbox you agree to <a [ngStyle]="{color:'var(--surface-0)'}" href="https://ipmworks.net/ipmworks/file/get/63b6b7c0d7f8f46f5b2a28b3">Part 1</a> of the Toolbox Terms and Conditions.</span>
<button type="button" (click)="closeDisclaimer()" class="p-message-close p-link"><i class=" p-message-close-icon pi pi-times"></i></button>
</ng-template>
</p-messages>
<div class="grid pt-6 pl-6 flex-wrap">
<div class="md:col-3">
<form #searchForm="ngForm" (ngSubmit)="searchResource()">
<p class="text-xl" i18n="@@{sectors}">Sectors</p>
<p-multiSelect i18n-defaultLabel="@@{select_sector}" defaultLabel="Select sector" [options]="sectors" name="sector" [(ngModel)]="searchModel.sector" optionLabel="{{locale === 'en' ? 'name' : 'name_'+locale}}">
</p-multiSelect>
<p class="text-xl" i18n="@@{country_origin}">Country of origin</p>
<p-multiSelect i18n-defaultLabel="@@{select_region}" defaultLabel="Select region" [options]="regions" name="region" [(ngModel)]="searchModel.region" optionLabel="{{locale === 'en' ? 'name' : 'name_'+locale}}"></p-multiSelect>
<!-- <div class="field">
<p class="text-xl">Pest type</p>
<p-dropdown autoWidth="false" [style]="{'width':'100%'}" placeholder="Select pest type" [options]="pestType" name="pestType"[(ngModel)]="searchModel.pestType" optionLabel="name" ></p-dropdown>
</div>-->
<p class="text-xl" i18n="@@{project}">Project</p>
<p-dropdown autoWidth="false" [style]="{'width':'100%'}" i18n-placeholder="@@{select_project}" placeholder="Select project" [(ngModel)]="searchModel.project" [options]="projects" name="project" optionLabel="name" ></p-dropdown>
<p class="text-xl" i18n="@@{resource_types}">Resource types</p>
<p-multiSelect i18n-placeholder="@@{select_resource_type}" placeholder="Select resource type" name="resourceType" [(ngModel)]="searchModel.resourceType" [options]="contentTypes" optionLabel="{{locale === 'en' ? 'name' : 'name_'+locale}}">
</p-multiSelect>
<div class="button-right pt-3" *ngIf="!more">
<button pButton type="button" i18n-pTooltip="@@{more_filters}" pTooltip="More filters..." icon="pi pi-plus" class="p-button-text p-button-sm p-button-rounded p-button-outlined " (click)="toggleFilters()"></button>
</div>
<!-- <div class="button-right pt-3" *ngIf="more">
<button pButton type="button" icon="pi pi-minus" i18n-pTooltip="@@{less_filters}" pTooltip="Less filters..."
class="p-button-text p-button-sm p-button-rounded p-button-outlined " (click)="toggleFilters()"></button>
</div>-->
<div *ngIf="more">
<p class="text-xl" i18n="@@{search_title}">Resource title</p>
<input id="title" name="title" type="text" pInputText [(ngModel)]="searchModel.title">
<p class="text-xl" i18n="@@{specific_pest}">Specific pest</p>
<p-multiSelect i18n-defaultLabel="@@{select_pests}" defaultLabel="Select pests" [options]="pests" optionLabel="commonName" filterBy="commonName,latinName,eppo" [(ngModel)]="searchModel.pest" name="pest">
<ng-template let-pest pTemplate="item">
<div>{{pest.commonName}} <i *ngIf="pest.latinName !=undefined">
({{pest.latinName}}, {{pest.eppo}})</i></div>
</ng-template>
</p-multiSelect>
<p class="text-xl" i18n="@specific_crop">Specific crop</p>
<p-multiSelect i18n-defaultLabel="@@{select_crops}" defaultLabel="Select crops" [options]="crops" optionLabel="commonName" filterBy="commonName,latinName,eppo" [(ngModel)]="searchModel.crop" name="crop">
<ng-template let-crop pTemplate="item">
<div>{{crop.commonName}} <i *ngIf="crop.latinName !=undefined">
({{crop.latinName}}, {{crop.eppo}})</i></div>
</ng-template>
</p-multiSelect>
<p class="text-xl" i18n="@@{languages}">Resource language</p>
<p-multiSelect i18n-defaultLabel="@@{select_language}" defaultLabel="Select language" [selectionLimit]="1" [options]="languages" name="language" [(ngModel)]="searchModel.language" optionLabel="{{locale === 'en' ? 'name' : 'name_'+locale}}"></p-multiSelect>
<!--<h3>Arbitrary text search</h3>
<input type="text" pInputText [(ngModel)]="searchTerm"/>-->
</div>
<div class="flex justify-content-center">
<div class="pt-3 pr-3">
<p-button type="submit" i18n-label="@@{search}" label="Search" [disabled] = "isEmptySearch()" icon="pi pi-search" [loading]="serachRunning"></p-button>
</div>
<div class="pt-3 pl-3">
<p-button type="submit" i18n-label="@@{reset}" label="Reset" icon="pi pi-refresh" (click)="resetSearch()" [loading]="false"></p-button>
</div>
</div>
</form>
</div>
<div class="md:col-9">
<p-card>
<p-dataView [value]="resources" [lazy]="true" (onLazyLoad)="onPageChange($event)" (onPage)="onPageChange($event)" [totalRecords]="totalElements" [paginator]="true" [rowsPerPageOptions]=[12,24,48,96] [rows]="12" layout="grid">
<ng-template let-resource pTemplate="gridItem">
<div class="card col-12 md:col-4 flex flex-column p-3 mt-3 hoverResource">
<div class="pb-1 align-self-end">
<p-tag *ngIf="resource.resourceType" [rounded]=true value="{{getResourceType(resource)}}">
</p-tag>
<p-avatar *ngIf="resource.external" shape="circle" icon="pi pi-external-link"></p-avatar>
</div>
<div routerLink="/resource/{{resource.idResource}}">
<img [src]="imagePath+'/'+resource.idResource" class="imageSizeHome"/>
<div class="flex flex-column">
<div class="pt-1 align-self-end">
<p-tag [rounded]=true severity="success" icon="pi pi-globe" value="{{getRegion(resource)}}">
</p-tag>
<p-tag *ngIf="getProject(resource)!== null" [rounded]=true severity="warning" icon="pi pi-book" value="{{getProject(resource)}}">
</p-tag>
</div>
</div>
<p class="text-center">{{resource.resourceName}}</p>
</div>
<div class="align-self-center">
<a routerLink="/resource/{{resource.idResource}}/{{getLang()}}" target="_blank" [style]="'text-decoration:unset'">
<button pButton type="button" icon="pi pi-forward" class="p-button-outlined mr-4" i18n-pTooltip="@@{find_more}" pTooltip="See more..."></button>
</a>
<!--<button pButton type="button" icon="pi pi-share-alt" class="p-button-outlined" pTooltip="Copy link for sharing" ngxClipboard [cbContent]="getPermLink(resource.idResource)" (click)=linkCopied(getPermLink(resource.idResource))></button>-->
</div>
</div>
</ng-template>
<ng-template let-item pTemplate="paginatordropdownitem" i18n="@@{per_page}">
{{item.value}} - per page
</ng-template>
</p-dataView>
<div class="justify-content-center flex pt-3" *ngIf="totalElements>0" i18n="@@{number_of_results}">Number of results: {{totalElements}}</div>
</p-card>
</div>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core';
import {
Crop,
Keyword,
Language,
PageableResource,
Pest,
Project,
Region,
Resource,
ResourceType,
Sector
} from "../../model/resource";
import {ResourceService} from "../../service/resource.service";
import {Message, MessageService} from "primeng/api";
import {environment} from "../../../environments/environment";
import {SearchModel} from "../../model/search-model";
import {NavigationEnd, Router} from "@angular/router";
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit{
resources: Resource[] = [];
totalResources: Resource[] = [];
search:boolean=false;
pageableResponse?: PageableResource;
pageSize = 10;
totalElements: number = 0;
sectors: Sector[] = [];
regions: Region[] = [];
projects: Project[] = [];
crops:Crop[] = [];
pests: Pest[] = [];
pestType: object[] = [{name:"weeds"}, {name:"disease"},{name:"invertebrate"}];
contentTypes: ResourceType[] = [];
languages: Language[] = [];
keywords: Keyword[] = [];
disclaimer: Message[] = [];
selectedSectors: Sector[] = [];
selectedRegions: Region[] = [];
selectedTypes: ResourceType[] = [];
selectedLanguages: Language[] = [];
selectedProjects: Project[] = [];
selectedCrops: Crop[] = [];
selectedPests:Pest[] = [];
selectedPestType: string | undefined;
serachRunning= false;
searchTerm: string = "";
more= true;
close = false;
imagePath = environment.baseUrl+"/resource/image";
imagePathRaw = environment.baseUrl+"/resource/image/raw";
searchModel:SearchModel = {};
loading:boolean = true;
counter:number = 0;
constructor(private resourceService:ResourceService, private router:Router,private messageService: MessageService, @Inject(LOCALE_ID) public locale: string) {
this.pageableResponse = {totalElements:0}
}
ngOnInit(): void {
this.resourceService.getAllProjects().subscribe((projects: Project[]) => {
this.projects = projects;});
this.resourceService.getAllSectors().subscribe((sectors: Sector[]) => {
this.sectors = sectors;});
this.resourceService.getAllRegions().subscribe((regions: Region[]) => {
this.regions = regions;});
this.resourceService.getAllResourceTypes().subscribe((contentTypes: ResourceType[]) => {
this.contentTypes = contentTypes;});
this.resourceService.getAllLanguages().subscribe((languages: Language[]) => {
this.languages = languages;});
this.resourceService.getAllPests().subscribe((pests: Pest[]) => {
this.pests = pests;});
this.resourceService.getAllCrops().subscribe((crops: Crop[]) => {
this.crops = crops;});
this.resourceService.getAllResources().subscribe((resources: PageableResource) => {
// @ts-ignore
this.resources = resources.content;
// @ts-ignore
this.totalElements = resources.totalElements;
this.search = false;
this.serachRunning=false;
});
}
public closeDisclaimer(){
this.close = true;
}
public toggleFilters(){
//kada se zatvore dodatni filteri isprazniti ako je nesto bilo selektovano u tim filterima
this.more = !this.more;
}
public searchResource(){
// @ts-ignore
this.serachRunning = true;
this.resourceService.searchResource(this.searchModel).subscribe((resources: Resource[]) => {
this.resources = resources;
this.totalResources = resources;
this.totalElements = resources.length
this.search = true;
this.serachRunning=false;
},error => {
this.messageService.add({severity: 'error', detail: 'Some error occurred!'})
this.serachRunning=false;
});
}
onPageChange(event:any) {
if (this.search === false){
var pNum = event.first / event.rows
this.resourceService.getAllResources(pNum, event.rows,)
.subscribe(ret => this.populate(ret));
}else {
this.resources = this.totalResources.slice(event.first, this.totalResources.length)
}
}
populate(ret: PageableResource) {
this.pageableResponse = ret;
// @ts-ignore
this.resources = this.pageableResponse?.content;
// @ts-ignore
this.totalElements = this.pageableResponse.totalElements;
}
public resetSearch(){
this.router.navigate(['home']);
/*this.serachRunning=true;
this.resourceService.getAllResources().subscribe((resources: PageableResource) => {
// @ts-ignore
this.resources = resources.content;
// @ts-ignore
this.totalElements = resources.totalElements;
this.searchModel = {};
this.search = false;
this.serachRunning=false;*/
//});
}
isEmptySearch(){
return JSON.stringify(this.searchModel) === '{}';
}
public getRegion(resource:Resource){
if (resource.regions !== undefined && resource.regions?.length >1){
return "EU"
}else if (resource.regions?.length ===1){
// if(this.locale !== 'en'){
// return Reflect.get(resource.regions[0],'name_'+this.locale)
// }else{
return resource.regions[0].name;
//}
}else{
return "EU"
}
}
public getResourceType(resource:Resource){
// if(this.locale !== 'en'){
// // @ts-ignore
// return Reflect.get(resource.resourceType,'name_'+this.locale)
// }else{
// @ts-ignore
return resource.resourceType.name;
// }
}
public getProject(resource:Resource){
if (resource.sectors == undefined|| resource.sectors.length==0 ){
return null;
}else{
return resource.project;
}
}
public getLang(){
if (this.searchModel.language !=undefined){
return this.searchModel.language[0].code;
}
return "";
}
}
<div class="p-fluid grid flex justify-content-center col-12">
<div class="pt-8 w-3">
<form [formGroup]="loginForm">
<div class="field">
<span class="p-float-label">
<input type="email" id="username" pInputText formControlName="username">
<label for="username" i18n="@@{username}">Username</label>
</span>
</div>
<div class="field">
<span class="p-float-label">
<input type="password" id="password" pInputText formControlName="password">
<label for="username" i18n="@@{password}">Password</label>
</span>
</div>
<div class="field flex justify-content-center pt-4">
<p-button class="pr-4" type="submit" (click)="signIn()" i18n="@@{login}">Login</p-button>
<p-button i18n-label="@@{forgot_password}" label="Forgot password?" (click)="displayDialog()" styleClass="p-button-link"></p-button>
</div>
</form>
</div>
</div>
<p-dialog [(visible)]="display" [breakpoints]="{'960px': '75vw', '640px': '100vw'}" [style]="{width: '50vw'} ">
<p i18n="@@{change_pass_instr}">If you want to change your password, please enter your e-mail used to login. If we find email in our system, we will send you instructions to follow.</p>
<input type="text" pInputText [(ngModel)]="email">
<div class="pt-4" >
<button type="button" pButton autofocus (click)="sendPasswordChange()" [disabled]="email.length == 0" i18n="@@{send_email}"> Send email</button>
</div>
</p-dialog>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnDestroy, OnInit} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';
import {Actions, ofActionCompleted, Store} from '@ngxs/store';
import {LoginAction, LogoutAction, UserState} from '../../states/user.state';
import {Router} from '@angular/router';
import {MessageService} from "primeng/api";
import {LoginService} from "../../service/login.service";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
public loginForm = new FormGroup({
username: new FormControl(),
password: new FormControl(),
});
display:boolean = false;
email:string = "";
// tslint:disable-next-line:max-line-length
constructor(private action: Actions, private router: Router, private messageService: MessageService, private store: Store, private loginService:LoginService) { }
ngOnInit() {
this.action.pipe(ofActionCompleted(LoginAction)).subscribe(res => {
if (res.result.error) {
this.messageService.add({severity: 'error', detail: 'General server error during login!Try latter'});
} else {
const user = this.store.selectSnapshot(UserState.userDetails);
if (user == null) {
this.messageService.add({severity: 'error', detail: 'Wrong username or password!'});
} else {
this.router.navigate(['/home']);
}
}
});
}
signIn() {
const username = this.loginForm.value.username.trim();
const password = this.loginForm.value.password.trim();
this.store.dispatch(new LoginAction(username, password));
}
displayDialog(){
this.display = true;
}
sendPasswordChange(){
// @ts-ignore
this.loginService.startChangingPassword(this.email).subscribe(d =>{
})
this.display = false;
}
}
.imageSize{
max-height: 400px;
max-width: 100%;
}
.horizontal{
padding:5px;
display: inline-block;
}
.sector{
width:25px;
height:25px;
}
.button-center{
display: flex;
justify-content: space-around;
}
.button-link{
background-color: unset !important;
}
.overflow-text{
overflow-wrap: anywhere;
}
.link-wrap {
white-space: pre-wrap; /* CSS3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
.keep-format{
white-space: pre-wrap;
}
<div class="grid pt-6 pl-6 flex flex-wrap">
<div class="flex col-12 justify-content-center">
<p class="text-3xl">{{resource.resourceName}}
</p>
</div>
<div class="md:col-6 flex flex-column">
<div class="col-12 text-center">
<img [src]="imagePath+'/'+resourceId" class="imageSize" >
</div>
<div class="col-12">
<p-panel class="col-12 inline">
<div class="flex align-items-center"><div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{project}">Project</div><div class="my-0">{{resource.project}}</div></div>
<div class="flex align-items-center"><div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{language}">Resource language</div><div class="my-0">{{resource.language?.name}}</div></div>
<div class="flex align-items-center"><div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{resourceOrigin}">Resource origin</div> <div class="my-0 overflow-text"><a href="{{resource.resourceOrigin}}" target="_blank">{{resource.resourceOrigin}}</a></div></div>
<div class="flex align-items-center"> <div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{resourceType}">Resource type</div><div class="my-0">{{resource.resourceType?.name}}</div></div>
<div class="flex align-items-center"><div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{contactInstitution}">Organization name</div><div class="my-0">{{resource.contactInstitution}}</div></div>
<div class="flex align-items-center"><div class="col-3 inline my-0"[ngStyle]="{color:'var(--surface-500)'}" i18n="@@{contactEmail}">Contact email</div><div class="my-0"></div>{{resource.contactEmail}}</div>
<div class="flex align-items-center"><div class="col-3 inline my-0"[ngStyle]="{color:'var(--surface-500)'}" i18n="@@{contactPhone}">Contact phone</div><div class="my-0">{{resource.contactPhone}}</div></div>
<div class="flex align-items-center"><div class="col-3 inline my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{citation}">Citation (DOI)</div><div class="overflow-text my-0">{{resource.citation}}</div></div>
</p-panel>
</div>
</div>
<div class="md:col-6 flex flex-column">
<p-panel i18n-header="@@{short_summary}" header="Short summary" class="col-12 p-d-block">
<div *ngIf="resource.descriptionNative == undefined" class="flex keep-format" [innerHTML]="resource.description"></div>
<p-tabView *ngIf="resource.descriptionNative != undefined">
<p-tabPanel i18n-header="@@{tab_english}" header="English" >
<div class="flex keep-format"> {{resource.description}}</div>
</p-tabPanel>
<p-tabPanel i18n-header="@@{tab_native}}" header="Native">
<div class="flex keep-format"> {{resource.descriptionNative}}</div>
</p-tabPanel>
</p-tabView>
<div class="flex mt-5">
<div class="inline col-3 my-0"[ngStyle]="{color:'var(--surface-500)'}" i18n="@@{sectors}">Sectors</div>
<div class="inline my-0" *ngFor="let sector of resource.sectors">
<img class="sector mx-2" [src]="'assets/sectors/'+sector.sectorIcon" pTooltip="{{sector.name}}">
</div>
</div>
<div class="flex flex-wrap">
<div class="inline col-3 my-0" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{regions}">Regions</div>
<div *ngFor="let region of resource.regions" class="inline my-0">
<p class=" my-0 py-2 mx-2" >{{region.name}}</p>
</div>
</div>
<div class="flex">
<div class="inline col-3" [ngStyle]="{color:'var(--surface-500)'}"i18n="@@{relevant_pests}">Relevant pest(s)</div>
<div class="col-9">
<div *ngFor="let pest of resource.pests" class="inline-flex pr-2 pb-2">
<p-badge severity="danger" value={{pest.commonName}}></p-badge>
</div>
</div>
</div>
<div class="flex">
<div class="inline col-3" [ngStyle]="{color:'var(--surface-500)'}" i18n="@@{relevant_crops}"> Relevant crop(s)</div>
<div class="col-9">
<div class="inline-flex pr-2 pb-2" *ngFor="let crop of resource.crops">
<p-badge severity="success" value={{crop.commonName}}></p-badge>
</div>
</div>
</div>
</p-panel>
<p-panel class="col-12 p-d-block" i18n-header="@@{find_more}" header="Find out more" *ngIf="(resource.files != undefined) || (resource.links != undefined)">
<div *ngFor="let file of resource.files" >
<i class="pi pi-file p-mr-2" style="font-size: 2rem"></i>
<button pButton type="button" label="{{file.description}}" class="p-button-link button-link" (click)="downloadFile(file.description)"></button>
</div>
<div class="mt-2" *ngFor="let link of resource.links" >
<i class="pi pi-link p-mr-2" style="font-size: 2rem"></i>
<a class="link-wrap" href="{{link}}" target="_blank">{{link}}</a>
</div>
</p-panel>
</div>
<div class="col-12 pt-3 flex justify-content-center">
<div class="pr-2">
<p-button i18n-label="@@{back_to_search}" label="Back to search" icon="pi pi-search" routerLink="/home" [loading]="false"></p-button>
<p-button i18n-label="@@{share}" label="Share" icon="pi pi-share-alt" class="ml-3"(click)=linkCopied(getPermLink(resource.idResource)) [loading]="false"></p-button>
<p-button i18n-label="@@{download}" label="Download" icon="pi pi-share-alt" class="ml-3"(click)=download(resource) [loading]="false"></p-button>
</div>
<div class="pr-2" *ngIf="!resource.approved && resource.canEdit && (loggeduser| async)?.roles?.includes('ADMIN')">
<p-button i18n-label="@@{approve}" label="Approve" icon="pi pi-check" (onClick)="approve($event)" [loading]="false"></p-button>
</div>
<div class="pr-2" *ngIf="(loggeduser| async)?.roles?.includes('ADMIN') && resource.canEdit">
<p-button i18n-label="@@{edit}" label="Edit" icon="pi pi-pencil" routerLink="/resource/edit/{{resourceId}}" [loading]="false"></p-button>
</div>
<div *ngIf="(loggeduser| async)?.roles?.includes('ADMIN') && resource.canEdit">
<p-button i18n-label="@@{delete]" label="Delete" icon="pi pi-trash" (onClick)="delete($event)" [loading]="false"></p-button>
</div>
</div>
</div>
<p-toast position="bottom-center" key="bc"></p-toast>
<p-confirmDialog></p-confirmDialog>
<p-dialog id="shareModal" name="shareModal" header="Copy link to share" [(visible)]="displayModal" [style]="{width: '40vw'}">
<div class="field">
<label i18n="@@{link}">Link</label>
<input id="link" type="text" [(ngModel)]="copiedLink" pInputText/>
</div>
<div class="field button-center">
<button pButton type="button" class="p-button-help" label="Ok" (click)="displayModal=false" ></button>
</div>
</p-dialog>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResourceComponent } from './resource.component';
describe('ResourceComponent', () => {
let component: ResourceComponent;
let fixture: ComponentFixture<ResourceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ResourceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResourceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {ResourceService} from "../../service/resource.service";
import {ActivatedRoute, Router} from "@angular/router";
import {environment} from "../../../environments/environment";
import {Resource} from "../../model/resource";
import {ConfirmationService, MessageService} from "primeng/api";
import {Observable} from "rxjs";
import {User} from "../../model/user";
import {UserState} from "../../states/user.state";
import {Store} from "@ngxs/store";
import {HttpResponse} from "@angular/common/http";
@Component({
selector: 'app-resource',
templateUrl: './resource.component.html',
styleUrls: ['./resource.component.css']
})
export class ResourceComponent implements OnInit {
resource: Resource ={};
imagePath = environment.baseUrl+"/resource/image";
filePath = environment.baseUrl+"/resource/file";
url:any;
resourceId = null;
lang = null;
displayModal=false;
copiedLink="";
// @ts-ignore
public loggeduser: Observable<User> = this.store.select(UserState.userDetails);
constructor(private resourceService:ResourceService, private route: ActivatedRoute,
private router: Router, private store:Store, private messageService:MessageService, private confirmationService: ConfirmationService) { }
ngOnInit(): void {
// @ts-ignore
this.resourceId = this.route.snapshot.paramMap.get('id');
// @ts-ignore
this.lang = this.route.snapshot.paramMap.get('lang');
// @ts-ignore
this.resourceService.getResource(this.resourceId,this.lang).subscribe(data =>{
this.resource =data;
});
}
downloadFile(fileName: any): void {
this.resourceService.downloadFile(fileName).subscribe((file: any) => {
const blob = new Blob([file], {type: file.type});
this.url = window.URL.createObjectURL(blob);
/*const link = document.createElement('a');
link.href = this.url;
link.target="_blank";
link.download = fileName;*/
window.open(this.url,"_blank")
}, error => {
console.log(error)
});
}
approve(event: Event) {
this.confirmationService.confirm({
message: 'Are you sure that you want to approve resource?',
icon: 'pi pi-exclamation-triangle',
accept: () => {
// @ts-ignore
this.resourceService.approveResource(this.resourceId).subscribe(r =>{
this.messageService.add({severity:'success', detail:'Resource has been approved.'});
this.router.navigate(['/home'])
},error =>{
this.messageService.add({severity:'error', detail:'Some error occured!'});
}
);
}
});
}
delete(event: Event) {
this.confirmationService.confirm({
message: 'Are you sure that you want to delete resource?',
icon: 'pi pi-exclamation-triangle',
accept: () => {
// @ts-ignore
this.resourceService.deleteResource(this.resourceId).subscribe(r =>{
this.messageService.add({severity:'success', detail:'Resource has been deleted.'});
this.router.navigate(['/home'])
},(error: { status: any; }) =>{
this.messageService.add({severity:'error', detail:'Some error occurred!'});
}
);
}
});
}
showInfo() {
this.messageService.add({severity:'info', summary: 'Info', detail: 'This feature has not been implemented yet!'});
}
getPermLink(resourceId: string|undefined) {
if(resourceId != undefined){
return location.origin + '/toolbox/#/resource/' + resourceId;
}else{
return location.origin + '/toolbox/#/home';
}
}
linkCopied(link: string) {
this.copiedLink=link;
this.displayModal=true;
}
download(resource:Resource) {
this.resourceService.download(resource).subscribe({
next: (data) => {
// @ts-ignore
if (resource.resourceName ==undefined){
resource.resourceName = "unknown"
}
this.downloadPDF(data as unknown as HttpResponse<any>, resource.resourceName)
},
error: () => {
this.messageService.add({severity:'error', detail:'Some error occurred!'});
}
});
}
private downloadPDF(resp: HttpResponse<any>,fileName:string) {
const content = resp.headers.get('Content-Type');
const blob = new Blob([resp.body], { type: resp.body.type });
const anchor = document.createElement('a');
anchor.href = window.URL.createObjectURL(blob);
anchor.download = fileName +'.pdf';
anchor.target = '_blank';
anchor.dataset.downloadurl = ['text/plain', anchor.download, anchor.href].join(':');
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
}
import { CustomRouteReuseStrategy } from './custom-route-reuse-strategy';
describe('CustomRouteReuseStrategy', () => {
it('should create an instance', () => {
expect(new CustomRouteReuseStrategy()).toBeTruthy();
});
});
import {ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy} from "@angular/router";
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
private storedRoutes = new Map<string, DetachedRouteHandle>();
shouldDetach(route: ActivatedRouteSnapshot): boolean {
// @ts-ignore
return route.routeConfig.path === 'home';
}
store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
// @ts-ignore
this.storedRoutes.set(route.routeConfig.path, handle);
}
shouldAttach(route: ActivatedRouteSnapshot): boolean {
// @ts-ignore
return !!route.routeConfig && !!this.storedRoutes.get(route.routeConfig.path);
}
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {
// @ts-ignore
return this.storedRoutes.get(route.routeConfig.path);
}
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
return future.routeConfig === curr.routeConfig;
}
}
import { TestBed } from '@angular/core/testing';
import { AuthGuard } from './auth.guard';
describe('AuthGuard', () => {
let guard: AuthGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(AuthGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree} from '@angular/router';
import { Observable } from 'rxjs';
import {Store} from "@ngxs/store";
import {UserState} from "../states/user.state";
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router, private store: Store) {
}
// @ts-ignore
canActivate(){
const token = this.store.selectSnapshot(UserState.getToken);
const userroles = this.store.selectSnapshot(UserState.getRoles);
if (token == null) {
this.router.navigate(['/login']);
}
else if (userroles?.includes('ADMIN')) {
return true;
}else {
this.router.navigate(['/home']);
}
}
}
import { TestBed } from '@angular/core/testing';
import { AuthInterceptor } from './auth.interceptor';
describe('AuthInterceptor', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [
AuthInterceptor
]
}));
it('should be created', () => {
const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor);
expect(interceptor).toBeTruthy();
});
});
import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Store} from "@ngxs/store";
import {Observable} from "rxjs";
import {UserState} from "../states/user.state";
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private store: Store) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.store.selectSnapshot(UserState.getToken);
if (token != null) {
request = request.clone({
setHeaders: {
Authorization: 'Bearer ' + token,
}
});
}
return next.handle(request);
}
}
declare module "*.json" {
const value: any;
export default value;
}
import { Resource } from './resource';
describe('Resource', () => {
it('should create an instance', () => {
expect(new Resource()).toBeTruthy();
});
});
export interface Resource {
idResource?: string;
resourceName?: string;
description?: string;
descriptionNative?:string;
links?:string[];
files?:any[];
resourceOrigin?: string;
resourceType?: ResourceType;
sectors?: Sector[];
regions?: Region[];
crops?:Crop[];
pests?:Pest[];
language?: Language;
project?: Project;
projectWeb?: string;
projectGrantNo?:string;
contactInstitution?: string;
contactEmail?: string;
contactPhone?: string;
canEdit?:boolean;
citation?:string;
creationDate?:Date;
approved?:boolean;
external?:boolean;
}
export class Crop{
public idCrop?:string;
public commonName?: string;
public latinName?:string;
public eppo?:string;
}
export class Pest{
public idPest?:string;
public commonName?: string;
public latinName?:string;
public eppo?:string;
}
export class ResourceType{
public idResourceType!:string;
public name!: string;
}
export class Sector{
public idSector!: string;
public name!: string;
public name_sr!: string;
public sectorIcon!:string;
}
export class Region{
public idRegion!: string;
public name!: string;
public code!:string;
}
export class Language{
public idLanguage!: string;
public name!: string;
public code!:string;
}
export class Project{
public idProject!: string;
public name!: string;
public description!: string;
}
export class Keyword{
public idKeyword!: string;
public name!: string;
}
export class FileDesc{
public fileIdentifier?: string;
public description?: string;
}
export class PageableResource {
content?: Resource[] = [];
empty?: boolean;
first?: boolean;
last?: boolean;
number?: number = 0;
numberOfElements?: number;
pageable?: any;
size?: number = 0;
sort?: any;
totalElements: number|undefined;
totalPages?: number =0;
}
import { SearchModel } from './search-model';
describe('SearchModel', () => {
it('should create an instance', () => {
expect(new SearchModel()).toBeTruthy();
});
});
import {Crop, Language, Pest, Project, Region, ResourceType, Sector} from "./resource";
export class SearchModel {
sector?:Sector[];
region?: Region[];
pestType?: string ;
project?: Project ;
resourceType?: ResourceType[];
pest?: Pest[];
crop?: Crop[];
language?: Language[];
title?:string;
}
import { User } from './user';
describe('User', () => {
it('should create an instance', () => {
expect(new User()).toBeTruthy();
});
});
export class User {
name?: string;
username?: string;
token?: string;
roles?: string[];
}
import { TestBed } from '@angular/core/testing';
import { LoginService } from './login.service';
describe('LoginService', () => {
let service: LoginService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoginService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {AppConfig} from "../../config/app.config";
import {User} from "../model/user";
@Injectable({
providedIn: 'root'
})
export class LoginService {
constructor(private httpClient: HttpClient) { }
public login(username: string, password: string) {
const data = {username, password};
return this.httpClient.post(`${AppConfig.ApiPaths.login}`, data);
}
public startChangingPassword(username: string) {
return this.httpClient.post(`${AppConfig.ApiPaths.startChangingPassword}`, username);
}
public checkToken(token: string) {
return this.httpClient.get<User>(`${AppConfig.ApiPaths.getUserName}`+"/"+token);
}
public changePassword(username: string,password:string) {
const data = {username, password};
return this.httpClient.post(`${AppConfig.ApiPaths.changePassword}`, data);
}
}
import { TestBed } from '@angular/core/testing';
import { ResourceService } from './resource.service';
describe('ResourceService', () => {
let service: ResourceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ResourceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {AppConfig} from "../../config/app.config";
import {
ResourceType,
Language,
Region,
Resource,
Sector,
Project,
Keyword,
Pest,
Crop,
PageableResource, FileDesc
} from "../model/resource";
import {SearchModel} from "../model/search-model";
@Injectable({
providedIn: 'root'
})
export class ResourceService {
private readonly _httpClient: HttpClient;
public constructor(httpClient: HttpClient) {
this._httpClient = httpClient;
}
public getAllResources(pageNo: number = 0,pageSize: number = 10) {
return this._httpClient.get<PageableResource>(`${AppConfig.ApiPaths.getAllResources}?pageNumber=${pageNo}&pageSize=${pageSize}`);
}
public getResourcesForApproval() {
return this._httpClient.get<Resource []>(`${AppConfig.ApiPaths.getResourcesForApproval}`);
}
public getResource(id:string, lang:string) {
return this._httpClient.get<Resource>(`${AppConfig.ApiPaths.getResource}`+"/"+id+"/"+lang);
}
public hasImage(id:string,type:string) {
return this._httpClient.get<boolean>(`${AppConfig.ApiPaths.hasImage}`+"/"+type+"/"+id);
}
public approveResource(id:string) {
return this._httpClient.get<Resource>(`${AppConfig.ApiPaths.approveResource}`+"/"+id);
}
public deleteResource(id:string) {
return this._httpClient.get<Resource>(`${AppConfig.ApiPaths.deleteResource}`+"/"+id);
}
public deleteFile(id:string) {
return this._httpClient.get<Resource>(`${AppConfig.ApiPaths.deleteFile}`+"/"+id);
}
public downloadFile(name: string) {
const httpOptions = {
responseType: 'blob' as 'json',
};
return this._httpClient.get<Resource>(`${AppConfig.ApiPaths.getFile}`+"/"+name, httpOptions);
}
public downloadFileById(id: string) {
const httpOptions = {
responseType: 'blob' as 'json',
};
return this._httpClient.get<any>(`${AppConfig.ApiPaths.getFileById}`+"/"+id, httpOptions);
}
public saveResource(resource: Resource,edit:boolean) {
resource.creationDate = new Date();
if(!edit){
resource.approved = false;
}
return this._httpClient.post<Resource>(`${AppConfig.ApiPaths.saveResource}`, resource);
}
public searchResource(search: SearchModel) {
return this._httpClient.post<Resource[]>(`${AppConfig.ApiPaths.searchResource}`, search);
}
public addFiles(image:File,logo:File,resourceContent:File[],idResource:any){
const formData: FormData = new FormData();
if(image != undefined) {
formData.append("image", image);
}
if(logo != undefined) {
formData.append("logo", logo);
}
resourceContent.forEach(r =>{
formData.append("files",r);
})
return this._httpClient.post(`${AppConfig.ApiPaths.addFile}`+"/"+idResource,formData)
}
public addFile(file:File,description:string, type:string){
const formData: FormData = new FormData();
if(file != undefined) {
formData.append("file", file);
}
if(description != undefined) {
formData.append("description", description);
}
if(type != undefined) {
formData.append("type", type);
}
return this._httpClient.post(`${AppConfig.ApiPaths.addHelpFile}`,formData)
}
public getAllSectors() {
return this._httpClient.get<Sector[]>(`${AppConfig.ApiPaths.getAllSectors}`);
}
public getAllRegions() {
return this._httpClient.get<Region[]>(`${AppConfig.ApiPaths.getAllRegions}`);
}
public getAllResourceTypes() {
return this._httpClient.get<ResourceType[]>(`${AppConfig.ApiPaths.getAllResourceTypes}`);
}
public getAllLanguages() {
return this._httpClient.get<Language[]>(`${AppConfig.ApiPaths.getAllLanguages}`);
}
public getAllProjects() {
return this._httpClient.get<Project[]>(`${AppConfig.ApiPaths.getAllProjects}`);
}
public getAllPests() {
return this._httpClient.get<Pest[]>(`${AppConfig.ApiPaths.getAllPests}`);
}
public getAllCrops() {
return this._httpClient.get<Crop[]>(`${AppConfig.ApiPaths.getAllCrops}`);
}
public getAllKeywords() {
return this._httpClient.get<Keyword[]>(`${AppConfig.ApiPaths.getAllKeywords}`);
}
public getAllHelpFilesDesc() {
return this._httpClient.get<FileDesc[]>(`${AppConfig.ApiPaths.getAllHelpFilesDesc}`);
}
public download(resource:Resource){
return this._httpClient.post(`${AppConfig.ApiPaths.download}`,resource,{responseType: 'blob' as 'blob', observe: 'response'})
}
}
import { User.State } from './user.state';
describe('User.State', () => {
it('should create an instance', () => {
expect(new User.State()).toBeTruthy();
});
});
import {Action, Selector, State, StateContext} from '@ngxs/store';
import {tap} from 'rxjs/operators';
import {User} from "../model/user";
import {LoginService} from "../service/login.service";
import {Injectable} from "@angular/core";
export class LoginAction {
static readonly type = '[Login Page] getUser';
public username: string;
public password: string;
public constructor(username: string, password: string) {
this.username = username;
this.password = password;
}
}
export class LogoutAction {
static readonly type = '[Auth] Logout';
}
export interface IAuthUser {
user?: User|null;
}
@State<IAuthUser>({
name: 'USER_STATE',
defaults:{user:null}
})
@Injectable()
export class UserState {
@Selector()
public static getRoles(state: IAuthUser) {
if (state.user && state.user.roles) {
return state.user.roles;
}
return null;
}
@Selector()
public static getToken(state: IAuthUser) {
if (state.user && state.user.token) {
return state.user.token;
}
return null;
}
@Selector()
public static userDetails(state: IAuthUser) {
return state.user;
}
constructor(private loginApi: LoginService) {}
@Action(LoginAction)
// tslint:disable-next-line:typedef
public signIn(ctx: StateContext<IAuthUser>, action: LoginAction) {
let userLogged: any;
try {
return this.loginApi.login(action.username, action.password).pipe(tap((value) => {
console.log(value)
userLogged = value;
ctx.patchState({user: userLogged});
}));
} catch (e) {
ctx.patchState({user:null});
return null;
console.log(e);
}
}
@Action([LogoutAction])
public signOut(ctx: StateContext<IAuthUser>) {
ctx.patchState({user: null});
}
}
import { CheckPasswordDirective } from './check-password.directive';
describe('CheckPasswordDirective', () => {
it('should create an instance', () => {
const directive = new CheckPasswordDirective();
expect(directive).toBeTruthy();
});
});
import { Directive } from '@angular/core';
import {AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn} from "@angular/forms";
export const checkValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
var password = control.get('password');
var confirmedPassword = control.get('confirmedPassword');
return password && confirmedPassword && password.value !== confirmedPassword.value ? { notSame: true } : null;
};
@Directive({
selector: '[appCheckPassword]',
providers: [{ provide: NG_VALIDATORS, useExisting: CheckPasswordDirective, multi: true }]
})
export class CheckPasswordDirective implements Validator {
validate(control: AbstractControl): ValidationErrors | null {
return checkValidator(control);
}
}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment