Merge frontend repository into app/ subdirectory

This commit is contained in:
2025-07-25 19:12:51 -03:00
parent bb2e4f2a42
commit cedf771f16
54 changed files with 1513 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { TabsPage } from '../pages/tabs/tabs';
@Component({
templateUrl: 'app.html'
})
export class Urnings {
rootPage: any = TabsPage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
});
}
}

1
app/src/app/app.html Normal file
View File

@@ -0,0 +1 @@
<ion-nav [root]="rootPage"></ion-nav>

65
app/src/app/app.module.ts Normal file
View File

@@ -0,0 +1,65 @@
import { NgModule, ErrorHandler } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpModule } from '@angular/http';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { IonicSwipeAllModule } from 'ionic-swipe-all';
import { Urnings } from './app.component';
import { ChatPage } from '../pages/chat/chat';
import { GridPage } from '../pages/grid/grid';
import { InformationPage } from '../pages/information/information';
import { LightboxPage } from '../pages/lightbox/lightbox';
import { MessagesPage } from '../pages/messages/messages';
import { ProfilePage } from '../pages/profile/profile';
import { TabsPage } from '../pages/tabs/tabs';
import { TellYourStoryPage } from '../pages/tell/tell';
import { UsersPage } from '../pages/users/users';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
@NgModule({
declarations: [
Urnings,
ChatPage,
InformationPage,
GridPage,
LightboxPage,
MessagesPage,
ProfilePage,
TabsPage,
TellYourStoryPage,
UsersPage
],
imports: [
BrowserModule,
HttpModule,
IonicSwipeAllModule,
IonicModule.forRoot(Urnings, {
iconMode: 'ios',
modalEnter: 'modal-slide-in',
modalLeave: 'modal-slide-out',
tabsPlacement: 'bottom',
pageTransition: 'ios-transition'
})
],
bootstrap: [IonicApp],
entryComponents: [
Urnings,
ChatPage,
InformationPage,
GridPage,
LightboxPage,
MessagesPage,
ProfilePage,
TabsPage,
TellYourStoryPage,
UsersPage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}

91
app/src/app/app.scss Normal file
View File

@@ -0,0 +1,91 @@
// http://ionicframework.com/docs/theming/
// App Global Sass
// --------------------------------------------------
// Put style rules here that you want to apply globally. These
// styles are for the entire app and not just one component.
// Additionally, this file can be also used as an entry point
// to import other Sass files to be included in the output CSS.
//
// Shared Sass variables, which can be used to adjust Ionic's
// default Sass variables, belong in "theme/variables.scss".
//
// To declare rules for a specific mode, create a child rule
// for the .md, .ios, or .wp mode classes. The mode class is
// automatically applied to the <body> element in the app.
body {
font-family: 'PT Sans', sans-serif;
}
ion-toolbar {
color: #fff;
.toolbar-background {
background-color: #000000;
}
.bar-button,
.toolbar-title {
color: #ffffff;
}
}
ion-title {
text-align: center;
}
.content {
background-color: #191b1c;
color: #fff;
}
.item {
background-color: #1d1e1f;
color: #fff;
}
.list {
.item-block .item-inner {
border-bottom-color: #333435;
}
}
.tabs {
.tabbar {
background-color: #090a0a;
}
.tab-button {
.tab-button-icon {
color: #ffffff;
}
&[aria-selected=true] {
.tab-button-icon {
color: #fdb315;
}
}
}
}
@media screen and (min-width: 769px) {
body {
background-color: #000000;
}
ion-app.app-root {
height: 480px;
left: 50%;
position: relative;
top: 50%;
transform: translate3d(-50%, -50%, 0);
width: 320px;
}
}

8
app/src/app/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

57
app/src/index.html Normal file
View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Urnings</title>
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#4e8ef7">
<!-- add to homescreen for ios -->
<link rel="apple-touch-icon" href="assets/imgs/logo.png">
<link rel="apple-touch-startup-image" sizes="640x1136" href="assets/imgs/launch-screen-640x1136.png">
<link rel="apple-touch-startup-image" sizes="750x1334" href="assets/imgs/launch-screen-750x1334.png">
<link rel="apple-touch-startup-image" sizes="1125x2436" href="assets/imgs/launch-screen-1125x2436.png">
<link rel="apple-touch-startup-image" sizes="1242x2208" href="assets/imgs/launch-screen-1242x2208.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Urge">
<!-- cordova.js required for cordova apps (remove if not needed) -->
<!--script src="cordova.js"></script-->
<!-- un-comment this code to enable service worker
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.error('Error', err));
}
</script>-->
<link href="https://fonts.googleapis.com/css?family=PT+Sans" rel="stylesheet">
<link href="build/main.css" rel="stylesheet">
</head>
<body>
<!-- Ionic's root component and where the app will load -->
<ion-app></ion-app>
<!-- The polyfills js is generated during the build process -->
<script src="build/polyfills.js"></script>
<!-- The vendor js is generated during the build process
It contains all of the dependencies in node_modules -->
<script src="build/vendor.js"></script>
<!-- The main bundle js is generated during the build process -->
<script src="build/main.js"></script>
</body>
</html>

