...
 
Commits (20)
......@@ -26,7 +26,6 @@
"scripts": [
"../node_modules/material-design-lite/dist/material.min.js",
"../node_modules/medium-editor/dist/js/medium-editor.min.js",
"shims/fontawesome.js",
"shims/jitsi-api.min.js"
],
"environmentSource": "environments/environment.ts",
......
This diff is collapsed.
......@@ -28,7 +28,6 @@
"scripts": [
"node_modules/material-design-lite/dist/material.min.js",
"node_modules/medium-editor/dist/js/medium-editor.min.js",
"src/shims/fontawesome.js",
"src/shims/jitsi-api.min.js"
]
},
......@@ -82,7 +81,6 @@
"scripts": [
"node_modules/material-design-lite/dist/material.min.js",
"node_modules/medium-editor/dist/js/medium-editor.min.js",
"src/shims/fontawesome.js",
"src/shims/jitsi-api.min.js"
],
"assets": [
......
......@@ -70,7 +70,7 @@ export class MindsHttpClient {
* Build the options
*/
private buildOptions(options: Object) {
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN');
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN') || '';
const headers = new HttpHeaders({
'X-XSRF-TOKEN': XSRF_TOKEN,
......
......@@ -153,7 +153,7 @@ export class EntitiesService {
if (!this.entities.has(urn)) {
this.entities.set(urn, new BehaviorSubject(null));
}
console.warn(`Entity ${urn} not found`);
this.entities.get(urn).error("Not found");
}
static _(client: Client, blockListService: BlockListService) {
......
......@@ -53,6 +53,8 @@ export class UpdateMarkersService {
if (this.isLoggedIn) {
this.get()
.subscribe((markers: any) => {
if (!markers)
return;
this.data = markers; //cache
for (let i in this.data) {
......
......@@ -218,7 +218,7 @@ m-hashtags--sidebar-selector {
width: 48px;
z-index: 1;
@include m-theme(){
background: linear-gradient(to right, rgba(themed($m-white-always), 0) 0%, rgba(themed($m-body-bg), 1) 50%);
background: linear-gradient(to right, rgba(themed($m-white), 0) 0%, rgba(themed($m-body-bg), 1) 50%);
}
.m-hashtagsSidebarSelectorCompactListOverflow__Arrow {
......
......@@ -175,7 +175,7 @@
[torrent]="[{ res: '360', key: activity.custom_data.guid + '/360.mp4' }]"
[isActivity]="true"
(videoMetadataLoaded)="setVideoDimensions($event)"
(mediaModalRequested)="showMediaModal()"
(mediaModalRequested)="clickedVideo()"
#player>
<video-ads [player]="player" *ngIf="activity.monetized"></video-ads>
</m-video>
......@@ -212,7 +212,7 @@
style="width:100%"
(error)="activity.custom_data[0].src = minds.cdn_assets_url + 'assets/logos/placeholder-bulb.jpg'"
*ngIf="activity.custom_data"
(click)="showMediaModal()"
(click)="clickedImage()"
#batchImage
>
</a>
......
......@@ -449,30 +449,43 @@ export class Activity implements OnInit {
this.activity.custom_data[0].height = img.naturalHeight;
}
showMediaModal() {
if (this.featuresService.has('media-modal')) {
// Mobile (not tablet) users go to media page instead of modal
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
clickedImage() {
// Check if is mobile (not tablet)
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.goToMediaPage();
return;
}
if (this.activity.custom_type === 'video') {
this.activity.custom_data.dimensions = this.videoDimensions;
} else { // Image
// Set image dimensions if they're not already there
if (this.activity.custom_data[0].width === '0' || this.activity.custom_data[0].height === '0') {
this.setImageDimensions();
}
if (!this.featuresService.has('media-modal')) {
// Non-canary
this.goToMediaPage();
return;
} else {
// Canary
if (this.activity.custom_data[0].width === '0' || this.activity.custom_data[0].height === '0') {
this.setImageDimensions();
}
this.openModal();
}
}
this.activity.modal_source_url = this.router.url;
clickedVideo() {
// Already filtered out mobile users/non-canary in video.component.ts
// So this is just applicable to desktop/tablet in canary and should always show modal
this.activity.custom_data.dimensions = this.videoDimensions;
this.openModal();
}
this.overlayModal.create(MediaModalComponent, this.activity, {
class: 'm-overlayModal--media'
}).present();
} else {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
openModal() {
this.activity.modal_source_url = this.router.url;
this.overlayModal.create(MediaModalComponent, this.activity, {
class: 'm-overlayModal--media'
}).present();
}
goToMediaPage() {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
detectChanges() {
......
......@@ -159,29 +159,40 @@ export class Remind {
this.activity.custom_data[0].height = img.naturalHeight;
}
showMediaModal() {
if (this.featuresService.has('media-modal')) {
// Mobile (not tablet) users go to media page instead of modal
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
clickedImage() {
// Check if is mobile (not tablet)
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.goToMediaPage();
}
if (this.activity.custom_type === 'video') {
this.activity.custom_data.dimensions = this.videoDimensions;
} else { // Image
// Set image dimensions if they're not already there
if (this.activity.custom_data[0].width === '0' || this.activity.custom_data[0].height === '0') {
this.setImageDimensions();
}
if (!this.featuresService.has('media-modal')) {
// Non-canary
this.goToMediaPage();
} else {
// Canary
if (this.activity.custom_data[0].width === '0' || this.activity.custom_data[0].height === '0') {
this.setImageDimensions();
}
this.openModal();
}
}
this.activity.modal_source_url = this.router.url;
clickedVideo() {
// Already filtered out mobile users/non-canary in video.component.ts
// So this is just applicable to desktop/tablet in canary and should always show modal
this.activity.custom_data.dimensions = this.videoDimensions;
this.openModal();
}
this.overlayModal.create(MediaModalComponent, this.activity, {
class: 'm-overlayModal--media'
}).present();
} else {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
openModal() {
this.activity.modal_source_url = this.router.url;
this.overlayModal.create(MediaModalComponent, this.activity, {
class: 'm-overlayModal--media'
}).present();
}
goToMediaPage() {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
}
......@@ -80,10 +80,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
this._emitCanPlayThrough();
};
protected _dblClick = () => {
this.requestFullScreen();
};
protected _onError = e => {
this.loading = false;
this.detectChanges();
......@@ -131,7 +127,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
ngOnInit() {
const player = this.getPlayer();
player.addEventListener('dblclick', this._dblClick);
player.addEventListener('playing', this._emitPlay);
player.addEventListener('pause', this._emitPause);
player.addEventListener('ended', this._emitEnd);
......@@ -161,7 +156,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
const player = this.getPlayer();
if (player) {
player.removeEventListener('dblclick', this._dblClick);
player.removeEventListener('playing', this._emitPlay);
player.removeEventListener('pause', this._emitPause);
player.removeEventListener('ended', this._emitEnd);
......
......@@ -12,7 +12,7 @@
(onError)="onError()"
(onCanPlayThrough)="onCanPlayThrough()"
(onLoadedMetadata)="loadedMetadata()"
(click)="isModal ? toggle() : requestMediaModal()"
(click)="clickedVideo()"
#player
></m-video--direct-http-player>
......@@ -29,14 +29,14 @@
(onError)="onError()"
(onCanPlayThrough)="onCanPlayThrough()"
(onLoadedMetadata)="loadedMetadata()"
(click)="isModal ? toggle() : requestMediaModal()"
(click)="clickedVideo()"
#player
></m-video--torrent-player>
<ng-container *ngIf="playerRef">
<i *ngIf="!playerRef.isPlaying() && !playerRef.isLoading() || isActivity && metadataLoaded"
<i *ngIf="(!playerRef.isPlaying() && !playerRef.isLoading()) || (isActivity && metadataLoaded && !playerRef.isPlaying())"
class="material-icons minds-video-play-icon"
(click)="isModal ? toggle() : requestMediaModal()"
(click)="clickedVideo()"
>play_circle_outline</i>
<ng-content></ng-content>
......@@ -97,7 +97,7 @@
(select)="selectedQuality($event)"
></m-video--quality-selector>
<i *ngIf="!isModal" class="material-icons" (click)="playerRef.requestFullScreen()">tv</i>
<i *ngIf="!isModal && !isActivity" class="material-icons" (click)="toggleFullscreen($event)">tv</i>
</div>
<div class="m-video--torrent-info" *ngIf="torrentInfo && current.type === 'torrent'">
......
......@@ -22,6 +22,10 @@ import { webtorrentServiceMock } from '../../../../../tests/webtorrent-service-m
import { MindsPlayerInterface } from './players/player.interface';
import { MediaModalComponent } from '../../modal/modal.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FeaturesService } from '../../../../services/features.service';
import { featuresServiceMock } from '../../../../../tests/features-service-mock.spec';
@Component({
selector: 'm-video--direct-http-player',
......@@ -174,7 +178,8 @@ describe('MindsVideo', () => {
providers: [
{ provide: ScrollService, useValue: scrollServiceMock },
{ provide: Client, useValue: clientMock },
{ provide: WebtorrentService, useValue: webtorrentServiceMock }
{ provide: WebtorrentService, useValue: webtorrentServiceMock },
{ provide: FeaturesService, useValue: featuresServiceMock },
]
})
.compileComponents(); // compile template and css
......
import { Component, ElementRef, Input, Output, EventEmitter, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Component, ElementRef, Input, Output, EventEmitter, ViewChild, ChangeDetectorRef, OnDestroy, HostListener } from '@angular/core';
import { Router } from '@angular/router';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { MindsVideoProgressBar } from './progress-bar/progress-bar.component';
......@@ -9,6 +9,7 @@ import { ScrollService } from '../../../../services/ux/scroll';
import { MindsPlayerInterface } from './players/player.interface';
import { WebtorrentService } from '../../../webtorrent/webtorrent.service';
import { SOURCE_CANDIDATE_PICK_ZIGZAG, SourceCandidates } from './source-candidates';
import { FeaturesService } from '../../../../services/features.service';
import isMobile from '../../../../helpers/is-mobile';
@Component({
......@@ -16,7 +17,7 @@ import isMobile from '../../../../helpers/is-mobile';
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()',
'[class.clickable]':'canPlayThrough'
'[class.clickable]':'metadataLoaded'
},
templateUrl: 'video.component.html',
animations: [
......@@ -43,6 +44,8 @@ export class MindsVideoComponent implements OnDestroy {
@Input() poster: string = '';
@Input() isActivity: boolean = false;
@Input() isModal: boolean = false;
// @Input() isTheatre: boolean = false;
@Output('finished') finished: EventEmitter<any> = new EventEmitter();
......@@ -82,6 +85,8 @@ export class MindsVideoComponent implements OnDestroy {
stopSeekerTimeout: any = null;
metadataLoaded: boolean = false;
canPlayThrough: boolean = false;
isFullscreen: boolean = false;
isMobile: boolean = false;
current: { type: 'torrent' | 'direct-http', src: string };
protected candidates: SourceCandidates = new SourceCandidates();
......@@ -100,7 +105,8 @@ export class MindsVideoComponent implements OnDestroy {
public client: Client,
protected webtorrent: WebtorrentService,
protected cd: ChangeDetectorRef,
private router: Router,
protected featuresService: FeaturesService,
private router: Router
) { }
ngOnInit() {
......@@ -185,17 +191,18 @@ export class MindsVideoComponent implements OnDestroy {
}
onMouseEnter() {
if (this.isActivity) {
if (this.isActivity && this.featuresService.has('media-modal')) {
return;
}
this.progressBar.getSeeker();
this.progressBar.enableKeyControls();
this.showControls = true;
if (this.videoMetadataLoaded){
this.progressBar.getSeeker();
this.progressBar.enableKeyControls();
this.showControls = true;
}
}
onMouseLeave() {
if (this.stageHover || this.isActivity) {
if (this.featuresService.has('media-modal') && (this.stageHover || this.isActivity)) {
return;
}
......@@ -391,28 +398,85 @@ export class MindsVideoComponent implements OnDestroy {
this.toggle();
}
requestMediaModal() {
if (!this.canPlayThrough) {
clickedVideo() {
if (!this.metadataLoaded) {
return;
}
if (this.isModal) {
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.isMobile = true;
this.toggle();
return;
}
// Mobile (not tablet) users go to media page instead of modal
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
this.router.navigate([`/media/${this.guid}`]);
if (this.isActivity && this.featuresService.has('media-modal')){
this.mediaModalRequested.emit();
return;
}
this.mediaModalRequested.emit();
this.toggle();
}
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
}
// * FULLSCREEN * --------------------------------------------------------------------------------
// Listen for fullscreen change event in case user enters/exits full screen without clicking button
@HostListener('document:fullscreenchange', ['$event'])
@HostListener('document:webkitfullscreenchange', ['$event'])
@HostListener('document:mozfullscreenchange', ['$event'])
@HostListener('document:MSFullscreenChange', ['$event'])
onFullscreenChange(event) {
if ( !document.fullscreenElement &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
this.isFullscreen = false;
} else {
this.isFullscreen = true;
}
}
toggleFullscreen($event) {
// This will only work on the main video on a media page (not comment attachments)
// TODO: make this work on pages with more than one m-video (i.e. feeds)
const elem = document.querySelector('m-video');
// If fullscreen is not already enabled
if ( !document['fullscreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
// Request full screen
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem['webkitRequestFullscreen']) {
elem['webkitRequestFullscreen']();
} else if (elem['mozRequestFullScreen']) {
elem['mozRequestFullScreen']();
} else if (elem['msRequestFullscreen']) {
elem['msRequestFullscreen']();
}
this.isFullscreen = true;
return;
}
// If fullscreen is already enabled, exit it
if ( document.exitFullscreen ) {
document.exitFullscreen();
} else if (document['webkitExitFullscreen']) {
document['webkitExitFullscreen']();
} else if (document['mozCancelFullScreen']) {
document['mozCancelFullScreen']();
} else if (document['msExitFullscreen']) {
document['msExitFullscreen']();
}
this.isFullscreen = false;
}
}
export { VideoAds } from './ads.component';
......@@ -596,3 +596,9 @@ m-media--grid{
.m-media-content--button-boost{
padding-left: $minds-padding;
}
.m-comment__attachment {
img, minds-video {
max-width: 50%;
}
}
......@@ -12,7 +12,7 @@
[style.line-height]="stageHeight + 'px'"
(mouseenter)="onMouseEnterStage()"
(mouseleave)="onMouseLeaveStage()"
(touchend)="showOverlays()"
(touchend)="showOverlaysOnTablet()"
>
<!-- LOADING PANEL -->
<div class="m-mediaModal__loadingPanel" *ngIf="isLoading">
......@@ -31,8 +31,8 @@
<img class="m-mediaModal__media--image"
[src]="thumbnail"
(load)="isLoaded()"
[style.height]="entity.height + 'px'"
[style.width]="entity.width + 'px'"
[style.height]="entityHeight + 'px'"
[style.width]="entityWidth + 'px'"
/>
</div>
......@@ -43,8 +43,8 @@
[style.height]="mediaHeight + 'px'"
>
<m-video class="m-mediaModal__media--video"
[style.height]="entity.height + 'px'"
[style.width]="entity.width + 'px'"
[style.height]="entityHeight + 'px'"
[style.width]="entityWidth + 'px'"
[isModal]="true"
[autoplay]="true"
[muted]="false"
......@@ -62,22 +62,28 @@
<!-- OVERLAY -->
<div class="m-mediaModal__overlayContainer"
*ngIf="showOverlay"
*ngIf="overlayVisible"
@fastFadeAnimation
>
<div class="m-mediaModal__overlayTitleWrapper">
<!-- TITLE -->
<span class="m-mediaModal__overlayTitle m-mediaModal__overlayTitle--notFullscreen" *ngIf="!isFullscreen">
<a [routerLink]="['/media', entity.entity_guid]">{{title}}</a>
<a [routerLink]="['/media', entity.entity_guid]"
(click)="$event.stopPropagation()"
>{{title}}</a>
</span>
<!-- TITLE: FULLSCREEN -->
<span class="m-mediaModal__overlayTitle m-mediaModal__overlayTitle--fullscreen" *ngIf="isFullscreen">
<a [routerLink]="['/', entity.ownerObj.username]">
<a [routerLink]="['/', entity.ownerObj.username]"
(click)="$event.stopPropagation()"
>
<img class="avatar" [src]="minds.cdn_url + 'icon/' + entity.ownerObj.guid + '/small/' + ownerIconTime" class="mdl-shadow--2dp"/>
<span title={{entity.ownerObj.name}}>{{entity.ownerObj.name}}</span>
</a>
<div class="m-mediaModal__overlayTitleSeparator"></div>
<a [routerLink]="['/media', entity.entity_guid]">{{title}}</a>
<a [routerLink]="['/media', entity.entity_guid]"
(click)="$event.stopPropagation()"
>{{title}}</a>
</span>
</div>
<!-- FULLSCREEN BUTTON -->
......
......@@ -55,6 +55,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
stageHeight: number;
mediaWidth: number;
mediaHeight: number;
entityWidth: number;
entityHeight: number;
maxStageWidth: number;
maxHeight: number;
......@@ -76,15 +78,15 @@ export class MediaModalComponent implements OnInit, OnDestroy {
isOpen: boolean = false;
isOpenTimeout: any = null;
showOverlay: boolean = false;
overlayVisible: boolean = false;
tabletOverlayTimeout: any = null;
routerSubscription: Subscription;
@Input('entity') set data(entity) {
this.entity = entity;
this.entity.width = 0;
this.entity.height = 0;
this.entityWidth = 0;
this.entityHeight = 0;
}
// Used to make sure video progress bar seeker / hover works
......@@ -145,7 +147,6 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// (but don't actually redirect)
this.location.replaceState(`/media/${this.entity.entity_guid}`);
// When user clicks a link from inside the modal
this.routerSubscription = this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
......@@ -168,16 +169,16 @@ export class MediaModalComponent implements OnInit, OnDestroy {
if (!this.isVideo) {
// Image
this.entity.width = this.entity.custom_data[0].width;
this.entity.height = this.entity.custom_data[0].height;
this.entityWidth = this.entity.custom_data[0].width;
this.entityHeight = this.entity.custom_data[0].height;
this.thumbnail = `${this.minds.cdn_url}fs/v1/thumbnail/${this.entity.entity_guid}/xlarge`;
} else {
this.entity.width = this.entity.custom_data.dimensions.width;
this.entity.height = this.entity.custom_data.dimensions.height;
this.entityWidth = this.entity.custom_data.dimensions.width;
this.entityHeight = this.entity.custom_data.dimensions.height;
this.thumbnail = this.entity.custom_data.thumbnail_src; // Not currently used
}
this.aspectRatio = this.entity.width / this.entity.height;
this.aspectRatio = this.entityWidth / this.entityHeight;
this.calculateDimensions();
}
......@@ -211,7 +212,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// If black stage background is visible on top/bottom, each strip should be at least 20px high
const heightDiff = this.stageHeight - this.mediaHeight;
if ( 0 < heightDiff && heightDiff <= this.padding * 2) {
this.stageHeight += 40;
this.stageHeight += (this.padding * 2);
}
} else { // isFullscreen
......@@ -221,8 +222,15 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageWidth = windowWidth;
this.stageHeight = windowHeight;
// Set mediaHeight as tall as possible but not taller than instrinsic height
this.mediaHeight = this.entity.height < windowHeight ? this.entity.height : windowHeight;
if (this.entity.custom_type === 'image') {
// For images, set mediaHeight as tall as possible but not taller than instrinsic height
this.mediaHeight = this.entityHeight < windowHeight ? this.entityHeight : windowHeight;
} else {
// It's ok if videos are taller than intrinsic height
this.mediaHeight = windowHeight;
}
this.mediaWidth = this.scaleWidth();
if ( this.mediaWidth > windowWidth ) {
......@@ -233,8 +241,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
}
if (this.isVideo) {
this.entity.height = this.mediaHeight;
this.entity.width = this.mediaWidth;
this.entityHeight = this.mediaHeight;
this.entityWidth = this.mediaWidth;
}
this.modalWidth = this.stageWidth + this.contentWidth;
......@@ -248,13 +256,13 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageHeight = Math.max(this.maxHeight, this.minStageHeight);
// Set mediaHeight as tall as stage but no larger than intrinsic height
if (!this.isVideo && this.entity.height < this.stageHeight) {
if (!this.isVideo && this.entityHeight < this.stageHeight) {
// Image is shorter than stage; scale down stage
this.mediaHeight = this.entity.height;
this.mediaHeight = this.entityHeight;
this.stageHeight = Math.max(this.mediaHeight, this.minStageHeight);
} else {
// Image is taller than stage; scale it down so it fits inside stage
// All videos should be as tall as possible but not taller than stage
// Either: Image is taller than stage; scale it down so it fits inside stage
// Or: Video should be as tall as possible but not taller than stage
this.mediaHeight = this.stageHeight;
}
......@@ -276,13 +284,11 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// shrink vertically until it hits minStageHeight
// When window is narrower than this, start to shrink height
const verticalShrinkWidthThreshold = this.mediaWidth + this.contentWidth + (this.padding * 4); // + 2;
const verticalShrinkWidthThreshold = this.mediaWidth + this.contentWidth + (this.padding * 4);
const widthDiff = verticalShrinkWidthThreshold - window.innerWidth;
// Is window narrow enough to start shrinking vertically?
if ( widthDiff >= 1 ) {
if (widthDiff >= 1) {
// What mediaHeight would be if it shrunk proportionally to difference in width
const mediaHeightPreview = Math.round((this.mediaWidth - widthDiff) / this.aspectRatio);
......@@ -293,7 +299,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageHeight = this.mediaHeight;
} else {
this.stageHeight = this.minStageHeight;
this.mediaHeight = Math.min(this.minStageHeight, this.entity.height);
this.mediaHeight = Math.min(this.minStageHeight, this.entityHeight);
this.mediaWidth = this.scaleWidth();
}
}
......@@ -316,7 +322,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
onFullscreenChange(event) {
this.calculateDimensions();
if ( !document.fullscreenElement &&
!document['webkitFullScreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
this.isFullscreen = false;
......@@ -332,10 +338,10 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// If fullscreen is not already enabled
if ( !document['fullscreenElement'] &&
!document['webkitFullScreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
// Request full screen
// Request full screen
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem['webkitRequestFullscreen']) {
......@@ -384,7 +390,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// Show overlay and video controls
onMouseEnterStage() {
this.showOverlay = true;
this.overlayVisible = true;
if (this.isVideo) {
// Make sure progress bar seeker is updating when video controls are visible
......@@ -393,9 +399,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
}
}
// Hide overlay and video controls
onMouseLeaveStage() {
this.showOverlay = false;
this.overlayVisible = false;
if (this.isVideo) {
// Stop updating progress bar seeker when controls aren't visible
......@@ -407,7 +412,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// * TABLETS ONLY: SHOW OVERLAY & VIDEO CONTROLS * -------------------------------------------
// Briefly display title overlay and video controls when finished loading and stage touch
showOverlays() {
showOverlaysOnTablet() {
this.onMouseEnterStage();
if (this.tabletOverlayTimeout) {
......@@ -425,7 +430,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.isLoading = false;
if ( this.isTablet ) {
this.showOverlays();
this.showOverlaysOnTablet();
}
}
......
......@@ -42,7 +42,6 @@ import { MindsVideoComponent } from '../../components/video/video.component';
[torrent]="[{ res: '720', key: object.guid + '/720.mp4' }, { res: '360', key: object.guid + '/360.mp4' }]"
[log]="object.guid"
[playCount]="false"
(click)="togglePlay($event)"
#player>
<video-ads [player]="player" *ngIf="object.monetized"></video-ads>
......
<div class="m-marketing m-mobile--marketing">
<!--Top / Hero -->
<div class="m-marketing--hero">
<div class="m-marketing--hero--video">
<img [src]="minds.cdn_assets_url + 'assets/photos/circles.png'">
</div>
<div class="m-marketing--hero--inner">
<div class="m-marketing--hero--slogans">
<h2>
Minds Android App
</h2>
<h1>
Minds Mobile App
</h1>
</div>
<div class="m-marketing--hero--actions">
<i class="material-icons m-mobile__giantDroid">android</i>
<!-- Space for an icon -->
</div>
</div>
</div>
<section class="m-marketing--section mdl-color--white" style="padding-bottom: 0; padding-top: 0">
<div class="m-marketing--downloadPlatform">
<h2>
For Android:
</h2>
<!--Android APK -->
<div class="m-marketing--downloadOption">
<div class="m-marketing--downloadButton">
<a [href]="latestRelease.href" target="_blank"><img [src]="minds.cdn_assets_url + 'assets/marketing/mobile-dl-button.png'"/></a>
<span>(recommended)</span>
</div>
<p>Download the mobile app directly from Minds for the best experience. All content is accessible.
<p>In order to install this version you must:</p>
<ul>
<li>Update Phone settings to "enable downloads from unverified source"</li>
<li>Download and install!</li>
</ul>
</div>
<!--Play Store -->
<div class="m-marketing--downloadOption">
<div class="m-marketing--downloadButton">
<a href='https://play.google.com/store/apps/details?id=com.minds.mobile&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1' target="_blank"><img alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a>
</div>
<p>You may also download the mobile app from the Google Play Store. Due to the Google Play Store terms of service, NSFW content is not accessible in this version.</p>
</div>
</div>
<!--iOS Download -->
<div class="m-marketing--downloadPlatform">
<h2>
For iPhone:
</h2>
<div class="m-marketing--downloadOption">
<div class="m-marketing--downloadButton m-marketing--downloadButton--iOS">
<a href="https://geo.itunes.apple.com/us/app/minds-com/id961771928?mt=8&amp;uo=6" target="_blank"><img alt="iOS App" src="https://devimages-cdn.apple.com/app-store/marketing/guidelines/images/badge-download-on-the-app-store.svg"></a>
</div>
<p>iPhone users may download the mobile app directly from the iOS App Store. All features of the Minds Token (wallet, boost, wire, etc) are not currently supported in this version. All content is accessible.</p>
</div>
</div>
<div class="m-marketing--section--subsection m-marketing--section--subsection--first">
<div class="m-marketing--section--subsection-container">
<div class="m-marketing--section--subsection-left m-marketing--section--subsection-text">
<div class="mdl-grid">
<h2>
Release History:
</h2>
<div class="mdl-cell mdl-cell--12-col m-mobile--marketing--spinner" *ngIf="inProgress">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</div>
......@@ -43,7 +75,6 @@
<ng-container *ngIf="release.unstable">Canary -</ng-container>
{{release.version}}
</h2>
<p>Released: {{release.timestamp * 1000 | date:'longDate'}} -
<a [href]="release.href" target="_blank">
Download
......
.m-mobile--marketing {
.m-marketing--hero {
padding:52px 0 !important;
margin-bottom: 32px;
padding: 0;
@media screen and (min-width: $max-mobile) {
padding:52px 0 !important;
}
.m-marketing--hero--inner {
padding-top: 52px;
padding-bottom: 52px;
flex-wrap: wrap;
padding: 52 0 52 0;
.m-marketing--hero--slogans h1 {
text-align: left;
padding-left: $minds-padding * 2;
font-size: xx-large;
font-weight: 700;
@media screen and (min-width: $min-tablet) {
margin-left: 120px;
font-size: 42px;
}
@include m-theme(){
color: themed($m-white-always);
}
}
}
img {
......@@ -24,7 +41,31 @@
}
.m-marketing--section--subsection--first .m-marketing--section--subsection-container {
padding-top: 32px !important;
padding-top: 0;
display: flex;
flex-direction: row;
align-items: center;
text-align: left;
width: 100%;
@media screen and (min-width: $min-tablet) {
width: unset;
margin: 0 25%;
}
.m-marketing--section--subsection-text {
max-width: 100%;
min-width: 100%;
margin: 0;
padding: 0;
h2 {
text-align: left;
font-size: x-large;
font-weight: 700;
padding: $minds-padding * 1.5 0;
margin: 0 0 ($minds-padding * 2) ($minds-padding * 2);
}
}
}
.m-marketing--hero--actions img {
......@@ -47,6 +88,72 @@
}
}
.m-marketing--section {
text-align: left;
margin-left: $minds-padding * 2;
h2 {
text-align: left;
font-size: x-large;
font-weight: 700;
padding-left: 12px;
}
.m-marketing--downloadPlatform {
@media screen and (min-width: $min-tablet) {
margin: 0 25%;
}
.m-marketing--downloadOption {
padding-bottom: $minds-padding * 2;
@media screen and (max-width: $min-tablet) {
padding-bottom: $minds-padding * 1;
}
.m-marketing--downloadButton--iOS {
img {
padding-left: 14px;
}
}
.m-marketing--downloadButton {
display: flex;
flex-wrap: wrap;
padding-bottom: $minds-padding * 4;
@media screen and (max-width: $min-tablet) {
padding-bottom: $minds-padding * 2;
}
img {
max-width: 200px;
min-width: 175px;
}
}
span {
font-size: large;
align-self: center;
padding-left: $minds-padding * 2;
font-weight: 400;
}
p {
padding: 0 ($minds-padding * 1.5);
line-height: 24pt;
@include m-theme(){
color: themed($m-grey-600);
}
}
li {
@include m-theme(){
color: themed($m-grey-600);
}
}
}
}
}
@media screen and (min-width: 500px) {
.m-marketing--hero--slogans h2 {
font-size: 60px !important;
......@@ -94,10 +201,3 @@
}
}
.m-mobile__giantDroid {
font-size: 152px;
@include m-theme(){
color: themed($m-white);
}
}
......@@ -2,6 +2,7 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from '@angular/co
import {MindsTitle} from '../../../services/ux/title';
import {Session} from '../../../services/session';
import {MobileService} from "../mobile.service";
import { first } from 'lodash';
@Component({
selector: 'm-mobile--marketing',
......@@ -15,6 +16,9 @@ export class MobileMarketingComponent {
releases: any[] = [];
inProgress: boolean = false;
error: string;
latestRelease: any = {
href: null,
};
constructor(
protected title: MindsTitle,
......@@ -26,18 +30,20 @@ export class MobileMarketingComponent {
ngOnInit() {
this.title.setTitle('Mobile');
this.user = this.session.getLoggedInUser();
this.load();
}
/**
* Gets releases for view.
*/
async load() {
try {
this.inProgress = true;
this.detectChanges();
this.releases = await this.service.getReleases();
this.latestRelease = await first(this.releases.filter(rel => rel.latest && !rel.unstable));
} catch (e) {
console.error(e);
this.error = e.message || 'Unknown error';
......
......@@ -269,7 +269,7 @@
<ng-template ngSwitchCase="remind">
<div *ngIf="notification.entityObj">
<a [routerLink]="['/newsfeed', notification.entityObj.guid]" *ngIf="notification.entityObj.type == 'object'">
<p><ng-container i18n="@@NOTIFICATIONS__NOTIFICATION__REMIND__REMINDED">{{notification.fromObj.name}} reminded</ng-container>
<p><ng-container i18n="@@NOTIFICATIONS__NOTIFICATION__REMIND__REMINDED">{{notification.fromObj.name}} reminded </ng-container>
<span class="pseudo-link mdl-color-text--blue-grey-400" *ngIf="notification.entityObj.title">{{notification.entityObj.title | excerpt}}</span>
<span class="pseudo-link mdl-color-text--blue-grey-400" *ngIf="!notification.entityObj.title" i18n="object belonging to current user@@NOTIFICATIONS__NOTIFICATION__OWN_OBJECT">your {{notification.entityObj.subtype}}</span>
</p>
......
......@@ -66,9 +66,13 @@ export class WireCreatorRewardsComponent {
@Input('rewards') set _rewards(rewards) {
this.rewards = [];
const methodsMap = [
{ method: 'money', currency: 'usd' },
{ method: 'tokens', currency: 'tokens' },
];
if (this.featuresService.has('wire-multi-currency')) {
methodsMap.push({ method: 'money', currency: 'usd' });
}
for (const { method, currency } of methodsMap) {
for (const reward of rewards.rewards[method]) {
this.rewards.push({
......
......@@ -178,7 +178,7 @@ export class Client {
* Build the options
*/
private buildOptions(options: Object) {
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN');
const XSRF_TOKEN = this.cookie.get('XSRF-TOKEN') || '';
const headers = new HttpHeaders({
'X-XSRF-TOKEN': XSRF_TOKEN,
......
File added
This source diff could not be displayed because it is too large. You can view the blob instead.
File added
File added
File added
src/assets/marketing/mobile-dl-button.png

8.2 KB

......@@ -64,3 +64,5 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
import './polyfills/pad-start.js';
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
padString = String(typeof padString !== 'undefined' ? padString : ' ');
if (this.length >= targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
}
return padString.slice(0, targetLength) + String(this);
}
};
}
window.FontAwesomeCdnConfig = {
autoA11y: {
enabled: true
},
asyncLoading: {
enabled: false,
},
reporting: {
enabled: true,
domains: "localhost, *.dev"
},
useUrl: "use.fontawesome.com",
faCdnUrl: "https://cdn.fontawesome.com:443",
code: "b19f8744bf"
};
!function(){function a(a){var b,c=[],d=document,e=d.documentElement.doScroll,f="DOMContentLoaded",g=(e?/^loaded|^c/:/^loaded|^i|^c/).test(d.readyState);g||d.addEventListener(f,b=function(){for(d.removeEventListener(f,b),g=1;b=c.shift();)b()}),g?setTimeout(a,0):c.push(a)}function b(a,b){var c=!1;return a.split(",").forEach(function(a){var d=new RegExp(a.trim().replace(".","\\.").replace("*","(.*)"));b.match(d)&&(c=!0)}),c}function c(a){"undefined"!=typeof MutationObserver&&new MutationObserver(a).observe(document,{childList:!0,subtree:!0})}function d(a){var b,c,d,e;a=a||"fa",b=document.querySelectorAll("."+a),Array.prototype.forEach.call(b,function(a){c=a.getAttribute("title"),a.setAttribute("aria-hidden","true"),d=a.nextElementSibling?!a.nextElementSibling.classList.contains("sr-only"):!0,c&&d&&(e=document.createElement("span"),e.innerHTML=c,e.classList.add("sr-only"),a.parentNode.insertBefore(e,a.nextSibling))})}!function(){"use strict";function a(a){l.push(a),1==l.length&&k()}function b(){for(;l.length;)l[0](),l.shift()}function c(a){this.a=m,this.b=void 0,this.f=[];var b=this;try{a(function(a){f(b,a)},function(a){g(b,a)})}catch(c){g(b,c)}}function d(a){return new c(function(b,c){c(a)})}function e(a){return new c(function(b){b(a)})}function f(a,b){if(a.a==m){if(b==a)throw new TypeError;var c=!1;try{var d=b&&b.then;if(null!=b&&"object"==typeof b&&"function"==typeof d)return void d.call(b,function(b){c||f(a,b),c=!0},function(b){c||g(a,b),c=!0})}catch(e){return void(c||g(a,e))}a.a=0,a.b=b,h(a)}}function g(a,b){if(a.a==m){if(b==a)throw new TypeError;a.a=1,a.b=b,h(a)}}function h(b){a(function(){if(b.a!=m)for(;b.f.length;){var a=b.f.shift(),c=a[0],d=a[1],e=a[2],a=a[3];try{0==b.a?e("function"==typeof c?c.call(void 0,b.b):b.b):1==b.a&&("function"==typeof d?e(d.call(void 0,b.b)):a(b.b))}catch(f){a(f)}}})}function i(a){return new c(function(b,c){function d(c){return function(d){g[c]=d,f+=1,f==a.length&&b(g)}}var f=0,g=[];0==a.length&&b(g);for(var h=0;h<a.length;h+=1)e(a[h]).c(d(h),c)})}function j(a){return new c(function(b,c){for(var d=0;d<a.length;d+=1)e(a[d]).c(b,c)})}var k,l=[];k=function(){setTimeout(b)};var m=2;c.prototype.g=function(a){return this.c(void 0,a)},c.prototype.c=function(a,b){var d=this;return new c(function(c,e){d.f.push([a,b,c,e]),h(d)})},window.Promise||(window.Promise=c,window.Promise.resolve=e,window.Promise.reject=d,window.Promise.race=j,window.Promise.all=i,window.Promise.prototype.then=c.prototype.c,window.Promise.prototype["catch"]=c.prototype.g)}(),function(){function a(a){this.el=a;for(var b=a.className.replace(/^\s+|\s+$/g,"").split(/\s+/),c=0;c<b.length;c++)d.call(this,b[c])}function b(a,b,c){Object.defineProperty?Object.defineProperty(a,b,{get:c}):a.__defineGetter__(b,c)}if(!("undefined"==typeof window.Element||"classList"in document.documentElement)){var c=Array.prototype,d=c.push,e=c.splice,f=c.join;a.prototype={add:function(a){this.contains(a)||(d.call(this,a),this.el.className=this.toString())},contains:function(a){return-1!=this.el.className.indexOf(a)},item:function(a){return this[a]||null},remove:function(a){if(this.contains(a)){for(var b=0;b<this.length&&this[b]!=a;b++);e.call(this,b,1),this.el.className=this.toString()}},toString:function(){return f.call(this," ")},toggle:function(a){return this.contains(a)?this.remove(a):this.add(a),this.contains(a)}},window.DOMTokenList=a,b(Element.prototype,"classList",function(){return new a(this)})}}();var e=function(a,b,c){function d(a){return g.body?a():void setTimeout(function(){d(a)})}function e(){h.addEventListener&&h.removeEventListener("load",e),h.media=c||"all"}var f,g=window.document,h=g.createElement("link");if(b)f=b;else{var i=(g.body||g.getElementsByTagName("head")[0]).childNodes;f=i[i.length-1]}var j=g.styleSheets;h.rel="stylesheet",h.href=a,h.media="only x",d(function(){f.parentNode.insertBefore(h,b?f:f.nextSibling)});var k=function(a){for(var b=h.href,c=j.length;c--;)if(j[c].href===b)return a();setTimeout(function(){k(a)})};return h.addEventListener&&h.addEventListener("load",e),h.onloadcssdefined=k,k(e),h},f=null;!function(){function a(a,b){document.addEventListener?a.addEventListener("scroll",b,!1):a.attachEvent("scroll",b)}function b(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b),a()}):document.attachEvent("onreadystatechange",function c(){"interactive"!=document.readyState&&"complete"!=document.readyState||(document.detachEvent("onreadystatechange",c),a())})}function c(a){this.a=document.createElement("div"),this.a.setAttribute("aria-hidden","true"),this.a.appendChild(document.createTextNode(a)),this.b=document.createElement("span"),this.c=document.createElement("span"),this.h=document.createElement("span"),this.f=document.createElement("span"),this.g=-1,this.b.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;",this.c.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;",this.f.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;",this.h.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;",this.b.appendChild(this.h),this.c.appendChild(this.f),this.a.appendChild(this.b),this.a.appendChild(this.c)}function d(a,b){a.a.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;left:-999px;white-space:nowrap;font:"+b+";"}function e(a){var b=a.a.offsetWidth,c=b+100;return a.f.style.width=c+"px",a.c.scrollLeft=c,a.b.scrollLeft=a.b.scrollWidth+100,a.g!==b?(a.g=b,!0):!1}function g(b,c){function d(){var a=f;e(a)&&a.a.parentNode&&c(a.g)}var f=b;a(b.b,d),a(b.c,d),e(b)}function h(a,b){var c=b||{};this.family=a,this.style=c.style||"normal",this.weight=c.weight||"normal",this.stretch=c.stretch||"normal"}function i(){if(null===l){var a=document.createElement("div");try{a.style.font="condensed 100px sans-serif"}catch(b){}l=""!==a.style.font}return l}function j(a,b){return[a.style,a.weight,i()?a.stretch:"","100px",b].join(" ")}var k=null,l=null,m=null;h.prototype.load=function(a,e){var f=this,h=a||"BESbswy",i=e||3e3,l=(new Date).getTime();return new Promise(function(a,e){if(null===m&&(m=!!window.FontFace),m){var n=new Promise(function(a,b){function c(){(new Date).getTime()-l>=i?b():document.fonts.load(j(f,f.family),h).then(function(b){1<=b.length?a():setTimeout(c,25)},function(){b()})}c()}),o=new Promise(function(a,b){setTimeout(b,i)});Promise.race([o,n]).then(function(){a(f)},function(){e(f)})}else b(function(){function b(){var b;(b=-1!=q&&-1!=r||-1!=q&&-1!=s||-1!=r&&-1!=s)&&((b=q!=r&&q!=s&&r!=s)||(null===k&&(b=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),k=!!b&&(536>parseInt(b[1],10)||536===parseInt(b[1],10)&&11>=parseInt(b[2],10))),b=k&&(q==t&&r==t&&s==t||q==u&&r==u&&s==u||q==v&&r==v&&s==v)),b=!b),b&&(w.parentNode&&w.parentNode.removeChild(w),clearTimeout(x),a(f))}function m(){if((new Date).getTime()-l>=i)w.parentNode&&w.parentNode.removeChild(w),e(f);else{var a=document.hidden;!0!==a&&void 0!==a||(q=n.a.offsetWidth,r=o.a.offsetWidth,s=p.a.offsetWidth,b()),x=setTimeout(m,50)}}var n=new c(h),o=new c(h),p=new c(h),q=-1,r=-1,s=-1,t=-1,u=-1,v=-1,w=document.createElement("div"),x=0;w.dir="ltr",d(n,j(f,"sans-serif")),d(o,j(f,"serif")),d(p,j(f,"monospace")),w.appendChild(n.a),w.appendChild(o.a),w.appendChild(p.a),document.body.appendChild(w),t=n.a.offsetWidth,u=o.a.offsetWidth,v=p.a.offsetWidth,m(),g(n,function(a){q=a,b()}),d(n,j(f,'"'+f.family+'",sans-serif')),g(o,function(a){r=a,b()}),d(o,j(f,'"'+f.family+'",serif')),g(p,function(a){s=a,b()}),d(p,j(f,'"'+f.family+'",monospace'))})})},f=h}();var g={observe:function(a,b){for(var c=b.prefix,d=function(a){var b=a.weight?"-"+a.weight:"",d=a.style?"-"+a.style:"",e=a.className?"-"+a.className:"",g=a.className?"-"+a.className+b+d:"",h=document.getElementsByTagName("html")[0].classList,i=function(a){h.add(c+e+"-"+a),h.add(c+g+"-"+a)},j=function(a){h.remove(c+e+"-"+a),h.remove(c+g+"-"+a)};i("loading"),new f(a.familyName).load(a.testString).then(function(){i("ready"),j("loading")},function(){i("failed"),j("loading")})},e=0;e<a.length;e++)d(a[e])}},h={load:function(a){var b=document.createElement("link");b.href=a,b.media="all",b.rel="stylesheet",document.getElementsByTagName("head")[0].appendChild(b)},loadAsync:function(a){e(a)}},i={load:function(a){var b=document.createElement("script"),c=document.scripts[0];b.src=a,c.parentNode.appendChild(b)}};if(window.FontAwesomeCdnConfig){var j=window.FontAwesomeCdnConfig,k=j.useUrl,l=j.faCdnUrl,m=j.code,n="FontAwesome",o="fa",p="",q=d.bind(d,"fa"),r=function(){};j.autoA11y.enabled&&(a(q),c(q)),j.reporting.enabled&&b(j.reporting.domains,location.host)&&i.load(l+"/js/stats.js"),cssUrl="https://"+k+"/"+m+".css",new f(n).load(p).then(function(){var a=(window.FontAwesomeHooks||{}).loaded||r;a()}),j.asyncLoading.enabled?h.loadAsync(cssUrl):h.load(cssUrl),g.observe([{familyName:n,testString:p}],{prefix:o+"-events-icons"})}}();
This diff is collapsed.
......@@ -251,6 +251,7 @@ blockquote:after { content: '\201D'; }
@import 'overrides.scss';
@import 'ionicons.scss';
@import 'fontawesome.scss';
/**
* For modern browsers
......