Commit 3ed076ba authored by Zéfling's avatar Zéfling 🎨
Browse files

Multi-list on Json

- Multi-list with assets/twitter/json/versions.js
- fix links on Moment
parent 3e56b51f
<header>
<nav>
<ul>
<li>
<a routerLink="/tweets">Tweets</a>
</li>
<li>
<a routerLink="/moments">Moments</a>
</li>
</ul>
</nav>
</header>
<ng-container *ngIf="isInit; else notInit">
<header>
<nav>
<ul>
<li>
<a routerLink="/tweets">Tweets</a>
</li>
<li>
<a routerLink="/moments">Moments</a>
</li>
</ul>
<div *ngIf="multi">
<select (change)="change($event)">
<option *ngFor="let item of multi"
[value]="item.folder">{{item.name}}</option>
</select>
</div>
<router-outlet></router-outlet>
</nav>
</header>
<router-outlet></router-outlet>
</ng-container>
<ng-template #notInit>
<div class="init">
Initialisation...
</div>
</ng-template>
<footer>
<ul>
......
......@@ -7,7 +7,7 @@ body {
footer,
header {
ul {
ul {
list-style: none;
padding: 3px;
margin: 0;
......@@ -21,7 +21,17 @@ header {
}
}
nav {
display: flex;
justify-content: space-between;
}
footer li {
width: 100%;
text-align: center;
}
.init {
text-align: center;
padding: 30px;
}
\ No newline at end of file
import { Component, ViewEncapsulation } from '@angular/core';
import { AppService } from './app.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
......@@ -10,5 +12,25 @@ import { Component, ViewEncapsulation } from '@angular/core';
]
})
export class AppComponent {
version = '0.1.0';
version = '0.2.0';
isInit = false;
get multi() {
return this.appService.versions;
}
constructor(private appService: AppService) {
appService.onData.subscribe(() => {
this.isInit = true;
});
}
change(event: Event) {
this.appService.getVersion(
(event.target as HTMLInputElement).value
);
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { Version } from './version';
@Injectable({
providedIn: 'root',
})
export class AppService {
readonly onData = new Subject<void>();
readonly updateData = new Subject<void>();
isInit = false;
versions: Version[];
selectVersion: Version;
constructor(
private http: HttpClient
) {
this.init();
}
init() {
if (!this.isInit) {
this.http
.get('./assets/twitter/json/versions.js', { responseType: 'text' })
.subscribe(
(data: string) => {
this.versions = JSON.parse(data);
this.getVersion();
},
() => { },
() => {
this.isInit = true;
this.onData.next();
});
}
}
getVersion(id: string = null) {
if (this.versions && this.versions.length > 0) {
this.selectVersion = !id
? this.versions[0]
: this.versions.find((i) => i.folder === id);
this.updateData.next();
} else {
return null;
}
}
getPath() {
return this.versions && this.selectVersion
? `./assets/twitter/json/${this.selectVersion.folder}/`
: `./assets/twitter/`;
}
}
import { Tweet, TweetMedia } from '../tweets/tweet';
const tweetMedia = './assets/twitter/tweet_media/';
export class Format {
static imageUrl(id: string, media: TweetMedia): string {
return `${tweetMedia}${id}-${media.media_url_https.replace(/.*\/media\//, '')}`;
}
static videoUrl(tweet: Tweet, media: TweetMedia): string {
let url: string;
if (tweet.extended_entities.media[0].video_info) {
tweet.extended_entities.media[0].video_info.variants
.sort((var1, var2) => (parseInt(var1.bitrate, 10) || 0) - (parseInt(var2.bitrate, 10) || 0));
const variants = tweet.extended_entities.media[0].video_info.variants;
url = variants[variants.length - 1].url.replace(/.*\/([^\/]*\.mp4).*$/, '$1');
} else {
url = media.media_url_https.replace(/.*\/(tweet_video_thumb|ext_tw_video_thumb.*\/img)\/(.*)\.(png|jpg)/, '$2') + '.mp4';
}
return `${tweetMedia}${tweet.id}-${url}`;
}
}
......@@ -27,6 +27,8 @@ export interface MomentTweetCoreData {
userId: string;
hasTakedown: boolean;
hasMedia: boolean;
//
html_text: string;
}
export interface MomentTweetLangue {
......@@ -100,7 +102,7 @@ export interface MomentTweetHashtags {
export interface MomentTweetInfo {
deviceSource: MomentTweetDeviceSource;
urls: MomentTweetUrl;
urls: MomentTweetUrl[];
coreData: MomentTweetCoreData;
mentions: any[];
id: string;
......@@ -108,7 +110,6 @@ export interface MomentTweetInfo {
media: MomentTweetMedia[];
cashtags: any[];
hashtags: MomentTweetHashtags[];
}
export interface MomentTweet {
......
......@@ -32,6 +32,6 @@
<section *ngFor="let tweet of moment.tweets"
class="tweet">
<div class="tweet-text"
[innerHTML]="tweet.tweet.coreData.text"></div>
[innerHTML]="tweet.tweet.coreData.html_text"></div>
</section>
</div>
\ No newline at end of file
......@@ -77,4 +77,8 @@
border: 1px solid var(--panel-bd-color);
margin: 5px;
padding: 5px;
}
.tweet-text {
white-space: pre;
}
\ No newline at end of file
......@@ -7,6 +7,7 @@ import { Lightbox } from 'ngx-lightbox';
import { Moments, Moment } from './moment';
import { UserService, TypeData } from '../user/user.service';
import { MomentsService } from './moments.service';
import { AppService } from '../app.service';
@Component({
selector: 'moments-list',
......@@ -25,16 +26,24 @@ export class MomentsListComponent implements OnInit, OnDestroy {
constructor(
private userService: UserService,
private momentsService: MomentsService,
private _lightbox: Lightbox
private _lightbox: Lightbox,
private appService: AppService
) {
this.listener.push(this.momentsService.onDataLoaded.subscribe((moments: Moments) => {
this.moments = moments;
this.listener.push(
this.momentsService.onDataLoaded.subscribe((moments: Moments) => {
console.warn(moments);
// select current
this.moments = moments;
// select current
}));
}),
this.appService.updateData.subscribe(() => {
this.ngOnInit();
})
);
}
ngOnInit() {
......
import { Injectable, Component } from '@angular/core';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { Moments } from './moment';
import { Moments, MomentTweetInfo } from './moment';
import { UserService, TypeData } from '../user/user.service';
import { AppService } from '../app.service';
@Injectable({
......@@ -19,10 +20,12 @@ export class MomentsService {
constructor(
private http: HttpClient,
private userService: UserService
private userService: UserService,
private appService: AppService
) {
this.userService.onDataLoaded.subscribe(this.loadData.bind(this));
this.onData.subscribe(this.readData.bind(this));
}
/**
......@@ -32,7 +35,7 @@ export class MomentsService {
if (typeData === TypeData.MOMENTS) {
if (!this.momentsData.init) {
this.http
.get('./assets/twitter/moment.js', { responseType: 'text' })
.get(this.appService.getPath() + 'moment.js', { responseType: 'text' })
.subscribe(
(data: string) => {
try {
......@@ -70,9 +73,30 @@ export class MomentsService {
const moment = data.moments[i];
moment.created_date = new Date(moment.createdAt);
for (const tweet of moment.tweets) {
this.formatTweet(tweet.tweet);
}
}
}
this.onDataLoaded.next(this.momentsData);
}
formatTweet(tweet: MomentTweetInfo) {
let html_text = tweet.coreData.text;
// urls
if (tweet.urls && tweet.urls.length > 0) {
for (const url of tweet.urls) {
html_text = html_text.replace(
url.url,
`<a href="${url.expanded}" title="${url.expanded}">${url.display}</a>`
);
}
}
tweet.coreData.html_text = html_text;
}
}
......@@ -106,6 +106,7 @@ export interface Tweet {
created_date: Date;
user_screen_name: string;
user_name: string;
html_text: string;
}
export class TweetsCalendar {
......
......@@ -43,9 +43,9 @@
<time class="tweet-date">{{tweet.created_date | date:'yyyy-MM-dd HH:mm:ss'}}</time>
</div>
<div class="tweet-text"
[innerHTML]="tweet.full_text"></div>
[innerHTML]="tweet.html_text | safe"></div>
<div class="tweet-media"
*ngIf="testImage(tweet.entities.media) | safe">
*ngIf="testImage(tweet.entities.media)">
<img *ngFor="let media of tweet.entities.media"
[src]="imageUrl(tweet, media)"
[alt]="media.id"
......
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { Lightbox } from 'ngx-lightbox';
import { Tweets, TweetsCalendar, Tweet, TweetMedia } from './tweet';
import { TweetsService } from './tweets.service';
import { Subscription } from 'rxjs';
import { Format } from '../common/format';
import { UserService, TypeData } from '../user/user.service';
import { Tweets, TweetsCalendar, TweetMedia, Tweet } from './tweet';
import { TweetsService } from './tweets.service';
import { AppService } from '../app.service';
@Component({
selector: 'tweets-calendar',
......@@ -24,17 +27,22 @@ export class TweetsCalendarComponent implements OnInit, OnDestroy {
constructor(
private userService: UserService,
private tweetsService: TweetsService,
private lightbox: Lightbox
private lightbox: Lightbox,
private appService: AppService
) {
this.listener.push(this.tweetsService.onDataLoaded.subscribe((tweets: Tweets) => {
this.tweets = tweets;
// select current month
const date = new Date();
const month = this.tweets.calendar[date.getFullYear()][date.getMonth()];
this.show(month);
}));
this.listener.push(
this.tweetsService.onDataLoaded.subscribe((tweets: Tweets) => {
this.tweets = tweets;
// select current month
const date = tweetsService.getCurrentDate();
const month = this.tweets.calendar[date.getFullYear()][date.getMonth()];
this.show(month);
}),
this.appService.updateData.subscribe(() => {
this.ngOnInit();
})
);
}
ngOnInit() {
......@@ -58,7 +66,7 @@ export class TweetsCalendarComponent implements OnInit, OnDestroy {
for (const tweet of month.tweets) {
if (this.testImage(tweet.entities.media)) {
for (const media of tweet.entities.media) {
const imageUrl = this.imageUrl(tweet, media);
const imageUrl = Format.imageUrl(tweet.id, media);
media.index = this.album.push({
src: imageUrl,
caption: '',
......@@ -85,23 +93,11 @@ export class TweetsCalendarComponent implements OnInit, OnDestroy {
}
imageUrl(tweet: Tweet, media: TweetMedia): string {
return './assets/twitter/tweet_media/' + tweet.id + '-'
+ media.media_url_https.replace(/.*\/media\//, '');
return Format.imageUrl(tweet.id, media);
}
videoUrl(tweet: Tweet, media: TweetMedia): string {
let url: string;
if (tweet.extended_entities.media[0].video_info) {
tweet.extended_entities.media[0].video_info.variants
.sort((var1, var2) => (parseInt(var1.bitrate, 10) || 0) - (parseInt(var2.bitrate, 10) || 0));
const variants = tweet.extended_entities.media[0].video_info.variants;
url = variants[variants.length - 1].url.replace(/.*\/([^\/]*\.mp4).*$/, '$1');
} else {
url = media.media_url_https.replace(/.*\/(tweet_video_thumb|ext_tw_video_thumb.*\/img)\/(.*)\.(png|jpg)/, '$2') + '.mp4';
}
return './assets/twitter/tweet_media/' + tweet.id + '-' + url;
return Format.videoUrl(tweet, media);
}
testImage(medias: TweetMedia[]): boolean {
......@@ -112,6 +108,7 @@ export class TweetsCalendarComponent implements OnInit, OnDestroy {
return medias && medias[0].media_url_https.match(/.*\/(tweet_video_thumb|ext_tw_video_thumb)\/.*/) !== null;
}
private unselectCurrentMouth() {
if (this.month) {
this.month.selected = false;
......
import { Injectable, Component } from '@angular/core';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Subject } from 'rxjs';
import { Tweets, TweetsCalendar } from './tweet';
import { Tweets, TweetsCalendar, Tweet } from './tweet';
import { UserService, TypeData } from '../user/user.service';
import { AppService } from '../app.service';
@Injectable({
providedIn: 'root',
......@@ -19,41 +19,52 @@ export class TweetsService {
constructor(
private http: HttpClient,
private userService: UserService
private userService: UserService,
private appService: AppService
) {
this.userService.onDataLoaded.subscribe(this.loadData.bind(this));
this.onData.subscribe(this.readData.bind(this));
this.appService.updateData.subscribe(() => {
this.tweetsData.init = false;
});
}
/**
* read data
* load data
*/
private loadData(typeData: TypeData) {
if (typeData === TypeData.TWEETS) {
if (!this.tweetsData.init) {
this.http
.get('./assets/twitter/tweet.js', { responseType: 'text' })
.subscribe(
(data: string) => {
try {
this.tweetsData.tweets = JSON.parse(data.replace(/^win[^\s]* = /, ''));
this.tweetsData.init = true;
this.onData.next();
} catch (e) {
this.tweetsData.error = true;
this.tweetsData.errorMessage = 'data not readable';
}
},
() => {
this.tweetsData.error = true;
this.tweetsData.errorMessage = 'file not exit';
});
this.getDate();
} else {
this.onDataLoaded.next(this.tweetsData);
}
}
}
/**
* read data
*/
private getDate() {
this.http
.get(this.appService.getPath() + 'tweet.js', { responseType: 'text' })
.subscribe(
(data: string) => {
try {
this.tweetsData.tweets = JSON.parse(data.replace(/^win[^\s]* = /, ''));
this.tweetsData.init = true;
this.onData.next();
} catch (e) {
this.tweetsData.error = true;
this.tweetsData.errorMessage = 'data not readable';
}
},
() => {
this.tweetsData.error = true;
this.tweetsData.errorMessage = 'file not exit';
});
}
/**
* read and format data
*/
......@@ -64,55 +75,7 @@ export class TweetsService {
if (data.total > 0) {
for (const tweet of data.tweets) {
// add date object on all tweet
tweet.created_date = new Date(tweet.created_at);
// retweet
const retweetUser = tweet.full_text.match(/^RT @([^:]+):\s*/);
if (retweetUser && tweet.entities.user_mentions) {
const user = tweet.entities.user_mentions.find(u => u.screen_name === retweetUser[1]);
if (user) {
tweet.user_name = user.name;
tweet.user_screen_name = user.screen_name;
tweet.full_text = tweet.full_text.replace(/^RT @[^:]+:\s*/, '');
}
}
// user
if (!tweet.user_name) {
tweet.user_name = this.userService.accountData.accountDisplayName;
tweet.user_screen_name = this.userService.accountData.username;
}
// user mentions
if (tweet.entities.user_mentions && tweet.entities.user_mentions.length > 0) {
for (const mention of tweet.entities.user_mentions) {
tweet.full_text = tweet.full_text.replace(
`@${mention.screen_name}`,
`<a href="https://twitter.com/${mention.screen_name}" title="${mention.name}">@${mention.screen_name}</a>`
);
}
}
// hashtags
if (tweet.entities.hashtags && tweet.entities.hashtags.length > 0) {
for (const hashtag of tweet.entities.hashtags) {
tweet.full_text = tweet.full_text.replace(
`#${hashtag.text}`,
`<a href="https://twitter.com/hashtag/${hashtag.text}?src=hash" title="#${hashtag.text}">#${hashtag.text}</a>`
);
}
}
// urls
if (tweet.entities.urls && tweet.entities.urls.length > 0) {
for (const url of tweet.entities.urls) {
tweet.full_text = tweet.full_text.replace(
url.url,
`<a href="${url.expanded_url}" title="${url.expanded_url}">${url.display_url}</a>`
);
}
}
<