瀏覽代碼

Frontend: Gallery navigation with touch gestures. Better styles

Iñigo Valentin 10 月之前
父節點
當前提交
1c28813b70

+ 1 - 1
frontend/package.json

@@ -1,6 +1,6 @@
 {
   "name": "leather-frontend",
-  "version": "0.0.3",
+  "version": "0.0.4",
   "author": "Iñigo Valentin",
   "authorURL": "https://inigovalentin.com",
   "sourceSite": "Github",

+ 1 - 2
frontend/src/app/home/home.ts

@@ -27,12 +27,11 @@ export class Home implements OnInit {
     }
 
     ngOnInit(): void {
-        this.projectService.getProjects().subscribe(data => { this.projects = data; });
+        this.projectService.getProjects(1).subscribe(data => { this.projects = data; });
         this.profileService.getProfile().subscribe(data => { this.profile = data; });
         this.startInterval();
         this.translate.use("" + localStorage.getItem("language"));
         //this.translate.use
-        console.log("GETTING TITLE IN " + this.translate.getCurrentLang()  + " OF " + localStorage.getItem("language"));
         this.translate.get(_('SITE.TITLE')).subscribe((res: string) => {
             this.titleService.setTitle(res)
         });

+ 41 - 34
frontend/src/app/project/project.html

@@ -2,7 +2,7 @@
   <div id='details'>
     @if (project.images.length > 0){
       <img
-        (click)=galleryOpen(0); id='img-0' class='img img_main'
+        (click)=galleryOpen(1); id='img-1' class='img img_main'
         src='{{ this.apiURL }}{{ project.images[0].path }}' alt='{{ project.images[0].title }}'
         srcset="{{ utilService.generateSrcset(apiURL + project.images[0].path) }}"
         attr.data-description='{{ project.images[0].description }}'
@@ -13,30 +13,35 @@
   </div>
   <div id='images'>
     @for (image of project.images; track image.id) {
-      @if (image.video == 0){
-        <img
-          (click)='galleryOpen(image.id)' id='img-{{ image.id }}' class='img'
-          src='{{ this.apiURL }}{{ image.path }}' alt='{{ image.title }}'
-          srcset="{{ utilService.generateSrcset(apiURL + image.path) }}"
-          attr.data-description='{{ image.description }}'
-          data-video='false'
-        />
-      }
-      @else{
-        <video
-          muted (click)='galleryOpen(image.id)' class='img img_vid' id='img-{{ image.id }}'
-          src='{{ this.apiURL }}{{ image.path }}'
-          data-video='true' attr.data-description='{{ image.description }}'
-        >
-        </video>
-        <img (click)='galleryOpen(image.id)' class='video_play'
-          src='/img/icon/video.png' alt='{{ image.title }}'
-        />
+      @if (image.id > 1){
+        @if (image.video == 0){
+          <img
+            (click)='galleryOpen(image.id)' id='img-{{ image.id }}' class='img'
+            src='{{ this.apiURL }}{{ image.path }}' alt='{{ image.title }}'
+            srcset="{{ utilService.generateSrcset(apiURL + image.path) }}"
+            attr.data-description='{{ image.description }}' data-video='false'
+          />
+        }
+        @else{
+          <video
+            muted (click)='galleryOpen(image.id)' class='img img_vid' id='img-{{ image.id }}'
+            src='{{ this.apiURL }}{{ image.path }}' playsinline
+            data-video='true' attr.data-description='{{ image.description }}'
+          >
+          </video>
+          <img (click)='galleryOpen(image.id)' class='video_play' type="video/mp4"
+            src='/img/icon/video.png' alt='{{ image.title }}'
+          />
+        }
       }
     }
   </div>
   <div id='gallery-cover' onClick='galleryClose();'></div>
-    <div id='gallery'>
+    <div
+      id='gallery'
+      (touchstart)="gallerySwipe($event, 'start')" (touchend)="gallerySwipe($event, 'end')"
+    >
+      <div id='gallery-fade'></div>
       <h3>
         <span>{{ project.title }}</span>
         <span id='gallery-title'></span>
@@ -45,20 +50,22 @@
           (click)='galleryClose();' value='X'
         />
       </h3>
-      <div id='gallery-flex'>
-        <div id='gallery-flex-img-container'>
-          <img id='gallery-img'/>
-          <video controls autoplay id='gallery-video'></video>
+      <div id='gallery-content'>
+        <div id='gallery-flex'>
+          <div id='gallery-flex-img-container'>
+            <img id='gallery-img'/>
+            <video controls autoplay playsinline type="video/mp4" id='gallery-video'></video>
+          </div>
+          <p id='gallery-text'></p>
+        </div>
+        <div id='gallery-controls'>
+          <input
+            type='button' id='gallery-prev' class='gallery-control' (click)='galleryPrev();' value='<'
+          />
+          <input
+            type='button' id='gallery-next' class='gallery-control' (click)='galleryNext();' value='>'
+          />
         </div>
-        <p id='gallery-text'></p>
-      </div>
-      <div id='gallery-controls'>
-        <input
-          type='button' id='gallery-prev' class='gallery-control' (click)='galleryPrev();' value='<'
-        />
-        <input
-          type='button' id='gallery-next' class='gallery-control' (click)='galleryNext();' value='>'
-        />
       </div>
     </div>
   }

+ 27 - 6
frontend/src/app/project/project.scss

@@ -99,11 +99,24 @@ div#gallery{
         left: 2%;
         right: 2%;
     }
+    
+    div#gallery-fade{
+        background-color: variables.$background-off-white;
+        display: none;
+        display: block;
+        height: calc(100% - 4em);
+        width: 100%;
+        position: absolute;
+        z-index: 6;
+        opacity: 0.5;
+        transition: opacity 0.2s;
+    }
 
     h3{
         text-align: left;
         font-size: 140%;
         margin: 0.6em;
+        max-height: 4em;
         border-bottom: 0.05em solid variables.$primary-brown;
 
         input[type='button']{
@@ -113,11 +126,20 @@ div#gallery{
         }
     }
     
+    div#gallery-content{
+        position: absolute;
+        height: calc(100% - 4em);
+        width: 100%;
+    }
+    
     div#gallery-flex{
         display: flex;
