[Updated] Titles, turned project card into component, working on showing long texts in cards

This commit is contained in:
Ninjaws
2024-11-24 15:57:53 +01:00
parent 02350a319f
commit de1de93358
11 changed files with 144 additions and 164 deletions
View File
-23
View File
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AboutComponent } from './about.component';
describe('AboutComponent', () => {
let component: AboutComponent;
let fixture: ComponentFixture<AboutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AboutComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AboutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+1 -2
View File
@@ -10,8 +10,7 @@ interface IconItem {
selector: 'app-about', selector: 'app-about',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule],
templateUrl: './about.component.html', templateUrl: './about.component.html'
styleUrl: './about.component.scss'
}) })
export class AboutComponent { export class AboutComponent {
iconItems: IconItem[] = []; iconItems: IconItem[] = [];
-23
View File
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactComponent } from './contact.component';
describe('ContactComponent', () => {
let component: ContactComponent;
let fixture: ComponentFixture<ContactComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ContactComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
-23
View File
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeaderComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,73 @@
<!-- bg-white/5 -->
<div class="backface-hidden rounded-sm overflow-hidden shadow-lg md:hover:scale-105 transition-all
duration-300 group relative fade-in-up bg-white/5 border-none flex flex-col h-full">
<img src="{{project.image}}" alt="{{project.title}}" class="w-full max-h-56 aspect-[3/2] object-cover">
<div class="p-2 xs:p-3 lg:px-6 lg:pb-6 flex-grow flex flex-col">
<div class="flex flex-wrap mb-2 xs:mb-4 items-center">
<h3 class="text-xl sm:text-2xl font-semibold text-teal-400 flex-grow">{{project.title}}</h3>
<p class="pl-2 ml-auto w-auto text-right text-sm xs:text-base text-gray-400">{{project.year}}</p>
</div>
<div class="flex flex-wrap gap-1 mb-4">
<span *ngFor="let technology of project.technologies"
class="bg-gray-800/55 text-white text-sm sm:text-md px-1 sm:px-2 py-1 rounded-md">{{technology}}</span>
</div>
<!-- max-h-60 -->
<!-- <p class="text-gray-300 mb-4 whitespace-pre-line overflow-hidden overflow-ellipsis max-h-60">
{{project.description}}</p> -->
<!-- <div class="relative">
<p #description
class="text-gray-300 mb-4 whitespace-pre-line overflow-hidden overflow-ellipsis overflow- max-h-52"
[class.max-h-none]="isExpanded">
{{ project.description }}
</p>
<button *ngIf="isTruncated && !isExpanded"
(click)="toggleExpand()"
class="text-blue-500 mt-2">
Show More
</button>
<button *ngIf="isExpanded"
(click)="toggleExpand()"
class="text-blue-500 mt-2">
Show Less
</button>
</div> -->
<div class="relative">
<p #description
class="text-gray-300 mb-4 whitespace-pre-line overflow-hidden max-h-60 transition-all duration-300 ease-in-out"
[class.max-h-none]="isExpanded"
[class.overflow-visible]="isExpanded">
{{ project.description }}
</p>
<!-- Faded area at the bottom of the text -->
<!-- <div *ngIf="isTruncated" class="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-b from-transparent to-gray-700 opacity-100 cursor-pointer" (click)="toggleExpand()"></div> -->
<!-- Show More Button (Optional) -->
<button *ngIf="isTruncated && !isExpanded"
(click)="toggleExpand()"
class="text-teal-600 mt-1 hover:text-teal-400">
Show More
</button>
<button *ngIf="isExpanded" (click)="toggleExpand()" class="text-teal-500 mt-2 hover:text-teal-200">
Show Less
</button>
</div>
<div class="flex justify-end items-end gap-5 flex-1">
<a *ngFor="let link of project.links" href="{{link.address}}" target="_blank" class="">
<!-- [ngClass]="link.type === 'link' ? 'border-2 border-gray-600 p-1 rounded-sm hover:bg-teal-400 hover:text-gray-700 hover:border-transparent' : ''"> -->
<p *ngIf="link.type === 'link'" class="text-base md:text-lg flex text-center items-center border-2 text-gray-400 border-gray-600 p-1 rounded-sm hover:bg-teal-400 hover:text-gray-700 hover:border-transparent transition-all duration-300 ease-in-out">
Try it here!
<i class="bi-arrow-right-circle-fill text-base md:text-lg ml-2 "></i>
</p>
<i *ngIf="link.type !== 'link'" class="text-3xl md:text-4xl text-gray-400 rounded-full hover:text-teal-400 transition-all duration-300 ease-in-out" [ngClass]="link.type === 'source' ? 'bi-github' : 'bi-youtube'"></i>
</a>
</div>
</div>
</div>
@@ -0,0 +1,57 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, Component, ElementRef, Input, ViewChild } from '@angular/core';
export interface Project {
title: string;
year: string;
description: string;
image: string;
technologies: string[];
links: {address: string, type: 'source' | 'demo' | 'link'}[];
// source?: string;
// demo?: string;
// link?: string;
}
@Component({
selector: 'app-project-card',
standalone: true,
imports: [CommonModule],
templateUrl: './project-card.component.html',
styles: ``
})
export class ProjectCardComponent {
@Input() project!: Project;
@ViewChild('description') descriptionElement!: ElementRef;
isExpanded = false;
isTruncated = false;
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() {
this.checkIfTruncated();
this.cdr.detectChanges(); // Notify Angular of the changes after view initialization
}
// Check if the description is being cut off
checkIfTruncated() {
const element = this.descriptionElement.nativeElement;
this.isTruncated = element.scrollHeight > element.offsetHeight;
console.log(this.isTruncated);
}
// Toggle expand/collapse
toggleExpand() {
this.isExpanded = !this.isExpanded;
if (this.isExpanded) {
this.isTruncated = false;
} else {
this.isTruncated = true;
}
this.cdr.detectChanges(); // Notify Angular of the changes after toggling
}
}
+1 -26
View File
@@ -3,32 +3,7 @@
<h2 class="text-4xl font-semibold text-center mb-12 fade-in-left winged-14">Projects</h2> <h2 class="text-4xl font-semibold text-center mb-12 fade-in-left winged-14">Projects</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6">
<div *ngFor="let project of projects" <app-project-card *ngFor="let project of projects" [project]="project"></app-project-card>
class="backface-hidden rounded-sm overflow-hidden shadow-lg md:hover:scale-105 transition-all
duration-300 group relative fade-in-up bg-white/5 border-none flex flex-col">
<img src="{{project.image}}" alt="{{project.title}}" class="w-full max-h-56 aspect-[3/2] object-cover">
<div class="p-2 xs:p-3 lg:px-6 lg:pb-6 flex-grow flex flex-col">
<div class="flex flex-wrap mb-2 xs:mb-4 items-center">
<h3 class="text-xl sm:text-2xl font-semibold text-teal-400 flex-grow">{{project.title}}</h3>
<p class="pl-2 ml-auto w-auto text-right text-sm xs:text-base text-gray-400">{{project.year}}</p>
</div>
<div class="flex flex-wrap gap-1 mb-4">
<span *ngFor="let technology of project.technologies"
class="bg-gray-800/55 text-white text-sm sm:text-md px-1 sm:px-2 py-1 rounded-md">{{technology}}</span>
</div>
<p class="text-gray-300 mb-4 whitespace-pre-line ">{{project.description}}</p>
<div class="flex justify-end items-end gap-5 flex-1">
<a *ngFor="let link of project.links" href="{{link.address}}" target="_blank" class="text-gray-400 rounded-full hover:text-teal-400 transition-all
duration-100 ease-in-out flex items-center gap-3" [ngClass]="link.type === 'link' ? 'border border-gray-600 p-1 rounded-sm hover:bg-teal-400 hover:text-gray-700 hover:border-transparent' : ''">
<p *ngIf="link.type === 'link'">Try now</p>
<i *ngIf="link.type === 'link'" class="bi-arrow-right-circle-fill text-xl"></i>
<i *ngIf="link.type !== 'link'" class="text-3xl md:text-4xl" [ngClass]="link.type === 'source' ? 'bi-github' : 'bi-youtube'"></i>
</a>
</div>
</div>
</div>
</div> </div>
</div> </div>
</section> </section>
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectsComponent } from './projects.component';
describe('ProjectsComponent', () => {
let component: ProjectsComponent;
let fixture: ComponentFixture<ProjectsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProjectsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ProjectsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+12 -21
View File
@@ -1,23 +1,11 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { AfterViewInit, Component } from '@angular/core'; import { AfterViewInit, Component } from '@angular/core';
import { Project, ProjectCardComponent } from "../project-card/project-card.component";
// export enum Link { Source, Demo, Link }
interface Project {
title: string;
year: string;
description: string;
image: string;
technologies: string[];
links: {address: string, type: 'source' | 'demo' | 'link'}[];
// source?: string;
// demo?: string;
// link?: string;
}
@Component({ @Component({
selector: 'app-projects', selector: 'app-projects',
imports: [CommonModule], imports: [CommonModule, ProjectCardComponent],
templateUrl: './projects.component.html' templateUrl: './projects.component.html'
}) })
export class ProjectsComponent { export class ProjectsComponent {
@@ -48,7 +36,7 @@ export class ProjectsComponent {
// link: 'https://quiz.ianvink.nl' // link: 'https://quiz.ianvink.nl'
}); });
this.projects.push({ this.projects.push({
title: 'Mobile-first ERP solution', title: 'Mobile-First ERP Solution',
year: '2021-2024', year: '2021-2024',
description: description:
`This is a comprehensive ERP solution that includes modules for a variety of functions, such as: `This is a comprehensive ERP solution that includes modules for a variety of functions, such as:
@@ -83,7 +71,7 @@ export class ProjectsComponent {
links: [], links: [],
}); });
this.projects.push({ this.projects.push({
title: 'AI supplier identification', title: 'AI Supplier Identification',
year: '2021', year: '2021',
description: description:
'An application that combines key-value detection and multiclass-classification, to identify the correct supplier for an invoice. It uses online machine learning to keep improving itself as the user provides feedback.', 'An application that combines key-value detection and multiclass-classification, to identify the correct supplier for an invoice. It uses online machine learning to keep improving itself as the user provides feedback.',
@@ -98,10 +86,12 @@ export class ProjectsComponent {
links: [], links: [],
}); });
this.projects.push({ this.projects.push({
title: 'Seabed modification tool', title: 'Seabed Modification Tool',
year: '2019-2020', year: '2019-2020',
description: description:
'A tool to edit the seabed mesh of a maritime simulator. The collisionmap is also updated, to allow ships to interact with this update terrain. \nThis tool allows instructors to rapidly create new terrain, for training shipcrews.', 'A tool for editing the seabed mesh in a maritime simulator. The collision map is automatically updated, enabling ships to interact with the modified terrain.'+
'\n\nThis tool allows instructors to quickly create new environments for the training of ship crews.',
// to edit the seabed mesh of a maritime simulator. The collisionmap is also updated, to allow ships to interact with this update terrain. \nThis tool allows instructors to rapidly create new terrain, for training shipcrews.',
image: 'Seabed Modification.png', image: 'Seabed Modification.png',
technologies: ['Unigine (C#)', 'AGX Dynamics (C++)', 'Qt Creator'], technologies: ['Unigine (C#)', 'AGX Dynamics (C++)', 'Qt Creator'],
links: [], links: [],
@@ -124,7 +114,7 @@ export class ProjectsComponent {
// demo: 'https://youtu.be/UnNgNyXE9ZY' // demo: 'https://youtu.be/UnNgNyXE9ZY'
}); });
this.projects.push({ this.projects.push({
title: 'Augmented Reality game', title: 'Augmented Reality Game',
year: '2019', year: '2019',
description: description:
'The game uses computer vision to track a red ball, which serves as the controller. A system has been created for the rigging and animations for the player character and the opponents. The math for OpenGL is written by hand.', 'The game uses computer vision to track a red ball, which serves as the controller. A system has been created for the rigging and animations for the player character and the opponents. The math for OpenGL is written by hand.',
@@ -135,10 +125,11 @@ export class ProjectsComponent {
// demo: 'https://youtu.be/fHJQNnytocc' // demo: 'https://youtu.be/fHJQNnytocc'
}); });
this.projects.push({ this.projects.push({
title: 'Flow Field pathfinding algorithm', title: 'Flow Field Pathfinding Algorithm',
year: '2018', year: '2018',
description: description:
'The application uses a vector field to direct particles towards a target. The vectors are calculated using a heatmap, which centers on the position of the target. \nThe particles collide with the environment, which can be edited and saved/loaded using the GUI.', 'The application uses a vector field to direct particles towards a target. The vectors are calculated using a heatmap, which centers on the position of the target.'+
'\n\nThe particles collide with the environment, which can be edited and saved/loaded using the GUI.',
image: 'black2.png', //'Particle Flow - Demonstration.png', //"black.png", image: 'black2.png', //'Particle Flow - Demonstration.png', //"black.png",
technologies: ['Java', 'Swing', 'IntelliJ'], technologies: ['Java', 'Swing', 'IntelliJ'],
links: [{type:'source', address:'https://github.com/Ninjaws/2D-Computer-Graphics/tree/master/EindOpdracht/Pathfinding'}, {type:'demo', address: 'https://youtu.be/7rvWAWpkYus'}] links: [{type:'source', address:'https://github.com/Ninjaws/2D-Computer-Graphics/tree/master/EindOpdracht/Pathfinding'}, {type:'demo', address: 'https://youtu.be/7rvWAWpkYus'}]
-23
View File
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SkillsComponent } from './skills.component';
describe('SkillsComponent', () => {
let component: SkillsComponent;
let fixture: ComponentFixture<SkillsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SkillsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SkillsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});