[Updated] Titles, turned project card into component, working on showing long texts in cards
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -10,8 +10,7 @@ interface IconItem {
|
||||
selector: 'app-about',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './about.component.html',
|
||||
styleUrl: './about.component.scss'
|
||||
templateUrl: './about.component.html'
|
||||
})
|
||||
export class AboutComponent {
|
||||
iconItems: IconItem[] = [];
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,32 +3,7 @@
|
||||
<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 *ngFor="let project of projects"
|
||||
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>
|
||||
<app-project-card *ngFor="let project of projects" [project]="project"></app-project-card>
|
||||
</div>
|
||||
</div>
|
||||
</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();
|
||||
});
|
||||
});
|
||||
@@ -1,23 +1,11 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
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({
|
||||
selector: 'app-projects',
|
||||
imports: [CommonModule],
|
||||
imports: [CommonModule, ProjectCardComponent],
|
||||
templateUrl: './projects.component.html'
|
||||
})
|
||||
export class ProjectsComponent {
|
||||
@@ -48,7 +36,7 @@ export class ProjectsComponent {
|
||||
// link: 'https://quiz.ianvink.nl'
|
||||
});
|
||||
this.projects.push({
|
||||
title: 'Mobile-first ERP solution',
|
||||
title: 'Mobile-First ERP Solution',
|
||||
year: '2021-2024',
|
||||
description:
|
||||
`This is a comprehensive ERP solution that includes modules for a variety of functions, such as:
|
||||
@@ -83,7 +71,7 @@ export class ProjectsComponent {
|
||||
links: [],
|
||||
});
|
||||
this.projects.push({
|
||||
title: 'AI supplier identification',
|
||||
title: 'AI Supplier Identification',
|
||||
year: '2021',
|
||||
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.',
|
||||
@@ -98,10 +86,12 @@ export class ProjectsComponent {
|
||||
links: [],
|
||||
});
|
||||
this.projects.push({
|
||||
title: 'Seabed modification tool',
|
||||
title: 'Seabed Modification Tool',
|
||||
year: '2019-2020',
|
||||
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',
|
||||
technologies: ['Unigine (C#)', 'AGX Dynamics (C++)', 'Qt Creator'],
|
||||
links: [],
|
||||
@@ -124,7 +114,7 @@ export class ProjectsComponent {
|
||||
// demo: 'https://youtu.be/UnNgNyXE9ZY'
|
||||
});
|
||||
this.projects.push({
|
||||
title: 'Augmented Reality game',
|
||||
title: 'Augmented Reality Game',
|
||||
year: '2019',
|
||||
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.',
|
||||
@@ -135,10 +125,11 @@ export class ProjectsComponent {
|
||||
// demo: 'https://youtu.be/fHJQNnytocc'
|
||||
});
|
||||
this.projects.push({
|
||||
title: 'Flow Field pathfinding algorithm',
|
||||
title: 'Flow Field Pathfinding Algorithm',
|
||||
year: '2018',
|
||||
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",
|
||||
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'}]
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user