+7
-1
@@ -1,4 +1,4 @@
|
|||||||
HELP.md
|
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
build/
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
@@ -38,3 +38,9 @@ out/
|
|||||||
/gradle/wrapper/gradle-wrapper.jar
|
/gradle/wrapper/gradle-wrapper.jar
|
||||||
/gradle/wrapper/gradle-wrapper.properties
|
/gradle/wrapper/gradle-wrapper.properties
|
||||||
/.gitattributes
|
/.gitattributes
|
||||||
|
/gradle/
|
||||||
|
/src/main/resources/public/
|
||||||
|
/logs/application.log
|
||||||
|
/logs/
|
||||||
|
/.idea/
|
||||||
|
/logs/
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
# How to run the application
|
||||||
|
|
||||||
|
To run the application, follow these steps:
|
||||||
|
|
||||||
|
1. **Build the Project**:
|
||||||
|
- Ensure you have Java 24, Gradle, and Node.js installed.
|
||||||
|
- Run the following command to build the project:
|
||||||
|
```bash
|
||||||
|
./gradlew build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Prepare Frontend Resources**:
|
||||||
|
- Build the Angular frontend by navigating to the `jambotron-ui` directory and running
|
||||||
|
```bash
|
||||||
|
cd jambotron-ui
|
||||||
|
```
|
||||||
|
- next:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
- next:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
- This will generate the frontend files in the `jambotron-ui/dist/jambotron-ui/browser` directory.
|
||||||
|
|
||||||
|
|
||||||
|
3. **Copy Frontend Resources**:
|
||||||
|
- Use the Gradle task to copy the frontend resources to the `jumbotron/src/main/resources/public` directory:
|
||||||
|
```bash
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
- next:
|
||||||
|
```bash
|
||||||
|
./gradlew copyResources
|
||||||
|
```
|
||||||
|
then
|
||||||
|
4. **Create postgres server and database**:
|
||||||
|
- Create a PostgreSQL database
|
||||||
|
- named `jambotronDB` with the
|
||||||
|
- user `admin` and
|
||||||
|
- password `postgrespw`.
|
||||||
|
|
||||||
|
- You can use a PostgreSQL client or run the following command in your terminal:
|
||||||
|
```sql
|
||||||
|
CREATE DATABASE jambotronDB;
|
||||||
|
CREATE USER admin WITH PASSWORD 'postgrespw';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE jambotronDB TO admin;
|
||||||
|
```
|
||||||
|
- If you are using Docker, you can run PostgreSQL using the following command:
|
||||||
|
```bash
|
||||||
|
docker run --name postgresDB -p 5432:5432 -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=postgrespw -e POSTGRES_DB=jambotronDB -d postgres
|
||||||
|
```
|
||||||
|
- if you are wont to use pgadmin
|
||||||
|
```bash
|
||||||
|
docker run --name pgadmin -e "PGADMIN_DEFAULT_EMAIL=name@example.com" -e "PGADMIN_DEFAULT_PASSWORD=admin" -p 5050:80 -d dpage/pgadmin4
|
||||||
|
```
|
||||||
|
then
|
||||||
|
- create a network for the containers to communicate:
|
||||||
|
```bash
|
||||||
|
docker network create --driver bridge pgnetwork
|
||||||
|
```
|
||||||
|
- Show list of networks:
|
||||||
|
```bash
|
||||||
|
docker network ls
|
||||||
|
```
|
||||||
|
- Connect pgadmin to the network:
|
||||||
|
```bash
|
||||||
|
docker network connect pgnetwork pgadmin
|
||||||
|
```
|
||||||
|
- Connect postgresDB to the network (postgresDB must by running):
|
||||||
|
```bash
|
||||||
|
docker network connect pgnetwork postgresDB
|
||||||
|
```
|
||||||
|
- Inspect the network to ensure both containers are connected:
|
||||||
|
```bash
|
||||||
|
docker network inspect pgnetwork
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
5. **Run the Application**:
|
||||||
|
- Start the Spring Boot application:
|
||||||
|
```bash
|
||||||
|
./gradlew bootRun
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Access the Application**:
|
||||||
|
- Open your browser and navigate to `http://localhost:8080`.
|
||||||
|
|
||||||
|
Make sure all dependencies and configurations are correctly set up before running these steps.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# How to run the application in Docker
|
||||||
|
|
||||||
|
To run the application in Docker, follow these steps:
|
||||||
|
|
||||||
|
- build jar file
|
||||||
|
```bash
|
||||||
|
./gradlew bootJar
|
||||||
|
```
|
||||||
|
- Run docker compose to start the application and PostgreSQL:
|
||||||
|
```bash
|
||||||
|
docker-compose -f src/Docker/docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
- For connection from pgadmin use
|
||||||
|
hostename: **host.docker.internal**
|
||||||
|
### Reference Documentation
|
||||||
|
For further reference, please consider the following sections:
|
||||||
|
|
||||||
|
* [Official Gradle documentation](https://docs.gradle.org)
|
||||||
|
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.5.0/gradle-plugin)
|
||||||
|
* [Create an OCI image](https://docs.spring.io/spring-boot/3.5.0/gradle-plugin/packaging-oci-image.html)
|
||||||
|
|
||||||
|
### Additional Links
|
||||||
|
These additional references should also help you:
|
||||||
|
|
||||||
|
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
For docker-compose db connect with pgAdmin used
|
||||||
|
host.docker.internal
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
|
com.jambotronGroup.jambotron: DEBUG
|
||||||
|
org.springframework: WARN
|
||||||
|
pattern:
|
||||||
|
console: "%d{yyyy-MM-dd HH:mm:ss} %-5level - %msg%n"
|
||||||
|
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||||
|
file:
|
||||||
|
name: logs/application.log
|
||||||
|
max-size: 10MB
|
||||||
|
max-history: 30
|
||||||
@@ -2,6 +2,7 @@ plugins {
|
|||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '3.5.0'
|
id 'org.springframework.boot' version '3.5.0'
|
||||||
id 'io.spring.dependency-management' version '1.1.7'
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
id 'org.flywaydb.flyway' version '10.4.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.jambotronGroup'
|
group = 'com.jambotronGroup'
|
||||||
@@ -20,6 +21,7 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter'
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
implementation 'org.projectlombok:lombok:1.18.26'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
|
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
|
||||||
@@ -30,6 +32,64 @@ dependencies {
|
|||||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
|
||||||
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // For JSON serialization/deserialization
|
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // For JSON serialization/deserialization
|
||||||
implementation 'org.postgresql:postgresql:42.6.0'
|
implementation 'org.postgresql:postgresql:42.6.0'
|
||||||
|
implementation 'org.flywaydb:flyway-core'
|
||||||
|
implementation("org.flywaydb:flyway-database-postgresql:10.4.1")
|
||||||
|
|
||||||
|
//implementation 'org.springframework.ai:spring-ai-starter-model-zhipuai'
|
||||||
|
//implementation 'org.springframework.ai:spring-ai-zhipuai-spring-boot-starter'
|
||||||
|
//implementation platform("org.springframework.ai:spring-ai-bom:1.0.0-SNAPSHOT")
|
||||||
|
// Replace the following with the starter dependencies of specific modules you wish to use
|
||||||
|
//implementation 'org.springframework.ai:spring-ai-zhipuai'
|
||||||
|
//implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
//implementation("org.springframework.ai:spring-ai-spring-boot-autoconfigure:1.0.0-M6")
|
||||||
|
// https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-retry
|
||||||
|
//implementation("org.springframework.ai:spring-ai-retry:1.0.0")
|
||||||
|
//implementation group: 'org.springframework.ai', name: 'spring-ai-spring-boot-autoconfigure', version: '1.0.0-M6'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
dependencyManagement {
|
||||||
|
imports {
|
||||||
|
mavenBom ("org.springframework.ai:spring-ai-retry:1.0.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyResouces( type:Copy
|
||||||
|
|
||||||
|
//tasks copyResouces( type:Copy){
|
||||||
|
//
|
||||||
|
// def buildDir = "${project.buildDir}"
|
||||||
|
//
|
||||||
|
// from "{$buildDir}/jambotron-ui/dist/jambotron-ui/browser" // Source directory
|
||||||
|
// into "{$buildDir}/build/resources" // Target directory
|
||||||
|
// include '**/*'
|
||||||
|
//}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
tasks.register('copyResources', Copy) {
|
||||||
|
from 'jambotron-ui/dist/jambotron-ui/browser'
|
||||||
|
into 'src/main/resources/public'
|
||||||
|
}
|
||||||
|
processResources.dependsOn(copyResources)
|
||||||
|
compileJava.dependsOn(copyResources)
|
||||||
|
//bootRun.dependsOn("flywayMigrate")
|
||||||
|
|
||||||
|
//flyway {
|
||||||
|
// user='admin'
|
||||||
|
// password= 'postgrespw'
|
||||||
|
// url = 'jdbc:postgresql://localhost:5432/jambotronDB'
|
||||||
|
// driver = 'org.postgresql.Driver'
|
||||||
|
// baselineOnMigrate = true
|
||||||
|
// locations = ['filesystem:src/main/resources/db.migration/']
|
||||||
|
//}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
resources {
|
||||||
|
srcDir 'jambotron-ui/dist/jambotron-ui/browser'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# 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
|
||||||
|
ij_typescript_use_double_quotes = false
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# JambotronUi
|
||||||
|
|
||||||
|
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.2.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
To start a local development server, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate component component-name
|
||||||
|
```
|
||||||
|
|
||||||
|
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the project run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng build
|
||||||
|
```
|
||||||
|
|
||||||
|
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
For end-to-end (e2e) testing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ng e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"jambotron-ui": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular/build:application",
|
||||||
|
"options": {
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "src/assets/",
|
||||||
|
"output": "/assets/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"server": "src/main.server.ts",
|
||||||
|
"outputMode": "server",
|
||||||
|
"ssr": {
|
||||||
|
"entry": "src/server.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kB",
|
||||||
|
"maximumError": "1MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "4kB",
|
||||||
|
"maximumError": "8kB"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular/build:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "jambotron-ui:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "jambotron-ui:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular/build:extract-i18n"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular/build:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "public"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||||
|
"src/styles.scss"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+9664
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "jambotron-ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test",
|
||||||
|
"serve:ssr:jambotron-ui": "node dist/jambotron-ui/server/server.mjs"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^20.0.4",
|
||||||
|
"@angular/cdk": "^20.0.3",
|
||||||
|
"@angular/common": "^20.0.0",
|
||||||
|
"@angular/compiler": "^20.0.0",
|
||||||
|
"@angular/core": "^20.0.0",
|
||||||
|
"@angular/forms": "^20.0.0",
|
||||||
|
"@angular/material": "^20.0.3",
|
||||||
|
"@angular/platform-browser": "^20.0.0",
|
||||||
|
"@angular/platform-server": "^20.0.0",
|
||||||
|
"@angular/router": "^20.0.0",
|
||||||
|
"@angular/ssr": "^20.0.2",
|
||||||
|
"@ant-design/icons-angular": "19.0.0",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "19.0.0",
|
||||||
|
"@popperjs/core": "2.11.8",
|
||||||
|
"@primeng/themes": "^19.1.3",
|
||||||
|
"bootstrap": "5.3.6",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"ngx-scrollbar": "18.0.0",
|
||||||
|
"primeng": "^19.1.3",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.15.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular/build": "^20.0.2",
|
||||||
|
"@angular/cli": "^20.0.2",
|
||||||
|
"@angular/compiler-cli": "^20.0.0",
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/node": "^20.17.19",
|
||||||
|
"jasmine-core": "~5.7.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.8.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { EventBusService } from './event-bus.service';
|
||||||
|
|
||||||
|
describe('EventBusService', () => {
|
||||||
|
let service: EventBusService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(EventBusService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Subject, Subscription } from 'rxjs';
|
||||||
|
import { filter, map } from 'rxjs/operators';
|
||||||
|
import { EventData } from './event.class';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class EventBusService {
|
||||||
|
private subject$ = new Subject<EventData>();
|
||||||
|
|
||||||
|
emit(event: EventData) {
|
||||||
|
this.subject$.next(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventName: string, action: any): Subscription {
|
||||||
|
return this.subject$.pipe(
|
||||||
|
filter((e: EventData) => e.name === eventName),
|
||||||
|
map((e: EventData) => e["value"])).subscribe(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export class EventData {
|
||||||
|
name: string;
|
||||||
|
value: any;
|
||||||
|
|
||||||
|
constructor(name: string, value: any) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p><p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p><p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p><p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
<p>admin-welcome.component works!</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminWelcomeComponent } from './admin-welcome.component';
|
||||||
|
|
||||||
|
describe('AdminWelcomeComponent', () => {
|
||||||
|
let component: AdminWelcomeComponent;
|
||||||
|
let fixture: ComponentFixture<AdminWelcomeComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AdminWelcomeComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminWelcomeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-welcome.component',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './admin-welcome.component.html',
|
||||||
|
styleUrl: './admin-welcome.component.scss'
|
||||||
|
})
|
||||||
|
export class AdminWelcomeComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<!--<p>admin.component works!</p>-->
|
||||||
|
|
||||||
|
<app-navigation
|
||||||
|
class="pc-sidebar"
|
||||||
|
[ngClass]="{
|
||||||
|
'navbar-collapsed': navCollapsed,
|
||||||
|
'mob-open': navCollapsedMob
|
||||||
|
}"
|
||||||
|
(NavCollapse)="this.navCollapsed = !this.navCollapsed"
|
||||||
|
/>
|
||||||
|
<app-nav-bar (NavCollapsedMob)="navMobClick()" (NavCollapse)="this.navCollapsed = !this.navCollapsed"/>
|
||||||
|
<div class="pc-container">
|
||||||
|
<div class="coded-wrapper">
|
||||||
|
<div class="coded-content">
|
||||||
|
<div class="coded-inner-content">
|
||||||
|
<app-breadcrumb />
|
||||||
|
<div class="main-body">
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<router-outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pc-menu-overlay" (click)="closeMenu()" (keydown)="handleKeyDown($event)" tabindex="0"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--<div class="pc-container">
|
||||||
|
<div class="coded-wrapper">
|
||||||
|
<div class="coded-content">
|
||||||
|
<div class="coded-inner-content">
|
||||||
|
<app-breadcrumb />
|
||||||
|
<div class="main-body">
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pc-menu-overlay" (click)="closeMenu()" (keydown)="handleKeyDown($event)" tabindex="0"></div>
|
||||||
|
</div>-->
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.example-container {
|
||||||
|
width: 500px;
|
||||||
|
height: 300px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-sidenav-content {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-sidenav {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pc-sidebar{
|
||||||
|
top: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminComponent } from './admin.component';
|
||||||
|
|
||||||
|
describe('AdminComponent', () => {
|
||||||
|
let component: AdminComponent;
|
||||||
|
let fixture: ComponentFixture<AdminComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AdminComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||||
|
import {BreadcrumbComponent} from "../../components/breadcrumb/breadcrumb.component";
|
||||||
|
import {NavBarComponent} from "../../layouts/admin-layout/nav-bar/nav-bar.component";
|
||||||
|
import {NavigationComponent} from "../../layouts/admin-layout/navigation/navigation.component";
|
||||||
|
import {Router, RouterLink, RouterOutlet} from "@angular/router";
|
||||||
|
import {DOCUMENT, NgClass, NgStyle} from '@angular/common';
|
||||||
|
import {MatDrawerContainer, MatSidenavModule} from '@angular/material/sidenav';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
||||||
|
import {MenuItem} from 'primeng/api';
|
||||||
|
import {PanelMenu} from 'primeng/panelmenu';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin.component',
|
||||||
|
imports: [
|
||||||
|
BreadcrumbComponent,
|
||||||
|
NavBarComponent,
|
||||||
|
NavigationComponent,
|
||||||
|
RouterOutlet,
|
||||||
|
NgClass,
|
||||||
|
MatDrawerContainer,
|
||||||
|
MatSidenavModule,
|
||||||
|
ScrollPanelModule,
|
||||||
|
MatButton,
|
||||||
|
RouterLink,
|
||||||
|
PanelMenu,
|
||||||
|
NgStyle,
|
||||||
|
|
||||||
|
],
|
||||||
|
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
templateUrl: './admin.component.html',
|
||||||
|
styleUrl: './admin.component.scss'
|
||||||
|
})
|
||||||
|
export class AdminComponent implements OnInit {
|
||||||
|
private document = inject<Document>(DOCUMENT);
|
||||||
|
height = 0;
|
||||||
|
|
||||||
|
items: MenuItem[] | undefined;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
this.height = 0
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// public props
|
||||||
|
navCollapsed: boolean = false;
|
||||||
|
navCollapsedMob: boolean = false;
|
||||||
|
|
||||||
|
// public method
|
||||||
|
navMobClick() {
|
||||||
|
if (this.navCollapsedMob && !document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||||
|
this.navCollapsedMob = !this.navCollapsedMob;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.navCollapsedMob = !this.navCollapsedMob;
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
this.navCollapsedMob = !this.navCollapsedMob;
|
||||||
|
}
|
||||||
|
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('navbar-collapsed')) {
|
||||||
|
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('navbar-collapsed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.closeMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMenu() {
|
||||||
|
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||||
|
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('mob-open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
if(this.document.defaultView !== null || this.document.defaultView !== undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
this.height = this.document.defaultView.innerHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.height = 0;
|
||||||
|
|
||||||
|
this.items = [
|
||||||
|
{
|
||||||
|
label: 'Admin',
|
||||||
|
icon: 'pi pi-palette',
|
||||||
|
command: () => {
|
||||||
|
this.router.navigate(['/main/admin/welcome']);
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'pi pi-eraser',
|
||||||
|
// route: '/main/admin/settings'
|
||||||
|
command: () => {
|
||||||
|
this.router.navigate(['/main/admin/settings']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'System',
|
||||||
|
icon: 'pi pi-heart',
|
||||||
|
route: '/main/admin/system'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
|
||||||
|
import {mainRouting} from '../main-module/main.routing';
|
||||||
|
import {MainComponent} from '../main-module/main.component/main.component';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
import {AdminComponent} from './admin.component/admin.component';
|
||||||
|
import {adminRouting} from './admin.routing';
|
||||||
|
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
|
||||||
|
],
|
||||||
|
imports: [adminRouting, AdminComponent,ScrollPanelModule],
|
||||||
|
exports: [RouterModule],
|
||||||
|
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class AdminModule {}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
|
import {AdminComponent} from './admin.component/admin.component';
|
||||||
|
|
||||||
|
const ADMIN_ROUTES: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AdminComponent,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'welcome',
|
||||||
|
loadComponent: () => import('../admin-module/admin-welcome.component/admin-welcome.component').then((c) => c.AdminWelcomeComponent),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings',
|
||||||
|
loadComponent: () => import('../admin-module/settings.component/settings.component').then((c) => c.SettingsComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'system',
|
||||||
|
|
||||||
|
loadComponent: () => import('../admin-module/system.component/system.component').then((c) => c.SystemComponent)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const adminRouting = RouterModule.forChild(ADMIN_ROUTES);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>settings.component works!</p>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SettingsComponent } from './settings.component';
|
||||||
|
|
||||||
|
describe('SettingsComponent', () => {
|
||||||
|
let component: SettingsComponent;
|
||||||
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SettingsComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-settings.component',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './settings.component.html',
|
||||||
|
styleUrl: './settings.component.scss'
|
||||||
|
})
|
||||||
|
export class SettingsComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Custom beans">
|
||||||
|
Content 1
|
||||||
|
<table mat-table
|
||||||
|
[dataSource]="customBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||||
|
@for (column of displayedColumns; track column) {
|
||||||
|
<ng-container matColumnDef="{{column}}">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||||
|
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
<ng-container matColumnDef="expand">
|
||||||
|
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<button
|
||||||
|
matIconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
(click)="toggle(element); $event.stopPropagation()"
|
||||||
|
class="example-toggle-button"
|
||||||
|
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||||
|
<ng-container matColumnDef="expandedDetail">
|
||||||
|
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||||
|
<div class="example-element-detail-wrapper"
|
||||||
|
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||||
|
<div class="example-element-detail">
|
||||||
|
|
||||||
|
<mat-list role="list">
|
||||||
|
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||||
|
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||||
|
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||||
|
class="example-element-row"
|
||||||
|
[class.example-expanded-row]="isExpanded(element)"
|
||||||
|
(click)="toggle(element)">
|
||||||
|
</tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="All beans">
|
||||||
|
Content 2
|
||||||
|
<table mat-table
|
||||||
|
[dataSource]="allBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||||
|
@for (column of displayedColumns; track column) {
|
||||||
|
<ng-container matColumnDef="{{column}}">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||||
|
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
<ng-container matColumnDef="expand">
|
||||||
|
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<button
|
||||||
|
matIconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
(click)="toggle(element); $event.stopPropagation()"
|
||||||
|
class="example-toggle-button"
|
||||||
|
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||||
|
<ng-container matColumnDef="expandedDetail">
|
||||||
|
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||||
|
<div class="example-element-detail-wrapper"
|
||||||
|
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||||
|
<div class="example-element-detail">
|
||||||
|
|
||||||
|
<mat-list role="list">
|
||||||
|
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||||
|
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||||
|
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||||
|
class="example-element-row"
|
||||||
|
[class.example-expanded-row]="isExpanded(element)"
|
||||||
|
(click)="toggle(element)">
|
||||||
|
</tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||||
|
</table>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
mat-card{
|
||||||
|
//margin: 20px;
|
||||||
|
background-color: rgba(240, 248, 255, 0.7);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-title{
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-subtitle{
|
||||||
|
color: #009dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tab{
|
||||||
|
color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
color:lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mat-list{
|
||||||
|
//background-color: #1389d3;
|
||||||
|
color:black;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-list-item{
|
||||||
|
margin: 10px 10px 10px 10px;
|
||||||
|
background-color: #447694;
|
||||||
|
|
||||||
|
color: #009dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-detail-row {
|
||||||
|
height: 0;
|
||||||
|
background: #fffdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #1389d3;
|
||||||
|
background: #fffdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row:not(.example-expanded-row):hover {
|
||||||
|
background: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row:not(.example-expanded-row):active {
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-row td {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
transition: grid-template-rows 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail-wrapper-expanded {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-diagram {
|
||||||
|
min-width: 80px;
|
||||||
|
border: 2px solid black;
|
||||||
|
padding: 8px;
|
||||||
|
font-weight: lighter;
|
||||||
|
margin: 8px 0;
|
||||||
|
height: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-symbol {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-description {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-description-attribution {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-toggle-button {
|
||||||
|
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-toggle-button-expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SystemComponent } from './system.component';
|
||||||
|
|
||||||
|
describe('SystemComponent', () => {
|
||||||
|
let component: SystemComponent;
|
||||||
|
let fixture: ComponentFixture<SystemComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SystemComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SystemComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||||
|
import {MatDivider} from '@angular/material/divider';
|
||||||
|
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||||
|
import {
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatColumnDef,
|
||||||
|
MatHeaderCell, MatHeaderCellDef,
|
||||||
|
MatHeaderRow, MatHeaderRowDef,
|
||||||
|
MatRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatTable
|
||||||
|
} from '@angular/material/table';
|
||||||
|
import {MatList, MatListItem} from '@angular/material/list';
|
||||||
|
import {MatIconButton} from '@angular/material/button';
|
||||||
|
import {SystemService} from '../../services/system.service';
|
||||||
|
import {Bean} from '../../models/Bean';
|
||||||
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-system.component',
|
||||||
|
imports: [
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
MatDivider,
|
||||||
|
MatTabGroup,
|
||||||
|
MatTab,
|
||||||
|
MatTable,
|
||||||
|
MatColumnDef,
|
||||||
|
MatIcon,
|
||||||
|
MatCell,
|
||||||
|
MatCellDef,
|
||||||
|
MatList,
|
||||||
|
MatListItem,
|
||||||
|
MatRow,
|
||||||
|
MatHeaderRow,
|
||||||
|
MatRowDef,
|
||||||
|
MatHeaderCell,
|
||||||
|
MatHeaderCellDef,
|
||||||
|
MatIconButton,
|
||||||
|
MatIcon,
|
||||||
|
MatHeaderRowDef
|
||||||
|
],
|
||||||
|
templateUrl: './system.component.html',
|
||||||
|
styleUrl: './system.component.scss',
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class SystemComponent {
|
||||||
|
customBeans:Bean[] = [] ;
|
||||||
|
allBeans:Bean[] = [] ;
|
||||||
|
displayedColumns: string[] = ['shortName', 'typeShortName', 'scope'];
|
||||||
|
|
||||||
|
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
|
||||||
|
|
||||||
|
expandedElement: Bean | null = null;
|
||||||
|
|
||||||
|
resultsLength = 0;
|
||||||
|
isLoadingResults = true;
|
||||||
|
isRateLimitReached = false;
|
||||||
|
|
||||||
|
constructor(private systemService: SystemService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.retrieveBeans();
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieveBeans(): void {
|
||||||
|
this.systemService.getCustomBeans()
|
||||||
|
.subscribe(
|
||||||
|
(data: Bean[]) => {
|
||||||
|
this.customBeans = data;
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
this.systemService.getAllBeans()
|
||||||
|
.subscribe(
|
||||||
|
(data: Bean[]) => {
|
||||||
|
this.allBeans = data;
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks whether an element is expanded. */
|
||||||
|
isExpanded(element: Bean) {
|
||||||
|
return this.expandedElement === element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggles the expanded state of an element. */
|
||||||
|
toggle(element: Bean) {
|
||||||
|
this.expandedElement = this.isExpanded(element) ? null : element;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//ngAfterViewInit() {
|
||||||
|
//this.exampleDatabase = new ExampleHttpDatabase(this._httpClient);
|
||||||
|
|
||||||
|
// If the user changes the sort order, reset back to the first page.
|
||||||
|
/* this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
|
||||||
|
|
||||||
|
merge(this.sort.sortChange, this.paginator.page)
|
||||||
|
.pipe(
|
||||||
|
startWith({}),
|
||||||
|
switchMap(() => {
|
||||||
|
this.isLoadingResults = true;
|
||||||
|
return this.exampleDatabase!.getRepoIssues(
|
||||||
|
this.sort.active,
|
||||||
|
this.sort.direction,
|
||||||
|
this.paginator.pageIndex,
|
||||||
|
).pipe(catchError(() => observableOf(null)));
|
||||||
|
}),
|
||||||
|
map(data => {
|
||||||
|
// Flip flag to show that loading has finished.
|
||||||
|
this.isLoadingResults = false;
|
||||||
|
this.isRateLimitReached = data === null;
|
||||||
|
|
||||||
|
if (data === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only refresh the result length if there is new data. In case of rate
|
||||||
|
// limit errors, we do not want to reset the paginator to zero, as that
|
||||||
|
// would prevent users from re-triggering requests.
|
||||||
|
this.resultsLength = data.total_count;
|
||||||
|
return data.items;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe(data => (this.data = data));*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
<mat-toolbar class="fixed-top">
|
||||||
|
<button mat-raised-button routerLink="/" href="#">
|
||||||
|
My App
|
||||||
|
</button>
|
||||||
|
<span class="example-spacer"></span>
|
||||||
|
<button mat-raised-button routerLink="tutorials">
|
||||||
|
Tutorials
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button routerLink="add" *ngIf="isLoggedIn">
|
||||||
|
Add tutorial
|
||||||
|
</button>
|
||||||
|
<span class="example-spacer"></span>
|
||||||
|
@if (showAdminBoard) {
|
||||||
|
<button mat-raised-button routerLink="admin" >
|
||||||
|
<mat-icon>manage_accounts</mat-icon>
|
||||||
|
Admin Bord
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (showAdminBoard) {
|
||||||
|
<button mat-raised-button routerLink="system" >
|
||||||
|
<mat-icon>settings_applications</mat-icon>
|
||||||
|
System
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="example-spacer"></span>
|
||||||
|
<button mat-raised-button routerLink="register" *ngIf="!isLoggedIn">
|
||||||
|
<mat-icon>app_registration</mat-icon>
|
||||||
|
Register
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-raised-button (click)="openDialog('100ms', '5ms')" *ngIf="!isLoggedIn">
|
||||||
|
<mat-icon>login</mat-icon>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-raised-button routerLink="profile" *ngIf="isLoggedIn">
|
||||||
|
<mat-icon>account_circle</mat-icon>
|
||||||
|
{{ username }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button mat-raised-button (click)="logout()"*ngIf="isLoggedIn">
|
||||||
|
<mat-icon>logout</mat-icon>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="router_outlet_padding">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//#header_panel{
|
||||||
|
// background-color: #181d1f;
|
||||||
|
//}
|
||||||
|
.example-spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-toolbar{
|
||||||
|
background-color: rgba(153,153,153,0.16);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-top {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000; // Ensure toolbar stays above other content
|
||||||
|
}
|
||||||
|
.router_outlet_padding{
|
||||||
|
padding-top: 60px;
|
||||||
|
scroll-padding-inline: 40%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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 'spring-angular-ui'`, () => {
|
||||||
|
// const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
// const app = fixture.componentInstance;
|
||||||
|
// expect(app.title).toEqual('spring-angular-ui');
|
||||||
|
// });
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.content span').textContent).toContain('spring-angular-ui app is running!');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||||
|
import { TokenStorageService } from './services/token-storage.service';
|
||||||
|
import {MatDialog} from "@angular/material/dialog";
|
||||||
|
import {DialogComponent} from "./components/dialog/dialog.component";
|
||||||
|
import {AuthService} from "./services/auth.service";
|
||||||
|
import {Subscription} from "rxjs";
|
||||||
|
import {EventBusService} from "./_shared/event-bus.service";
|
||||||
|
import {RouterLink, RouterOutlet} from '@angular/router';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {MatToolbar} from '@angular/material/toolbar';
|
||||||
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
imports: [
|
||||||
|
RouterOutlet,
|
||||||
|
MatIcon,
|
||||||
|
MatButton,
|
||||||
|
RouterLink,
|
||||||
|
MatToolbar
|
||||||
|
],
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent implements OnInit {
|
||||||
|
readonly dialog = inject(MatDialog);
|
||||||
|
//private authService = new AuthService(provideHttpClient())
|
||||||
|
public dialogData : DialogData = {password: "", username: ""};
|
||||||
|
|
||||||
|
private roles: string[] = [];
|
||||||
|
isLoggedIn = false;
|
||||||
|
showAdminBoard = false;
|
||||||
|
showModeratorBoard = false;
|
||||||
|
username?: string;
|
||||||
|
|
||||||
|
eventBusSub?: Subscription;
|
||||||
|
private errorMessage: any;
|
||||||
|
private isLoginFailed: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private storageService: TokenStorageService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private eventBusService: EventBusService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||||
|
|
||||||
|
if (this.isLoggedIn) {
|
||||||
|
const user = this.storageService.getUser();
|
||||||
|
this.roles = user.roles;
|
||||||
|
|
||||||
|
this.showAdminBoard = true;//this.roles.includes('ROLE_ADMIN');
|
||||||
|
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
|
||||||
|
|
||||||
|
this.username = user.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventBusSub = this.eventBusService.on('logout', () => {
|
||||||
|
this.logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout().subscribe({
|
||||||
|
next: res => {
|
||||||
|
console.log(res);
|
||||||
|
this.storageService.clean();
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||||
|
|
||||||
|
let dialogRef = this.dialog.open(DialogComponent, {
|
||||||
|
width: '350px',
|
||||||
|
enterAnimationDuration,
|
||||||
|
exitAnimationDuration,
|
||||||
|
data: {username: this.dialogData.username, password: this.dialogData.password}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
console.log('The dialog was closed');
|
||||||
|
if(result== null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dialogData = result;
|
||||||
|
|
||||||
|
this.authService.login(this.dialogData.username, this.dialogData.password).subscribe(
|
||||||
|
data => {
|
||||||
|
this.storageService.saveToken(data.accessToken);
|
||||||
|
this.storageService.saveUser(data);
|
||||||
|
|
||||||
|
this.isLoginFailed = false;
|
||||||
|
this.isLoggedIn = true;
|
||||||
|
let user = this.storageService.getUser();
|
||||||
|
this.roles = user.roles;
|
||||||
|
//this.username = user.username;
|
||||||
|
this.reloadPage();
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
this.errorMessage = err.error.message;
|
||||||
|
this.isLoginFailed = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
reloadPage(): void {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export interface DialogData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideServerRendering, withRoutes } from '@angular/ssr';
|
||||||
|
import { appConfig } from './app.config';
|
||||||
|
import { serverRoutes } from './app.routes.server';
|
||||||
|
|
||||||
|
const serverConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideServerRendering(withRoutes(serverRoutes))
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
ApplicationConfig,
|
||||||
|
importProvidersFrom,
|
||||||
|
provideBrowserGlobalErrorListeners,
|
||||||
|
provideZoneChangeDetection
|
||||||
|
} from '@angular/core';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||||
|
import {AppModule} from './app.module';
|
||||||
|
import {providePrimeNG} from 'primeng/config';
|
||||||
|
import Aura from '@primeng/themes/aura';
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideBrowserGlobalErrorListeners(),
|
||||||
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
|
provideRouter(routes), provideClientHydration(withEventReplay()),
|
||||||
|
|
||||||
|
importProvidersFrom(AppModule),
|
||||||
|
providePrimeNG({
|
||||||
|
theme: {
|
||||||
|
preset: Aura
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<router-outlet></router-outlet>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
|
||||||
|
import { BrowserModule }
|
||||||
|
from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { BrowserAnimationsModule }
|
||||||
|
from '@angular/platform-browser/animations';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
|
||||||
|
|
||||||
|
import {MainComponent} from './main-module/main.component/main.component';
|
||||||
|
import {authInterceptorProviders} from './helpers/auth.interceptor';
|
||||||
|
import {AdminComponent} from './admin-module/admin.component/admin.component';
|
||||||
|
import {AdminWelcomeComponent} from './admin-module/admin-welcome.component/admin-welcome.component';
|
||||||
|
import {SettingsComponent} from './admin-module/settings.component/settings.component';
|
||||||
|
import {SystemComponent} from './admin-module/system.component/system.component';
|
||||||
|
import {appRouting} from './app.routes';
|
||||||
|
import {App} from './app';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
appRouting,
|
||||||
|
MainComponent,
|
||||||
|
BrowserModule,
|
||||||
|
MatIconModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
AdminComponent,
|
||||||
|
AdminWelcomeComponent,
|
||||||
|
SettingsComponent,
|
||||||
|
SystemComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
authInterceptorProviders,provideHttpClient(withInterceptorsFromDi())],
|
||||||
|
|
||||||
|
bootstrap: [],
|
||||||
|
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||||
|
|
||||||
|
export const serverRoutes: ServerRoute[] = [
|
||||||
|
{
|
||||||
|
path: '**',
|
||||||
|
renderMode: RenderMode.Prerender
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
|
import {AdminComponent} from './layouts/admin-layout/admin-layout.component';
|
||||||
|
import {MainComponent} from './main-module/main.component/main.component';
|
||||||
|
import {NgModule} from '@angular/core';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirectTo: 'main/home',
|
||||||
|
pathMatch: 'full'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'main',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./main-module/main.module').then((m) => m.MainModule),
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
export const appRouting = RouterModule.forRoot(routes);
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { App } from './app';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [App],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the app', () => {
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
const app = fixture.componentInstance;
|
||||||
|
expect(app).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render title', () => {
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement as HTMLElement;
|
||||||
|
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, jambotron-ui');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.html',
|
||||||
|
imports: [
|
||||||
|
RouterOutlet
|
||||||
|
],
|
||||||
|
styleUrl: './app.scss'
|
||||||
|
})
|
||||||
|
export class App {
|
||||||
|
protected title = 'jambotron-ui';
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<!--<div>-->
|
||||||
|
<!-- <div class="submit-form">-->
|
||||||
|
<!-- <div *ngIf="!submitted">-->
|
||||||
|
<!-- <div class="form-group">-->
|
||||||
|
<!-- <label for="title">Title</label>-->
|
||||||
|
<!-- <input-->
|
||||||
|
<!-- type="text"-->
|
||||||
|
<!-- class="form-control"-->
|
||||||
|
<!-- id="title"-->
|
||||||
|
<!-- required-->
|
||||||
|
<!-- [(ngModel)]="tutorial.title"-->
|
||||||
|
<!-- name="title"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div class="form-group">-->
|
||||||
|
<!-- <label for="description">Description</label>-->
|
||||||
|
<!-- <input-->
|
||||||
|
<!-- class="form-control"-->
|
||||||
|
<!-- id="description"-->
|
||||||
|
<!-- required-->
|
||||||
|
<!-- [(ngModel)]="tutorial.description"-->
|
||||||
|
<!-- name="description"-->
|
||||||
|
<!-- />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <button (click)="saveTutorial()" class="btn btn-success">Submit</button>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div *ngIf="submitted">-->
|
||||||
|
<!-- <h4>Tutorial was submitted successfully!</h4>-->
|
||||||
|
<!-- <button class="btn btn-success" (click)="newTutorial()">Add</button>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!--</div>-->
|
||||||
|
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Add tutorial</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content *ngIf="!submitted">
|
||||||
|
<mat-form-field class="example-full-width">
|
||||||
|
<mat-label>Title</mat-label>
|
||||||
|
<input matInput required
|
||||||
|
[(ngModel)]="tutorial.title">
|
||||||
|
</mat-form-field>
|
||||||
|
<!-- <kendo-editor [(ngModel)]="tutorial.description" ></kendo-editor>-->
|
||||||
|
<textarea matInput required [(ngModel)]="tutorial.description"></textarea>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button matButton (click)="saveTutorial()" *ngIf="!submitted">Save</button>
|
||||||
|
<div *ngIf="submitted">
|
||||||
|
<h4>Tutorial was submitted successfully!</h4>
|
||||||
|
<button matButton (click)="newTutorial()">Add new tutorial</button>
|
||||||
|
</div>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
.submit-form {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card{
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
mat-card-title{
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-form {
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AddTutorialComponent } from './add-tutorial.component';
|
||||||
|
|
||||||
|
describe('AddTutorialComponent', () => {
|
||||||
|
let component: AddTutorialComponent;
|
||||||
|
let fixture: ComponentFixture<AddTutorialComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AddTutorialComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AddTutorialComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
|
||||||
|
import {Tutorial} from '../../models/tutorial.model';
|
||||||
|
import {TutorialService} from '../../services/tutorial.service';
|
||||||
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-add-tutorial',
|
||||||
|
templateUrl: './add-tutorial.component.html',
|
||||||
|
styleUrls: ['./add-tutorial.component.scss'],
|
||||||
|
imports: [
|
||||||
|
MatCardActions,
|
||||||
|
FormsModule,
|
||||||
|
MatCard,
|
||||||
|
MatCardHeader,
|
||||||
|
MatCardContent,
|
||||||
|
MatFormField,
|
||||||
|
MatLabel,
|
||||||
|
|
||||||
|
MatButton,
|
||||||
|
MatInput,
|
||||||
|
NgIf,
|
||||||
|
MatLabel,
|
||||||
|
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class AddTutorialComponent implements OnInit {
|
||||||
|
|
||||||
|
tutorial: Tutorial = {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
published: false
|
||||||
|
};
|
||||||
|
submitted = false;
|
||||||
|
|
||||||
|
constructor(private tutorialService: TutorialService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
saveTutorial(): void {
|
||||||
|
const data = {
|
||||||
|
title: this.tutorial.title,
|
||||||
|
description: this.tutorial.description
|
||||||
|
};
|
||||||
|
|
||||||
|
this.tutorialService.create(data)
|
||||||
|
.subscribe(
|
||||||
|
response => {
|
||||||
|
console.log(response);
|
||||||
|
this.submitted = true;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
newTutorial(): void {
|
||||||
|
this.submitted = false;
|
||||||
|
this.tutorial = {
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
published: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<h2>Comments</h2>
|
||||||
|
<p class="comment">
|
||||||
|
Building for the web is fantastic!
|
||||||
|
</p>
|
||||||
|
<p class="comment">
|
||||||
|
The new template syntax is great
|
||||||
|
</p>
|
||||||
|
<p class="comment">
|
||||||
|
I agree with the other comments!
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.comment {
|
||||||
|
padding: 15px;
|
||||||
|
margin-left: 30px;
|
||||||
|
background-color: paleturquoise;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ArticleComments } from './article-comments';
|
||||||
|
|
||||||
|
describe('ArticleComments', () => {
|
||||||
|
let component: ArticleComments;
|
||||||
|
let fixture: ComponentFixture<ArticleComments>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ArticleComments]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ArticleComments);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-article-comments',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './article-comments.html',
|
||||||
|
styleUrl: './article-comments.scss'
|
||||||
|
})
|
||||||
|
export class ArticleComments {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<div class="col-md-6">
|
||||||
|
<h4>Users List</h4>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li
|
||||||
|
class="list-group-item"
|
||||||
|
*ngFor="let row of rowData; let i = index"
|
||||||
|
|
||||||
|
>
|
||||||
|
{{ row.username }}
|
||||||
|
|
||||||
|
{{ row.email}}
|
||||||
|
{{ row.password}}
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<mat-form-field class="example-chip-list">
|
||||||
|
<mat-chip-grid #chipGrid aria-label="Role selection">
|
||||||
|
@for (role of row.roles; track $index) {
|
||||||
|
<mat-chip-row (removed)="remove(role)">
|
||||||
|
{{role.name}}
|
||||||
|
<button matChipRemove [attr.aria-label]="'remove ' + role.name">
|
||||||
|
<mat-icon>cancel</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-chip-row>
|
||||||
|
}
|
||||||
|
</mat-chip-grid>
|
||||||
|
<input
|
||||||
|
name="currentFruit"
|
||||||
|
placeholder="Add role..."
|
||||||
|
#fruitInput
|
||||||
|
[(ngModel)]="currentRole"
|
||||||
|
[matChipInputFor]="chipGrid"
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
|
[formControl]="myControl"
|
||||||
|
(matChipInputTokenEnd)="add($event)"
|
||||||
|
(input)="change($event,filteredRoles(row.roles))"
|
||||||
|
/>
|
||||||
|
<mat-autocomplete [formControl]="ac" autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="selected(row.roles,$event); ">
|
||||||
|
@for (role of filteredRoles(row.roles); track role) {
|
||||||
|
<mat-option [value]="role">{{role.name}}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-autocomplete>
|
||||||
|
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.example-chip-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BoardAdminComponent } from './board-admin.component';
|
||||||
|
|
||||||
|
describe('BoardAdminComponent', () => {
|
||||||
|
let component: BoardAdminComponent;
|
||||||
|
let fixture: ComponentFixture<BoardAdminComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ BoardAdminComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BoardAdminComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import {Component, computed, CUSTOM_ELEMENTS_SCHEMA, model, OnInit} from '@angular/core';
|
||||||
|
|
||||||
|
import {User} from "../../models/user.model";
|
||||||
|
import {MatChipGrid, MatChipInput, MatChipInputEvent, MatChipRow} from "@angular/material/chips";
|
||||||
|
import {
|
||||||
|
MatAutocomplete,
|
||||||
|
MatAutocompleteSelectedEvent,
|
||||||
|
MatAutocompleteTrigger,
|
||||||
|
MatOption
|
||||||
|
} from "@angular/material/autocomplete";
|
||||||
|
import {COMMA, ENTER} from "@angular/cdk/keycodes";
|
||||||
|
import {AsyncPipe, NgForOf} from "@angular/common";
|
||||||
|
import {FormControl, ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {Observable, startWith} from "rxjs";
|
||||||
|
import {map} from "rxjs/operators";
|
||||||
|
import {UserService} from '../../services/user.service';
|
||||||
|
import {RolesService} from '../../services/roles.service';
|
||||||
|
import {Role} from '../../models/role';
|
||||||
|
import {MatIcon} from '@angular/material/icon';
|
||||||
|
import {MatFormField} from '@angular/material/form-field';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-board-admin',
|
||||||
|
templateUrl: './board-admin.component.html',
|
||||||
|
styleUrls: ['./board-admin.component.scss'],
|
||||||
|
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
imports: [
|
||||||
|
MatAutocomplete,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatOption,
|
||||||
|
NgForOf,
|
||||||
|
MatFormField,
|
||||||
|
MatChipGrid,
|
||||||
|
|
||||||
|
MatChipRow,
|
||||||
|
MatIcon,
|
||||||
|
MatChipInput,
|
||||||
|
MatAutocompleteTrigger
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class BoardAdminComponent implements OnInit {
|
||||||
|
private gridApi: any;
|
||||||
|
|
||||||
|
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||||
|
rowData?: User[];
|
||||||
|
allRoles: any;
|
||||||
|
|
||||||
|
readonly currentRole = model('');
|
||||||
|
|
||||||
|
protected myControl = new FormControl('');
|
||||||
|
protected ac = new FormControl('');
|
||||||
|
|
||||||
|
|
||||||
|
constructor(private userService: UserService, private roleService: RolesService ) {
|
||||||
|
this.getAllRoles();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
|
||||||
|
//this.myControl.valueChanges.pipe(
|
||||||
|
// startWith(''),
|
||||||
|
// map(value => this._filter(value || '')),
|
||||||
|
//);
|
||||||
|
|
||||||
|
this.userService.getAdminBoard().subscribe(
|
||||||
|
(data : any) => {
|
||||||
|
this.rowData = data;
|
||||||
|
},
|
||||||
|
(err : any)=> {
|
||||||
|
this.rowData = JSON.parse(err.error).message;
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredRoles(roles : Role[]):any {
|
||||||
|
return this.allRoles.filter(
|
||||||
|
(r:Role) => !roles.some((item) => item.id === r.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// private _filter(value: string): string[] {
|
||||||
|
// const filterValue = value.toLowerCase();
|
||||||
|
// this.ac.
|
||||||
|
// return this.options.filter(option => option.toLowerCase().includes(filterValue));
|
||||||
|
//}
|
||||||
|
|
||||||
|
getAllRoles():any{
|
||||||
|
this.roleService.getAllRoles().subscribe(
|
||||||
|
(data : any) => {
|
||||||
|
this.allRoles = data;
|
||||||
|
},
|
||||||
|
(err : any)=> {
|
||||||
|
this.allRoles = JSON.parse(err.error).message;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(role: string): void {
|
||||||
|
// this.fruits.update(fruits => {
|
||||||
|
// const index = fruits.indexOf(fruit);
|
||||||
|
// if (index < 0) {
|
||||||
|
// return fruits;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fruits.splice(index, 1);
|
||||||
|
// this.announcer.announce(`Removed ${fruit}`);
|
||||||
|
// return [...fruits];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
add(event: MatChipInputEvent): void {
|
||||||
|
const value = (event.value || '').trim();
|
||||||
|
|
||||||
|
// Add our fruit
|
||||||
|
if (value) {
|
||||||
|
// this.fruits.update(fruits => [...fruits, value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the input value
|
||||||
|
this.currentRole.set('');
|
||||||
|
}
|
||||||
|
|
||||||
|
selected(roles : Role[], event: MatAutocompleteSelectedEvent): void {
|
||||||
|
|
||||||
|
roles.push(event.option.value);
|
||||||
|
this.currentRole.set('');
|
||||||
|
event.option.deselect();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
change($event: Event, roles: Role[]) {
|
||||||
|
|
||||||
|
roles.filter(
|
||||||
|
(r:Role) => !roles.some((item) => item.name?.toLowerCase() === r.id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>board-moderator works!</p>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BoardModeratorComponent } from './board-moderator.component';
|
||||||
|
|
||||||
|
describe('BoardModeratorComponent', () => {
|
||||||
|
let component: BoardModeratorComponent;
|
||||||
|
let fixture: ComponentFixture<BoardModeratorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ BoardModeratorComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BoardModeratorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-board-moderator',
|
||||||
|
templateUrl: './board-moderator.component.html',
|
||||||
|
styleUrls: ['./board-moderator.component.scss'],
|
||||||
|
standalone: false
|
||||||
|
})
|
||||||
|
export class BoardModeratorComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>board-user works!</p>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BoardUserComponent } from './board-user.component';
|
||||||
|
|
||||||
|
describe('BoardUserComponent', () => {
|
||||||
|
let component: BoardUserComponent;
|
||||||
|
let fixture: ComponentFixture<BoardUserComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ BoardUserComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BoardUserComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-board-user',
|
||||||
|
templateUrl: './board-user.component.html',
|
||||||
|
styleUrls: ['./board-user.component.scss'],
|
||||||
|
standalone: false
|
||||||
|
})
|
||||||
|
export class BoardUserComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
@for (breadcrumb of navigationList; track breadcrumb; let last = $last) {
|
||||||
|
@if (last && breadcrumb.breadcrumbs !== false) {
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-block">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<ul class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
@if (type === 'theme2') {
|
||||||
|
<a [routerLink]="['/main/home']" title="Home" class="home"><i class="feather icon-home"></i></a>
|
||||||
|
}
|
||||||
|
@if (type === 'theme1') {
|
||||||
|
<a [routerLink]="['/main/home']" class="home">Home</a>
|
||||||
|
}
|
||||||
|
</li>
|
||||||
|
@for (breadcrumb of navigationList; track breadcrumb) {
|
||||||
|
@if (breadcrumb.url !== false) {
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a [routerLink]="breadcrumb.url" class="f-14 f-w-600">{{ breadcrumb.title }}</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if (breadcrumb.url === false && breadcrumb.type !== 'group') {
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="javascript:">{{ breadcrumb.title }}</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="page-header-title">
|
||||||
|
@for (breadcrumb of navigationList; track breadcrumb; let last = $last) {
|
||||||
|
@if (last) {
|
||||||
|
<h2 class="mb-0 f-w-600 mt-2">{{ breadcrumb.title }}</h2>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Angular Import
|
||||||
|
import { Component, Input, inject, input } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NavigationEnd, Router, RouterModule, Event } from '@angular/router';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
//import { NavigationItem, NavigationItems } from 'src/app/theme/layouts/admin-layout/navigation/navigation';
|
||||||
|
|
||||||
|
// icons
|
||||||
|
import { IconService } from '@ant-design/icons-angular';
|
||||||
|
import { GlobalOutline, NodeExpandOutline } from '@ant-design/icons-angular/icons';
|
||||||
|
import {NavigationItem, NavigationItems} from '../../layouts/admin-layout/navigation/navigation';
|
||||||
|
|
||||||
|
interface titleType {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
url: any;
|
||||||
|
title: string;
|
||||||
|
breadcrumbs: unknown;
|
||||||
|
type: string;
|
||||||
|
link?: string | undefined;
|
||||||
|
description?: string | undefined;
|
||||||
|
path?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-breadcrumb',
|
||||||
|
imports: [CommonModule, RouterModule],
|
||||||
|
templateUrl: './breadcrumb.component.html',
|
||||||
|
styleUrls: ['./breadcrumb.component.scss']
|
||||||
|
})
|
||||||
|
export class BreadcrumbComponent {
|
||||||
|
private route = inject(Router);
|
||||||
|
private titleService = inject(Title);
|
||||||
|
private iconService = inject(IconService);
|
||||||
|
|
||||||
|
// public props
|
||||||
|
@Input() type: string;
|
||||||
|
dashboard = input(true);
|
||||||
|
Component = input(false);
|
||||||
|
|
||||||
|
navigations: NavigationItem[];
|
||||||
|
ComponentNavigations: NavigationItem[] = [];
|
||||||
|
breadcrumbList: Array<string> = [];
|
||||||
|
navigationList!: titleType[];
|
||||||
|
componentList!: titleType[];
|
||||||
|
|
||||||
|
// constructor
|
||||||
|
constructor() {
|
||||||
|
this.navigations = NavigationItems;
|
||||||
|
this.type = 'theme1';
|
||||||
|
this.setBreadcrumb();
|
||||||
|
this.iconService.addIcon(...[GlobalOutline, NodeExpandOutline]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public method
|
||||||
|
setBreadcrumb() {
|
||||||
|
this.route.events.subscribe((router: Event) => {
|
||||||
|
if (router instanceof NavigationEnd) {
|
||||||
|
const activeLink = router.url;
|
||||||
|
const breadcrumbList = this.filterNavigation(this.navigations, activeLink);
|
||||||
|
|
||||||
|
this.navigationList = breadcrumbList;//breadcrumbList.slice(breadcrumbList.length - 1, breadcrumbList.length);
|
||||||
|
const title = breadcrumbList[breadcrumbList.length - 1]?.title || 'Welcome';
|
||||||
|
this.titleService.setTitle(title + ' | Mantis Angular Admin Template');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filterNavigation(navItems: NavigationItem[], activeLink: string): titleType[] {
|
||||||
|
for (const navItem of navItems) {
|
||||||
|
if (navItem.type === 'item' && 'url' in navItem && navItem.url === activeLink) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: 'url' in navItem ? navItem.url : false,
|
||||||
|
title: navItem.title,
|
||||||
|
link: navItem.link,
|
||||||
|
description: navItem.description,
|
||||||
|
path: navItem.path,
|
||||||
|
breadcrumbs: 'breadcrumbs' in navItem ? navItem.breadcrumbs : true,
|
||||||
|
type: navItem.type
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ((navItem.type === 'group' || navItem.type === 'collapse') && 'children' in navItem) {
|
||||||
|
const breadcrumbList = this.filterNavigation(navItem.children!, activeLink);
|
||||||
|
if (breadcrumbList.length > 0) {
|
||||||
|
breadcrumbList.unshift({
|
||||||
|
url: 'url' in navItem ? navItem.url : false,
|
||||||
|
title: navItem.title,
|
||||||
|
link: navItem.link,
|
||||||
|
path: navItem.path,
|
||||||
|
description: navItem.description,
|
||||||
|
breadcrumbs: 'breadcrumbs' in navItem ? navItem.breadcrumbs : true,
|
||||||
|
type: navItem.type
|
||||||
|
});
|
||||||
|
return breadcrumbList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="card" [ngClass]="cardClass()">
|
||||||
|
@if (showHeader()) {
|
||||||
|
<div class="card-header d-flex align-items-center justify-content-between" [ngClass]="headerClass()">
|
||||||
|
<div>
|
||||||
|
<h5>{{ cardTitle() }}</h5>
|
||||||
|
<ng-container *ngTemplateOutlet="headerTitleTemplate"></ng-container>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngTemplateOutlet="headerOptionsTemplate"></ng-container>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (showContent()) {
|
||||||
|
<div class="card-body" [ngClass]="blockClass()" [style.padding.px]="padding()">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Angular import
|
||||||
|
import { Component, ContentChild, ElementRef, TemplateRef, input } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-card',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './card.component.html',
|
||||||
|
styleUrls: ['./card.component.scss']
|
||||||
|
})
|
||||||
|
export class CardComponent {
|
||||||
|
// public props
|
||||||
|
/**
|
||||||
|
* Title of card. It will be visible at left side of card header
|
||||||
|
*/
|
||||||
|
cardTitle = input<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to be applied at card level
|
||||||
|
*/
|
||||||
|
cardClass = input<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To hide content from card
|
||||||
|
*/
|
||||||
|
showContent = input(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to be applied at card content.
|
||||||
|
*/
|
||||||
|
blockClass = input<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to be applied on card header
|
||||||
|
*/
|
||||||
|
headerClass = input<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To hide header from card
|
||||||
|
*/
|
||||||
|
showHeader = input(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* padding around card content. default in px
|
||||||
|
*/
|
||||||
|
padding = input(20); // set default to 24 px
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template reference of header actions on custom header
|
||||||
|
*/
|
||||||
|
@ContentChild('headerOptionsTemplate') headerOptionsTemplate!: TemplateRef<ElementRef>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template reference of header actions besides title at left
|
||||||
|
*/
|
||||||
|
@ContentChild('headerTitleTemplate') headerTitleTemplate!: TemplateRef<ElementRef>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<h1 mat-dialog-title>Hi </h1>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<p>User name</p>
|
||||||
|
<mat-form-field class="fillField">
|
||||||
|
<input matInput [(ngModel)]="data.username">
|
||||||
|
</mat-form-field>
|
||||||
|
<p>Password</p>
|
||||||
|
<mat-form-field class="fillField">
|
||||||
|
<mat-label>Enter your password</mat-label>
|
||||||
|
<input matInput
|
||||||
|
[type]="hide() ? 'password' : 'text'"
|
||||||
|
[(ngModel)]="data.password"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
matSuffix
|
||||||
|
(click)="clickEvent($event)"
|
||||||
|
[attr.aria-label]="'Hide password'"
|
||||||
|
[attr.aria-pressed]="hide()"
|
||||||
|
>
|
||||||
|
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
|
||||||
|
<button mat-button [mat-dialog-close]="data" cdkFocusInitial>Ok</button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
.fillField{
|
||||||
|
width: 100%;
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DialogComponent } from './dialog.component';
|
||||||
|
|
||||||
|
describe('DialogComponent', () => {
|
||||||
|
let component: DialogComponent;
|
||||||
|
let fixture: ComponentFixture<DialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DialogComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import {ChangeDetectionStrategy, Component, Inject, inject, signal} from '@angular/core';
|
||||||
|
import {MatButtonModule} from "@angular/material/button";
|
||||||
|
import {
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogRef,
|
||||||
|
MatDialogTitle
|
||||||
|
} from "@angular/material/dialog";
|
||||||
|
|
||||||
|
|
||||||
|
import {MatFormField, MatLabel, MatSuffix} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {DialogData} from '../../main-module/main.component/main.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dialog',
|
||||||
|
imports: [MatButtonModule, MatLabel, MatDialogActions, MatDialogClose, MatDialogTitle, MatDialogContent, MatFormField, MatInput, FormsModule, MatIcon, MatSuffix],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: './dialog.component.html',
|
||||||
|
styleUrl: './dialog.component.scss'
|
||||||
|
})
|
||||||
|
export class DialogComponent {
|
||||||
|
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
||||||
|
hide = signal(true);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data:DialogData) {}
|
||||||
|
|
||||||
|
onNoClick() {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
clickEvent(event: MouseEvent) {
|
||||||
|
this.hide.set(!this.hide());
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
|
<p>home.component works!</p>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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({
|
||||||
|
imports: [HomeComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(HomeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home.component',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrl: './home.component.scss'
|
||||||
|
})
|
||||||
|
export class HomeComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card card-container">
|
||||||
|
<img
|
||||||
|
id="profile-img"
|
||||||
|
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||||
|
class="profile-img-card"
|
||||||
|
/>
|
||||||
|
<form
|
||||||
|
*ngIf="!isLoggedIn"
|
||||||
|
name="form"
|
||||||
|
(ngSubmit)="f.form.valid && onSubmit()"
|
||||||
|
#f="ngForm"
|
||||||
|
novalidate
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="username"
|
||||||
|
[(ngModel)]="form.username"
|
||||||
|
required
|
||||||
|
#username="ngModel"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert"
|
||||||
|
*ngIf="username.errors && f.submitted"
|
||||||
|
>
|
||||||
|
Username is required!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
[(ngModel)]="form.password"
|
||||||
|
required
|
||||||
|
minlength="6"
|
||||||
|
#password="ngModel"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert"
|
||||||
|
*ngIf="password.errors && f.submitted"
|
||||||
|
>
|
||||||
|
<div *ngIf="password.errors['required']">Password is required</div>
|
||||||
|
<div *ngIf="password.errors['minlength']">
|
||||||
|
Password must be at least 6 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-primary btn-block">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div
|
||||||
|
class="alert alert-danger"
|
||||||
|
role="alert"
|
||||||
|
*ngIf="f.submitted && isLoginFailed"
|
||||||
|
>
|
||||||
|
Login failed: {{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="isLoggedIn">
|
||||||
|
Logged in as {{ roles }}.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {NgIf} from "@angular/common";
|
||||||
|
import {AuthService} from '../../services/auth.service';
|
||||||
|
import {TokenStorageService} from '../../services/token-storage.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit {
|
||||||
|
|
||||||
|
form: any = {
|
||||||
|
username: null,
|
||||||
|
password: null
|
||||||
|
};
|
||||||
|
isLoggedIn = false;
|
||||||
|
isLoginFailed = false;
|
||||||
|
errorMessage = '';
|
||||||
|
roles: string[] = [];
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private tokenStorage: TokenStorageService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.tokenStorage.getToken()) {
|
||||||
|
this.isLoggedIn = true;
|
||||||
|
this.roles = this.tokenStorage.getUser().roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
const { username, password } = this.form;
|
||||||
|
|
||||||
|
this.authService.login(username, password).subscribe(
|
||||||
|
data => {
|
||||||
|
this.tokenStorage.saveToken(data.accessToken);
|
||||||
|
this.tokenStorage.saveUser(data);
|
||||||
|
|
||||||
|
this.isLoginFailed = false;
|
||||||
|
this.isLoggedIn = true;
|
||||||
|
this.roles = this.tokenStorage.getUser().roles;
|
||||||
|
this.reloadPage();
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
this.errorMessage = err.error.message;
|
||||||
|
this.isLoginFailed = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadPage(): void {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="container" *ngIf="currentUser; else loggedOut">
|
||||||
|
<header class="jumbotron">
|
||||||
|
<h3>
|
||||||
|
<strong>{{ currentUser.username }}</strong> Profile
|
||||||
|
</h3>
|
||||||
|
</header>
|
||||||
|
<p>
|
||||||
|
<strong>Token:</strong>
|
||||||
|
{{ currentUser.accessToken.substring(0, 20) }} ...
|
||||||
|
{{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Email:</strong>
|
||||||
|
{{ currentUser.email }}
|
||||||
|
</p>
|
||||||
|
<strong>Roles:</strong>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let role of currentUser.roles">
|
||||||
|
{{ role }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #loggedOut>
|
||||||
|
Please login.
|
||||||
|
</ng-template>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProfileComponent } from './profile.component';
|
||||||
|
|
||||||
|
describe('ProfileComponent', () => {
|
||||||
|
let component: ProfileComponent;
|
||||||
|
let fixture: ComponentFixture<ProfileComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ProfileComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ProfileComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {TokenStorageService} from '../../services/token-storage.service';
|
||||||
|
import {NgForOf, NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-profile',
|
||||||
|
templateUrl: './profile.component.html',
|
||||||
|
imports: [
|
||||||
|
NgForOf,
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
styleUrls: ['./profile.component.scss']
|
||||||
|
})
|
||||||
|
export class ProfileComponent implements OnInit {
|
||||||
|
currentUser: any;
|
||||||
|
|
||||||
|
constructor(private token: TokenStorageService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.currentUser = this.token.getUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<div class="col-md-12">
|
||||||
|
<div id="register">
|
||||||
|
<img id="foto"
|
||||||
|
id="profile-img"
|
||||||
|
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||||
|
class="profile-img-card"
|
||||||
|
/>
|
||||||
|
<form
|
||||||
|
*ngIf="!isSuccessful"
|
||||||
|
name="form"
|
||||||
|
(ngSubmit)="f.form.valid && onSubmit()"
|
||||||
|
#f="ngForm"
|
||||||
|
novalidate
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="username"
|
||||||
|
[(ngModel)]="form.username"
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
required
|
||||||
|
minlength="3"
|
||||||
|
maxlength="20"
|
||||||
|
#username="ngModel"
|
||||||
|
/>
|
||||||
|
<div class="alert-danger" *ngIf="username.errors && f.submitted">
|
||||||
|
<div *ngIf="username.errors['required']">Username is required</div>
|
||||||
|
<div *ngIf="username.errors['minlength']">
|
||||||
|
Username must be at least 3 characters
|
||||||
|
</div>
|
||||||
|
<div *ngIf="username.errors['maxlength']">
|
||||||
|
Username must be at most 20 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-control"
|
||||||
|
id="email"
|
||||||
|
[(ngModel)]="form.email"
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
required
|
||||||
|
email
|
||||||
|
#email="ngModel"
|
||||||
|
/>
|
||||||
|
<div class="alert-danger" *ngIf="email.errors && f.submitted">
|
||||||
|
<div *ngIf="email.errors['required']">Email is required</div>
|
||||||
|
<div *ngIf="email.errors['email']">
|
||||||
|
Email must be a valid email address
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
id="password"
|
||||||
|
[(ngModel)]="form.password"
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
required
|
||||||
|
minlength="6"
|
||||||
|
#password="ngModel"
|
||||||
|
/>
|
||||||
|
<div class="alert-danger" *ngIf="password.errors && f.submitted">
|
||||||
|
<div *ngIf="password.errors['required']">Password is required</div>
|
||||||
|
<div *ngIf="password.errors['minlength']">
|
||||||
|
Password must be at least 6 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<button class="btn btn-primary btn-block">Sign Up</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning" *ngIf="f.submitted && isSignUpFailed">
|
||||||
|
Signup failed!<br />{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="alert alert-success" *ngIf="isSuccessful">
|
||||||
|
Your registration is successful!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RegisterComponent } from './register.component';
|
||||||
|
|
||||||
|
describe('RegisterComponent', () => {
|
||||||
|
let component: RegisterComponent;
|
||||||
|
let fixture: ComponentFixture<RegisterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ RegisterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(RegisterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {AuthService} from '../../services/auth.service';
|
||||||
|
import {FormsModule} from '@angular/forms';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-register',
|
||||||
|
templateUrl: './register.component.html',
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
styleUrls: ['./register.component.scss']
|
||||||
|
})
|
||||||
|
export class RegisterComponent implements OnInit {
|
||||||
|
form: any = {
|
||||||
|
username: null,
|
||||||
|
email: null,
|
||||||
|
password: null
|
||||||
|
};
|
||||||
|
isSuccessful = false;
|
||||||
|
isSignUpFailed = false;
|
||||||
|
errorMessage = '';
|
||||||
|
|
||||||
|
constructor(private authService: AuthService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
const { username, email, password } = this.form;
|
||||||
|
|
||||||
|
this.authService.register(username, email, password).subscribe(
|
||||||
|
data => {
|
||||||
|
console.log(data);
|
||||||
|
this.isSuccessful = true;
|
||||||
|
this.isSignUpFailed = false;
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
this.errorMessage = err.error.message;
|
||||||
|
this.isSignUpFailed = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
.sk-line-material {
|
||||||
|
top: 0;
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-line-material .sk-child {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
display: inline-block;
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
-webkit-animation: sk-line-material 2s ease-in-out 0s infinite both;
|
||||||
|
animation: sk-line-material 2s ease-in-out 0s infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sk-line-material {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scaleX(0);
|
||||||
|
transform: scaleX(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
-webkit-transform: scaleX(1);
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sk-line-material {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: scaleX(0);
|
||||||
|
transform: scaleX(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scaleX(1);
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#http-loader {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-bg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
filter: alpha(opacity=70);
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.colored-parent,
|
||||||
|
.colored > div {
|
||||||
|
background-color: rgba(26, 188, 156, 0.8);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export const Spinkit = {
|
||||||
|
skChasingDots: 'sk-chasing-dots',
|
||||||
|
skCubeGrid: 'sk-cube-grid',
|
||||||
|
skDoubleBounce: 'sk-double-bounce',
|
||||||
|
skRotatingPlane: 'sk-rotationg-plane',
|
||||||
|
skSpinnerPulse: 'sk-spinner-pulse',
|
||||||
|
skThreeBounce: 'sk-three-bounce',
|
||||||
|
skWanderingCubes: 'sk-wandering-cubes',
|
||||||
|
skWave: 'sk-wave',
|
||||||
|
skLine: 'sk-line-material'
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
@if (isSpinnerVisible) {
|
||||||
|
<div id="http-loader">
|
||||||
|
<div class="loader-bg">
|
||||||
|
@if (spinner() === Spinkit.skLine) {
|
||||||
|
<div class="sk-line-material" [class.colored]="!backgroundColor()">
|
||||||
|
<div class="sk-child sk-bounce1" [style.background-color]="backgroundColor()"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#http-loader {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader-bg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
filter: alpha(opacity=70);
|
||||||
|
opacity: 0.7;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colored-parent,
|
||||||
|
.colored > div {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Angular import
|
||||||
|
import { Component, OnDestroy, ViewEncapsulation, inject, input } from '@angular/core';
|
||||||
|
import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
|
||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
|
|
||||||
|
// project import
|
||||||
|
import { Spinkit } from './spinkits';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-spinner',
|
||||||
|
templateUrl: './spinner.component.html',
|
||||||
|
styleUrls: ['./spinner.component.scss', './spinkit-css/sk-line-material.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class SpinnerComponent implements OnDestroy {
|
||||||
|
private router = inject(Router);
|
||||||
|
private document = inject<Document>(DOCUMENT);
|
||||||
|
|
||||||
|
// public props
|
||||||
|
isSpinnerVisible = true;
|
||||||
|
Spinkit = Spinkit;
|
||||||
|
backgroundColor = input('#1890ff');
|
||||||
|
spinner = input(Spinkit.skLine);
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
constructor() {
|
||||||
|
this.router.events.subscribe(
|
||||||
|
(event) => {
|
||||||
|
if (event instanceof NavigationStart) {
|
||||||
|
this.isSpinnerVisible = true;
|
||||||
|
} else if (event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError) {
|
||||||
|
|
||||||
|
this.isSpinnerVisible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.isSpinnerVisible = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// life cycle event
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isSpinnerVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>System</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Custom beans">
|
||||||
|
Content 1
|
||||||
|
<table mat-table
|
||||||
|
[dataSource]="customBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||||
|
@for (column of displayedColumns; track column) {
|
||||||
|
<ng-container matColumnDef="{{column}}">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||||
|
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
<ng-container matColumnDef="expand">
|
||||||
|
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<button
|
||||||
|
matIconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
(click)="toggle(element); $event.stopPropagation()"
|
||||||
|
class="example-toggle-button"
|
||||||
|
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||||
|
<ng-container matColumnDef="expandedDetail">
|
||||||
|
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||||
|
<div class="example-element-detail-wrapper"
|
||||||
|
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||||
|
<div class="example-element-detail">
|
||||||
|
|
||||||
|
<mat-list role="list">
|
||||||
|
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||||
|
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||||
|
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||||
|
class="example-element-row"
|
||||||
|
[class.example-expanded-row]="isExpanded(element)"
|
||||||
|
(click)="toggle(element)">
|
||||||
|
</tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="All beans">
|
||||||
|
Content 2
|
||||||
|
<table mat-table
|
||||||
|
[dataSource]="allBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||||
|
@for (column of displayedColumns; track column) {
|
||||||
|
<ng-container matColumnDef="{{column}}">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||||
|
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||||
|
</ng-container>
|
||||||
|
}
|
||||||
|
<ng-container matColumnDef="expand">
|
||||||
|
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||||
|
<td mat-cell *matCellDef="let element">
|
||||||
|
<button
|
||||||
|
matIconButton
|
||||||
|
aria-label="expand row"
|
||||||
|
(click)="toggle(element); $event.stopPropagation()"
|
||||||
|
class="example-toggle-button"
|
||||||
|
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||||
|
<ng-container matColumnDef="expandedDetail">
|
||||||
|
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||||
|
<div class="example-element-detail-wrapper"
|
||||||
|
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||||
|
<div class="example-element-detail">
|
||||||
|
|
||||||
|
<mat-list role="list">
|
||||||
|
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||||
|
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||||
|
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||||
|
class="example-element-row"
|
||||||
|
[class.example-expanded-row]="isExpanded(element)"
|
||||||
|
(click)="toggle(element)">
|
||||||
|
</tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||||
|
</table>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
mat-card{
|
||||||
|
margin: 20px;
|
||||||
|
background-color: rgba(240, 248, 255, 0.7);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-title{
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card-subtitle{
|
||||||
|
color: #009dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-tab{
|
||||||
|
color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
color:lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mat-list{
|
||||||
|
//background-color: #1389d3;
|
||||||
|
color:black;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-list-item{
|
||||||
|
margin: 10px 10px 10px 10px;
|
||||||
|
background-color: #447694;
|
||||||
|
|
||||||
|
color: #009dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-detail-row {
|
||||||
|
height: 0;
|
||||||
|
background: #fffdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #1389d3;
|
||||||
|
background: #fffdfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row:not(.example-expanded-row):hover {
|
||||||
|
background: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.example-element-row:not(.example-expanded-row):active {
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-row td {
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
transition: grid-template-rows 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail-wrapper-expanded {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-detail {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-diagram {
|
||||||
|
min-width: 80px;
|
||||||
|
border: 2px solid black;
|
||||||
|
padding: 8px;
|
||||||
|
font-weight: lighter;
|
||||||
|
margin: 8px 0;
|
||||||
|
height: 104px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-symbol {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-description {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-element-description-attribution {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-toggle-button {
|
||||||
|
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-toggle-button-expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user