Browse Source

Merge branch 'feature/fine_tuning' into develop

hotfix/class_typo
Dslak 5 years ago
parent
commit
7e0b435879
  1. 27
      auth/identity.pem
  2. 5
      deploy.sh
  3. 2
      package.json
  4. 41
      src/apis/auth.php
  5. 80
      src/apis/exhibition.php
  6. 149
      src/apis/index.php
  7. 24
      src/apis/remove.php
  8. 35
      src/apis/upload.php
  9. 75
      src/apis/work.php
  10. 191
      src/app/admin/admin.component.html
  11. 210
      src/app/admin/admin.component.scss
  12. 12
      src/app/admin/admin.component.spec.ts
  13. 437
      src/app/admin/admin.component.ts
  14. 5
      src/app/app-layout/app-layout.component.html
  15. 6
      src/app/app-layout/app-layout.component.ts
  16. 5
      src/app/app-routing.module.ts
  17. 8
      src/app/app.module.ts
  18. 20
      src/app/detail/detail.component.html
  19. 18
      src/app/detail/detail.component.scss
  20. 62
      src/app/detail/detail.component.ts
  21. 5
      src/app/portfolio/portfolio.component.html
  22. 26
      src/app/portfolio/portfolio.component.scss
  23. 46
      src/app/portfolio/portfolio.component.ts
  24. 44
      src/app/services/apis.service.ts
  25. 16
      src/app/services/auth.service.spec.ts
  26. 33
      src/app/services/auth.service.ts
  27. 1
      src/app/workshops/workshops.component.html
  28. 0
      src/app/workshops/workshops.component.scss
  29. 15
      src/app/workshops/workshops.component.ts
  30. BIN
      src/assets/fonts/icomoon.eot
  31. 13
      src/assets/fonts/icomoon.svg
  32. BIN
      src/assets/fonts/icomoon.ttf
  33. BIN
      src/assets/fonts/icomoon.woff
  34. 2
      src/assets/fonts/selection.json
  35. 7
      src/assets/images/angle-down.svg
  36. BIN
      src/assets/images/loader.webp
  37. 90
      src/assets/scss/forms.scss
  38. 7
      src/assets/scss/global.scss
  39. 39
      src/assets/scss/icons.scss
  40. 1
      src/assets/scss/main.scss
  41. 9
      src/assets/scss/variables.scss
  42. 52
      src/config/config.ts
  43. 4
      src/environments/environment.prod.ts
  44. 4
      src/environments/environment.ts

27
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-----

5
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/

2
package.json

@ -6,6 +6,7 @@
"start": "ng serve",
"dev": "ng serve",
"build": "ng build",
"prod": "ng build --prod --configuration production",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
@ -20,6 +21,7 @@
"@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",
"ng-particles": "^2.1.11",
"ngx-image-gallery": "^2.0.5",

41
src/apis/auth.php

@ -0,0 +1,41 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$content->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);
?>

80
src/apis/exhibition.php

@ -0,0 +1,80 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->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);
?>

149
src/apis/index.php

@ -1,95 +1,98 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn=@mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$content->items = array();
$filter = array("portfolio", "installations", "entertainment", "performances", "workshops");
if(isset($_GET['query'])) {
switch($_GET['query']) {
case "portfolio":
case "installations":
case "entertainment":
case "performances":
case "workshops":
if($_GET['query'] == 'portfolio') {$filter = '';} else {$filter = "WHERE type='".$_GET['query']."'";}
$qe = mysqli_query($conn,"SELECT * FROM `works` $filter ORDER BY id 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->type = $re['type'];
$item->tags = $re['tags'];
$item->image = $re['image'];
array_push($content->items, $item);
$content->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']."'";}
$qe = mysqli_query($conn,"SELECT * FROM `works` $filter ORDER BY id 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->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)) {
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->date_from = $re['date_from'];
$item->date_to = $re['date_to'];
$item->content = $re['content'];
$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'];
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->videos = $re['videos'];
$item->gallery = $re['gallery'];
$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);
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;
}
$content->item = $item;
}
break;
break;
}
}
http_response_code(200);
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json');
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);
?>

24
src/apis/remove.php

@ -0,0 +1,24 @@
<?php
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->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);
?>

35
src/apis/upload.php

@ -0,0 +1,35 @@
<?php
$content = null;
if(isset($_POST['token']) && $_POST['token'] == base64_encode('admin:JohnHolmes'.date("Y-m-d"))) {
if(is_uploaded_file($_FILES['file']['tmp_name'])) {
$file = $_FILES['file']['tmp_name'];
$filename = date("YmdHis").".".end((explode(".", $_FILES["file"]["name"])));
$path = isset($_POST['path']) ? "/uploads/".$_POST['path'] : "/uploads/";
@move_uploaded_file($file, "..".$path."/".$filename);
http_response_code(200);
$content->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);
?>

75
src/apis/work.php

@ -0,0 +1,75 @@
<?php
@include 'conn.conn';
$GLOBALS['conn'];
$conn = @mysqli_connect($DATAhst,$DATAusr,$DATApwd,$DATAdtb)or die("CONNECTION ERROR");
$content = null;
$data = json_decode(file_get_contents("php://input"));
if(isset($data->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);
?>

191
src/app/admin/admin.component.html

@ -0,0 +1,191 @@
<div class="component-admin">
<div class="row no-gutters" *ngIf="!authCheck">
<div class="col-12 col-md-6 mx-auto">
<form class="login-form-container" (submit)="login()">
<div class="m-2">
<span class="login-label">Username</span>
<input type="text" class="input-text" name="userName" [(ngModel)]="userName">
</div>
<div class="m-2">
<span class="login-label">Password</span>
<input type="password" class="input-text" name="password" [(ngModel)]="password">
</div>
<div class="m-2 pt-4">
<button type="submit" class="button">Sign-in</button>
</div>
</form>
</div>
</div>
<div class="row no-gutters" *ngIf="authCheck">
<div class="col-12 col-md-3 col-lg-2">
<div class="menu">
<span class="section-title">Works</span>
<button class="action" [ngClass]="{'active': activeEditor == 'works-add'}" (click)="showEditor('works-add')">Add</button>
<button class="action" [ngClass]="{'active': activeEditor == 'works-modify'}" (click)="showEditor('works-modify')">Modify</button>
<button class="action" [ngClass]="{'active': activeEditor == 'works-delete'}" (click)="showEditor('works-delete')">Delete</button>
<span class="section-title">Exhibitions</span>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-add'}" (click)="showEditor('exhibitions-add')">Add</button>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-modify'}" (click)="showEditor('exhibitions-modify')">Modify</button>
<button class="action" [ngClass]="{'active': activeEditor == 'exhibitions-delete'}" (click)="showEditor('exhibitions-delete')">Delete</button>
</div>
</div>
<div class="col px-5 py-4">
<div class="edit-container">
<span class="title">{{sectionTitle}}</span>
<form class="form row" *ngIf="activeEditor == 'works-modify' || activeEditor == 'works-delete'">
<div class="col-12">
<select class="input-select" (change)="selectWork($event.target.value)">
<option value="">- Select work from list -</option>
<option value="{{work.id}}" *ngFor="let work of works">
{{work.type}} | {{work.title}}
</option>
</select>
</div>
</form>
<form class="form row" *ngIf="activeEditor == 'exhibitions-modify' || activeEditor == 'exhibitions-delete'">
<div class="col-12">
<select class="input-select" (change)="selectExhibition($event.target.value)">
<option value="">- Select exhibition from list -</option>
<option value="{{exhibition.id}}" *ngFor="let exhibition of exhibitions">
{{exhibition.date_from | date}} | {{exhibition.title}}
</option>
</select>
</div>
</form>
<form class="form row" (submit)="saveData()"
*ngIf="activeEditor == 'works-add' || (activeEditor == 'works-modify' && activeModify) ||
activeEditor == 'exhibitions-add' || (activeEditor == 'exhibitions-modify' && activeModify)">
<div [ngClass]="{'col-8': activeEditor == 'works-add' || activeEditor == 'works-modify',
'col-6': activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'}">
<span class="label">Title</span>
<input type="text" class="input-text" name="title" [(ngModel)]="title">
</div>
<div class="col-4" *ngIf="activeEditor == 'works-add' || activeEditor == 'works-modify'">
<span class="label">Type</span>
<select class="input-select" name="type" [(ngModel)]="type">
<option [value]="sec.section" *ngFor="let sec of workSections">{{sec.title}}</option>
</select>
</div>
<div class="col-3" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Date from</span>
<input type="date" class="input-text w-100" name="dateFrom" [(ngModel)]="dateFrom" (change)="dateTo = dateFrom">
</div>
<div class="col-3" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Date to</span>
<input type="date" class="input-text w-100" name="dateTo" [(ngModel)]="dateTo">
</div>
<div class="col-12">
<span class="label">Content</span>
<angular-editor [placeholder]="'Enter text here...'" [config]="editorConfig" name="content" [(ngModel)]="content"></angular-editor>
</div>
<div class="col-12">
<span class="label">Tags</span>
<input type="text" class="input-text" name="tags" [(ngModel)]="tags">
</div>
<div class="col-12">
<span class="label">Gallery</span>
<div class="gallery-container">
<label class="image-add" for="image-add">
<input type="file" id="image-add" (change)="onFileChanged($event)">
</label>
<div class="image-box" [ngClass]="{'main': image.main}" *ngFor="let image of selectedGallery">
<img class="image" [src]="basePath+image.url">
<button type="button" class="remove" (click)="galleryRemove(image.url)"><span class="icon-trash-2"></span></button>
<button type="button" class="set-main" (click)="gallerySetMain(image.url)" *ngIf="!image.main"><span class="icon-check"></span></button>
</div>
</div>
</div>
<div class="col-6" *ngIf="activeEditor == 'works-add' || activeEditor == 'works-modify'">
<span class="label">Exhibitions</span>
<select class="input-select" name="exhibitions" (change)="exhibitionAdd($event.target.value)">
<option value=""></option>
<option value="{{exhibition.id}}" *ngFor="let exhibition of exhibitions">
{{exhibition.date_from | date}} | {{exhibition.title}}
</option>
</select>
<span class="label font-12 pt-2">Selected exhibitions</span>
<span class="selected-exhibition" *ngFor="let se of selectedExhibitions" (click)="exhibitionRemove(se.id)">
{{se.date_from | date}} | {{se.title}}
</span>
</div>
<div class="col-6" *ngIf="activeEditor == 'exhibitions-add' || activeEditor == 'exhibitions-modify'">
<span class="label">Works</span>
<select class="input-select" name="works" (change)="workAdd($event.target.value)">
<option value=""></option>
<option value="{{work.id}}" *ngFor="let work of works">
{{work.type}} | {{work.title}}
</option>
</select>
<span class="label font-12 pt-2">Selected works</span>
<span class="selected-work" *ngFor="let sw of selectedWorks" (click)="workRemove(sw.id)">
{{sw.type}} | {{sw.title}}
</span>
</div>
<div class="col-6">
<span class="label">Video</span>
<div class="w-30 d-inline-block pr-2">
<select class="input-select" name="videoType" [(ngModel)]="videoType">
<option value="youtube">YouTube</option>
<option value="vimeo">Vimeo</option>
<option value="embed">Embed</option>
</select>
</div>
<div class="w-60 d-inline-block pr-2">
<input type="text" class="input-text" name="videoURL" [(ngModel)]="videoURL">
</div>
<div class="w-10 d-inline-block">
<span class="button button-transparent icon-plus-square px-0 w-100" (click)="videoAdd()"></span>
</div>
<span class="label font-12 pt-2">Selected Videos</span>
<span class="selected-video" *ngFor="let sv of selectedVideos" (click)="videoRemove(sv.url)">
{{sv.type}} | {{sv.url}}
</span>
</div>
<div class="col-12 pt-5">
<button class="button w-100" type="submit">Save</button>
</div>
</form>
<form class="form row" (submit)="deleteData(modifyId)"
*ngIf="(activeEditor == 'works-delete' || activeEditor == 'exhibitions-delete') && modifyId">
<div class="col-12">
<span class="label">Title</span>
<div class="preview-box" *ngIf="activeEditor == 'works-delete'">{{type}} | {{title}}</div>
<div class="preview-box" *ngIf="activeEditor == 'exhibitions-delete'">{{dateFrom}} | {{title}}</div>
</div>
<div class="col-12">
<span class="label">Content</span>
<div class="preview-box" [innerHTML]="content"></div>
</div>
<div class="col-12">
<span class="label">Gallery</span>
<div class="gallery-container">
<div class="image-box" [ngClass]="{'main': image.main}" *ngFor="let image of selectedGallery">
<img class="image" [src]="basePath+image.url">
<button type="button" class="remove" (click)="galleryRemove(image.url)"><span class="icon-trash-2"></span></button>
</div>
</div>
</div>
<div class="col-12 pt-5">
<button class="button w-100" type="submit">Delete</button>
</div>
</form>
</div>
</div>
</div>
</div>

210
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%;
}
}
}

