[examples][frameworks] Add missing examples and add a test to ensure every framework has an example (#4372)
* Add test to frameworks * Add example for Docusaurus v2 * Add example for ionic-angular * Update READMEs * Use existing versions * Reset yarn.lock * Add schema validation and add missing Scully.io logo
42
examples/ionic-angular/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { CheckTutorial } from './providers/check-tutorial.service';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/tutorial',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./pages/account/account.module').then(m => m.AccountModule)
|
||||
},
|
||||
{
|
||||
path: 'support',
|
||||
loadChildren: () => import('./pages/support/support.module').then(m => m.SupportModule)
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule)
|
||||
},
|
||||
{
|
||||
path: 'signup',
|
||||
loadChildren: () => import('./pages/signup/signup.module').then(m => m.SignUpModule)
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
loadChildren: () => import('./pages/tabs-page/tabs-page.module').then(m => m.TabsModule)
|
||||
},
|
||||
{
|
||||
path: 'tutorial',
|
||||
loadChildren: () => import('./pages/tutorial/tutorial.module').then(m => m.TutorialModule),
|
||||
canLoad: [CheckTutorial]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
114
examples/ionic-angular/src/app/app.component.html
Normal file
@@ -0,0 +1,114 @@
|
||||
<ion-app [class.dark-theme]="dark">
|
||||
<ion-split-pane contentId="main-content">
|
||||
|
||||
<ion-menu contentId="main-content">
|
||||
<ion-content>
|
||||
<ion-list lines="none">
|
||||
<ion-list-header>
|
||||
Conference
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle autoHide="false" *ngFor="let p of appPages; let i = index">
|
||||
<ion-item [routerLink]="p.url" routerLinkActive="selected" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" [name]="p.icon + '-outline'"></ion-icon>
|
||||
<ion-label>
|
||||
{{p.title}}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="loggedIn" lines="none">
|
||||
<ion-list-header>
|
||||
Account
|
||||
</ion-list-header>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item routerLink="/account" routerLinkActive="active" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" name="person"></ion-icon>
|
||||
<ion-label>
|
||||
Account
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item routerLink="/support" routerLinkActive="active" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" name="help"></ion-icon>
|
||||
<ion-label>
|
||||
Support
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item button (click)="logout()" detail="false">
|
||||
<ion-icon slot="start" name="log-out"></ion-icon>
|
||||
<ion-label>
|
||||
Logout
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="!loggedIn" lines="none">
|
||||
<ion-list-header>
|
||||
Account
|
||||
</ion-list-header>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item routerLink="/login" routerLinkActive="active" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" name="log-in"></ion-icon>
|
||||
<ion-label>
|
||||
Login
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item routerLink="/support" routerLinkActive="active" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" name="help"></ion-icon>
|
||||
<ion-label>
|
||||
Support
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item routerLink="/signup" routerLinkActive="active" routerDirection="root" detail="false">
|
||||
<ion-icon slot="start" name="person-add"></ion-icon>
|
||||
<ion-label>
|
||||
Signup
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
|
||||
<ion-item>
|
||||
<ion-icon slot="start" name="moon-outline"></ion-icon>
|
||||
<ion-label>
|
||||
Dark Mode
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="dark"></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list lines="none">
|
||||
<ion-list-header>
|
||||
Tutorial
|
||||
</ion-list-header>
|
||||
<ion-menu-toggle autoHide="false">
|
||||
<ion-item button (click)="openTutorial()" detail="false">
|
||||
<ion-icon slot="start" name="hammer"></ion-icon>
|
||||
<ion-label>Show Tutorial</ion-label>
|
||||
</ion-item>
|
||||
</ion-menu-toggle>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
|
||||
<ion-router-outlet id="main-content"></ion-router-outlet>
|
||||
|
||||
</ion-split-pane>
|
||||
|
||||
</ion-app>
|
||||
87
examples/ionic-angular/src/app/app.component.scss
Normal file
@@ -0,0 +1,87 @@
|
||||
ion-menu ion-content {
|
||||
--padding-top: 20px;
|
||||
--padding-bottom: 20px;
|
||||
|
||||
--background: var(--ion-item-background, var(--ion-background-color, #fff));
|
||||
}
|
||||
|
||||
/* Remove background transitions for switching themes */
|
||||
ion-menu ion-item {
|
||||
--transition: none;
|
||||
}
|
||||
|
||||
ion-item.selected {
|
||||
--color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
/*
|
||||
* Material Design Menu
|
||||
*/
|
||||
ion-menu.md ion-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
ion-menu.md ion-list-header {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .1em;
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item {
|
||||
--padding-start: 18px;
|
||||
|
||||
margin-right: 10px;
|
||||
|
||||
border-radius: 0 50px 50px 0;
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected {
|
||||
--background: rgba(var(--ion-color-primary-rgb), 0.14);
|
||||
}
|
||||
|
||||
ion-menu.md ion-item.selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
ion-menu.md ion-list-header,
|
||||
ion-menu.md ion-item ion-icon {
|
||||
color: var(--ion-color-step-650, #5f6368);
|
||||
}
|
||||
|
||||
ion-menu.md ion-list:not(:last-of-type) {
|
||||
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* iOS Menu
|
||||
*/
|
||||
ion-menu.ios ion-list-header {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-list {
|
||||
padding: 20px 0 0;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item {
|
||||
--padding-start: 16px;
|
||||
--min-height: 50px;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item ion-icon {
|
||||
font-size: 24px;
|
||||
color: #73849a;
|
||||
}
|
||||
|
||||
ion-menu.ios ion-item.selected ion-icon {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
65
examples/ionic-angular/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
|
||||
import { MenuController, Platform } from '@ionic/angular';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
||||
import { IonicStorageModule } from '@ionic/storage';
|
||||
import { AppComponent } from './app.component';
|
||||
import { UserData } from './providers/user-data';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let menuSpy,
|
||||
routerSpy,
|
||||
userDataSpy,
|
||||
statusBarSpy,
|
||||
splashScreenSpy,
|
||||
swUpdateSpy,
|
||||
platformReadySpy,
|
||||
platformSpy,
|
||||
app,
|
||||
fixture;
|
||||
|
||||
beforeEach(async(() => {
|
||||
menuSpy = jasmine.createSpyObj('MenuController', ['toggle', 'enable']);
|
||||
routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
|
||||
userDataSpy = jasmine.createSpyObj('UserData', ['isLoggedIn', 'logout']);
|
||||
statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']);
|
||||
splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']);
|
||||
swUpdateSpy = jasmine.createSpyObj('SwUpdate', ['available', 'activateUpdate']);
|
||||
platformReadySpy = Promise.resolve();
|
||||
platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy });
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [IonicStorageModule.forRoot()],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: MenuController, useValue: menuSpy },
|
||||
{ provide: Router, useValue: routerSpy },
|
||||
{ provide: UserData, useValue: userDataSpy },
|
||||
{ provide: StatusBar, useValue: statusBarSpy },
|
||||
{ provide: SplashScreen, useValue: splashScreenSpy },
|
||||
{ provide: SwUpdate, useValue: swUpdateSpy },
|
||||
{ provide: Platform, useValue: platformSpy }
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
app = fixture.debugElement.componentInstance;
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should initialize the app', async () => {
|
||||
expect(platformSpy.ready).toHaveBeenCalled();
|
||||
await platformReadySpy;
|
||||
expect(statusBarSpy.styleDefault).toHaveBeenCalled();
|
||||
expect(splashScreenSpy.hide).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
129
examples/ionic-angular/src/app/app.component.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
import { MenuController, Platform, ToastController } from '@ionic/angular';
|
||||
|
||||
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
||||
|
||||
import { Storage } from '@ionic/storage';
|
||||
|
||||
import { UserData } from './providers/user-data';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
appPages = [
|
||||
{
|
||||
title: 'Schedule',
|
||||
url: '/app/tabs/schedule',
|
||||
icon: 'calendar'
|
||||
},
|
||||
{
|
||||
title: 'Speakers',
|
||||
url: '/app/tabs/speakers',
|
||||
icon: 'people'
|
||||
},
|
||||
{
|
||||
title: 'Map',
|
||||
url: '/app/tabs/map',
|
||||
icon: 'map'
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
url: '/app/tabs/about',
|
||||
icon: 'information-circle'
|
||||
}
|
||||
];
|
||||
loggedIn = false;
|
||||
dark = false;
|
||||
|
||||
constructor(
|
||||
private menu: MenuController,
|
||||
private platform: Platform,
|
||||
private router: Router,
|
||||
private splashScreen: SplashScreen,
|
||||
private statusBar: StatusBar,
|
||||
private storage: Storage,
|
||||
private userData: UserData,
|
||||
private swUpdate: SwUpdate,
|
||||
private toastCtrl: ToastController,
|
||||
) {
|
||||
this.initializeApp();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.checkLoginStatus();
|
||||
this.listenForLoginEvents();
|
||||
|
||||
this.swUpdate.available.subscribe(async res => {
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: 'Update available!',
|
||||
position: 'bottom',
|
||||
buttons: [
|
||||
{
|
||||
role: 'cancel',
|
||||
text: 'Reload'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
|
||||
toast
|
||||
.onDidDismiss()
|
||||
.then(() => this.swUpdate.activateUpdate())
|
||||
.then(() => window.location.reload());
|
||||
});
|
||||
}
|
||||
|
||||
initializeApp() {
|
||||
this.platform.ready().then(() => {
|
||||
this.statusBar.styleDefault();
|
||||
this.splashScreen.hide();
|
||||
});
|
||||
}
|
||||
|
||||
checkLoginStatus() {
|
||||
return this.userData.isLoggedIn().then(loggedIn => {
|
||||
return this.updateLoggedInStatus(loggedIn);
|
||||
});
|
||||
}
|
||||
|
||||
updateLoggedInStatus(loggedIn: boolean) {
|
||||
setTimeout(() => {
|
||||
this.loggedIn = loggedIn;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
listenForLoginEvents() {
|
||||
window.addEventListener('user:login', () => {
|
||||
this.updateLoggedInStatus(true);
|
||||
});
|
||||
|
||||
window.addEventListener('user:signup', () => {
|
||||
this.updateLoggedInStatus(true);
|
||||
});
|
||||
|
||||
window.addEventListener('user:logout', () => {
|
||||
this.updateLoggedInStatus(false);
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userData.logout().then(() => {
|
||||
return this.router.navigateByUrl('/app/tabs/schedule');
|
||||
});
|
||||
}
|
||||
|
||||
openTutorial() {
|
||||
this.menu.enable(false);
|
||||
this.storage.set('ion_did_tutorial', false);
|
||||
this.router.navigateByUrl('/tutorial');
|
||||
}
|
||||
}
|
||||
32
examples/ionic-angular/src/app/app.module.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
|
||||
import { StatusBar } from '@ionic-native/status-bar/ngx';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { IonicStorageModule } from '@ionic/storage';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
IonicModule.forRoot(),
|
||||
IonicStorageModule.forRoot(),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
||||
enabled: environment.production
|
||||
})
|
||||
],
|
||||
declarations: [AppComponent],
|
||||
providers: [InAppBrowser, SplashScreen, StatusBar],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
||||
0
examples/ionic-angular/src/app/app.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
export interface UserOptions {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { PopoverController } from '@ionic/angular';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ion-list>
|
||||
<ion-item button (click)="close('https://ionicframework.com/getting-started')">
|
||||
<ion-label>Learn Ionic</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="close('https://ionicframework.com/docs/')">
|
||||
<ion-label>Documentation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="close('https://showcase.ionicframework.com')">
|
||||
<ion-label>Showcase</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="close('https://github.com/ionic-team/ionic')">
|
||||
<ion-label>GitHub Repo</ion-label>
|
||||
</ion-item>
|
||||
<ion-item button (click)="support()">
|
||||
<ion-label>Support</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
`
|
||||
})
|
||||
export class PopoverPage {
|
||||
constructor(public popoverCtrl: PopoverController) {}
|
||||
|
||||
support() {
|
||||
// this.app.getRootNavs()[0].push('/support');
|
||||
this.popoverCtrl.dismiss();
|
||||
}
|
||||
|
||||
close(url: string) {
|
||||
window.open(url, '_blank');
|
||||
this.popoverCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AboutPage } from './about';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AboutPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AboutPageRoutingModule { }
|
||||
77
examples/ionic-angular/src/app/pages/about/about.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<ion-content>
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="presentPopover($event)">
|
||||
<ion-icon slot="icon-only" ios="ellipsis-horizontal" md="ellipsis-vertical"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div class="about-header">
|
||||
<!-- Instead of loading an image each time the select changes, use opacity to transition them -->
|
||||
<div class="about-image madison" [ngStyle]="location === 'madison' && {'opacity': '1'}"></div>
|
||||
<div class="about-image austin" [ngStyle]="location === 'austin' && {'opacity': '1'}"></div>
|
||||
<div class="about-image chicago" [ngStyle]="location === 'chicago' && {'opacity': '1'}"></div>
|
||||
<div class="about-image seattle" [ngStyle]="location === 'seattle' && {'opacity': '1'}"></div>
|
||||
</div>
|
||||
|
||||
<div class="about-info">
|
||||
<h3 class="ion-padding-top ion-padding-start">About</h3>
|
||||
|
||||
<p class="ion-padding-start ion-padding-end">
|
||||
The Ionic Conference is a one-day conference on {{ conferenceDate | date: 'mediumDate' }} featuring talks from the Ionic team. It is focused on Ionic applications being built with Ionic Framework. This includes migrating apps to the latest version of the framework, Angular concepts, Webpack, Sass, and many other technologies used in Ionic 2. Tickets are completely sold out, and we’re expecting more than 1000 developers – making this the largest Ionic conference ever!
|
||||
</p>
|
||||
|
||||
<h3 class="ion-padding-top ion-padding-start">Details</h3>
|
||||
|
||||
<ion-list lines="none">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Location
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="location" [interfaceOptions]="selectOptions">
|
||||
<ion-select-option value="madison">Madison, WI</ion-select-option>
|
||||
<ion-select-option value="austin">Austin, TX</ion-select-option>
|
||||
<ion-select-option value="chicago">Chicago, IL</ion-select-option>
|
||||
<ion-select-option value="seattle">Seattle, WA</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Date
|
||||
</ion-label>
|
||||
<ion-datetime
|
||||
displayFormat="MMM DD, YYYY"
|
||||
max="2056"
|
||||
[(ngModel)]="conferenceDate">
|
||||
</ion-datetime>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<h3 class="ion-padding-top ion-padding-start">Internet</h3>
|
||||
|
||||
<ion-list lines="none">
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Wifi network
|
||||
</ion-label>
|
||||
<ion-label class="ion-text-end">
|
||||
ica{{conferenceDate | date: 'y'}}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
Password
|
||||
</ion-label>
|
||||
<ion-label class="ion-text-end">
|
||||
makegoodthings
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</ion-content>
|
||||
21
examples/ionic-angular/src/app/pages/about/about.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { AboutPage } from './about';
|
||||
import { PopoverPage } from '../about-popover/about-popover';
|
||||
import { AboutPageRoutingModule } from './about-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
AboutPageRoutingModule
|
||||
],
|
||||
declarations: [AboutPage, PopoverPage],
|
||||
entryComponents: [PopoverPage],
|
||||
bootstrap: [AboutPage],
|
||||
})
|
||||
export class AboutModule {}
|
||||
90
examples/ionic-angular/src/app/pages/about/about.scss
Normal file
@@ -0,0 +1,90 @@
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.about-header .about-image {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.about-header .madison {
|
||||
background-image: url(/assets/img/about/madison.jpg);
|
||||
}
|
||||
.about-header .austin {
|
||||
background-image: url(/assets/img/about/austin.jpg);
|
||||
}
|
||||
.about-header .chicago {
|
||||
background-image: url(/assets/img/about/chicago.jpg);
|
||||
}
|
||||
.about-header .seattle {
|
||||
background-image: url(/assets/img/about/seattle.jpg);
|
||||
}
|
||||
|
||||
.about-info {
|
||||
position: absolute;
|
||||
margin-top: -10px;
|
||||
border-radius: 10px;
|
||||
background: var(--ion-background-color, #fff);
|
||||
}
|
||||
|
||||
.about-info h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.about-info ion-list {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
line-height: 130%;
|
||||
|
||||
color: var(--ion-color-dark);
|
||||
}
|
||||
|
||||
.about-info ion-icon {
|
||||
margin-inline-end: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Only
|
||||
*/
|
||||
|
||||
.ios .about-info {
|
||||
--ion-padding: 19px;
|
||||
}
|
||||
|
||||
.ios .about-info h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
29
examples/ionic-angular/src/app/pages/about/about.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { PopoverController } from '@ionic/angular';
|
||||
|
||||
import { PopoverPage } from '../about-popover/about-popover';
|
||||
|
||||
@Component({
|
||||
selector: 'page-about',
|
||||
templateUrl: 'about.html',
|
||||
styleUrls: ['./about.scss'],
|
||||
})
|
||||
export class AboutPage {
|
||||
location = 'madison';
|
||||
conferenceDate = '2047-05-17';
|
||||
|
||||
selectOptions = {
|
||||
header: 'Select a Location'
|
||||
};
|
||||
|
||||
constructor(public popoverCtrl: PopoverController) { }
|
||||
|
||||
async presentPopover(event: Event) {
|
||||
const popover = await this.popoverCtrl.create({
|
||||
component: PopoverPage,
|
||||
event
|
||||
});
|
||||
await popover.present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AccountPage } from './account';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AccountPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AccountPageRoutingModule { }
|
||||
23
examples/ionic-angular/src/app/pages/account/account.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Account</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div *ngIf="username" class="ion-padding-top ion-text-center">
|
||||
<img src="https://www.gravatar.com/avatar?d=mm&s=140" alt="avatar">
|
||||
<h2>{{username}}</h2>
|
||||
|
||||
<ion-list inset>
|
||||
<ion-item (click)="updatePicture()">Update Picture</ion-item>
|
||||
<ion-item (click)="changeUsername()">Change Username</ion-item>
|
||||
<ion-item (click)="changePassword()">Change Password</ion-item>
|
||||
<ion-item (click)="support()">Support</ion-item>
|
||||
<ion-item (click)="logout()">Logout</ion-item>
|
||||
</ion-list>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { AccountPage } from './account';
|
||||
import { AccountPageRoutingModule } from './account-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
AccountPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
AccountPage,
|
||||
]
|
||||
})
|
||||
export class AccountModule { }
|
||||
@@ -0,0 +1,4 @@
|
||||
img {
|
||||
max-width: 140px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
77
examples/ionic-angular/src/app/pages/account/account.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { AfterViewInit, Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AlertController } from '@ionic/angular';
|
||||
|
||||
import { UserData } from '../../providers/user-data';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'page-account',
|
||||
templateUrl: 'account.html',
|
||||
styleUrls: ['./account.scss'],
|
||||
})
|
||||
export class AccountPage implements AfterViewInit {
|
||||
username: string;
|
||||
|
||||
constructor(
|
||||
public alertCtrl: AlertController,
|
||||
public router: Router,
|
||||
public userData: UserData
|
||||
) { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.getUsername();
|
||||
}
|
||||
|
||||
updatePicture() {
|
||||
console.log('Clicked to update picture');
|
||||
}
|
||||
|
||||
// Present an alert with the current username populated
|
||||
// clicking OK will update the username and display it
|
||||
// clicking Cancel will close the alert and do nothing
|
||||
async changeUsername() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Change Username',
|
||||
buttons: [
|
||||
'Cancel',
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (data: any) => {
|
||||
this.userData.setUsername(data.username);
|
||||
this.getUsername();
|
||||
}
|
||||
}
|
||||
],
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
value: this.username,
|
||||
placeholder: 'username'
|
||||
}
|
||||
]
|
||||
});
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
getUsername() {
|
||||
this.userData.getUsername().then((username) => {
|
||||
this.username = username;
|
||||
});
|
||||
}
|
||||
|
||||
changePassword() {
|
||||
console.log('Clicked to change password');
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userData.logout();
|
||||
this.router.navigateByUrl('/login');
|
||||
}
|
||||
|
||||
support() {
|
||||
this.router.navigateByUrl('/support');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { LoginPage } from './login';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LoginPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class LoginPageRoutingModule { }
|
||||
54
examples/ionic-angular/src/app/pages/login/login.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
|
||||
<ion-title>Login</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="login-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic logo">
|
||||
</div>
|
||||
|
||||
<form #loginForm="ngForm" novalidate>
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label position="stacked" color="primary">Username</ion-label>
|
||||
<ion-input [(ngModel)]="login.username" name="username" type="text" #username="ngModel" spellcheck="false" autocapitalize="off"
|
||||
required>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-text color="danger">
|
||||
<p [hidden]="username.valid || submitted == false" class="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</ion-text>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="stacked" color="primary">Password</ion-label>
|
||||
<ion-input [(ngModel)]="login.password" name="password" type="password" #password="ngModel" required>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-text color="danger">
|
||||
<p [hidden]="password.valid || submitted == false" class="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</ion-text>
|
||||
</ion-list>
|
||||
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-button (click)="onLogin(loginForm)" type="submit" expand="block">Login</ion-button>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<ion-button (click)="onSignup()" color="light" expand="block">Signup</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</form>
|
||||
|
||||
</ion-content>
|
||||
20
examples/ionic-angular/src/app/pages/login/login.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { LoginPage } from './login';
|
||||
import { LoginPageRoutingModule } from './login-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
LoginPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
LoginPage,
|
||||
]
|
||||
})
|
||||
export class LoginModule { }
|
||||
13
examples/ionic-angular/src/app/pages/login/login.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.login-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
37
examples/ionic-angular/src/app/pages/login/login.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { UserData } from '../../providers/user-data';
|
||||
|
||||
import { UserOptions } from '../../interfaces/user-options';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'page-login',
|
||||
templateUrl: 'login.html',
|
||||
styleUrls: ['./login.scss'],
|
||||
})
|
||||
export class LoginPage {
|
||||
login: UserOptions = { username: '', password: '' };
|
||||
submitted = false;
|
||||
|
||||
constructor(
|
||||
public userData: UserData,
|
||||
public router: Router
|
||||
) { }
|
||||
|
||||
onLogin(form: NgForm) {
|
||||
this.submitted = true;
|
||||
|
||||
if (form.valid) {
|
||||
this.userData.login(this.login.username);
|
||||
this.router.navigateByUrl('/app/tabs/schedule');
|
||||
}
|
||||
}
|
||||
|
||||
onSignup() {
|
||||
this.router.navigateByUrl('/signup');
|
||||
}
|
||||
}
|
||||
161
examples/ionic-angular/src/app/pages/map/map-dark-style.js
Normal file
@@ -0,0 +1,161 @@
|
||||
export const darkStyle = [
|
||||
{
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#242f3e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#242f3e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "administrative.locality",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#263c3f"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "poi.park",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#6b9a76"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#38414e"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#212a37"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#9ca5b3"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#746855"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "geometry.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#1f2835"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "road.highway",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#f3d19c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#2f3948"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "transit.station",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#d59563"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "geometry",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#17263c"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.fill",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#515c6d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"featureType": "water",
|
||||
"elementType": "labels.text.stroke",
|
||||
"stylers": [
|
||||
{
|
||||
"color": "#17263c"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { MapPage } from './map';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MapPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class MapPageRoutingModule { }
|
||||
12
examples/ionic-angular/src/app/pages/map/map.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Map</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div #mapCanvas class="map-canvas"></div>
|
||||
</ion-content>
|
||||
18
examples/ionic-angular/src/app/pages/map/map.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { MapPage } from './map';
|
||||
import { MapPageRoutingModule } from './map-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
MapPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
MapPage,
|
||||
]
|
||||
})
|
||||
export class MapModule { }
|
||||
15
examples/ionic-angular/src/app/pages/map/map.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
.map-canvas {
|
||||
position: absolute;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-color: transparent;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease-in;
|
||||
}
|
||||
|
||||
.show-map {
|
||||
opacity: 1;
|
||||
}
|
||||
107
examples/ionic-angular/src/app/pages/map/map.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Component, ElementRef, Inject, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
import { Platform } from '@ionic/angular';
|
||||
import { DOCUMENT} from '@angular/common';
|
||||
|
||||
import { darkStyle } from './map-dark-style';
|
||||
|
||||
@Component({
|
||||
selector: 'page-map',
|
||||
templateUrl: 'map.html',
|
||||
styleUrls: ['./map.scss']
|
||||
})
|
||||
export class MapPage implements AfterViewInit {
|
||||
@ViewChild('mapCanvas', { static: true }) mapElement: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private doc: Document,
|
||||
public confData: ConferenceData,
|
||||
public platform: Platform) {}
|
||||
|
||||
async ngAfterViewInit() {
|
||||
const appEl = this.doc.querySelector('ion-app');
|
||||
let isDark = false;
|
||||
let style = [];
|
||||
if (appEl.classList.contains('dark-theme')) {
|
||||
style = darkStyle;
|
||||
}
|
||||
|
||||
const googleMaps = await getGoogleMaps(
|
||||
'AIzaSyB8pf6ZdFQj5qw7rc_HSGrhUwQKfIe9ICw'
|
||||
);
|
||||
|
||||
let map;
|
||||
|
||||
this.confData.getMap().subscribe((mapData: any) => {
|
||||
const mapEle = this.mapElement.nativeElement;
|
||||
|
||||
map = new googleMaps.Map(mapEle, {
|
||||
center: mapData.find((d: any) => d.center),
|
||||
zoom: 16,
|
||||
styles: style
|
||||
});
|
||||
|
||||
mapData.forEach((markerData: any) => {
|
||||
const infoWindow = new googleMaps.InfoWindow({
|
||||
content: `<h5>${markerData.name}</h5>`
|
||||
});
|
||||
|
||||
const marker = new googleMaps.Marker({
|
||||
position: markerData,
|
||||
map,
|
||||
title: markerData.name
|
||||
});
|
||||
|
||||
marker.addListener('click', () => {
|
||||
infoWindow.open(map, marker);
|
||||
});
|
||||
});
|
||||
|
||||
googleMaps.event.addListenerOnce(map, 'idle', () => {
|
||||
mapEle.classList.add('show-map');
|
||||
});
|
||||
});
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.attributeName === 'class') {
|
||||
const el = mutation.target as HTMLElement;
|
||||
isDark = el.classList.contains('dark-theme');
|
||||
if (map && isDark) {
|
||||
map.setOptions({styles: darkStyle});
|
||||
} else if (map) {
|
||||
map.setOptions({styles: []});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(appEl, {
|
||||
attributes: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getGoogleMaps(apiKey: string): Promise<any> {
|
||||
const win = window as any;
|
||||
const googleModule = win.google;
|
||||
if (googleModule && googleModule.maps) {
|
||||
return Promise.resolve(googleModule.maps);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&v=3.31`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.body.appendChild(script);
|
||||
script.onload = () => {
|
||||
const googleModule2 = win.google;
|
||||
if (googleModule2 && googleModule2.maps) {
|
||||
resolve(googleModule2.maps);
|
||||
} else {
|
||||
reject('google maps not available');
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button *ngIf="ios" (click)="dismiss()">Cancel</ion-button>
|
||||
<ion-button *ngIf="!ios" (click)="selectAll(false)">Reset</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
<ion-title>
|
||||
Filter Sessions
|
||||
</ion-title>
|
||||
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="applyFilters()" strong>Done</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list [lines]="ios ? 'inset' : 'full'">
|
||||
<ion-list-header>Tracks</ion-list-header>
|
||||
|
||||
<ion-item *ngFor="let track of tracks" [attr.track]="track.name | lowercase">
|
||||
<ion-icon *ngIf="ios" slot="start" [name]="track.icon" color="medium"></ion-icon>
|
||||
<ion-label>{{track.name}}</ion-label>
|
||||
<ion-checkbox [(ngModel)]="track.isChecked"></ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
</ion-list>
|
||||
|
||||
</ion-content>
|
||||
|
||||
<ion-footer translucent="true" *ngIf="ios">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-button (click)="selectAll(false)">Deselect All</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="selectAll(true)">Select All</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Material Design
|
||||
*/
|
||||
|
||||
.md ion-toolbar ion-button {
|
||||
text-transform: capitalize;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.md ion-checkbox {
|
||||
--background-checked: transparent;
|
||||
--border-color: transparent;
|
||||
--border-color-checked: transparent;
|
||||
--checkmark-color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.md ion-list {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* iOS
|
||||
*/
|
||||
|
||||
.ios ion-list-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ios ion-label {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Config, ModalController, NavParams } from '@ionic/angular';
|
||||
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'page-schedule-filter',
|
||||
templateUrl: 'schedule-filter.html',
|
||||
styleUrls: ['./schedule-filter.scss'],
|
||||
})
|
||||
export class ScheduleFilterPage {
|
||||
ios: boolean;
|
||||
|
||||
tracks: {name: string, icon: string, isChecked: boolean}[] = [];
|
||||
|
||||
constructor(
|
||||
public confData: ConferenceData,
|
||||
private config: Config,
|
||||
public modalCtrl: ModalController,
|
||||
public navParams: NavParams
|
||||
) { }
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.ios = this.config.get('mode') === `ios`;
|
||||
|
||||
// passed in array of track names that should be excluded (unchecked)
|
||||
const excludedTrackNames = this.navParams.get('excludedTracks');
|
||||
|
||||
this.confData.getTracks().subscribe((tracks: any[]) => {
|
||||
tracks.forEach(track => {
|
||||
this.tracks.push({
|
||||
name: track.name,
|
||||
icon: track.icon,
|
||||
isChecked: (excludedTrackNames.indexOf(track.name) === -1)
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
selectAll(check: boolean) {
|
||||
// set all to checked or unchecked
|
||||
this.tracks.forEach(track => {
|
||||
track.isChecked = check;
|
||||
});
|
||||
}
|
||||
|
||||
applyFilters() {
|
||||
// Pass back a new array of track names to exclude
|
||||
const excludedTrackNames = this.tracks.filter(c => !c.isChecked).map(c => c.name);
|
||||
this.dismiss(excludedTrackNames);
|
||||
}
|
||||
|
||||
dismiss(data?: any) {
|
||||
// using the injected ModalController this page
|
||||
// can "dismiss" itself and pass back data
|
||||
this.modalCtrl.dismiss(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SchedulePage } from './schedule';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SchedulePage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SchedulePageRoutingModule { }
|
||||
104
examples/ionic-angular/src/app/pages/schedule/schedule.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<ion-header translucent="true">
|
||||
<ion-toolbar>
|
||||
<ion-buttons *ngIf="!showSearchbar" slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-segment *ngIf="ios" [(ngModel)]="segment" (ionChange)="updateSchedule()">
|
||||
<ion-segment-button value="all">
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="favorites">
|
||||
Favorites
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-title *ngIf="!ios && !showSearchbar">Schedule</ion-title>
|
||||
<ion-searchbar *ngIf="showSearchbar" showCancelButton="always" [(ngModel)]="queryText" (ionChange)="updateSchedule()" (ionCancel)="showSearchbar = false" placeholder="Search"></ion-searchbar>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button *ngIf="!ios && !showSearchbar" (click)="showSearchbar = true">
|
||||
<ion-icon slot="icon-only" name="search"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button *ngIf="!showSearchbar" (click)="presentFilter()">
|
||||
<span *ngIf="ios">Filter</span>
|
||||
<span *ngIf="!ios">
|
||||
<ion-icon slot="icon-only" name="options"></ion-icon>
|
||||
</span>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar *ngIf="!ios">
|
||||
<ion-segment [(ngModel)]="segment" (ionChange)="updateSchedule()">
|
||||
<ion-segment-button value="all">
|
||||
All
|
||||
</ion-segment-button>
|
||||
<ion-segment-button value="favorites">
|
||||
Favorites
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Schedule</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar>
|
||||
<ion-searchbar [(ngModel)]="queryText" (ionChange)="updateSchedule()" placeholder="Search"></ion-searchbar>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-list #scheduleList [hidden]="shownSessions === 0">
|
||||
<ion-item-group *ngFor="let group of groups" [hidden]="group.hide">
|
||||
<ion-item-divider sticky>
|
||||
<ion-label>
|
||||
{{group.time}}
|
||||
</ion-label>
|
||||
</ion-item-divider>
|
||||
|
||||
<ion-item-sliding *ngFor="let session of group.sessions" #slidingItem [attr.track]="session.tracks[0] | lowercase"
|
||||
[hidden]="session.hide">
|
||||
<ion-item routerLink="/app/tabs/schedule/session/{{session.id}}">
|
||||
<ion-label>
|
||||
<h3>{{session.name}}</h3>
|
||||
<p>
|
||||
{{session.timeStart}} — {{session.timeEnd}}: {{session.location}}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options>
|
||||
<ion-item-option color="favorite" (click)="addFavorite(slidingItem, session)" *ngIf="segment === 'all'">
|
||||
Favorite
|
||||
</ion-item-option>
|
||||
<ion-item-option color="danger" (click)="removeFavorite(slidingItem, session, 'Remove Favorite')"
|
||||
*ngIf="segment === 'favorites'">
|
||||
Remove
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
</ion-item-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-list-header [hidden]="shownSessions > 0">
|
||||
No Sessions Found
|
||||
</ion-list-header>
|
||||
|
||||
<ion-fab slot="fixed" vertical="bottom" horizontal="end" #fab>
|
||||
<ion-fab-button>
|
||||
<ion-icon name="share-social"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-list side="top">
|
||||
<ion-fab-button color="vimeo" (click)="openSocial('Vimeo', fab)">
|
||||
<ion-icon name="logo-vimeo"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button color="instagram" (click)="openSocial('Instagram', fab)">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button color="twitter" (click)="openSocial('Twitter', fab)">
|
||||
<ion-icon name="logo-twitter"></ion-icon>
|
||||
</ion-fab-button>
|
||||
<ion-fab-button color="facebook" (click)="openSocial('Facebook', fab)">
|
||||
<ion-icon name="logo-facebook"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab-list>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { SchedulePage } from './schedule';
|
||||
import { ScheduleFilterPage } from '../schedule-filter/schedule-filter';
|
||||
import { SchedulePageRoutingModule } from './schedule-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
SchedulePageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SchedulePage,
|
||||
ScheduleFilterPage
|
||||
],
|
||||
entryComponents: [
|
||||
ScheduleFilterPage
|
||||
]
|
||||
})
|
||||
export class ScheduleModule { }
|
||||
35
examples/ionic-angular/src/app/pages/schedule/schedule.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
ion-fab-button {
|
||||
--background: var(--ion-color-step-150, #fff);
|
||||
--background-hover: var(--ion-color-step-200, #f2f2f2);
|
||||
--background-focused: var(--ion-color-step-250, #d9d9d9);
|
||||
|
||||
--color: var(--ion-color-primary, #3880ff);
|
||||
}
|
||||
|
||||
/*
|
||||
* Material Design uses the ripple for activated
|
||||
* so only style the iOS activated background
|
||||
*/
|
||||
.ios ion-fab-button {
|
||||
--background-activated: var(--ion-color-step-250, #d9d9d9);
|
||||
}
|
||||
|
||||
$categories: (
|
||||
ionic: var(--ion-color-primary),
|
||||
angular: #ac282b,
|
||||
communication: #8e8d93,
|
||||
tooling: #fe4c52,
|
||||
services: #fd8b2d,
|
||||
design: #fed035,
|
||||
workshop: #69bb7b,
|
||||
food: #3bc7c4,
|
||||
documentation: #b16be3,
|
||||
navigation: #6600cc
|
||||
);
|
||||
|
||||
@each $track, $value in map-remove($categories) {
|
||||
ion-item-sliding[track='#{$track}'] ion-label {
|
||||
border-left: 2px solid $value;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
140
examples/ionic-angular/src/app/pages/schedule/schedule.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { Component, ViewChild, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AlertController, IonList, IonRouterOutlet, LoadingController, ModalController, ToastController, Config } from '@ionic/angular';
|
||||
|
||||
import { ScheduleFilterPage } from '../schedule-filter/schedule-filter';
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
import { UserData } from '../../providers/user-data';
|
||||
|
||||
@Component({
|
||||
selector: 'page-schedule',
|
||||
templateUrl: 'schedule.html',
|
||||
styleUrls: ['./schedule.scss'],
|
||||
})
|
||||
export class SchedulePage implements OnInit {
|
||||
// Gets a reference to the list element
|
||||
@ViewChild('scheduleList', { static: true }) scheduleList: IonList;
|
||||
|
||||
ios: boolean;
|
||||
dayIndex = 0;
|
||||
queryText = '';
|
||||
segment = 'all';
|
||||
excludeTracks: any = [];
|
||||
shownSessions: any = [];
|
||||
groups: any = [];
|
||||
confDate: string;
|
||||
showSearchbar: boolean;
|
||||
|
||||
constructor(
|
||||
public alertCtrl: AlertController,
|
||||
public confData: ConferenceData,
|
||||
public loadingCtrl: LoadingController,
|
||||
public modalCtrl: ModalController,
|
||||
public router: Router,
|
||||
public routerOutlet: IonRouterOutlet,
|
||||
public toastCtrl: ToastController,
|
||||
public user: UserData,
|
||||
public config: Config
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.updateSchedule();
|
||||
|
||||
this.ios = this.config.get('mode') === 'ios';
|
||||
}
|
||||
|
||||
updateSchedule() {
|
||||
// Close any open sliding items when the schedule updates
|
||||
if (this.scheduleList) {
|
||||
this.scheduleList.closeSlidingItems();
|
||||
}
|
||||
|
||||
this.confData.getTimeline(this.dayIndex, this.queryText, this.excludeTracks, this.segment).subscribe((data: any) => {
|
||||
this.shownSessions = data.shownSessions;
|
||||
this.groups = data.groups;
|
||||
});
|
||||
}
|
||||
|
||||
async presentFilter() {
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: ScheduleFilterPage,
|
||||
swipeToClose: true,
|
||||
presentingElement: this.routerOutlet.nativeEl,
|
||||
componentProps: { excludedTracks: this.excludeTracks }
|
||||
});
|
||||
await modal.present();
|
||||
|
||||
const { data } = await modal.onWillDismiss();
|
||||
if (data) {
|
||||
this.excludeTracks = data;
|
||||
this.updateSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
async addFavorite(slidingItem: HTMLIonItemSlidingElement, sessionData: any) {
|
||||
if (this.user.hasFavorite(sessionData.name)) {
|
||||
// Prompt to remove favorite
|
||||
this.removeFavorite(slidingItem, sessionData, 'Favorite already added');
|
||||
} else {
|
||||
// Add as a favorite
|
||||
this.user.addFavorite(sessionData.name);
|
||||
|
||||
// Close the open item
|
||||
slidingItem.close();
|
||||
|
||||
// Create a toast
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: `${sessionData.name} was successfully added as a favorite.`,
|
||||
duration: 3000,
|
||||
buttons: [{
|
||||
text: 'Close',
|
||||
role: 'cancel'
|
||||
}]
|
||||
});
|
||||
|
||||
// Present the toast at the bottom of the page
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async removeFavorite(slidingItem: HTMLIonItemSlidingElement, sessionData: any, title: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: title,
|
||||
message: 'Would you like to remove this session from your favorites?',
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
handler: () => {
|
||||
// they clicked the cancel button, do not remove the session
|
||||
// close the sliding item and hide the option buttons
|
||||
slidingItem.close();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
handler: () => {
|
||||
// they want to remove this session from their favorites
|
||||
this.user.removeFavorite(sessionData.name);
|
||||
this.updateSchedule();
|
||||
|
||||
// close the sliding item and hide the option buttons
|
||||
slidingItem.close();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
// now present the alert on top of all other content
|
||||
await alert.present();
|
||||
}
|
||||
|
||||
async openSocial(network: string, fab: HTMLIonFabElement) {
|
||||
const loading = await this.loadingCtrl.create({
|
||||
message: `Posting to ${network}`,
|
||||
duration: (Math.random() * 1000) + 500
|
||||
});
|
||||
await loading.present();
|
||||
await loading.onWillDismiss();
|
||||
fab.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SessionDetailPage } from './session-detail';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SessionDetailPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SessionDetailPageRoutingModule { }
|
||||
@@ -0,0 +1,47 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [defaultHref]="defaultHref"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="toggleFavorite()">
|
||||
<ion-icon *ngIf="!isFavorite" slot="icon-only" name="star-outline"></ion-icon>
|
||||
<ion-icon *ngIf="isFavorite" slot="icon-only" name="star"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button (click)="shareSession()">
|
||||
<ion-icon slot="icon-only" name="share"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div *ngIf="session" class="ion-padding">
|
||||
<h1>{{session.name}}</h1>
|
||||
<span *ngFor="let track of session?.tracks" [class]="'session-track-'+track.toLowerCase()">{{track}}</span>
|
||||
<p>{{session.description}}</p>
|
||||
<ion-text color="medium">
|
||||
{{session.timeStart}} – {{session.timeEnd}}
|
||||
<br /> {{session.location}}
|
||||
</ion-text>
|
||||
</div>
|
||||
|
||||
<ion-list>
|
||||
<ion-item (click)="sessionClick('watch')" button>
|
||||
<ion-label color="primary">Watch</ion-label>
|
||||
</ion-item>
|
||||
<ion-item (click)="sessionClick('add to calendar')" button>
|
||||
<ion-label color="primary">Add to Calendar</ion-label>
|
||||
</ion-item>
|
||||
<ion-item (click)="sessionClick('mark as unwatched')" button>
|
||||
<ion-label color="primary">Mark as Unwatched</ion-label>
|
||||
</ion-item>
|
||||
<ion-item (click)="sessionClick('download video')" button>
|
||||
<ion-label color="primary">Download Video</ion-label>
|
||||
<ion-icon slot="end" color="primary" size="small" name="cloud-download"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item (click)="sessionClick('leave feedback')" button>
|
||||
<ion-label color="primary">Leave Feedback</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SessionDetailPage } from './session-detail';
|
||||
import { SessionDetailPageRoutingModule } from './session-detail-routing.module';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SessionDetailPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SessionDetailPage,
|
||||
]
|
||||
})
|
||||
export class SessionDetailModule { }
|
||||
@@ -0,0 +1,86 @@
|
||||
.session-track-ionic {
|
||||
color: var(--ion-color-primary);
|
||||
}
|
||||
|
||||
.session-track-angular {
|
||||
color: var(--ion-color-angular);
|
||||
}
|
||||
|
||||
.session-track-communication {
|
||||
color: var(--ion-color-communication);
|
||||
}
|
||||
|
||||
.session-track-tooling {
|
||||
color: var(--ion-color-tooling);
|
||||
}
|
||||
|
||||
.session-track-services {
|
||||
color: var(--ion-color-services);
|
||||
}
|
||||
|
||||
.session-track-design {
|
||||
color: var(--ion-color-design);
|
||||
}
|
||||
|
||||
.session-track-workshop {
|
||||
color: var(--ion-color-workshop);
|
||||
}
|
||||
|
||||
.session-track-food {
|
||||
color: var(--ion-color-food);
|
||||
}
|
||||
|
||||
.session-track-documentation {
|
||||
color: var(--ion-color-documentation);
|
||||
}
|
||||
|
||||
.session-track-navigation {
|
||||
color: var(--ion-color-navigation);
|
||||
}
|
||||
|
||||
/* Favorite Icon
|
||||
* --------------------------------------------------------
|
||||
*/
|
||||
|
||||
.show-favorite {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon-heart-empty,
|
||||
.icon-heart {
|
||||
--border-radius: 50%;
|
||||
--padding-start: 0;
|
||||
--padding-end: 0;
|
||||
|
||||
position: absolute;
|
||||
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
font-size: 16px;
|
||||
|
||||
transition: transform 300ms ease;
|
||||
}
|
||||
|
||||
.icon-heart-empty {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.icon-heart {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.show-favorite .icon-heart-empty {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UserData } from '../../providers/user-data';
|
||||
|
||||
@Component({
|
||||
selector: 'page-session-detail',
|
||||
styleUrls: ['./session-detail.scss'],
|
||||
templateUrl: 'session-detail.html'
|
||||
})
|
||||
export class SessionDetailPage {
|
||||
session: any;
|
||||
isFavorite = false;
|
||||
defaultHref = '';
|
||||
|
||||
constructor(
|
||||
private dataProvider: ConferenceData,
|
||||
private userProvider: UserData,
|
||||
private route: ActivatedRoute
|
||||
) { }
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.dataProvider.load().subscribe((data: any) => {
|
||||
if (data && data.schedule && data.schedule[0] && data.schedule[0].groups) {
|
||||
const sessionId = this.route.snapshot.paramMap.get('sessionId');
|
||||
for (const group of data.schedule[0].groups) {
|
||||
if (group && group.sessions) {
|
||||
for (const session of group.sessions) {
|
||||
if (session && session.id === sessionId) {
|
||||
this.session = session;
|
||||
|
||||
this.isFavorite = this.userProvider.hasFavorite(
|
||||
this.session.name
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ionViewDidEnter() {
|
||||
this.defaultHref = `/app/tabs/schedule`;
|
||||
}
|
||||
|
||||
sessionClick(item: string) {
|
||||
console.log('Clicked', item);
|
||||
}
|
||||
|
||||
toggleFavorite() {
|
||||
if (this.userProvider.hasFavorite(this.session.name)) {
|
||||
this.userProvider.removeFavorite(this.session.name);
|
||||
this.isFavorite = false;
|
||||
} else {
|
||||
this.userProvider.addFavorite(this.session.name);
|
||||
this.isFavorite = true;
|
||||
}
|
||||
}
|
||||
|
||||
shareSession() {
|
||||
console.log('Clicked share session');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SignupPage } from './signup';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SignupPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SignupPageRoutingModule { }
|
||||
46
examples/ionic-angular/src/app/pages/signup/signup.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Signup</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
|
||||
<div class="signup-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic Logo">
|
||||
</div>
|
||||
|
||||
<form #signupForm="ngForm" novalidate>
|
||||
<ion-list lines="none">
|
||||
<ion-item>
|
||||
<ion-label position="stacked" color="primary">Username</ion-label>
|
||||
<ion-input [(ngModel)]="signup.username" name="username" type="text" #username="ngModel" required>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-text color="danger">
|
||||
<p [hidden]="username.valid || submitted == false" class="ion-padding-start">
|
||||
Username is required
|
||||
</p>
|
||||
</ion-text>
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="stacked" color="primary">Password</ion-label>
|
||||
<ion-input [(ngModel)]="signup.password" name="password" type="password" #password="ngModel" required>
|
||||
</ion-input>
|
||||
</ion-item>
|
||||
<ion-text color="danger">
|
||||
<p [hidden]="password.valid || submitted == false" class="ion-padding-start">
|
||||
Password is required
|
||||
</p>
|
||||
</ion-text>
|
||||
</ion-list>
|
||||
|
||||
<div class="ion-padding">
|
||||
<ion-button (click)="onSignup(signupForm)" type="submit" expand="block">Create</ion-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</ion-content>
|
||||
20
examples/ionic-angular/src/app/pages/signup/signup.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { SignupPage } from './signup';
|
||||
import { SignupPageRoutingModule } from './signup-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
SignupPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SignupPage,
|
||||
]
|
||||
})
|
||||
export class SignUpModule { }
|
||||
13
examples/ionic-angular/src/app/pages/signup/signup.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.signup-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.signup-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
33
examples/ionic-angular/src/app/pages/signup/signup.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { UserData } from '../../providers/user-data';
|
||||
|
||||
import { UserOptions } from '../../interfaces/user-options';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'page-signup',
|
||||
templateUrl: 'signup.html',
|
||||
styleUrls: ['./signup.scss'],
|
||||
})
|
||||
export class SignupPage {
|
||||
signup: UserOptions = { username: '', password: '' };
|
||||
submitted = false;
|
||||
|
||||
constructor(
|
||||
public router: Router,
|
||||
public userData: UserData
|
||||
) {}
|
||||
|
||||
onSignup(form: NgForm) {
|
||||
this.submitted = true;
|
||||
|
||||
if (form.valid) {
|
||||
this.userData.signup(this.signup.username);
|
||||
this.router.navigateByUrl('/app/tabs/schedule');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SpeakerDetailPage } from './speaker-detail';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SpeakerDetailPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SpeakerDetailPageRoutingModule { }
|
||||
@@ -0,0 +1,43 @@
|
||||
<ion-content class="speaker-detail">
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button defaultHref="/app/tabs/speakers"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-buttons *ngIf="speaker" slot="end">
|
||||
<ion-button (click)="openContact(speaker)">
|
||||
<ion-icon slot="icon-only" ios="call-outline" md="call-sharp"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button (click)="openSpeakerShare(speaker)">
|
||||
<ion-icon slot="icon-only" ios="share-outline" md="share-sharp"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<div class="speaker-background">
|
||||
<img [src]="speaker?.profilePic" [alt]="speaker?.name">
|
||||
<h2>{{speaker?.name}}</h2>
|
||||
</div>
|
||||
|
||||
<div class="ion-padding speaker-detail">
|
||||
<p>{{speaker?.about}} Say hello on social media!</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<ion-chip color="twitter" button (click)="openExternalUrl('https://twitter.com/' + speaker.twitter)">
|
||||
<ion-icon name="logo-twitter"></ion-icon>
|
||||
<ion-label>Twitter</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip color="dark" button (click)="openExternalUrl('https://github.com/ionic-team/ionic')">
|
||||
<ion-icon name="logo-github"></ion-icon>
|
||||
<ion-label>GitHub</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip color="instagram" button (click)="openExternalUrl('https://instagram.com/ionicframework')">
|
||||
<ion-icon name="logo-instagram"></ion-icon>
|
||||
<ion-label>Instagram</ion-label>
|
||||
</ion-chip>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SpeakerDetailPage } from './speaker-detail';
|
||||
import { SpeakerDetailPageRoutingModule } from './speaker-detail-routing.module';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SpeakerDetailPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SpeakerDetailPage,
|
||||
]
|
||||
})
|
||||
export class SpeakerDetailModule { }
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Speaker Background
|
||||
*/
|
||||
|
||||
ion-toolbar {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
--background: transparent;
|
||||
--color: white;
|
||||
}
|
||||
|
||||
ion-toolbar ion-button,
|
||||
ion-toolbar ion-back-button,
|
||||
ion-toolbar ion-menu-button {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.speaker-background {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
||||
padding-top: var(--ion-safe-area-top);
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
height: calc(250px + var(--ion-safe-area-top));
|
||||
|
||||
background: center / cover url(/assets/img/speaker-background.png) no-repeat;
|
||||
}
|
||||
|
||||
.speaker-background img {
|
||||
width: 70px;
|
||||
border-radius: 50%;
|
||||
margin-top: calc(-1 * var(--ion-safe-area-top));
|
||||
}
|
||||
|
||||
.speaker-background h2 {
|
||||
position: absolute;
|
||||
|
||||
bottom: 10px;
|
||||
|
||||
color: white;
|
||||
}
|
||||
|
||||
.md .speaker-background {
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
|
||||
}
|
||||
|
||||
.ios .speaker-background {
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0px 4px 16px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Speaker Details
|
||||
*/
|
||||
|
||||
.speaker-detail p {
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.speaker-detail hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
background: var(--ion-color-step-150, #d7d8da);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
import { ActionSheetController } from '@ionic/angular';
|
||||
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
|
||||
|
||||
@Component({
|
||||
selector: 'page-speaker-detail',
|
||||
templateUrl: 'speaker-detail.html',
|
||||
styleUrls: ['./speaker-detail.scss'],
|
||||
})
|
||||
export class SpeakerDetailPage {
|
||||
speaker: any;
|
||||
|
||||
constructor(
|
||||
private dataProvider: ConferenceData,
|
||||
private route: ActivatedRoute,
|
||||
public actionSheetCtrl: ActionSheetController,
|
||||
public confData: ConferenceData,
|
||||
public inAppBrowser: InAppBrowser,
|
||||
) {}
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.dataProvider.load().subscribe((data: any) => {
|
||||
const speakerId = this.route.snapshot.paramMap.get('speakerId');
|
||||
if (data && data.speakers) {
|
||||
for (const speaker of data.speakers) {
|
||||
if (speaker && speaker.id === speakerId) {
|
||||
this.speaker = speaker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openExternalUrl(url: string) {
|
||||
this.inAppBrowser.create(
|
||||
url,
|
||||
'_blank'
|
||||
);
|
||||
}
|
||||
|
||||
async openSpeakerShare(speaker: any) {
|
||||
const actionSheet = await this.actionSheetCtrl.create({
|
||||
header: 'Share ' + speaker.name,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Copy Link',
|
||||
handler: () => {
|
||||
console.log(
|
||||
'Copy link clicked on https://twitter.com/' + speaker.twitter
|
||||
);
|
||||
if (
|
||||
(window as any).cordova &&
|
||||
(window as any).cordova.plugins.clipboard
|
||||
) {
|
||||
(window as any).cordova.plugins.clipboard.copy(
|
||||
'https://twitter.com/' + speaker.twitter
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Share via ...'
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await actionSheet.present();
|
||||
}
|
||||
|
||||
async openContact(speaker: any) {
|
||||
const mode = 'ios'; // this.config.get('mode');
|
||||
|
||||
const actionSheet = await this.actionSheetCtrl.create({
|
||||
header: 'Contact ' + speaker.name,
|
||||
buttons: [
|
||||
{
|
||||
text: `Email ( ${speaker.email} )`,
|
||||
icon: mode !== 'ios' ? 'mail' : null,
|
||||
handler: () => {
|
||||
window.open('mailto:' + speaker.email);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: `Call ( ${speaker.phone} )`,
|
||||
icon: mode !== 'ios' ? 'call' : null,
|
||||
handler: () => {
|
||||
window.open('tel:' + speaker.phone);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
await actionSheet.present();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SpeakerListPage } from './speaker-list';
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SpeakerListPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SpeakerListPageRoutingModule {}
|
||||
@@ -0,0 +1,52 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Speakers</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content fullscreen="true">
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">Speakers</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-grid fixed>
|
||||
<ion-row>
|
||||
<ion-col size="12" size-md="6" *ngFor="let speaker of speakers">
|
||||
<ion-card class="speaker-card">
|
||||
<ion-card-header>
|
||||
<ion-item detail="false" lines="none" class="speaker-item" routerLink="/app/tabs/speakers/speaker-details/{{speaker.id}}">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="speaker.profilePic" [alt]="speaker.name + ' profile picture'">
|
||||
</ion-avatar>
|
||||
<ion-label>
|
||||
<h2>{{speaker.name}}</h2>
|
||||
<p>{{speaker.title}}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content>
|
||||
<ion-list lines="none">
|
||||
<ion-item *ngFor="let session of speaker.sessions" detail="false" routerLink="/app/tabs/speakers/session/{{session.id}}">
|
||||
<ion-label>
|
||||
<h3>{{session.name}}</h3>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item detail="false" routerLink="/app/tabs/speakers/speaker-details/{{speaker.id}}">
|
||||
<ion-label>
|
||||
<h3>About {{speaker.name}}</h3>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { SpeakerListPage } from './speaker-list';
|
||||
import { SpeakerListPageRoutingModule } from './speaker-list-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SpeakerListPageRoutingModule
|
||||
],
|
||||
declarations: [SpeakerListPage],
|
||||
})
|
||||
export class SpeakerListModule {}
|
||||
@@ -0,0 +1,46 @@
|
||||
.speaker-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Due to the fact the cards are inside of columns the margins don't overlap
|
||||
* properly so we want to remove the extra margin between cards
|
||||
*/
|
||||
ion-col:not(:last-of-type) .speaker-card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.speaker-card .speaker-item {
|
||||
--min-height: 85px;
|
||||
}
|
||||
|
||||
.speaker-card .speaker-item h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.speaker-card .speaker-item p {
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.speaker-card ion-card-content {
|
||||
flex: 1 1 auto;
|
||||
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ios ion-list {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.md ion-list {
|
||||
border-top: 1px solid var(--ion-color-step-150, #d7d8da);
|
||||
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { ActionSheetController } from '@ionic/angular';
|
||||
|
||||
import { InAppBrowser } from '@ionic-native/in-app-browser/ngx';
|
||||
import { SpeakerListPage } from './speaker-list';
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
|
||||
const confDataSub = {};
|
||||
|
||||
describe('SpeakerListPage', () => {
|
||||
let fixture, app;
|
||||
beforeEach(async(() => {
|
||||
const actionSheetSpy = jasmine.createSpyObj('ActionSheetController', [
|
||||
'create'
|
||||
]);
|
||||
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
|
||||
const iabSpy = jasmine.createSpyObj('InAppBrowser', ['create']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SpeakerListPage],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [
|
||||
{ provide: ActionSheetController, useValue: actionSheetSpy },
|
||||
{ provide: InAppBrowser, useValue: iabSpy },
|
||||
{ provide: Router, useValue: routerSpy },
|
||||
{ provide: ConferenceData, useValue: confDataSub }
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SpeakerListPage);
|
||||
app = fixture.debugElement.componentInstance;
|
||||
});
|
||||
it('should create the speaker list page', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ConferenceData } from '../../providers/conference-data';
|
||||
|
||||
@Component({
|
||||
selector: 'page-speaker-list',
|
||||
templateUrl: 'speaker-list.html',
|
||||
styleUrls: ['./speaker-list.scss'],
|
||||
})
|
||||
export class SpeakerListPage {
|
||||
speakers: any[] = [];
|
||||
|
||||
constructor(public confData: ConferenceData) {}
|
||||
|
||||
ionViewDidEnter() {
|
||||
this.confData.getSpeakers().subscribe((speakers: any[]) => {
|
||||
this.speakers = speakers;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { SupportPage } from './support';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SupportPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SupportPageRoutingModule { }
|
||||
34
examples/ionic-angular/src/app/pages/support/support.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Support</ion-title>
|
||||
</ion-toolbar>
|
||||
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div class="support-logo">
|
||||
<img src="assets/img/appicon.svg" alt="Ionic Logo">
|
||||
</div>
|
||||
|
||||
<form #submitForm="ngForm" novalidate (ngSubmit)="submit(submitForm)">
|
||||
<ion-list lines="none">
|
||||
<ion-item>
|
||||
<ion-label position="stacked" color="primary">Enter your support message below</ion-label>
|
||||
<ion-textarea [(ngModel)]="supportMessage" name="supportQuestion" #supportQuestion="ngModel" rows="6" required></ion-textarea>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-text color="danger">
|
||||
<p [hidden]="supportQuestion.valid || submitted === false" class="ion-padding-start">
|
||||
Support message is required
|
||||
</p>
|
||||
</ion-text>
|
||||
|
||||
<div class="ion-padding">
|
||||
<ion-button expand="block" type="submit">Submit</ion-button>
|
||||
</div>
|
||||
</form>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { SupportPage } from './support';
|
||||
import { SupportPageRoutingModule } from './support-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
SupportPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
SupportPage,
|
||||
]
|
||||
})
|
||||
export class SupportModule { }
|
||||
13
examples/ionic-angular/src/app/pages/support/support.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.support-logo {
|
||||
padding: 20px 0;
|
||||
min-height: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.support-logo img {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
65
examples/ionic-angular/src/app/pages/support/support.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { AlertController, ToastController } from '@ionic/angular';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'page-support',
|
||||
templateUrl: 'support.html',
|
||||
styleUrls: ['./support.scss'],
|
||||
})
|
||||
export class SupportPage {
|
||||
submitted = false;
|
||||
supportMessage: string;
|
||||
|
||||
constructor(
|
||||
public alertCtrl: AlertController,
|
||||
public toastCtrl: ToastController
|
||||
) { }
|
||||
|
||||
async ionViewDidEnter() {
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: 'This does not actually send a support request.',
|
||||
duration: 3000
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
async submit(form: NgForm) {
|
||||
this.submitted = true;
|
||||
|
||||
if (form.valid) {
|
||||
this.supportMessage = '';
|
||||
this.submitted = false;
|
||||
|
||||
const toast = await this.toastCtrl.create({
|
||||
message: 'Your support request has been sent.',
|
||||
duration: 3000
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
|
||||
// If the user enters text in the support question and then navigates
|
||||
// without submitting first, ask if they meant to leave the page
|
||||
// async ionViewCanLeave(): Promise<boolean> {
|
||||
// // If the support message is empty we should just navigate
|
||||
// if (!this.supportMessage || this.supportMessage.trim().length === 0) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// return new Promise((resolve: any, reject: any) => {
|
||||
// const alert = await this.alertCtrl.create({
|
||||
// title: 'Leave this page?',
|
||||
// message: 'Are you sure you want to leave this page? Your support message will not be submitted.',
|
||||
// buttons: [
|
||||
// { text: 'Stay', handler: reject },
|
||||
// { text: 'Leave', role: 'cancel', handler: resolve }
|
||||
// ]
|
||||
// });
|
||||
|
||||
// await alert.present();
|
||||
// });
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { TabsPage } from './tabs-page';
|
||||
import { SchedulePage } from '../schedule/schedule';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'tabs',
|
||||
component: TabsPage,
|
||||
children: [
|
||||
{
|
||||
path: 'schedule',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: SchedulePage,
|
||||
},
|
||||
{
|
||||
path: 'session/:sessionId',
|
||||
loadChildren: () => import('../session-detail/session-detail.module').then(m => m.SessionDetailModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'speakers',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('../speaker-list/speaker-list.module').then(m => m.SpeakerListModule)
|
||||
},
|
||||
{
|
||||
path: 'session/:sessionId',
|
||||
loadChildren: () => import('../session-detail/session-detail.module').then(m => m.SessionDetailModule)
|
||||
},
|
||||
{
|
||||
path: 'speaker-details/:speakerId',
|
||||
loadChildren: () => import('../speaker-detail/speaker-detail.module').then(m => m.SpeakerDetailModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'map',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('../map/map.module').then(m => m.MapModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('../about/about.module').then(m => m.AboutModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: '/app/tabs/schedule',
|
||||
pathMatch: 'full'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class TabsPageRoutingModule { }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<ion-tabs>
|
||||
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="schedule">
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<ion-label>Schedule</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="speakers">
|
||||
<ion-icon name="people"></ion-icon>
|
||||
<ion-label>Speakers</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="map">
|
||||
<ion-icon name="location"></ion-icon>
|
||||
<ion-label>Map</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button tab="about">
|
||||
<ion-icon name="information-circle"></ion-icon>
|
||||
<ion-label>About</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
|
||||
</ion-tabs>
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { TabsPage } from './tabs-page';
|
||||
import { TabsPageRoutingModule } from './tabs-page-routing.module';
|
||||
|
||||
import { AboutModule } from '../about/about.module';
|
||||
import { MapModule } from '../map/map.module';
|
||||
import { ScheduleModule } from '../schedule/schedule.module';
|
||||
import { SessionDetailModule } from '../session-detail/session-detail.module';
|
||||
import { SpeakerDetailModule } from '../speaker-detail/speaker-detail.module';
|
||||
import { SpeakerListModule } from '../speaker-list/speaker-list.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AboutModule,
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
MapModule,
|
||||
ScheduleModule,
|
||||
SessionDetailModule,
|
||||
SpeakerDetailModule,
|
||||
SpeakerListModule,
|
||||
TabsPageRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
TabsPage,
|
||||
]
|
||||
})
|
||||
export class TabsModule { }
|
||||
@@ -0,0 +1,7 @@
|
||||
.tabbar {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
max-width: 200px;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
|
||||
import { TabsPage } from './tabs-page';
|
||||
|
||||
describe('TabsPage', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TabsPage],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the tabs page', () => {
|
||||
const fixture = TestBed.createComponent(TabsPage);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'tabs-page.html'
|
||||
})
|
||||
export class TabsPage {}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { TutorialPage } from './tutorial';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: TutorialPage
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class TutorialPageRoutingModule { }
|
||||
49
examples/ionic-angular/src/app/pages/tutorial/tutorial.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<ion-header no-border>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button color='primary' (click)="startApp()" [hidden]="!showSkip">Skip</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content fullscreen="true">
|
||||
<ion-slides #slides (ionSlideWillChange)="onSlideChangeStart($event)" pager="false">
|
||||
<ion-slide>
|
||||
<img src="assets/img/ica-slidebox-img-1.png" class="slide-image" />
|
||||
<h2 class="slide-title">
|
||||
Welcome to
|
||||
<b>ICA</b>
|
||||
</h2>
|
||||
<p>
|
||||
The
|
||||
<b>ionic conference app</b> is a practical preview of the ionic framework in action, and a demonstration of proper code
|
||||
use.
|
||||
</p>
|
||||
</ion-slide>
|
||||
|
||||
<ion-slide>
|
||||
<img src="assets/img/ica-slidebox-img-2.png" class="slide-image" />
|
||||
<h2 class="slide-title">What is Ionic?</h2>
|
||||
<p>
|
||||
<b>Ionic Framework</b> is an open source SDK that enables developers to build high quality mobile apps with web technologies
|
||||
like HTML, CSS, and JavaScript.</p>
|
||||
</ion-slide>
|
||||
|
||||
<ion-slide>
|
||||
<img src="assets/img/ica-slidebox-img-3.png" class="slide-image" />
|
||||
<h2 class="slide-title">What is Ionic Appflow?</h2>
|
||||
<p>
|
||||
<b>Ionic Appflow</b> is a powerful set of services and features built on top of Ionic Framework that brings a totally new
|
||||
level of app development agility to mobile dev teams.</p>
|
||||
</ion-slide>
|
||||
|
||||
<ion-slide>
|
||||
<img src="assets/img/ica-slidebox-img-4.png" class="slide-image" />
|
||||
<h2 class="slide-title">Ready to Play?</h2>
|
||||
<ion-button fill="clear" (click)="startApp()">
|
||||
Continue
|
||||
<ion-icon slot="end" name="arrow-forward"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-slide>
|
||||
</ion-slides>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { TutorialPage } from './tutorial';
|
||||
import { TutorialPageRoutingModule } from './tutorial-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TutorialPageRoutingModule
|
||||
],
|
||||
declarations: [TutorialPage],
|
||||
entryComponents: [TutorialPage],
|
||||
})
|
||||
export class TutorialModule {}
|
||||
34
examples/ionic-angular/src/app/pages/tutorial/tutorial.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
ion-toolbar {
|
||||
--background: transparent;
|
||||
--border-color: transparent;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
|
||||
.slide-image {
|
||||
max-height: 50%;
|
||||
max-width: 60%;
|
||||
margin: 36px 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 0 40px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--ion-color-step-600, #60646b);
|
||||
|
||||
b {
|
||||
color: var(--ion-text-color, #000000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { MenuController } from '@ionic/angular';
|
||||
|
||||
import { TutorialPage } from './tutorial';
|
||||
|
||||
import { IonicStorageModule } from '@ionic/storage';
|
||||
describe('TutorialPage', () => {
|
||||
let fixture, app;
|
||||
beforeEach(async(() => {
|
||||
const menuSpy = jasmine.createSpyObj('MenuController', [
|
||||
'toggle',
|
||||
'enable'
|
||||
]);
|
||||
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TutorialPage],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [IonicStorageModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: MenuController, useValue: menuSpy },
|
||||
{ provide: Router, useValue: routerSpy }
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TutorialPage);
|
||||
app = fixture.debugElement.componentInstance;
|
||||
});
|
||||
it('should create the tutorial page', () => {
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should check the tutorial status', async () => {
|
||||
const didTuts = await app.storage.get('ion_did_tutorial');
|
||||
expect(didTuts).toBeFalsy();
|
||||
});
|
||||
});
|
||||
50
examples/ionic-angular/src/app/pages/tutorial/tutorial.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { MenuController, IonSlides } from '@ionic/angular';
|
||||
|
||||
import { Storage } from '@ionic/storage';
|
||||
|
||||
@Component({
|
||||
selector: 'page-tutorial',
|
||||
templateUrl: 'tutorial.html',
|
||||
styleUrls: ['./tutorial.scss'],
|
||||
})
|
||||
export class TutorialPage {
|
||||
showSkip = true;
|
||||
|
||||
@ViewChild('slides', { static: true }) slides: IonSlides;
|
||||
|
||||
constructor(
|
||||
public menu: MenuController,
|
||||
public router: Router,
|
||||
public storage: Storage
|
||||
) {}
|
||||
|
||||
startApp() {
|
||||
this.router
|
||||
.navigateByUrl('/app/tabs/schedule', { replaceUrl: true })
|
||||
.then(() => this.storage.set('ion_did_tutorial', true));
|
||||
}
|
||||
|
||||
onSlideChangeStart(event) {
|
||||
event.target.isEnd().then(isEnd => {
|
||||
this.showSkip = !isEnd;
|
||||
});
|
||||
}
|
||||
|
||||
ionViewWillEnter() {
|
||||
this.storage.get('ion_did_tutorial').then(res => {
|
||||
if (res === true) {
|
||||
this.router.navigateByUrl('/app/tabs/schedule', { replaceUrl: true });
|
||||
}
|
||||
});
|
||||
|
||||
this.menu.enable(false);
|
||||
}
|
||||
|
||||
ionViewDidLeave() {
|
||||
// enable the root left menu when leaving the tutorial page
|
||||
this.menu.enable(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanLoad, Router } from '@angular/router';
|
||||
import { Storage } from '@ionic/storage';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CheckTutorial implements CanLoad {
|
||||
constructor(private storage: Storage, private router: Router) {}
|
||||
|
||||
canLoad() {
|
||||
return this.storage.get('ion_did_tutorial').then(res => {
|
||||
if (res) {
|
||||
this.router.navigate(['/app', 'tabs', 'schedule']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
161
examples/ionic-angular/src/app/providers/conference-data.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { UserData } from './user-data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ConferenceData {
|
||||
data: any;
|
||||
|
||||
constructor(public http: HttpClient, public user: UserData) {}
|
||||
|
||||
load(): any {
|
||||
if (this.data) {
|
||||
return of(this.data);
|
||||
} else {
|
||||
return this.http
|
||||
.get('assets/data/data.json')
|
||||
.pipe(map(this.processData, this));
|
||||
}
|
||||
}
|
||||
|
||||
processData(data: any) {
|
||||
// just some good 'ol JS fun with objects and arrays
|
||||
// build up the data by linking speakers to sessions
|
||||
this.data = data;
|
||||
|
||||
// loop through each day in the schedule
|
||||
this.data.schedule.forEach((day: any) => {
|
||||
// loop through each timeline group in the day
|
||||
day.groups.forEach((group: any) => {
|
||||
// loop through each session in the timeline group
|
||||
group.sessions.forEach((session: any) => {
|
||||
session.speakers = [];
|
||||
if (session.speakerNames) {
|
||||
session.speakerNames.forEach((speakerName: any) => {
|
||||
const speaker = this.data.speakers.find(
|
||||
(s: any) => s.name === speakerName
|
||||
);
|
||||
if (speaker) {
|
||||
session.speakers.push(speaker);
|
||||
speaker.sessions = speaker.sessions || [];
|
||||
speaker.sessions.push(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
getTimeline(
|
||||
dayIndex: number,
|
||||
queryText = '',
|
||||
excludeTracks: any[] = [],
|
||||
segment = 'all'
|
||||
) {
|
||||
return this.load().pipe(
|
||||
map((data: any) => {
|
||||
const day = data.schedule[dayIndex];
|
||||
day.shownSessions = 0;
|
||||
|
||||
queryText = queryText.toLowerCase().replace(/,|\.|-/g, ' ');
|
||||
const queryWords = queryText.split(' ').filter(w => !!w.trim().length);
|
||||
|
||||
day.groups.forEach((group: any) => {
|
||||
group.hide = true;
|
||||
|
||||
group.sessions.forEach((session: any) => {
|
||||
// check if this session should show or not
|
||||
this.filterSession(session, queryWords, excludeTracks, segment);
|
||||
|
||||
if (!session.hide) {
|
||||
// if this session is not hidden then this group should show
|
||||
group.hide = false;
|
||||
day.shownSessions++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return day;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
filterSession(
|
||||
session: any,
|
||||
queryWords: string[],
|
||||
excludeTracks: any[],
|
||||
segment: string
|
||||
) {
|
||||
let matchesQueryText = false;
|
||||
if (queryWords.length) {
|
||||
// of any query word is in the session name than it passes the query test
|
||||
queryWords.forEach((queryWord: string) => {
|
||||
if (session.name.toLowerCase().indexOf(queryWord) > -1) {
|
||||
matchesQueryText = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if there are no query words then this session passes the query test
|
||||
matchesQueryText = true;
|
||||
}
|
||||
|
||||
// if any of the sessions tracks are not in the
|
||||
// exclude tracks then this session passes the track test
|
||||
let matchesTracks = false;
|
||||
session.tracks.forEach((trackName: string) => {
|
||||
if (excludeTracks.indexOf(trackName) === -1) {
|
||||
matchesTracks = true;
|
||||
}
|
||||
});
|
||||
|
||||
// if the segment is 'favorites', but session is not a user favorite
|
||||
// then this session does not pass the segment test
|
||||
let matchesSegment = false;
|
||||
if (segment === 'favorites') {
|
||||
if (this.user.hasFavorite(session.name)) {
|
||||
matchesSegment = true;
|
||||
}
|
||||
} else {
|
||||
matchesSegment = true;
|
||||
}
|
||||
|
||||
// all tests must be true if it should not be hidden
|
||||
session.hide = !(matchesQueryText && matchesTracks && matchesSegment);
|
||||
}
|
||||
|
||||
getSpeakers() {
|
||||
return this.load().pipe(
|
||||
map((data: any) => {
|
||||
return data.speakers.sort((a: any, b: any) => {
|
||||
const aName = a.name.split(' ').pop();
|
||||
const bName = b.name.split(' ').pop();
|
||||
return aName.localeCompare(bName);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getTracks() {
|
||||
return this.load().pipe(
|
||||
map((data: any) => {
|
||||
return data.tracks.sort();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getMap() {
|
||||
return this.load().pipe(
|
||||
map((data: any) => {
|
||||
return data.map;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
75
examples/ionic-angular/src/app/providers/user-data.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Storage } from '@ionic/storage';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserData {
|
||||
favorites: string[] = [];
|
||||
HAS_LOGGED_IN = 'hasLoggedIn';
|
||||
HAS_SEEN_TUTORIAL = 'hasSeenTutorial';
|
||||
|
||||
constructor(
|
||||
public storage: Storage
|
||||
) { }
|
||||
|
||||
hasFavorite(sessionName: string): boolean {
|
||||
return (this.favorites.indexOf(sessionName) > -1);
|
||||
}
|
||||
|
||||
addFavorite(sessionName: string): void {
|
||||
this.favorites.push(sessionName);
|
||||
}
|
||||
|
||||
removeFavorite(sessionName: string): void {
|
||||
const index = this.favorites.indexOf(sessionName);
|
||||
if (index > -1) {
|
||||
this.favorites.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
login(username: string): Promise<any> {
|
||||
return this.storage.set(this.HAS_LOGGED_IN, true).then(() => {
|
||||
this.setUsername(username);
|
||||
return window.dispatchEvent(new CustomEvent('user:login'));
|
||||
});
|
||||
}
|
||||
|
||||
signup(username: string): Promise<any> {
|
||||
return this.storage.set(this.HAS_LOGGED_IN, true).then(() => {
|
||||
this.setUsername(username);
|
||||
return window.dispatchEvent(new CustomEvent('user:signup'));
|
||||
});
|
||||
}
|
||||
|
||||
logout(): Promise<any> {
|
||||
return this.storage.remove(this.HAS_LOGGED_IN).then(() => {
|
||||
return this.storage.remove('username');
|
||||
}).then(() => {
|
||||
window.dispatchEvent(new CustomEvent('user:logout'));
|
||||
});
|
||||
}
|
||||
|
||||
setUsername(username: string): Promise<any> {
|
||||
return this.storage.set('username', username);
|
||||
}
|
||||
|
||||
getUsername(): Promise<string> {
|
||||
return this.storage.get('username').then((value) => {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
isLoggedIn(): Promise<boolean> {
|
||||
return this.storage.get(this.HAS_LOGGED_IN).then((value) => {
|
||||
return value === true;
|
||||
});
|
||||
}
|
||||
|
||||
checkHasSeenTutorial(): Promise<string> {
|
||||
return this.storage.get(this.HAS_SEEN_TUTORIAL).then((value) => {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
467
examples/ionic-angular/src/assets/data/data.json
Executable file
@@ -0,0 +1,467 @@
|
||||
{
|
||||
"schedule": [
|
||||
{
|
||||
"date": "2047-05-17",
|
||||
"groups": [
|
||||
{
|
||||
"time": "8:00 am",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Breakfast",
|
||||
"timeStart": "8:00 am",
|
||||
"timeEnd": "9:00 am",
|
||||
"location": "Dining Hall",
|
||||
"tracks": ["Food"],
|
||||
"id": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "9:15 am",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Getting Started with Ionic",
|
||||
"location": "Hall 2",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Ted Turtle"],
|
||||
"timeStart": "9:30 am",
|
||||
"timeEnd": "9:45 am",
|
||||
"tracks": ["Ionic"],
|
||||
"id": "2"
|
||||
},
|
||||
{
|
||||
"name": "Ionic Tooling",
|
||||
"location": "Executive Ballroom",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Rachel Rabbit"],
|
||||
"timeStart": "9:45 am",
|
||||
"timeEnd": "10:00 am",
|
||||
"tracks": ["Tooling"],
|
||||
"id": "3"
|
||||
},
|
||||
{
|
||||
"name": "University of Ionic",
|
||||
"location": "Hall 3",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Ellie Elephant"],
|
||||
"timeStart": "9:15 am",
|
||||
"timeEnd": "9:30 am",
|
||||
"tracks": ["Ionic"],
|
||||
"id": "4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "10:00 am",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Migrating to Ionic",
|
||||
"location": "Hall 1",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Eva Eagle", "Lionel Lion"],
|
||||
"timeStart": "10:00 am",
|
||||
"timeEnd": "10:15 am",
|
||||
"tracks": ["Ionic"],
|
||||
"id": "5"
|
||||
},
|
||||
{
|
||||
"name": "What's New in Angular",
|
||||
"location": "Hall 3",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Rachel Rabbit"],
|
||||
"timeStart": "10:15 am",
|
||||
"timeEnd": "10:30 am",
|
||||
"tracks": ["Angular"],
|
||||
"id": "6"
|
||||
},
|
||||
{
|
||||
"name": "The Evolution of Ionicons",
|
||||
"location": "Hall 2",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Isabella Iguana", "Eva Eagle"],
|
||||
"timeStart": "10:15 am",
|
||||
"timeEnd": "10:30 am",
|
||||
"tracks": ["Design"],
|
||||
"id": "7"
|
||||
},
|
||||
{
|
||||
"name": "Ionic Pro",
|
||||
"location": "Grand Ballroom A",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Charlie Cheetah"],
|
||||
"timeStart": "10:45 am",
|
||||
"timeEnd": "11:00 am",
|
||||
"tracks": ["Services"],
|
||||
"id": "8"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "11:00 am",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Ionic Workshop",
|
||||
"location": "Hall 1",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Karl Kitten", "Lionel Lion"],
|
||||
"timeStart": "11:00 am",
|
||||
"timeEnd": "11:45 am",
|
||||
"tracks": ["Workshop"],
|
||||
"id": "9"
|
||||
},
|
||||
{
|
||||
"name": "Community Interaction",
|
||||
"location": "Hall 3",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Lionel Lion", "Gino Giraffe"],
|
||||
"timeStart": "11:30 am",
|
||||
"timeEnd": "11:50 am",
|
||||
"tracks": ["Communication"],
|
||||
"id": "10"
|
||||
},
|
||||
{
|
||||
"name": "Navigation in Ionic",
|
||||
"location": "Grand Ballroom A",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Rachel Rabbit", "Eva Eagle"],
|
||||
"timeStart": "11:30 am",
|
||||
"timeEnd": "12:00 pm",
|
||||
"tracks": ["Navigation"],
|
||||
"id": "11"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "12:00 pm",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Lunch",
|
||||
"location": "Dining Hall",
|
||||
"description": "Come grab lunch with all the Ionic fanatics and talk all things Ionic",
|
||||
"timeStart": "12:00 pm",
|
||||
"timeEnd": "1:00 pm",
|
||||
"tracks": ["Food"],
|
||||
"id": "12"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "1:00 pm",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Ionic in the Enterprise",
|
||||
"location": "Hall 1",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Paul Puppy"],
|
||||
"timeStart": "1:00 pm",
|
||||
"timeEnd": "1:15 pm",
|
||||
"tracks": ["Communication"],
|
||||
"id": "13"
|
||||
},
|
||||
{
|
||||
"name": "Ionic Worldwide",
|
||||
"location": "Hall 1",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Gino Giraffe"],
|
||||
"timeStart": "1:15 pm",
|
||||
"timeEnd": "1:30 pm",
|
||||
"tracks": ["Communication"],
|
||||
"id": "14"
|
||||
},
|
||||
{
|
||||
"name": "The Ionic Package",
|
||||
"location": "Grand Ballroom B",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Molly Mouse", "Burt Bear"],
|
||||
"timeStart": "1:30 pm",
|
||||
"timeEnd": "2:00 pm",
|
||||
"tracks": ["Services"],
|
||||
"id": "15"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "2:00 pm",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Push Notifications in Ionic",
|
||||
"location": "Hall 2",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Burt Bear", "Charlie Cheetah"],
|
||||
"timeStart": "2:00 pm",
|
||||
"timeEnd": "2:30 pm",
|
||||
"tracks": ["Services"],
|
||||
"id": "16"
|
||||
},
|
||||
{
|
||||
"name": "Ionic Documentation",
|
||||
"location": "Grand Ballroom B",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Donald Duck"],
|
||||
"timeStart": "2:30 pm",
|
||||
"timeEnd": "2:45 pm",
|
||||
"tracks": ["Documentation"],
|
||||
"id": "17"
|
||||
},
|
||||
{
|
||||
"name": "UX in Ionic",
|
||||
"location": "Hall 3",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Isabella Iguana", "Ellie Elephant"],
|
||||
"timeStart": "2:45 pm",
|
||||
"timeEnd": "3:00 pm",
|
||||
"tracks": ["Design"],
|
||||
"id": "18"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": "3:00",
|
||||
"sessions": [
|
||||
{
|
||||
"name": "Angular Directives in Ionic",
|
||||
"location": "Hall 1",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Ted Turtle"],
|
||||
"timeStart": "3:00 pm",
|
||||
"timeEnd": "3:30 pm",
|
||||
"tracks": ["Angular"],
|
||||
"id": "19"
|
||||
},
|
||||
{
|
||||
"name": "Mobile States",
|
||||
"location": "Hall 2",
|
||||
"description": "Mobile devices and browsers are now advanced enough that developers can build native-quality mobile apps using open web technologies like HTML5, Javascript, and CSS. In this talk, we’ll provide background on why and how we created Ionic, the design decisions made as we integrated Ionic with Angular, and the performance considerations for mobile platforms that our team had to overcome. We’ll also review new and upcoming Ionic features, and talk about the hidden powers and benefits of combining mobile app development and Angular.",
|
||||
"speakerNames": ["Rachel Rabbit"],
|
||||
"timeStart": "3:30 pm",
|
||||
"timeEnd": "3:45 pm",
|
||||
"tracks": ["Navigation"],
|
||||
"id": "20"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"speakers": [
|
||||
{
|
||||
"name": "Burt Bear",
|
||||
"profilePic": "/assets/img/speakers/bear.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Burt is a Bear. Burt's interests include poetry, dashing space heroes, and lions.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "burt@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "1"
|
||||
},
|
||||
{
|
||||
"name": "Charlie Cheetah",
|
||||
"profilePic": "/assets/img/speakers/cheetah.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Charlie is a Cheetah. Charlie's interests include country music, plush animals, pyrotechnics, and skeletons.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "charlie@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "2"
|
||||
},
|
||||
{
|
||||
"name": "Donald Duck",
|
||||
"profilePic": "/assets/img/speakers/duck.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Donald is a Duck. Donald's interests include carpentry, superheroes, merpeople, and glam rock.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "donald@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "3"
|
||||
},
|
||||
{
|
||||
"name": "Eva Eagle",
|
||||
"profilePic": "/assets/img/speakers/eagle.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Eva is an Eagle. Eva's interests include ants, seashells, and cupcakes.",
|
||||
"title": "Developer Advocate",
|
||||
"location": "Everywhere",
|
||||
"email": "eva@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "4"
|
||||
},
|
||||
{
|
||||
"name": "Ellie Elephant",
|
||||
"profilePic": "/assets/img/speakers/elephant.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Ellie is an Elephant. Ellie's interests include pocket watches, pool, hand fans, and ninjas.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "ellie@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "5"
|
||||
},
|
||||
{
|
||||
"name": "Gino Giraffe",
|
||||
"profilePic": "/assets/img/speakers/giraffe.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Gino is a Giraffe. Gino's interests include candy-making, unicorns, and birdhouses.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "gino@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "6"
|
||||
},
|
||||
{
|
||||
"name": "Isabella Iguana",
|
||||
"profilePic": "/assets/img/speakers/iguana.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Isabella is an Iguana. Isabella's interests include crystals, architecture, and candle-making.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "isabella@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "7"
|
||||
},
|
||||
{
|
||||
"name": "Karl Kitten",
|
||||
"profilePic": "/assets/img/speakers/kitten.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Karl is a Kitten. Karl's interests include skiing, jewelry, and needlepoint.",
|
||||
"title": "Developer Advocate",
|
||||
"location": "Everywhere",
|
||||
"email": "karl@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "8"
|
||||
},
|
||||
{
|
||||
"name": "Lionel Lion",
|
||||
"profilePic": "/assets/img/speakers/lion.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Lionel is a Lion. Lionel's interests include lizards and mathematics.",
|
||||
"title": "Developer Advocate",
|
||||
"location": "Everywhere",
|
||||
"email": "lionel@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "9"
|
||||
},
|
||||
{
|
||||
"name": "Molly Mouse",
|
||||
"profilePic": "/assets/img/speakers/mouse.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Molly is a Mouse. Molly's interests include werewolves and magic.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "molly@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "10"
|
||||
},
|
||||
{
|
||||
"name": "Paul Puppy",
|
||||
"profilePic": "/assets/img/speakers/puppy.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Paul is a Puppy. Paul's interests include maps, whales, and dragons.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "paul@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "11"
|
||||
},
|
||||
{
|
||||
"name": "Rachel Rabbit",
|
||||
"profilePic": "/assets/img/speakers/rabbit.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Rachel is a Rabbit. Rachel's interests include clowns, skeletons, and yo-yos.",
|
||||
"title": "Senior Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "rachel@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "12"
|
||||
},
|
||||
{
|
||||
"name": "Ted Turtle",
|
||||
"profilePic": "/assets/img/speakers/turtle.jpg",
|
||||
"instagram": "ionicframework",
|
||||
"twitter": "ionicframework",
|
||||
"about": "Ted is a Turtle. Ted's interests include butterflies, skiing, and cupcakes.",
|
||||
"title": "Software Engineer",
|
||||
"location": "Everywhere",
|
||||
"email": "ted@example.com",
|
||||
"phone": "+1-541-754-3010",
|
||||
"id": "13"
|
||||
}
|
||||
],
|
||||
|
||||
"map": [
|
||||
{
|
||||
"name": "Monona Terrace Convention Center",
|
||||
"lat": 43.071584,
|
||||
"lng": -89.38012,
|
||||
"center": true
|
||||
},
|
||||
{
|
||||
"name": "Ionic HQ",
|
||||
"lat": 43.074395,
|
||||
"lng": -89.381056
|
||||
},
|
||||
{
|
||||
"name": "Afterparty - Brocach Irish Pub",
|
||||
"lat": 43.07336,
|
||||
"lng": -89.38335
|
||||
}
|
||||
],
|
||||
|
||||
"tracks": [
|
||||
{
|
||||
"name": "Angular",
|
||||
"icon": "logo-angular"
|
||||
},
|
||||
{
|
||||
"name": "Documentation",
|
||||
"icon": "document"
|
||||
},
|
||||
{
|
||||
"name": "Food",
|
||||
"icon": "restaurant"
|
||||
},
|
||||
{
|
||||
"name": "Ionic",
|
||||
"icon": "logo-ionic"
|
||||
},
|
||||
{
|
||||
"name": "Tooling",
|
||||
"icon": "hammer"
|
||||
},
|
||||
{
|
||||
"name": "Design",
|
||||
"icon": "color-palette"
|
||||
},
|
||||
{
|
||||
"name": "Services",
|
||||
"icon": "cog"
|
||||
},
|
||||
{
|
||||
"name": "Workshop",
|
||||
"icon": "construct"
|
||||
},
|
||||
{
|
||||
"name": "Communication",
|
||||
"icon": "call"
|
||||
},
|
||||
{
|
||||
"name": "Navigation",
|
||||
"icon": "compass"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
examples/ionic-angular/src/assets/img/about/Archive.zip
Normal file
BIN
examples/ionic-angular/src/assets/img/about/austin.jpg
Normal file
|
After Width: | Height: | Size: 1004 KiB |
BIN
examples/ionic-angular/src/assets/img/about/chicago.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
examples/ionic-angular/src/assets/img/about/madison.jpg
Normal file
|
After Width: | Height: | Size: 599 KiB |
BIN
examples/ionic-angular/src/assets/img/about/seattle.jpg
Normal file
|
After Width: | Height: | Size: 700 KiB |
BIN
examples/ionic-angular/src/assets/img/appicon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
2
examples/ionic-angular/src/assets/img/appicon.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3880FF" d="M256 139.3c-64.3 0-116.7 52.3-116.7 116.7 0 64.3 52.3 116.7 116.7 116.7S372.7 320.3 372.7 256 320.3 139.3 256 139.3z"/><circle fill="#3880FF" cx="423.5" cy="96.5" r="53.2"/><path fill="#3880FF" d="M489 149.9l-2.2-4.9-3.6 4c-8.7 9.9-19.8 17.5-32.1 22.1l-3.4 1.3 1.4 3.3c10.6 25.5 16 52.5 16 80.2 0 115.3-93.8 209.2-209.2 209.2S46.8 371.3 46.8 256 140.7 46.8 256 46.8c31.3 0 61.5 6.8 89.6 20.2l3.3 1.6 1.4-3.3c5.1-12 13.3-22.7 23.6-31l4.2-3.4-4.8-2.5C336.8 9.6 297.3 0 256 0 114.8 0 0 114.8 0 256s114.8 256 256 256 256-114.8 256-256c0-36.9-7.7-72.6-23-106.1z"/></svg>
|
||||
|
||||
|
After Width: | Height: | Size: 655 B |
BIN
examples/ionic-angular/src/assets/img/ica-slidebox-img-1.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
examples/ionic-angular/src/assets/img/ica-slidebox-img-2.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
examples/ionic-angular/src/assets/img/ica-slidebox-img-3.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
examples/ionic-angular/src/assets/img/ica-slidebox-img-4.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" d="M256 139.3c-64.3 0-116.7 52.3-116.7 116.7 0 64.3 52.3 116.7 116.7 116.7S372.7 320.3 372.7 256 320.3 139.3 256 139.3z"/><circle fill="#FFF" cx="423.5" cy="96.5" r="53.2"/><path fill="#FFF" d="M489 149.9l-2.2-4.9-3.6 4c-8.7 9.9-19.8 17.5-32.1 22.1l-3.4 1.3 1.4 3.3c10.6 25.5 16 52.5 16 80.2 0 115.3-93.8 209.2-209.2 209.2S46.8 371.3 46.8 256 140.7 46.8 256 46.8c31.3 0 61.5 6.8 89.6 20.2l3.3 1.6 1.4-3.3c5.1-12 13.3-22.7 23.6-31l4.2-3.4-4.8-2.5C336.8 9.6 297.3 0 256 0 114.8 0 0 114.8 0 256s114.8 256 256 256 256-114.8 256-256c0-36.9-7.7-72.6-23-106.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 643 B |
BIN
examples/ionic-angular/src/assets/img/speaker-background.png
Normal file
|
After Width: | Height: | Size: 910 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/bear.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/cheetah.jpg
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/duck.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/eagle.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/elephant.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/giraffe.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
examples/ionic-angular/src/assets/img/speakers/iguana.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |