refactored projects site
This commit is contained in:
61
src/app/pages/projects/dialog/project-dialog.component.html
Normal file
61
src/app/pages/projects/dialog/project-dialog.component.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<h2 mat-dialog-title>{{ project.title | translate }}</h2>
|
||||
<mat-dialog-content>
|
||||
<p>{{ project.introduction | translate }}</p>
|
||||
|
||||
<ul>
|
||||
@for(bullet of project.bulletPoints; track bullet) {
|
||||
<li>{{ bullet | translate }}</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@if (project.images.length > 0)
|
||||
{
|
||||
<swiper-container
|
||||
class="my-swiper"
|
||||
[attr.slides-per-view]="1.2"
|
||||
[attr.space-between]="12"
|
||||
[attr.navigation]="true"
|
||||
[attr.pagination]="true"
|
||||
[attr.keyboard]="true"
|
||||
style="width: 100%;"
|
||||
>
|
||||
@for (img of project.images; track img) {
|
||||
<swiper-slide>
|
||||
<img
|
||||
class="slide-img"
|
||||
[src]="img"
|
||||
[alt]="project.title | translate"
|
||||
/>
|
||||
</swiper-slide>
|
||||
}
|
||||
</swiper-container>
|
||||
}
|
||||
|
||||
<mat-chip-set aria-label="Technologies">
|
||||
@for(tech of project.technologies; track tech) {
|
||||
<mat-chip>{{tech}}</mat-chip>
|
||||
}
|
||||
</mat-chip-set>
|
||||
|
||||
<div class="link-section">
|
||||
@if (project.link)
|
||||
{
|
||||
<a mat-button href="{{project.link}}" target="_blank" rel="noopener noreferrer">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
{{ 'PROJECTS.LINK_TO_PROJECT' | translate }}
|
||||
</a>
|
||||
}
|
||||
|
||||
@if(project.assets)
|
||||
{
|
||||
<a mat-button href="{{project.assets}}" rel="noopener noreferrer">
|
||||
<mat-icon>download</mat-icon>
|
||||
{{ 'PROJECTS.DOWNLOAD' | translate}}
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>{{ 'PROJECTS.CLOSE' | translate }}</button>
|
||||
</mat-dialog-actions>
|
||||
60
src/app/pages/projects/dialog/project-dialog.component.scss
Normal file
60
src/app/pages/projects/dialog/project-dialog.component.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
.my-swiper::part(button-prev),
|
||||
.my-swiper::part(button-next) {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
padding: 5px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0,0,0,.5);
|
||||
color: white;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.my-swiper::part(button-prev):hover,
|
||||
.my-swiper::part(button-next):hover {
|
||||
background: rgba(0,0,0,.75);
|
||||
}
|
||||
|
||||
.my-swiper::part(pagination) {
|
||||
bottom: 12px;
|
||||
}
|
||||
|
||||
.slide-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 512px !important;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
mat-chip-set {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.link-section {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
mat-dialog-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
35
src/app/pages/projects/dialog/project-dialog.component.ts
Normal file
35
src/app/pages/projects/dialog/project-dialog.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {Component, inject, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogTitle
|
||||
} from '@angular/material/dialog';
|
||||
import {Projects} from '../projects.component';
|
||||
import {TranslatePipe} from '@ngx-translate/core';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatChip, MatChipSet} from '@angular/material/chips';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-dialog',
|
||||
templateUrl: './project-dialog.component.html',
|
||||
styleUrls: ['./project-dialog.component.scss'],
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatDialogTitle,
|
||||
MatDialogContent,
|
||||
TranslatePipe,
|
||||
MatIcon,
|
||||
MatChip,
|
||||
MatChipSet,
|
||||
MatDialogActions,
|
||||
MatButton,
|
||||
MatDialogClose
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class ProjectDialogComponent {
|
||||
project: Projects = inject(MAT_DIALOG_DATA);
|
||||
}
|
||||
@@ -1,63 +1,54 @@
|
||||
<mat-accordion class="project-headers-align">
|
||||
@for (project of allProjects; track project) {
|
||||
<mat-expansion-panel [expanded]="isExpanded(project.identifier)">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{ project.title | translate }}</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
{{ project.shortDescription | translate}}
|
||||
<mat-icon>{{ project.icon }}</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<p>{{ project.introduction | translate }}</p>
|
||||
|
||||
@if (project.link)
|
||||
{
|
||||
<div class="link-row">
|
||||
<a class="link-with-icon"
|
||||
href="{{project.link}}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
{{project.link}}
|
||||
</a>
|
||||
<div class="project-grid">
|
||||
@if (featuredProject(); as project) {
|
||||
<mat-card class="project-card featured">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
||||
<mat-card-subtitle>{{ project.shortDescription | translate }}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
@if(project.images.length > 0) {
|
||||
<img mat-card-image [src]="project.images[0]" [alt]="project.title | translate">
|
||||
} @else {
|
||||
<div class="icon-container">
|
||||
<mat-icon class="fallback-icon">{{ project.icon }}</mat-icon>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if(project.assets)
|
||||
{
|
||||
<div class="link-row">
|
||||
<a class="link-with-icon"
|
||||
href="{{project.assets}}"
|
||||
rel="noopener noreferrer">
|
||||
<mat-icon>download</mat-icon>
|
||||
{{ 'PROJECTS.DOWNLOAD' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (project.images.length > 0)
|
||||
{
|
||||
<swiper-container
|
||||
class="my-swiper"
|
||||
[attr.slides-per-view]="1.2"
|
||||
[attr.space-between]="12"
|
||||
[attr.navigation]="true"
|
||||
[attr.pagination]="true"
|
||||
[attr.keyboard]="true"
|
||||
style="width: 100%;"
|
||||
>
|
||||
@for (img of project.images; track img) {
|
||||
<swiper-slide>
|
||||
<img
|
||||
class="slide-img"
|
||||
[src]="img"
|
||||
[alt]="project.title | translate"
|
||||
(click)="openImage(project.title, img)"
|
||||
/>
|
||||
</swiper-slide>
|
||||
<mat-card-content>
|
||||
<p>{{ project.introduction | translate }}</p>
|
||||
<mat-chip-set aria-label="Technologies">
|
||||
@for(tech of project.technologies; track tech) {
|
||||
<mat-chip>{{tech}}</mat-chip>
|
||||
}
|
||||
</swiper-container>
|
||||
}
|
||||
</mat-expansion-panel>
|
||||
</mat-chip-set>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
}
|
||||
</mat-accordion>
|
||||
|
||||
@for (project of otherProjects(); track project) {
|
||||
<mat-card class="project-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ project.title | translate }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
@if(project.images.length > 0) {
|
||||
<img mat-card-image [src]="project.images[0]" [alt]="project.title | translate">
|
||||
} @else {
|
||||
<div class="icon-container">
|
||||
<mat-icon class="fallback-icon">{{ project.icon }}</mat-icon>
|
||||
</div>
|
||||
}
|
||||
<mat-card-content>
|
||||
<p>{{ project.shortDescription | translate }}</p>
|
||||
<mat-chip-set aria-label="Technologies">
|
||||
@for(tech of project.technologies; track tech) {
|
||||
<mat-chip>{{tech}}</mat-chip>
|
||||
}
|
||||
</mat-chip-set>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button (click)="openProjectDialog(project)">{{ 'PROJECTS.READ_MORE' | translate }}</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,40 +1,60 @@
|
||||
.project-headers-align .mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.project-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
}
|
||||
|
||||
.project-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.slide-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 512px !important;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.my-swiper::part(button-prev),
|
||||
.my-swiper::part(button-next) {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
padding: 5px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0,0,0,.5);
|
||||
|
||||
.project-card {
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
&.featured {
|
||||
grid-column: 1 / -1; // Span full width
|
||||
}
|
||||
|
||||
mat-card-header {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
mat-card-content {
|
||||
flex-grow: 1; // Ensure content area expands
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
mat-chip-set {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
mat-card-actions {
|
||||
margin-top: auto; // Push actions to the bottom
|
||||
}
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px; /* Or a height that fits your design */
|
||||
background-color: #f0f0f0; /* A light background for the icon */
|
||||
}
|
||||
|
||||
.my-swiper::part(button-prev):hover,
|
||||
.my-swiper::part(button-next):hover {
|
||||
background: rgba(0,0,0,.75);
|
||||
.fallback-icon {
|
||||
font-size: 4rem;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.my-swiper::part(pagination) {
|
||||
bottom: 12px;
|
||||
// Ensure images don't exceed the card width and maintain aspect ratio
|
||||
img[mat-card-image] {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {Component, inject, signal, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import {MatAccordion, MatExpansionPanel, MatExpansionPanelDescription, MatExpansionPanelHeader, MatExpansionPanelTitle} from '@angular/material/expansion';
|
||||
import {Component, computed, inject, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatChipsModule} from '@angular/material/chips';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {TranslatePipe, TranslateService} from '@ngx-translate/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {AssetsConstants} from '../../constants/AssetsConstants';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ImageDialogComponent} from '../../layout/imageDialog/image.component';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {ProjectDialogComponent} from './dialog/project-dialog.component';
|
||||
|
||||
|
||||
export interface Projects {
|
||||
@@ -18,18 +20,18 @@ export interface Projects {
|
||||
assets: string,
|
||||
link: string,
|
||||
bulletPoints: string[],
|
||||
isFeatured: boolean,
|
||||
technologies: string[]
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-projects',
|
||||
imports: [
|
||||
MatAccordion,
|
||||
MatExpansionPanel,
|
||||
MatExpansionPanelHeader,
|
||||
MatExpansionPanelTitle,
|
||||
MatExpansionPanelDescription,
|
||||
MatCardModule,
|
||||
MatChipsModule,
|
||||
MatIcon,
|
||||
TranslatePipe
|
||||
TranslatePipe,
|
||||
MatButtonModule
|
||||
],
|
||||
templateUrl: './projects.component.html',
|
||||
styleUrl: './projects.component.scss',
|
||||
@@ -41,8 +43,6 @@ export class ProjectsComponent {
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly translateService = inject(TranslateService);
|
||||
|
||||
selectedKey = signal<string | null>(null);
|
||||
|
||||
allProjects: Projects[] = [
|
||||
{
|
||||
identifier: "playground",
|
||||
@@ -53,7 +53,14 @@ export class ProjectsComponent {
|
||||
icon: 'web',
|
||||
assets: '',
|
||||
link: 'https://andreas-dahm.eu',
|
||||
bulletPoints: []
|
||||
bulletPoints: [
|
||||
'PROJECTS.PLAYGROUND.BULLET_1',
|
||||
'PROJECTS.PLAYGROUND.BULLET_2',
|
||||
'PROJECTS.PLAYGROUND.BULLET_3',
|
||||
'PROJECTS.PLAYGROUND.BULLET_4',
|
||||
],
|
||||
isFeatured: false,
|
||||
technologies: ['Angular', 'TypeScript', 'SCSS', 'HTML', 'GitHub Actions', 'Docker']
|
||||
},
|
||||
{
|
||||
identifier: "elmucho",
|
||||
@@ -63,8 +70,15 @@ export class ProjectsComponent {
|
||||
images: [AssetsConstants.EL_MUCHO_1, AssetsConstants.EL_MUCHO_2, AssetsConstants.EL_MUCHO_3, AssetsConstants.EL_MUCHO_4],
|
||||
icon: 'sports_esports',
|
||||
assets: '',
|
||||
link: 'https://store.steampowered.com/app/1532640/El_Mucho/',
|
||||
bulletPoints: []
|
||||
link: 'https.store.steampowered.com/app/1532640/El_Mucho/',
|
||||
bulletPoints: [
|
||||
'PROJECTS.EL_MUCHO.BULLET_1',
|
||||
'PROJECTS.EL_MUCHO.BULLET_2',
|
||||
'PROJECTS.EL_MUCHO.BULLET_3',
|
||||
'PROJECTS.EL_MUCHO.BULLET_4',
|
||||
],
|
||||
isFeatured: true,
|
||||
technologies: ['Unity', 'C#', 'Steamworks', 'Git']
|
||||
},
|
||||
{
|
||||
identifier: "gamejams",
|
||||
@@ -74,8 +88,15 @@ export class ProjectsComponent {
|
||||
images: [AssetsConstants.GAME_JAMS_1, AssetsConstants.GAME_JAMS_2, AssetsConstants.GAME_JAMS_3],
|
||||
icon: 'videogame_asset',
|
||||
assets: '',
|
||||
link: 'https://itch.io/c/6628860/lobos-collection',
|
||||
bulletPoints: []
|
||||
link: 'https.itch.io/c/6628860/lobos-collection',
|
||||
bulletPoints: [
|
||||
'PROJECTS.GAME_JAMS.BULLET_1',
|
||||
'PROJECTS.GAME_JAMS.BULLET_2',
|
||||
'PROJECTS.GAME_JAMS.BULLET_3',
|
||||
'PROJECTS.GAME_JAMS.BULLET_4',
|
||||
],
|
||||
isFeatured: false,
|
||||
technologies: ['Unity', 'C#', 'Git']
|
||||
},
|
||||
{
|
||||
identifier: "diploma",
|
||||
@@ -85,32 +106,33 @@ export class ProjectsComponent {
|
||||
images: [AssetsConstants.DIPLOMA_1, AssetsConstants.DIPLOMA_2, AssetsConstants.DIPLOMA_3, AssetsConstants.DIPLOMA_4, AssetsConstants.DIPLOMA_5, AssetsConstants.DIPLOMA_6],
|
||||
icon: 'history_edu',
|
||||
assets: AssetsConstants.DIPLOMA,
|
||||
link: 'https://www.th-bingen.de',
|
||||
bulletPoints: []
|
||||
link: 'https.www.th-bingen.de',
|
||||
bulletPoints: [
|
||||
'PROJECTS.DIPLOMA.BULLET_1',
|
||||
'PROJECTS.DIPLOMA.BULLET_2',
|
||||
'PROJECTS.DIPLOMA.BULLET_3',
|
||||
'PROJECTS.DIPLOMA.BULLET_4',
|
||||
],
|
||||
isFeatured: false,
|
||||
technologies: ['C++', 'OpenGL', 'Qt', '3D-Scanner']
|
||||
}
|
||||
]
|
||||
|
||||
featuredProject = computed(() => this.allProjects.find(p => p.isFeatured));
|
||||
otherProjects = computed(() => this.allProjects.filter(p => !p.isFeatured));
|
||||
|
||||
|
||||
constructor() {
|
||||
this.route.queryParamMap.subscribe(params => {
|
||||
this.selectedKey.set(params.get('project'));
|
||||
// this.selectedKey.set(params.get('project'));
|
||||
});
|
||||
}
|
||||
|
||||
isExpanded(projectKey: string): boolean {
|
||||
return this.selectedKey() === projectKey;
|
||||
}
|
||||
|
||||
openImage(title: string, src: string) {
|
||||
const translatedTitle = this.translateService.instant(title);
|
||||
this.dialog.open(ImageDialogComponent, {
|
||||
data: { title: translatedTitle, src },
|
||||
width: '96vw',
|
||||
height: '96vh',
|
||||
maxWidth: '96vw',
|
||||
maxHeight: '96vh',
|
||||
panelClass: 'image-dialog-panel',
|
||||
autoFocus: false,
|
||||
restoreFocus: true,
|
||||
});
|
||||
openProjectDialog(project: Projects) {
|
||||
this.dialog.open(ProjectDialogComponent, {
|
||||
data: project,
|
||||
width: '90vw',
|
||||
maxWidth: '900px',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user