Commit 94ed6cc6 authored by Mark Harding's avatar Mark Harding

(refactor): re-implement boost and block

1 merge request!373Refactor/es feeds
Pipeline #67936714 failed with stage
in 8 minutes and 11 seconds
......@@ -96,10 +96,6 @@ export class Minds {
this.webtorrent.setUp();
this.themeService.setUp();
if (this.session.isLoggedIn()) {
this.blockListService.sync();
}
}
ngOnDestroy() {
......
......@@ -94,6 +94,7 @@ import { UserMenuComponent } from "./layout/v2-topbar/user-menu.component";
import { FeaturedContentComponent } from "./components/featured-content/featured-content.component";
import { FeaturedContentService } from "./components/featured-content/featured-content.service";
import { BoostedContentService } from "./services/boosted-content.service";
import { FeedsService } from './services/feeds.service';
import { EntitiesService } from "./services/entities.service";
import { BlockListService } from "./services/block-list.service";
import { SettingsService } from "../modules/settings/settings.service";
......@@ -310,7 +311,7 @@ import { HorizontalInfiniteScroll } from "./components/infinite-scroll/horizonta
{
provide: FeaturedContentService,
useFactory: boostedContentService => new FeaturedContentService(boostedContentService),
deps: [ BoostedContentService ],
deps: [ FeedsService ],
}
],
entryComponents: [
......
import { Injectable } from "@angular/core";
import { BoostedContentService } from "../../services/boosted-content.service";
import { filter, first, map, switchMap } from 'rxjs/operators';
import { FeedsService } from "../../services/feeds.service";
@Injectable()
export class FeaturedContentService {
offset: number = -1;
constructor(
protected boostedContentService: BoostedContentService,
protected feedsService: FeedsService,
) {
this.feedsService
.setLimit(50)
.setOffset(0)
.setEndpoint('api/v2/boost/feed')
.fetch();
}
async fetch() {
return await this.boostedContentService.fetch();
return await this.feedsService.feed
.pipe(
filter(feed => feed.length > 0),
first(),
map(feed => feed[this.offset++]),
switchMap(async entity => {
if (!entity)
return false;
return await entity.pipe(first()).toPromise();
}),
).toPromise();
}
}
import { Injectable } from "@angular/core";
import { BehaviorSubject } from 'rxjs';
import { Client } from "../../services/api/client";
import { Session } from "../../services/session";
import { Storage } from '../../services/storage';
import AsyncLock from "../../helpers/async-lock";
......@@ -12,88 +14,60 @@ import AsyncStatus from "../../helpers/async-status";
@Injectable()
export class BlockListService {
protected blockListSync: BlockListSync;
protected syncLock = new AsyncLock();
protected status = new AsyncStatus();
blocked: BehaviorSubject<string[]>;
constructor(
protected client: Client,
protected session: Session,
protected storage: Storage
) {
this.setUp();
this.blocked = new BehaviorSubject(JSON.parse(this.storage.get('blocked')));
this.fetch();
}
async setUp() {
this.blockListSync = new BlockListSync(
new MindsClientHttpAdapter(this.client),
await browserStorageAdapterFactory('minds-block-190314'),
);
this.blockListSync.setUp();
//
fetch() {
this.client.get('api/v1/block', { sync: 1, limit: 10000 })
.then((response: any) => {
if (response.guids !== this.blocked.getValue())
this.blocked.next(response.guids); // re-emit as we have a change
this.status.done();
// Prune on session changes
this.session.isLoggedIn((is: boolean) => {
if (is) {
this.sync();
} else {
this.prune();
}
});
this.storage.set('blocked', JSON.stringify(response.guids)); // save to storage
});
return this;
}
async sync() {
await this.status.untilReady();
if (this.syncLock.isLocked()) {
return false;
}
this.syncLock.lock();
this.blockListSync.sync();
this.syncLock.unlock();
}
async prune() {
await this.status.untilReady();
if (this.syncLock.isLocked()) {
return false;
}
}
this.syncLock.lock();
this.blockListSync.prune();
this.syncLock.unlock();
async get() {
}
async getList() {
await this.status.untilReady();
await this.syncLock.untilUnlocked();
return await this.blockListSync.getList();
return this.blocked.getValue();
}
async add(guid: string) {
await this.status.untilReady();
await this.syncLock.untilUnlocked();
return await this.blockListSync.add(guid);
const guids = this.blocked.getValue();
if (guids.indexOf(guid) < 0)
this.blocked.next([...guids, ...[ guid ]]);
this.storage.set('blocked', JSON.stringify(this.blocked.getValue()));
}
async remove(guid: string) {
await this.status.untilReady();
await this.syncLock.untilUnlocked();
const guids = this.blocked.getValue();
const index = guids.indexOf(guid);
if (index > -1) {
guids.splice(index, 1);
}
return await this.blockListSync.remove(guid);
this.blocked.next(guids);
this.storage.set('blocked', JSON.stringify(this.blocked.getValue()));
}
static _(client: Client, session: Session) {
return new BlockListService(client, session);
static _(client: Client, session: Session, storage: Storage) {
return new BlockListService(client, session, storage);
}
}
......@@ -16,10 +16,6 @@ import AsyncStatus from "../../helpers/async-status";
@Injectable()
export class BoostedContentService {
protected boostedContentSync: BoostedContentSync;
protected status = new AsyncStatus();
constructor(
protected client: Client,
protected session: Session,
......@@ -31,59 +27,27 @@ export class BoostedContentService {
}
async setUp() {
this.boostedContentSync = new BoostedContentSync(
new MindsClientHttpAdapter(this.client),
await browserStorageAdapterFactory('minds-boosted-content-190314'),
5 * 60, // Stale after 5 minutes
15 * 60, // Cooldown of 15 minutes
500,
);
this.boostedContentSync.setResolvers({
currentUser: () => this.session.getLoggedInUser() && this.session.getLoggedInUser().guid,
blockedUserGuids: async () => await this.blockListService.getList(),
fetchEntities: async guids => await this.entitiesService.fetch(guids),
});
//
this.boostedContentSync.setUp();
//
this.status.done();
// User session / rating handlers
if (this.session.isLoggedIn()) {
this.boostedContentSync.setRating(this.session.getLoggedInUser().boost_rating || null);
// this.boostedContentSync.setRating(this.session.getLoggedInUser().boost_rating || null);
}
this.session.isLoggedIn((is: boolean) => {
if (is) {
this.boostedContentSync.setRating(this.session.getLoggedInUser().boost_rating || null);
} else {
this.boostedContentSync.destroy();
// this.boostedContentSync.setRating(this.session.getLoggedInUser().boost_rating || null);
}
});
// Garbage collection
this.boostedContentSync.gc();
setTimeout(() => this.boostedContentSync.gc(), 5 * 60 * 1000); // Every 5 minutes
// Rating changes hook
this.settingsService.ratingChanged.subscribe(rating => this.boostedContentSync.changeRating(rating));
//this.settingsService.ratingChanged.subscribe(rating => this.boostedContentSync.changeRating(rating));
}
async get(opts = {}) {
await this.status.untilReady();
return await this.boostedContentSync.get(opts);
setEndpoint(endpoint: string) {
}
async fetch(opts = {}) {
await this.status.untilReady();
return await this.boostedContentSync.fetch(opts);
fetch(opts = {}): BoostedContentService {
return this;
}
}
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable } 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";
......@@ -7,44 +10,61 @@ import EntitiesSync from '../../lib/minds-sync/services/EntitiesSync.js';
import AsyncStatus from "../../helpers/async-status";
import normalizeUrn from "../../helpers/normalize-urn";
@Injectable()
export class EntitiesService {
type EntityObservable = BehaviorSubject<Object>;
protected entitiesSync: EntitiesSync;
interface EntityObservables {
[key: string]: EntityObservable
}
protected status = new AsyncStatus();
@Injectable()
export class EntitiesService {
entities: Object = {};
entities: EntityObservables = {};
constructor(
protected client: Client
protected client: Client,
protected blockListService: BlockListService,
) {
}
async getFromFeed(feed): Promise<Object[]> {
async getFromFeed(feed): Promise<EntityObservable[]> {
if (!feed || !feed.length) {
return [];
}
const blockedGuids = await this.blockListService.blocked.pipe(first()).toPromise();
const urnsToFetch = [];
const urnsToResync = [];
const entities = [];
for (const feedItem of feed) {
if (feedItem.entity) {
this.entities[feedItem.urn] = feedItem.entity;
this.addEntity(feedItem.entity);
}
if (!this.entities[feedItem.urn]) {
urnsToFetch.push(feedItem.urn);
}
if (this.entities[feedItem.urn] && !feedItem.entity) {
urnsToResync.push(feedItem.urn);
}
}
// Fetch entities we don't have
if (urnsToFetch.length) {
await this.fetch(urnsToFetch);
}
// Fetch entities, asynchronously, with no need to wait
if (urnsToResync.length) {
this.fetch(urnsToResync);
}
for (const feedItem of feed) {
entities.push(this.entities[feedItem.urn]);
if (blockedGuids.indexOf(feedItem.owner_guid) < 0)
entities.push(this.entities[feedItem.urn]);
}
return entities;
......@@ -55,29 +75,70 @@ export class EntitiesService {
* @param urn string
* @return Object
*/
async single(urn: string): Promise<Object | false> {
single(urn: string): EntityObservable {
if (urn.indexOf('urn:') < 0) { // not a urn, so treat as a guid
urn = `urn:activity:${urn}`; // and assume activity
}
if (!this.entities[urn]) {
await this.fetch([ urn ]);
}
this.entities[urn] = new BehaviorSubject(null);
this.fetch([ urn ]); // Update in the background
return this.entities[urn];
}
/**
* Fetch entities
* @param urns string[]
* @return []
*/
async fetch(urns: string[]): Promise<Array<Object>> {
const response: any = await this.client.get('api/v2/entities/', { urns });
try {
const response: any = await this.client.get('api/v2/entities/', { urns });
for (const entity of response.entities) {
this.entities[entity.urn] = entity;
if (!response.entities.length) {
for (const urn of urns) {
this.addNotFoundEntity(urn);
}
}
for (const entity of response.entities) {
this.addEntity(entity);
}
return response;
} catch (err) {
// TODO: find a good way of sending server errors to subscribers
}
}
return response.entities;
/**
* Add or resync an entity
* @param entity
* @return void
*/
addEntity(entity): void {
if (this.entities[entity.urn]) {
this.entities[entity.urn].next(entity);
} else {
this.entities[entity.urn] = new BehaviorSubject(entity);
}
}
/**
* Register a urn as not found
* @param urn string
* @return void
*/
addNotFoundEntity(urn): void {
if (!this.entities[urn]) {
this.entities[urn] = new BehaviorSubject(null);
}
this.entities[urn].error("Not found");
}
static _(client: Client) {
return new EntitiesService(client);
static _(client: Client, blockListService: BlockListService) {
return new EntitiesService(client, blockListService);
}
}
......@@ -13,7 +13,7 @@ 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 } from "rxjs/operators";
import { take, switchMap, map, tap, skipWhile, first, filter } from "rxjs/operators";
export type FeedsServiceGetParameters = {
endpoint: string;
......@@ -36,17 +36,14 @@ export type FeedsServiceGetResponse = {
@Injectable()
export class FeedsService {
protected feedsSync: FeedsSync;
protected status = new AsyncStatus();
limit: BehaviorSubject<number> = new BehaviorSubject(12);
offset: BehaviorSubject<number> = new BehaviorSubject(0);
pageSize: Observable<number>;
endpoint: string = '';
params: any = { sync: 1 };
rawFeed: BehaviorSubject<Object[]> = new BehaviorSubject([]);
feed: Observable<Object[]>;
feed: Observable<BehaviorSubject<Object>[]>;
inProgress: BehaviorSubject<boolean> = new BehaviorSubject(true);
hasMore: Observable<boolean>;
......@@ -56,26 +53,29 @@ export class FeedsService {
protected entitiesService: EntitiesService,
protected blockListService: BlockListService,
) {
this.pageSize = this.offset.pipe(
map(offset => this.limit.getValue() + offset)
);
this.feed = this.rawFeed.pipe(
tap(() => {
this.inProgress.next(true);
tap(feed => {
if (feed.length)
this.inProgress.next(true);
}),
switchMap(async feed => {
return feed.slice(0, await this.pageSize.pipe(first()).toPromise())
}),
map(feed => feed.slice(0, this.limit.getValue() + this.offset.getValue())),
switchMap(feed => this.entitiesService.getFromFeed(feed)),
tap(() => {
if (this.offset.getValue() > 0) {
tap(feed => {
if (feed.length) // We should have skipped but..
this.inProgress.next(false);
}
}),
);
this.hasMore = combineLatest(this.rawFeed, this.inProgress, this.limit, this.offset).pipe(
this.hasMore = combineLatest(this.rawFeed, this.inProgress, this.offset).pipe(
map(values => {
const feed = values[0];
const inProgress = values[1];
const limit = values[2];
const offset = values[3];
return inProgress
? true : (limit + offset) <= feed.length;
const offset = values[2];
return inProgress || feed.length > offset;
}),
);
}
......@@ -107,6 +107,7 @@ export class FeedsService {
this.inProgress.next(true);
this.client.get(this.endpoint, {...this.params, ...{ limit: 150 }}) // Over 12 scrolls
.then((response: any) => {
this.inProgress.next(false);
this.rawFeed.next(response.entities);
})
.catch(err => {
......@@ -115,14 +116,15 @@ export class FeedsService {
}
loadMore(): FeedsService {
this.setOffset(this.limit.getValue() + this.offset.getValue());
this.rawFeed.next(this.rawFeed.getValue());
if (!this.inProgress.getValue()) {
this.setOffset(this.limit.getValue() + this.offset.getValue());
this.rawFeed.next(this.rawFeed.getValue());
}
return this;
}
clear(): FeedsService {
this.offset.next(0);
this.inProgress.next(true);
this.rawFeed.next([]);
return this;
}
......@@ -132,8 +134,6 @@ export class FeedsService {
}
async destroy() {
await this.status.untilReady();
return await this.feedsSync.destroy();
}
static _(
......
......@@ -27,7 +27,7 @@
<ng-template #entityListView>
<m-newsfeed__entity
*ngFor="let entity of (feedsService.feed | async); let i = index"
[entity]="entity"
[entity]="entity | async"
[slot]="i + 1"
></m-newsfeed__entity>
</ng-template>
......
......@@ -68,7 +68,7 @@ export class ChannelSortedComponent implements OnInit {
@ViewChild('poster') protected poster: PosterComponent;
constructor(
protected feedsService: FeedsService,
public feedsService: FeedsService,
protected service: SortedService,
protected session: Session,
protected clientMetaService: ClientMetaService,
......@@ -111,8 +111,8 @@ export class ChannelSortedComponent implements OnInit {
this.detectChanges();
}
loadMore() {
this.feedsService.setOffset(this.feedsService.offset.getValue() + 12);
loadNext() {
this.feedsService.loadMore();
}
setFilter(type: string) {
......
......@@ -38,7 +38,7 @@
<minds-activity
*ngFor="let entity of (feedsService.feed | async); let i = index"
class="mdl-card item"
[object]="entity"
[object]="entity | async"
[canDelete]="group['is:owner'] || group['is:moderator']"
(delete)="delete(entity)"
[slot]="i + 1"
......
......@@ -65,7 +65,7 @@ export class GroupProfileFeedSortedComponent {
constructor(
protected service: GroupsService,
protected feedsService: FeedsService,
public feedsService: FeedsService,
protected sortedService: SortedService,
protected session: Session,
protected router: Router,
......
......@@ -23,9 +23,9 @@
<ng-container *ngIf="!disabled">
<minds-activity
*ngFor="let boost of boosts; let i = index"
*ngFor="let boost of boosts; let i = index"
[object]="boost"
[boostToggle]="boost.boostToggle"
[boostToggle]="true"
[class]="'mdl-card m-border item m-boost-rotator-item m-boost-rotator-item-' + i"
visible="true"
[hidden]="i != currentPosition"
......
import { ChangeDetectorRef, Component, ElementRef, Injector, QueryList, SkipSelf, ViewChildren } from '@angular/core';
import { first } from 'rxjs/operators';
import { ScrollService } from '../../../services/ux/scroll';
import { Client } from '../../../services/api';
......@@ -12,6 +13,7 @@ import { NewsfeedBoostService } from '../newsfeed-boost.service';
import { SettingsService } from '../../settings/settings.service';
import { FeaturesService } from "../../../services/features.service";
import { BoostedContentService } from "../../../common/services/boosted-content.service";
import { FeedsService } from "../../../common/services/feeds.service";
import { ClientMetaService } from "../../../common/services/client-meta.service";
@Component({
......@@ -24,7 +26,10 @@ import { ClientMetaService } from "../../../common/services/client-meta.service"
'(mouseout)': 'mouseOut()'
},
inputs: ['interval', 'channel'],
providers: [ ClientMetaService ],
providers: [
ClientMetaService,
FeedsService,
],
templateUrl: 'boost-rotator.component.html',
})
......@@ -66,7 +71,7 @@ export class NewsfeedBoostRotatorComponent {
public service: NewsfeedBoostService,
private cd: ChangeDetectorRef,
protected featuresService: FeaturesService,
protected boostedContentService: BoostedContentService,
public feedsService: FeedsService,
protected clientMetaService: ClientMetaService,
@SkipSelf() injector: Injector,
) {
......@@ -91,43 +96,32 @@ export class NewsfeedBoostRotatorComponent {
this.scroll_listener = this.scroll.listenForView().subscribe(() => this.isVisible());
this.paused = this.service.isBoostPaused();
}
async load() {
if (this.featuresService.has('es-feeds')) {
return await this.loadFromService();
} else {
return await this.loadLegacy();
}
this.feedsService.feed.subscribe(async boosts => {
if (!boosts.length)
return;
for (const boost of boosts) {
if (boost)
this.boosts.push(await boost.pipe(first()).toPromise());
}
if (this.currentPosition === 0) {
this.recordImpression(this.currentPosition, true);
}
});
}
async loadFromService() {
load() {
try {
const boosts = await this.boostedContentService.get({
limit: 10,
offset: 8,
exclude: this.boosts.map(boost => boost.urn),
passive: true,
});
if (!boosts || !boosts.length) {
throw new Error(''); // Legacy behavior
}
this.boosts.push(...boosts);
if (this.boosts.length >= 40) {
this.boosts.splice(0, 20);
this.currentPosition = 0;
}
if (!this.running) {
if (this.currentPosition === 0) {
this.recordImpression(this.currentPosition, true);
}
this.start();
this.isVisible();
}
this.feedsService
.setEndpoint('api/v2/boost/feed')
.setParams({
rating: this.rating,
})
.setLimit(10)
.setOffset(0)
.fetch();
} catch (e) {
if (e && e.message) {
console.warn(e);
......@@ -139,61 +133,7 @@ export class NewsfeedBoostRotatorComponent {
this.inProgress = false;
return true;
}
/**
* Load newsfeed
*/
loadLegacy() {
return new Promise((resolve, reject) => {
if (this.inProgress) {
return reject(false);
}
this.inProgress = true;
if (this.storage.get('boost:offset:rotator')) {
this.offset = this.storage.get('boost:offset:rotator');
}
let show = 'all';
if (!this.channel || !this.channel.merchant) {
show = 'points';
}
this.client.get('api/v1/boost/fetch/newsfeed', {
limit: 10,
rating: this.rating,
offset: this.offset,
show: show
})
.then((response: any) => {
if (!response.boosts) {
this.inProgress = false;
return reject(false);
}
this.boosts = this.boosts.concat(response.boosts);
if (this.boosts.length >= 40) {
this.boosts.splice(0, 20);
this.currentPosition = 0;
}
if (!this.running) {
if (this.currentPosition === 0) {
this.recordImpression(this.currentPosition, true);
}
this.start();
this.isVisible();
}
this.offset = response['load-next'];
this.storage.set('boost:offset:rotator', this.offset);
this.inProgress = false;
return resolve(true);
})
.catch((e) => {
this.inProgress = false;
return reject();
});
});
}
onExplicitChanged(value: boolean) {
this.load();
}
......@@ -292,11 +232,11 @@ export class NewsfeedBoostRotatorComponent {
}
async next() {
this.activities.toArray()[this.currentPosition].hide();
//this.activities.toArray()[this.currentPosition].hide();
if (this.currentPosition + 1 > this.boosts.length - 1) {
//this.currentPosition = 0;
try {
await this.load();
this.feedsService.loadMore();
this.currentPosition++;
} catch(e) {
this.currentPosition = 0;
......
......@@ -30,7 +30,7 @@
</ng-container>
<m-newsfeed__entity
[entity]="entity"
[entity]="entity | async"
[slot]="i + 1"
></m-newsfeed__entity>
</ng-container>
......
......@@ -66,7 +66,7 @@ export class NewsfeedSortedComponent implements OnInit, OnDestroy {
protected newsfeedService: NewsfeedService,
protected topbarHashtagsService: TopbarHashtagsService,
protected newsfeedHashtagSelectorService: NewsfeedHashtagSelectorService,
protected feedsService: FeedsService,
public feedsService: FeedsService,
protected featuresService: FeaturesService,
protected clientMetaService: ClientMetaService,
@SkipSelf() injector: Injector,
......
......@@ -18,7 +18,7 @@
<minds-activity
class="mdl-card m-border item"
[object]="activity"
[object]="activity | async"
[boostToggle]="activity.boostToggle"
(delete)="delete(activity)"
[showRatingToggle]="true"
......
......@@ -65,7 +65,7 @@ export class NewsfeedSubscribedComponent {
private storage: Storage,
private context: ContextService,
protected featuresService: FeaturesService,
protected feedsService: FeedsService,
public feedsService: FeedsService,
protected newsfeedService: NewsfeedService,
protected clientMetaService: ClientMetaService,
@SkipSelf() injector: Injector,
......
......@@ -2,8 +2,9 @@
[class.m-border]="entities && entities.length"
[class.m-newsfeed__tiles--has-elements]="entities && entities.length"
>
<ng-container *ngFor="let entity$ of entities">
<a
*ngFor="let entity of entities"
*ngIf="(entity$ | async) as entity"
class="m-newsfeed-tiles__Tile"
[ngClass]="{ 'm-newsfeed-tiles__Tile--is-mature': attachment.shouldBeBlurred(entity) }"
[routerLink]="['/newsfeed', entity.guid]"
......@@ -12,4 +13,5 @@
<i *ngIf="attachment.shouldBeBlurred(entity)" class="material-icons mature-icon">explicit</i>
<i *ngIf="isUnlisted(entity)" class="material-icons unlisted-icon">visibility_off</i>
</a>
</ng-container>
</div>
import { Component, Injector, SkipSelf } from '@angular/core';
import { Component, Injector, SkipSelf, EventEmitter } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { Session } from '../../../services/session';
import { ContextService } from '../../../services/context.service';
......@@ -74,7 +75,11 @@ export class NewsfeedSingleComponent {
this.loadFromFeedsService(guid) :
this.loadLegacy(guid);
fetchSingleGuid.then((activity: any) => {
fetchSingleGuid.subscribe((activity: any) => {
if (activity === null) {
return; // Not yet loaded
}
this.activity = activity;
switch (this.activity.subtype) {
......@@ -104,30 +109,30 @@ export class NewsfeedSingleComponent {
} else {
this.context.reset();
}
})
.catch(e => {
}, err => {
this.inProgress = false;
if (e.status === 0) {
if (err.status === 0) {
this.error = 'Sorry, there was a timeout error.';
} else {
this.error = 'Sorry, we couldn\'t load the activity';
}
});
});
}
async loadFromFeedsService(guid: string) {
const activity = await this.entitiesService.single(guid);
loadFromFeedsService(guid: string) {
return this.entitiesService.single(guid);
}
if (!activity) {
throw new Error('Activity not found');
}
loadLegacy(guid: string) {
const fakeEmitter = new EventEmitter();
return activity;
}
this.client.get('api/v1/newsfeed/single/' + guid, {}, { cache: true })
.then((response: any) => {
fakeEmitter.next(response.activity);
});
async loadLegacy(guid: string) {
return (<any>await this.client.get('api/v1/newsfeed/single/' + guid, {}, { cache: true })).activity;
return fakeEmitter;
}
delete(activity) {
......
......@@ -2,7 +2,7 @@
<h4 i18n>Blocked Channels</h4>
<div class="m-settingsBlockedChannels__List">
<div *ngFor="let channel of channels" class="m-settingsBlockedChannels__Channel m-border">
<div *ngFor="let channel of channels | async" class="m-settingsBlockedChannels__Channel m-border">
<div class="m-settingsBlockedChannelsChannel__Avatar">
<a [routerLink]="['/', channel.username]">
<img [src]="getChannelIcon(channel)" />
......@@ -33,7 +33,7 @@
<infinite-scroll
distance="25%"
(load)="load()"
(load)="loadMore()"
[moreData]="moreData"
[inProgress]="inProgress">
</infinite-scroll>
......
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core";
import { tap, filter, switchMap } from 'rxjs/operators';
import { BlockListService } from "../../../common/services/block-list.service";
import { EntitiesService } from "../../../common/services/entities.service";
import { Client } from "../../../services/api/client";
......@@ -11,7 +12,7 @@ import { Client } from "../../../services/api/client";
export class SettingsBlockedChannelsComponent implements OnInit {
blockedGuids: any[] = [];
channels: any[] = [];
channels;
offset: number = 0;
......@@ -30,46 +31,30 @@ export class SettingsBlockedChannelsComponent implements OnInit {
ngOnInit() {
this.load(true);
this.channels = this.blockListService.blocked.pipe(
tap(() => {
this.inProgress = true;
this.moreData = false; // Support pagination in the future
}),
filter(list => list.length > 0),
switchMap(async guids => {
const response: any = await this.entitiesService.fetch(guids);
return response.entities;
}),
tap((blocked) => {
this.inProgress = false;
})
);
}
async load(refresh: boolean = false) {
const limit = 24;
if (!refresh && this.inProgress) {
return false;
}
try {
this.inProgress = true;
if (refresh) {
this.blockedGuids = [];
this.channels = [];
this.offset = 0;
this.moreData = true;
}
if (!this.offset) {
this.blockedGuids = (await this.blockListService.getList()) || [];
}
const next = this.offset + limit;
const guids = this.blockedGuids.slice(this.offset, next);
const channels = (await this.entitiesService.fetch(guids)) || [];
if (!channels.length) {
this.moreData = false;
}
this.channels.push(...channels);
this.offset = next;
} catch (e) {
this.moreData = false;
}
if (this.inProgress)
return;
this.blockListService.fetch(); // Get latest
}
this.inProgress = false;
this.detectChanges();
loadMore() {
// Implement soon
}
getChannelIcon(channel) {
......
......@@ -201,12 +201,12 @@ export const MINDS_PROVIDERS : any[] = [
{
provide: BlockListService,
useFactory: BlockListService._,
deps: [ Client, Session ],
deps: [ Client, Session, Storage ],
},
{
provide: EntitiesService,
useFactory: EntitiesService._,
deps: [ Client ],
deps: [ Client, BlockListService ],
},
{
provide: FeedsService,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment