From 176407a613f61616c5d845dfdeeba8d55027a1fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com>
Date: Wed, 28 Oct 2020 17:24:51 +0100
Subject: [PATCH] MOBILE-3565 core: Add empty box and icon properties

---
 src/app/components/components.module.ts       |  3 +
 .../components/empty-box/core-empty-box.html  |  9 +++
 src/app/components/empty-box/empty-box.scss   | 70 +++++++++++++++++++
 src/app/components/empty-box/empty-box.ts     | 43 ++++++++++++
 src/app/components/icon/icon.scss             | 28 --------
 src/app/components/icon/icon.ts               |  9 +--
 src/app/core/courses/pages/home/home.html     |  6 +-
 src/app/core/settings/pages/about/about.html  |  9 ++-
 src/app/directives/fa-icon.ts                 |  2 -
 src/theme/app.scss                            | 32 +++++++++
 10 files changed, 170 insertions(+), 41 deletions(-)
 create mode 100644 src/app/components/empty-box/core-empty-box.html
 create mode 100644 src/app/components/empty-box/empty-box.scss
 create mode 100644 src/app/components/empty-box/empty-box.ts

diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts
index afc95a694..b16efbbcb 100644
--- a/src/app/components/components.module.ts
+++ b/src/app/components/components.module.ts
@@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
 import { CoreIconComponent } from './icon/icon';
 import { CoreLoadingComponent } from './loading/loading';
 import { CoreShowPasswordComponent } from './show-password/show-password';
+import { CoreEmptyBoxComponent } from './empty-box/empty-box';
 import { CoreDirectivesModule } from '@app/directives/directives.module';
 import { CorePipesModule } from '@app/pipes/pipes.module';
 
@@ -28,6 +29,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
         CoreIconComponent,
         CoreLoadingComponent,
         CoreShowPasswordComponent,
+        CoreEmptyBoxComponent,
     ],
     imports: [
         CommonModule,
@@ -40,6 +42,7 @@ import { CorePipesModule } from '@app/pipes/pipes.module';
         CoreIconComponent,
         CoreLoadingComponent,
         CoreShowPasswordComponent,
+        CoreEmptyBoxComponent,
     ],
 })
 export class CoreComponentsModule {}
diff --git a/src/app/components/empty-box/core-empty-box.html b/src/app/components/empty-box/core-empty-box.html
new file mode 100644
index 000000000..b15958be0
--- /dev/null
+++ b/src/app/components/empty-box/core-empty-box.html
@@ -0,0 +1,9 @@
+<div class="core-empty-box ion-padding" [class.core-empty-box-inline]="(!image && !icon) || inline">
+    <div class="core-empty-box-content">
+        <img *ngIf="image && !icon" [src]="image" role="presentation">
+        <ion-icon *ngIf="icon" [name]="icon" [class.icon-flip-rtl]="flipIconRtl">
+        </ion-icon>
+        <p *ngIf="message" [class.ion-padding-top]="image || icon">{{ message }}</p>
+        <ng-content></ng-content>
+    </div>
+</div>
diff --git a/src/app/components/empty-box/empty-box.scss b/src/app/components/empty-box/empty-box.scss
new file mode 100644
index 000000000..9fea08738
--- /dev/null
+++ b/src/app/components/empty-box/empty-box.scss
@@ -0,0 +1,70 @@
+:host {
+    .core-empty-box {
+        position: absolute;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        left: 0;
+        display: table;
+        height: 100%;
+        width: 100%;
+        margin: 0;
+        clear: both;
+        pointer-events: none;
+
+        .core-empty-box-content {
+            margin: 0;
+            display: table-cell;
+            text-align: center;
+            vertical-align: middle;
+            pointer-events: auto;
+        }
+
+        &.core-empty-box-inline {
+            position: relative;
+            z-index: initial;
+            top: initial;
+            right: initial;
+            bottom: 0;
+            left: initial;
+            height: auto;
+        }
+
+        ion-icon {
+            font-size: 120px;
+        }
+        img {
+            height: 125px;
+            width: 145px;
+            margin: 0 auto;
+        }
+        p {
+            font-size: 120%;
+        }
+    }
+
+    &.core-empty-box-clickable .core-empty-box {
+        z-index: 0;
+    }
+
+    @media (max-width: 350px) {
+        .core-empty-box {
+            position: relative;
+            height: auto;
+            margin-top: 50px;
+
+            ion-icon {
+                font-size: 100px;
+            }
+            img {
+                height: 104px;
+                width: 121px;
+            }
+        }
+    }
+}
+
+:host-context(core-block-course-blocks) .core-empty-box {
+    position: relative;
+}
+
diff --git a/src/app/components/empty-box/empty-box.ts b/src/app/components/empty-box/empty-box.ts
new file mode 100644
index 000000000..f2d5ef172
--- /dev/null
+++ b/src/app/components/empty-box/empty-box.ts
@@ -0,0 +1,43 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, Input } from '@angular/core';
+
+/**
+ * Component to show an empty box message. It will show an optional icon or image and a text centered on page.
+ *
+ * Use class="core-empty-box-clickable" if you want to add some clickable elements to the box.
+ *
+ * Usage:
+ * <core-empty-box *ngIf="empty" icon="bell" [message]="'core.emptymessage' | translate"></core-empty-box>
+ */
+@Component({
+    selector: 'core-empty-box',
+    templateUrl: 'core-empty-box.html',
+    styleUrls: ['empty-box.scss'],
+})
+export class CoreEmptyBoxComponent {
+
+    @Input() message = ''; // Message to display.
+    @Input() icon?: string; // Name of the icon to use.
+    @Input() image?: string; // Image source. If an icon is provided, image won't be used.
+
+    /**
+     * If this has to be shown inline instead of occupying whole page.
+     * If image or icon is not supplied, it's true by default.
+     */
+    @Input() inline?: boolean;
+    @Input() flipIconRtl?: boolean; // Whether to flip the icon in RTL. Defaults to false.
+
+}
diff --git a/src/app/components/icon/icon.scss b/src/app/components/icon/icon.scss
index 99a0a14ee..93c842fce 100644
--- a/src/app/components/icon/icon.scss
+++ b/src/app/components/icon/icon.scss
@@ -123,31 +123,3 @@
         }
     }
 }
-
-// TODO ionic 5
-:host-context([dir=rtl]) ion-icon {
-    &.core-icon-dir-flip,
-    &.fa-caret-right,
-    &.ion-md-send, &.ion-ios-send {
-        -webkit-transform: scale(-1, 1);
-        transform: scale(-1, 1);
-    }
-}
-
-:host {
-    &.icon-slash {
-        &::after {
-            content: " ";
-            position: absolute;
-            top: 0;
-            bottom: 0;
-            left: 0;
-            right: 0;
-            background-color: var(--ion-color-danger);
-            -webkit-mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
-            mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
-            -webkit-transform: scale(-1, 1);
-            transform: scale(-1, 1);
-        }
-    }
-}
diff --git a/src/app/components/icon/icon.ts b/src/app/components/icon/icon.ts
index c9a36fd21..4d181a0a5 100644
--- a/src/app/components/icon/icon.ts
+++ b/src/app/components/icon/icon.ts
@@ -20,6 +20,7 @@ import { Component, Input, OnChanges, ElementRef, SimpleChange } from '@angular/
  * the component will detect it.
  *
  * Check available icons at https://fontawesome.com/icons?d=gallery&m=free
