Add new components for AI and tutorials, update routing, and enhance file upload functionality
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminModuleService } from './admin-module.service';
|
||||
|
||||
describe('AdminModuleService', () => {
|
||||
let service: AdminModuleService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AdminModuleService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {User} from '../../models/user.model';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AdminModuleService {
|
||||
baseUrl = GlobalConstants.API_URL;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
|
||||
}
|
||||
|
||||
updateUserRoles(id: any,data: User): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/users/${id}`, data);
|
||||
}
|
||||
}
|
||||
+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();
|
||||
});
|
||||
});
|
||||
+11
@@ -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,10 @@
|
||||
<app-side-bar-admin
|
||||
class="pc-sidebar"
|
||||
>
|
||||
</app-side-bar-admin>
|
||||
<div class="pc-container">
|
||||
<router-outlet />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//---------------
|
||||
.pc-sidebar{
|
||||
top: 65px;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,95 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||
import {Router, RouterOutlet} from "@angular/router";
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import { ScrollPanelModule } from 'primeng/scrollpanel';
|
||||
import {MenuItem} from 'primeng/api';
|
||||
import {SideBarAdminComponent} from '../side-bar-admin.component/side-bar-admin.component';
|
||||
@Component({
|
||||
selector: 'app-admin.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
ScrollPanelModule,
|
||||
SideBarAdminComponent
|
||||
],
|
||||
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,30 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AdminComponent} from './admin.component/admin.component';
|
||||
|
||||
const ADMIN_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AdminComponent,
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'admin-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)
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
loadComponent: () => import('../admin-module/users.component/users.component').then((c) => c.UsersComponent)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const adminRouting = RouterModule.forChild(ADMIN_ROUTES);
|
||||
@@ -0,0 +1,27 @@
|
||||
<p>settings.component works!</p>
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Data Source properties">
|
||||
|
||||
<table mat-table
|
||||
[dataSource]="properties" 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>
|
||||
}
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let element; columns: displayedColumns;"
|
||||
class="example-element-row"
|
||||
>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+23
@@ -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,63 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {MatCard, MatCardContent} 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 {SystemService} from '../../../services/system.service';
|
||||
import {NameValueItem} from '../../../models/name-value-item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings.component',
|
||||
imports: [
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
MatDivider,
|
||||
MatTab,
|
||||
MatTabGroup,
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
|
||||
MatTable,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef
|
||||
],
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrl: './settings.component.scss'
|
||||
})
|
||||
export class SettingsComponent {
|
||||
properties: NameValueItem[] = [] ;
|
||||
|
||||
displayedColumns: string[] = ['name', 'value'];
|
||||
|
||||
|
||||
constructor(private systemService: SystemService) {
|
||||
this.retrieveProperties();
|
||||
}
|
||||
|
||||
|
||||
retrieveProperties(): void {
|
||||
this.systemService.getDataSourceProperties()
|
||||
.subscribe(
|
||||
(data: NameValueItem[]) => {
|
||||
this.properties = data;
|
||||
console.log(data);
|
||||
},
|
||||
(error: any) => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink="admin-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="users">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Users</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="settings">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Settings</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="system">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >System</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
.entry{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding:0.75rem;
|
||||
color: rgba(24, 255, 255, 0.96);
|
||||
|
||||
}
|
||||
|
||||
a.mdc-list-item
|
||||
{
|
||||
|
||||
background-color: rgba(24,255,255,0.04);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SideBarAdminComponent } from './side-bar-admin.component';
|
||||
|
||||
describe('SideBarAdminComponent', () => {
|
||||
let component: SideBarAdminComponent;
|
||||
let fixture: ComponentFixture<SideBarAdminComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SideBarAdminComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SideBarAdminComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatListItem, MatNavList} from '@angular/material/list';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-side-bar-admin',
|
||||
imports: [
|
||||
MatIcon,
|
||||
MatListItem,
|
||||
MatNavList,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './side-bar-admin.component.html',
|
||||
styleUrl: './side-bar-admin.component.scss',
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SideBarAdminComponent {
|
||||
isCollapsed = false;
|
||||
}
|
||||
@@ -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,137 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatCard, MatCardContent} 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,
|
||||
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,56 @@
|
||||
<table mat-table [dataSource]="users">
|
||||
@for (column of columnsSchema; track column){
|
||||
<ng-container [matColumnDef]="column.key">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{column.label}}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
@switch (column.type) {
|
||||
@case ('list') {
|
||||
<mat-form-field class="example-chip-list">
|
||||
<mat-chip-grid #chipGrid aria-label="Role selection">
|
||||
@for (role of element.roles; track $index) {
|
||||
<mat-chip-row (removed)="remove(role,element)">
|
||||
{{role.name}}
|
||||
|
||||
<mat-icon
|
||||
matChipRemove
|
||||
[attr.aria-label]="'remove ' + role.name"
|
||||
>
|
||||
cancel
|
||||
</mat-icon>
|
||||
|
||||
</mat-chip-row>
|
||||
}
|
||||
</mat-chip-grid>
|
||||
<input
|
||||
name="currentRole"
|
||||
placeholder="Add role..."
|
||||
[matChipInputFor]="chipGrid"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
[formControl]="rolesControl"
|
||||
[matChipInputAddOnBlur]="true"
|
||||
(matChipInputTokenEnd)="add($event,element)"
|
||||
(input)="change($event,filteredRoles(element.roles))"
|
||||
/>
|
||||
<mat-autocomplete [formControl]="ac"
|
||||
autoActiveFirstOption
|
||||
#auto
|
||||
(optionSelected)="selected(element,$event); ">
|
||||
@for (role of filteredRoles(element.roles); track role) {
|
||||
<mat-option [value]="role">{{role.name}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
}
|
||||
@default {
|
||||
{{ element[column.key] }}
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
}
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,3 @@
|
||||
.example-chip-list {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UsersComponent } from './users.component';
|
||||
|
||||
describe('UsersComponent', () => {
|
||||
let component: UsersComponent;
|
||||
let fixture: ComponentFixture<UsersComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UsersComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UsersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, model} from '@angular/core';
|
||||
import {UserService} from '../../../services/user.service';
|
||||
import {RolesService} from '../../../services/roles.service';
|
||||
import {Role} from '../../../models/role';
|
||||
import {User} from '../../../models/user.model';
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef, MatColumnDef,
|
||||
MatHeaderCell, MatHeaderCellDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef, MatTable
|
||||
} from '@angular/material/table';
|
||||
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {
|
||||
MatAutocomplete,
|
||||
MatAutocompleteSelectedEvent,
|
||||
MatAutocompleteTrigger,
|
||||
MatOption
|
||||
} from '@angular/material/autocomplete';
|
||||
import {
|
||||
MatChip,
|
||||
MatChipGrid,
|
||||
MatChipInput,
|
||||
MatChipInputEvent,
|
||||
MatChipRow,
|
||||
MatChipsModule
|
||||
} from '@angular/material/chips';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {COMMA, ENTER} from '@angular/cdk/keycodes';
|
||||
import {MatFormField} from '@angular/material/input';
|
||||
import {MatSelect, MatSelectTrigger} from '@angular/material/select';
|
||||
import {AdminModuleService} from '../admin-module.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users.component',
|
||||
imports: [
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatChipGrid,
|
||||
MatChipRow,
|
||||
MatIcon,
|
||||
MatAutocompleteTrigger,
|
||||
MatChipInput,
|
||||
MatAutocomplete,
|
||||
MatOption,
|
||||
MatFormField,
|
||||
MatChipsModule
|
||||
],
|
||||
templateUrl: './users.component.html',
|
||||
styleUrl: './users.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class UsersComponent {
|
||||
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
allRoles:Role[] = [];
|
||||
users: User[] = [];
|
||||
|
||||
displayedColumns: string[] = UserColumns.map((col) => col.key)
|
||||
columnsSchema: any = UserColumns
|
||||
|
||||
readonly currentRole = model('');
|
||||
|
||||
rolesControl = new FormControl([]);
|
||||
protected ac = new FormControl('');
|
||||
|
||||
constructor(private userService: UserService,
|
||||
private roleService: RolesService,
|
||||
private adminService:AdminModuleService) {
|
||||
this.getAllRoles();
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
getAllRoles():any{
|
||||
this.roleService.getAllRoles().subscribe(
|
||||
(data : any) => {
|
||||
this.allRoles = data;
|
||||
console.log(data);
|
||||
},
|
||||
(err : any)=> {
|
||||
this.allRoles = JSON.parse(err.error).message;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getUsers(){
|
||||
this.userService.getAdminBoard().subscribe(
|
||||
(data : any) => {
|
||||
this.users = data;
|
||||
console.log(data);
|
||||
},
|
||||
(err : any)=> {
|
||||
console.log(JSON.parse(err.error).message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
filteredRoles(roles : Role[]):any {
|
||||
return this.allRoles.filter(
|
||||
(r:Role) => !roles.some((item) => item.id === r.id),
|
||||
);
|
||||
}
|
||||
|
||||
change($event: Event, roles: Role[]) {
|
||||
roles.filter(
|
||||
(r:Role) => !roles.some((item) => item.name?.toLowerCase() === r.name?.toLowerCase()),
|
||||
)
|
||||
}
|
||||
|
||||
selected(user: User, $event: MatAutocompleteSelectedEvent) {
|
||||
user.roles.push($event.option.value);
|
||||
this.saveUserRoles(user);
|
||||
this.currentRole.set('');
|
||||
$event.option.deselect();
|
||||
}
|
||||
|
||||
add($event: MatChipInputEvent, user:User) {
|
||||
/*const value = ($event.value || '').trim();
|
||||
|
||||
const role = this.allRoles.find(value1 => value1.name === value);
|
||||
if(role){
|
||||
user.roles.push(value);
|
||||
this.saveUserRoles(user);
|
||||
}*/
|
||||
// Clear the input value
|
||||
this.currentRole.set('');
|
||||
}
|
||||
|
||||
remove(role: any, user: User) {
|
||||
const updatedRoles: Role[] = user.roles.filter(
|
||||
(r: Role) => r.id !== role.id // Simple comparison with the role to remove
|
||||
);
|
||||
|
||||
if (updatedRoles && updatedRoles.length >= 1) {
|
||||
user.roles = updatedRoles;
|
||||
this.saveUserRoles(user);
|
||||
}
|
||||
}
|
||||
|
||||
saveUserRoles(user:User){
|
||||
this.adminService.updateUserRoles(user.id, user).subscribe(
|
||||
(data:any) => {
|
||||
console.log(data);
|
||||
},
|
||||
(err:any) => {
|
||||
console.log(err);
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const UserColumns = [
|
||||
{
|
||||
key: 'username',
|
||||
type: 'text',
|
||||
label: 'Name',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
type: 'text',
|
||||
label: 'Email',
|
||||
},
|
||||
{
|
||||
key: 'roles',
|
||||
type: 'list',
|
||||
label: 'Roles',
|
||||
required: true,
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AiMainComponent } from './ai-main.component';
|
||||
|
||||
describe('AiMainComponent', () => {
|
||||
let component: AiMainComponent;
|
||||
let fixture: ComponentFixture<AiMainComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AiMainComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AiMainComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ai-main.component',
|
||||
imports: [
|
||||
RouterOutlet
|
||||
],
|
||||
templateUrl: './ai-main.component.html',
|
||||
styleUrl: './ai-main.component.scss'
|
||||
})
|
||||
export class AiMainComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>ai-welcome.component works!</p>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AiWelcomeComponent } from './ai-welcome.component';
|
||||
|
||||
describe('AiWelcomeComponent', () => {
|
||||
let component: AiWelcomeComponent;
|
||||
let fixture: ComponentFixture<AiWelcomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AiWelcomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AiWelcomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ai-welcome.component',
|
||||
imports: [],
|
||||
templateUrl: './ai-welcome.component.html',
|
||||
styleUrl: './ai-welcome.component.scss'
|
||||
})
|
||||
export class AiWelcomeComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {moderatorRouting} from '../moderator-module/moderator.routing';
|
||||
import {ModeratorComponent} from '../moderator-module/moderator.component/moderator.component';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {AiWelcomeComponent} from './ai-welcome.component/ai-welcome.component';
|
||||
import {AiMainComponent} from './ai-main.component/ai-main.component';
|
||||
import {aiRouting} from './ai.routing';
|
||||
|
||||
import {MatDialogModule} from "@angular/material/dialog";
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
aiRouting,
|
||||
AiMainComponent,
|
||||
CommonModule,
|
||||
|
||||
MatDialogModule
|
||||
]
|
||||
})
|
||||
export class AiModule { }
|
||||
@@ -0,0 +1,22 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AiMainComponent} from './ai-main.component/ai-main.component';
|
||||
|
||||
const AI_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AiMainComponent,
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'ai-welcome',
|
||||
loadComponent: () => import('../ai-module/ai-welcome.component/ai-welcome.component').then((c) => c.AiWelcomeComponent),
|
||||
},
|
||||
{
|
||||
path: 'generate-image',
|
||||
loadComponent: () => import('../ai-module/generate-image.component/generate-image.component').then((c) => c.GenerateImageComponent)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const aiRouting = RouterModule.forChild(AI_ROUTES);
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<div class="generate-image-view">
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Generate image</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Query</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="query">
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
|
||||
@if (spinnerService.visibility | async) {
|
||||
<mat-spinner [diameter]="25" ></mat-spinner>
|
||||
} @else {
|
||||
<button matButton (click)="generateImage()" >Generate</button>
|
||||
}
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
@for (image of images; track image) {
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<div class="image-container">
|
||||
<img src="{{image.url}}"
|
||||
alt="" width="100%" height="100%">
|
||||
<mat-toolbar class="image-toolbar">
|
||||
<mat-toolbar-row>
|
||||
<span class="menu-spacer"></span>
|
||||
<!-- <button matMiniFab matTooltip="Dawnload image" aria-label="Download" (click)="downloadImage(image.url)">
|
||||
<mat-icon>download</mat-icon>
|
||||
</button>-->
|
||||
@if (isLoggedIn) {
|
||||
<button matMiniFab matTooltip="Save to server" aria-label="Save" (click)="save(image.url)" >
|
||||
<mat-icon>upload_file</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
.generate-image-view {
|
||||
margin-right: 12px;
|
||||
padding: 20px 22px 18px 22px;
|
||||
margin-top: 81px;
|
||||
margin-left: 12px;
|
||||
background-color: #e7edf3c2;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.image-container{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
.image-container .image-toolbar
|
||||
{
|
||||
background-color: rgba(153,153,153,0);
|
||||
|
||||
color: white;
|
||||
position: absolute;
|
||||
left : 50%;
|
||||
top: 5%;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
padding: 0px 2px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.image-container .menu-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.image-container .image-file-name {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.mat-mdc-mini-fab{
|
||||
margin: 5px;
|
||||
}
|
||||
.example-full-width {
|
||||
width: 100%;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GenerateImageComponent } from './generate-image.component';
|
||||
|
||||
describe('GenerateImageComponent', () => {
|
||||
let component: GenerateImageComponent;
|
||||
let fixture: ComponentFixture<GenerateImageComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GenerateImageComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GenerateImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, inject, Renderer2} from '@angular/core';
|
||||
import {AsyncPipe, DOCUMENT, NgOptimizedImage} from "@angular/common";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatButton, MatMiniFabButton} from "@angular/material/button";
|
||||
import {
|
||||
MatCard,
|
||||
MatCardActions,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatCardImage,
|
||||
MatCardXlImage
|
||||
} from "@angular/material/card";
|
||||
import {MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
||||
import {Image} from '../../../models/image';
|
||||
import {ZhipuaiImageService} from '../zhipuai-image.service';
|
||||
import {SpinnerService} from '../../../services/spinner.service';
|
||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||
|
||||
import {FileUploadService} from '../../../services/file-upload-api.service';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {HttpClient, HttpResponse} from '@angular/common/http';
|
||||
import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {AuthService} from '../../../services/auth.service';
|
||||
import {TokenStorageService} from '../../../services/token-storage.service';
|
||||
import {FileInfo} from '../../../models/file-info';
|
||||
import {UserApiService} from '../../user-module/user-api.service';
|
||||
import {MatProgressBar} from '@angular/material/progress-bar';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-generate-image.component',
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
FormsModule,
|
||||
MatButton,
|
||||
MatCard,
|
||||
MatCardActions,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatFormField,
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatProgressSpinner,
|
||||
NgOptimizedImage,
|
||||
MatCardXlImage,
|
||||
MatCardImage,
|
||||
MatToolbarRow,
|
||||
MatToolbar,
|
||||
MatIcon,
|
||||
MatMiniFabButton,
|
||||
MatTooltip,
|
||||
MatProgressBar
|
||||
|
||||
|
||||
],
|
||||
templateUrl: './generate-image.component.html',
|
||||
styleUrl: './generate-image.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GenerateImageComponent {
|
||||
|
||||
isSpinnerVisible = false;
|
||||
|
||||
query : string = '';
|
||||
|
||||
images: Image[] = [];
|
||||
image: Image = {
|
||||
url: ''
|
||||
};
|
||||
|
||||
currentFile?: File;
|
||||
message = '';
|
||||
|
||||
|
||||
isLoggedIn = false;
|
||||
|
||||
private storageService: TokenStorageService = inject(TokenStorageService);
|
||||
|
||||
constructor(
|
||||
private zhipuaiImageService: ZhipuaiImageService,
|
||||
public spinnerService: SpinnerService,
|
||||
private uploadService: FileUploadService,
|
||||
private userApiService:UserApiService,
|
||||
|
||||
|
||||
) {
|
||||
|
||||
|
||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||
}
|
||||
|
||||
generateImage(){
|
||||
this.zhipuaiImageService.generate(this.query).subscribe(
|
||||
data => {
|
||||
|
||||
this.image = data;
|
||||
|
||||
this.images.unshift(this.image);
|
||||
|
||||
// this.output.file = data.;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
save(url: string | undefined) {
|
||||
let requestUrl:string = url?url:"";
|
||||
console.log(requestUrl);
|
||||
this.userApiService.saveZhipuaiImage(requestUrl).subscribe(data =>{
|
||||
console.log(data);
|
||||
},error =>{
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ZhipuaiImageService } from './zhipuai-image.service';
|
||||
|
||||
describe('ZhipuaiImageService', () => {
|
||||
let service: ZhipuaiImageService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ZhipuaiImageService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Image} from '../../models/image';
|
||||
import {SpinnerService} from '../../services/spinner.service';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
||||
|
||||
export class ZhipuaiImageService {
|
||||
baseUrl : string = `${GlobalConstants.API_URL}/public/zhipuai`;
|
||||
|
||||
constructor(private http: HttpClient,public spinnerService: SpinnerService) {
|
||||
|
||||
}
|
||||
|
||||
generate(query: String): Observable<Image> {
|
||||
return this.http.get(`${(this.baseUrl)}/image/${query}`);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<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 (click)="signup()">Sign Up</button>
|
||||
<span class="menu-spacer"></span>
|
||||
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
|
||||
<button mat-button cdkFocusInitial (click)="login()">Ok</button>
|
||||
</div>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
.menu-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.fillField{
|
||||
width: 100%;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DialogLoginComponent } from './dialog-login.component';
|
||||
|
||||
describe('DialogLoginComponent', () => {
|
||||
let component: DialogLoginComponent;
|
||||
let fixture: ComponentFixture<DialogLoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DialogLoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DialogLoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
CUSTOM_ELEMENTS_SCHEMA,
|
||||
EventEmitter, Inject,
|
||||
inject,
|
||||
Output,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MatButton, MatIconButton} from '@angular/material/button';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef,
|
||||
MatDialogTitle
|
||||
} from '@angular/material/dialog';
|
||||
import {MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {DialogLoginData} from '../main.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-login.component',
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatButton,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogTitle,
|
||||
MatFormField,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatSuffix
|
||||
],
|
||||
templateUrl: './dialog-login.component.html',
|
||||
styleUrl: './dialog-login.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DialogLoginComponent {
|
||||
readonly dialogRef = inject(MatDialogRef<DialogLoginComponent>);
|
||||
hide = signal(true);
|
||||
|
||||
@Output() loginClicked = new EventEmitter<any>();
|
||||
@Output() signupClicked = new EventEmitter<any>();
|
||||
|
||||
private _snackBar = inject(MatSnackBar);
|
||||
durationInSeconds = 5;
|
||||
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data:DialogLoginData) {
|
||||
|
||||
}
|
||||
|
||||
openLoginFailedSnackBar(errorMessage : string = "Login failed.") {
|
||||
this._snackBar.open(errorMessage , "", {
|
||||
duration: this.durationInSeconds * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
onNoClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
|
||||
clickEvent(event: MouseEvent) {
|
||||
this.hide.set(!this.hide());
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
login() {
|
||||
this.loginClicked.emit(this.data);
|
||||
|
||||
}
|
||||
|
||||
signup() {
|
||||
this.signupClicked.emit();
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<h1 mat-dialog-title>Hi </h1>
|
||||
<form [formGroup]="signUpForm">
|
||||
<div mat-dialog-content>
|
||||
<p>User name</p>
|
||||
<mat-form-field class="fillField">
|
||||
<input matInput
|
||||
formControlName="username"
|
||||
[(ngModel)]="data.username"
|
||||
minlength="3"
|
||||
>
|
||||
</mat-form-field>
|
||||
<p>Email</p>
|
||||
<mat-form-field class="fillField">
|
||||
<mat-label>Email</mat-label>
|
||||
<input matInput type="email"
|
||||
formControlName="email"
|
||||
|
||||
placeholder="Ex. pat@example.com"
|
||||
[(ngModel)]="data.email"
|
||||
(blur)="updateErrorMessage()"
|
||||
required
|
||||
>
|
||||
@if (emailFormControl.invalid) {
|
||||
<mat-error>{{errorMessage()}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<p>Password</p>
|
||||
<mat-form-field class="fillField">
|
||||
<mat-label>Enter your password</mat-label>
|
||||
<input matInput
|
||||
|
||||
formControlName = "password"
|
||||
[type]="hide() ? 'password' : 'text'"
|
||||
[(ngModel)]="data.password"
|
||||
minlength="6"
|
||||
required
|
||||
/>
|
||||
<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>
|
||||
<p>Confirm password</p>
|
||||
<mat-form-field class="fillField">
|
||||
<mat-label>Confirm your password</mat-label>
|
||||
<input matInput
|
||||
|
||||
formControlName = "confirmPassword"
|
||||
[type]="hide() ? 'password' : 'text'"
|
||||
[(ngModel)]="data.confirmPassword"
|
||||
/>
|
||||
@if (signUpForm.get("confirmPassword")?.value !== signUpForm.get("password")?.value) {
|
||||
<mat-error>Passwords do not match</mat-error>
|
||||
}
|
||||
<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 (click)="login()">Login</button>
|
||||
<span class="menu-spacer"></span>
|
||||
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
|
||||
<button mat-button cdkFocusInitial (click)="signup()">Ok</button>
|
||||
</div>
|
||||
</form>
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
.menu-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.fillField{
|
||||
width: 100%;
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DialogSignupComponent } from './dialog-signup.component';
|
||||
|
||||
describe('DialogSignupComponent', () => {
|
||||
let component: DialogSignupComponent;
|
||||
let fixture: ComponentFixture<DialogSignupComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DialogSignupComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DialogSignupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, Inject, inject, OnInit, Output, signal} from '@angular/core';
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
FormGroupDirective,
|
||||
FormsModule,
|
||||
NgForm,
|
||||
ReactiveFormsModule, ValidationErrors, ValidatorFn,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef,
|
||||
MatDialogTitle
|
||||
} from '@angular/material/dialog';
|
||||
import {MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
import {DialogSignupData} from '../main.component';
|
||||
import {ErrorStateMatcher} from '@angular/material/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog-signup.component',
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatButtonModule,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogTitle,
|
||||
MatFormField,
|
||||
MatIcon,
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatSuffix,
|
||||
MatFormField,
|
||||
ReactiveFormsModule,
|
||||
|
||||
],
|
||||
templateUrl: './dialog-signup.component.html',
|
||||
styleUrl: './dialog-signup.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class DialogSignupComponent implements OnInit {
|
||||
readonly dialogRef = inject(MatDialogRef<DialogSignupComponent>);
|
||||
hide = signal(true);
|
||||
|
||||
@Output() signupClicked = new EventEmitter<any>();
|
||||
@Output() loginClicked = new EventEmitter<any>();
|
||||
|
||||
private _snackBar = inject(MatSnackBar);
|
||||
|
||||
durationInSeconds = 5;
|
||||
|
||||
emailFormControl = new FormControl('', [Validators.required, Validators.email]);
|
||||
|
||||
matcher = new MyErrorStateMatcher();
|
||||
|
||||
errorMessage = signal('');
|
||||
|
||||
signUpForm = new FormGroup({
|
||||
username: new FormControl('', [Validators.required]),
|
||||
email: new FormControl('', [Validators.required, Validators.email]),
|
||||
password: new FormControl('', [Validators.required]),
|
||||
confirmPassword: new FormControl('', [Validators.required, this.validateSamePassword]),
|
||||
});
|
||||
minPw = 8;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data:DialogSignupData,
|
||||
private formBuilder: FormBuilder) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
updateErrorMessage() {
|
||||
if (this.emailFormControl.hasError('required')) {
|
||||
this.errorMessage.set('You must enter a value');
|
||||
} else if (this.emailFormControl.hasError('email')) {
|
||||
this.errorMessage.set('Not a valid email');
|
||||
} else {
|
||||
this.errorMessage.set('');
|
||||
}
|
||||
}
|
||||
|
||||
clickEvent(event: MouseEvent) {
|
||||
this.hide.set(!this.hide());
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onNoClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
openSignupFailedSnackBar(errorMessage : string = "Sign up failed.") {
|
||||
this._snackBar.open(errorMessage , "", {
|
||||
duration: this.durationInSeconds * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
openSignupSuccessSnackBar(message : string = "User registered successfully!") {
|
||||
this._snackBar.open(message, "", {
|
||||
duration: this.durationInSeconds * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
signup() {
|
||||
if (!this.signUpForm.invalid) {
|
||||
this.signupClicked.emit(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
login() {
|
||||
this.loginClicked.emit();
|
||||
}
|
||||
|
||||
private validateSamePassword(control: AbstractControl): ValidationErrors | null {
|
||||
const password = control.parent?.get('password');
|
||||
const confirmPassword = control.parent?.get('confirmPassword');
|
||||
return password?.value == confirmPassword?.value ? null : { 'notSame': true };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Error when invalid control is dirty, touched, or submitted. */
|
||||
export class MyErrorStateMatcher implements ErrorStateMatcher {
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const isSubmitted = form && form.submitted;
|
||||
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<mat-toolbar class="fixed-top">
|
||||
<button mat-button routerLink="">
|
||||
{{title}}
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./ai/generate-image">
|
||||
Generate Image
|
||||
</button>
|
||||
<button mat-raised-button routerLink="./tutorials/tutorials-all">
|
||||
Tutorials
|
||||
</button>
|
||||
|
||||
<span class="menu-spacer"></span>
|
||||
|
||||
@if (!isLoggedIn) {
|
||||
<button matMiniFab matTooltip="Sign Up" aria-label="Sign Up" (click)="openSignupDialog('100ms', '5ms')">
|
||||
<mat-icon>person_add</mat-icon>
|
||||
</button>
|
||||
<button matMiniFab matTooltip="Login" aria-label="Login" (click)="openLoginDialog('100ms', '5ms')" >
|
||||
<mat-icon>login</mat-icon>
|
||||
</button>
|
||||
}@else {
|
||||
<button matButton="elevated" aria-label="Account" [matMenuTriggerFor]="menu">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
<mat-label>{{username}}</mat-label>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
@if (showAdminBoard) {
|
||||
<button mat-menu-item routerLink="admin/admin-welcome">
|
||||
<mat-icon>admin_panel_settings</mat-icon>
|
||||
Admin Panel
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (showUserBoard) {
|
||||
<button mat-menu-item routerLink="user/user-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
User tools
|
||||
</button>
|
||||
}
|
||||
@if (showModeratorBoard) {
|
||||
<button mat-menu-item routerLink="moderator/moderator-welcome">
|
||||
<mat-icon>space_dashboard</mat-icon>
|
||||
Moderator board
|
||||
</button>
|
||||
}
|
||||
|
||||
<button mat-menu-item routerLink="profile">
|
||||
<mat-icon>person</mat-icon>
|
||||
Profile
|
||||
</button>
|
||||
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon>logout</mat-icon>
|
||||
Logout
|
||||
</button>
|
||||
</mat-menu>
|
||||
}
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
<div class="router_outlet">
|
||||
<router-outlet/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
mat-toolbar{
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.menu-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.fixed-top {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1000; // Ensure toolbar stays above other content
|
||||
height: 65px;
|
||||
}
|
||||
.mat-mdc-raised-button:not(:disabled){
|
||||
color: rgba(24, 255, 255, 0.96);
|
||||
background-color: rgba(24, 255, 255, 0);
|
||||
}
|
||||
.mat-mdc-button:not(:disabled){
|
||||
color: rgba(24, 255, 255, 0.96);
|
||||
background-color: rgba(24, 255, 255, 0);
|
||||
}
|
||||
.mat-mdc-mini-fab{
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.router_outlet{
|
||||
margin-top: 65px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MainComponent } from './main.component';
|
||||
|
||||
describe('MainComponent', () => {
|
||||
let component: MainComponent;
|
||||
let fixture: ComponentFixture<MainComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MainComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MainComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||
import {Router, RouterLink, RouterOutlet} from '@angular/router';
|
||||
import {MatToolbar} from '@angular/material/toolbar';
|
||||
import {MatButton, MatFabButton, MatMiniFabButton} from '@angular/material/button';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {TokenStorageService} from '../../../services/token-storage.service';
|
||||
import {AuthService} from '../../../services/auth.service';
|
||||
import {EventBusService} from '../../../_shared/event-bus.service';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
import {MatLabel} from '@angular/material/input';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import {DialogLoginComponent} from './dialog-login.component/dialog-login.component';
|
||||
import {DialogSignupComponent} from './dialog-signup.component/dialog-signup.component';
|
||||
import {GlobalConstants} from '../../../global-constants';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main.component',
|
||||
imports: [
|
||||
MatToolbar,
|
||||
MatButton,
|
||||
RouterLink,
|
||||
RouterOutlet,
|
||||
CommonModule,
|
||||
MatIcon,
|
||||
MatMiniFabButton,
|
||||
MatMenuTrigger,
|
||||
MatMenu,
|
||||
MatMenuItem,
|
||||
MatTooltip,
|
||||
MatLabel
|
||||
],
|
||||
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
|
||||
templateUrl: './main.component.html',
|
||||
styleUrl: './main.component.scss'
|
||||
})
|
||||
export class MainComponent implements OnInit{
|
||||
title = GlobalConstants.TITLE;
|
||||
|
||||
// public props
|
||||
navCollapsed: boolean = false;
|
||||
navCollapsedMob: boolean = false;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
//private authService = new AuthService(provideHttpClient())
|
||||
public dialogLoginData : DialogLoginData = {password: "", username: ""};
|
||||
public dialogSignupData : DialogSignupData = {password: "", username: "", email: "", confirmPassword: ""};
|
||||
|
||||
|
||||
private roles: string[] = [];
|
||||
isLoggedIn = false;
|
||||
showAdminBoard = false;
|
||||
showModeratorBoard = false;
|
||||
showUserBoard = false;
|
||||
username?: string;
|
||||
|
||||
eventBusSub?: Subscription;
|
||||
|
||||
private environment = environment;
|
||||
|
||||
private storageService: TokenStorageService = inject(TokenStorageService);
|
||||
private authService: AuthService = inject(AuthService);
|
||||
private eventBusService: EventBusService = inject(EventBusService);
|
||||
|
||||
constructor(private router: Router,private http: HttpClient) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refreshToolbar();
|
||||
|
||||
this.eventBusSub = this.eventBusService.on('logout', () => {
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
|
||||
refreshToolbar() {
|
||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||
if (this.isLoggedIn) {
|
||||
const user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
|
||||
this.showAdminBoard = this.roles.includes('ROLE_ADMIN');
|
||||
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
|
||||
this.showUserBoard = this.roles.includes('ROLE_USER');
|
||||
|
||||
this.username = user.username;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.eventBusSub) {
|
||||
this.eventBusSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout().subscribe({
|
||||
next: res => {
|
||||
console.log(res);
|
||||
this.storageService.clean();
|
||||
|
||||
//window.location.reload();
|
||||
this.router.navigate([GlobalConstants.DEFAULT_PAGE]).then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
//window.location.reload();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openLoginDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||
|
||||
let dialogLoginRef = this.dialog.open(DialogLoginComponent, {
|
||||
width: '350px',
|
||||
enterAnimationDuration,
|
||||
exitAnimationDuration,
|
||||
data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password}
|
||||
});
|
||||
dialogLoginRef.componentInstance.signupClicked.subscribe(result => {
|
||||
dialogLoginRef.close();
|
||||
this.openSignupDialog(enterAnimationDuration, exitAnimationDuration);
|
||||
})
|
||||
|
||||
const dialogSubmitSubscription = dialogLoginRef.componentInstance.loginClicked
|
||||
.subscribe(result => {
|
||||
console.log('Got the data!', result);
|
||||
|
||||
if(result== null){
|
||||
return;
|
||||
}
|
||||
this.dialogLoginData = result;
|
||||
|
||||
this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe(
|
||||
data => {
|
||||
|
||||
|
||||
this.storageService.saveUser(data);
|
||||
|
||||
this.isLoggedIn = true;
|
||||
let user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
|
||||
this.refreshToolbar();
|
||||
dialogLoginRef.close()
|
||||
|
||||
|
||||
this.router.navigate([GlobalConstants.DEFAULT_PAGE]);
|
||||
},
|
||||
err => {
|
||||
dialogLoginRef.componentInstance.openLoginFailedSnackBar(err.error.message);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
openSignupDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||
|
||||
let dialogSignupRef = this.dialog.open(DialogSignupComponent, {
|
||||
width: '350px',
|
||||
enterAnimationDuration,
|
||||
exitAnimationDuration,
|
||||
data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password}
|
||||
});
|
||||
|
||||
const dialogloginSubscription = dialogSignupRef.componentInstance.loginClicked.subscribe(result => {
|
||||
dialogSignupRef.close();
|
||||
this.openLoginDialog(enterAnimationDuration, exitAnimationDuration);
|
||||
})
|
||||
|
||||
const dialogSubmitSubscription = dialogSignupRef.componentInstance.signupClicked
|
||||
.subscribe(result => {
|
||||
console.log('Got the data!', result);
|
||||
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
this.dialogSignupData = result;
|
||||
|
||||
this.authService.register(this.dialogSignupData.username, this.dialogSignupData.email, this.dialogSignupData.password).subscribe(
|
||||
data => {
|
||||
console.log(data);
|
||||
dialogSignupRef.componentInstance.openSignupSuccessSnackBar(data.message)
|
||||
dialogSignupRef.close();
|
||||
},
|
||||
err => {
|
||||
dialogSignupRef.componentInstance.openSignupFailedSnackBar(err.error.message);
|
||||
}
|
||||
);
|
||||
|
||||
// do something here with the data
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
export interface DialogLoginData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface DialogSignupData {
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {MainComponent} from './main.component/main.component';
|
||||
import {mainRouting} from './main.routing';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
|
||||
import {CustomHttpInterceptor} from '../../helpers/custom-http-interceptor';
|
||||
import {authInterceptorProviders} from '../../helpers/auth.interceptor';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
||||
],
|
||||
imports: [
|
||||
mainRouting,
|
||||
MainComponent
|
||||
],
|
||||
exports: [RouterModule],
|
||||
providers: [authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CustomHttpInterceptor,
|
||||
multi: true
|
||||
}],
|
||||
})
|
||||
export class MainModule {}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {MainComponent} from './main.component/main.component';
|
||||
|
||||
const MAIN_ROUTES: Routes =[
|
||||
{
|
||||
path: '',
|
||||
component: MainComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'tutorials',
|
||||
loadChildren: () =>
|
||||
import('../tutorials-module/tutorials.module').then((m) => m.TutorialsModule),
|
||||
},
|
||||
{
|
||||
path: 'ai',
|
||||
loadChildren: () =>
|
||||
import('../ai-module/ai.module').then((m) => m.AiModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () =>
|
||||
import('../admin-module/admin.module').then((m) => m.AdminModule),
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
loadChildren: () =>
|
||||
import('../user-module/user.module').then((m) => m.UserModule),
|
||||
},
|
||||
{
|
||||
path: 'moderator',
|
||||
loadChildren: () =>
|
||||
import('../moderator-module/moderator.module').then((m) => m.ModeratorModule),
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
export const mainRouting = RouterModule.forChild(MAIN_ROUTES);
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModeratorApiService } from './moderator-api.service';
|
||||
|
||||
describe('ModeratorApiService', () => {
|
||||
let service: ModeratorApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ModeratorApiService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ModeratorApiService {
|
||||
baseUrl = `${GlobalConstants.API_URL}/moderator`;
|
||||
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
|
||||
}
|
||||
|
||||
getBePublishedTutorials(): Observable<Tutorial[]> {
|
||||
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
|
||||
}
|
||||
|
||||
getTutorial(id: string | null | undefined): Observable<Tutorial> {
|
||||
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
|
||||
}
|
||||
|
||||
publish(id: any,data: any): Observable<any> {
|
||||
return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data);
|
||||
}
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<p>moderator-welcome.component works!</p>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModeratorWelcomeComponent } from './moderator-welcome.component';
|
||||
|
||||
describe('ModeratorWelcomeComponent', () => {
|
||||
let component: ModeratorWelcomeComponent;
|
||||
let fixture: ComponentFixture<ModeratorWelcomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ModeratorWelcomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ModeratorWelcomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-moderator-welcome.component',
|
||||
imports: [],
|
||||
templateUrl: './moderator-welcome.component.html',
|
||||
styleUrl: './moderator-welcome.component.scss'
|
||||
})
|
||||
export class ModeratorWelcomeComponent {
|
||||
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
<app-side-bar-moderator class="pc-sidebar" ></app-side-bar-moderator>
|
||||
<div class="pc-container">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//---------------
|
||||
.pc-sidebar{
|
||||
top: 65px;
|
||||
overflow-y: auto;
|
||||
background-color: rgba(153, 153, 153, 0.16);
|
||||
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.pc-container{
|
||||
top: 0px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModeratorComponent } from './moderator.component';
|
||||
|
||||
describe('ModeratorComponent', () => {
|
||||
let component: ModeratorComponent;
|
||||
let fixture: ComponentFixture<ModeratorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ModeratorComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ModeratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {SideBarModeratorComponent} from '../side-bar-moderator.component/side-bar-moderator.component';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-moderator.component',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
SideBarModeratorComponent,
|
||||
|
||||
],
|
||||
templateUrl: './moderator.component.html',
|
||||
styleUrl: './moderator.component.scss'
|
||||
})
|
||||
export class ModeratorComponent {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {moderatorRouting} from './moderator.routing';
|
||||
import {ModeratorComponent} from './moderator.component/moderator.component';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
moderatorRouting,
|
||||
ModeratorComponent,
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class ModeratorModule { }
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ModeratorRouting } from './moderator.routing';
|
||||
|
||||
describe('ModeratorRouting', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new ModeratorRouting()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
|
||||
import {ModeratorComponent} from './moderator.component/moderator.component';
|
||||
|
||||
const MODERATOR_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ModeratorComponent,
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'moderator-welcome',
|
||||
loadComponent: () => import('../moderator-module/moderator-welcome.component/moderator-welcome.component').then((c) => c.ModeratorWelcomeComponent),
|
||||
},
|
||||
{
|
||||
path: 'tutorials-list',
|
||||
loadComponent: () => import('../moderator-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent)
|
||||
},
|
||||
{
|
||||
path: 'tutorial-preview',
|
||||
loadComponent: () => import('../moderator-module/tutorial-preview.component/tutorial-preview.component').then((c) => c.TutorialPreviewComponent)
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const moderatorRouting = RouterModule.forChild(MODERATOR_ROUTES);
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink="moderator-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="tutorials-list">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Tutorials</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
.entry{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding:0.75rem;
|
||||
color: rgba(24, 255, 255, 0.96);
|
||||
|
||||
}
|
||||
|
||||
a.mdc-list-item
|
||||
{
|
||||
|
||||
background-color: rgba(24,255,255,0.04);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SideBarModeratorComponent } from './side-bar-moderator.component';
|
||||
|
||||
describe('SideBarModeratorComponent', () => {
|
||||
let component: SideBarModeratorComponent;
|
||||
let fixture: ComponentFixture<SideBarModeratorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SideBarModeratorComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SideBarModeratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatListItem, MatNavList} from '@angular/material/list';
|
||||
import {RouterLink} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-side-bar-moderator',
|
||||
imports: [
|
||||
MatIcon,
|
||||
MatListItem,
|
||||
MatNavList,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './side-bar-moderator.component.html',
|
||||
styleUrl: './side-bar-moderator.component.scss',
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class SideBarModeratorComponent {
|
||||
isCollapsed = false;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Edit tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content >
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="tutorial.title">
|
||||
</mat-form-field>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Preview">
|
||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Edit">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
|
||||
<button matButton (click)="publishTutorial()">Publicate</button>
|
||||
@if (hasError){
|
||||
<mat-error>
|
||||
{{errorMessage}}
|
||||
</mat-error>
|
||||
}
|
||||
@if(submitted) {
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
|
||||
<button matButton routerLink="../tutorials-list">Back to list</button>
|
||||
}
|
||||
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
.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%;
|
||||
}
|
||||
|
||||
|
||||
.variable-binding,
|
||||
.variable-textarea {
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.variable-textarea {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.07);
|
||||
min-height: 420px;
|
||||
padding: 8px;
|
||||
transition: all 300ms ease-out;
|
||||
}
|
||||
|
||||
.variable-textarea:hover {
|
||||
box-shadow: 0 6px 12px 3px rgba(0,0,0,.09),
|
||||
0 2px 3px 1px rgba(0,0,0,.06);
|
||||
}
|
||||
|
||||
.variable-binding {
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.preview {
|
||||
/* display: block;
|
||||
float: right;*/
|
||||
}
|
||||
|
||||
.mat-mdc-card-outlined {
|
||||
background-color: var(--mat-card-outlined-container-color, var(--mat-sys-surface));
|
||||
border-radius:0;
|
||||
border-width: var(--mat-card-outlined-outline-width, 1px);
|
||||
border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant));
|
||||
box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0));
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialPreviewComponent } from './tutorial-preview.component';
|
||||
|
||||
describe('TutorialPreviewComponent', () => {
|
||||
let component: TutorialPreviewComponent;
|
||||
let fixture: ComponentFixture<TutorialPreviewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialPreviewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialPreviewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MarkdownComponent} from "ngx-markdown";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from "@angular/material/card";
|
||||
import {MatError, MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
||||
import {MatTab, MatTabGroup} from "@angular/material/tabs";
|
||||
import {ActivatedRoute, RouterLink} from "@angular/router";
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {ModeratorApiService} from '../moderator-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-preview.component',
|
||||
imports: [
|
||||
FormsModule,
|
||||
MarkdownComponent,
|
||||
MatButton,
|
||||
MatCard,
|
||||
MatCardActions,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatError,
|
||||
MatFormField,
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatTab,
|
||||
MatTabGroup,
|
||||
RouterLink,
|
||||
MatError,
|
||||
MatFormField
|
||||
],
|
||||
templateUrl: './tutorial-preview.component.html',
|
||||
styleUrl: './tutorial-preview.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class TutorialPreviewComponent {
|
||||
tutorial: Tutorial = new Tutorial();
|
||||
submitted = false;
|
||||
hasError = false;
|
||||
errorMessage = '';
|
||||
markdown = `## Markdown __rulez__!
|
||||
---
|
||||
|
||||
### Syntax highlight
|
||||
\`\`\`typescript
|
||||
const language = 'typescript';
|
||||
\`\`\`
|
||||
|
||||
### Lists
|
||||
1. Ordered list
|
||||
2. Another bullet point
|
||||
- Unordered list
|
||||
- Another unordered bullet
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
|
||||
private id: string | null | undefined;
|
||||
|
||||
constructor(private moderatorApiService: ModeratorApiService, private route: ActivatedRoute) {
|
||||
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
console.log(params);
|
||||
this.id = params['id'];
|
||||
console.log(this.id);
|
||||
});
|
||||
|
||||
this.moderatorApiService.getTutorial(this.id).subscribe(
|
||||
data=>{
|
||||
this.tutorial = data;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
publishTutorial(): void {
|
||||
this.tutorial.published = true;
|
||||
this.moderatorApiService.publish(this.id, this.tutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
this.hasError = false;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
|
||||
this.errorMessage = error.error.message;
|
||||
|
||||
this.hasError = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<table mat-table [dataSource]="dataSource">
|
||||
@for (column of columnsSchema; track column){
|
||||
<ng-container [matColumnDef]="column.key">
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{column.label}}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
@switch (column.type) {
|
||||
|
||||
@case('isEdit') {
|
||||
<div class="btn-edit" >
|
||||
<button mat-button routerLink='../tutorial-preview' [queryParams]="{id:element.id}" (click)="element.isEdit = !element.isEdit">
|
||||
Preview
|
||||
</button>
|
||||
|
||||
</div>
|
||||
}
|
||||
@case ('boolean') {
|
||||
<mat-slide-toggle
|
||||
class="example-margin"
|
||||
[checked]="element[column.key]"
|
||||
|
||||
(change)="publish(element, $event.checked)"
|
||||
>
|
||||
|
||||
</mat-slide-toggle>
|
||||
}
|
||||
@case ('datetime') {
|
||||
{{ element[column.key] | date: 'medium' }}
|
||||
}
|
||||
@default {
|
||||
{{ element[column.key] }}
|
||||
}
|
||||
}
|
||||
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
}
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialsListComponent } from './tutorials-list.component';
|
||||
|
||||
describe('TutorialsListComponent', () => {
|
||||
let component: TutorialsListComponent;
|
||||
let fixture: ComponentFixture<TutorialsListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialsListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialsListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {DatePipe} from "@angular/common";
|
||||
import {MatButton} from "@angular/material/button";
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef, MatColumnDef,
|
||||
MatHeaderCell, MatHeaderCellDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef, MatTable, MatTableDataSource
|
||||
} from "@angular/material/table";
|
||||
import {MatSlideToggle} from "@angular/material/slide-toggle";
|
||||
import {Router, RouterLink} from "@angular/router";
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {EventData} from '../../../_shared/event.class';
|
||||
import {ModeratorApiService} from '../moderator-api.service';
|
||||
import {TokenStorageService} from '../../../services/token-storage.service';
|
||||
import {EventBusService} from '../../../_shared/event-bus.service';
|
||||
import {AuthService} from '../../../services/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-list.component',
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatButton,
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef,
|
||||
MatSlideToggle,
|
||||
MatTable,
|
||||
RouterLink,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef
|
||||
],
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
styleUrl: './tutorials-list.component.scss'
|
||||
})
|
||||
export class TutorialsListComponent {
|
||||
displayedColumns: string[] = TutorialColumns.map((col) => col.key)
|
||||
columnsSchema: any = TutorialColumns
|
||||
dataSource = new MatTableDataSource<Tutorial>()
|
||||
|
||||
|
||||
|
||||
constructor(private moderatorApiService: ModeratorApiService,
|
||||
private storageService: TokenStorageService,
|
||||
private eventBusService: EventBusService,
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) {
|
||||
this.getTutorials();
|
||||
}
|
||||
getTutorials(): void {
|
||||
this.moderatorApiService.getBePublishedTutorials()
|
||||
.subscribe(( data:Tutorial[] ) => {
|
||||
this.dataSource.data = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
if (
|
||||
(
|
||||
error.status === 401
|
||||
|
||||
)
|
||||
&& this.storageService.isLoggedIn()
|
||||
) {
|
||||
this.eventBusService.emit(new EventData('logout', null));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
publish(element: any, checked: boolean){
|
||||
element.published = checked;
|
||||
this.moderatorApiService.publish(element.id, element).subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout().subscribe({
|
||||
next: res => {
|
||||
console.log(res);
|
||||
this.storageService.clean();
|
||||
|
||||
//window.location.reload();
|
||||
this.router.navigate(['main/generate-image']).then(() => {
|
||||
//window.location.reload();
|
||||
})
|
||||
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const TutorialColumns = [
|
||||
|
||||
{
|
||||
key: 'title',
|
||||
type: 'text',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'published',
|
||||
type: 'boolean',
|
||||
label: 'Is Published',
|
||||
},
|
||||
{
|
||||
key: 'created',
|
||||
type: 'datetime',
|
||||
label: 'Created Date',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'modified',
|
||||
type: 'datetime',
|
||||
label: 'Modified Date',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'isEdit',
|
||||
type: 'isEdit',
|
||||
label: '',
|
||||
}
|
||||
];
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<div class="tutorial-view">
|
||||
<img style="width: 600px; height: 500px;" src="{{tutorial.titleimage}}" alt="">
|
||||
<markdown class="tutorial-body" [data]="tutorial.body"></markdown>
|
||||
</div>
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
.tutorial-view {
|
||||
margin-right: 12px;
|
||||
padding: 20px 12px 12px 31px;
|
||||
margin-top: 81px;
|
||||
margin-left: 12px;
|
||||
background-color: #e7edf3c2;
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tutorial-body{
|
||||
width: 100%;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialViewComponent } from './tutorial-view.component';
|
||||
|
||||
describe('TutorialViewComponent', () => {
|
||||
let component: TutorialViewComponent;
|
||||
let fixture: ComponentFixture<TutorialViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {ModeratorApiService} from '../../moderator-module/moderator-api.service';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {TutorialsApiService} from '../tutorials-api.service';
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {MarkdownComponent} from 'ngx-markdown';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-view.component',
|
||||
imports: [
|
||||
MarkdownComponent
|
||||
],
|
||||
templateUrl: './tutorial-view.component.html',
|
||||
styleUrl: './tutorial-view.component.scss'
|
||||
})
|
||||
export class TutorialViewComponent {
|
||||
private id: string | null | undefined;
|
||||
tutorial: Tutorial = new Tutorial();
|
||||
|
||||
constructor(private tutorialsApiService: TutorialsApiService, private route: ActivatedRoute) {
|
||||
|
||||
this.route.queryParams
|
||||
.subscribe(params => {
|
||||
console.log(params);
|
||||
this.id = params['id'];
|
||||
console.log(this.id);
|
||||
});
|
||||
|
||||
this.tutorialsApiService.getTutorial(this.id).subscribe(
|
||||
data => {
|
||||
this.tutorial = data;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialsApiService } from './tutorials-api.service';
|
||||
|
||||
describe('TutorialsApiService', () => {
|
||||
let service: TutorialsApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TutorialsApiService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {GlobalConstants} from '../../global-constants';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TutorialsApiService {
|
||||
|
||||
baseUrl = `${GlobalConstants.API_URL}/public`;
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getAllPublic(): Observable<Tutorial[]> {
|
||||
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
|
||||
}
|
||||
|
||||
getTutorial(id: string | null | undefined): Observable<Tutorial> {
|
||||
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
|
||||
}}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<div class="container-card-view">
|
||||
@for(tutorial of tutorials; track $index){
|
||||
<mat-card>
|
||||
<img mat-card-image [src]="tutorial.titleimage" alt="Photo of a" />
|
||||
|
||||
<mat-card-actions>
|
||||
<button mat-button routerLink="../tutorial-view" [queryParams]="{id:tutorial.id}" target="_blank">{{tutorial.title}}</button>
|
||||
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
//mat-card{
|
||||
// width: 20%;
|
||||
//}
|
||||
|
||||
.container-card-view{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
padding: 24px;
|
||||
//width: 100%;
|
||||
//height: 100%;
|
||||
//padding-top: 20px;
|
||||
//display: flex;
|
||||
//flex-wrap: wrap;
|
||||
//flex-direction: row;
|
||||
//align-content: flex-start;
|
||||
//justify-content: space-around;
|
||||
//align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.responsive-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialsCardViewComponent } from './tutorials-card-view.component';
|
||||
|
||||
describe('TutorialsCardViewComponent', () => {
|
||||
let component: TutorialsCardViewComponent;
|
||||
let fixture: ComponentFixture<TutorialsCardViewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialsCardViewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialsCardViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Tutorial} from '../../../models/tutorial.model';
|
||||
import {MatCard, MatCardActions, MatCardImage} from '@angular/material/card';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {TutorialsApiService} from '../tutorials-api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-card-view.component',
|
||||
imports: [
|
||||
MatCard,
|
||||
MatCardActions,
|
||||
RouterLink,
|
||||
MatButton,
|
||||
MatCardImage
|
||||
],
|
||||
templateUrl: './tutorials-card-view.component.html',
|
||||
styleUrl: './tutorials-card-view.component.scss'
|
||||
})
|
||||
export class TutorialsCardViewComponent {
|
||||
tutorials: Tutorial[] = [];
|
||||
|
||||
constructor(private tutorialsApiService: TutorialsApiService) {
|
||||
this.retrieveTutorials();
|
||||
}
|
||||
|
||||
retrieveTutorials(): void {
|
||||
this.tutorialsApiService.getAllPublic().subscribe(
|
||||
(data : Tutorial[]) =>{
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<p>tutorials-welcome.component works!</p>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialsWelcomeComponent } from './tutorials-welcome.component';
|
||||
|
||||
describe('TutorialsWelcomeComponent', () => {
|
||||
let component: TutorialsWelcomeComponent;
|
||||
let fixture: ComponentFixture<TutorialsWelcomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TutorialsWelcomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TutorialsWelcomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-welcome.component',
|
||||
imports: [],
|
||||
templateUrl: './tutorials-welcome.component.html',
|
||||
styleUrl: './tutorials-welcome.component.scss'
|
||||
})
|
||||
export class TutorialsWelcomeComponent {
|
||||
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
|
||||
.router_outlet{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials.component',
|
||||
imports: [
|
||||
RouterOutlet
|
||||
],
|
||||
templateUrl: './tutorials.component.html',
|
||||
styleUrl: './tutorials.component.scss'
|
||||
})
|
||||
export class TutorialsComponent {
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user