Skip to content
Commits on Source (22)
image: markharding/minds-front-base
services:
- docker:dind
stages:
- test
- build
......@@ -154,6 +151,8 @@ build:production:i18n:
prepare:review:
stage: prepare
image: minds/ci:latest
services:
- docker:dind
script:
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
- docker build -t $CI_REGISTRY_IMAGE/front-init:$CI_PIPELINE_ID -f containers/front-init/Dockerfile dist/.
......@@ -179,6 +178,8 @@ prepare:review:sentry:
prepare:production:
stage: prepare
image: minds/ci:latest
services:
- docker:dind
script:
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
- docker build -t $CI_REGISTRY_IMAGE/front-init:$CI_PIPELINE_ID -f containers/front-init/Dockerfile dist/.
......@@ -261,6 +262,8 @@ review:stop:
.deploy: &deploy
image: minds/ci:latest
services:
- docker:dind
script:
## Sync assets with CDN
- aws s3 sync dist $S3_REPOSITORY_URL
......
......@@ -6,7 +6,7 @@ Minds Front
Front-end web application for Minds. Please run inside of [the Minds repo](https://github.com/minds/minds).
## Documentation
Documentation for Minds can be found at [minds.org/docs](https://www.minds.org/docs)
Please see the documentation on [developers.minds.com](https://developers.minds.com) for instructions on how to [build the Minds Front-end](https://developers.minds.com/docs/guides/frontend).
### Building
Please see the documentation on Minds.org for instructions on how to [build the Minds Front-end](https://www.minds.org/docs/install/preparation.html#front-end).
......
import generateRandomId from '../support/utilities';
context('Newsfeed', () => {
before(() => {
cy.getCookie('minds_sess').then(sessionCookie => {
......@@ -14,7 +16,6 @@ context('Newsfeed', () => {
cy.route('POST', '**/api/v1/media').as('mediaPOST');
cy.route('POST', '**/api/v1/newsfeed/**').as('newsfeedEDIT');
cy.route('POST', '**/api/v1/media/**').as('mediaEDIT');
cy.visit('/newsfeed/subscriptions')
.location('pathname')
.should('eq', '/newsfeed/subscriptions');
......@@ -37,6 +38,19 @@ context('Newsfeed', () => {
cy.get('minds-newsfeed-poster textarea').type(content);
};
const attachRichEmbed = (embedUrl) => {
cy.get('minds-newsfeed-poster').should('be.visible');
cy.get('minds-newsfeed-poster textarea')
.type(embedUrl);
cy.route('GET', `**/api/v1/newsfeed/preview?url=${embedUrl}**`)
.as('previewGET')
.wait('@previewGET')
.then(xhr => {
expect(xhr.status).to.equal(200);
});
}
const attachImageToActivity = () => {
cy.uploadFile(
'#attachment-input-poster',
......@@ -511,4 +525,140 @@ context('Newsfeed', () => {
deleteActivityFromNewsfeed();
});
it('should show a rich embed post from youtube in a modal', () => {
const content = generateRandomId() + " ",
url = 'https://www.youtube.com/watch?v=jNQXAC9IVRw';
// set up post.
newActivityContent(content);
attachRichEmbed(url);
// post and await.
cy.get('.m-posterActionBar__PostButton')
.click()
.wait('@newsfeedPOST').then(xhr => {
expect(xhr.status).to.equal(200);
//get activity, click it.
cy.get(`[minds-data-activity-guid='${xhr.response.body.guid}']`)
.click();
//check modal is open.
cy.get('[data-cy=data-minds-media-modal]')
.contains(content);
// close modal and tidy.
cy.get('.m-overlay-modal--backdrop')
.click({force: true});
deleteActivityFromNewsfeed();
});
});
it('should not open vimeo in a modal', () => {
const content = generateRandomId() + " ",
url = 'https://vimeo.com/8733915';
// set up post.
newActivityContent(content);
attachRichEmbed(url);
// post and await.
cy.get('.m-posterActionBar__PostButton')
.click()
.wait('@newsfeedPOST').then(xhr => {
expect(xhr.status).to.equal(200);
//get activity, make assertions tht would not be true for modals.
cy.get(`[minds-data-activity-guid='${xhr.response.body.guid}']`)
.should('be.visible')
.get('iframe')
.should('be.visible')
.get('.minds-more')
.should('be.visible');
// tidy.
deleteActivityFromNewsfeed();
});
});
it('should not open soundcloud in a modal', () => {
const content = generateRandomId() + " ",
url = 'https://soundcloud.com/richarddjames/piano-un10-it-happened';
// set up post.
newActivityContent(content);
attachRichEmbed(url);
// post and await.
cy.get('.m-posterActionBar__PostButton')
.click()
.wait('@newsfeedPOST').then(xhr => {
expect(xhr.status).to.equal(200);
//get activity, make assertions tht would not be true for modals.
cy.get(`[minds-data-activity-guid='${xhr.response.body.guid}']`)
.should('be.visible')
.get('.m-rich-embed-action-overlay')
.should('be.visible')
.get('.minds-more')
.should('be.visible');
deleteActivityFromNewsfeed();
});
});
it('should not open spotify in a modal', () => {
const content = generateRandomId() + " ",
url = 'https://open.spotify.com/track/2MZSXhq4XDJWu6coGoXX1V?si=nvja0EfwR3q6GMQmYg6gPQ';
// set up post.
newActivityContent(content);
attachRichEmbed(url);
// post and await.
cy.get('.m-posterActionBar__PostButton')
.click()
.wait('@newsfeedPOST').then(xhr => {
expect(xhr.status).to.equal(200);
//get activity, make assertions tht would not be true for modals.
cy.get(`[minds-data-activity-guid='${xhr.response.body.guid}']`)
.should('be.visible')
.get('.m-rich-embed-action-overlay')
.should('be.visible')
.get('.minds-more')
.should('be.visible');
deleteActivityFromNewsfeed();
});
});
it('should not open spotify in a modal', () => {
const content = generateRandomId() + " ",
url = 'http://giphygifs.s3.amazonaws.com/media/IzVquL965ib4s/giphy.gif';
// set up post.
newActivityContent(content);
attachRichEmbed(url);
// post and await.
cy.get('.m-posterActionBar__PostButton')
.click()
.wait('@newsfeedPOST').then(xhr => {
expect(xhr.status).to.equal(200);
//get activity, make assertions tht would not be true for modals.
cy.get(`[minds-data-activity-guid='${xhr.response.body.guid}']`)
.should('be.visible')
.get('.m-rich-embed-action-overlay')
.should('be.visible')
.get('.minds-more')
.should('be.visible');
deleteActivityFromNewsfeed();
});
});
});
......@@ -41,13 +41,13 @@
display: flex;
flex-direction: row;
align-items: center;
padding: 0 8px 0 0;
list-style: none;
opacity: 1;
text-overflow: ellipsis;
text-align: left;
a {
> * {
padding: 0 8px 0 0;
text-decoration: none;
width: 100%;
font-weight: 400;
......@@ -94,11 +94,13 @@
.m-dropdown--list--item,
.m-dropdownList__item {
padding: 8px;
@include m-theme() {
border-bottom: 1px solid themed($m-grey-50);
}
> * {
padding: 8px;
}
&.m-dropdown--list--item--selected,
.m-dropdownList__item--selected {
@include m-theme() {
......
import { Injectable } from '@angular/core';
import {
filter,
first,
map,
switchMap,
mergeMap,
skip,
take,
} from 'rxjs/operators';
import { filter, first, switchMap, mergeMap, skip, take } from 'rxjs/operators';
import { FeedsService } from '../../services/feeds.service';
import { Subscription } from 'rxjs';
@Injectable()
export class FeaturedContentService {
offset: number = -1;
offset = 0;
maximumOffset = 0;
feedLength = 0;
protected feedSubscription: Subscription;
constructor(protected feedsService: FeedsService) {
this.onInit();
}
onInit() {
this.feedSubscription = this.feedsService.feed.subscribe(feed => {
this.feedLength = feed.length;
this.maximumOffset = this.feedLength - 1;
});
this.feedsService
.setLimit(12)
.setOffset(0)
......@@ -23,28 +28,36 @@ export class FeaturedContentService {
}
async fetch() {
if (this.offset >= this.feedsService.rawFeed.getValue().length) {
this.offset = -1;
}
// Refetch every 2 calls, if not loading
if (this.offset % 2 && !this.feedsService.inProgress.getValue()) {
this.feedsService.clear();
this.feedsService.fetch();
}
return await this.feedsService.feed
.pipe(
filter(feed => feed.length > 0),
first(),
mergeMap(feed => feed),
filter(entities => entities.length > 0),
mergeMap(feed => feed), // Convert feed array to stream
skip(this.offset++),
take(1),
switchMap(async entity => {
if (!entity) {
return false;
} else {
const resolvedEntity = await entity.pipe(first()).toPromise();
this.resetOffsetAtEndOfStream();
return resolvedEntity;
}
return await entity.pipe(first()).toPromise();
})
)
.toPromise();
}
protected resetOffsetAtEndOfStream() {
if (this.offset >= this.maximumOffset) {
this.offset = 0;
this.fetchNextFeed();
}
}
protected fetchNextFeed() {
if (!this.feedsService.inProgress.getValue()) {
this.feedsService.clear();
this.feedsService.fetch();
}
}
}
......@@ -31,6 +31,12 @@ export class NSFWSelectorComponent {
private storage: Storage
) {}
ngOnInit() {
if (this.service.reasons) {
this.service.reasons.map(r => this.toggle(r.value));
}
}
get service() {
switch (this.serviceRef) {
case 'editing':
......
......@@ -55,7 +55,7 @@
[href]="src.perma_url"
target="_blank"
rel="noopener noreferrer"
class="meta mdl-color-text--blue-grey-900"
class="meta"
[ngClass]="{ 'm-rich-embed-has-thumbnail': src.thumbnail_src, 'm-rich-embed--title--no-padding': hasInlineContentLoaded() }"
>
<h2
......@@ -74,10 +74,7 @@
<a class="thumbnail" *ngIf="preview.thumbnail">
<img src="{{preview.thumbnail}}" />
</a>
<a
class="meta mdl-color-text--blue-grey-900"
[ngClass]="{ 'm-has-thumbnail': preview.thumbnail }"
>
<a class="meta" [ngClass]="{ 'm-has-thumbnail': preview.thumbnail }">
<h2 class="m-rich-embed--title mdl-card__title-text">
{{preview.title | excerpt}}
</h2>
......
......@@ -16,9 +16,6 @@ minds-rich-embed {
left: 0;
width: 100%;
height: 100%;
@include m-theme() {
background-color: rgba(themed($m-black-always), 0.2);
}
&:hover {
background: transparent;
......
......@@ -3,11 +3,14 @@ import {
ElementRef,
ChangeDetectorRef,
ChangeDetectionStrategy,
Output,
EventEmitter,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { RichEmbedService } from '../../../services/rich-embed';
import mediaProxyUrl from '../../../helpers/media-proxy-url';
import { FeaturesService } from '../../../services/features.service';
@Component({
moduleId: module.id,
......@@ -17,18 +20,22 @@ import mediaProxyUrl from '../../../helpers/media-proxy-url';
})
export class MindsRichEmbed {
type: string = '';
mediaSource: string = '';
src: any = {};
preview: any = {};
maxheight: number = 320;
inlineEmbed: any = null;
embeddedInline: boolean = false;
cropImage: boolean = false;
modalRequestSubscribed: boolean = false;
@Output() mediaModalRequested: EventEmitter<any> = new EventEmitter();
private lastInlineEmbedParsed: string;
constructor(
private sanitizer: DomSanitizer,
private service: RichEmbedService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
protected featureService: FeaturesService
) {}
set _src(value: any) {
......@@ -65,6 +72,14 @@ export class MindsRichEmbed {
// Inline Embedding
let inlineEmbed = this.parseInlineEmbed(this.inlineEmbed);
if (
this.featureService.has('media-modal') &&
this.mediaSource === 'youtube'
) {
this.modalRequestSubscribed =
this.mediaModalRequested.observers.length > 0;
}
if (
inlineEmbed &&
inlineEmbed.id &&
......@@ -80,9 +95,35 @@ export class MindsRichEmbed {
}
this.inlineEmbed = inlineEmbed;
if (
this.modalRequestSubscribed &&
this.featureService.has('media-modal') &&
this.mediaSource === 'youtube'
) {
if (this.inlineEmbed && this.inlineEmbed.htmlProvisioner) {
this.inlineEmbed.htmlProvisioner().then(html => {
this.inlineEmbed.html = html;
this.detectChanges();
});
// @todo: catch any error here and forcefully window.open to destination
}
}
}
action($event) {
if (
this.modalRequestSubscribed &&
this.featureService.has('media-modal') &&
this.mediaSource === 'youtube'
) {
$event.preventDefault();
$event.stopPropagation();
this.mediaModalRequested.emit();
return;
}
if (this.inlineEmbed && !this.embeddedInline) {
$event.preventDefault();
$event.stopPropagation();
......@@ -120,6 +161,7 @@ export class MindsRichEmbed {
if ((matches = youtube.exec(url)) !== null) {
if (matches[1]) {
this.mediaSource = 'youtube';
return {
id: `video-youtube-${matches[1]}`,
className:
......@@ -138,12 +180,13 @@ export class MindsRichEmbed {
if ((matches = vimeo.exec(url)) !== null) {
if (matches[1]) {
this.mediaSource = 'vimeo';
return {
id: `video-vimeo-${matches[1]}`,
className:
'm-rich-embed-video m-rich-embed-video-iframe m-rich-embed-video-vimeo',
html: this.sanitizer.bypassSecurityTrustHtml(`<iframe
src="https://player.vimeo.com/video/${matches[1]}?autoplay=1&title=0&byline=0&portrait=0"
src="https://player.vimeo.com/video/${matches[1]}?title=0&byline=0&portrait=0"
frameborder="0"
webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>`),
playable: true,
......@@ -156,6 +199,7 @@ export class MindsRichEmbed {
if ((matches = soundcloud.exec(url)) !== null) {
if (matches[1]) {
this.mediaSource = 'soundcloud';
return {
id: `audio-soundcloud-${matches[1]}`,
className:
......@@ -183,6 +227,7 @@ export class MindsRichEmbed {
if ((matches = spotify.exec(url)) !== null) {
if (matches[1]) {
this.mediaSource = 'spotify';
return {
id: `audio-spotify-${matches[1]}`,
className:
......@@ -207,7 +252,7 @@ export class MindsRichEmbed {
if (!id) {
return null;
}
this.mediaSource = 'giphy';
return {
id: `image-giphy-${matches[1]}`,
className:
......@@ -225,7 +270,11 @@ export class MindsRichEmbed {
}
hasInlineContentLoaded() {
return this.embeddedInline && this.inlineEmbed && this.inlineEmbed.html;
return this.featureService.has('media-modal')
? !this.modalRequestSubscribed &&
this.inlineEmbed &&
this.inlineEmbed.html
: this.embeddedInline && this.inlineEmbed && this.inlineEmbed.html;
}
detectChanges() {
......
......@@ -159,10 +159,12 @@
*ngIf="getCurrentUser()"
(click)="toggleTheme()"
>
<i class="material-icons" *ngIf="isDark">brightness_7</i>
<i class="material-icons" *ngIf="!isDark">brightness_2</i>
<span i18n *ngIf="isDark">Light Mode</span>
<span i18n *ngIf="!isDark">Dark Mode</span>
<div>
<i class="material-icons" *ngIf="isDark">brightness_7</i>
<i class="material-icons" *ngIf="!isDark">brightness_2</i>
<span i18n *ngIf="isDark">Light Mode</span>
<span i18n *ngIf="!isDark">Dark Mode</span>
</div>
</li>
</ng-container>
......
......@@ -4,13 +4,6 @@ import { Client } from '../../services/api/client';
import { Session } from '../../services/session';
import { Storage } from '../../services/storage';
import AsyncLock from '../../helpers/async-lock';
import MindsClientHttpAdapter from '../../lib/minds-sync/adapters/MindsClientHttpAdapter.js';
import browserStorageAdapterFactory from '../../helpers/browser-storage-adapter-factory';
import BlockListSync from '../../lib/minds-sync/services/BlockListSync.js';
import AsyncStatus from '../../helpers/async-status';
@Injectable()
export class BlockListService {
blocked: BehaviorSubject<string[]>;
......
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { first, catchError } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
import { first } from 'rxjs/operators';
import { Client } from '../../services/api';
import { BlockListService } from './block-list.service';
import MindsClientHttpAdapter from '../../lib/minds-sync/adapters/MindsClientHttpAdapter.js';
import browserStorageAdapterFactory from '../../helpers/browser-storage-adapter-factory';
import EntitiesSync from '../../lib/minds-sync/services/EntitiesSync.js';
import AsyncStatus from '../../helpers/async-status';
import normalizeUrn from '../../helpers/normalize-urn';
type EntityObservable = BehaviorSubject<Object>;
type EntityObservables = Map<string, EntityObservable>;
......
......@@ -6,40 +6,8 @@ import { Session } from '../../services/session';
import { EntitiesService } from './entities.service';
import { BlockListService } from './block-list.service';
import MindsClientHttpAdapter from '../../lib/minds-sync/adapters/MindsClientHttpAdapter.js';
import browserStorageAdapterFactory from '../../helpers/browser-storage-adapter-factory';
import FeedsSync from '../../lib/minds-sync/services/FeedsSync.js';
import hashCode from '../../helpers/hash-code';
import AsyncStatus from '../../helpers/async-status';
import { BehaviorSubject, Observable, of, forkJoin, combineLatest } from 'rxjs';
import {
take,
switchMap,
map,
tap,
skipWhile,
first,
filter,
} from 'rxjs/operators';
export type FeedsServiceGetParameters = {
endpoint: string;
timebased: boolean;
//
limit: number;
offset?: number;
//
syncPageSize?: number;
forceSync?: boolean;
};
export type FeedsServiceGetResponse = {
entities: any[];
next?: number;
};
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
import { switchMap, map, tap, first } from 'rxjs/operators';
/**
* Enables the grabbing of data through observable feeds.
......@@ -69,6 +37,7 @@ export class FeedsService {
this.pageSize = this.offset.pipe(
map(offset => this.limit.getValue() + offset)
);
this.feed = this.rawFeed.pipe(
tap(feed => {
if (feed.length) this.inProgress.next(true);
......@@ -87,6 +56,7 @@ export class FeedsService {
this.inProgress.next(false);
})
);
this.hasMore = combineLatest(
this.rawFeed,
this.inProgress,
......
......@@ -219,7 +219,12 @@
<span i18n="@@M__COMMON__CONFIRM_18">Click to confirm you are 18+</span>
</span>
</div>
<minds-rich-embed [src]="activity" [maxheight]="480"></minds-rich-embed>
<minds-rich-embed
(mediaModalRequested)="openModal()"
[src]="activity"
[maxheight]="480"
>
</minds-rich-embed>
</div>
<div
......
<div class="m-mediaModal__wrapper">
<div class="m-mediaModal__wrapper" data-cy="data-minds-media-modal">
<div class="m-mediaModal__theater" (click)="clickedModal($event)">
<div
class="m-mediaModal m-mediaModal__clearFix"
......@@ -15,7 +15,10 @@
(touchend)="showOverlaysOnTablet()"
>
<!-- LOADING PANEL -->
<div class="m-mediaModal__loadingPanel" *ngIf="isLoading">
<div
class="m-mediaModal__loadingPanel"
*ngIf="isLoading && contentType !== 'rich-embed'"
>
<div class="mdl-spinner mdl-js-spinner is-active" [mdl]></div>
</div>
......@@ -65,6 +68,15 @@
</m-video>
</div>
<!-- RICH-EMBED -->
<div
class="m-mediaModal__mediaWrapper m-mediaModal__mediaWrapper--richEmbed"
*ngIf="contentType === 'rich-embed'"
>
<minds-rich-embed [src]="entity" [maxheight]="480">
</minds-rich-embed>
</div>
<!-- MEDIA: BLOG -->
<div
class="m-mediaModal__mediaWrapper m-mediaModal__mediaWrapper--blog"
......@@ -83,12 +95,12 @@
<!-- OVERLAY -->
<div
class="m-mediaModal__overlayContainer"
*ngIf="overlayVisible"
*ngIf="overlayVisible && contentType !== 'rich-embed'"
@fastFadeAnimation
>
<div
class="m-mediaModal__overlayTitleWrapper"
*ngIf="this.contentType !== 'blog'"
*ngIf="contentType !== 'blog'"
>
<!-- TITLE -->
<span
......
......@@ -99,6 +99,14 @@ m-overlay-modal {
}
}
.m-mediaModal__mediaWrapper--richEmbed {
width: 100%;
.meta {
display: none;
}
}
.m-mediaModal__mediaWrapper--blog {
line-height: initial;
overflow-y: auto;
......
import {
Component,
HostListener,
Injector,
Input,
OnDestroy,
OnInit,
ViewChild,
SkipSelf,
Injector,
ViewChild,
} from '@angular/core';
import { Location } from '@angular/common';
import { Event, NavigationStart, Router } from '@angular/router';
......@@ -26,6 +26,7 @@ import isMobileOrTablet from '../../../helpers/is-mobile-or-tablet';
import { ActivityService } from '../../../common/services/activity.service';
import { SiteService } from '../../../common/services/site.service';
import { ClientMetaService } from '../../../common/services/client-meta.service';
import { FeaturesService } from '../../../services/features.service';
export type MediaModalParams = {
redirectUrl?: string;
......@@ -119,66 +120,78 @@ export class MediaModalComponent implements OnInit, OnDestroy {
@ViewChild(MindsVideoComponent, { static: false })
videoComponent: MindsVideoComponent;
get videoDirectSrc() {
const sources = [
videoDirectSrc = [];
videoTorrentSrc = [];
constructor(
public session: Session,
public analyticsService: AnalyticsService,
private overlayModal: OverlayModalService,
private router: Router,
private location: Location,
private site: SiteService,
private clientMetaService: ClientMetaService,
private featureService: FeaturesService,
@SkipSelf() injector: Injector
) {
this.clientMetaService
.inherit(injector)
.setSource('single')
.setMedium('modal');
}
updateSources() {
this.videoDirectSrc = [
{
res: '720',
uri:
'api/v1/media/' + this.entity.entity_guid + '/play?s=modal&res=720',
'api/v1/media/' +
this.entity.entity_guid +
'/play/' +
Date.now() +
'?s=modal&res=720',
type: 'video/mp4',
},
{
res: '360',
uri: 'api/v1/media/' + this.entity.entity_guid + '/play?s=modal',
uri:
'api/v1/media/' +
this.entity.entity_guid +
'/play/' +
Date.now() +
'?s=modal',
type: 'video/mp4',
},
];
this.videoTorrentSrc = [
{ res: '720', key: this.entity.entity_guid + '/720.mp4' },
{ res: '360', key: this.entity.entity_guid + '/360.mp4' },
];
if (this.entity.custom_data.full_hd) {
sources.push({
this.videoDirectSrc.unshift({
res: '1080',
uri:
'api/v1/media/' + this.entity.entity_guid + '/play?s=modal&res=1080',
'api/v1/media/' +
this.entity.entity_guid +
'/play/' +
Date.now() +
'?s=modal&res=1080',
type: 'video/mp4',
});
}
return sources;
}
get videoTorrentSrc() {
const sources = [
{ res: '720', key: this.entity.entity_guid + '/720.mp4' },
{ res: '360', key: this.entity.entity_guid + '/360.mp4' },
];
if (this.entity.custom_data.full_hd) {
sources.push({ res: '1080', key: this.entity.entity_guid + '/1080.mp4' });
this.videoTorrentSrc.unshift({
res: '1080',
key: this.entity.entity_guid + '/1080.mp4',
});
}
return sources;
}
constructor(
public session: Session,
public analyticsService: AnalyticsService,
private overlayModal: OverlayModalService,
private router: Router,
private location: Location,
private site: SiteService,
private clientMetaService: ClientMetaService,
@SkipSelf() injector: Injector
) {
this.clientMetaService
.inherit(injector)
.setSource('single')
.setMedium('modal');
}
ngOnInit() {
// Prevent dismissal of modal when it's just been opened
this.isOpenTimeout = setTimeout(() => (this.isOpen = true), 20);
switch (this.entity.type) {
case 'activity':
this.title =
......@@ -190,6 +203,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
? this.entity.thumbnails.xlarge
: null;
this.updateSources();
switch (this.entity.custom_type) {
case 'video':
this.contentType = 'video';
......@@ -200,12 +215,41 @@ export class MediaModalComponent implements OnInit, OnDestroy {
? this.entity.custom_data.dimensions.height
: 720;
this.entity.thumbnail_src = this.entity.custom_data.thumbnail_src;
this.updateSources();
break;
case 'batch':
this.contentType = 'image';
this.entity.width = this.entity.custom_data[0].width;
this.entity.height = this.entity.custom_data[0].height;
break;
default:
if (
this.featureService.has('media-modal') &&
this.entity.perma_url &&
this.entity.title &&
!this.entity.entity_guid
) {
this.contentType = 'rich-embed';
this.entity.width = this.entity.custom_data.dimensions
? this.entity.custom_data.dimensions.width
: 1280;
this.entity.height = this.entity.custom_data.dimensions
? this.entity.custom_data.dimensions.height
: 720;
this.entity.thumbnail_src = this.entity.custom_data.thumbnail_src;
break;
} else {
// Modal not implemented, redirect.
this.router.navigate([
this.entity.route
? `/${this.entity.route}`
: `/blog/view/${this.entity.guid}`,
]);
// Close modal.
this.clickedBackdrop(null);
}
}
break;
case 'object':
switch (this.entity.subtype) {
......@@ -213,6 +257,9 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.contentType = 'video';
this.title = this.entity.title;
this.entity.entity_guid = this.entity.guid;
this.entity.custom_data = {
full_hd: this.entity.flags.full_hd,
};
break;
case 'image':
this.contentType = 'image';
......@@ -243,12 +290,10 @@ export class MediaModalComponent implements OnInit, OnDestroy {
if (this.redirectUrl) {
this.pageUrl = this.redirectUrl;
} else if (this.contentType !== 'blog') {
this.pageUrl = `/media/${this.entity.entity_guid}`;
} else if (this.contentType === 'rich-embed') {
this.pageUrl = `/newsfeed/${this.entity.guid}`;
} else {
this.pageUrl = this.entity.route
? `/${this.entity.route}`
: `/blog/view${this.entity.guid}`;
this.pageUrl = `/media/${this.entity.entity_guid}`;
}
this.boosted = this.entity.boosted || this.entity.p2p_boosted || false;
......@@ -559,7 +604,6 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// Show overlay and video controls
onMouseEnterStage() {
this.overlayVisible = true;
if (this.contentType === 'video') {
// Make sure progress bar seeker is updating when video controls are visible
this.videoComponent.stageHover = true;
......
import { Component, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { timer, Subscription } from 'rxjs';
import { Subscription, timer } from 'rxjs';
import { Client } from '../../../../services/api';
import { Session } from '../../../../services/session';
......@@ -71,8 +71,18 @@ export class MediaTheatreComponent {
@ViewChild(MindsVideoComponent, { static: false })
videoComponent: MindsVideoComponent;
get videoDirectSrc() {
const sources = [
videoDirectSrc = [];
videoTorrentSrc = [];
constructor(
public session: Session,
public client: Client,
public router: Router,
private recommended: RecommendedService
) {}
updateSources() {
this.videoDirectSrc = [
{
res: '720',
uri: 'api/v1/media/' + this.object.guid + '/play?s=modal&res=720',
......@@ -85,40 +95,29 @@ export class MediaTheatreComponent {
},
];
this.videoTorrentSrc = [
{ res: '720', key: this.object.guid + '/720.mp4' },
{ res: '360', key: this.object.guid + '/360.mp4' },
];
if (this.object.flags.full_hd) {
sources.push({
this.videoDirectSrc.unshift({
res: '1080',
uri: 'api/v1/media/' + this.object.guid + '/play?s=modal&res=1080',
type: 'video/mp4',
});
}
return sources;
}
get videoTorrentSrc() {
const sources = [
{ res: '720', key: this.object.guid + '/720.mp4' },
{ res: '360', key: this.object.guid + '/360.mp4' },
];
if (this.object.flags.full_hd) {
sources.push({ res: '1080', key: this.object.guid + '/1080.mp4' });
this.videoTorrentSrc.unshift({
res: '1080',
key: this.object.guid + '/1080.mp4',
});
}
return sources;
}
constructor(
public session: Session,
public client: Client,
public router: Router,
private recommended: RecommendedService
) {}
set _object(value: any) {
if (!value.guid) return;
this.object = value;
this.updateSources();
}
getThumbnail() {
......
......@@ -14,6 +14,11 @@ m-pro--channel {
background-blend-mode: overlay;
background-color: var(--m-pro--more-transparent-background-color) !important;
&.m-pro-channel--plainBackground {
background-blend-mode: initial;
background-color: var(--m-pro--plain-background-color) !important;
}
@media screen and (min-width: ($min-tablet + 1px)) {
m-pro__hamburger-menu {
display: none;
......
......@@ -130,7 +130,7 @@ export class ProChannelComponent implements OnInit, AfterViewInit, OnDestroy {
}
@HostBinding('style.backgroundImage') get backgroundImageCssValue() {
if (!this.channel) {
if (!this.channel || !this.channel.pro_settings.background_image) {
return 'none';
}
......@@ -142,8 +142,16 @@ export class ProChannelComponent implements OnInit, AfterViewInit, OnDestroy {
return '';
}
return `m-theme--wrapper m-theme--wrapper__${this.channel.pro_settings
.scheme || 'light'}`;
const classes = [
'm-theme--wrapper',
`m-theme--wrapper__${this.channel.pro_settings.scheme || 'light'}`,
];
if (!this.channel || !this.channel.pro_settings.background_image) {
classes.push('m-pro-channel--plainBackground');
}
return classes.join(' ');
}
constructor(
......