+ *
  * @deprecated since 3.9.3. Please use <ion-icon name="fa-icon"> instead.
  */
 @Component({
@@ -81,9 +82,9 @@ export class CoreIconComponent implements OnChanges {
         }
 
         if (this.isTrueProperty(this.flipRtl)) {
-            iconElement.classList.add('core-icon-dir-flip');
+            iconElement.classList.add('icon-flip-rtl');
         } else {
-            iconElement.classList.remove('core-icon-dir-flip');
+            iconElement.classList.remove('icon-flip-rtl');
         }
 
         if (this.isTrueProperty(this.fixedWidth)) {
@@ -96,10 +97,10 @@ export class CoreIconComponent implements OnChanges {
     /**
      * Check if the value is true or on.
      *
-     * @param val value to be checked.
+     * @param val Value to be checked.
      * @return If has a value equivalent to true.
      */
-    isTrueProperty(val: any): boolean {
+    isTrueProperty(val: unknown): boolean {
         if (typeof val === 'string') {
             val = val.toLowerCase().trim();
 
diff --git a/src/app/core/courses/pages/home/home.html b/src/app/core/courses/pages/home/home.html
index 79b79100d..2b428a553 100644
--- a/src/app/core/courses/pages/home/home.html
+++ b/src/app/core/courses/pages/home/home.html
@@ -16,5 +16,7 @@
 </ion-header>
 <ion-content>
     <!-- @todo -->
-    Home page.
-</ion-content>
\ No newline at end of file
+    <core-empty-box icon="fa-home" [message]="'core.courses.nocourses' | translate">
+        <div>Home page</div>
+    </core-empty-box>
+</ion-content>
diff --git a/src/app/core/settings/pages/about/about.html b/src/app/core/settings/pages/about/about.html
index c0d25bb80..8a47fbf42 100644
--- a/src/app/core/settings/pages/about/about.html
+++ b/src/app/core/settings/pages/about/about.html
@@ -1,4 +1,3 @@
-
 <ion-header>
     <ion-toolbar>
         <ion-buttons slot="start">
@@ -11,18 +10,18 @@
 </ion-header>
 
 <ion-content>
-    <ion-item text-wrap>
+    <ion-item class="ion-text-wrap">
         <ion-label><h2>{{ appName }} {{ versionName }}</h2></ion-label>
     </ion-item>
-    <ion-item text-wrap (click)="openPage('licenses')" detail>
+    <ion-item button class="ion-text-wrap" (click)="openPage('licenses')" detail>
         <ion-icon name="far-copyright" slot="start"></ion-icon>
         <ion-label>{{ 'core.settings.opensourcelicenses' | translate }}</ion-label>
     </ion-item>
-    <ion-item text-wrap *ngIf="privacyPolicy" [href]="privacyPolicy" core-link auto-login="no" detail>
+    <ion-item button class="ion-text-wrap" *ngIf="privacyPolicy" [href]="privacyPolicy" core-link auto-login="no" detail>
         <ion-icon name="fa-user-shield" slot="start"></ion-icon>
         <ion-label>{{ 'core.settings.privacypolicy' | translate }}</ion-label>
     </ion-item>
-    <ion-item text-wrap (click)="openPage('deviceinfo')" detail>
+    <ion-item button class="ion-text-wrap" (click)="openPage('deviceinfo')" detail>
         <ion-icon name="fa-mobile" slot="start"></ion-icon>
         <ion-label>{{ 'core.settings.deviceinfo' | translate }}</ion-label>
     </ion-item>
diff --git a/src/app/directives/fa-icon.ts b/src/app/directives/fa-icon.ts
index c68c7bdf5..f2f60c509 100644
--- a/src/app/directives/fa-icon.ts
+++ b/src/app/directives/fa-icon.ts
@@ -32,8 +32,6 @@ export class CoreFaIconDirective implements OnChanges {
 
     @Input() name = '';
 
-    // TODO: Support slash, RTL and fixed width.
-
     protected element: HTMLElement;
 
     protected logger: CoreLogger;
diff --git a/src/theme/app.scss b/src/theme/app.scss
index 033a25fa9..8808db1b7 100644
--- a/src/theme/app.scss
+++ b/src/theme/app.scss
@@ -4,3 +4,35 @@ ion-toolbar ion-back-button,
 ion-toolbar .in-toolbar.button-clear {
     --color: var(--ion-color-primary-contrast);
 }
+
+// Ion icon styles.
+ion-icon {
+    &.icon-slash::after,
+    &.icon-backslash::after {
+        content: " ";
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: var(--ion-color-danger);
+        -webkit-mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
+        mask: url("/assets/fonts/font-awesome/solid/slash.svg") no-repeat 50% 50%;
+    }
+
+    &.icon-slash::after {
+        -webkit-transform: scale(-1, 1);
+        transform: scale(-1, 1);
+    }
+
+    &.fa-fw {
+        text-align: center;
+        width: 1.25em;
+    }
+}
+
+[dir=rtl] ion-icon.icon-flip-rtl {
+    -webkit-transform: scale(-1, 1);
+    transform: scale(-1, 1);
+}
+