diff --git a/.gitignore b/.gitignore
index 27fa5eb..cd1b3c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,7 @@
node_modules/
package-lock\.json
+
+src/assets/images/contents/
+
+dist/
diff --git a/angular.json b/angular.json
index 52df64f..be23e47 100644
--- a/angular.json
+++ b/angular.json
@@ -24,8 +24,9 @@
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
- "src/favicon.ico",
- "src/assets"
+ "src/assets/images/favicon.png",
+ "src/assets",
+ "src/apis"
],
"styles": [
"src/assets/scss/main.scss"
diff --git a/auth/identity.pem b/auth/identity.pem
new file mode 100644
index 0000000..0fff9d4
--- /dev/null
+++ b/auth/identity.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEApNcN8cn0872DhGSdPobrIR9kBfd+qSL/Sqrgk3mywEuuzhN3
+MOYbaejwdIJacC0OjCMVgm3f2TziefYd2ssvAUjT+M9FCESUSSDHgPVxt22UDZxz
+ZraasA+jdVW0QQtBv8AjyDCXAmMZxcGT1x1htKsKfG2JDxxc1DXub1BaBSkrqcZ1
+1u50UK0DBbRcUhd85z9Oivju3IktDaVow09fpwUR/tK1xO8PPsaAUQMKdN1ArGA/
+O+FFXAirQ7OD8tT4gCmJYrU2h4mAlcjKwpvmXbtHGmZ2tn4nJsaYUxL0vMPHWoTl
+ymOkFbKujBDkTSdKgUdTBy7Bda9ZzAcEBJoGgQIDAQABAoIBAEGVmuO3obEUlu4n
+BfUpwwVzst044nkzBnXT1PR4OCmQMyWk0whulTunRXxlnMwC8UXKc7VoN+b79XPm
++2vg6XvOWSAmf2XRu1n5I8doYG1FuOFNfRDB2HvyTOvTRJuYeflr3hC5XGvDuC5Z
+XZP6CbTTVKG7BwwvEbQRHSPGyXpBiqd+OOfTpeoobrkGJJAKxvXHO0G7zgB5h/H+
+0fjSWvo4QxzOAF74f0qUmWBbvLjWL3yZhVE8mxNcVoV1HlIdXV5anMe9FURci5D4
+U8ehGky3sbHjcc7wro5IPWJYTrDAmvhI8wMMx7gbHe2HOzyWRX++ifjfP77kw2e3
+IYnwMAECgYEA05Ehmsipk4rmJeCgmDJomQVsJgMW1zjOJwIljSZ9o55+LHpma6r0
+O6sdQYgiNwQT0f//iJZkatpDAyC3U6EU9RY7Yoc0R3XK86vryKQlB/Nh1boLldt6
+9TGdL5eCzaraiYuLjGvXMGTS8LjB0ftFCGQswl2kcuOFx1YjE4WPoUECgYEAx3Wo
+yenPXHCdD4kIrQ9SxPha2aobh33MjdwSYSiDliaEoBg/voWJExFmMJuvhR46xqmy
+i8E0RuzzBrG4Zhbdo4kcpdF2GtAuxNw8uYvG+SQub8zAmBT7YpXzTmpGQ2TR3i8S
+XzD57s7C1mSkBIwWuVJYcx3Kgm1mQNIOELU31UECgYEAhNc14HhqcaffRp06eRX9
+s0dCVsPNzalvV/LzHSOz886KrubT9HrNC8Ivhnwx75Vx1IQHMP4tYyJUvVwHgE0+
+WX1yIDWAz/XYTxP94meekNVy8r30lE3RcK+MYNujV/wVaBPktXDpFwvXnyqDGJPL
+Dq/HouslXLYbw8QEFjfgrYECgYB+oSU6ozThpCEihsY6ULskj+PlwohdubEO8wO8
+KSN5RRT4Ksz1YQPIVkiBXaXOJoX8MCpJbayJxs73lgbS0Xt+4oKMh3GqzjaTBpuK
+1MHK1Hyiv+QZ6WA7k6V3SCM5kB1pKItKYeabBStPP2+d725R04SR+PzjVx8O0gzZ
+8KL0wQKBgC7LRztq8PMDGTHGXVxWXwZ08ZAf2MAsBvYyAU5xudS+pRqlNGDgyxiv
+YQEe9n2DVu7GVsoNnKIapmXV1qx0vTU/CpKV0cpXi6m3XVnbtjBsaYtkYNNZfHVl
+E++gP4qgvALKugnzaGn5oby0PPcFFhnNqRwFb5c3diihwMQhkudG
+-----END RSA PRIVATE KEY-----
diff --git a/deploy.sh b/deploy.sh
new file mode 100644
index 0000000..0d0a48f
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+yarn prod
+rsync -avz --delete --exclude '/apis/conn.conn' --exclude '.well-known' --exclude '/uploads' -e "ssh -i ./auth/identity.pem -p2222" ./dist/dslak-website/* cdr@2.238.194.8:/www/dslak.it/
+
diff --git a/package.json b/package.json
index 5c909c1..a68d2bd 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,8 @@
"start": "ng serve",
"dev": "ng serve",
"build": "ng build",
+ "prod": "ng build --prod --configuration production",
+ "deploy": "sh ./deploy.sh",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
@@ -20,6 +22,11 @@
"@angular/platform-browser": "~9.1.7",
"@angular/platform-browser-dynamic": "~9.1.7",
"@angular/router": "~9.1.7",
+ "@kolkov/angular-editor": "^1.1.4",
+ "bootstrap": "^4.5.3",
+ "hammerjs": "^2.0.8",
+ "ng-particles": "^2.1.11",
+ "ngx-image-gallery": "^2.0.5",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.10.2"
diff --git a/src/apis/auth.php b/src/apis/auth.php
new file mode 100755
index 0000000..1cd0185
--- /dev/null
+++ b/src/apis/auth.php
@@ -0,0 +1,41 @@
+status = 200;
+
+$data = json_decode(file_get_contents("php://input"));
+
+if(isset($_GET['act']) && $_GET['act'] == 'login') {
+ if(isset($data->usr) && $data->usr == 'admin' && isset($data->pwd) && $data->pwd == 'JohnHolmes') {
+ http_response_code(200);
+ $content->status = 200;
+ $content->authToken = base64_encode('admin:JohnHolmes'.date("Y-m-d"));
+ } else {
+ http_response_code(401);
+ $content->status = 401;
+ $content->action = 'login';
+ }
+} else if(isset($_GET['act']) && $_GET['act'] == 'check') {
+ if(isset($data->token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
+ http_response_code(200);
+ $content->status = 200;
+ $content->authToken = base64_encode('admin:JohnHolmes'.date("Y-m-d"));
+ } else {
+ http_response_code(200);
+ $content->status = 401;
+ $content->action = 'check';
+ $content->token = $data->token;
+ }
+}
+
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: POST");
+header("Access-Control-Max-Age: 3600");
+
+echo json_encode($content);
+
+?>
diff --git a/src/apis/conn.conn b/src/apis/conn.conn
new file mode 100755
index 0000000..877d806
--- /dev/null
+++ b/src/apis/conn.conn
@@ -0,0 +1,16 @@
+
diff --git a/src/apis/exhibition.php b/src/apis/exhibition.php
new file mode 100644
index 0000000..e008311
--- /dev/null
+++ b/src/apis/exhibition.php
@@ -0,0 +1,80 @@
+token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
+
+ if(isset($_GET['act'])) {
+ if($_GET['act'] == 'save') {
+ if(isset($data->id)) {
+ $q = mysqli_query($conn,"UPDATE `exhibitions` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
+ tags = '".$data->tags."', date_from = '".$data->date_from."', date_to = '".$data->date_to."',
+ image = '".$data->image."', works = '".$data->works."', gallery = '".$data->gallery."',
+ videos = '".$data->videos."' WHERE id = ".$data->id."");
+ } else {
+ $q = mysqli_query($conn,"INSERT INTO `exhibitions`
+ (`id`, `title`, `content`, `tags`, `date_from`, `date_to`, `image`, `works`, `gallery`, `videos`)
+ VALUES (NULL, '".addslashes($data->title)."', '".addslashes($data->content)."', '".$data->tags."',
+ '".$data->date_from."', '".$data->date_to."', '".$data->image."', '".$data->works."',
+ '".$data->gallery."', '".$data->videos."')");
+ }
+
+ if($q) {
+ http_response_code(201);
+ $content->status = 201;
+ } else {
+ http_response_code(403);
+ $content->status = "UPDATE `exhibitions` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
+ tags = '".$data->tags."', date_from = '".$data->date_from."', date_to = '".$data->date_to."',
+ image = '".$data->image."', works = '".$data->works."', gallery = '".$data->gallery."',
+ videos = '".$data->videos."' WHERE id = ".$data->id."";
+ }
+ }
+
+ if($_GET['act'] == 'delete') {
+ if(isset($data->id)) {
+ $q = mysqli_query($conn,"DELETE FROM `exhibitions` WHERE id = ".$data->id."");
+ if($q) {
+ http_response_code(201);
+ $content->status = 201;
+ } else {
+ http_response_code(403);
+ $content->status = 403;
+ }
+ }
+ }
+
+ if($q) {
+ $qe = mysqli_query($conn,"SELECT * FROM `exhibitions` ORDER BY id DESC");
+ if(mysqli_num_rows($qe) > 0) {
+ $content->items = array();
+ while($re = mysqli_fetch_array($qe)) {
+ $item = null;
+ $item->id = $re['id'];
+ $item->title = $re['title'];
+ $item->date_from = $re['date_from'];
+ $item->date_to = $re['date_to'];
+ $item->tags = $re['tags'];
+ $item->image = $re['image'];
+ array_push($content->items, $item);
+ }
+ }
+ }
+ }
+
+} else {
+ http_response_code(401);
+ $content->status = 401;
+}
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: POST");
+header("Access-Control-Max-Age: 3600");
+
+echo json_encode($content);
+
+?>
diff --git a/src/apis/index.php b/src/apis/index.php
new file mode 100644
index 0000000..b91173e
--- /dev/null
+++ b/src/apis/index.php
@@ -0,0 +1,99 @@
+items = array();
+ switch($_GET['query']) {
+ case "portfolio":
+ case "installations":
+ case "entertainment":
+ case "performances":
+ case "workshops":
+ if($_GET['query'] == 'portfolio') {$filter = '';} else {$filter = "WHERE type='".$_GET['query']."'";}
+ if($_GET['random']) {$order = 'ORDER BY RAND()';} else {$order = "ORDER BY id DESC";}
+ $qe = mysqli_query($conn,"SELECT * FROM `works` $filter $order");
+ if(mysqli_num_rows($qe) > 0) {
+ $content = null;
+ $content->items = array();
+ while($re = mysqli_fetch_array($qe)) {
+ $item = null;
+ $item->id = $re['id'];
+ $item->title = $re['title'];
+ $item->type = $re['type'];
+ $item->tags = $re['tags'];
+ $item->image = $re['image'];
+ array_push($content->items, $item);
+ }
+ }
+ break;
+ case "exhibitions":
+ $qe = mysqli_query($conn,"SELECT * FROM `exhibitions` ORDER BY date_from DESC");
+ if(mysqli_num_rows($qe) > 0) {
+ $content = null;
+ $content->items = array();
+ while($re = mysqli_fetch_array($qe)) {
+ $item = null;
+ $item->id = $re['id'];
+ $item->title = $re['title'];
+ $item->date_from = $re['date_from'];
+ $item->date_to = $re['date_to'];
+ $item->tags = $re['tags'];
+ $item->image = $re['image'];
+ array_push($content->items, $item);
+ }
+ }
+ break;
+ case "detail":
+ $qe = mysqli_query($conn,"SELECT * FROM `".$_GET['type']."` WHERE id=".$_GET['id']);
+ if(mysqli_num_rows($qe)>0) {
+ $content = null;
+ $re = mysqli_fetch_array($qe);
+ $item = null;
+ $item->id = $re['id'];
+ $item->title = $re['title'];
+ $item->content = $re['content'];
+ $item->tags = $re['tags'];
+ $item->image = $re['image'];
+ $item->videos = $re['videos'];
+ $item->gallery = $re['gallery'];
+ if($_GET['type'] == 'exhibitions') {
+ $item->date_from = $re['date_from'];
+ $item->date_to = $re['date_to'];
+ $item->works = array();
+ $qx = mysqli_query($conn,"SELECT id,title FROM `works` WHERE id IN (".$re['works'].")");
+ while($re = mysqli_fetch_array($qx)) {
+ $ex = null;
+ $ex->id = $re['id'];
+ $ex->title = $re['title'];
+ array_push($item->works, $ex);
+ }
+ } else if($_GET['type'] == 'works') {
+ $item->type = $re['type'];
+ $item->exhibitions = array();
+ $qx = mysqli_query($conn,"SELECT id,title FROM `exhibitions` WHERE id IN (".$re['exhibitions'].")");
+ while($re = mysqli_fetch_array($qx)) {
+ $ex = null;
+ $ex->id = $re['id'];
+ $ex->title = $re['title'];
+ array_push($item->exhibitions, $ex);
+ }
+ }
+ $content->item = $item;
+ }
+ break;
+ }
+}
+http_response_code(200);
+
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: GET");
+header("Access-Control-Max-Age: 3600");
+echo json_encode($content);
+
+?>
diff --git a/src/apis/remove.php b/src/apis/remove.php
new file mode 100644
index 0000000..8f3b4a0
--- /dev/null
+++ b/src/apis/remove.php
@@ -0,0 +1,24 @@
+token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
+
+ @unlink('..'.$data->url);
+ http_response_code(200);
+ $content->status = 200;
+
+} else {
+ http_response_code(401);
+ $content->status = 401;
+}
+
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: POST");
+header("Access-Control-Max-Age: 3600");
+
+echo json_encode($content);
+
+?>
diff --git a/src/apis/upload.php b/src/apis/upload.php
new file mode 100644
index 0000000..39e84a7
--- /dev/null
+++ b/src/apis/upload.php
@@ -0,0 +1,35 @@
+status = 200;
+ $content->imageUrl = $path."/".$filename;
+
+ } else {
+ http_response_code(401);
+ $content->status = 401;
+ $content->megssage = 'No file uploaded';
+ }
+} else {
+ http_response_code(401);
+ $content->status = 401;
+}
+
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: POST");
+header("Access-Control-Max-Age: 3600");
+
+echo json_encode($content);
+
+?>
diff --git a/src/apis/work.php b/src/apis/work.php
new file mode 100644
index 0000000..9ec5edf
--- /dev/null
+++ b/src/apis/work.php
@@ -0,0 +1,75 @@
+token) && $data->token == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
+
+ if(isset($_GET['act'])) {
+ if($_GET['act'] == 'save') {
+ if(isset($data->id)) {
+ $q = mysqli_query($conn,"UPDATE `works` SET title = '".addslashes($data->title)."', content = '".addslashes($data->content)."',
+ type = '".$data->type."', tags = '".$data->tags."', image = '".$data->image."',
+ exhibitions = '".$data->exhibitions."', gallery = '".$data->gallery."', videos = '".$data->videos."'
+ WHERE id = ".$data->id."");
+ } else {
+ $q = mysqli_query($conn,"INSERT INTO `works` (`id`, `title`, `content`, `type`, `tags`, `image`, `exhibitions`, `gallery`, `videos`)
+ VALUES (NULL, '".addslashes($data->title)."', '".addslashes($data->content)."', '".$data->type."',
+ '".$data->tags."', '".$data->image."', '".$data->exhibitions."', '".$data->gallery."',
+ '".$data->videos."')");
+ }
+
+ if($q) {
+ http_response_code(201);
+ $content->status = 201;
+ } else {
+ http_response_code(403);
+ $content->status = 403;
+ }
+ }
+
+ if($_GET['act'] == 'delete') {
+ if(isset($data->id)) {
+ $q = mysqli_query($conn,"DELETE FROM `works` WHERE id = ".$data->id."");
+ if($q) {
+ http_response_code(201);
+ $content->status = 201;
+ } else {
+ http_response_code(403);
+ $content->status = 403;
+ }
+ }
+ }
+
+ if($q) {
+ $qe = mysqli_query($conn,"SELECT * FROM `works` ORDER BY id DESC");
+ if(mysqli_num_rows($qe) > 0) {
+ $content->items = array();
+ while($re = mysqli_fetch_array($qe)) {
+ $item = null;
+ $item->id = $re['id'];
+ $item->title = $re['title'];
+ $item->type = $re['type'];
+ $item->tags = $re['tags'];
+ $item->image = $re['image'];
+ array_push($content->items, $item);
+ }
+ }
+ }
+ }
+
+} else {
+ http_response_code(401);
+ $content->status = 401;
+}
+header("Access-Control-Allow-Origin: *");
+header("Content-Type: application/json; charset=UTF-8");
+header("Access-Control-Allow-Methods: POST");
+header("Access-Control-Max-Age: 3600");
+
+echo json_encode($content);
+
+?>
diff --git a/src/app/about/about.component.html b/src/app/about/about.component.html
new file mode 100644
index 0000000..980843a
--- /dev/null
+++ b/src/app/about/about.component.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
DSLAK è un progetto che nasce nel 2010 da un'idea di Carmine De Rosa, sviluppatore ed amministratore di sistema in ambiente Unix/Linux, dopo aver completato gli studi in ambito elettronico approfondisce le proprie conoscenze informatiche presso l'Università di Salerno dove insieme ad un gruppo di appassionati di haking fonda HCSSLUG (Linux Users Group dell'Università di Salerno) e ed il relativo HackLab con il quale realizza numerosi progetti in ambito OpenSource, basati sulla ricerca e sulla sperimentazione in ambito tecnologico.
+
+
Nel 2006 si avvicina alle arti digitali e nel 2009, incuriosito dapprima dall'aspetto tecnico ma senza tralasciare quello scenico e concettuale, inizia ad approfondire le propie conoscenze nell'ambito della new-media art e delle installazioni interattive, studiando le soluzioni adottate da artisti affermati e sviluppando varie soluzioni alternative che utilizzano però un approccio più sostenibile, efficiente e soprattutto open.
+
+
Nel 2011 realizza le sue prime installazioni interattive e ad oggi è alla continua ricerca di soluzioni creative.
+
+
DSLAK ha ad oggi all'attivo numerosi progetti, come workshops sull'utilizzo di sensori e microcontrollori nel campo dell'interattività e delle arti digitali, installazioni interattive e performances live frutto della sperimentazione e della ricerca continua, oltre alle collaborazioni con diversi artisti nazionali ed internazionali.
+
+
+
+
+
diff --git a/src/app/about/about.component.scss b/src/app/about/about.component.scss
new file mode 100644
index 0000000..22ceada
--- /dev/null
+++ b/src/app/about/about.component.scss
@@ -0,0 +1,86 @@
+@import "../../assets/scss/variables";
+
+.component-about {
+ .content {
+ position: relative;
+ margin: 150px auto 80px auto;
+ padding: 40px 50px;
+ font-size: $font-18;
+ text-align: justify;
+ background: $white-alpha;
+ color: $black;
+ box-shadow: 0px 0px 25px $white-alpha;
+ border-radius: 10px;
+ z-index: 1;
+
+
+ .about-links {
+ color: $black;
+
+ .link {
+ display: flex;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+ line-height: 35px;
+ width: 200px;
+ transition: transform .3s;
+ -webkit-backface-visibility: hidden;
+
+ .icon {
+ display: inline-block;
+ font-size: 15px;
+ padding: 5px;
+ margin: 5px;
+ background: $dark-gray;
+ border-radius: 2px;
+ color: $white;
+ height: 25px;
+ width: 25px;
+ text-align: center;
+ }
+
+ .label {
+ display: inline-block;
+ color: $dark-gray;
+ font-size: $font-16;
+ padding-left: 5px;
+
+ }
+
+ &:hover {
+ transform: scale(1.1);
+ }
+ }
+ }
+
+ .back {
+ position: absolute;
+ top: -40px;
+ left: 0px;
+ height: 40px;
+ width: 60px;
+ appearance: none;
+ border: none;
+ padding: 0;
+ font-size: $font-40;
+ color: $black-alpha;
+ background: transparent;
+ cursor: pointer;
+ transition: transform .3s;
+ -webkit-backface-visibility: hidden;
+
+ &:hover {
+ transform: scale(1.1) translateX(-10px);
+ }
+ }
+ }
+}
+
+@media (min-width: map-get($grid-breakpoints, 'md')) {
+ .component-about {
+ .content {
+ transform: rotate(2deg) skew(0deg, -6deg);
+ }
+ }
+}
diff --git a/src/app/about/about.component.spec.ts b/src/app/about/about.component.spec.ts
new file mode 100644
index 0000000..6b77344
--- /dev/null
+++ b/src/app/about/about.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AboutComponent } from './about.component';
+
+describe('AboutComponent', () => {
+ let component: AboutComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AboutComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AboutComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/about/about.component.ts b/src/app/about/about.component.ts
new file mode 100644
index 0000000..204648a
--- /dev/null
+++ b/src/app/about/about.component.ts
@@ -0,0 +1,21 @@
+import { Component, OnInit } from '@angular/core'
+import { Location } from '@angular/common'
+
+@Component({
+ selector: 'app-about',
+ templateUrl: './about.component.html',
+ styleUrls: ['./about.component.scss']
+})
+export class AboutComponent implements OnInit {
+
+ constructor(
+ private location: Location
+ ) { }
+
+ ngOnInit(): void {
+ }
+
+ back() {
+ this.location.back()
+ }
+}
diff --git a/src/app/admin/admin.component.html b/src/app/admin/admin.component.html
new file mode 100644
index 0000000..4bcd7dc
--- /dev/null
+++ b/src/app/admin/admin.component.html
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
{{sectionTitle}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/admin/admin.component.scss b/src/app/admin/admin.component.scss
new file mode 100644
index 0000000..be80feb
--- /dev/null
+++ b/src/app/admin/admin.component.scss
@@ -0,0 +1,210 @@
+@import "../../assets/scss/variables";
+
+.component-admin {
+
+ .login-form-container {
+ text-align: center;
+ padding: 30px 40px;
+ color: $white;
+
+ .login-label {
+ font-size: $font-14;
+ color: $black;
+ padding: 8px;
+ }
+
+ .button {
+ width: 300px;
+ }
+ }
+
+ .edit-container {
+ .title {
+ display: block;
+ font-size: $font-30;
+ font-weight: bolder;
+ text-transform: uppercase;
+ padding: 20px 0;
+ }
+ .form {
+ .label {
+ display: block;
+ font-size: $font-20;
+ text-transform: uppercase;
+ padding: 20px 0 5px 0;
+ }
+
+ .gallery-container {
+ display: flex;
+ background: $white;
+ border-radius: 4px;
+ width: 100%;
+ padding: 5px;
+ min-height: 100px;
+
+ .image-add {
+ appearance: none;
+ display: inline-block;
+ position: relative;
+ border: 2px solid $light-gray;
+ border-radius: 4px;
+ height: 100px;
+ width: 100px;
+ margin: 5px;
+ cursor: pointer;
+
+ &:before {
+ content: '\e90a';
+ font-family: $font-icon;
+ font-size: $font-30;
+ color: $light-gray;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ input {
+ visibility: hidden;
+ }
+ }
+
+ .image-box {
+ display: inline-block;
+ position: relative;
+ border: 2px solid $light-gray;
+ height: 100px;
+ width: 120px;
+ margin: 5px;
+ border-radius: 4px;
+ overflow: hidden;
+
+ .image {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ object-fit: cover;
+ z-index: 0;
+ }
+
+ .remove,
+ .set-main {
+ position: absolute;
+ top: 4px;
+ right: 4px;
+ border: 0;
+ border-radius: 2px;
+ color: $black;
+ height: 20px;
+ width: 20px;
+ background: $white-alpha;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+ font-size: $font-12;
+ z-index: 10;
+ }
+
+ .remove {
+ color: $red;
+ }
+ .set-main {
+ color: $green;
+ right: 30px;
+ }
+
+ &.main {
+ border: 2px solid $yellow;
+ }
+ }
+ }
+
+ .selected-exhibition,
+ .selected-work,
+ .selected-video {
+ display: block;
+ position: relative;
+ font-size: $font-16;
+ font-weight: bolder;
+ border-radius: 4px;
+ border: 2px solid $white;
+ background: $white-alpha;
+ cursor: pointer;
+ padding: 8px 50px 8px 15px;
+ margin-bottom: 5px;
+
+ &:before {
+ content: '\e903';
+ position: absolute;
+ top: 8px;
+ right: 10px;
+ font-family: $font-icon;
+ font-size: $font-20;
+ color: $gray;
+ }
+ }
+
+ .preview-box {
+ border-radius: 4px;
+ background: $white-alpha2;
+ padding: 10px;
+ width: 100%;
+ }
+ }
+ }
+
+ .menu {
+ background: $dark-gray;
+
+ .section-title {
+ display: block;
+ width: 100%;
+ padding: 50px 10px 10px;
+ font-size: $font-22;
+ font-weight: bolder;
+ text-transform: uppercase;
+ color: $white;
+ text-align: center;
+ border-bottom: 1px solid $black-alpha;
+ }
+
+ .action {
+ display: block;
+ appearance: none;
+ border: none;
+ border-radius: 0px;
+ width: 100%;
+ padding: 10px;
+ font-size: $font-14;
+ text-transform: uppercase;
+ color: $white;
+ background: $dark-gray;
+ cursor: pointer;
+ border-bottom: 1px solid $black-alpha;
+
+ &.active {
+ background: $yellow;
+ color: $black;
+ border: none;
+ }
+ }
+ }
+}
+
+@media (min-width: map-get($grid-breakpoints, 'md')) {
+ .component-admin {
+ .menu {
+ position: fixed;
+ height: 100vh;
+ width: 25%;
+ }
+ }
+}
+
+@media (min-width: map-get($grid-breakpoints, 'lg')) {
+ .component-admin {
+ .menu {
+ width: 16.66%;
+ }
+ }
+}
diff --git a/src/app/admin/admin.component.spec.ts b/src/app/admin/admin.component.spec.ts
new file mode 100644
index 0000000..72e742f
--- /dev/null
+++ b/src/app/admin/admin.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { AdminComponent } from './admin.component';
+
+describe('AdminComponent', () => {
+ let component: AdminComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ AdminComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AdminComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/admin/admin.component.ts b/src/app/admin/admin.component.ts
new file mode 100644
index 0000000..03443d9
--- /dev/null
+++ b/src/app/admin/admin.component.ts
@@ -0,0 +1,437 @@
+import { Component, OnInit } from '@angular/core'
+import { ApisService } from '../services/apis.service'
+import { AuthService } from '../services/auth.service'
+import { AngularEditorConfig } from '@kolkov/angular-editor'
+import { environment } from '../../environments/environment'
+import { editorConfig } from '../../config/config'
+
+@Component({
+ selector: 'app-admin',
+ templateUrl: './admin.component.html',
+ styleUrls: ['./admin.component.scss']
+})
+export class AdminComponent implements OnInit {
+
+ private restApi = `${environment.API_URL}`
+ public basePath = `${environment.BASE_PATH}`
+
+ public authCheck: boolean = false
+ public userName: string = ''
+ public password: string = ''
+ public activeEditor: string = ''
+ public sectionTitle: string = ''
+ public activeModify: boolean = false
+ public modifyId: number = null
+
+ public exhibitions: any = []
+ public works: any = []
+ public selectedExhibitions: any = []
+ public selectedWorks: any = []
+ public selectedVideos: any = []
+ public selectedGallery: any = []
+
+ // ngModels
+ public title: string = ''
+ public type: string = ''
+ public content: string = ''
+ public tags: string = ''
+ public dateFrom: string = ''
+ public dateTo: string = ''
+ public mainImage: string = ''
+ public videoType: string = ''
+ public videoURL: string = ''
+
+ public editorConfig: AngularEditorConfig = editorConfig
+ public workSections: any = [
+ {title: 'Entertainment', section: 'entertainment'},
+ {title: 'Installations', section: 'installations'},
+ {title: 'Performances', section: 'performances'},
+ {title: 'Workshops', section: 'workshops'}
+ ]
+
+ constructor(
+ private authService: AuthService,
+ private apisService: ApisService
+ ) { }
+
+ ngOnInit(): void {
+
+ const body = { token: window.sessionStorage.getItem('authToken') }
+ this.authService.authCheck(body).toPromise().then((response) => {
+ this.authCheck = response.status && response.status == 200
+
+ if(this.authCheck) {
+ this.loadData()
+ }
+
+ },(error) => {
+ this.authCheck = false
+ console.error('Auth ERROR INIT', error)
+ }).catch((e) => {
+ this.authCheck = false
+ console.error('Auth CATCH INIT', e)
+ })
+ }
+
+ login(): void {
+ const body = { usr: this.userName, pwd: this.password }
+ this.authService.login(body).toPromise().then((response) => {
+ this.authCheck = response.status && response.status == 200
+ if(this.authCheck) {
+ window.sessionStorage.setItem('authToken', response.authToken)
+ this.loadData()
+ }
+ },(error) => {
+ console.error('Auth ERROR', error)
+ }).catch((e) => {
+ console.error('Auth CATCH', e)
+ })
+ }
+
+ loadData(): void {
+ this.apisService.getPortfolio('exhibitions').toPromise().then((response) => {
+ this.exhibitions = response.items
+ },(error) => {
+ console.error('getPortfolio ERROR', error)
+ }).catch((e) => {
+ console.error('getPortfolio CATCH', e)
+ })
+
+ this.apisService.getPortfolio('portfolio').toPromise().then((response) => {
+ this.works = response.items
+ },(error) => {
+ console.error('getPortfolio ERROR', error)
+ }).catch((e) => {
+ console.error('getPortfolio CATCH', e)
+ })
+ }
+
+ showEditor(section): void {
+ switch(section) {
+ case 'works-add':
+ this.sectionTitle = 'Add work'
+ break;
+ case 'works-modify':
+ this.sectionTitle = 'Modify work'
+ break;
+ case 'works-delete':
+ this.sectionTitle = 'Delete work'
+ break;
+ case 'exhibitions-add':
+ this.sectionTitle = 'Add exhibition'
+ break;
+ case 'exhibitions-modify':
+ this.sectionTitle = 'Modify exhibition'
+ break;
+ case 'exhibitions-delete':
+ this.sectionTitle = 'Delete exhibition'
+ break;
+ }
+ this.activeModify = false
+ this.activeEditor = section
+ this.modifyId = null
+ this.resetFields()
+ }
+
+ exhibitionAdd(id): void {
+ this.selectedExhibitions.push(
+ this.exhibitions.filter(item => item.id == id)[0]
+ )
+ this.exhibitions = this.exhibitions.filter(item => item.id != id)
+ }
+
+ exhibitionRemove(id): void {
+ this.exhibitions.push(
+ this.selectedExhibitions.filter(item => item.id == id)[0]
+ )
+ this.selectedExhibitions = this.selectedExhibitions.filter(item => item.id != id)
+ }
+
+ workAdd(id): void {
+ this.selectedWorks.push(
+ this.works.filter(item => item.id == id)[0]
+ )
+ this.works = this.works.filter(item => item.id != id)
+ }
+
+ workRemove(id): void {
+ this.works.push(
+ this.selectedWorks.filter(item => item.id == id)[0]
+ )
+ this.selectedWorks = this.selectedWorks.filter(item => item.id != id)
+ }
+
+ videoAdd(): void {
+ this.selectedVideos.push({
+ type: this.videoType,
+ url: this.videoURL.replace(/\"/g, "\\\"")
+ })
+ this.videoURL = ''
+ }
+
+ videoRemove(url): void {
+ this.selectedVideos = this.selectedVideos.filter(item => item.url != url)
+ }
+
+ onFileChanged(e) {
+ const file = (e.target).files[0]
+ const uploadData = new FormData()
+ uploadData.append('token', window.sessionStorage.getItem('authToken'))
+ uploadData.append('path', 'assets')
+ uploadData.append('file', file, file.name)
+ this.apisService.uploadImage(uploadData).toPromise().then((response) => {
+ this.selectedGallery.push({
+ title: response.title || '',
+ url: response.imageUrl
+ })
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ gallerySetMain(url): void {
+ this.selectedGallery.forEach((e) => {
+ if(e.url == url) {
+ e.main = true
+ } else {
+ delete e.main
+ }
+ })
+ }
+
+ galleryRemove(url): void {
+ const body = {
+ token: window.sessionStorage.getItem('authToken'),
+ url: url
+ }
+ this.apisService.removeImage(body).toPromise().then((response) => {
+ this.selectedGallery = this.selectedGallery.filter(item => item.url != url)
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ selectWork(id): void {
+ this.activeModify = true
+ this.modifyId = id
+ this.apisService.getDetails('works', id).toPromise().then((response) => {
+ const detail = response.item
+ this.apisService.getPortfolio('exhibitions').toPromise().then((response) => {
+ this.exhibitions = response.items
+ this.title = detail.title
+ this.content = detail.content
+ this.type = detail.type
+ this.tags = detail.tags
+ this.selectedExhibitions = detail.exhibitions.length ?
+ this.exhibitions.filter(item => detail.exhibitions.map(a => a.id).indexOf(item.id) > -1) : []
+ this.selectedGallery = detail.gallery ? JSON.parse(detail.gallery) : []
+ this.selectedVideos = detail.videos ? JSON.parse(detail.videos) : []
+
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ selectExhibition(id): void {
+
+ this.activeModify = true
+ this.modifyId = id
+ this.apisService.getDetails('exhibitions', id).toPromise().then((response) => {
+ const detail = response.item
+ this.apisService.getPortfolio('portfolio').toPromise().then((response) => {
+ this.works = response.items
+ this.title = detail.title
+ this.content = detail.content
+ this.tags = detail.tags
+ this.dateFrom = detail.date_from
+ this.dateTo = detail.date_to
+ this.selectedWorks = detail.works.length ?
+ this.works.filter(item => detail.works.map(a => a.id).indexOf(item.id) > -1) : []
+ this.selectedGallery = detail.gallery ? JSON.parse(detail.gallery) : []
+ this.selectedVideos = detail.videos ? JSON.parse(detail.videos) : []
+
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ saveData(): void {
+ if(this.activeEditor == 'works-add' || this.activeEditor == 'works-modify') {
+ this.saveWork()
+ }
+ if(this.activeEditor == 'exhibitions-add' || this.activeEditor == 'exhibitions-modify') {
+ this.saveExhibition()
+ }
+ }
+
+ saveWork(): void {
+ let error = false
+ let errorMessages = []
+ const mainImage = this.selectedGallery.filter(item => item.main)
+
+ if(!this.title){
+ error = true
+ errorMessages.push('No title')
+ }
+ if(!this.type){
+ error = true
+ errorMessages.push('No type selected')
+ }
+
+ if(this.selectedGallery.length == 0 || mainImage.length == 0){
+ error = true
+ errorMessages.push('No main image selected')
+ }
+
+ if(error) {
+ console.log('ERRORS:',errorMessages)
+ } else {
+ const body = {
+ id: this.activeModify ? this.modifyId : null,
+ token: window.sessionStorage.getItem('authToken'),
+ title: this.title,
+ content: this.content,
+ type: this.type,
+ tags: this.tags,
+ image: mainImage[0].url,
+ exhibitions: this.selectedExhibitions.map(a => a.id).join(','),
+ gallery: JSON.stringify(this.selectedGallery),
+ videos: JSON.stringify(this.selectedVideos)
+ }
+
+ this.apisService.saveWork(body).toPromise().then((response) => {
+ this.resetFields()
+ this.works = response.items
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+ }
+
+ saveExhibition(): void {
+ let error = false
+ let errorMessages = []
+ const mainImage = this.selectedGallery.filter(item => item.main)
+
+ if(!this.title){
+ error = true
+ errorMessages.push('No title')
+ }
+ if(!this.dateFrom){
+ error = true
+ errorMessages.push('No date from selected')
+ }
+ if(!this.dateTo){
+ error = true
+ errorMessages.push('No date to selected')
+ }
+ if(this.selectedGallery.length == 0 || mainImage.length == 0){
+ error = true
+ errorMessages.push('No main image selected')
+ }
+
+ if(error) {
+ console.log('ERRORS:',errorMessages)
+ } else {
+ const body = {
+ id: this.activeModify ? this.modifyId : null,
+ token: window.sessionStorage.getItem('authToken'),
+ title: this.title,
+ content: this.content,
+ date_from: this.dateFrom,
+ date_to: this.dateTo,
+ tags: this.tags,
+ image: mainImage[0].url,
+ works: this.selectedWorks.map(a => a.id).join(','),
+ gallery: JSON.stringify(this.selectedGallery),
+ videos: JSON.stringify(this.selectedVideos)
+ }
+
+ this.apisService.saveExhibition(body).toPromise().then((response) => {
+ this.resetFields()
+ this.exhibitions = response.items
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+ }
+
+ deleteData(id): void {
+ if(this.activeEditor == 'works-delete') {
+ this.deleteWork(id)
+ }
+ if(this.activeEditor == 'exhibitions-delete') {
+ this.deleteExhibition(id)
+ }
+ }
+
+ deleteWork(id): void {
+
+ const body = {
+ id: id,
+ token: window.sessionStorage.getItem('authToken')
+ }
+
+ this.apisService.deleteWork(body).toPromise().then((response) => {
+ this.resetFields()
+ this.works = response.items
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ deleteExhibition(id): void {
+
+ const body = {
+ id: id,
+ token: window.sessionStorage.getItem('authToken')
+ }
+
+ this.apisService.deleteExhibition(body).toPromise().then((response) => {
+ this.resetFields()
+ this.exhibitions = response.items
+ },(error) => {
+ console.error(error)
+ }).catch((e) => {
+ console.error(e)
+ })
+ }
+
+ resetFields(): void {
+ this.title = ''
+ this.content = ''
+ this.type = ''
+ this.tags = ''
+ this.dateFrom = ''
+ this.dateTo = ''
+ this.selectedExhibitions = []
+ this.selectedWorks = []
+ this.selectedGallery = []
+ this.selectedVideos = []
+ this.modifyId = null
+ }
+}
diff --git a/src/app/app-layout/app-layout.component.html b/src/app/app-layout/app-layout.component.html
index 04d51c8..e46bcfe 100644
--- a/src/app/app-layout/app-layout.component.html
+++ b/src/app/app-layout/app-layout.component.html
@@ -1,2 +1,7 @@
-
-app-layout works!
+
+
+
+
+
+
diff --git a/src/app/app-layout/app-layout.component.scss b/src/app/app-layout/app-layout.component.scss
index e69de29..0d2866d 100644
--- a/src/app/app-layout/app-layout.component.scss
+++ b/src/app/app-layout/app-layout.component.scss
@@ -0,0 +1,3 @@
+main {
+ overflow: hidden;
+}
diff --git a/src/app/app-layout/app-layout.component.ts b/src/app/app-layout/app-layout.component.ts
index ec8cf2b..26318ab 100644
--- a/src/app/app-layout/app-layout.component.ts
+++ b/src/app/app-layout/app-layout.component.ts
@@ -1,4 +1,6 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core'
+import { Router } from '@angular/router'
+import type { Container } from 'tsparticles'
@Component({
selector: 'app-app-layout',
@@ -7,9 +9,98 @@ import { Component, OnInit } from '@angular/core';
})
export class AppLayoutComponent implements OnInit {
- constructor() { }
+ public page: string = '/'
+ public particlesEnabled: boolean = false
+ public id: string = 'tsparticles'
+
+ public particlesOptions: any = {
+ background: {
+ color: {
+ value: "transparent"
+ }
+ },
+ fpsLimit: 60,
+ interactivity: {
+ detectsOn: "canvas",
+ events: {
+ onClick: {
+ enable: true,
+ mode: "push"
+ },
+ onHover: {
+ enable: true,
+ mode: "repulse"
+ },
+ resize: true
+ },
+ modes: {
+ bubble: {
+ distance: 200,
+ duration: 2,
+ opacity: 0.8,
+ size: 40
+ },
+ push: {
+ quantity: 6
+ },
+ repulse: {
+ distance: 100,
+ duration: 0.4
+ }
+ }
+ },
+ particles: {
+ color: {
+ value: "#fff"
+ },
+ links: {
+ color: "#fff",
+ distance: 150,
+ enable: true,
+ opacity: 0.7,
+ width: 1
+ },
+ collisions: {
+ enable: true
+ },
+ move: {
+ direction: "none",
+ enable: true,
+ outMode: "bounce",
+ random: false,
+ speed: 2,
+ straight: false
+ },
+ number: {
+ density: {
+ enable: true,
+ value_area: 600
+ },
+ value: 90
+ },
+ opacity: {
+ value: 0.5
+ },
+ shape: {
+ type: "circle"
+ },
+ size: {
+ random: true,
+ value: 5
+ }
+ },
+ detectRetina: true
+ }
+
+ constructor(private router: Router) { }
ngOnInit(): void {
+ this.page = this.router.url
+ this.particlesEnabled = this.page != '/admin'
}
+
+ particlesLoaded(container: Container): void {
+ //console.log('particlesLoaded', container)
+ }
}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 9f96b1f..d21da2f 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -1,13 +1,37 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { AppLayoutComponent } from './app-layout/app-layout.component'
-
+import { AboutComponent } from './about/about.component'
+import { PortfolioComponent } from './portfolio/portfolio.component'
+import { DetailComponent } from './detail/detail.component'
+import { HomeComponent } from './home/home.component'
+import { AdminComponent } from './admin/admin.component'
const routes: Routes = [
{
path: '',
component: AppLayoutComponent,
- children: []
+ children: [
+ //{ path: '', redirectTo: '/portfolio', pathMatch: 'full' },
+ { path: '', component: HomeComponent },
+ { path: 'about', component: AboutComponent },
+ { path: 'portfolio', component: PortfolioComponent },
+ { path: 'exhibitions', component: PortfolioComponent },
+ { path: 'installations', component: PortfolioComponent },
+ { path: 'entertainment', component: PortfolioComponent },
+ { path: 'performances', component: PortfolioComponent },
+ { path: 'workshops', component: PortfolioComponent },
+ { path: 'detail', component: DetailComponent,
+ children: [
+ { path: '**', component: DetailComponent,
+ children: [
+ { path: '**', component: DetailComponent }
+ ]
+ }
+ ]
+ },
+ { path: 'admin', component: AdminComponent }
+ ]
}
]
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index a10ec68..cf7935c 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,20 +1,42 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
+import { HttpClientModule } from '@angular/common/http';
+import { NgParticlesModule } from "ng-particles";
+import { NgxImageGalleryModule } from 'ngx-image-gallery';
+import { FormsModule } from '@angular/forms';
+import { AngularEditorModule } from '@kolkov/angular-editor';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { AppLayoutComponent } from './app-layout/app-layout.component';
+import { AboutComponent } from './about/about.component';
+import { PortfolioComponent } from './portfolio/portfolio.component';
+import { DetailComponent } from './detail/detail.component';
+import { AdminComponent } from './admin/admin.component';
+import { HomeComponent } from './home/home.component';
+import { SpinnerComponent } from './spinner/spinner.component';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
- AppLayoutComponent
+ AppLayoutComponent,
+ AboutComponent,
+ PortfolioComponent,
+ DetailComponent,
+ AdminComponent,
+ HomeComponent,
+ SpinnerComponent
],
imports: [
BrowserModule,
- AppRoutingModule
+ AppRoutingModule,
+ NgParticlesModule,
+ NgxImageGalleryModule,
+ FormsModule,
+ AngularEditorModule,
+ HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
diff --git a/src/app/detail/detail.component.html b/src/app/detail/detail.component.html
new file mode 100644
index 0000000..488b0f3
--- /dev/null
+++ b/src/app/detail/detail.component.html
@@ -0,0 +1,76 @@
+
+
+
+
+
{{details.title}}
+
+
+ from
+ on
+ {{details.date_from | date}}
+ to {{details.date_to | date}}
+
+
+
+
= 3}">
+
+
{{image.title}}
+
+
+
+
+
+
+
+
+
+
= 3}">
+
{{video.title}}
+
+
+
+
+
+
+
+
+
+
+
+
+
Tags: {{details.tags}}
+
+
Exhibitions:
+ {{exhibition.title}}
+
+
+
Works:
+ {{work.title}}
+
+
+
+
+
+
+
diff --git a/src/app/detail/detail.component.scss b/src/app/detail/detail.component.scss
new file mode 100644
index 0000000..86015ae
--- /dev/null
+++ b/src/app/detail/detail.component.scss
@@ -0,0 +1,152 @@
+@import "../../assets/scss/variables";
+
+.component-detail {
+
+ .content {
+ position: relative;
+ margin: 150px auto 80px auto;
+ padding: 40px 50px;
+ background: $white-alpha;
+ color: $black;
+ box-shadow: 0px 0px 25px $white-alpha;
+ border-radius: 10px;
+ z-index: 1;
+
+ .title {
+ margin: 0;
+ font-size: $font-34;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+
+ .text {
+ font-size: $font-18;
+ text-align: justify;
+ padding: 40px 0;
+ }
+
+ .date-container {
+ display: inline-flex;
+
+ .date {
+ display: inline-flex;
+ margin: auto;
+ font-size: $font-20;
+ }
+
+ .date-indication {
+ margin: 2px 5px auto 5px;
+ font-size: $font-12;
+ }
+ }
+
+ .gallery {
+ padding-top: 40px;
+
+ .gallery-title {
+ font-size: $font-18;
+ font-weight: bold;
+ padding-bottom: 5px;
+ }
+
+ .gallery-container {
+ position: relative;
+ //padding-bottom: 56.25%;
+ height: 180px;
+
+ .image{
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ cursor: pointer;
+ transition: transform .4s;
+
+ &:hover {
+ transform: scale(1.2) rotate(1deg);
+ z-index: 10;
+ }
+ }
+ }
+ }
+
+ .videos {
+ padding-bottom: 40px;
+
+ .video-title {
+ font-size: $font-18;
+ font-weight: bold;
+ padding-bottom: 5px;
+ }
+
+ .youtube,
+ .vimeo,
+ .embed {
+ position: relative;
+ padding-bottom: 56.25%;
+
+ iframe,
+ .iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+ }
+ }
+
+ .tags,
+ .links {
+ display: block;
+ font-size: $font-12;
+ text-transform: uppercase;
+ padding: 5px 0;
+
+ .link {
+ display: inline-block;
+ font-size: $font-12;
+ text-transform: uppercase;
+ padding: 0 5px;
+ cursor: pointer;
+ }
+ }
+
+ .back {
+ position: absolute;
+ top: -40px;
+ left: 0px;
+ height: 40px;
+ width: 60px;
+ appearance: none;
+ border: none;
+ padding: 0;
+ font-size: $font-40;
+ color: $black-alpha;
+ background: transparent;
+ cursor: pointer;
+ transition: transform .3s;
+ -webkit-backface-visibility: hidden;
+
+ &:hover {
+ transform: scale(1.1) translateX(-10px);
+ }
+ }
+ }
+}
+
+@media (min-width: map-get($grid-breakpoints, 'md')) {
+ .component-detail {
+ .content {
+ transform: rotate(2deg) skew(0deg, -6deg);
+
+ .date-container {
+ position: absolute;
+ top: 40px;
+ right: 40px;
+ }
+ }
+ }
+}
diff --git a/src/app/detail/detail.component.spec.ts b/src/app/detail/detail.component.spec.ts
new file mode 100644
index 0000000..149b9be
--- /dev/null
+++ b/src/app/detail/detail.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DetailComponent } from './detail.component';
+
+describe('DetailComponent', () => {
+ let component: DetailComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DetailComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/detail/detail.component.ts b/src/app/detail/detail.component.ts
new file mode 100644
index 0000000..00d9403
--- /dev/null
+++ b/src/app/detail/detail.component.ts
@@ -0,0 +1,184 @@
+import { Component, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'
+import { Router, NavigationEnd, NavigationStart, ActivatedRoute } from '@angular/router'
+import { DomSanitizer } from '@angular/platform-browser'
+import { Location } from '@angular/common'
+import { NgxImageGalleryComponent, GALLERY_IMAGE, GALLERY_CONF } from "ngx-image-gallery"
+import { ApisService } from '../services/apis.service'
+import { environment } from '../../environments/environment'
+
+@Component({
+ selector: 'app-detail',
+ templateUrl: './detail.component.html',
+ styleUrls: ['./detail.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+})
+export class DetailComponent implements OnInit {
+
+ public basePath = `${environment.BASE_PATH}`
+ public loaded = false
+ public init = false
+
+ @ViewChild(NgxImageGalleryComponent) ngxImageGallery: NgxImageGalleryComponent
+
+ public details: any = {}
+ public section: string = ''
+ public id: number = 0
+ private history: string[] = []
+
+ public conf: GALLERY_CONF = {
+ imageOffset: '0px',
+ showDeleteControl: false,
+ showImageTitle: false,
+ }
+
+ public galleryImages: GALLERY_IMAGE[] = []
+ public loadedImages: boolean[] = []
+
+ constructor(
+ private apisService: ApisService,
+ private router: Router,
+ private location: Location,
+ private activeRoute: ActivatedRoute,
+ private sanitizer: DomSanitizer
+ ) { }
+
+ ngOnInit(): void {
+ this.section = this.router.url.split('/')[2]
+ this.id = parseInt(this.router.url.split('/')[3])
+ this.showDetails(this.section, this.id)
+ }
+
+ ngAfterContentInit() {
+ this.init = true
+ }
+
+ showDetails(section, id, title = ''): void {
+ this.galleryImages = []
+ this.apisService.getDetails(section, id).toPromise().then((response) => {
+ if(this.history[this.history.length - 1] != `/detail/${section}/${id}`) {
+ this.history.push(`/detail/${section}/${id}/${title.toLowerCase().replace(/[^a-zA-Z0-9]/g, '_')}`)
+ }
+
+ const detail = response.item
+ detail.videos = detail.videos ? JSON.parse(detail.videos) : []
+ detail.videos.forEach((e) => {
+ e.code = e.url.split('/').pop()
+ switch(e.type) {
+ case 'youtube':
+ e.embed = this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${e.code}`)
+ break
+ case 'vimeo':
+ e.embed = this.sanitizer.bypassSecurityTrustResourceUrl(`https://player.vimeo.com/video/${e.code}`)
+ break
+ case 'embed':
+ e.embed = this.sanitizer.bypassSecurityTrustHtml(e.url)
+ break
+ }
+ })
+
+ detail.gallery = detail.gallery ? JSON.parse(detail.gallery) : []
+
+ detail.gallery.forEach((e) => {
+ if(!e.main) {
+ this.galleryImages.push({
+ url: `${this.basePath}${e.url}`,
+ altText: e.title,
+ title: e.title,
+ thumbnailUrl: `${this.basePath}${e.url}`
+ })
+ this.loadedImages.push(true)
+ }
+ })
+
+ this.loaded = !this.loadedImages.length
+ this.details = detail
+
+ },(error) => {
+ console.error('getPortfolio ERROR', error)
+ }).catch((e) => {
+ console.error('getPortfolio CATCH', e)
+ })
+ }
+
+ back(): void {
+ this.history.pop()
+ if(this.history.length > 0) {
+ const last = this.history[this.history.length - 1]
+ this.section = last.split('/')[2]
+ this.id = parseInt(last.split('/')[3])
+ this.showDetails(this.section, this.id)
+ this.location.back()
+ } else {
+ this.location.back()
+ }
+ }
+
+
+ onLoad(index): void {
+ this.loadedImages[index] = false
+ this.loaded = this.init && this.loadedImages.every( (val) => !val)
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ openGallery(index: number = 0) {
+ this.ngxImageGallery.open(index)
+ }
+
+ // close gallery
+ closeGallery() {
+ this.ngxImageGallery.close()
+ }
+
+ // set new active(visible) image in gallery
+ newImage(index: number = 0) {
+ this.ngxImageGallery.setActiveImage(index)
+ }
+
+ // next image in gallery
+ nextImage(index: number = 0) {
+ this.ngxImageGallery.next()
+ }
+
+ // prev image in gallery
+ prevImage(index: number = 0) {
+ this.ngxImageGallery.prev()
+ }
+
+ /**************************************************/
+
+ // EVENTS
+ // callback on gallery opened
+ galleryOpened(index) {
+ console.info('Gallery opened at index ', index)
+ }
+
+ // callback on gallery closed
+ galleryClosed() {
+ console.info('Gallery closed.')
+ }
+
+ // callback on gallery image clicked
+ galleryImageClicked(index) {
+ console.info('Gallery image clicked with index ', index)
+ }
+
+ // callback on gallery image changed
+ galleryImageChanged(index) {
+ console.info('Gallery image changed to index ', index)
+ }
+
+ // callback on user clicked delete button
+ deleteImage(index) {
+ console.info('Delete image at index ', index)
+ }
+}
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 3c43e57..dbba668 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -1,3 +1,21 @@
-