@ -12,10 +12,11 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, ViewChild, AfterViewInit } from '@angular/core';
 | 
			
		||||
import { Platform, Nav } from 'ionic-angular';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { Platform } from 'ionic-angular';
 | 
			
		||||
import { StatusBar } from '@ionic-native/status-bar';
 | 
			
		||||
import { SplashScreen } from '@ionic-native/splash-screen';
 | 
			
		||||
import { CoreAppProvider } from '../providers/app';
 | 
			
		||||
import { CoreEventsProvider } from '../providers/events';
 | 
			
		||||
import { CoreLoggerProvider } from '../providers/logger';
 | 
			
		||||
import { CoreLoginHelperProvider } from '../core/login/providers/helper';
 | 
			
		||||
@ -23,8 +24,7 @@ import { CoreLoginHelperProvider } from '../core/login/providers/helper';
 | 
			
		||||
@Component({
 | 
			
		||||
    templateUrl: 'app.html'
 | 
			
		||||
})
 | 
			
		||||
export class MyApp implements AfterViewInit {
 | 
			
		||||
    @ViewChild(Nav) navCtrl;
 | 
			
		||||
export class MoodleMobileApp implements OnInit {
 | 
			
		||||
    // Use the page name (string) because the page is lazy loaded (Ionic feature). That way we can load pages without
 | 
			
		||||
    // having to import them. The downside is that each page needs to implement a ngModule.
 | 
			
		||||
    rootPage:any = 'CoreLoginInitPage';
 | 
			