13
app/src/manifest.json Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "urge",
"short_name": "urge",
"start_url": "index.html",
"display": "standalone",
"icons": [{
"src": "assets/imgs/logo.png",
"sizes": "512x512",
"type": "image/png"
}],
"background_color": "#191b1c",
"theme_color": "#191b1c"
}

View File

@@ -0,0 +1,25 @@
<ion-header>
<ion-toolbar>
<ion-buttons left>
<button ion-button icon-only (tap)="closeChat($event)">
<ion-icon name="arrow-back"></ion-icon>
</button>
</ion-buttons>
<ion-title><img class="title-profile-avatar" [src]="'https://appsby.fitz.guru/urge/' + this.profile.details.pic.thumb" height="24" width="24"> {{this.profile.details.name}}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item class="message-bubble" *ngFor="let message of this.profile.messages" [ngClass]="{ 'is-user': (message.isUser == true) }">
<img *ngIf="message.image" [src]="'https://appsby.fitz.guru/urge/' + message.image" (press)="showLightbox($event, message.image)">
<p *ngIf="message.text != ''">{{message.text}}</p>
</ion-item>
</ion-list>
</ion-content>
<ion-footer>
<ion-toolbar>
</ion-toolbar>
</ion-footer>

View File

@@ -0,0 +1,48 @@
page-chat {
.toolbar-title {
.title-profile-avatar {
vertical-align: bottom;
}
}
.list {
.message-bubble {
background-color: #fdb315;
border-radius: 0.5rem;
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
line-height: 1.5;
margin: 1rem 1rem 2.5rem auto;
max-width: 75%;
overflow: visible;
padding: 0.75rem;
position: relative;
&.is-user {
background-color: #6fbedf;
margin: 1rem auto 2.5rem 1rem;
}
.item-inner {
border-bottom: none;
}
ion-label {
overflow: visible;
text-overflow: unset;
margin: 0 0.75rem;
}
p {
color: #1d1e1f;
white-space: normal;
&.timestamp {
color: #acacac;
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { LightboxPage } from '../lightbox/lightbox';
@Component({
selector: 'page-chat',
templateUrl: 'chat.html'
})
export class ChatPage {
profile: any;
tabNavEl: any;
constructor(public navCtrl: NavController, private _params: NavParams) {
this.profile = this._params.get('profile');
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'none';
}
closeChat(event) {
this.navCtrl.pop();
}
showLightbox(event, image) {
this.navCtrl.push(LightboxPage, {
image: image
});
}
}

View File

@@ -0,0 +1,15 @@
<ion-header>
<ion-toolbar>
<ion-title>Urnings</ion-title>
</ion-toolbar>
</ion-header>
<ion-content no-padding>
<ion-grid no-padding>
<ion-row align-items-stretch>
<ion-col col-4 class="profile" *ngFor="let current of profiles" (tap)="profileTapped($event, current)" (press)="profilePressed($event, current)" [style.backgroundImage]="getBackgroundThumbnail(current.details.pic)">
<span class="username" [ngClass]="{ 'online': (current.messages?.length > 0) }">{{current.details.name}}</span>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@@ -0,0 +1,66 @@
page-grid {
ion-toolbar {
.toolbar-title {
color: #ffffff;
font-size: 2.42em;
font-weight: 700;
line-height: 1.29;
text-decoration: underline;
}
}
.grid {
.row {
.col {
&.profile {
background-size: cover;
border: 1px solid #000000;
box-sizing: border-box;
padding: 0 0 33% !important;
position: relative;
.username {
background-size: cover;
bottom: 0.25rem;
box-sizing: border-box;
color: #ffffff;
display: inline-block;
left: 0.5rem;
overflow: hidden;
position: absolute;
right: 0.25rem;
text-overflow: ellipsis;
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
white-space: nowrap;
&::before {
border: 0.125rem solid #acacac;
border-radius: 1rem;
bottom: 0.125rem;
content: '';
display: inline-block;
height: 0.8rem;
margin-right: 0.5rem;
position: relative;
vertical-align: middle;
width: 0.8rem;
}
&.online {
&::before {
background-color: #00ff00;
border-color: #00ff00;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { NavController } from 'ionic-angular';
import { ChatPage } from '../chat/chat';
import { ProfileService } from '../../services/profiles';
import { ProfilePage } from '../profile/profile';
@Component({
selector: 'page-grid',
templateUrl: 'grid.html',
providers: [ ProfileService ]
})
export class GridPage {
profiles: any;
tabNavEl: any;
constructor(public navCtrl: NavController, public profileService: ProfileService, private _sanitizer: DomSanitizer) {
profileService.loadVerified().then((data) => {
this.profiles = data;
console.debug('profiles: ', this.profiles);
});
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'flex';
}
doTellStory() {
this.navCtrl.push(TellYourStoryPage);
}
getBackgroundThumbnail(pics) {
return this._sanitizer.bypassSecurityTrustStyle('url(https://appsby.fitz.guru/urge/' + pics.thumb + ')');
}
profilePressed(event, profile) {
if (profile.messages && profile.messages.length) {
this.navCtrl.push(ChatPage, {
profile: profile
});
}
}
profileTapped(event, profile) {
this.navCtrl.push(ProfilePage, {
profile: profile,
});
}
}

View File

@@ -0,0 +1,20 @@
<ion-header>
<ion-toolbar>
<ion-buttons right>
<button ion-button icon-only (tap)="close($event)">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content padding>
<div class="content-box" padding margin>
<h3>About this Project</h3>
<div class="info-blurb">
<p>The app was designed by Nicholas Pfosi and developed by Michael Fitzpatrick, modeled after the popular gay dating app Grindr.</p>
<p>Presenting these stories in this form, which is the conduit through which much participation in dating occurs, served multiple purposes. First, it educated the viewer who may not have used Grindr before, how it functions and how it is different from other apps such as Tinder, whereby matching with a person is a prerequisite for conversation. Second, it makes the scope of the project flexible, allowing for the submission of stories from the audience to be slotted into an expandable presentation.</p>
<p>Please direct any questions or concerns to Nicholas Pfosi at npfosi@gmail.com</p>
</div>
</div>
</ion-content>

View File

@@ -0,0 +1,9 @@
page-information {
.content-box {
background-color: #ffffff;
color: #000000;
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
}

View File

@@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-information',
templateUrl: 'information.html',
})
export class InformationPage {
tabNavEl: any;
constructor(public navCtrl: NavController) {
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'none';
}
close(event) {
this.navCtrl.pop();
}
}

View File

@@ -0,0 +1,13 @@
<ion-header>
<ion-toolbar>
<ion-buttons right>
<button ion-button icon-only (tap)="close($event)">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content (click)="close($event)">
<img class="image-detail" [src]="'https://appsby.fitz.guru/urge/' + this.image">
</ion-content>

View File

@@ -0,0 +1,11 @@
page-lightbox {
.image-detail {
display: block;
height: auto;
position: relative;
top: 50%;
transform: translate3d(0, -50%, 0);
width: 100%;
}
}

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
@Component({
selector: 'page-lightbox',
templateUrl: 'lightbox.html'
})
export class LightboxPage {
image: string;
tabNavEl: any;
constructor(public navCtrl: NavController, private _params: NavParams) {
this.image = this._params.get('image');
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'none';
}
close(event) {
this.navCtrl.pop();
}
}

View File

@@ -0,0 +1,28 @@
<ion-header>
<ion-toolbar>
<ion-title>Urnings</ion-title>
</ion-toolbar>
</ion-header>
<ion-content no-padding>
<ion-list>
<ng-container *ngFor="let profile of profiles">
<ion-item no-padding *ngIf="profile.messages?.length > 0">
<ion-thumbnail padding-left item-start (tap)="profilePictureTapped($event, profile)">
<img [src]="'https://appsby.fitz.guru/urge/' + profile.details.pic.thumb">
</ion-thumbnail>
<ion-grid (tap)="interviewTapped($event, profile)">
<ion-row nowrap justify-content-between>
<ion-col class="username">
{{profile.details.name}}
</ion-col>
<ion-col class="timestamp" [innerHTML]="getLatestMessageTimestamp(profile.messages)"></ion-col>
</ion-row>
<ion-row class="latest-message" nowrap>
<ion-col [innerHTML]="getLatestMessage(profile.messages)"></ion-col>
</ion-row>
</ion-grid>
</ion-item>
</ng-container>
</ion-list>
</ion-content>

View File

@@ -0,0 +1,47 @@
page-messages {
ion-toolbar {
.toolbar-title {
color: #ffffff;
font-size: 2.42em;
font-weight: 700;
line-height: 1.29;
text-decoration: underline;
}
}
ion-header {
.button {
color: #9e9ea8;
}
}
.col {
color: #ffffff;
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
line-height: 1.5;
&.username {
font-weight: 700;
}
&.timestamp {
font-size: 0.7em;
font-style: italic;
text-align: right;
}
.latest-message {
font-size: 0.8em;
}
}
.list {
> .item-block:last-child {
border-bottom: none;
}
}
}

View File

@@ -0,0 +1,52 @@
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ProfileService } from '../../services/profiles';
import { ProfilePage } from '../profile/profile';
import { ChatPage } from '../chat/chat';
import moment from 'moment';
@Component({
selector: 'page-messages',
templateUrl: 'messages.html',
providers: [ ProfileService ]
})
export class MessagesPage {
profiles: any;
tabNavEl: any;
constructor(public navCtrl: NavController, public profileService: ProfileService) {
profileService.load().then((data) => {
this.profiles = data;
});
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'flex';
}
getLatestMessage(messages) {
var latest = messages[(messages.length - 1)];
var isUser = latest.isUser;
return latest.text ? latest.text : '<em>' + (!isUser ? 'Sent ' : '') + 'Photo' + (isUser ? ' Recieved' : '') + '</em>';
}
getLatestMessageTimestamp(messages) {
return moment(messages[(messages.length - 1)].timestamp).fromNow();
}
interviewTapped(event, profile) {
this.navCtrl.push(ChatPage, {
profile: profile
});
}
profilePictureTapped(event, profile) {
this.navCtrl.push(ProfilePage, {
profile: profile
});
}
}

View File

@@ -0,0 +1,27 @@
<ion-content no-padding [style.backgroundImage]="getBackground(profile.details.pic)" (press)="showLightbox($event, profile.details.pic.detail)" on-swipe-left="nextProfile($event)" on-swipe-right="previousProfile($event)">
<ion-toolbar class="profile-toolbar">
<ion-buttons left>
<button ion-button icon-only (tap)="closeProfile($event)">
<ion-icon name="arrow-back"></ion-icon>
</button>
</ion-buttons>
<ion-title>{{this.profile.details.name}}</ion-title>
</ion-toolbar>
<button ion-button icon-only clear large (tap)="openChat($event, this.profile)" class="button-chat">
<ion-icon name="ios-chatboxes"></ion-icon>
</button>
<div id="detail-overlay" class="details">
<ion-grid>
<ion-row nowrap align-items-center justify-content-between>
<ion-col col-12 text-center (click)="toggleProfileDetails($event)" class="detail-toggle">
<ion-icon name="arrow-down"></ion-icon>
</ion-col>
</ion-row>
<ion-row class="about" *ngIf="this.profile.details.about">
<ion-col col-12 [innerHTML]="this.profile.details.about"></ion-col>
</ion-row>
</ion-grid>
</div>
</ion-content>

View File

@@ -0,0 +1,72 @@
page-profile {
ion-content {
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
.scroll-content {
overflow-y: hidden;
}
ion-toolbar {
border-bottom: 1px solid #ffffff;
transition: opacity 250ms 125ms ease-in-out;
&.hidden {
opacity: 0;
z-index: -1;
}
.toolbar-background {
background-color: rgba(0, 0, 0, 1);
}
.bar-button,
.toolbar-title {
color: #ffffff;
}
}
.button-chat {
bottom: 3rem;
color: #fdb315;
position: absolute;
right: 1.5rem;
z-index: 100;
}
.detail-toggle {
font-size: 2.5em;
z-index: 100;
}
.details {
bottom: 0;
height: 60px;
left: 0;
position: absolute;
right: 0;
transition: all 250ms 125ms ease-in-out;
&.open {
background: rgba(0, 0, 0, 0.8);
height: 100%;
overflow-y: scroll;
}
.about {
font-family: 'Helvetica Neue', HelveticaNeue, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
.actions {
text-align: right;
.button-clear {
color: #fdb315;
}
}
}
}

View File

@@ -0,0 +1,89 @@
import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { NavController, NavParams } from 'ionic-angular';
import { ChatPage } from '../chat/chat';
import { LightboxPage } from '../lightbox/lightbox';
import { ProfileService } from '../../services/profiles';
@Component({
selector: 'page-profile',
templateUrl: 'profile.html',
providers: [ ProfileService ]
})
export class ProfilePage {
detailsOpen: boolean = false;
profile: any;
tabNavEl: any;
constructor(public navCtrl: NavController, public navParams: NavParams, public profileService: ProfileService, private _sanitizer: DomSanitizer) {
this.profile = navParams.get('profile');
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'none';
}
closeProfile(event) {
this.navCtrl.pop();
}
closeProfileDetails(event) {
if (this.detailsOpen) {
this.detailsOpen = false;
document.querySelector('.profile-toolbar').classList.remove('hidden');
document.getElementById('detail-overlay').classList.remove('open');
}
}
getBackground(pics) {
return this._sanitizer.bypassSecurityTrustStyle('url(https://appsby.fitz.guru/urge/' + pics.detail + ')');
}
markFavorite(event, profile) {
console.debug('favorite profile', { event: event, profile: profile });
}
nextProfile(event) {
this.profile = this.profileService.getNextProfile(this.profile._id);
this.navCtrl.setRoot(this.navCtrl.getActive().component);
}
openChat(event, profile) {
this.navCtrl.push(ChatPage, {
profile: profile
});
}
openProfileDetails(event) {
if (!this.detailsOpen) {
this.detailsOpen = true;
document.querySelector('.profile-toolbar').classList.add('hidden');
document.getElementById('detail-overlay').classList.add('open');
}
}
previousProfile(event) {
this.profile = this.profileService.getPreviousProfile(this.profile._id);
this.navCtrl.setRoot(this.navCtrl.getActive().component);
}
showLightbox(event, image) {
if (event.target.classList.contains('scroll-content')) {
this.navCtrl.push(LightboxPage, {
image: image
});
}
}
toggleProfileDetails(event) {
if (!this.detailsOpen) {
this.openProfileDetails(event);
} else {
this.closeProfileDetails(event);
}
}
}

View File

@@ -0,0 +1,6 @@
<ion-tabs id="tab-nav" selectedIndex="0">
<ion-tab [root]="tab1Root" tabIcon="contacts"></ion-tab>
<ion-tab [root]="tab2Root" tabIcon="compass"></ion-tab>
<ion-tab [root]="tab3Root" tabIcon="chatboxes"></ion-tab>
<ion-tab [root]="tab4Root" tabIcon="information-circle"></ion-tab>
</ion-tabs>

View File

@@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { GridPage } from '../grid/grid';
import { InformationPage } from '../information/information';
import { MessagesPage } from '../messages/messages';
import { UsersPage } from '../users/users';
@Component({
templateUrl: 'tabs.html'
})
export class TabsPage {
tab1Root = GridPage;
tab2Root = UsersPage;
tab3Root = MessagesPage;
tab4Root = InformationPage;
constructor() {
}
}

View File

@@ -0,0 +1,12 @@
<ion-header>
<ion-toolbar>
<ion-buttons right>
<button ion-button icon-only (tap)="close($event)">
<ion-icon name="close"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
</ion-content>

View File

@@ -0,0 +1,27 @@
page-tell-your-story {
ion-col {
&.cruise {
background-size: cover;
border: 1px solid #000000;
box-sizing: border-box;
padding: 0 0 37.5% !important;
position: relative;
.placename {
bottom: 0.25rem;
box-sizing: border-box;
color: #acacac;
display: inline-block;
left: 0.5rem;
overflow: hidden;
position: absolute;
right: 0.25rem;
text-overflow: ellipsis;
text-shadow: rgba(0, 0, 0, 1);
white-space: nowrap;
}
}
}
}

View File

@@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ProfileService } from '../../services/profiles';
@Component({
selector: 'page-tell-your-story',
templateUrl: 'tell.html',
providers: [ ProfileService ]
})
export class TellYourStoryPage {
tabNavEl: any;
constructor(public navCtrl: NavController) {
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'none';
}
close(event) {
this.navCtrl.pop();
}
}

View File

@@ -0,0 +1,20 @@
<ion-header>
<ion-toolbar>
<ion-title>Urnings</ion-title>
</ion-toolbar>
</ion-header>
<ion-content no-padding>
<ion-grid no-padding>
<ion-row align-items-stretch>
<ion-col col-4 class="profile tell-your-story">
<button ion-button clear large icon-only (tap)="doTellStory()">
<ion-icon name="md-person-add"></ion-icon>
</button>
</ion-col>
<ion-col col-4 class="profile" *ngFor="let current of profiles" (tap)="profileTapped($event, current)" (press)="profilePressed($event, current)" [style.backgroundImage]="getBackgroundThumbnail(current.details.pic)">
<span class="username" [ngClass]="{ 'online': (current.messages?.length > 0) }">{{current.details.name}}</span>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@@ -0,0 +1,78 @@
page-users {
ion-toolbar {
.toolbar-title {
color: #ffffff;
font-size: 2.42em;
font-weight: 700;
line-height: 1.29;
text-decoration: underline;
}
}
.grid {
.row {
.col {
&.profile {
background-size: cover;
border: 1px solid #000000;
box-sizing: border-box;
padding: 0 0 33% !important;
position: relative;
&.tell-your-story {
position: relative;
button {
color: #acacac;
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
}
.username {
background-size: cover;
bottom: 0.25rem;
box-sizing: border-box;
color: #ffffff;
display: inline-block;
left: 0.5rem;
overflow: hidden;
position: absolute;
right: 0.25rem;
text-overflow: ellipsis;
text-shadow: 0 0 3px rgba(0, 0, 0, 1);
white-space: nowrap;
&::before {
border: 0.125rem solid #acacac;
border-radius: 1rem;
bottom: 0.125rem;
content: '';
display: inline-block;
height: 0.8rem;
margin-right: 0.5rem;
position: relative;
vertical-align: middle;
width: 0.8rem;
}
&.online {
&::before {
background-color: #00ff00;
border-color: #00ff00;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { NavController } from 'ionic-angular';
import { ChatPage } from '../chat/chat';
import { ProfileService } from '../../services/profiles';
import { ProfilePage } from '../profile/profile';
import { TellYourStoryPage } from '../tell/tell';
@Component({
selector: 'page-users',
templateUrl: 'users.html',
providers: [ ProfileService ]
})
export class UsersPage {
profiles: any;
tabNavEl: any;
constructor(public navCtrl: NavController, public profileService: ProfileService, private _sanitizer: DomSanitizer) {
profileService.loadSubmitted().then((data) => {
this.profiles = data;
});
this.tabNavEl = document.querySelector('#tab-nav .tabbar');
}
ionViewWillEnter() {
this.tabNavEl.style.display = 'flex';
}
doTellStory() {
this.navCtrl.push(TellYourStoryPage);
}
getBackgroundThumbnail(pics) {
return this._sanitizer.bypassSecurityTrustStyle('url(https://appsby.fitz.guru/urge/' + pics.thumb + ')');
}
profilePressed(event, profile) {
if (profile.messages && profile.messages.length) {
this.navCtrl.push(ChatPage, {
profile: profile
});
}
}
profileTapped(event, profile) {
this.navCtrl.push(ProfilePage, {
profile: profile,
});
}
}

31
app/src/service-worker.js Normal file
View File

@@ -0,0 +1,31 @@
/**
* Check out https://googlechromelabs.github.io/sw-toolbox/ for
* more info on how to use sw-toolbox to custom configure your service worker.
*/
'use strict';
importScripts('./build/sw-toolbox.js');
self.toolbox.options.cache = {
name: 'ionic-cache'
};
// pre-cache our key assets
self.toolbox.precache(
[
'./build/main.js',
'./build/vendor.js',
'./build/main.css',
'./build/polyfills.js',
'index.html',
'manifest.json'
]
);
// dynamically cache any other local assets
self.toolbox.router.any('/*', self.toolbox.fastest);
// for any other requests go to the network, cache,
// and then only use that cached resource if your user goes offline
self.toolbox.router.default = self.toolbox.networkFirst;

View File

@@ -0,0 +1,97 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class ProfileService {
endpoint: string = 'https://api.fitz.guru/urnings/profiles';
fallback: string = 'assets/data/profiles.json';
epSubmitted: string = '/submitted';
epVerified: string = '/verified';
idMap: any = { all: {}, submitted: {}, verified: {} };
profiles: any;
constructor(private http: Http) {
this.idMap = {};
this.profiles = null;
}
load() {
if (this.profiles) {
return Promise.resolve(this.profiles);
}
return new Promise(resolve => {
this.doGetRequest(this.endpoint, resolve);
});
}
loadSubmitted() {
if (this.profiles && this.profiles.submitted) {
return Promise.resolve(this.profiles.submitted);
}
return new Promise(resolve => {
this.doGetRequest(this.endpoint + this.epSubmitted, resolve, 'submitted');
});
}
loadVerified() {
if (this.profiles && this.profiles.verified) {
return Promise.resolve(this.profiles.verified);
}
return new Promise(resolve => {
this.doGetRequest(this.endpoint + this.epVerified, resolve, 'verified');
});
}
doGetRequest(endpoint, resolve, type = 'all') {
this.http.get(endpoint)
.map(res => res.json())
.subscribe(
data => {
this.profiles = this.profiles || {};
this.profiles[type] = data;
this.profiles[type].reduce((map, profile, i) => {
map[profile._id] = i;
return map;
}, this.idMap[type]);
resolve(this.profiles[type]);
},
error => {
this.doGetRequest(this.fallback, resolve, type);
}
)
}
getNextProfile(id, type = 'all') {
var nextIdIndex = this.idMap[type][id] + 1;
nextIdIndex = nextIdIndex >= this.profiles[type].length ? 0 : nextIdIndex;
return this.profiles[type][nextIdIndex];
}
getPreviousProfile(id, type = 'all') {
var prevIdIndex = this.idMap[type][id] - 1;
prevIdIndex = prevIdIndex < 0 ? (this.profiles[type].length - 1) : prevIdIndex;
return this.profiles[type][prevIdIndex];
}
getProfiles() {
return this.profiles.all;
}
getProfileById(id) {
return this.profiles[this.idMap[id]];
}
getSubmittedProfiles() {
return this.profiles.submitted;
}
getVerifiedProfiles() {
return this.profiles.verified;
}
}

View File

@@ -0,0 +1,88 @@
// Ionic Variables and Theming. For more info, please see:
// http://ionicframework.com/docs/theming/
// Font path is used to include ionicons,
// roboto, and noto sans fonts
$font-path: "../assets/fonts";
// The app direction is used to include
// rtl styles in your app. For more info, please see:
// http://ionicframework.com/docs/theming/rtl-support/
$app-direction: ltr;
@import "ionic.globals";
// Shared Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass variables found in Ionic's source scss files.
// To view all the possible Ionic variables, see:
// http://ionicframework.com/docs/theming/overriding-ionic-variables/
// Named Color Variables
// --------------------------------------------------
// Named colors makes it easy to reuse colors on various components.
// It's highly recommended to change the default colors
// to match your app's branding. Ionic uses a Sass map of
// colors so you can add, rename and remove colors as needed.
// The "primary" color is the only required color in the map.
$colors: (
primary: #488aff,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222
);
// App iOS Variables
// --------------------------------------------------
// iOS only Sass variables can go here
// App Material Design Variables
// --------------------------------------------------
// Material Design only Sass variables can go here
// App Windows Variables
// --------------------------------------------------
// Windows only Sass variables can go here
// App Theme
// --------------------------------------------------
// Ionic apps can have different themes applied, which can
// then be future customized. This import comes last
// so that the above variables are used and Ionic's
// default are overridden.
@import "ionic.theme.default";
// Ionicons
// --------------------------------------------------
// The premium icon font for Ionic. For more info, please see:
// http://ionicframework.com/docs/ionicons/
@import "ionic.ionicons";
// Fonts
// --------------------------------------------------
@import "roboto";
@import "noto-sans";