12
src/app/workshops/workshops.component.spec.ts → src/app/admin/admin.component.spec.ts

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkshopsComponent } from './workshops.component';
import { AdminComponent } from './admin.component';
describe('WorkshopsComponent', () => {
let component: WorkshopsComponent;
let fixture: ComponentFixture<WorkshopsComponent>;
describe('AdminComponent', () => {
let component: AdminComponent;
let fixture: ComponentFixture<AdminComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WorkshopsComponent ]
declarations: [ AdminComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WorkshopsComponent);
fixture = TestBed.createComponent(AdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

437
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 = (<HTMLInputElement>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
}
}

5
src/app/app-layout/app-layout.component.html

@ -1,4 +1,5 @@
<app-header></app-header>
<app-header *ngIf="page != '/admin'"></app-header>
<router-outlet></router-outlet>
<Particles class="particles" [id]="id" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)" *ngIf="particlesEnabled"></Particles>
<Particles class="particles" *ngIf="particlesEnabled && page != '/admin'"
[id]="id" [options]="particlesOptions" (particlesLoaded)="particlesLoaded($event)"></Particles>

6
src/app/app-layout/app-layout.component.ts

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import type { Container } from 'tsparticles'
@Component({
@ -8,6 +9,7 @@ import type { Container } from 'tsparticles'
})
export class AppLayoutComponent implements OnInit {
public page: string = '/'
public particlesEnabled: boolean = false
public id: string = 'tsparticles'
@ -90,9 +92,11 @@ export class AppLayoutComponent implements OnInit {
detectRetina: true
}
constructor() { }
constructor(private router: Router) { }
ngOnInit(): void {
this.page = this.router.url
this.particlesEnabled = this.page != '/admin'
}

5
src/app/app-routing.module.ts

@ -4,6 +4,7 @@ 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'
const routes: Routes = [
{
@ -27,8 +28,8 @@ const routes: Routes = [
]
}
]
}
},
{ path: 'admin', component: AdminComponent }
]
}
]

8
src/app/app.module.ts

@ -3,6 +3,8 @@ 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';
@ -11,6 +13,7 @@ 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';
@NgModule({
declarations: [
@ -19,13 +22,16 @@ import { DetailComponent } from './detail/detail.component';
AppLayoutComponent,
AboutComponent,
PortfolioComponent,
DetailComponent
DetailComponent,
AdminComponent
],
imports: [
BrowserModule,
AppRoutingModule,
NgParticlesModule,
NgxImageGalleryModule,
FormsModule,
AngularEditorModule,
HttpClientModule
],
providers: [],

20
src/app/detail/detail.component.html

@ -5,14 +5,15 @@
<h2 class="title">{{details.title}}</h2>
<div class="row no-gutters gallery" *ngIf="details.gallery && details.gallery.length">
<div class="col-12" *ngFor="let image of details.gallery; let i = index"
[ngClass]="{'col-md-4': details.gallery.length < 4 && (details.gallery.length % 3) == 2,
'col-md-12': details.gallery.length < 4 && (details.gallery.length % 3) == 1,
'col-md-3': details.gallery.length >= 3}">
<div class="row no-gutters gallery" *ngIf="galleryImages.length">
<div class="col-12" *ngFor="let image of galleryImages; let i = index"
[ngClass]="{'col-md-4': galleryImages.length < 4 && (galleryImages.length % 3) == 2,
'col-md-12': galleryImages.length < 4 && (galleryImages.length % 3) == 1,
'col-md-3': galleryImages.length >= 3}">
<div class="gallery-container">
<span class="gallery-title">{{image.title}}</span>
<img class="image" [src]="image.url" (click)="openGallery(i)">
<img class="image" *ngIf="loadedImages[i]" src="/assets/images/loader.webp" alt="loading">
<img class="image" [hidden]="loadedImages[i]" (load)="onLoad(i)" [src]="image.url" (click)="openGallery(i)">
</div>
</div>
</div>
@ -33,11 +34,16 @@
'col-md-4': details.videos.length >= 3}">
<span class="video-title">{{video.title}}</span>
<div class="youtube" *ngIf="video.type == 'youtube'">
{{video.code}}
<iframe class="iframe" [src]="video.embed"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</div>
<div class="vimeo" *ngIf="video.type == 'vimeo'">
<iframe class="iframe" title="vimeo-player" [src]="video.embed" src="https://player.vimeo.com/video/104753208"
frameborder="0" allowfullscreen></iframe>
</div>
<div class="embed" *ngIf="video.type == 'embed'" [innerHTML]="video.embed"></div>
</div>
</div>

