Initial Implementation of Project Cards
This commit is contained in:
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { HomeComponent } from './home/home.component';
|
import { HomeComponent } from './home/home.component';
|
||||||
import { CallbackComponent } from './header/header-popup/callback/callback.component';
|
import { CallbackComponent } from './header/header-popup/callback/callback.component';
|
||||||
|
import {ProjectsComponent} from "./projects/projects.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -14,6 +15,10 @@ const routes: Routes = [
|
|||||||
path: 'home',
|
path: 'home',
|
||||||
component: HomeComponent,
|
component: HomeComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'projects',
|
||||||
|
component: ProjectsComponent,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'callback',
|
path: 'callback',
|
||||||
component: CallbackComponent,
|
component: CallbackComponent,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ServiceWorkerModule } from '@angular/service-worker';
|
|||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { FooterComponent } from './footer/footer.component';
|
import { FooterComponent } from './footer/footer.component';
|
||||||
import {HomeModule} from "./home/home.module";
|
import {HomeModule} from "./home/home.module";
|
||||||
|
import {ProjectsModule} from "./projects/projects.module";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent, FooterComponent],
|
declarations: [AppComponent, FooterComponent],
|
||||||
@@ -18,6 +19,7 @@ import {HomeModule} from "./home/home.module";
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
HeaderModule,
|
HeaderModule,
|
||||||
HomeModule,
|
HomeModule,
|
||||||
|
ProjectsModule,
|
||||||
AppRouterModule,
|
AppRouterModule,
|
||||||
AppServiceWorkerModule,
|
AppServiceWorkerModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { MyProfileComponent } from './header-popup/my-profile/my-profile.compone
|
|||||||
export class HeaderComponent implements OnInit, OnDestroy {
|
export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
pages: { name: string; route: string }[] = [
|
pages: { name: string; route: string }[] = [
|
||||||
{ name: 'Home', route: '/home' },
|
{ name: 'Home', route: '/home' },
|
||||||
{ name: 'Projects', route: '/home' },
|
{ name: 'Projects', route: '/projects' },
|
||||||
{ name: 'Contact', route: '/home' },
|
{ name: 'Contact', route: '/home' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
74
src/app/projects/project-card/project-card.component.css
Normal file
74
src/app/projects/project-card/project-card.component.css
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
.card .inverse-card-image {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-content {
|
||||||
|
height: 350px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.card .card-content > * {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-content .card-content-h {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .card-content .card-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
.card .card-content .card-text {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.card .card-content .card-content-f {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.card .card-content .card-content-f .card-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.card .card-content .card-content-f .card-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
margin: 5px 0px 5px 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: start;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
.stat-item .stat-icon {
|
||||||
|
margin: 0px 15px 0px 0px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.stat-item span {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 300;
|
||||||
|
margin: 0;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-inline {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row !important;
|
||||||
|
}
|
||||||
41
src/app/projects/project-card/project-card.component.html
Normal file
41
src/app/projects/project-card/project-card.component.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<div class="card container">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-h">
|
||||||
|
<h2 class="card-title">
|
||||||
|
<a [href]="project.link">{{project.name}}</a>
|
||||||
|
</h2>
|
||||||
|
<p class="card-text">{{project.description}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-content-f row">
|
||||||
|
<div class="card-info col-9" *ngIf="hasLanguage">
|
||||||
|
<p >Language: {{project.languages}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-stats" [ngClass]="hasLanguage ? 'col-3' : 'stats-inline'">
|
||||||
|
<div class="stat-item" *ngIf="hasLicense">
|
||||||
|
<div class="stat-icon">
|
||||||
|
<fa-icon [icon]="faLicense"></fa-icon>
|
||||||
|
</div>
|
||||||
|
<span>{{project.license}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon">
|
||||||
|
<fa-icon [icon]="faStars"></fa-icon>
|
||||||
|
</div>
|
||||||
|
<span>{{project.stars}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon">
|
||||||
|
<fa-icon [icon]="faCodeFork"></fa-icon>
|
||||||
|
</div>
|
||||||
|
<span>{{project.forks}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-icon">
|
||||||
|
<fa-icon [icon]="faEye"></fa-icon>
|
||||||
|
</div>
|
||||||
|
<span>{{project.watchers}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
21
src/app/projects/project-card/project-card.component.spec.ts
Normal file
21
src/app/projects/project-card/project-card.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectCardComponent } from './project-card.component';
|
||||||
|
|
||||||
|
describe('ProjectCardComponent', () => {
|
||||||
|
let component: ProjectCardComponent;
|
||||||
|
let fixture: ComponentFixture<ProjectCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ProjectCardComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(ProjectCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
33
src/app/projects/project-card/project-card.component.ts
Normal file
33
src/app/projects/project-card/project-card.component.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import { faCodeFork, faEye, faStar } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import {Project} from "../../shared/model/project/project.model";
|
||||||
|
import {faScaleBalanced} from "@fortawesome/free-solid-svg-icons/faScaleBalanced";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-project-card',
|
||||||
|
templateUrl: './project-card.component.html',
|
||||||
|
styleUrls: ['./project-card.component.css']
|
||||||
|
})
|
||||||
|
export class ProjectCardComponent {
|
||||||
|
@Input() inverted: boolean = false;
|
||||||
|
|
||||||
|
@Input() project!: Project;
|
||||||
|
|
||||||
|
// Stats Icons Definitions
|
||||||
|
faLicense = faScaleBalanced;
|
||||||
|
|
||||||
|
faStars = faStar;
|
||||||
|
|
||||||
|
faCodeFork = faCodeFork;
|
||||||
|
|
||||||
|
faEye = faEye;
|
||||||
|
|
||||||
|
get hasLicense(): boolean {
|
||||||
|
return this.project.license !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasLanguage(): boolean {
|
||||||
|
return this.project.languages !== undefined &&
|
||||||
|
this.project.languages?.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/app/projects/projects.component.css
Normal file
8
src/app/projects/projects.component.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
app-project-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
app-project-card:last-child {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
6
src/app/projects/projects.component.html
Normal file
6
src/app/projects/projects.component.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div *ngFor="let p of projects; index as i;trackBy: identifyProject">
|
||||||
|
<app-project-card [project]="p" [inverted]="i % 2 !== 0">
|
||||||
|
</app-project-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
21
src/app/projects/projects.component.spec.ts
Normal file
21
src/app/projects/projects.component.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ProjectsComponent } from './projects.component';
|
||||||
|
|
||||||
|
describe('ProjectsComponent', () => {
|
||||||
|
let component: ProjectsComponent;
|
||||||
|
let fixture: ComponentFixture<ProjectsComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ProjectsComponent]
|
||||||
|
});
|
||||||
|
fixture = TestBed.createComponent(ProjectsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/app/projects/projects.component.ts
Normal file
27
src/app/projects/projects.component.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {GithubService} from "../shared/github-service/github.service";
|
||||||
|
import {Project} from "../shared/model/project/project.model";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-projects',
|
||||||
|
templateUrl: './projects.component.html',
|
||||||
|
styleUrls: ['./projects.component.css']
|
||||||
|
})
|
||||||
|
export class ProjectsComponent implements OnInit {
|
||||||
|
projects!: Project[];
|
||||||
|
|
||||||
|
constructor(private githubService: GithubService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.projects = [];
|
||||||
|
this.githubService.getProjects().subscribe((project: Project) => {
|
||||||
|
this.projects.push(project);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
identifyProject(index: number, project: Project) {
|
||||||
|
return project.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
src/app/projects/projects.module.ts
Normal file
21
src/app/projects/projects.module.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {ProjectsComponent} from "./projects.component";
|
||||||
|
import {CommonModule, NgOptimizedImage} from "@angular/common";
|
||||||
|
import { ProjectCardComponent } from './project-card/project-card.component';
|
||||||
|
import {MatIconModule} from "@angular/material/icon";
|
||||||
|
import {FontAwesomeModule} from "@fortawesome/angular-fontawesome";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ProjectsComponent,
|
||||||
|
ProjectCardComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
NgOptimizedImage,
|
||||||
|
MatIconModule,
|
||||||
|
FontAwesomeModule
|
||||||
|
],
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class ProjectsModule { }
|
||||||
@@ -2,19 +2,15 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
first,
|
first,
|
||||||
firstValueFrom,
|
|
||||||
map,
|
map,
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
of,
|
||||||
Subject,
|
Subject,
|
||||||
take,
|
|
||||||
tap,
|
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { catchError } from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { HttpError } from '../model/httpError/httpError.model';
|
import { HttpError } from '../model/httpError/httpError.model';
|
||||||
import { User } from '../model/user/user.model';
|
import { User } from '../model/user/user.model';
|
||||||
import * as http from 'http';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|||||||
16
src/app/shared/github-service/github.service.spec.ts
Normal file
16
src/app/shared/github-service/github.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GithubServiceService } from './github.service';
|
||||||
|
|
||||||
|
describe('GithubServiceService', () => {
|
||||||
|
let service: GithubServiceService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(GithubServiceService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
78
src/app/shared/github-service/github.service.ts
Normal file
78
src/app/shared/github-service/github.service.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {Language, Project} from "../model/project/project.model";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {
|
||||||
|
map, mergeMap,
|
||||||
|
Observable,
|
||||||
|
pipe, switchMap, take
|
||||||
|
} from 'rxjs';
|
||||||
|
import { environment } from 'src/environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class GithubService {
|
||||||
|
|
||||||
|
GITHUB_API_URL = 'https://api.github.com';
|
||||||
|
|
||||||
|
GITHUB_USER = environment.githubUser;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
getProjects(): Observable<Project> {
|
||||||
|
return this.http.get(this.apiReposString()).pipe(
|
||||||
|
map((projects: any) => {
|
||||||
|
return projects.map((project: any) => {
|
||||||
|
return {
|
||||||
|
name: project.name,
|
||||||
|
description: project.description,
|
||||||
|
license: project.license?.key,
|
||||||
|
link: project.html_url,
|
||||||
|
|
||||||
|
stars: project.stargazers_count,
|
||||||
|
forks: project.forks_count,
|
||||||
|
watchers: project.watchers_count
|
||||||
|
} as Project;
|
||||||
|
}).filter((project: Project) => {
|
||||||
|
return project.name !== this.GITHUB_USER;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
switchMap((projects: Project[]) => {
|
||||||
|
return new Observable<Project>((observer) => {
|
||||||
|
projects.forEach((project: Project, index: number) => {
|
||||||
|
this.getProjectLanguage(project).subscribe((languages: Language[]) => {
|
||||||
|
project.languages = languages;
|
||||||
|
observer.next(project);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProjectLanguage(project: Project): Observable<Language[]> {
|
||||||
|
return this.http.get(this.apiRepoLanguagesString(project.name)).pipe(
|
||||||
|
map((languages: any) => {
|
||||||
|
let totalBytes = 0;
|
||||||
|
Object.keys(languages).forEach((language: string) => {
|
||||||
|
totalBytes += languages[language];
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.keys(languages).map((language: string) => {
|
||||||
|
return {
|
||||||
|
name: language,
|
||||||
|
percentage: (languages[language]/totalBytes)*100
|
||||||
|
} as Language;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private apiReposString() {
|
||||||
|
return `${this.GITHUB_API_URL}/users/${this.GITHUB_USER}/repos`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private apiRepoLanguagesString(repoName: string) {
|
||||||
|
return `${this.GITHUB_API_URL}/repos/${this.GITHUB_USER}/${repoName}/languages`;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/app/shared/model/project/project.model.ts
Normal file
17
src/app/shared/model/project/project.model.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type Language = {
|
||||||
|
name: string;
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Project = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
link: string;
|
||||||
|
|
||||||
|
license?: string;
|
||||||
|
languages?: Language[];
|
||||||
|
|
||||||
|
stars: number;
|
||||||
|
forks: number;
|
||||||
|
watchers: number;
|
||||||
|
}
|
||||||
@@ -2,4 +2,5 @@ export const environment = {
|
|||||||
production: true,
|
production: true,
|
||||||
backendPath: (<any>window)['env']['BACKEND_URL'],
|
backendPath: (<any>window)['env']['BACKEND_URL'],
|
||||||
backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'],
|
backendOAuthPath: (<any>window)['env']['BACKEND_OAUTH_URL'],
|
||||||
|
githubUser: (<any>window)['env']['GITHUB_USER'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const environment = {
|
|||||||
production: false,
|
production: false,
|
||||||
backendPath: 'http://localhost:8070',
|
backendPath: 'http://localhost:8070',
|
||||||
backendOAuthPath: 'http://localhost:8070',
|
backendOAuthPath: 'http://localhost:8070',
|
||||||
|
githubUser: 'HideyoshiNakazone',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user