		||||
@ -32,7 +32,8 @@ export class MyApp implements AfterViewInit {
 | 
			
		||||
    protected lastUrls = {};
 | 
			
		||||
 | 
			
		||||
    constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, logger: CoreLoggerProvider,
 | 
			
		||||
            private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider) {
 | 
			
		||||
            private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider,
 | 
			
		||||
            private appProvider: CoreAppProvider) {
 | 
			
		||||
        this.logger = logger.getInstance('AppComponent');
 | 
			
		||||
 | 
			
		||||
        platform.ready().then(() => {
 | 
			
		||||
@ -45,14 +46,12 @@ export class MyApp implements AfterViewInit {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * View has been initialized.
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngAfterViewInit() {
 | 
			
		||||
        this.loginHelper.setNavCtrl(this.navCtrl);
 | 
			
		||||
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        // Go to sites page when user is logged out.
 | 
			
		||||
        this.eventsProvider.on(CoreEventsProvider.LOGOUT, () => {
 | 
			
		||||
            this.navCtrl.setRoot('CoreLoginSitesPage');
 | 
			
		||||
            this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Listen for session expired events.
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,25 @@
 | 
			
		||||
  color: color($colors, primary, base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.col[align-self-stretch] .card-ios {
 | 
			
		||||
  height: calc(100% - #{($card-ios-margin-end + $card-ios-margin-start)});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Top tabs
 | 
			
		||||
// -------------------------
 | 
			
		||||
.ios .core-top-tabbar {
 | 
			
		||||
  -webkit-box-pack: center;
 | 
			
		||||
  -webkit-justify-content: center;
 | 
			
		||||
  -ms-flex-pack: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  > a {
 | 
			
		||||
    font-size: 1.6rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Highlights inside the input element.
 | 
			
		||||
@if ($mm-text-input-ios-show-highlight) {
 | 
			
		||||
@if ($core-text-input-ios-show-highlight) {
 | 
			
		||||
  .card-ios, .list-ios {
 | 
			
		||||
    // In order to get a 2px border we need to add an inset
 | 
			
		||||
    // box-shadow 1px (this is to avoid the div resizing)
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,12 @@
 | 
			
		||||
  color: color($colors, primary, base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.col[align-self-stretch] .card-md {
 | 
			
		||||
  height: calc(100% - #{($card-md-margin-end + $card-md-margin-start)});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Highlights inside the input element.
 | 
			
		||||
@if ($mm-text-input-md-show-highlight) {
 | 
			
		||||
@if ($core-text-input-md-show-highlight) {
 | 
			
		||||
  .card-md, .list-md {
 | 
			
		||||
    // In order to get a 2px border we need to add an inset
 | 
			
		||||
    // box-shadow 1px (this is to avoid the div resizing)
 | 
			
		||||
 | 
			
		||||
@ -18,20 +18,15 @@ import { IonicApp, IonicModule, Platform } from 'ionic-angular';
 | 
			
		||||
import { HttpModule } from '@angular/http';
 | 
			
		||||
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
 | 
			
		||||
 | 
			
		||||
import { SplashScreen } from '@ionic-native/splash-screen';
 | 
			
		||||
import { StatusBar } from '@ionic-native/status-bar';
 | 
			
		||||
import { SQLite } from '@ionic-native/sqlite';
 | 
			
		||||
import { Keyboard } from '@ionic-native/keyboard';
 | 
			
		||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
 | 
			
		||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
 | 
			
		||||
 | 
			
		||||
import { MyApp } from './app.component';
 | 
			
		||||
import { MoodleMobileApp } from './app.component';
 | 
			
		||||
import { CoreInterceptor } from '../classes/interceptor';
 | 
			
		||||
import { CoreLoggerProvider } from '../providers/logger';
 | 
			
		||||
import { CoreDbProvider } from '../providers/db';
 | 
			
		||||
import { CoreAppProvider } from '../providers/app';
 | 
			
		||||
import { CoreConfigProvider } from '../providers/config';
 | 
			
		||||
import { CoreEmulatorModule } from '../core/emulator/emulator.module';
 | 
			
		||||
import { CoreLangProvider } from '../providers/lang';
 | 
			
		||||
import { CoreTextUtilsProvider } from '../providers/utils/text';
 | 
			
		||||
import { CoreDomUtilsProvider } from '../providers/utils/dom';
 | 
			
		||||
@ -53,7 +48,12 @@ import { CoreFilepoolProvider } from '../providers/filepool';
 | 
			
		||||
import { CoreUpdateManagerProvider } from '../providers/update-manager';
 | 
			
		||||
import { CorePluginFileDelegate } from '../providers/plugin-file-delegate';
 | 
			
		||||
 | 
			
		||||
import { CoreComponentsModule } from '../components/components.module';
 | 
			
		||||
import { CoreEmulatorModule } from '../core/emulator/emulator.module';
 | 
			
		||||
import { CoreLoginModule } from '../core/login/login.module';
 | 
			
		||||
import { CoreMainMenuModule } from '../core/mainmenu/mainmenu.module';
 | 
			
		||||
import { CoreCoursesModule } from '../core/courses/courses.module';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// For translate loader. AoT requires an exported function for factories.
 | 
			
		||||
export function createTranslateLoader(http: HttpClient) {
 | 
			
		||||
@ -62,13 +62,13 @@ export function createTranslateLoader(http: HttpClient) {
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        MyApp
 | 
			
		||||
        MoodleMobileApp
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        BrowserModule,
 | 
			
		||||
        HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
 | 
			
		||||
        HttpModule,
 | 
			
		||||
        IonicModule.forRoot(MyApp, {
 | 
			
		||||
        IonicModule.forRoot(MoodleMobileApp, {
 | 
			
		||||
            pageTransition: 'ios-transition'
 | 
			
		||||
        }),
 | 
			
		||||
        TranslateModule.forRoot({
 | 
			
		||||
@ -79,11 +79,14 @@ export function createTranslateLoader(http: HttpClient) {
 | 
			
		||||
            }
 | 
			
		||||
        }),
 | 
			
		||||
        CoreEmulatorModule,
 | 
			
		||||
        CoreLoginModule
 | 
			
		||||
        CoreLoginModule,
 | 
			
		||||
        CoreMainMenuModule,
 | 
			
		||||
        CoreCoursesModule,
 | 
			
		||||
        CoreComponentsModule
 | 
			
		||||
    ],
 | 
			
		||||
    bootstrap: [IonicApp],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        MyApp
 | 
			
		||||
        MoodleMobileApp
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
@ -91,10 +94,6 @@ export function createTranslateLoader(http: HttpClient) {
 | 
			
		||||
            useClass: CoreInterceptor,
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        StatusBar,
 | 
			
		||||
        SplashScreen,
 | 
			
		||||
        SQLite,
 | 
			
		||||
        Keyboard,
 | 
			
		||||
        CoreLoggerProvider,
 | 
			
		||||
        CoreDbProvider,
 | 
			
		||||
        CoreAppProvider,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										169
									
								
								src/app/app.scss
									
									
									
									
									
								
							
							
						
						@ -23,10 +23,16 @@
 | 
			
		||||
.text-right          { text-align: right; }
 | 
			
		||||
.text-center         { text-align: center; }
 | 
			
		||||
.text-justify        { text-align: justify; }
 | 
			
		||||
 | 
			
		||||
.clearfix {
 | 
			
		||||
  &:after {
 | 
			
		||||
    content: "";
 | 
			
		||||
    display: table;
 | 
			
		||||
    clear: both;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (min-width: 430px) {
 | 
			
		||||
  .mm-center-view .scroll-content {
 | 
			
		||||
  .core-center-view .scroll-content {
 | 
			
		||||
    display: flex!important;
 | 
			
		||||
    align-content: center !important;
 | 
			
		||||
    align-items: center !important;
 | 
			
		||||
@ -37,14 +43,27 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (max-width: 768px) {
 | 
			
		||||
  .hidden-phone {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media only screen and (min-width: 769px) {
 | 
			
		||||
  .hidden-tablet {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// Define an alternative way to set a heading in an item without using a heading tag.
 | 
			
		||||
// This is done for accessibility reasons when a heading is semantically incorrect.
 | 
			
		||||
.item .item-heading {
 | 
			
		||||
  @extend h6;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mm-oauth-icon, .item.mm-oauth-icon, .list .item.mm-oauth-icon {
 | 
			
		||||
.core-oauth-icon, .item.core-oauth-icon, .list .item.core-oauth-icon {
 | 
			
		||||
  min-height: 32px;
 | 
			
		||||
  img, .label {
 | 
			
		||||
    max-height: 32px;
 | 
			
		||||
@ -59,7 +78,7 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mm-bold, .mm-bold .label {
 | 
			
		||||
.core-bold, .core-bold .label {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -113,3 +132,145 @@ ion-avatar ion-img, ion-avatar img {
 | 
			
		||||
  font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** Format Text */
 | 
			
		||||
core-format-text[maxHeight], *[core-format-text][maxHeight] {
 | 
			
		||||
  display: block;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  /* Force display inline */
 | 
			
		||||
  &.inline {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: auto;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // This is to allow clicks in radio/checkbox content.
 | 
			
		||||
  &.core-text-formatted {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
 | 
			
		||||
    .core-show-more {
 | 
			
		||||
      display: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(.core-shortened) {
 | 
			
		||||
      max-height: none !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.core-shortened {
 | 
			
		||||
      color: $gray-darker;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      min-height: 50px;
 | 
			
		||||
 | 
			
		||||
      .core-show-more {
 | 
			
		||||
        color: color($colors, dark);
 | 
			
		||||
        text-align: right;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        display: block;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        z-index: 1001;
 | 
			
		||||
        background-color: $white;
 | 
			
		||||
        padding-left: 10px;
 | 
			
		||||
 | 
			
		||||
        /* @todo
 | 
			
		||||
        &:after {
 | 
			
		||||
          @extend .ion;
 | 
			
		||||
          content: $ionicon-var-chevron-down;
 | 
			
		||||
          margin-left: 10px;
 | 
			
		||||
          color: $item-icon-accessory-color;
 | 
			
		||||
        }
 | 
			
		||||
        */
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &.core-expand-in-fullview .core-show-more:after {
 | 
			
		||||
        // content: $ionicon-var-chevron-right; @todo
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &:before {
 | 
			
		||||
        content: '';
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        background: -moz-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px));
 | 
			
		||||
        background: -webkit-gradient(left top, left bottom, color-stop(calc(100% - 50px), rgba(255, 255, 255, 0)), color-stop(calc(100% - 15px), white));
 | 
			
		||||
        background: -webkit-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px));
 | 
			
		||||
        background: -o-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px));
 | 
			
		||||
        background: -ms-linear-gradient(top, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px));
 | 
			
		||||
        background: linear-gradient(to bottom, rgba(255, 255, 255, 0) calc(100% - 50px), white calc(100% - 15px));
 | 
			
		||||
        z-index: 1000;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
core-format-text, *[core-format-text] {
 | 
			
		||||
  audio, video, a, iframe {
 | 
			
		||||
    pointer-events: auto;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Fix lists styles in core-format-text.
 | 
			
		||||
  ul, ol {
 | 
			
		||||
    -webkit-padding-start: 40px;
 | 
			
		||||
  }
 | 
			
		||||
  ul {
 | 
			
		||||
    list-style: disc;
 | 
			
		||||
  }
 | 
			
		||||
  ol {
 | 
			
		||||
    list-style: decimal;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .badge {
 | 
			
		||||
    position: initial !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Message item.
 | 
			
		||||
.item-message {
 | 
			
		||||
  core-format-text > p:only-child {
 | 
			
		||||
    display: inline;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Media item, ideal for icons.
 | 
			
		||||
.item-media {
 | 
			
		||||
  min-height: $item-media-height + ($content-padding * 2);
 | 
			
		||||
  > img:first-child {
 | 
			
		||||
    max-width: $item-media-width;
 | 
			
		||||
    max-height: $item-media-height;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ionic fix. Button can occupy all page if not.
 | 
			
		||||
ion-select {
 | 
			
		||||
  position: relative
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Top tabs
 | 
			
		||||
// -------------------------
 | 
			
		||||
 | 
			
		||||
.core-top-tabbar {
 | 
			
		||||
  @include position(null, null, 0, 0);
 | 
			
		||||
 | 
			
		||||
  z-index: $z-index-toolbar;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background: $core-top-tabs-background;
 | 
			
		||||
 | 
			
		||||
  > a {
 | 
			
		||||
    @extend .tab-button;
 | 
			
		||||
 | 
			
		||||
    background: $core-top-tabs-background;
 | 
			
		||||
    color: $core-top-tabs-color !important;
 | 
			
		||||
    border-bottom: 1px solid $core-top-tabs-border;
 | 
			
		||||
    font-size: 1.6rem;
 | 
			
		||||
 | 
			
		||||
    &[aria-selected=true] {
 | 
			
		||||
      color: $core-top-tabs-color-active !important;
 | 
			
		||||
      border-bottom: 2px solid $core-top-tabs-color-active;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,3 +7,7 @@
 | 
			
		||||
.button-wp-light {
 | 
			
		||||
  color: color($colors, primary, base);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.col[align-self-stretch] .card-wp {
 | 
			
		||||
  height: calc(100% - #{($card-wp-margin-end + $card-wp-margin-start)});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/archive-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/audio-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/avi-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/base-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/bmp-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/calc-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/chart-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/database-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/document-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/draw-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/eps-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/epub-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/flash-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/folder-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/folder-open-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/gif-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/html-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/image-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/impress-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/isf-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/jpeg-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/markup-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/math-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/moodle-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/mp3-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/mpeg-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/oth-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/pdf-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/png-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/powerpoint-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/psd-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/publisher-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/quicktime-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/sourcecode-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/spreadsheet-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/text-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/tiff-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/unknown-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/video-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/wav-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/wmv-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/files/writer-64.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										178
									
								
								src/assets/img/icons/activities.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,178 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   viewBox="157 -1509 148 125"
 | 
			
		||||
   preserveAspectRatio="xMinYMid meet"
 | 
			
		||||
   version="1.1"
 | 
			
		||||
   id="svg23"
 | 
			
		||||
   sodipodi:docname="activities.svg"
 | 
			
		||||
   inkscape:version="0.92.1 r15371">
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata27">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     gridtolerance="10"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:window-width="1920"
 | 
			
		||||
     inkscape:window-height="1016"
 | 
			
		||||
     id="namedview25"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     inkscape:zoom="5.981125"
 | 
			
		||||
     inkscape:cx="38.889548"
 | 
			
		||||
     inkscape:cy="62.5"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="27"
 | 
			
		||||
     inkscape:window-maximized="1"
 | 
			
		||||
     inkscape:current-layer="Group_42" />
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs7">
 | 
			
		||||
    <style
 | 
			
		||||
       id="style2">
 | 
			
		||||
      .cls-1 {
 | 
			
		||||
        clip-path: url(#clip-Activities);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-2 {
 | 
			
		||||
        fill: #eee;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-3 {
 | 
			
		||||
        fill: #c4c8cc;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-4 {
 | 
			
		||||
        fill: #fff;
 | 
			
		||||
      }
 | 
			
		||||
    </style>
 | 
			
		||||
    <clipPath
 | 
			
		||||
       id="clip-Activities">
 | 
			
		||||
      <rect
 | 
			
		||||
         x="157"
 | 
			
		||||
         y="-1509"
 | 
			
		||||
         width="148"
 | 
			
		||||
         height="125"
 | 
			
		||||
         id="rect4" />
 | 
			
		||||
    </clipPath>
 | 
			
		||||
  </defs>
 | 
			
		||||
  <g
 | 
			
		||||
     id="Activities"
 | 
			
		||||
     class="cls-1"
 | 
			
		||||
     clip-path="url(#clip-Activities)">
 | 
			
		||||
    <g
 | 
			
		||||
       id="Group_42"
 | 
			
		||||
       data-name="Group 42"
 | 
			
		||||
       transform="translate(-268 -1985)">
 | 
			
		||||
      <ellipse
 | 
			
		||||
         id="Ellipse_37"
 | 
			
		||||
         data-name="Ellipse 37"
 | 
			
		||||
         class="cls-2"
 | 
			
		||||
         cx="74"
 | 
			
		||||
         cy="14.785"
 | 
			
		||||
         rx="74"
 | 
			
		||||
         ry="14.785"
 | 
			
		||||
         transform="translate(425 571.43)"
 | 
			
		||||
         style="fill:#000000;fill-opacity:0.06666667" />
 | 
			
		||||
      <rect
 | 
			
		||||
         id="Rectangle_80"
 | 
			
		||||
         data-name="Rectangle 80"
 | 
			
		||||
         class="cls-3"
 | 
			
		||||
         width="94.182"
 | 
			
		||||
         height="110.215"
 | 
			
		||||
         transform="translate(451.909 476)" />
 | 
			
		||||
      <g
 | 
			
		||||
         id="Group_41"
 | 
			
		||||
         data-name="Group 41"
 | 
			
		||||
         transform="translate(467.043 493)">
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_81"
 | 
			
		||||
           data-name="Rectangle 81"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="44.456"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 0.549)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_82"
 | 
			
		||||
           data-name="Rectangle 82"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="33.342"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 11.652)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_83"
 | 
			
		||||
           data-name="Rectangle 83"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="44.456"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 30.772)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_84"
 | 
			
		||||
           data-name="Rectangle 84"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="33.342"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 41.875)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_85"
 | 
			
		||||
           data-name="Rectangle 85"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="44.456"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 61.291)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_86"
 | 
			
		||||
           data-name="Rectangle 86"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="33.342"
 | 
			
		||||
           height="5.625"
 | 
			
		||||
           transform="translate(21.16 72.393)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_38"
 | 
			
		||||
           data-name="Ellipse 38"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           cx="7.007"
 | 
			
		||||
           cy="7"
 | 
			
		||||
           rx="7.007"
 | 
			
		||||
           ry="7"
 | 
			
		||||
           transform="translate(0 0)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_39"
 | 
			
		||||
           data-name="Ellipse 39"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           cx="7.007"
 | 
			
		||||
           cy="7"
 | 
			
		||||
           rx="7.007"
 | 
			
		||||
           ry="7"
 | 
			
		||||
           transform="translate(0 31)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_40"
 | 
			
		||||
           data-name="Ellipse 40"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           cx="7.007"
 | 
			
		||||
           cy="7"
 | 
			
		||||
           rx="7.007"
 | 
			
		||||
           ry="7"
 | 
			
		||||
           transform="translate(0 61)" />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 4.5 KiB  | 
							
								
								
									
										257
									
								
								src/assets/img/icons/courses.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,257 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   viewBox="157 -1305 148 125"
 | 
			
		||||
   preserveAspectRatio="xMinYMid meet"
 | 
			
		||||
   version="1.1"
 | 
			
		||||
   id="svg34"
 | 
			
		||||
   sodipodi:docname="courses.svg"
 | 
			
		||||
   inkscape:version="0.92.1 r15371">
 | 
			
		||||
  <metadata
 | 
			
		||||
     id="metadata38">
 | 
			
		||||
    <rdf:RDF>
 | 
			
		||||
      <cc:Work
 | 
			
		||||
         rdf:about="">
 | 
			
		||||
        <dc:format>image/svg+xml</dc:format>
 | 
			
		||||
        <dc:type
 | 
			
		||||
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
 | 
			
		||||
      </cc:Work>
 | 
			
		||||
    </rdf:RDF>
 | 
			
		||||
  </metadata>
 | 
			
		||||
  <sodipodi:namedview
 | 
			
		||||
     pagecolor="#ffffff"
 | 
			
		||||
     bordercolor="#666666"
 | 
			
		||||
     borderopacity="1"
 | 
			
		||||
     objecttolerance="10"
 | 
			
		||||
     gridtolerance="10"
 | 
			
		||||
     guidetolerance="10"
 | 
			
		||||
     inkscape:pageopacity="0"
 | 
			
		||||
     inkscape:pageshadow="2"
 | 
			
		||||
     inkscape:window-width="744"
 | 
			
		||||
     inkscape:window-height="480"
 | 
			
		||||
     id="namedview36"
 | 
			
		||||
     showgrid="false"
 | 
			
		||||
     inkscape:zoom="1.888"
 | 
			
		||||
     inkscape:cx="74"
 | 
			
		||||
     inkscape:cy="62.5"
 | 
			
		||||
     inkscape:window-x="0"
 | 
			
		||||
     inkscape:window-y="27"
 | 
			
		||||
     inkscape:window-maximized="0"
 | 
			
		||||
     inkscape:current-layer="Group_44" />
 | 
			
		||||
  <defs
 | 
			
		||||
     id="defs7">
 | 
			
		||||
    <style
 | 
			
		||||
       id="style2">
 | 
			
		||||
      .cls-1 {
 | 
			
		||||
        clip-path: url(#clip-Courses);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-2 {
 | 
			
		||||
        fill: #eee;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-3 {
 | 
			
		||||
        fill: #c4c8cc;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .cls-4 {
 | 
			
		||||
        fill: #fff;
 | 
			
		||||
      }
 | 
			
		||||
    </style>
 | 
			
		||||
    <clipPath
 | 
			
		||||
       id="clip-Courses">
 | 
			
		||||
      <rect
 | 
			
		||||
         x="157"
 | 
			
		||||
         y="-1305"
 | 
			
		||||
         width="148"
 | 
			
		||||
         height="125"
 | 
			
		||||
         id="rect4" />
 | 
			
		||||
    </clipPath>
 | 
			
		||||
  </defs>
 | 
			
		||||
  <g
 | 
			
		||||
     id="Courses"
 | 
			
		||||
     class="cls-1"
 | 
			
		||||
     clip-path="url(#clip-Courses)">
 | 
			
		||||
    <g
 | 
			
		||||
       id="Group_44"
 | 
			
		||||
       data-name="Group 44"
 | 
			
		||||
       transform="translate(-268 -1781)">
 | 
			
		||||
      <ellipse
 | 
			
		||||
         id="Ellipse_41"
 | 
			
		||||
         data-name="Ellipse 41"
 | 
			
		||||
         class="cls-2"
 | 
			
		||||
         cx="74"
 | 
			
		||||
         cy="14.785"
 | 
			
		||||
         rx="74"
 | 
			
		||||
         ry="14.785"
 | 
			
		||||
         transform="translate(425 571.43)"
 | 
			
		||||
         style="fill:#000000;fill-opacity:0.06666667" />
 | 
			
		||||
      <rect
 | 
			
		||||
         id="Rectangle_87"
 | 
			
		||||
         data-name="Rectangle 87"
 | 
			
		||||
         class="cls-3"
 | 
			
		||||
         width="95.097"
 | 
			
		||||
         height="110.215"
 | 
			
		||||
         transform="translate(451.909 476)" />
 | 
			
		||||
      <g
 | 
			
		||||
         id="Group_43"
 | 
			
		||||
         data-name="Group 43"
 | 
			
		||||
         transform="translate(464.04 494)">
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_88"
 | 
			
		||||
           data-name="Rectangle 88"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="31.043"
 | 
			
		||||
           height="34"
 | 
			
		||||
           transform="translate(0)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_89"
 | 
			
		||||
           data-name="Rectangle 89"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="31.043"
 | 
			
		||||
           height="34"
 | 
			
		||||
           transform="translate(0 42)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_90"
 | 
			
		||||
           data-name="Rectangle 90"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="31.067"
 | 
			
		||||
           height="34"
 | 
			
		||||
           transform="translate(39.005)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_91"
 | 
			
		||||
           data-name="Rectangle 91"
 | 
			
		||||
           class="cls-4"
 | 
			
		||||
           width="31.067"
 | 
			
		||||
           height="34"
 | 
			
		||||
           transform="translate(39.005 42)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_92"
 | 
			
		||||
           data-name="Rectangle 92"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="23.023"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 16.549)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_93"
 | 
			
		||||
           data-name="Rectangle 93"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="23.023"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 58.549)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_94"
 | 
			
		||||
           data-name="Rectangle 94"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="23.023"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 16.549)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_95"
 | 
			
		||||
           data-name="Rectangle 95"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="23.023"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 58.549)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_96"
 | 
			
		||||
           data-name="Rectangle 96"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="14.014"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 21.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_97"
 | 
			
		||||
           data-name="Rectangle 97"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="18.845"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 26.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_98"
 | 
			
		||||
           data-name="Rectangle 98"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="14.014"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 63.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_99"
 | 
			
		||||
           data-name="Rectangle 99"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="18.845"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(3.081 68.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_100"
 | 
			
		||||
           data-name="Rectangle 100"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="14.014"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 21.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_101"
 | 
			
		||||
           data-name="Rectangle 101"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="18.845"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 26.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_102"
 | 
			
		||||
           data-name="Rectangle 102"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="14.014"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 63.825)" />
 | 
			
		||||
        <rect
 | 
			
		||||
           id="Rectangle_103"
 | 
			
		||||
           data-name="Rectangle 103"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           width="18.845"
 | 
			
		||||
           height="3.18"
 | 
			
		||||
           transform="translate(43.122 68.825)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_42"
 | 
			
		||||
           data-name="Ellipse 42"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           cx="5.658"
 | 
			
		||||
           cy="5.652"
 | 
			
		||||
           rx="5.658"
 | 
			
		||||
           ry="5.652"
 | 
			
		||||
           transform="translate(3.003 3.55)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_43"
 | 
			
		||||
           data-name="Ellipse 43"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           cx="5.658"
 | 
			
		||||
           cy="5.652"
 | 
			
		||||
           rx="5.658"
 | 
			
		||||
           ry="5.652"
 | 
			
		||||
           transform="translate(3.003 45.55)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_44"
 | 
			
		||||
           data-name="Ellipse 44"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           cx="5.658"
 | 
			
		||||
           cy="5.652"
 | 
			
		||||
           rx="5.658"
 | 
			
		||||
           ry="5.652"
 | 
			
		||||
           transform="translate(43.044 3.55)" />
 | 
			
		||||
        <ellipse
 | 
			
		||||
           id="Ellipse_45"
 | 
			
		||||
           data-name="Ellipse 45"
 | 
			
		||||
           class="cls-3"
 | 
			
		||||
           cx="5.658"
 | 
			
		||||
           cy="5.652"
 | 
			
		||||
           rx="5.658"
 | 
			
		||||
           ry="5.652"
 | 
			
		||||
           transform="translate(43.044 45.55)" />
 | 
			
		||||
      </g>
 | 
			
		||||
    </g>
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 6.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/assets/img/icons/paypal.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.0 KiB  | 
@ -482,7 +482,14 @@ export class CoreSite {
 | 
			
		||||
 | 
			
		||||
                // We pass back a clone of the original object, this may
 | 
			
		||||
                // prevent errors if in the callback the object is modified.
 | 
			
		||||
                return Object.assign({}, response);
 | 
			
		||||
                if (typeof response == 'object') {
 | 
			
		||||
                    if (Array.isArray(response)) {
 | 
			
		||||
                        return Array.from(response);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return Object.assign({}, response);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return response;
 | 
			
		||||
            }).catch((error) => {
 | 
			
		||||
                if (error.errorcode == 'invalidtoken' ||
 | 
			
		||||
                        (error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) {
 | 
			
		||||
@ -853,10 +860,10 @@ export class CoreSite {
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the URL to the documentation of the app, based on Moodle version and current language.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} [page]    Docs page to go to.
 | 
			
		||||
     * @return {Promise}         Promise resolved with the Moodle docs URL.
 | 
			
		||||
     * @param {string} [page] Docs page to go to.
 | 
			
		||||
     * @return {Promise<string>} Promise resolved with the Moodle docs URL.
 | 
			
		||||
     */
 | 
			
		||||
    getDocsUrl(page: string) : Promise<string> {
 | 
			
		||||
    getDocsUrl(page?: string) : Promise<string> {
 | 
			
		||||
        const release = this.infos.release ? this.infos.release : undefined;
 | 
			
		||||
        return this.urlUtils.getDocsUrl(release, page);
 | 
			
		||||
    }
 | 
			
		||||
@ -1054,7 +1061,7 @@ export class CoreSite {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (alertMessage) {
 | 
			
		||||
                    let alert = this.domUtils.showAlert('core.notice', alertMessage, null, 3000);
 | 
			
		||||
                    let alert = this.domUtils.showAlert('core.notice', alertMessage, undefined, 3000);
 | 
			
		||||
                    alert.onDidDismiss(() => {
 | 
			
		||||
                        if (inApp) {
 | 
			
		||||
                            resolve(this.utils.openInApp(url, options));
 | 
			
		||||
 | 
			
		||||
@ -262,7 +262,7 @@ export class SQLiteDB {
 | 
			
		||||
     * @return {Promise<any>} Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    deleteRecords(table: string, conditions?: object) : Promise<any> {
 | 
			
		||||
        if (conditions === null) {
 | 
			
		||||
        if (conditions === null || typeof conditions == 'undefined') {
 | 
			
		||||
            // No conditions, delete the whole table.
 | 
			
		||||
            return this.execute(`DELETE FROM TABLE ${table}`);
 | 
			
		||||
        }
 | 
			
		||||
@ -329,6 +329,21 @@ export class SQLiteDB {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Format the data to insert in the database. Removes undefined entries so they are stored as null instead of 'undefined'.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {object} data Data to insert.
 | 
			
		||||
     */
 | 
			
		||||
    protected formatDataToInsert(data: object) : void {
 | 
			
		||||
        // Remove undefined entries and convert null to "NULL".
 | 
			
		||||
        for (let name in data) {
 | 
			
		||||
            let value = data[name];
 | 
			
		||||
            if (typeof value == 'undefined') {
 | 
			
		||||
                delete data[name];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the records from a table.
 | 
			
		||||
     *
 | 
			
		||||
@ -585,6 +600,8 @@ export class SQLiteDB {
 | 
			
		||||
     * @return {any[]} Array with the SQL query and the params.
 | 
			
		||||
     */
 | 
			
		||||
    protected getSqlInsertQuery(table: string, data: object) : any[] {
 | 
			
		||||
        this.formatDataToInsert(data);
 | 
			
		||||
 | 
			
		||||
        let keys = Object.keys(data),
 | 
			
		||||
            fields = keys.join(','),
 | 
			
		||||
            questionMarks = ',?'.repeat(keys.length).substr(1);
 | 
			
		||||
@ -674,10 +691,10 @@ export class SQLiteDB {
 | 
			
		||||
     */
 | 
			
		||||
    normaliseLimitFromNum(limitFrom: any, limitNum: any) : number[] {
 | 
			
		||||
        // We explicilty treat these cases as 0.
 | 
			
		||||
        if (limitFrom === null || limitFrom === '' || limitFrom === -1) {
 | 
			
		||||
        if (typeof limitFrom == 'undefined' || limitFrom === null || limitFrom === '' || limitFrom === -1) {
 | 
			
		||||
            limitFrom = 0;
 | 
			
		||||
        }
 | 
			
		||||
        if (limitNum === null || limitNum === '' || limitNum === -1) {
 | 
			
		||||
        if (typeof limitNum == 'undefined' || limitNum === null || limitNum === '' || limitNum === -1) {
 | 
			
		||||
            limitNum = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -773,6 +790,8 @@ export class SQLiteDB {
 | 
			
		||||
            sql,
 | 
			
		||||
            params;
 | 
			
		||||
 | 
			
		||||
        this.formatDataToInsert(data);
 | 
			
		||||
 | 
			
		||||
        for (let key in data) {
 | 
			
		||||
            sets.push(`${key} = ?`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,13 @@ import { CoreMarkRequiredComponent } from './mark-required/mark-required';
 | 
			
		||||
import { CoreInputErrorsComponent } from './input-errors/input-errors';
 | 
			
		||||
import { CoreShowPasswordComponent } from './show-password/show-password';
 | 
			
		||||
import { CoreIframeComponent } from './iframe/iframe';
 | 
			
		||||
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
 | 
			
		||||
import { CoreEmptyBoxComponent } from './empty-box/empty-box';
 | 
			
		||||
import { CoreSearchBoxComponent } from './search-box/search-box';
 | 
			
		||||
import { CoreFileComponent } from './file/file';
 | 
			
		||||
import { CoreContextMenuComponent } from './context-menu/context-menu';
 | 
			
		||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
 | 
			
		||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
@ -28,7 +35,17 @@ import { CoreIframeComponent } from './iframe/iframe';
 | 
			
		||||
        CoreMarkRequiredComponent,
 | 
			
		||||
        CoreInputErrorsComponent,
 | 
			
		||||
        CoreShowPasswordComponent,
 | 
			
		||||
        CoreIframeComponent
 | 
			
		||||
        CoreIframeComponent,
 | 
			
		||||
        CoreProgressBarComponent,
 | 
			
		||||
        CoreEmptyBoxComponent,
 | 
			
		||||
        CoreSearchBoxComponent,
 | 
			
		||||
        CoreFileComponent,
 | 
			
		||||
        CoreContextMenuComponent,
 | 
			
		||||
        CoreContextMenuItemComponent,
 | 
			
		||||
        CoreContextMenuPopoverComponent
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreContextMenuPopoverComponent
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        IonicModule,
 | 
			
		||||
@ -40,7 +57,13 @@ import { CoreIframeComponent } from './iframe/iframe';
 | 
			
		||||
        CoreMarkRequiredComponent,
 | 
			
		||||
        CoreInputErrorsComponent,
 | 
			
		||||
        CoreShowPasswordComponent,
 | 
			
		||||
        CoreIframeComponent
 | 
			
		||||
        CoreIframeComponent,
 | 
			
		||||
        CoreProgressBarComponent,
 | 
			
		||||
        CoreEmptyBoxComponent,
 | 
			
		||||
        CoreSearchBoxComponent,
 | 
			
		||||
        CoreFileComponent,
 | 
			
		||||
        CoreContextMenuComponent,
 | 
			
		||||
        CoreContextMenuItemComponent
 | 
			
		||||
    ]
 | 
			
		||||
})
 | 
			
		||||
export class CoreComponentsModule {}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										114
									
								
								src/components/context-menu/context-menu-item.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,114 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
 | 
			
		||||
import { CoreContextMenuComponent } from './context-menu';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This directive adds a item to the Context Menu popover.
 | 
			
		||||
 *
 | 
			
		||||
 * @description
 | 
			
		||||
 * This directive defines and item to be added to the popover generated in CoreContextMenu.
 | 
			
		||||
 *
 | 
			
		||||
 * It is required to place this tag inside a core-context-menu tag.
 | 
			
		||||
 *
 | 
			
		||||
 * <core-context-menu>
 | 
			
		||||
 *     <core-context-menu-item [hidden]="showGrid" [priority]="601" [content]="'core.layoutgrid' | translate"
 | 
			
		||||
 *         (action)="switchGrid()" [iconAction]="'apps'"></core-context-menu-item>
 | 
			
		||||
 * </core-context-menu>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-context-menu-item',
 | 
			
		||||
    template: ''
 | 
			
		||||
})
 | 
			
		||||
export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges {
 | 
			
		||||
    @Input() content: string; // Content of the item.
 | 
			
		||||
    @Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item.
 | 
			
		||||
    @Input() iconAction?: string; // Name of the icon to be shown on the right side of the item. It represents the action to do on
 | 
			
		||||
                                  // click. If is "spinner" an spinner will be shown. If no icon or spinner is selected, no action
 | 
			
		||||
                                  // or link will work. If href but no iconAction is provided ion-arrow-right-c will be used.
 | 
			
		||||
    @Input() ariaDescription?: string; // Aria label to add to iconDescription.
 | 
			
		||||
    @Input() ariaAction?: string; // Aria label to add to iconAction. If not set, it will be equal to content.
 | 
			
		||||
    @Input() href?: string; // Link to go if no action provided.
 | 
			
		||||
    @Input() captureLink?: boolean|string; // Whether the link needs to be captured by the app.
 | 
			
		||||
    @Input() autoLogin?: string; // Whether the link needs to be opened using auto-login.
 | 
			
		||||
    @Input() closeOnClick?: boolean|string = true; // Whether to close the popover when the item is clicked.
 | 
			
		||||
    @Input() priority?: number; // Used to sort items. The highest priority, the highest position.
 | 
			
		||||
    @Input() badge?: string; // A badge to show in the item.
 | 
			
		||||
    @Input() badgeClass?: number; // A class to set in the badge.
 | 
			
		||||
    @Input() hidden?: boolean; // Whether the item should be hidden.
 | 
			
		||||
    @Output() action?: EventEmitter<string>; // Will emit an event when the item clicked.
 | 
			
		||||
 | 
			
		||||
    protected hasAction = false;
 | 
			
		||||
    protected destroyed = false;
 | 
			
		||||
 | 
			
		||||
    constructor(private ctxtMenu: CoreContextMenuComponent) {
 | 
			
		||||
        this.action = new EventEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        // Initialize values.
 | 
			
		||||
        this.priority = this.priority || 1;
 | 
			
		||||
        this.closeOnClick = this.getBooleanValue(this.closeOnClick, true);
 | 
			
		||||
        this.hasAction = this.action.observers.length > 0;
 | 
			
		||||
        this.ariaAction = this.ariaAction || this.content;
 | 
			
		||||
 | 
			
		||||
        if (this.hasAction) {
 | 
			
		||||
            this.href = '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Navigation help if href provided.
 | 
			
		||||
        this.captureLink = this.href && this.captureLink ? this.captureLink : false;
 | 
			
		||||
        this.autoLogin = this.autoLogin || 'check';
 | 
			
		||||
 | 
			
		||||
        if (!this.destroyed) {
 | 
			
		||||
            this.ctxtMenu.addItem(this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a boolean value from item.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {any} value Value to check.
 | 
			
		||||
     * @param {boolean} defaultValue Value to use if undefined.
 | 
			
		||||
     * @return {boolean} Boolean value.
 | 
			
		||||
     */
 | 
			
		||||
    protected getBooleanValue(value: any, defaultValue: boolean) : boolean {
 | 
			
		||||
        if (typeof value == 'undefined') {
 | 
			
		||||
            return defaultValue;
 | 
			
		||||
        }
 | 
			
		||||
        return value && value !== 'false';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy() {
 | 
			
		||||
        this.destroyed = true;
 | 
			
		||||
        this.ctxtMenu.removeItem(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: {[name: string]: SimpleChange}) {
 | 
			
		||||
        if (changes.hidden && !changes.hidden.firstChange) {
 | 
			
		||||
            this.ctxtMenu.itemsChanged();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/components/context-menu/context-menu-popover.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,10 @@
 | 
			
		||||
<ion-list>
 | 
			
		||||
    <ion-list-header *ngIf="title">{{title}}</ion-list-header>
 | 
			
		||||
    <a ion-item text-wrap *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [attr.detail-none]="!item.href || item.iconAction">
 | 
			
		||||
        <ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [attr.aria-label]="item.ariaDescription" item-start></ion-icon>
 | 
			
		||||
        <core-format-text [clean]="true" [text]="item.content"></core-format-text>
 | 
			
		||||
        <ion-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction" item-end></ion-icon>
 | 
			
		||||
        <ion-spinner *ngIf="(item.href || item.action) && item.iconAction == 'spinner'" item-end></ion-spinner>
 | 
			
		||||
        <ion-badge class="{{item.badgeClass}}" item-end *ngIf="item.badge">{{item.badge}}</ion-badge>
 | 
			
		||||
    </a>
 | 
			
		||||
</ion-list>
 | 
			
		||||
							
								
								
									
										69
									
								
								src/components/context-menu/context-menu-popover.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,69 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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 } from '@angular/core';
 | 
			
		||||
import { NavParams, ViewController } from 'ionic-angular';
 | 
			
		||||
import { CoreContextMenuItemComponent } from './context-menu-item';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display a list of items received by param in a popover.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-context-menu-popover',
 | 
			
		||||
    templateUrl: 'context-menu-popover.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreContextMenuPopoverComponent {
 | 
			
		||||
    title: string;
 | 
			
		||||
    items: CoreContextMenuItemComponent[];
 | 
			
		||||
 | 
			
		||||
    constructor(navParams: NavParams, private viewCtrl: ViewController) {
 | 
			
		||||
        this.title = navParams.get('title');
 | 
			
		||||
        this.items = navParams.get('items') || [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Close the popover.
 | 
			
		||||
     */
 | 
			
		||||
    closeMenu() : void {
 | 
			
		||||
        this.viewCtrl.dismiss();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function called when an item is clicked.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Event} event Click event.
 | 
			
		||||
     * @param {CoreContextMenuItemComponent} item Item clicked.
 | 
			
		||||
     * @return {boolean} Return true if success, false if error.
 | 
			
		||||
     */
 | 
			
		||||
    itemClicked(event: Event, item: CoreContextMenuItemComponent) : boolean {
 | 
			
		||||
        if (item.action.observers.length > 0) {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
            if (!item.iconAction || item.iconAction == 'spinner') {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (item.closeOnClick) {
 | 
			
		||||
                this.closeMenu();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            item.action.emit(this.closeMenu.bind(this));
 | 
			
		||||
        } else if (item.href && item.closeOnClick) {
 | 
			
		||||
            this.closeMenu();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								src/components/context-menu/context-menu.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,4 @@
 | 
			
		||||
<button [hidden]="hideMenu" ion-button clear icon-only [attr.aria-label]="ariaLabel" (click)="showContextMenu($event)">
 | 
			
		||||
    <ion-icon [name]="icon"></ion-icon>
 | 
			
		||||
</button>
 | 
			
		||||
<ng-content></ng-content>
 | 
			
		||||
							
								
								
									
										98
									
								
								src/components/context-menu/context-menu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,98 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, OnInit } from '@angular/core';
 | 
			
		||||
import { PopoverController } from 'ionic-angular';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { CoreContextMenuItemComponent } from './context-menu-item';
 | 
			
		||||
import { CoreContextMenuPopoverComponent } from './context-menu-popover';
 | 
			
		||||
import { Subject } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This component adds a button (usually in the navigation bar) that displays a context menu popover.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-context-menu',
 | 
			
		||||
    templateUrl: 'context-menu.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreContextMenuComponent implements OnInit {
 | 
			
		||||
    @Input() icon?: string; // Icon to be shown on the navigation bar. Default: Kebab menu icon.
 | 
			
		||||
    @Input() title?: string; // Aria label and text to be shown on the top of the popover.
 | 
			
		||||
 | 
			
		||||
    hideMenu: boolean;
 | 
			
		||||
    ariaLabel: string;
 | 
			
		||||
    protected items: CoreContextMenuItemComponent[] = [];
 | 
			
		||||
    protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
 | 
			
		||||
 | 
			
		||||
    constructor(private translate: TranslateService, private popoverCtrl: PopoverController) {
 | 
			
		||||
        // Create the stream and subscribe to it. We ignore successive changes during 250ms.
 | 
			
		||||
        this.itemsChangedStream = new Subject<void>();
 | 
			
		||||
        this.itemsChangedStream.auditTime(250).subscribe(() => {
 | 
			
		||||
            // Hide the menu if all items are hidden.
 | 
			
		||||
            this.hideMenu = !this.items.some((item) => {
 | 
			
		||||
                return !item.hidden;
 | 
			
		||||
            });
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        this.icon = this.icon || 'more';
 | 
			
		||||
        this.ariaLabel = this.title || this.translate.instant('core.info');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a context menu item.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {CoreContextMenuItemComponent} item The item to add.
 | 
			
		||||
     */
 | 
			
		||||
    addItem(item: CoreContextMenuItemComponent) : void {
 | 
			
		||||
        this.items.push(item);
 | 
			
		||||
        this.itemsChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function called when the items change.
 | 
			
		||||
     */
 | 
			
		||||
    itemsChanged() {
 | 
			
		||||
        this.itemsChangedStream.next();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove an item from the context menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {CoreContextMenuItemComponent} item The item to remove.
 | 
			
		||||
     */
 | 
			
		||||
    removeItem(item: CoreContextMenuItemComponent) : void {
 | 
			
		||||
        let index = this.items.indexOf(item);
 | 
			
		||||
        if (index >= 0) {
 | 
			
		||||
            this.items.splice(index, 1);
 | 
			
		||||
        }
 | 
			
		||||
        this.itemsChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the context menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {MouseEvent} event Event.
 | 
			
		||||
     */
 | 
			
		||||
    showContextMenu(event: MouseEvent) : void {
 | 
			
		||||
        let popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, {title: this.title, items: this.items});
 | 
			
		||||
        popover.present({
 | 
			
		||||
            ev: event
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/components/empty-box/empty-box.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,8 @@
 | 
			
		||||
<div class="core-empty-box" [class.core-empty-box-inline]="!image && !icon">
 | 
			
		||||
    <div class="core-empty-box-content" padding>
 | 
			
		||||
        <img *ngIf="image && !icon" [src]="image" role="presentation">
 | 
			
		||||
        <ion-icon *ngIf="icon" [name]="icon"></ion-icon>
 | 
			
		||||
        <p *ngIf="message" [class.padding-top]="image || icon">{{ message }}</p>
 | 
			
		||||
        <ng-content></ng-content>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										57
									
								
								src/components/empty-box/empty-box.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,57 @@
 | 
			
		||||
core-empty-box {
 | 
			
		||||
  .core-empty-box {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    display: table;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    z-index: -1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    clear: both;
 | 
			
		||||
 | 
			
		||||
    .core-empty-box-content {
 | 
			
		||||
      color: $black;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      display: table-cell;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      vertical-align: middle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.core-empty-box-inline {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      top: initial;
 | 
			
		||||
      left: initial;
 | 
			
		||||
      right: initial;
 | 
			
		||||
      z-index: initial;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
      font-size: 120px;
 | 
			
		||||
    }
 | 
			
		||||
    img {
 | 
			
		||||
      height: 125px;
 | 
			
		||||
      width: 145px;
 | 
			
		||||
    }
 | 
			
		||||
    p {
 | 
			
		||||
      font-size: 120%;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media only screen and (max-height: 420px) {
 | 
			
		||||
    .core-empty-box {
 | 
			
		||||
      position: relative;
 | 
			
		||||
 | 
			
		||||
      .icon {
 | 
			
		||||
        font-size: 100px;
 | 
			
		||||
      }
 | 
			
		||||
      img {
 | 
			
		||||
        height: 104px;
 | 
			
		||||
        width: 121px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/components/empty-box/empty-box.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,33 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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.
 | 
			
		||||
 *
 | 
			
		||||
 * Usage:
 | 
			
		||||
 * <core-empty-box *ngIf="empty" icon="bell" [message]="'core.emptymessage' | translate"></core-empty-box>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-empty-box',
 | 
			
		||||
    templateUrl: 'empty-box.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreEmptyBoxComponent {
 | 
			
		||||
    @Input() message: string; // 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.
 | 
			
		||||
 | 
			
		||||
    constructor() {}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/components/file/file.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,13 @@
 | 
			
		||||
<a ion-item text-wrap class="item-media" (click)="download($event, true)" [class.item-2-button-right]="canDelete">
 | 
			
		||||
    <img [src]="fileIcon" alt="" role="presentation" item-start />
 | 
			
		||||
    <p>{{fileName}}</p>
 | 
			
		||||
    <div class="buttons" item-end>
 | 
			
		||||
        <button ion-button clear icon-only (click)="download($event)" *ngIf="!isDownloading && showDownload" [attr.aria-label]="'core.download' | translate">
 | 
			
		||||
            <ion-icon [name]="isDownloaded ? 'refresh' : 'cloud-download'"></ion-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button ion-button clear icon-only (click)="delete($event)" *ngIf="!isDownloading && canDelete" [attr.aria-label]="'core.delete' | translate" color="danger">
 | 
			
		||||
            <ion-icon name="trash"></ion-icon>
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ion-spinner *ngIf="isDownloading" item-end></ion-spinner>
 | 
			
		||||
</a>
 | 
			
		||||
							
								
								
									
										2
									
								
								src/components/file/file.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,2 @@
 | 
			
		||||
core-file {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										291
									
								
								src/components/file/file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,291 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, Output, OnInit, OnDestroy, EventEmitter } from '@angular/core';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { CoreAppProvider } from '../../providers/app';
 | 
			
		||||
import { CoreEventsProvider } from '../../providers/events';
 | 
			
		||||
import { CoreFileProvider } from '../../providers/file';
 | 
			
		||||
import { CoreFilepoolProvider } from '../../providers/filepool';
 | 
			
		||||
import { CoreSitesProvider } from '../../providers/sites';
 | 
			
		||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
 | 
			
		||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
 | 
			
		||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
 | 
			
		||||
import { CoreConstants } from '../../core/constants';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
 | 
			
		||||
 * to download/refresh it.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-file',
 | 
			
		||||
    templateUrl: 'file.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreFileComponent implements OnInit, OnDestroy {
 | 
			
		||||
    @Input() file: any; // The file. Must have a property 'filename' and a 'fileurl' or 'url'
 | 
			
		||||
    @Input() component?: string; // Component the file belongs to.
 | 
			
		||||
    @Input() componentId?: string|number; // Component ID.
 | 
			
		||||
    @Input() timemodified?: number; // If set, the value will be used to check if the file is outdated.
 | 
			
		||||
    @Input() canDelete?: boolean|string; // Whether file can be deleted.
 | 
			
		||||
    @Input() alwaysDownload?: boolean|string; // Whether it should always display the refresh button when the file is downloaded.
 | 
			
		||||
                                              // Use it for files that you cannot determine if they're outdated or not.
 | 
			
		||||
    @Input() canDownload?: boolean|string = true; // Whether file can be downloaded.
 | 
			
		||||
    @Output() onDelete?: EventEmitter<string>; // Will notify when the delete button is clicked.
 | 
			
		||||
 | 
			
		||||
    isDownloaded: boolean;
 | 
			
		||||
    isDownloading: boolean;
 | 
			
		||||
    showDownload: boolean;
 | 
			
		||||
    fileIcon: string;
 | 
			
		||||
    fileName: string;
 | 
			
		||||
 | 
			
		||||
    protected fileUrl: string;
 | 
			
		||||
    protected siteId: string;
 | 
			
		||||
    protected fileSize: number;
 | 
			
		||||
    protected observer;
 | 
			
		||||
 | 
			
		||||
    constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
 | 
			
		||||
            private domUtils: CoreDomUtilsProvider, private filepoolProvider: CoreFilepoolProvider,
 | 
			
		||||
            private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider,
 | 
			
		||||
            private mimeUtils: CoreMimetypeUtilsProvider, private eventsProvider: CoreEventsProvider) {
 | 
			
		||||
        this.onDelete = new EventEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        this.canDelete = this.utils.isTrueOrOne(this.canDelete);
 | 
			
		||||
        this.alwaysDownload = this.utils.isTrueOrOne(this.alwaysDownload);
 | 
			
		||||
        this.canDownload = this.utils.isTrueOrOne(this.canDownload);
 | 
			
		||||
        this.timemodified = this.timemodified || 0;
 | 
			
		||||
 | 
			
		||||
        this.fileUrl = this.file.fileurl || this.file.url;
 | 
			
		||||
        this.siteId = this.sitesProvider.getCurrentSiteId();
 | 
			
		||||
        this.fileSize = this.file.filesize;
 | 
			
		||||
        this.fileName = this.file.filename;
 | 
			
		||||
 | 
			
		||||
        if (this.file.isexternalfile) {
 | 
			
		||||
            this.alwaysDownload = true; // Always show the download button in external files.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.fileIcon = this.mimeUtils.getFileIcon(this.file.filename);
 | 
			
		||||
 | 
			
		||||
        if (this.canDownload) {
 | 
			
		||||
            this.calculateState();
 | 
			
		||||
 | 
			
		||||
            // Update state when receiving events about this file.
 | 
			
		||||
            this.filepoolProvider.getFileEventNameByUrl(this.siteId, this.fileUrl).then((eventName) => {
 | 
			
		||||
                this.observer = this.eventsProvider.on(eventName, () => {
 | 
			
		||||
                    this.calculateState();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to get the file state and set variables based on it.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Promise<void>} Promise resolved when state has been calculated.
 | 
			
		||||
     */
 | 
			
		||||
    protected calculateState() : Promise<void> {
 | 
			
		||||
        return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => {
 | 
			
		||||
            let canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
 | 
			
		||||
 | 
			
		||||
            this.isDownloaded = state === CoreConstants.downloaded || state === CoreConstants.outdated;
 | 
			
		||||
            this.isDownloading = canDownload && state === CoreConstants.downloading;
 | 
			
		||||
            this.showDownload = canDownload && (state === CoreConstants.notDownloaded || state === CoreConstants.outdated ||
 | 
			
		||||
                    (this.alwaysDownload && state === CoreConstants.downloaded));
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download the file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Promise<string>} Promise resolved when file is downloaded.
 | 
			
		||||
     */
 | 
			
		||||
    protected downloadFile() : Promise<string> {
 | 
			
		||||
        if (!this.sitesProvider.getCurrentSite().canDownloadFiles()) {
 | 
			
		||||
            this.domUtils.showErrorModal('core.cannotdownloadfiles', true);
 | 
			
		||||
            return Promise.reject(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.isDownloading = true;
 | 
			
		||||
        return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId,
 | 
			
		||||
                this.timemodified, undefined, undefined, this.file).catch(() => {
 | 
			
		||||
 | 
			
		||||
            // Call calculateState to make sure we have the right state.
 | 
			
		||||
            return this.calculateState().then(() => {
 | 
			
		||||
                if (this.isDownloaded) {
 | 
			
		||||
                    return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
 | 
			
		||||
                } else {
 | 
			
		||||
                    return Promise.reject(null);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to open a file, downloading it if needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Promise<string>} Promise resolved when file is opened.
 | 
			
		||||
     */
 | 
			
		||||
    protected openFile() : Promise<any> {
 | 
			
		||||
        let fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl),
 | 
			
		||||
            promise;
 | 
			
		||||
 | 
			
		||||
        if (this.fileProvider.isAvailable()) {
 | 
			
		||||
            promise = Promise.resolve().then(() => {
 | 
			
		||||
                // The file system is available.
 | 
			
		||||
                let isWifi = !this.appProvider.isNetworkAccessLimited(),
 | 
			
		||||
                    isOnline = this.appProvider.isOnline();
 | 
			
		||||
 | 
			
		||||
                if (this.isDownloaded && !this.showDownload) {
 | 
			
		||||
                    // File is downloaded, get the local file URL.
 | 
			
		||||
                    return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
 | 
			
		||||
                            this.component, this.componentId, this.timemodified, false, false, this.file);
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (!isOnline && !this.isDownloaded) {
 | 
			
		||||
                        // Not downloaded and user is offline, reject.
 | 
			
		||||
                        return Promise.reject(this.translate.instant('core.networkerrormsg'));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let isDownloading = this.isDownloading;
 | 
			
		||||
                    this.isDownloading = true; // This check could take a while, show spinner.
 | 
			
		||||
                    return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, this.fileSize).then(() => {
 | 
			
		||||
                        if (isDownloading) {
 | 
			
		||||
                            // It's already downloading, stop.
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        // Download and then return the local URL.
 | 
			
		||||
                        return this.downloadFile();
 | 
			
		||||
                    }, () => {
 | 
			
		||||
                        // Start the download if in wifi, but return the URL right away so the file is opened.
 | 
			
		||||
                        if (isWifi && isOnline) {
 | 
			
		||||
                            this.downloadFile();
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if (isDownloading || !this.isDownloaded || isOnline) {
 | 
			
		||||
                            // Not downloaded or outdated and online, return the online URL.
 | 
			
		||||
                            return fixedUrl;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // Outdated but offline, so we return the local URL.
 | 
			
		||||
                            return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl,
 | 
			
		||||
                                    this.component, this.componentId, this.timemodified, false, false, this.file);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // Use the online URL.
 | 
			
		||||
            promise = Promise.resolve(fixedUrl);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return promise.then((url) => {
 | 
			
		||||
            if (!url) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (url.indexOf('http') === 0) {
 | 
			
		||||
                return this.utils.openOnlineFile(url).catch((error) => {
 | 
			
		||||
                    // Error opening the file, some apps don't allow opening online files.
 | 
			
		||||
                    if (!this.fileProvider.isAvailable()) {
 | 
			
		||||
                        return Promise.reject(error);
 | 
			
		||||
                    } else if (this.isDownloading) {
 | 
			
		||||
                        return Promise.reject(this.translate.instant('core.erroropenfiledownloading'));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    let subPromise;
 | 
			
		||||
 | 
			
		||||
                    if (status === CoreConstants.notDownloaded) {
 | 
			
		||||
                        // File is not downloaded, download and then return the local URL.
 | 
			
		||||
                        subPromise = this.downloadFile();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // File is outdated and can't be opened in online, return the local URL.
 | 
			
		||||
                        subPromise = this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return subPromise.then((url) => {
 | 
			
		||||
                        return this.utils.openFile(url);
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.utils.openFile(url);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download a file and, optionally, open it afterwards.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Event} e Click event.
 | 
			
		||||
     * @param {boolean} openAfterDownload Whether the file should be opened after download.
 | 
			
		||||
     */
 | 
			
		||||
    download(e: Event, openAfterDownload: boolean) : void {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        let promise;
 | 
			
		||||
 | 
			
		||||
        if (this.isDownloading && !openAfterDownload) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) {
 | 
			
		||||
            this.domUtils.showErrorModal('core.networkerrormsg', true);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (openAfterDownload) {
 | 
			
		||||
            // File needs to be opened now.
 | 
			
		||||
            this.openFile().catch((error) => {
 | 
			
		||||
                this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big.
 | 
			
		||||
            promise = this.fileSize ? this.domUtils.confirmDownloadSize({size: this.fileSize, total: true}) : Promise.resolve();
 | 
			
		||||
            promise.then(() => {
 | 
			
		||||
                // User confirmed, add the file to queue.
 | 
			
		||||
                this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => {
 | 
			
		||||
                    this.isDownloading = true;
 | 
			
		||||
                    this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component,
 | 
			
		||||
                            this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => {
 | 
			
		||||
                        this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true);
 | 
			
		||||
                        this.calculateState();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete the file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Event} e Click event.
 | 
			
		||||
     */
 | 
			
		||||
    deleteFile(e: Event) : void {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        if (this.canDelete) {
 | 
			
		||||
            this.onDelete.emit();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy() {
 | 
			
		||||
        this.observer && this.observer.off();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
<div [class.mm-loading-container]="loading">
 | 
			
		||||
    <iframe #iframe [hidden]="loading" class="mm-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl"></iframe>
 | 
			
		||||
<div [class.core-loading-container]="loading">
 | 
			
		||||
    <iframe #iframe [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl"></iframe>
 | 
			
		||||
    <ion-spinner *ngIf="loading"></ion-spinner>
 | 
			
		||||
</div>
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
<div class="mm-loading-container mm-animate-show-hide" *ngIf="!hideUntil">
 | 
			
		||||
    <span class="mm-loading-spinner">
 | 
			
		||||
<!-- @TODO: Add show hide animation -->
 | 
			
		||||
<div class="core-loading-container" *ngIf="!hideUntil">
 | 
			
		||||
    <span class="core-loading-spinner">
 | 
			
		||||
        <ion-spinner></ion-spinner>
 | 
			
		||||
        <p class="mm-loading-message" *ngIf="message">{{message}}</p>
 | 
			
		||||
        <p class="core-loading-message" *ngIf="message">{{message}}</p>
 | 
			
		||||
    </span>
 | 
			
		||||
</div>
 | 
			
		||||
<ng-content class="mm-loading-content mm-animate-show-hide" *ngIf="hideUntil">
 | 
			
		||||
<ng-content class="core-loading-content" *ngIf="hideUntil">
 | 
			
		||||
</ng-content>
 | 
			
		||||
@ -1,26 +1,37 @@
 | 
			
		||||
core-loading {
 | 
			
		||||
    .mm-loading-container {
 | 
			
		||||
    .core-loading-container {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        padding-top: 10px;
 | 
			
		||||
        clear: both;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mm-loading-content {
 | 
			
		||||
    .core-loading-content {
 | 
			
		||||
        padding-bottom: 1px; /* This makes height be real */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.mm-loading-noheight .mm-loading-content {
 | 
			
		||||
    &.core-loading-noheight .core-loading-content {
 | 
			
		||||
        height: auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scroll-content > .padding > core-loading > .mm-loading-container,
 | 
			
		||||
ion-content[padding] > .scroll-content > core-loading > .mm-loading-container,
 | 
			
		||||
.mm-loading-center .mm-loading-container {
 | 
			
		||||
.scroll-content > .padding > core-loading > .core-loading-container,
 | 
			
		||||
ion-content[padding] > .scroll-content > core-loading > .core-loading-container,
 | 
			
		||||
.core-loading-center .core-loading-container {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    display: table;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    clear: both;
 | 
			
		||||
 | 
			
		||||
    .mm-loading-spinner {
 | 
			
		||||
    .core-loading-spinner {
 | 
			
		||||
        display: table-cell;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								src/components/progress-bar/progress-bar.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,8 @@
 | 
			
		||||
<div *ngIf="progress >= 0">
 | 
			
		||||
    <progress max="100" [value]="progress">
 | 
			
		||||
        <div class="progress-bar-fallback" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="progress">
 | 
			
		||||
            <span [style.width]="width"></span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </progress>
 | 
			
		||||
    <span class="core-progress-text">{{ 'core.percentagenumber' | translate: {$a: text} }}</span>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										53
									
								
								src/components/progress-bar/progress-bar.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,53 @@
 | 
			
		||||
$core-progress-bar-height: 5px !default;
 | 
			
		||||
 | 
			
		||||
core-progress-bar {
 | 
			
		||||
    padding-right: 55px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: block;
 | 
			
		||||
    @extend .clearfix;
 | 
			
		||||
 | 
			
		||||
    .core-progress-text {
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
        line-height: normal;
 | 
			
		||||
        font-size: 1.4rem;
 | 
			
		||||
        color: $gray-darker;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        top: -6px;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    progress {
 | 
			
		||||
        -webkit-appearance: none;
 | 
			
		||||
        appearance: none;
 | 
			
		||||
        height: $core-progress-bar-height;
 | 
			
		||||
        margin: 15px 0;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        display: block;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
 | 
			
		||||
        .progress-bar-fallback,
 | 
			
		||||
        &[value]::-webkit-progress-bar {
 | 
			
		||||
            background-color: $gray-light;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .progress-bar-fallback span,
 | 
			
		||||
        &[value]::-webkit-progress-value {
 | 
			
		||||
            background-color: $core-color-light;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .progress-bar-fallback {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: $core-progress-bar-height;
 | 
			
		||||
            display: block;
 | 
			
		||||
            position: relative;
 | 
			
		||||
 | 
			
		||||
            span {
 | 
			
		||||
                height: $core-progress-bar-height;
 | 
			
		||||
                display: block;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/components/progress-bar/progress-bar.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,68 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, OnChanges, SimpleChange, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to show a progress bar and its value.
 | 
			
		||||
 *
 | 
			
		||||
 * Example usage:
 | 
			
		||||
 * <core-progress-bar [progress]="percentage"></core-progress-bar>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-progress-bar',
 | 
			
		||||
    templateUrl: 'progress-bar.html',
 | 
			
		||||
    changeDetection: ChangeDetectionStrategy.OnPush
 | 
			
		||||
})
 | 
			
		||||
export class CoreProgressBarComponent implements OnChanges {
 | 
			
		||||
    @Input() progress: number|string; // Percentage from 0 to 100.
 | 
			
		||||
    @Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
 | 
			
		||||
    width: SafeStyle;
 | 
			
		||||
    protected textSupplied = false;
 | 
			
		||||
 | 
			
		||||
    constructor(private sanitizer: DomSanitizer) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: {[name: string]: SimpleChange}) {
 | 
			
		||||
        if (changes.text && typeof changes.text.currentValue != 'undefined') {
 | 
			
		||||
            // User provided a custom text, don't use default.
 | 
			
		||||
            this.textSupplied = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (changes.progress) {
 | 
			
		||||
            // Progress has changed.
 | 
			
		||||
            if (typeof this.progress == 'string') {
 | 
			
		||||
                this.progress = parseInt(this.progress, 10);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.progress < 0 || isNaN(this.progress)) {
 | 
			
		||||
                this.progress = -1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.progress != -1) {
 | 
			
		||||
                // Remove decimals.
 | 
			
		||||
                this.progress = Math.floor(this.progress);
 | 
			
		||||
 | 
			
		||||
                if (!this.textSupplied) {
 | 
			
		||||
                    this.text = String(this.progress);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.width = this.sanitizer.bypassSecurityTrustStyle(this.progress + '%');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/components/search-box/search-box.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,10 @@
 | 
			
		||||
<ion-card>
 | 
			
		||||
    <form #f="ngForm" (ngSubmit)="submitForm(f.value.search)">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-input type="text" name="search" ngModel [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus"></ion-input>
 | 
			
		||||
            <button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="!f.value.search || (f.value.search.length < lengthCheck)">
 | 
			
		||||
                <ion-icon name="search"></ion-icon>
 | 
			
		||||
            </button>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </form>
 | 
			
		||||
</ion-card>
 | 
			
		||||
							
								
								
									
										9
									
								
								src/components/search-box/search-box.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,9 @@
 | 
			
		||||
core-search-box {
 | 
			
		||||
    .button.item-button[icon-only] {
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: ($content-padding / 2) $content-padding;
 | 
			
		||||
    }
 | 
			
		||||
    .item.item-input.item-block .item-inner ion-input {
 | 
			
		||||
       border-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								src/components/search-box/search-box.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,67 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, Output, EventEmitter, OnInit } from '@angular/core';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display a "search box".
 | 
			
		||||
 *
 | 
			
		||||
 * @description
 | 
			
		||||
 * This component will display a standalone search box with its search button in order to have a better UX.
 | 
			
		||||
 *
 | 
			
		||||
 * Example usage:
 | 
			
		||||
 * <core-search-box (onSubmit)="search($event)" [placeholder]="'core.courses.search' | translate"
 | 
			
		||||
 *     [searchLabel]="'core.courses.search' | translate" autoFocus="true"></core-search-box>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-search-box',
 | 
			
		||||
    templateUrl: 'search-box.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreSearchBoxComponent implements OnInit {
 | 
			
		||||
    @Input() initialValue?: string = ''; // Initial value for search text.
 | 
			
		||||
    @Input() searchLabel?: string ; // Label to be used on action button.
 | 
			
		||||
    @Input() placeholder?: string; // Placeholder text for search text input.
 | 
			
		||||
    @Input() autocorrect?: string = 'on'; // Enables/disable Autocorrection on search text input.
 | 
			
		||||
    @Input() spellcheck?: string|boolean = true; // Enables/disable Spellchecker on search text input.
 | 
			
		||||
    @Input() autoFocus?: string|boolean; // Enables/disable Autofocus when entering view.
 | 
			
		||||
    @Input() lengthCheck?: number = 3; // Check value length before submit. If 0, any string will be submitted.
 | 
			
		||||
    @Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
 | 
			
		||||
 | 
			
		||||
    constructor(private translate: TranslateService, private utils: CoreUtilsProvider) {
 | 
			
		||||
        this.onSubmit = new EventEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        this.searchLabel = this.searchLabel || this.translate.instant('core.search');
 | 
			
		||||
        this.placeholder = this.placeholder || this.translate.instant('core.search');
 | 
			
		||||
        this.spellcheck = this.utils.isTrueOrOne(this.spellcheck);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Form submitted.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} value Entered value.
 | 
			
		||||
     */
 | 
			
		||||
    submitForm(value: string) {
 | 
			
		||||
        if (value.length < this.lengthCheck) {
 | 
			
		||||
            // The view should handle this case, but we check it here too just in case.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.onSubmit.emit(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -16,6 +16,11 @@ core-show-password {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        margin-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-ioninput-password {
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
        padding-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.md {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								src/core/courses/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,48 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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 { NgModule } from '@angular/core';
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { IonicModule } from 'ionic-angular';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
import { CoreComponentsModule } from '../../../components/components.module';
 | 
			
		||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
 | 
			
		||||
import { CorePipesModule } from '../../../pipes/pipes.module';
 | 
			
		||||
import { CoreCoursesCourseProgressComponent } from '../components/course-progress/course-progress';
 | 
			
		||||
import { CoreCoursesCourseListItemComponent } from '../components/course-list-item/course-list-item';
 | 
			
		||||
import { CoreCoursesOverviewEventsComponent } from '../components/overview-events/overview-events';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCoursesCourseProgressComponent,
 | 
			
		||||
        CoreCoursesCourseListItemComponent,
 | 
			
		||||
        CoreCoursesOverviewEventsComponent
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreComponentsModule,
 | 
			
		||||
        CoreDirectivesModule,
 | 
			
		||||
        CorePipesModule
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        CoreCoursesCourseProgressComponent,
 | 
			
		||||
        CoreCoursesCourseListItemComponent,
 | 
			
		||||
        CoreCoursesOverviewEventsComponent
 | 
			
		||||
    ]
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesComponentsModule {}
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
<a ion-item text-wrap (click)="openCourse(course)" [attr.disabled]="course.visible == 0 ? true : null" [attr.detail-none]="course.visible == 0 ? true : null" [title]="course.fullname">
 | 
			
		||||
    <ion-icon name="ionic" item-start></ion-icon>
 | 
			
		||||
    <h2><core-format-text [text]="course.fullname"></core-format-text></h2>
 | 
			
		||||
    <div item-end>
 | 
			
		||||
        <span *ngIf="!course.isEnrolled">
 | 
			
		||||
            <span ion-button icon-only clear color="gray" *ngFor="let instance of course.enrollment" [attr.aria-label]=" instance.name | translate">
 | 
			
		||||
                <ion-icon *ngIf="instance.icon" [name]="instance.icon"></ion-icon>
 | 
			
		||||
                <img *ngIf="instance.img && !instance.icon" [src]="instance.img" class="core-course-enrollment-img">
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
</a>
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
core-courses-course-list-item {
 | 
			
		||||
    .core-course-enrollment-img {
 | 
			
		||||
        max-width: 16px;
 | 
			
		||||
        max-height: 16px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,82 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, OnInit } from '@angular/core';
 | 
			
		||||
import { NavController } from 'ionic-angular';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { CoreCoursesProvider } from '../../providers/courses';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This directive is meant to display an item for a list of courses.
 | 
			
		||||
 *
 | 
			
		||||
 * Example usage:
 | 
			
		||||
 *
 | 
			
		||||
 * <core-courses-course-list-item [course]="course"></core-courses-course-list-item>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-courses-course-list-item',
 | 
			
		||||
    templateUrl: 'course-list-item.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesCourseListItemComponent implements OnInit {
 | 
			
		||||
    @Input() course: any; // The course to render.
 | 
			
		||||
 | 
			
		||||
    constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        // Check if the user is enrolled in the course.
 | 
			
		||||
        return this.coursesProvider.getUserCourse(this.course.id).then(() => {
 | 
			
		||||
            this.course.isEnrolled = true;
 | 
			
		||||
        }).catch(() => {
 | 
			
		||||
            this.course.isEnrolled = false;
 | 
			
		||||
            this.course.enrollment = [];
 | 
			
		||||
 | 
			
		||||
            this.course.enrollmentmethods.forEach((instance) => {
 | 
			
		||||
                if (instance === 'self') {
 | 
			
		||||
                    this.course.enrollment.push({
 | 
			
		||||
                        name: this.translate.instant('core.courses.selfenrolment'),
 | 
			
		||||
                        icon: 'unlock'
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (instance === 'guest') {
 | 
			
		||||
                    this.course.enrollment.push({
 | 
			
		||||
                        name: this.translate.instant('core.courses.allowguests'),
 | 
			
		||||
                        icon: 'person'
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (instance === 'paypal') {
 | 
			
		||||
                    this.course.enrollment.push({
 | 
			
		||||
                        name: this.translate.instant('core.courses.paypalaccepted'),
 | 
			
		||||
                        img: 'assets/img/icons/paypal.png'
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.course.enrollment.length == 0) {
 | 
			
		||||
                this.course.enrollment.push({
 | 
			
		||||
                    name: this.translate.instant('core.courses.notenrollable'),
 | 
			
		||||
                    icon: 'lock'
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open a course.
 | 
			
		||||
     */
 | 
			
		||||
    openCourse(course) {
 | 
			
		||||
        this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
<ion-card>
 | 
			
		||||
    <a ion-item text-wrap detail-none (click)="openCourse(course)" [title]="course.fullname">
 | 
			
		||||
        <h2 float-start><core-format-text [text]="course.fullname"></core-format-text></h2>
 | 
			
		||||
        <!-- Download course. -->
 | 
			
		||||
        <!--<button [hidden]="!downloadButton.isDownload" ion-button icon-only clear color="dark" float-end>
 | 
			
		||||
            <ion-icon name="cloud-download"></ion-icon>
 | 
			
		||||
        </button>-->
 | 
			
		||||
        <!-- Download course spinner. -->
 | 
			
		||||
        <!-- <ion-spinner *ngIf="prefetchCourseIcon == 'spinner'" class="core-course-download-spinner"></ion-spinner> -->
 | 
			
		||||
    </a>
 | 
			
		||||
    <ion-item text-wrap *ngIf="course.summary && course.summary.length">
 | 
			
		||||
        <p>
 | 
			
		||||
            <summary>
 | 
			
		||||
                <core-format-text [text]="course.summary" maxHeight="20"></core-format-text>
 | 
			
		||||
            </summary>
 | 
			
		||||
        </p>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ion-item *ngIf="course.progress != null && course.progress >= 0">
 | 
			
		||||
        <core-progress-bar [progress]="course.progress"></core-progress-bar>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ng-content></ng-content>
 | 
			
		||||
</ion-card>
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
core-courses-course-progress.core-courseoverview {
 | 
			
		||||
    @media (max-width: 576px) {
 | 
			
		||||
        ion-card.card {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            border-radius: 0;
 | 
			
		||||
            box-shadow: none;
 | 
			
		||||
            border-bottom: 1px solid $list-border-color;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100% !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, OnInit } from '@angular/core';
 | 
			
		||||
import { NavController } from 'ionic-angular';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This component is meant to display a course for a list of courses with progress.
 | 
			
		||||
 *
 | 
			
		||||
 * Example usage:
 | 
			
		||||
 *
 | 
			
		||||
 * <core-courses-course-progress [course]="course">
 | 
			
		||||
 * </core-courses-course-progress>
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-courses-course-progress',
 | 
			
		||||
    templateUrl: 'course-progress.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesCourseProgressComponent implements OnInit {
 | 
			
		||||
    @Input() course: any; // The course to render.
 | 
			
		||||
 | 
			
		||||
    isDownloading: boolean;
 | 
			
		||||
 | 
			
		||||
    protected obsStatus;
 | 
			
		||||
    protected downloadText;
 | 
			
		||||
    protected downloadingText;
 | 
			
		||||
    protected downloadButton = {
 | 
			
		||||
        isDownload: true,
 | 
			
		||||
        className: 'core-download-course',
 | 
			
		||||
        priority: 1000
 | 
			
		||||
    };
 | 
			
		||||
    protected buttons;
 | 
			
		||||
 | 
			
		||||
    constructor(private navCtrl: NavController, private translate: TranslateService) {
 | 
			
		||||
        this.downloadText = this.translate.instant('core.course.downloadcourse');
 | 
			
		||||
        this.downloadingText = this.translate.instant('core.downloading');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        // @todo: Handle course prefetch.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open a course.
 | 
			
		||||
     */
 | 
			
		||||
    openCourse(course) {
 | 
			
		||||
        this.navCtrl.push('CoreCourseSectionPage', {course: course});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
<ng-template #eventTemplate let-event="event">
 | 
			
		||||
    <a ion-item core-link text-wrap detail-none captureLink="true" class="core-course-module-handler item-media" [href]="event.url" [title]="event.name" [class.item-badge-right-phone]="event.action && event.action.showitemcount">
 | 
			
		||||
        <img [src]="event.iconUrl" core-external-content alt="" role="presentation" *ngIf="event.iconUrl">
 | 
			
		||||
        <p class="item-heading"><core-format-text [text]="event.name"></core-format-text></p>
 | 
			
		||||
        <p>{{event.timesort * 1000 | coreFormatDate:"dfmediumdate" }} <core-format-text *ngIf="showCourse" [text]="event.course.fullnamedisplay"></core-format-text></p>
 | 
			
		||||
        <button ion-button clear item-end class="hidden-phone" (click)="action($event, event.action.url)" [title]="event.action.name" [disabled]="!event.action.actionable" *ngIf="event.action">
 | 
			
		||||
            {{event.action.name}}
 | 
			
		||||
            <ion-badge item-end margin-left *ngIf="event.action.showitemcount">{{event.action.itemcount}}</ion-badge>
 | 
			
		||||
        </button>
 | 
			
		||||
        <ion-badge class="hidden-tablet" item-end *ngIf="event.action.showitemcount">{{event.action.itemcount}}</ion-badge>
 | 
			
		||||
    </a>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ion-item-group *ngIf="recentlyOverdue.length > 0">
 | 
			
		||||
    <ion-item-divider color="danger">{{ 'core.courses.recentlyoverdue' | translate }}</ion-item-divider>
 | 
			
		||||
    <ng-container *ngFor="let event of recentlyOverdue">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ion-item-group>
 | 
			
		||||
 | 
			
		||||
<ion-item-group *ngIf="next7Days.length > 0">
 | 
			
		||||
    <ion-item-divider color="light">{{ 'core.courses.next7days' | translate }}</ion-item-divider>
 | 
			
		||||
    <ng-container *ngFor="let event of next7Days">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ion-item-group>
 | 
			
		||||
 | 
			
		||||
<ion-item-group *ngIf="next30Days.length > 0">
 | 
			
		||||
    <ion-item-divider color="light">{{ 'core.courses.next30days' | translate }}</ion-item-divider>
 | 
			
		||||
    <ng-container *ngFor="let event of next30Days">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ion-item-group>
 | 
			
		||||
 | 
			
		||||
<ion-item-group *ngIf="future.length > 0">
 | 
			
		||||
    <ion-item-divider color="light">{{ 'core.courses.future' | translate }}</ion-item-divider>
 | 
			
		||||
    <ng-container *ngFor="let event of future">
 | 
			
		||||
        <ng-container *ngTemplateOutlet="eventTemplate; context: {event: event}"></ng-container>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ion-item-group>
 | 
			
		||||
 | 
			
		||||
<div padding text-center *ngIf="canLoadMore && !empty">
 | 
			
		||||
    <!-- Button and spinner to show more attempts. -->
 | 
			
		||||
    <button *ngIf="!loadingMore" ion-button block (click)="loadMoreEvents()">{{ 'core.loadmore' | translate }}</button>
 | 
			
		||||
    <ion-spinner *ngIf="loadingMore"></ion-spinner>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<core-empty-box *ngIf="empty && showCourse" image="assets/img/icons/activities.svg" [message]="'core.courses.noevents' | translate"></core-empty-box>
 | 
			
		||||
<core-empty-box *ngIf="empty && !showCourse" [message]="'core.courses.noevents' | translate"></core-empty-box>
 | 
			
		||||
@ -0,0 +1,24 @@
 | 
			
		||||
core-courses-course-progress {
 | 
			
		||||
 | 
			
		||||
    .core-course-module-handler.item-md.item-block .item-inner {
 | 
			
		||||
      border-bottom: 1px solid $list-md-border-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-module-handler.item-ios.item-block .item-inner {
 | 
			
		||||
      border-bottom: $hairlines-width solid $list-ios-border-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-module-handler.item-wp.item-block .item-inner {
 | 
			
		||||
      border-bottom: 1px solid $list-wp-border-color;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-module-handler.item:last-child .item-inner {
 | 
			
		||||
        border-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-module-handler.item .item-heading:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										135
									
								
								src/core/courses/components/overview-events/overview-events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,135 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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, Output, OnChanges, EventEmitter, SimpleChange } from '@angular/core';
 | 
			
		||||
import { CoreSitesProvider } from '../../../../providers/sites';
 | 
			
		||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
 | 
			
		||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
 | 
			
		||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
 | 
			
		||||
import * as moment from 'moment';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Directive to render a list of events in course overview.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-courses-overview-events',
 | 
			
		||||
    templateUrl: 'overview-events.html'
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesOverviewEventsComponent implements OnChanges {
 | 
			
		||||
    @Input() events: any[]; // The events to render.
 | 
			
		||||
    @Input() showCourse?: boolean|string; // Whether to show the course name.
 | 
			
		||||
    @Input() canLoadMore?: boolean; // Whether more events can be loaded.
 | 
			
		||||
    @Output() loadMore: EventEmitter<void>; // Notify that more events should be loaded.
 | 
			
		||||
 | 
			
		||||
    empty: boolean;
 | 
			
		||||
    loadingMore: boolean;
 | 
			
		||||
    recentlyOverdue: any[] = [];
 | 
			
		||||
    today: any[] = [];
 | 
			
		||||
    next7Days: any[] = [];
 | 
			
		||||
    next30Days: any[] = [];
 | 
			
		||||
    future: any[] = [];
 | 
			
		||||
 | 
			
		||||
    constructor(private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider,
 | 
			
		||||
            private domUtils: CoreDomUtilsProvider, private sitesProvider: CoreSitesProvider) {
 | 
			
		||||
        this.loadMore = new EventEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: {[name: string]: SimpleChange}) {
 | 
			
		||||
        this.showCourse = this.utils.isTrueOrOne(this.showCourse);
 | 
			
		||||
 | 
			
		||||
        if (changes.events) {
 | 
			
		||||
            this.updateEvents();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filter the events by time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {number} start Number of days to start getting events from today. E.g. -1 will get events from yesterday.
 | 
			
		||||
     * @param {number} [end] Number of days after the start.
 | 
			
		||||
     */
 | 
			
		||||
    protected filterEventsByTime(start: number, end?: number) {
 | 
			
		||||
        start = moment().add(start, 'days').unix();
 | 
			
		||||
        end = typeof end != 'undefined' ? moment().add(end, 'days').unix() : end;
 | 
			
		||||
 | 
			
		||||
        return this.events.filter((event) => {
 | 
			
		||||
            if (end) {
 | 
			
		||||
                return start <= event.timesort && event.timesort < end;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return start <= event.timesort;
 | 
			
		||||
        }).map((event) => {
 | 
			
		||||
            // @todo: event.iconUrl = this.courseProvider.getModuleIconSrc(event.icon.component);
 | 
			
		||||
            return event;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the events displayed.
 | 
			
		||||
     */
 | 
			
		||||
    protected updateEvents() {
 | 
			
		||||
        this.empty = !this.events || this.events.length <= 0;
 | 
			
		||||
        if (!this.empty) {
 | 
			
		||||
            this.recentlyOverdue = this.filterEventsByTime(-14, 0);
 | 
			
		||||
            this.today = this.filterEventsByTime(0, 1);
 | 
			
		||||
            this.next7Days = this.filterEventsByTime(1, 7);
 | 
			
		||||
            this.next30Days = this.filterEventsByTime(7, 30);
 | 
			
		||||
            this.future = this.filterEventsByTime(30);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load more events clicked.
 | 
			
		||||
     */
 | 
			
		||||
    loadMoreEvents() {
 | 
			
		||||
        this.loadingMore = true;
 | 
			
		||||
        this.loadMore.emit();
 | 
			
		||||
        // this.loadMore().finally(function() {
 | 
			
		||||
        //     scope.loadingMore = false;
 | 
			
		||||
        // });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Action clicked.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Event} e Click event.
 | 
			
		||||
     * @param {string} url Url of the action.
 | 
			
		||||
     */
 | 
			
		||||
    action(e: Event, url: string) {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        // Fix URL format.
 | 
			
		||||
        url = this.textUtils.decodeHTMLEntities(url);
 | 
			
		||||
 | 
			
		||||
        let modal = this.domUtils.showModalLoading();
 | 
			
		||||
        this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url).finally(() => {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // @todo
 | 
			
		||||
        // $mmContentLinksHelper.handleLink(url).then((treated) => {
 | 
			
		||||
        //     if (!treated) {
 | 
			
		||||
                // return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url);
 | 
			
		||||
            // }
 | 
			
		||||
        // }).finally(() => {
 | 
			
		||||
        //     modal.dismiss();
 | 
			
		||||
        // });
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/core/courses/courses.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,38 @@
 | 
			
		||||
// (C) Copyright 2015 Martin Dougiamas
 | 
			
		||||
//
 | 
			
		||||
// 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 { NgModule } from '@angular/core';
 | 
			
		||||
import { CoreCoursesProvider } from './providers/courses';
 | 
			
		||||
import { CoreCoursesMainMenuHandler } from './providers/handlers';
 | 
			
		||||
import { CoreCoursesMyOverviewProvider } from './providers/my-overview';
 | 
			
		||||
import { CoreCoursesDelegate } from './providers/delegate';
 | 
			
		||||
import { CoreMainMenuDelegate } from '../mainmenu/providers/delegate';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [],
 | 
			
		||||
    imports: [
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        CoreCoursesProvider,
 | 
			
		||||
        CoreCoursesMainMenuHandler,
 | 
			
		||||
        CoreCoursesMyOverviewProvider,
 | 
			
		||||
        CoreCoursesDelegate
 | 
			
		||||
    ],
 | 
			
		||||
    exports: []
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesModule {
 | 
			
		||||
    constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: CoreCoursesMainMenuHandler) {
 | 
			
		||||
        mainMenuDelegate.registerHandler(mainMenuHandler);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/core/courses/lang/ar.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "يسمح للمستخدمين الضيوف بالدخول إلى هذا المقرر الدراسي",
 | 
			
		||||
    "availablecourses": "المقررات الدراسية المتاحة",
 | 
			
		||||
    "categories": "تصنيفات المقررات الدراسية",
 | 
			
		||||
    "courses": "المقررات الدراسية",
 | 
			
		||||
    "enrolme": "سجلني",
 | 
			
		||||
    "frontpage": "الصفحة الرئيسية",
 | 
			
		||||
    "mycourses": "مقرراتي الدراسية",
 | 
			
		||||
    "nocourses": "لا يوجد معلومات لمقرر دراسي ليتم اظهرها",
 | 
			
		||||
    "nocoursesyet": "لا توجد مقررات دراسية لهذه الفئة",
 | 
			
		||||
    "nosearchresults": "لا توجد نتائج لهذا البحث",
 | 
			
		||||
    "notenroled": "أنت لست مسجلاً كطالب في هذا المقرر",
 | 
			
		||||
    "password": "كلمة المرور",
 | 
			
		||||
    "paymentrequired": "هذا المقرر الدراسي غير مجانين لذا يجب دفع القيمة للدخول.",
 | 
			
		||||
    "paypalaccepted": "تم قبول التبرع المدفوع",
 | 
			
		||||
    "search": "بحث",
 | 
			
		||||
    "searchcourses": "بحث مقررات دراسية",
 | 
			
		||||
    "sendpaymentbutton": "ارسل القيمة المدفوعة عن طريق التبرع"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								src/core/courses/lang/bg.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "В този курс могат да влизат гости",
 | 
			
		||||
    "availablecourses": "Налични курсове",
 | 
			
		||||
    "categories": "Категории курсове",
 | 
			
		||||
    "courses": "Курсове",
 | 
			
		||||
    "enrolme": "Запишете ме",
 | 
			
		||||
    "errorloadcourses": "Грешка при зареждането на курсовете.",
 | 
			
		||||
    "frontpage": "Заглавна страница",
 | 
			
		||||
    "mycourses": "Моите курсове",
 | 
			
		||||
    "nocourses": "Няма информация за курса, която да бъде показана.",
 | 
			
		||||
    "nocoursesyet": "Няма курсове в тази категория",
 | 
			
		||||
    "nosearchresults": "Няма открити резултати за Вашето търсене",
 | 
			
		||||
    "password": "Ключ за записване",
 | 
			
		||||
    "search": "Търсене",
 | 
			
		||||
    "searchcourses": "Търсене на курсове"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/ca.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Aquest curs permet entrar als usuaris visitants",
 | 
			
		||||
    "availablecourses": "Cursos disponibles",
 | 
			
		||||
    "cannotretrievemorecategories": "No es poden recuperar categories més enllà del nivell {{$a}}.",
 | 
			
		||||
    "categories": "Categories de cursos",
 | 
			
		||||
    "confirmselfenrol": "Segur que voleu autoinscriure-us en aquest curs?",
 | 
			
		||||
    "courses": "Cursos",
 | 
			
		||||
    "enrolme": "Inscriu-me",
 | 
			
		||||
    "errorloadcategories": "S'ha produït un error en carregar les categories.",
 | 
			
		||||
    "errorloadcourses": "S'ha produït un error carregant els cursos.",
 | 
			
		||||
    "errorsearching": "S'ha produït un error durant la cerca.",
 | 
			
		||||
    "errorselfenrol": "S'ha produït un error durant l'autoinscripció.",
 | 
			
		||||
    "filtermycourses": "Filtrar els meus cursos",
 | 
			
		||||
    "frontpage": "Pàgina principal",
 | 
			
		||||
    "mycourses": "Els meus cursos",
 | 
			
		||||
    "nocourses": "No hi ha informació de cursos per mostrar.",
 | 
			
		||||
    "nocoursesyet": "No hi ha cursos en aquesta categoria",
 | 
			
		||||
    "nosearchresults": "La cerca no ha obtingut resultats",
 | 
			
		||||
    "notenroled": "No us heu inscrit en aquest curs",
 | 
			
		||||
    "notenrollable": "No podeu autoinscriure-us en aquest curs.",
 | 
			
		||||
    "password": "Contrasenya",
 | 
			
		||||
    "paymentrequired": "Aquest curs requereix pagament.",
 | 
			
		||||
    "paypalaccepted": "S'accepten pagaments via PayPal",
 | 
			
		||||
    "search": "Cerca...",
 | 
			
		||||
    "searchcourses": "Cerca cursos",
 | 
			
		||||
    "searchcoursesadvice": "Podeu fer servir el botó de cercar cursos per accedir als cursos com a convidat o autoinscriure-us en cursos que ho permetin.",
 | 
			
		||||
    "selfenrolment": "Autoinscripció",
 | 
			
		||||
    "sendpaymentbutton": "Envia pagament via Paypal",
 | 
			
		||||
    "totalcoursesearchresults": "Total de cursos: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/cs.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Tento kurz je otevřen i pro hosty",
 | 
			
		||||
    "availablecourses": "Dostupné kurzy",
 | 
			
		||||
    "cannotretrievemorecategories": "Kategorie hlubší než úroveň {{$a}} nelze načíst.",
 | 
			
		||||
    "categories": "Kategorie kurzů",
 | 
			
		||||
    "confirmselfenrol": "Jste si jisti, že chcete zapsat se do tohoto kurzu?",
 | 
			
		||||
    "courses": "Kurzy",
 | 
			
		||||
    "enrolme": "Zapsat se do kurzu",
 | 
			
		||||
    "errorloadcategories": "Při načítání kategorií došlo k chybě.",
 | 
			
		||||
    "errorloadcourses": "Při načítání kurzů došlo k chybě.",
 | 
			
		||||
    "errorsearching": "Při vyhledávání došlo k chybě.",
 | 
			
		||||
    "errorselfenrol": "Při zápisu sebe sama došlo k chybě.",
 | 
			
		||||
    "filtermycourses": "Filtrovat mé kurzy",
 | 
			
		||||
    "frontpage": "Titulní stránka",
 | 
			
		||||
    "mycourses": "Moje kurzy",
 | 
			
		||||
    "nocourses": "Žádné dostupné informace o kurzech",
 | 
			
		||||
    "nocoursesyet": "Žádný kurz v této kategorii",
 | 
			
		||||
    "nosearchresults": "Vaše vyhledávání nepřineslo žádný výsledek",
 | 
			
		||||
    "notenroled": "Nejste zapsáni v tomto kurzu",
 | 
			
		||||
    "notenrollable": "Do tohoto kurzu se nemůžete sami zapsat.",
 | 
			
		||||
    "password": "Heslo",
 | 
			
		||||
    "paymentrequired": "Tento kurz je placený",
 | 
			
		||||
    "paypalaccepted": "Platby přes PayPal přijímány",
 | 
			
		||||
    "search": "Hledat",
 | 
			
		||||
    "searchcourses": "Vyhledat kurzy",
 | 
			
		||||
    "searchcoursesadvice": "Můžete použít tlačítko Vyhledat kurzy, pracovat jako host nebo se zapsat do kurzů, které to umožňují.",
 | 
			
		||||
    "selfenrolment": "Zápis sebe sama",
 | 
			
		||||
    "sendpaymentbutton": "Poslat platbu přes službu PayPal",
 | 
			
		||||
    "totalcoursesearchresults": "Celkem kurzů:  {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								src/core/courses/lang/da.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,28 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Dette kursus tillader gæster",
 | 
			
		||||
    "availablecourses": "Tilgængelige kurser",
 | 
			
		||||
    "categories": "Kursuskategorier",
 | 
			
		||||
    "confirmselfenrol": "Er du sikker på at du ønsker at tilmelde dig dette kursus?",
 | 
			
		||||
    "courses": "Alle kurser",
 | 
			
		||||
    "enrolme": "Tilmeld mig",
 | 
			
		||||
    "errorloadcourses": "En fejl opstod ved indlæsning af kurset.",
 | 
			
		||||
    "errorsearching": "En fejl opstod under søgning.",
 | 
			
		||||
    "errorselfenrol": "En fejl opstod under selvtilmelding.",
 | 
			
		||||
    "filtermycourses": "Filtrer mit kursus",
 | 
			
		||||
    "frontpage": "Forside",
 | 
			
		||||
    "mycourses": "Mine kurser",
 | 
			
		||||
    "nocourses": "Du er ikke tilmeldt nogen kurser.",
 | 
			
		||||
    "nocoursesyet": "Der er ingen kurser i denne kategori",
 | 
			
		||||
    "nosearchresults": "Der var ingen beskeder der opfyldte søgekriteriet",
 | 
			
		||||
    "notenroled": "Du er ikke tilmeldt dette kursus",
 | 
			
		||||
    "notenrollable": "Du kan ikke selv tilmelde dig dette kursus.",
 | 
			
		||||
    "password": "Adgangskode",
 | 
			
		||||
    "paymentrequired": "Dette kursus kræver betaling for tilmelding.",
 | 
			
		||||
    "paypalaccepted": "PayPal-betalinger er velkomne",
 | 
			
		||||
    "search": "Søg...",
 | 
			
		||||
    "searchcourses": "Søg efter kurser",
 | 
			
		||||
    "searchcoursesadvice": "Du kan bruge knappen kursussøgning for at få adgang som gæst eller tilmelde dig kurser der tillader det.",
 | 
			
		||||
    "selfenrolment": "Selvtilmelding",
 | 
			
		||||
    "sendpaymentbutton": "Send betaling via PayPal",
 | 
			
		||||
    "totalcoursesearchresults": "Kurser i alt: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/de-du.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Dieser Kurs erlaubt einen Gastzugang.",
 | 
			
		||||
    "availablecourses": "Kursliste",
 | 
			
		||||
    "cannotretrievemorecategories": "Kursbereiche tiefer als Level {{$a}} können nicht abgerufen werden.",
 | 
			
		||||
    "categories": "Kursbereiche",
 | 
			
		||||
    "confirmselfenrol": "Möchtest du dich selbst in diesen Kurs einschreiben?",
 | 
			
		||||
    "courses": "Kurse",
 | 
			
		||||
    "enrolme": "Einschreiben",
 | 
			
		||||
    "errorloadcategories": "Fehler beim Laden von Kursbereichen",
 | 
			
		||||
    "errorloadcourses": "Fehler beim Laden von Kursen",
 | 
			
		||||
    "errorsearching": "Fehler beim Suchen",
 | 
			
		||||
    "errorselfenrol": "Fehler bei der Selbsteinschreibung",
 | 
			
		||||
    "filtermycourses": "Meine Kurse filtern",
 | 
			
		||||
    "frontpage": "Startseite",
 | 
			
		||||
    "mycourses": "Meine Kurse",
 | 
			
		||||
    "nocourses": "Keine Kurse",
 | 
			
		||||
    "nocoursesyet": "Keine Kurse in diesem Kursbereich",
 | 
			
		||||
    "nosearchresults": "Keine Ergebnisse",
 | 
			
		||||
    "notenroled": "Sie sind nicht in diesen Kurs eingeschrieben",
 | 
			
		||||
    "notenrollable": "Du kannst dich nicht selbst in diesen Kurs einschreiben.",
 | 
			
		||||
    "password": "Öffentliches Kennwort",
 | 
			
		||||
    "paymentrequired": "Dieser Kurs ist gebührenpflichtig. Bitte bezahle die Teilnahmegebühr, um im Kurs eingeschrieben zu werden.",
 | 
			
		||||
    "paypalaccepted": "PayPal-Zahlungen möglich",
 | 
			
		||||
    "search": "Suchen",
 | 
			
		||||
    "searchcourses": "Kurse suchen",
 | 
			
		||||
    "searchcoursesadvice": "Du kannst Kurse suchen, um als Gast teilzunehmen oder dich selbst einzuschreiben, falls dies erlaubt ist.",
 | 
			
		||||
    "selfenrolment": "Selbsteinschreibung",
 | 
			
		||||
    "sendpaymentbutton": "Zahlung über PayPal",
 | 
			
		||||
    "totalcoursesearchresults": "Alle Kurse: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/de.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Dieser Kurs erlaubt einen Gastzugang.",
 | 
			
		||||
    "availablecourses": "Kursliste",
 | 
			
		||||
    "cannotretrievemorecategories": "Kursbereiche tiefer als Level {{$a}} können nicht abgerufen werden.",
 | 
			
		||||
    "categories": "Kursbereiche",
 | 
			
		||||
    "confirmselfenrol": "Möchten Sie sich selbst in diesen Kurs einschreiben?",
 | 
			
		||||
    "courses": "Kurse",
 | 
			
		||||
    "enrolme": "Einschreiben",
 | 
			
		||||
    "errorloadcategories": "Fehler beim Laden von Kursbereichen",
 | 
			
		||||
    "errorloadcourses": "Fehler beim Laden von Kursen",
 | 
			
		||||
    "errorsearching": "Fehler beim Suchen",
 | 
			
		||||
    "errorselfenrol": "Fehler bei der Selbsteinschreibung",
 | 
			
		||||
    "filtermycourses": "Meine Kurse filtern",
 | 
			
		||||
    "frontpage": "Startseite",
 | 
			
		||||
    "mycourses": "Meine Kurse",
 | 
			
		||||
    "nocourses": "Keine Kurse",
 | 
			
		||||
    "nocoursesyet": "Keine Kurse in diesem Kursbereich",
 | 
			
		||||
    "nosearchresults": "Keine Suchergebnisse",
 | 
			
		||||
    "notenroled": "Sie sind nicht in diesen Kurs eingeschrieben",
 | 
			
		||||
    "notenrollable": "Sie können sich nicht selbst in diesen Kurs einschreiben.",
 | 
			
		||||
    "password": "Öffentliches Kennwort",
 | 
			
		||||
    "paymentrequired": "Dieser Kurs ist entgeltpflichtig. Bitte bezahlen Sie das Teilnahmeentgelt, um in den Kurs eingeschrieben zu werden.",
 | 
			
		||||
    "paypalaccepted": "PayPal-Zahlungen möglich",
 | 
			
		||||
    "search": "Suchen",
 | 
			
		||||
    "searchcourses": "Kurse suchen",
 | 
			
		||||
    "searchcoursesadvice": "Sie können Kurse suchen, um als Gast teilzunehmen oder sich selbst einzuschreiben, falls dies erlaubt ist.",
 | 
			
		||||
    "selfenrolment": "Selbsteinschreibung",
 | 
			
		||||
    "sendpaymentbutton": "Zahlung über PayPal",
 | 
			
		||||
    "totalcoursesearchresults": "Alle Kurse: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/el.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Σε αυτό το μάθημα επιτρέπονται και οι επισκέπτες",
 | 
			
		||||
    "availablecourses": "Διαθέσιμα Μαθήματα",
 | 
			
		||||
    "cannotretrievemorecategories": "Δεν είναι δυνατή η ανάκτηση κατηγοριών μετά από το επίπεδο {{$a}}.",
 | 
			
		||||
    "categories": "Κατηγορίες μαθημάτων",
 | 
			
		||||
    "confirmselfenrol": "Είστε σίγουροι ότι θέλετε να εγγραφείτε σε αυτό το μάθημα;",
 | 
			
		||||
    "courses": "Μαθήματα",
 | 
			
		||||
    "enrolme": "Εγγραφή",
 | 
			
		||||
    "errorloadcategories": "Παρουσιάστηκε σφάλμα κατά την φόρτωση των κατηγοριών.",
 | 
			
		||||
    "errorloadcourses": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση των μαθημάτων.",
 | 
			
		||||
    "errorsearching": "Παρουσιάστηκε σφάλμα κατά τη διάρκεια της αναζήτησης.",
 | 
			
		||||
    "errorselfenrol": "Παρουσιάστηκε σφάλμα κατά τη διάρκεια της αυτο-εγγραφής.",
 | 
			
		||||
    "filtermycourses": "Φιλτράρισμα των μαθημάτων μου",
 | 
			
		||||
    "frontpage": "Αρχική σελίδα",
 | 
			
		||||
    "mycourses": "Τα μαθήματά μου",
 | 
			
		||||
    "nocourses": "Δεν υπάρχει πληροφορία του μαθήματος για προβολή.",
 | 
			
		||||
    "nocoursesyet": "Δεν υπάρχουν μαθήματα σε αυτήν την κατηγορία",
 | 
			
		||||
    "nosearchresults": "Δε βρέθηκαν αποτελέσματα για την αναζήτησή σας",
 | 
			
		||||
    "notenroled": "Δεν είσαι εγγεγραμμένος σε αυτό το μάθημα",
 | 
			
		||||
    "notenrollable": "Δεν μπορείτε να αυτο-εγγραφείτε σε αυτό το μάθημα.",
 | 
			
		||||
    "password": "Κωδικός πρόσβασης",
 | 
			
		||||
    "paymentrequired": "Αυτό το μάθημα απαιτεί πληρωμή για την είσοδο.",
 | 
			
		||||
    "paypalaccepted": "Αποδεκτές οι πληρωμές μέσω PayPal",
 | 
			
		||||
    "search": "Αναζήτηση",
 | 
			
		||||
    "searchcourses": "Αναζήτηση μαθημάτων",
 | 
			
		||||
    "searchcoursesadvice": "Μπορείτε να χρησιμοποιήσετε το κουμπί Αναζήτηση μαθημάτων για πρόσβαση ως επισκέπτης ή για να αυτο-εγγραφείτε σε μαθήματα που το επιτρέπουν.",
 | 
			
		||||
    "selfenrolment": "Αυτο-εγγραφή",
 | 
			
		||||
    "sendpaymentbutton": "Αποστολή πληρωμής με Paypal",
 | 
			
		||||
    "totalcoursesearchresults": "Συνολικά μαθήματα: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/core/courses/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,47 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "This course allows guest users to enter",
 | 
			
		||||
    "availablecourses": "Available courses",
 | 
			
		||||
    "cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.",
 | 
			
		||||
    "categories": "Course categories",
 | 
			
		||||
    "confirmselfenrol": "Are you sure you want to enrol yourself in this course?",
 | 
			
		||||
    "courseoverview": "Course overview",
 | 
			
		||||
    "courses": "Courses",
 | 
			
		||||
    "downloadcourses": "Download courses",
 | 
			
		||||
    "enrolme": "Enrol me",
 | 
			
		||||
    "errorloadcategories": "An error occurred while loading categories.",
 | 
			
		||||
    "errorloadcourses": "An error occurred while loading courses.",
 | 
			
		||||
    "errorsearching": "An error occurred while searching.",
 | 
			
		||||
    "errorselfenrol": "An error occurred while self enrolling.",
 | 
			
		||||
    "filtermycourses": "Filter my courses",
 | 
			
		||||
    "frontpage": "Front page",
 | 
			
		||||
    "future": "Future",
 | 
			
		||||
    "inprogress": "In progress",
 | 
			
		||||
    "morecourses": "More courses",
 | 
			
		||||
    "mycourses": "My courses",
 | 
			
		||||
    "next30days": "Next 30 days",
 | 
			
		||||
    "next7days": "Next 7 days",
 | 
			
		||||
    "nocourses": "No course information to show.",
 | 
			
		||||
    "nocoursesfuture": "No future courses",
 | 
			
		||||
    "nocoursesinprogress": "No in progress courses",
 | 
			
		||||
    "nocoursesoverview": "No courses",
 | 
			
		||||
    "nocoursespast": "No past courses",
 | 
			
		||||
    "nocoursesyet": "No courses in this category",
 | 
			
		||||
    "noevents": "No upcoming activities due",
 | 
			
		||||
    "nosearchresults": "There were no results from your search",
 | 
			
		||||
    "notenroled": "You are not enrolled in this course",
 | 
			
		||||
    "notenrollable": "You cannot enrol yourself in this course.",
 | 
			
		||||
    "password": "Enrolment key",
 | 
			
		||||
    "past": "Past",
 | 
			
		||||
    "paymentrequired": "This course requires a payment for entry.",
 | 
			
		||||
    "paypalaccepted": "PayPal payments accepted",
 | 
			
		||||
    "recentlyoverdue": "Recently overdue",
 | 
			
		||||
    "search": "Search",
 | 
			
		||||
    "searchcourses": "Search courses",
 | 
			
		||||
    "searchcoursesadvice": "You can use the search courses button to find courses to access as a  guest or enrol yourself in courses that allow it.",
 | 
			
		||||
    "selfenrolment": "Self enrolment",
 | 
			
		||||
    "sendpaymentbutton": "Send payment via PayPal",
 | 
			
		||||
    "sortbycourses": "Sort by courses",
 | 
			
		||||
    "sortbydates": "Sort by dates",
 | 
			
		||||
    "timeline": "Timeline",
 | 
			
		||||
    "totalcoursesearchresults": "Total courses: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/es-mx.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Este curso permite la entrada de invitados",
 | 
			
		||||
    "availablecourses": "Cursos disponibles",
 | 
			
		||||
    "cannotretrievemorecategories": "No se pueden recuperar categorías más profundas que el nivel {{$a}}.",
 | 
			
		||||
    "categories": "Categorías",
 | 
			
		||||
    "confirmselfenrol": "¿Está Usted seguro de querer inscribirse a Usted mismo en este curso?",
 | 
			
		||||
    "courses": "Cursos",
 | 
			
		||||
    "enrolme": "Inscribirme",
 | 
			
		||||
    "errorloadcategories": "Ocurrió un error al cargar categorías.",
 | 
			
		||||
    "errorloadcourses": "Ocurrió un error al cargar los cursos.",
 | 
			
		||||
    "errorsearching": "Ocurrio un error al buscar.",
 | 
			
		||||
    "errorselfenrol": "Ocurrio un error al auto-inscribir.",
 | 
			
		||||
    "filtermycourses": "<<<filtrar mis cursos",
 | 
			
		||||
    "frontpage": "Portada",
 | 
			
		||||
    "mycourses": "Mis cursos",
 | 
			
		||||
    "nocourses": "Sin cursos",
 | 
			
		||||
    "nocoursesyet": "No hay cursos en esta categoría",
 | 
			
		||||
    "nosearchresults": "Sin resultados",
 | 
			
		||||
    "notenroled": "Usted no está inscrito en este curso",
 | 
			
		||||
    "notenrollable": "Usted no puede inscribirse a Usted mismo en este curso.",
 | 
			
		||||
    "password": "Secreto Compartido",
 | 
			
		||||
    "paymentrequired": "Para entrar a este curso es necesario pagar.",
 | 
			
		||||
    "paypalaccepted": "Pagos PayPal aceptados",
 | 
			
		||||
    "search": "Buscar",
 | 
			
		||||
    "searchcourses": "Buscar cursos",
 | 
			
		||||
    "searchcoursesadvice": "Usted puede usar el botón de 'buscar cursos' para encontrar cursos a los cuales acceder como un invitado o para inscribirse Usted mismo en los cursos que lo permitan.",
 | 
			
		||||
    "selfenrolment": "Auto-inscripción",
 | 
			
		||||
    "sendpaymentbutton": "Enviar pago por Paypal",
 | 
			
		||||
    "totalcoursesearchresults": "Total de cursos: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/es.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Este curso permite la entrada de invitados",
 | 
			
		||||
    "availablecourses": "Cursos disponibles",
 | 
			
		||||
    "cannotretrievemorecategories": "No se pueden recuperar categorías más profundas que el nivel {{$a}}.",
 | 
			
		||||
    "categories": "Categorías",
 | 
			
		||||
    "confirmselfenrol": "¿Está seguro que desea auto-matricularse en este curso?",
 | 
			
		||||
    "courses": "Cursos",
 | 
			
		||||
    "enrolme": "Matricularme",
 | 
			
		||||
    "errorloadcategories": "Ocurrió un error al cargar categorías.",
 | 
			
		||||
    "errorloadcourses": "Se ha producido un error cargando los cursos.",
 | 
			
		||||
    "errorsearching": "Se ha producido un error durante la búsqueda.",
 | 
			
		||||
    "errorselfenrol": "Se ha producido un error durante la auto-matriculación.",
 | 
			
		||||
    "filtermycourses": "Filtrar mis cursos",
 | 
			
		||||
    "frontpage": "Página Principal",
 | 
			
		||||
    "mycourses": "Mis cursos",
 | 
			
		||||
    "nocourses": "Sin cursos",
 | 
			
		||||
    "nocoursesyet": "No hay cursos en esta categoría",
 | 
			
		||||
    "nosearchresults": "La búsqueda no produjo resultados",
 | 
			
		||||
    "notenroled": "Usted no está matriculado en este curso",
 | 
			
		||||
    "notenrollable": "No puede auto-matricularse en este curso.",
 | 
			
		||||
    "password": "Contraseña",
 | 
			
		||||
    "paymentrequired": "Para entrar a este curso es necesario pagar.",
 | 
			
		||||
    "paypalaccepted": "Pagos PayPal aceptados",
 | 
			
		||||
    "search": "Buscar",
 | 
			
		||||
    "searchcourses": "Buscar cursos",
 | 
			
		||||
    "searchcoursesadvice": "Puede utilizar el botón de buscar cursos para acceder como invitado o auto-matricularse en los cursos que lo permitan.",
 | 
			
		||||
    "selfenrolment": "Auto-matriculación",
 | 
			
		||||
    "sendpaymentbutton": "Enviar pago por Paypal",
 | 
			
		||||
    "totalcoursesearchresults": "Total de cursos: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/eu.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Ikastaro honetan bisitariak sar daitezke",
 | 
			
		||||
    "availablecourses": "Eskura dauden ikastaroak",
 | 
			
		||||
    "cannotretrievemorecategories": "{{$a}}. maila baino sakonagoko kategoriak ezin dira eskuratu.",
 | 
			
		||||
    "categories": "Ikastaro-kategoriak",
 | 
			
		||||
    "confirmselfenrol": "Ziur zaude ikastaro honetan izena eman nahi duzula?",
 | 
			
		||||
    "courses": "Ikastaroak",
 | 
			
		||||
    "enrolme": "Matrikula nazazu",
 | 
			
		||||
    "errorloadcategories": "Errorea gertatu da kategoriak kargatzean.",
 | 
			
		||||
    "errorloadcourses": "Errore bat gertatu da ikastaroak kargatzean.",
 | 
			
		||||
    "errorsearching": "Errorea gertatu da bilatzean.",
 | 
			
		||||
    "errorselfenrol": "Errorea gertatu da matrikulazio automatikoa egitean",
 | 
			
		||||
    "filtermycourses": "Nire ikastaroak iragazi",
 | 
			
		||||
    "frontpage": "Hasiera-orria",
 | 
			
		||||
    "mycourses": "Nire ikastaroak",
 | 
			
		||||
    "nocourses": "Ez dago ikastaroei buruzko informaziorik",
 | 
			
		||||
    "nocoursesyet": "Ez dago ikastarorik kategoria honetan",
 | 
			
		||||
    "nosearchresults": "Zure bilaketak du ezer topatu",
 | 
			
		||||
    "notenroled": "Ez zaude matrikulatuta ikastaro honetan",
 | 
			
		||||
    "notenrollable": "Ezin duzu zeure burua matrikulatu ikastaro honetan.",
 | 
			
		||||
    "password": "Pasahitza",
 | 
			
		||||
    "paymentrequired": "Ikastaro hau ordainpekoa da",
 | 
			
		||||
    "paypalaccepted": "Paypal ordainketak onartu dira",
 | 
			
		||||
    "search": "Bilatu...",
 | 
			
		||||
    "searchcourses": "Bilatu Ikastaroak",
 | 
			
		||||
    "searchcoursesadvice": "Bilatu botoia erabil dezakezu ikastaroak topatu eta bisitari gisa sartu edo bertan matrikulatzeko ikastaroak baimentzen badu.",
 | 
			
		||||
    "selfenrolment": "Matrikulazio automatikoa",
 | 
			
		||||
    "sendpaymentbutton": "Paypal bidezko ordainketa bidali",
 | 
			
		||||
    "totalcoursesearchresults": "Ikastaroak guztira: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/core/courses/lang/fa.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "ورود کاربران مهمان به این درس مجاز است",
 | 
			
		||||
    "availablecourses": "درسهای موجود",
 | 
			
		||||
    "categories": "طبقههای درسی",
 | 
			
		||||
    "confirmselfenrol": "آیا مطمئنید که میخواهید خود را در این درس ثبتنام کنید؟",
 | 
			
		||||
    "courses": "درسها",
 | 
			
		||||
    "enrolme": "ثبتنام من",
 | 
			
		||||
    "errorloadcourses": "در هنگام بارگیری درسها خطایی رخ داد.",
 | 
			
		||||
    "filtermycourses": "پالایش درسهای من",
 | 
			
		||||
    "frontpage": "صفحهٔ اول",
 | 
			
		||||
    "mycourses": "درسهای من",
 | 
			
		||||
    "nocourses": "هیچ درسی برای نمایش وجود ندارد",
 | 
			
		||||
    "nocoursesyet": "درسی در این طبقه وجود ندارد",
 | 
			
		||||
    "nosearchresults": "نتایجی برای جستجوی شما وجود نداشت.",
 | 
			
		||||
    "notenroled": "شما در این درس ثبتنام نیستید",
 | 
			
		||||
    "password": "رمز ورود",
 | 
			
		||||
    "paymentrequired": "ثبتنام در این درس مستلزم پرداخت شهریه است.",
 | 
			
		||||
    "search": "جستجو...",
 | 
			
		||||
    "searchcourses": "جستجو بین درسها"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/core/courses/lang/fi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "allowguests": "Tämä kurssi päästää vierailijatunnuksella sisään.",
 | 
			
		||||
    "availablecourses": "Saatavilla olevat kurssit",
 | 
			
		||||
    "cannotretrievemorecategories": "Kategorioita, jotka ovat \"syvemmällä\" kuin tasolla {{$a}} ei voida noutaa.",
 | 
			
		||||
    "categories": "Kategoriat",
 | 
			
		||||
    "confirmselfenrol": "Oletko varma, että haluat lisätä itsesi kurssin osallistujaksi?",
 | 
			
		||||
    "courses": "Kurssit",
 | 
			
		||||
    "enrolme": "Lisää minut kurssialueelle",
 | 
			
		||||
    "errorloadcategories": "Kategorioita ladattaessa tapahtui virhe.",
 | 
			
		||||
    "errorloadcourses": "Kursseja ladattaessa tapahtui virhe.",
 | 
			
		||||
    "errorsearching": "Hauan aikana tapahtu virhe.",
 | 
			
		||||
    "errorselfenrol": "Itserekisteröitymisessä tapahtui virhe.",
 | 
			
		||||
    "filtermycourses": "Suodata kursseja",
 | 
			
		||||
    "frontpage": "Etusivu",
 | 
			
		||||
    "mycourses": "Omat kurssini",
 | 
			
		||||
    "nocourses": "Ei kursseja",
 | 
			
		||||
    "nocoursesyet": "Ei kursseja tässä kategoriassa",
 | 
			
		||||
    "nosearchresults": "Ei tuloksia",
 | 
			
		||||
    "notenroled": "Et ole ilmoittautuneena kurssille",
 | 
			
		||||
    "notenrollable": "Et voi itserekisteröityä tälle kursille.",
 | 
			
		||||
    "password": "Jaettu salaisuus",
 | 
			
		||||
    "paymentrequired": "Tämä kurssi vaatii osallistumismaksun.",
 | 
			
		||||
    "paypalaccepted": "PayPal maksu hyväksytty",
 | 
			
		||||
    "search": "Etsi",
 | 
			
		||||
    "searchcourses": "Etsi kursseilta",
 | 
			
		||||
    "searchcoursesadvice": "Voit käyttää kurssinhakupainiketta liittääksesi itsesi kurssille vierailijaksi tai osallistujaksi, mikäli kurssin asetukset sallivat sen.",
 | 
			
		||||
    "selfenrolment": "Itserekisteröityminen",
 | 
			
		||||
    "sendpaymentbutton": "Lähetä maksu PayPalin kautta",
 | 
			
		||||
    "totalcoursesearchresults": "Kursseja yhteensä: {{$a}}"
 | 
			
		||||
}
 | 
			
		||||