18
src/app/detail/detail.component.scss

@ -27,9 +27,6 @@
.date-container {
display: inline-flex;
position: absolute;
top: 40px;
right: 40px;
.date {
display: inline-flex;
@ -76,17 +73,22 @@
}
.videos {
padding-bottom: 40px;
.video-title {
font-size: $font-18;
font-weight: bold;
padding-bottom: 5px;
}
.youtube {
.youtube,
.vimeo,
.embed {
position: relative;
padding-bottom: 56.25%;
.iframe{
iframe,
.iframe {
position: absolute;
top: 0;
left: 0;
@ -139,6 +141,12 @@
.component-detail {
.content {
transform: rotate(2deg) skew(0deg, -6deg);
.date-container {
position: absolute;
top: 40px;
right: 40px;
}
}
}
}

62
src/app/detail/detail.component.ts

@ -1,17 +1,21 @@
import { Component, OnInit, ViewChild } from '@angular/core'
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']
styleUrls: ['./detail.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class DetailComponent implements OnInit {
public basePath = `${environment.BASE_PATH}`
@ViewChild(NgxImageGalleryComponent) ngxImageGallery: NgxImageGalleryComponent
public details: any = {}
@ -26,6 +30,7 @@ export class DetailComponent implements OnInit {
}
public galleryImages: GALLERY_IMAGE[] = []
public loadedImages: boolean[] = []
constructor(
private apisService: ApisService,
@ -42,6 +47,7 @@ export class DetailComponent implements OnInit {
}
showDetails(section, id): 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}`)
@ -51,17 +57,31 @@ export class DetailComponent implements OnInit {
detail.videos = detail.videos ? JSON.parse(detail.videos) : []
detail.videos.forEach((e) => {
e.code = e.url.split('/').pop()
e.embed = this.sanitizer.bypassSecurityTrustResourceUrl(`https://www.youtube.com/embed/${e.code}`)
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) => {
this.galleryImages.push({
url: e.url,
altText: e.title,
title: e.title,
thumbnailUrl: e.url
})
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.details = detail
@ -86,7 +106,9 @@ export class DetailComponent implements OnInit {
}
onLoad(index): void {
this.loadedImages[index] = false
}
@ -100,27 +122,27 @@ export class DetailComponent implements OnInit {
openGallery(index: number = 0) {
this.ngxImageGallery.open(index);
this.ngxImageGallery.open(index)
}
// close gallery
closeGallery() {
this.ngxImageGallery.close();
this.ngxImageGallery.close()
}
// set new active(visible) image in gallery
newImage(index: number = 0) {
this.ngxImageGallery.setActiveImage(index);
this.ngxImageGallery.setActiveImage(index)
}
// next image in gallery
nextImage(index: number = 0) {
this.ngxImageGallery.next();
this.ngxImageGallery.next()
}
// prev image in gallery
prevImage(index: number = 0) {
this.ngxImageGallery.prev();
this.ngxImageGallery.prev()
}
/**************************************************/
@ -128,26 +150,26 @@ export class DetailComponent implements OnInit {
// EVENTS
// callback on gallery opened
galleryOpened(index) {
console.info('Gallery opened at index ', index);
console.info('Gallery opened at index ', index)
}
// callback on gallery closed
galleryClosed() {
console.info('Gallery closed.');
console.info('Gallery closed.')
}
// callback on gallery image clicked
galleryImageClicked(index) {
console.info('Gallery image clicked with index ', index);
console.info('Gallery image clicked with index ', index)
}
// callback on gallery image changed
galleryImageChanged(index) {
console.info('Gallery image changed to index ', index);
console.info('Gallery image changed to index ', index)
}
// callback on user clicked delete button
deleteImage(index) {
console.info('Delete image at index ', index);
console.info('Delete image at index ', index)
}
}

5
src/app/portfolio/portfolio.component.html

@ -1,9 +1,10 @@
<div class="component-portfolio">
<div class="container">
<div class="row">
<div class="col-12 col-sm-6 mx-auto" [ngClass]="'col-md-' + ((item.id % 3)+3)" *ngFor="let item of portfolioItems">
<div class="col-12 col-sm-6 mx-auto" [ngClass]="'col-md-' + item.width" *ngFor="let item of portfolioItems">
<div class="box" [ngClass]="'skew-' + (item.id % 6)" (click)="showDetails(item.id)">
<img class="image" src="assets/{{item.image}}">
<img class="image" *ngIf="item.loading" src="/assets/images/loader.webp" 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>

26
src/app/portfolio/portfolio.component.scss

@ -6,7 +6,7 @@
.box {
position: relative;
display: flex;
//background: $black-alpha;
background: $black-alpha;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
@ -23,10 +23,10 @@
height: 100%;
width: 100%;
object-fit: cover;
transform: translate(-50%, -50%) scale(1.2);
opacity: .5;
filter: grayscale(100%) invert(100%);
transition: transform .4s, opacity .4s, filter .4s;
transform: translate(-50%, -50%);
opacity: .9;
filter: grayscale(100%) brightness(.4);
transition: opacity .4s, filter .4s;
-webkit-backface-visibility: hidden;
z-index: 0;
}
@ -36,8 +36,8 @@
margin: auto;
text-align: center;
transform: translate(0%, 0%);
color: $black;
transition: color .4s;
color: $yellow;
//transition: color .4s;
-webkit-backface-visibility: hidden;
z-index: 1;
@ -99,21 +99,19 @@
background: $black;
z-index: 50;
//animation-play-state: paused;
@each $angle in 0,1,2,3,4,5,6 {
&.skew-#{$angle} {
transform: scale(1.4) rotate(2deg) skew(#{3 - $angle}deg, #{3 - $angle}deg);
//transform: scale(1.4) rotate(2deg) skew(#{3 - $angle}deg, #{3 - $angle}deg);
transform: scale(1.4) rotate(0deg) skew(0deg, 0deg);
}
}
.image {
filter: grayscale(100%) invert(0%) brightness(.5);
opacity: .9;
transform: translate(-50%, -50%) scale(1.6);
filter: grayscale(0%) brightness(1);
opacity: .5;
}
.text {
color: $yellow;
//color: $yellow;
}
}
}

46
src/app/portfolio/portfolio.component.ts

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'
import { Router, NavigationEnd } from '@angular/router'
import { ApisService } from '../services/apis.service'
import { environment } from '../../environments/environment'
@Component({
selector: 'app-portfolio',
@ -9,6 +10,8 @@ import { ApisService } from '../services/apis.service'
})
export class PortfolioComponent implements OnInit {
public basePath = `${environment.BASE_PATH}`
public portfolioItems: any = []
public section: string = ''
@ -22,6 +25,45 @@ export class PortfolioComponent implements OnInit {
this.apisService.getPortfolio(this.section).toPromise().then((response) => {
this.portfolioItems = response.items
let cnt = 0
let width = 0
let tot = 0
this.portfolioItems.forEach((e) => {
e.loading = true
switch (cnt) {
case 0:
width = Math.floor(Math.random()*3)+3
tot = width
cnt++
break;
case 1:
width = Math.floor(Math.random()*3)+3
cnt++
if(tot + width > 9) {
width = 12 - tot
tot = 0
cnt = 0
} else {
tot = tot + width
}
break;
case 2:
width = 12 - tot
tot = 0
cnt = 0
break;
}
e.width = width
//((e.id % 5)+3)
//Math.floor((Math.random()*3)+1)+2
})
},(error) => {
console.error('getPortfolio ERROR', error)
}).catch((e) => {
@ -34,4 +76,8 @@ export class PortfolioComponent implements OnInit {
this.router.navigate([`/detail/${section}/${id}`])
}
onLoad(id): void {
this.portfolioItems.filter(item => item.id == id)[0].loading = false
}
}

44
src/app/services/apis.service.ts

@ -30,4 +30,48 @@ export class ApisService extends BaseService {
)
}
// ADMIN SERVICES
uploadImage(body): Observable<any> {
let urlApi = `${this.restApi}upload.php`
return this.http.post<any>(urlApi, body).pipe(
catchError(this.handleError)
)
}
removeImage(body): Observable<any> {
let urlApi = `${this.restApi}remove.php`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
saveWork(body): Observable<any> {
let urlApi = `${this.restApi}work.php?act=save`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
deleteWork(body): Observable<any> {
let urlApi = `${this.restApi}work.php?act=delete`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
saveExhibition(body): Observable<any> {
let urlApi = `${this.restApi}exhibition.php?act=save`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
deleteExhibition(body): Observable<any> {
let urlApi = `${this.restApi}exhibition.php?act=delete`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
}

16
src/app/services/auth.service.spec.ts

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

33
src/app/services/auth.service.ts

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core'
import { HttpClient, HttpHeaders, HttpParams, HttpRequest } from '@angular/common/http'
import { Observable, Subject, throwError } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { BaseService } from './base-service'
import { environment } from '../../environments/environment'
@Injectable({
providedIn: 'root'
})
export class AuthService extends BaseService {
private restApi = `${environment.API_URL}`
constructor(private http: HttpClient) {
super()
}
login(body): Observable<any> {
let urlApi = `${this.restApi}auth.php?act=login`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
authCheck(body): Observable<any> {
let urlApi = `${this.restApi}auth.php?act=check`
return this.http.post<any>(urlApi, JSON.stringify(body)).pipe(
catchError(this.handleError)
)
}
}

1
src/app/workshops/workshops.component.html

@ -1 +0,0 @@
<p>workshops works!</p>

0
src/app/workshops/workshops.component.scss

15
src/app/workshops/workshops.component.ts

@ -1,15 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-workshops',
templateUrl: './workshops.component.html',
styleUrls: ['./workshops.component.scss']
})
export class WorkshopsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

BIN
src/assets/fonts/icomoon.eot

Binary file not shown.

13
src/assets/fonts/icomoon.svg

@ -8,6 +8,19 @@
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="back" d="M424.633 593.293l-386.090-308.871h946.914v100h-661.836l163.48 130.785z" />
<glyph unicode="&#xe901;" glyph-name="arrow-left" d="M542.165 158.166l-225.835 225.835h494.336c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-494.336l225.835 225.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-298.667-298.667c-4.096-4.096-7.168-8.789-9.259-13.824-2.176-5.205-3.243-10.795-3.243-16.341 0-10.923 4.181-21.845 12.501-30.165l298.667-298.667c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331z" />
<glyph unicode="&#xe902;" glyph-name="chevron-down" d="M225.835 524.502l256-256c16.683-16.683 43.691-16.683 60.331 0l256 256c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-225.835-225.835-225.835 225.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" />
<glyph unicode="&#xe903;" glyph-name="delete" d="M896 725.334c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-512c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-535.296l-261.333 298.667 261.333 298.667zM896 810.667h-554.667c-12.8 0-24.235-5.632-32.128-14.549l-298.667-341.333c-14.208-16.213-13.909-40.192 0-56.192l298.667-341.333c8.448-9.643 20.224-14.549 32.128-14.592h554.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v512c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM481.835 524.502l97.835-97.835-97.835-97.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l97.835 97.835 97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835 97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-97.835-97.835-97.835 97.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331z" />
<glyph unicode="&#xe904;" glyph-name="alert-circle" d="M981.333 426.667c0 129.579-52.565 246.997-137.472 331.861s-202.283 137.472-331.861 137.472-246.997-52.565-331.861-137.472-137.472-202.283-137.472-331.861 52.565-246.997 137.472-331.861 202.283-137.472 331.861-137.472 246.997 52.565 331.861 137.472 137.472 202.283 137.472 331.861zM896 426.667c0-106.069-42.923-201.984-112.469-271.531s-165.461-112.469-271.531-112.469-201.984 42.923-271.531 112.469-112.469 165.461-112.469 271.531 42.923 201.984 112.469 271.531 165.461 112.469 271.531 112.469 201.984-42.923 271.531-112.469 112.469-165.461 112.469-271.531zM469.333 597.334v-170.667c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM512 213.334c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667-42.667-19.115-42.667-42.667 19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe905;" glyph-name="alert-triangle" d="M475.648 752.043c3.115 5.248 7.893 10.325 14.251 14.165 10.069 6.101 21.589 7.595 32.256 4.949s20.224-9.216 26.197-19.115l361.216-603.008c2.987-5.12 5.077-11.435 5.461-18.517-0.64-15.701-5.077-25.216-12.075-32.384-7.68-7.851-18.219-12.715-29.568-12.843l-722.645 0.043c-6.485 0.043-13.696 1.749-20.523 5.717-10.197 5.888-17.024 15.317-19.883 25.899s-1.621 22.144 3.925 31.787zM402.432 795.904l-361.387-603.307c-18.005-31.189-21.589-66.133-13.141-97.707s29.013-60.075 59.648-77.739c19.797-11.435 41.643-17.067 62.933-17.152h722.901c35.797 0.384 67.712 15.104 90.581 38.485s36.864 55.595 36.48 90.923c-0.256 22.869-6.528 44.544-17.323 62.891l-361.557 603.605c-18.432 30.421-47.36 50.389-79.104 58.155s-66.603 3.456-96.811-14.891c-18.304-11.093-33.067-26.24-43.179-43.264zM469.333 554.667v-170.667c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM512 170.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667-42.667-19.115-42.667-42.667 19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe906;" glyph-name="check" d="M823.168 712.832l-439.168-439.168-183.168 183.168c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l213.333-213.333c16.683-16.683 43.691-16.683 60.331 0l469.333 469.333c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0z" />
<glyph unicode="&#xe907;" glyph-name="corner-right-down" d="M170.667 725.334h298.667c35.371 0 67.285-14.293 90.496-37.504s37.504-55.125 37.504-90.496v-409.003l-140.501 140.501c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l213.333-213.333c3.925-3.925 8.619-7.083 13.824-9.259 10.453-4.309 22.229-4.309 32.683 0 5.035 2.091 9.728 5.163 13.824 9.259l213.333 213.333c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0l-140.501-140.501v409.003c0 58.88-23.936 112.299-62.464 150.869s-91.989 62.464-150.869 62.464h-298.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe908;" glyph-name="edit" d="M469.333 810.667h-298.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v298.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-298.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h298.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM759.168 862.166l-405.333-405.333c-5.205-5.163-9.259-11.947-11.221-19.84l-42.667-170.667c-1.664-6.4-1.792-13.568 0-20.693 5.717-22.869 28.885-36.779 51.755-31.061l170.667 42.667c7.125 1.749 14.080 5.504 19.84 11.221l405.333 405.333c25.984 25.984 38.997 60.16 38.997 94.165s-13.013 68.181-38.997 94.165-60.203 39.040-94.208 39.040-68.181-13.013-94.165-38.997zM819.499 801.835c9.344 9.344 21.504 13.995 33.835 13.995s24.491-4.651 33.835-13.995 13.995-21.504 13.995-33.835-4.651-24.491-13.995-33.835l-396.971-396.971-90.197-22.571 22.571 90.197z" />
<glyph unicode="&#xe909;" glyph-name="minus-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM341.333 384h341.333c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-341.333c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe90a;" glyph-name="plus-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM341.333 384h128v-128c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v128h128c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667h-128v128c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-128h-128c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667z" />
<glyph unicode="&#xe90b;" glyph-name="share" d="M128 426.667v-341.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h512c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v341.333c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-341.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-512c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v341.333c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM469.333 750.336v-451.669c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v451.669l97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-170.667 170.667c-3.925 3.925-8.619 7.083-13.824 9.259-10.453 4.309-22.229 4.309-32.683 0-5.035-2.091-9.728-5.163-13.824-9.259l-170.667-170.667c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0z" />
<glyph unicode="&#xe90c;" glyph-name="trash-2" d="M768 640v-554.667c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-426.667c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v554.667zM725.333 725.334v42.667c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504h-170.667c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-42.667h-170.667c-23.552 0-42.667-19.115-42.667-42.667s19.115-42.667 42.667-42.667h42.667v-554.667c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h426.667c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v554.667h42.667c23.552 0 42.667 19.115 42.667 42.667s-19.115 42.667-42.667 42.667zM384 725.334v42.667c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501h170.667c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-42.667zM384 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM554.667 469.334v-256c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v256c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667z" />
<glyph unicode="&#xe90d;" glyph-name="x-square" d="M213.333 853.334c-35.328 0-67.413-14.379-90.496-37.504s-37.504-55.168-37.504-90.496v-597.333c0-35.328 14.379-67.413 37.504-90.496s55.168-37.504 90.496-37.504h597.333c35.328 0 67.413 14.379 90.496 37.504s37.504 55.168 37.504 90.496v597.333c0 35.328-14.379 67.413-37.504 90.496s-55.168 37.504-90.496 37.504zM213.333 768h597.333c11.776 0 22.4-4.736 30.165-12.501s12.501-18.389 12.501-30.165v-597.333c0-11.776-4.736-22.4-12.501-30.165s-18.389-12.501-30.165-12.501h-597.333c-11.776 0-22.4 4.736-30.165 12.501s-12.501 18.389-12.501 30.165v597.333c0 11.776 4.736 22.4 12.501 30.165s18.389 12.501 30.165 12.501zM609.835 584.832l-97.835-97.835-97.835 97.835c-16.683 16.683-43.691 16.683-60.331 0s-16.683-43.691 0-60.331l97.835-97.835-97.835-97.835c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0l97.835 97.835 97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-97.835 97.835 97.835 97.835c16.683 16.683 16.683 43.691 0 60.331s-43.691 16.683-60.331 0z" />
<glyph unicode="&#xf003;" glyph-name="envelope-o" d="M950.857 91.428v438.857c-12-13.714-25.143-26.286-39.429-37.714-81.714-62.857-164-126.857-243.429-193.143-42.857-36-96-80-155.429-80h-1.143c-59.429 0-112.571 44-155.429 80-79.429 66.286-161.714 130.286-243.429 193.143-14.286 11.429-27.429 24-39.429 37.714v-438.857c0-9.714 8.571-18.286 18.286-18.286h841.143c9.714 0 18.286 8.571 18.286 18.286zM950.857 692c0 14.286 3.429 39.429-18.286 39.429h-841.143c-9.714 0-18.286-8.571-18.286-18.286 0-65.143 32.571-121.714 84-162.286 76.571-60 153.143-120.571 229.143-181.143 30.286-24.571 85.143-77.143 125.143-77.143h1.143c40 0 94.857 52.571 125.143 77.143 76 60.571 152.571 121.143 229.143 181.143 37.143 29.143 84 92.571 84 141.143zM1024 713.143v-621.714c0-50.286-41.143-91.429-91.429-91.429h-841.143c-50.286 0-91.429 41.143-91.429 91.429v621.714c0 50.286 41.143 91.429 91.429 91.429h841.143c50.286 0 91.429-41.143 91.429-91.429z" />
<glyph unicode="&#xf081;" glyph-name="twitter-square" horiz-adv-x="878" d="M731.429 602.286c-21.714-9.714-44.571-16-69.143-19.429 25.143 14.857 44 38.857 53.143 66.857-23.429-13.714-49.143-24-76.571-29.143-21.714 23.429-53.143 37.714-87.429 37.714-66.286 0-120-53.714-120-120 0-9.143 0.571-18.857 2.857-27.429-100 5.143-188.571 52.571-248 125.714-10.286-17.714-16.571-38.857-16.571-60.571 0-41.714 19.429-78.286 52-100-20 0.571-38.857 6.286-57.143 14.857v-1.143c0-58.286 44-106.857 98.857-117.714-10.286-2.857-18.286-4.571-29.143-4.571-7.429 0-14.857 1.143-22.286 2.286 15.429-47.429 59.429-82.286 112-83.429-41.143-32-92.571-51.429-149.143-51.429-9.714 0-19.429 0.571-28.571 1.714 53.143-33.714 116-53.714 184-53.714 220.571 0 341.714 182.857 341.714 341.714 0 5.143 0 10.286-0.571 15.429 23.429 16.571 44 37.714 60 62.286zM877.714 713.143v-548.571c0-90.857-73.714-164.571-164.571-164.571h-548.571c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571c90.857 0 164.571-73.714 164.571-164.571z" />
<glyph unicode="&#xf082;" glyph-name="facebook-square" horiz-adv-x="878" d="M713.143 877.714c90.857 0 164.571-73.714 164.571-164.571v-548.571c0-90.857-73.714-164.571-164.571-164.571h-107.429v340h113.714l17.143 132.571h-130.857v84.571c0 38.286 10.286 64 65.714 64l69.714 0.571v118.286c-12 1.714-53.714 5.143-101.714 5.143-101.143 0-170.857-61.714-170.857-174.857v-97.714h-114.286v-132.571h114.286v-340h-304c-90.857 0-164.571 73.714-164.571 164.571v548.571c0 90.857 73.714 164.571 164.571 164.571h548.571z" />

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 28 KiB

BIN
src/assets/fonts/icomoon.ttf

Binary file not shown.

BIN
src/assets/fonts/icomoon.woff

Binary file not shown.

2
src/assets/fonts/selection.json

File diff suppressed because one or more lines are too long

7
src/assets/images/angle-down.svg

@ -0,0 +1,7 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768">
<title></title>
<g id="icomoon-ignore">
</g>
<path d="M169.376 310.624l192 192c12.512 12.512 32.768 12.512 45.248 0l192-192c12.512-12.512 12.512-32.768 0-45.248s-32.768-12.512-45.248 0l-169.376 169.376-169.376-169.376c-12.512-12.512-32.768-12.512-45.248 0s-12.512 32.768 0 45.248z"></path>
</svg>

After

Width:  |  Height:  |  Size: 432 B

BIN
src/assets/images/loader.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

90
src/assets/scss/forms.scss

@ -0,0 +1,90 @@
input,
select,
button,
textarea {
border: none;
border-radius: 4px;
background: $white;
appearance: none;
font-family: $font-primary;
font-size: $font-20;
resize: none;
&::-ms-clear {
display: none;
}
&:focus {outline:none;}
&::-moz-focus-inner {border:0;}
}
input[type=text],
input[type=password],
input[type=file],
select {
color: $black;
padding: 10px 20px;
width: 100%;
text-align: left;
box-sizing: border-box;
&:focus::placeholder {
color: transparent;
}
}
.input-text {
padding: 10px 20px !important;
}
.input-file {
padding: 6px 20px !important;
}
.input-textarea,
.angular-editor {
border-radius: 4px;
background: $white;
padding: 10px;
width: 100%;
}
.input-select {
padding: 9px 20px !important;
background-image: url('/assets/images/angle-down.svg');
background-size: 28px;
background-position: right 10px top 10px;
background-repeat: no-repeat;
}
.button {
position: relative;
appearance: none;
color: $white;
border: none;
border-radius: 4px;
background: $black;
display: inline-block;
padding: 10px 20px 10px 20px !important;
text-align: center;
font-family: $font-20;
text-transform: uppercase;
font-weight: 500;
transition: opacity .3s;
white-space: nowrap;
outline: none;
cursor: pointer;
&:disabled {
opacity: 0.5;
}
&.button-white {
background: $white;
color: $black !important;
}
&.button-transparent {
background: $white-alpha2;
color: $black !important;
}
}

7
src/assets/scss/global.scss

@ -24,10 +24,13 @@ button {
}
}
.w-100 {
width: 100%;
@each $size in 10,20,30,40,50,60,70,80,90,100 {
.w-#{$size} {width: percentage($size/100) !important;}
}
.text-right {text-align: right !important;}
.text-center {text-align: center !important;}
.text-left {text-align: left !important;}
.particles {
position: fixed;

39
src/assets/scss/icons.scss

@ -25,6 +25,45 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-alert-circle:before {
content: "\e904";
}
.icon-alert-triangle:before {
content: "\e905";
}
.icon-arrow-left:before {
content: "\e901";
}
.icon-check:before {
content: "\e906";
}
.icon-chevron-down:before {
content: "\e902";
}
.icon-corner-right-down:before {
content: "\e907";
}
.icon-delete:before {
content: "\e903";
}
.icon-edit:before {
content: "\e908";
}
.icon-minus-square:before {
content: "\e909";
}
.icon-plus-square:before {
content: "\e90a";
}
.icon-share:before {
content: "\e90b";
}
.icon-trash-2:before {
content: "\e90c";
}
.icon-x-square:before {
content: "\e90d";
}
.icon-envelope-o:before {
content: "\f003";
}

1
src/assets/scss/main.scss

@ -1,6 +1,7 @@
@import "./variables";
@import "./fonts";
@import "./icons";
@import "./forms";
@import "./global";

9
src/assets/scss/variables.scss

@ -3,14 +3,14 @@ $grid-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
lg: 1024px,
xl: 1440px
);
$container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
lg: 990px,
xl: 1418px
);
@ -23,13 +23,16 @@ $breadcrumb-height: 60px;
// Colors
$white: #fff;
$black: #000;
$gray: #ccc;
$gray: #999;
$light-gray: #eee;
$dark-gray: #333;
$red: #d00;
$green: #0c0;
$yellow: #a2dc02;
$white-alpha: rgba(255, 255, 255, 0.8);
$white-alpha2: rgba(255, 255, 255, 0.4);
$black-alpha: rgba(0, 0, 0, 0.8);
$yellow-alpha: rgba(160, 220, 0, 0.8);

52
src/config/config.ts

@ -0,0 +1,52 @@
import { environment } from '../environments/environment'
import { AngularEditorConfig } from '@kolkov/angular-editor'
export const editorConfig: AngularEditorConfig = {
editable: true,
spellcheck: true,
height: '300px',
translate: 'no',
enableToolbar: true,
showToolbar: true,
placeholder: 'Enter text here...',
defaultParagraphSeparator: 'p',
defaultFontName: '',
defaultFontSize: '',
uploadUrl: `${environment.API_URL}upload.php`,
uploadWithCredentials: false,
sanitize: true,
toolbarPosition: 'top',
toolbarHiddenButtons: [
[
'undo',
'redo',
//'bold',
//'italic',
//'underline',
'strikeThrough',
'subscript',
'superscript',
//'justifyLeft',
//'justifyCenter',
//'justifyRight',
//'justifyFull',
'indent',
'outdent',
'insertUnorderedList',
'insertOrderedList',
'heading',
'fontName',
'fontSize',
'textColor',
'backgroundColor',
'customClasses',
//'link',
'unlink',
//'insertImage',
'insertVideo',
'insertHorizontalRule',
//'removeFormat',
//'toggleEditorMode'
]
]
}

4
src/environments/environment.prod.ts

@ -1,5 +1,5 @@
export const environment = {
production: true,
API_URL: `https://apis.dslak.it/`
API_URL: `https://www.dslak.it/apis/`,
BASE_PATH: `https://www.dslak.it`
}

4
src/environments/environment.ts

@ -1,5 +1,5 @@
export const environment = {
production: false,
API_URL: `http://dslakng.local/apis/`
API_URL: `http://dslakng.local/apis/`,
BASE_PATH: `http://dslakng.local`
}

Loading…
Cancel
Save