26 changed files with 641 additions and 146 deletions
@ -1,5 +1,7 @@ |
|||||
|
<main> |
||||
<app-header *ngIf="page != '/admin'"></app-header> |
<app-header *ngIf="page != '/admin'"></app-header> |
||||
<router-outlet></router-outlet> |
<router-outlet></router-outlet> |
||||
|
|
||||
<Particles class="particles" *ngIf="particlesEnabled && page != '/admin'" |
<Particles class="particles" *ngIf="particlesEnabled && page != '/admin'" |
||||
[id]="id" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"></Particles> |
[id]="id" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"></Particles> |
||||
|
</main> |
||||
|
@ -0,0 +1,3 @@ |
|||||
|
main { |
||||
|
overflow: hidden; |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
<div class="component-home"> |
||||
|
<button class="goto-prev" (click)="scroll('prev')" (mouseover)="paused=true"><span class="icon icon-back"></span></button> |
||||
|
<button class="goto-next" (click)="scroll('next')" (mouseover)="paused=true"><span class="icon icon-back"></span></button> |
||||
|
<div class="content" #scrollContent (touchstart)="swipe($event, 'start')" (touchend)="swipe($event, 'end')"> |
||||
|
<div [ngClass]="'slide-' + item.width" *ngFor="let item of homeItems"> |
||||
|
<div class="box" [ngClass]="'skew-' + (item.id % 6)" (click)="showDetails(item.id, item.title)" (mouseover)="paused=true" (mouseout)="paused=false"> |
||||
|
<img class="image" *ngIf="item.loading" src="/assets/images/loader.svg" alt="loading"> |
||||
|
<img class="image" [hidden]="item.loading" (load)="onLoad(item.id)" [src]="basePath+item.image"> |
||||
|
<div class="text"> |
||||
|
<span class="title">{{item.title}}</span> |
||||
|
<span class="type" *ngIf="section != 'exhibitions'">{{item.type}}</span> |
||||
|
|
||||
|
<div class="date-container" *ngIf="section == 'exhibitions'"> |
||||
|
<div class="date-row"> |
||||
|
<span class="date-indication" *ngIf="item.date_from != item.date_to">from</span> |
||||
|
<span class="date-indication" *ngIf="item.date_from == item.date_to">on</span> |
||||
|
<span class="date">{{item.date_from | date}}</span> |
||||
|
</div> |
||||
|
<div class="date-row"> |
||||
|
<span class="date" *ngIf="item.date_from != item.date_to"> <span class="date-indication">to</span> {{item.date_to | date}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<span class="tags">{{item.tags}}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<app-spinner [show]="!loaded"></app-spinner> |
@ -0,0 +1,185 @@ |
|||||
|
@import "../../assets/scss/variables"; |
||||
|
|
||||
|
.component-home { |
||||
|
display: flex; |
||||
|
padding-top: 100px; |
||||
|
height: 100vh; |
||||
|
transform: skew(-15deg) rotate(-15deg); |
||||
|
z-index: 1; |
||||
|
|
||||
|
.goto-prev, |
||||
|
.goto-next { |
||||
|
position: absolute; |
||||
|
height: 40px; |
||||
|
width: 60px; |
||||
|
border: none; |
||||
|
background: none; |
||||
|
color: $black; |
||||
|
font-size: $font-40; |
||||
|
appearance: none; |
||||
|
margin: 0; |
||||
|
padding: 5px; |
||||
|
cursor: pointer; |
||||
|
z-index: 2; |
||||
|
|
||||
|
.icon { |
||||
|
&:before { |
||||
|
transform: translate(-50%, -50%); |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.goto-prev { |
||||
|
top: 60px; |
||||
|
left: 20px; |
||||
|
} |
||||
|
.goto-next { |
||||
|
top: calc(80vh + 10px); |
||||
|
right: 20px; |
||||
|
.icon { |
||||
|
&:before { |
||||
|
transform: translate(-50%, -50%) rotate(180deg); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
display: inline-flex; |
||||
|
margin: 0; |
||||
|
margin-left: -50px; |
||||
|
//animation: slide 150s linear infinite; |
||||
|
transition: margin-left .5s; |
||||
|
|
||||
|
@each $width in 1,2,3,4,5,6 { |
||||
|
.slide-#{$width} { |
||||
|
width: #{($width+2)*100}px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.box { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
background: $black-alpha2; |
||||
|
border-radius: 5px; |
||||
|
overflow: hidden; |
||||
|
margin: auto 0; |
||||
|
padding: 40px 20px; |
||||
|
height: calc(80vh - 90px); |
||||
|
min-height: 250px; |
||||
|
//max-height: 700px; |
||||
|
cursor: pointer; |
||||
|
transform: skew(-6deg, -6deg) rotate(6deg); |
||||
|
transition: transform .4s, background .4s, opacity .4s; |
||||
|
-webkit-backface-visibility: hidden; |
||||
|
|
||||
|
.image { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
object-fit: cover; |
||||
|
transform: translate(-50%, -50%); |
||||
|
opacity: .8; |
||||
|
filter: grayscale(100%) contrast(3); |
||||
|
transition: opacity .4s, filter .4s; |
||||
|
-webkit-backface-visibility: hidden; |
||||
|
z-index: 0; |
||||
|
} |
||||
|
|
||||
|
.text { |
||||
|
display: block; |
||||
|
margin: auto; |
||||
|
text-align: center; |
||||
|
transform: translate(0%, 0%); |
||||
|
color: $yellow; |
||||
|
-webkit-backface-visibility: hidden; |
||||
|
z-index: 1; |
||||
|
|
||||
|
.title { |
||||
|
display: block; |
||||
|
font-size: $font-20; |
||||
|
text-transform: uppercase; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.type { |
||||
|
display: block; |
||||
|
font-size: $font-16; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
|
||||
|
.tags { |
||||
|
display: block; |
||||
|
font-size: $font-12; |
||||
|
text-transform: uppercase; |
||||
|
font-weight: bold; |
||||
|
padding-top: 10px; |
||||
|
} |
||||
|
|
||||
|
.date-container { |
||||
|
display: inline-flex; |
||||
|
flex-wrap: wrap; |
||||
|
|
||||
|
.date-row { |
||||
|
display: block; |
||||
|
width: 100%; |
||||
|
|
||||
|
.date { |
||||
|
display: inline-flex; |
||||
|
margin: auto; |
||||
|
font-size: $font-20; |
||||
|
} |
||||
|
|
||||
|
.date-indication { |
||||
|
margin: 2px 5px auto 5px; |
||||
|
font-size: $font-12; |
||||
|
} |
||||
|
|
||||
|
&:nth-of-type(2) { |
||||
|
margin-top: -12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background: $black; |
||||
|
transform: scale(1.4) rotate(14deg) skew(14deg, 0deg); |
||||
|
z-index: 50; |
||||
|
|
||||
|
.image { |
||||
|
filter: grayscale(0%); |
||||
|
opacity: .5; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:hover, |
||||
|
&.paused { |
||||
|
//animation: none; |
||||
|
animation-play-state: paused; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@media (min-width: map-get($grid-breakpoints, 'md')) { |
||||
|
.component-home { |
||||
|
.goto-prev { |
||||
|
left: 50px; |
||||
|
} |
||||
|
.goto-next { |
||||
|
right: 50px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@keyframes slide { |
||||
|
0% {margin-left: 0;} |
||||
|
50% {margin-left: -100%;} |
||||
|
100% {margin-left: 0;} |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; |
||||
|
|
||||
|
import { HomeComponent } from './home.component'; |
||||
|
|
||||
|
describe('HomeComponent', () => { |
||||
|
let component: HomeComponent; |
||||
|
let fixture: ComponentFixture<HomeComponent>; |
||||
|
|
||||
|
beforeEach(async(() => { |
||||
|
TestBed.configureTestingModule({ |
||||
|
declarations: [ HomeComponent ] |
||||
|
}) |
||||
|
.compileComponents(); |
||||
|
})); |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
fixture = TestBed.createComponent(HomeComponent); |
||||
|
component = fixture.componentInstance; |
||||
|
fixture.detectChanges(); |
||||
|
}); |
||||
|
|
||||
|
it('should create', () => { |
||||
|
expect(component).toBeTruthy(); |
||||
|
}); |
||||
|
}); |
@ -0,0 +1,129 @@ |
|||||
|
import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '@angular/core' |
||||
|
import { Router, NavigationEnd } from '@angular/router' |
||||
|
import { ApisService } from '../services/apis.service' |
||||
|
import { environment } from '../../environments/environment' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-home', |
||||
|
templateUrl: './home.component.html', |
||||
|
styleUrls: ['./home.component.scss'] |
||||
|
}) |
||||
|
export class HomeComponent implements OnInit { |
||||
|
|
||||
|
@ViewChild('scrollContent') scrollContent: ElementRef |
||||
|
|
||||
|
public basePath = `${environment.BASE_PATH}` |
||||
|
public loaded = false |
||||
|
public init = false |
||||
|
|
||||
|
public homeItems: any = [] |
||||
|
public section: string = 'portfolio' |
||||
|
public paused: boolean = false |
||||
|
private scrollPos: number = 0 |
||||
|
|
||||
|
private swipeCoord?: [number, number] |
||||
|
private swipeTime?: number |
||||
|
|
||||
|
constructor( |
||||
|
private apisService: ApisService, |
||||
|
private router: Router, |
||||
|
private renderer: Renderer2 |
||||
|
) { } |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
|
||||
|
this.apisService.getPortfolio(this.section, true).toPromise().then((response) => { |
||||
|
this.homeItems = response.items |
||||
|
|
||||
|
let cnt = 0 |
||||
|
let width = 0 |
||||
|
let tot = 0 |
||||
|
|
||||
|
this.homeItems.forEach((e) => { |
||||
|
e.loading = true |
||||
|
e.width = Math.floor(Math.random()*4)+1 |
||||
|
cnt++ |
||||
|
}) |
||||
|
|
||||
|
},(error) => { |
||||
|
console.error('getPortfolio ERROR', error) |
||||
|
}).catch((e) => { |
||||
|
console.error('getPortfolio CATCH', e) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
ngAfterContentInit() { |
||||
|
|
||||
|
this.init = true |
||||
|
|
||||
|
setInterval( () => { |
||||
|
if(!this.paused) { |
||||
|
const scrollWidth = 300 |
||||
|
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0 |
||||
|
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ? |
||||
|
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth |
||||
|
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px' |
||||
|
} |
||||
|
},2000) |
||||
|
} |
||||
|
|
||||
|
showDetails(id, title = ''): void { |
||||
|
const section = this.section == 'exhibitions' ? 'exhibitions' : 'works' |
||||
|
this.router.navigate([`/detail/${section}/${id}/${title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')}`]) |
||||
|
} |
||||
|
|
||||
|
onLoad(id): void { |
||||
|
this.homeItems.filter(item => item.id == id)[0].loading = false |
||||
|
this.loaded = this.init && this.homeItems.every( (val) => !val.loading) |
||||
|
} |
||||
|
|
||||
|
scroll(dir): void { |
||||
|
const scrollWidth = document.body.clientWidth / 3 |
||||
|
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0 |
||||
|
this.paused = true |
||||
|
|
||||
|
switch(dir) { |
||||
|
case 'prev': |
||||
|
this.scrollPos = scrollPos + scrollWidth >= 0 ? 0 : scrollPos + scrollWidth |
||||
|
break; |
||||
|
case 'next': |
||||
|
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ? |
||||
|
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth |
||||
|
break; |
||||
|
} |
||||
|
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px' |
||||
|
} |
||||
|
|
||||
|
swipe(e: TouchEvent, when: string): void { |
||||
|
const coord: [number, number] = [e.changedTouches[0].clientX, e.changedTouches[0].clientY] |
||||
|
const time = new Date().getTime() |
||||
|
const scrollWidth = document.body.clientWidth |
||||
|
const scrollPos = parseInt(this.scrollContent.nativeElement.style.marginLeft) || 0 |
||||
|
|
||||
|
this.paused = true |
||||
|
|
||||
|
if (when === 'start') { |
||||
|
this.swipeCoord = coord |
||||
|
this.swipeTime = time |
||||
|
} else if (when === 'end') { |
||||
|
const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]] |
||||
|
const duration = time - this.swipeTime |
||||
|
|
||||
|
if (duration < 1000 |
||||
|
&& Math.abs(direction[0]) > 30 |
||||
|
&& Math.abs(direction[0]) > Math.abs(direction[1] * 3)) { |
||||
|
const swipe = direction[0] < 0 ? 'next' : 'prev' |
||||
|
switch(swipe) { |
||||
|
case 'prev': |
||||
|
this.scrollPos = scrollPos + scrollWidth >= 0 ? 0 : scrollPos + scrollWidth |
||||
|
break; |
||||
|
case 'next': |
||||
|
this.scrollPos = scrollPos - scrollWidth <= -(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) ? |
||||
|
-(this.scrollContent.nativeElement.offsetWidth - document.body.clientWidth) : scrollPos - scrollWidth |
||||
|
break; |
||||
|
} |
||||
|
this.scrollContent.nativeElement.style.marginLeft = this.scrollPos + 'px' |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
|
||||
|
<div class="spinner" #spinner></div> |
@ -0,0 +1,15 @@ |
|||||
|
@import "../../assets/scss/variables"; |
||||
|
|
||||
|
.spinner { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
height: 100vh; |
||||
|
width: 100vw; |
||||
|
background: $yellow-alpha; |
||||
|
background-image: url('/assets/images/loader.svg'); |
||||
|
background-size: 200px 200px; |
||||
|
background-repeat: no-repeat; |
||||
|
background-position: center center; |
||||
|
z-index: 100; |
||||
|
} |
@ -0,0 +1,56 @@ |
|||||
|
import { Component, OnInit, Input, ViewChild, ElementRef, SimpleChanges } from '@angular/core' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-spinner', |
||||
|
templateUrl: './spinner.component.html', |
||||
|
styleUrls: ['./spinner.component.scss'] |
||||
|
}) |
||||
|
|
||||
|
export class SpinnerComponent implements OnInit { |
||||
|
|
||||
|
@ViewChild('spinner') spinner: ElementRef |
||||
|
@Input() show: boolean = false |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges) { |
||||
|
if(changes.show && changes.show.firstChange) { |
||||
|
this.loader(changes.show.currentValue ? 'show' : 'hide') |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
loader(action) { |
||||
|
let op = 0 |
||||
|
let timer = null |
||||
|
|
||||
|
switch(action) { |
||||
|
case 'show': |
||||
|
op = 1 |
||||
|
timer = setInterval(() => { |
||||
|
if(op <= 0.1){ |
||||
|
clearInterval(timer) |
||||
|
this.spinner.nativeElement.style.display = 'none' |
||||
|
} |
||||
|
this.spinner.nativeElement.style.opacity = op |
||||
|
this.spinner.nativeElement.style.filter = 'alpha(opacity=' + op * 100 + ")" |
||||
|
op -= op * 0.1 |
||||
|
}, 20) |
||||
|
|
||||
|
break |
||||
|
case 'hide': |
||||
|
op = 0.1 |
||||
|
this.spinner.nativeElement.style.display = 'block' |
||||
|
timer = setInterval(() => { |
||||
|
if(op >= 1){ |
||||
|
clearInterval(timer) |
||||
|
} |
||||
|
this.spinner.nativeElement.style.opacity = op |
||||
|
this.spinner.nativeElement.style.filter = 'alpha(opacity=' + op * 100 + ")" |
||||
|
op += op * 0.1 |
||||
|
}, 20) |
||||
|
|
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
} |
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in new issue