-        max-height: calc(100% - 8em);
+        flex-direction: row;
+        height: calc(100% - 4em);
         
-        @media only screen and (max-width : 800px) {display: block;}
+        @media only screen and (max-width : 800px) {
+            flex-direction: column;
+        }
         
         div#gallery-flex-img-container{
             flex: 70%;
@@ -135,8 +157,6 @@ div#gallery{
                 margin: auto;
                 
                 @media only screen and (max-width : 800px) {
-                    display: block;
-                    max-height: calc(60% - 2em);
                     max-width: 95%;
                     margin: 0.2em auto;
                 }
@@ -145,7 +165,7 @@ div#gallery{
         
         p#gallery-text{
             flex: 25%;
-            margin: 2%;
+            margin: 2% 2% 2% auto;
             background-color: #ddd;
             padding: 0.7em;
             text-align: left;
@@ -154,7 +174,6 @@ div#gallery{
             overflow-y: scroll;
             
             @media only screen and (max-width : 800px) {
-                display: block;
                 margin: auto 2em;
             }
         }
@@ -167,12 +186,14 @@ div#gallery{
             position: absolute;
             bottom: 0;
             width: 100%;
+            @media only screen and (max-width : 800px){display: flex}
         }
         
         input[type='button']{
             display: inline-block;
             padding: 1em 1.5em;
             margin: 0.3em 1em;
+            @media only screen and (max-width : 800px){flex: 50%}
         }
     }
 }

+ 36 - 3
frontend/src/app/project/project.ts

@@ -81,10 +81,16 @@ export class Project {
         video.currentTime = 0;
     }
     
+    private delay(ms: number){ return new Promise(resolve => setTimeout(resolve, ms)); }
+    
     /**
      * Pupulates the data in the gallery.
      */
-    gallerySet(){
+    async gallerySet(){
+        var fade = <HTMLDivElement> document.getElementById('gallery-fade');
+        fade.style.display = 'block';
+        fade.style.opacity = '1';
+        await this.delay(200);
         let titleText = document.getElementById('img-' + this.curIndex)?.getAttribute("alt") || "";
         if (titleText != null && titleText.length > 0 && titleText != "" + this.project?.title)
             titleText = ': ' + titleText;
@@ -116,6 +122,9 @@ export class Project {
         text.innerHTML = description;
         if (description.length > 0) text.style.display = 'block';
         else text.style.display = 'none';
+        fade.style.opacity = '0';
+        await this.delay(200);
+        fade.style.display = 'none';
     }
 
     /**
@@ -123,7 +132,7 @@ export class Project {
      */
     galleryNext(){
         this.curIndex ++;
-        if (this.curIndex > this.maxIndex) this.curIndex = 0;
+        if (this.curIndex >= this.maxIndex) this.curIndex = 1;
         this.gallerySet();
     }
 
@@ -132,8 +141,32 @@ export class Project {
      */
     galleryPrev(){
         this.curIndex --;
-        if (this.curIndex < 0) this.curIndex = this.maxIndex;
+        if (this.curIndex < 1) this.curIndex = this.maxIndex;
         this.gallerySet();
     }
+    
+    private swipeCoord?: [number, number];
+    private swipeTime?: number;
+    
+    gallerySwipe(e: TouchEvent, when: string): void {
+        console.log("SWIPE");
+        const coord: [number, number] = [e.changedTouches[0].pageX, e.changedTouches[0].pageY];
+        const time = new Date().getTime();
+        if (when === 'start') {
+            this.swipeCoord = coord;
+            this.swipeTime = time;
+        }
+        else if (this.swipeCoord && this.swipeTime && when === 'end') {
+            const direction = [coord[0] - this.swipeCoord[0], coord[1] - this.swipeCoord[1]];
+            const duration = time - this.swipeTime;
+            if (
+              duration < 1000
+              && Math.abs(direction[0]) > 30 && Math.abs(direction[0]) > Math.abs(direction[1] * 3)
+            ){ 
+                if (direction[0] < 0) this.galleryNext();
+                else this.galleryPrev();
+            }
+        }
+    }
 }
 

+ 7 - 2
frontend/src/app/service/project-service.ts

@@ -11,8 +11,13 @@ export class ProjectService {
   private apiUrl = environment.apiUrl + '/projects';
   constructor(private http: HttpClient) { }
 
-  getProjects(): Observable<ProjectModel[]> {
-    return this.http.get<ProjectModel[]>(this.apiUrl + "?lang=" + localStorage.getItem('language'));
+  getProjects(images: any): Observable<ProjectModel[]> {
+    var paramImages: string = "";
+    if (images == true || parseInt(images) >= 0)
+        paramImages = "&images=" + images;
+    return this.http.get<ProjectModel[]>(
+      this.apiUrl + "?lang=" + localStorage.getItem('language') + paramImages
+    );
   }
 
   getProject(id: string): Observable<ProjectModel> {