Merge pull request #7 from NicolasConstant/topic-start-column-handling
Topic start column handling
This commit is contained in:
commit
9dc85568a2
21
LICENSE
21
LICENSE
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Nicolas Constant
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,5 +1,9 @@
|
||||
<div class="toot">
|
||||
<img class="toot__avatar" src="{{ status.account.avatar }}" />
|
||||
<a href class="toot__profile-link"><span class="toot__fullname" innerHTML="{{status.account.display_name}}"></span> @<span id="toot-username" innerHTML="{{status.account.username}}"></span></a>
|
||||
<div class="toot__content" innerHTML="{{status.content}}"></div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<a href class="status__profile-link" title="{{status.account.acct}}">
|
||||
<img class="status__avatar" src="{{ status.account.avatar }}" />
|
||||
<span class="status__fullname" innerHTML="{{status.account.display_name}}"></span>
|
||||
@<span id="status-username">{{status.account.acct}}</span>
|
||||
</a>
|
||||
<div class="status__created-at" title="{{ status.created_at | date: 'full' }}">{{ getCompactRelativeTime(status.created_at) }}</div>
|
||||
<div class="status__content" innerHTML="{{status.content}}"></div>
|
||||
</div>
|
@ -1,47 +1,56 @@
|
||||
.toot {
|
||||
border: solid #06070b;
|
||||
border-width: 0 0 1px 0;
|
||||
@import "variables";
|
||||
.status {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: calc(100%);
|
||||
min-height: 70px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
&__avatar {
|
||||
margin: 10px 0 0 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px; // margin: 10px 0 0 10px;
|
||||
/* margin: 0; */
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
float: left;
|
||||
height: 50px; // float: left;
|
||||
border-radius: 2px;
|
||||
}
|
||||
&__fullname {
|
||||
color: white;
|
||||
display: block;
|
||||
color: $status-primary-color;
|
||||
}
|
||||
&__profile-link {
|
||||
color: #353e64;
|
||||
color: $status-secondary-color;
|
||||
margin: 7px 0 0 70px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
&__content {
|
||||
/*width: calc(100% - 50px);*/
|
||||
margin: 10px 10px 10px 70px;
|
||||
margin: 0px 10px 10px 70px;
|
||||
}
|
||||
&__content p {
|
||||
margin: 0;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
&__created-at {
|
||||
color: $status-secondary-color;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// #toot-avatar img {
|
||||
// width: 50px;
|
||||
// height: 50px;
|
||||
// border-radius: 2px;
|
||||
// margin: 0;
|
||||
// }
|
||||
/* #toot-username {
|
||||
color: grey;
|
||||
} */
|
||||
|
||||
/* ::ng-deep .invisible {
|
||||
display: inline;
|
||||
color: red;
|
||||
} */
|
||||
//Mastodon styling
|
||||
:host ::ng-deep .status__content {
|
||||
color: $status-primary-color;
|
||||
& a,
|
||||
.mention,
|
||||
.ellipsis {
|
||||
color: $status-links-color;
|
||||
}
|
||||
& .invisible {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -1,18 +1,36 @@
|
||||
import { Component, OnInit, Input } from "@angular/core";
|
||||
import { Component, OnInit, Input, Inject, LOCALE_ID } from "@angular/core";
|
||||
import { Status } from "../../../services/models/mastodon.interfaces";
|
||||
import { formatDate } from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: "app-status",
|
||||
templateUrl: "./status.component.html",
|
||||
styleUrls: ["./status.component.scss"]
|
||||
selector: "app-status",
|
||||
templateUrl: "./status.component.html",
|
||||
styleUrls: ["./status.component.scss"]
|
||||
})
|
||||
export class StatusComponent implements OnInit {
|
||||
@Input() status: Status;
|
||||
@Input() status: Status;
|
||||
|
||||
constructor() { }
|
||||
constructor(@Inject(LOCALE_ID) private locale: string) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
getCompactRelativeTime(d: string): string {
|
||||
const date = (new Date(d)).getTime();
|
||||
const now = Date.now();
|
||||
const timeDelta = (now - date) / (1000);
|
||||
|
||||
if (timeDelta < 60) {
|
||||
return `${timeDelta | 0}s`;
|
||||
} else if (timeDelta < 60 * 60) {
|
||||
return `${timeDelta / 60 | 0}m`;
|
||||
} else if (timeDelta < 60 * 60 * 24) {
|
||||
return `${timeDelta / (60 * 60)| 0}h`;
|
||||
} else if (timeDelta < 60 * 60 * 24 * 31) {
|
||||
return `${timeDelta / (60 * 60 * 24) | 0}d`;
|
||||
}
|
||||
|
||||
return formatDate(date, 'MM/dd', this.locale);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<a href title="return to top" (click)="goToTop()"><h1>{{ streamElement.name.toUpperCase() }}</h1></a>
|
||||
</div>
|
||||
<div class="stream-toots flexcroll" #statusstream (scroll)="onScroll()"> <!-- data-simplebar -->
|
||||
<div *ngFor="let status of statuses">
|
||||
<div class="stream-toots__status" *ngFor="let status of statuses">
|
||||
<app-status [status]="status" ></app-status>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,33 +1,32 @@
|
||||
@import "variables";
|
||||
.stream-column {
|
||||
width: $stream-column-width;
|
||||
height: calc(100%);
|
||||
|
||||
background-color: #0f111a;
|
||||
margin: 0 0 0 $stream-column-separator;
|
||||
|
||||
&__stream-header {
|
||||
width: calc(100%);
|
||||
height: 30px;
|
||||
|
||||
background-color: black;
|
||||
|
||||
border-bottom: 1px solid black;
|
||||
|
||||
& h1 {
|
||||
color: whitesmoke;
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 8px 0 0 10px;
|
||||
width: $stream-column-width;
|
||||
height: calc(100%);
|
||||
background-color: #0f111a;
|
||||
margin: 0 0 0 $stream-column-separator;
|
||||
&__stream-header {
|
||||
width: calc(100%);
|
||||
height: 30px;
|
||||
background-color: black;
|
||||
border-bottom: 1px solid black;
|
||||
& h1 {
|
||||
color: whitesmoke;
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 8px 0 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stream-toots {
|
||||
height: calc(100% - 30px);
|
||||
width: 320px;
|
||||
overflow: auto;
|
||||
height: calc(100% - 30px);
|
||||
width: 320px;
|
||||
overflow: auto;
|
||||
&__status:not(:last-child) {
|
||||
border: solid #06070b;
|
||||
border-width: 0 0 1px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flexcroll {
|
||||
|
@ -18,6 +18,8 @@ export class StreamComponent implements OnInit {
|
||||
private websocketStreaming: StreamingWrapper;
|
||||
|
||||
statuses: Status[] = [];
|
||||
private bufferStream: Status[] = [];
|
||||
private bufferWasCleared: boolean;
|
||||
|
||||
@Input()
|
||||
set streamElement(streamElement: StreamElement) {
|
||||
@ -28,7 +30,7 @@ export class StreamComponent implements OnInit {
|
||||
const instance = splitedUserName[1];
|
||||
this.account = this.getRegisteredAccounts().find(x => x.username == user && x.instance == instance);
|
||||
|
||||
this.retrieveToots(); //TODO change this for WebSockets
|
||||
this.retrieveToots();
|
||||
this.launchWebsocket();
|
||||
}
|
||||
|
||||
@ -47,6 +49,10 @@ export class StreamComponent implements OnInit {
|
||||
|
||||
@ViewChild('statusstream') public statustream: ElementRef;
|
||||
goToTop(): boolean {
|
||||
this.loadBuffer();
|
||||
if (this.statuses.length > 40) {
|
||||
this.statuses.length = 40;
|
||||
}
|
||||
const stream = this.statustream.nativeElement as HTMLElement;
|
||||
stream.scrollTo({
|
||||
top: 0,
|
||||
@ -56,25 +62,58 @@ export class StreamComponent implements OnInit {
|
||||
}
|
||||
|
||||
private streamPositionnedAtTop: boolean = true;
|
||||
private streamPositionnedAtBottom: boolean;
|
||||
private isProcessingInfiniteScroll: boolean;
|
||||
|
||||
onScroll() {
|
||||
var element = this.statustream.nativeElement as HTMLElement;
|
||||
const atBottom = element.scrollHeight - element.scrollTop === element.clientHeight;
|
||||
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 500;
|
||||
const atTop = element.scrollTop === 0;
|
||||
|
||||
this.streamPositionnedAtTop = false;
|
||||
this.streamPositionnedAtBottom = false;
|
||||
|
||||
if (atBottom) {
|
||||
console.log('Bottom reached!!');
|
||||
this.streamPositionnedAtBottom = true;
|
||||
if (atBottom && !this.isProcessingInfiniteScroll) {
|
||||
this.scrolledToBottom();
|
||||
} else if (atTop) {
|
||||
console.log('Top reached!!');
|
||||
this.streamPositionnedAtTop = true;
|
||||
this.scrolledToTop();
|
||||
}
|
||||
}
|
||||
|
||||
private scrolledToTop() {
|
||||
this.streamPositionnedAtTop = true;
|
||||
|
||||
this.loadBuffer();
|
||||
}
|
||||
|
||||
private loadBuffer(){
|
||||
if(this.bufferWasCleared) {
|
||||
this.statuses.length = 0;
|
||||
this.bufferWasCleared = false;
|
||||
}
|
||||
|
||||
for (const status of this.bufferStream) {
|
||||
this.statuses.unshift(status);
|
||||
}
|
||||
|
||||
this.bufferStream.length = 0;
|
||||
}
|
||||
|
||||
private scrolledToBottom() {
|
||||
this.isProcessingInfiniteScroll = true;
|
||||
|
||||
const lastStatus = this.statuses[this.statuses.length - 1];
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.id)
|
||||
.then((status: Status[]) => {
|
||||
for (const s of status) {
|
||||
this.statuses.push(s);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isProcessingInfiniteScroll = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getRegisteredAccounts(): AccountInfo[] {
|
||||
var regAccounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
return regAccounts;
|
||||
@ -95,7 +134,11 @@ export class StreamComponent implements OnInit {
|
||||
if (update) {
|
||||
if (update.type === EventEnum.update) {
|
||||
if (!this.statuses.find(x => x.id == update.status.id)) {
|
||||
this.statuses.unshift(update.status);
|
||||
if (this.streamPositionnedAtTop) {
|
||||
this.statuses.unshift(update.status);
|
||||
} else {
|
||||
this.bufferStream.push(update.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,11 +147,16 @@ export class StreamComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private checkAndCleanUpStream(): void {
|
||||
if (this.streamPositionnedAtTop && this.statuses.length > 60) {
|
||||
console.log(`clean up start! ${this.statuses.length}`);
|
||||
this.statuses.length = 40;
|
||||
console.log(`clean up ends! ${this.statuses.length}`);
|
||||
}
|
||||
|
||||
if (this.bufferStream.length > 60) {
|
||||
this.bufferWasCleared = true;
|
||||
this.bufferStream.length = 40;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,11 @@ $color-secondary: #090b10;
|
||||
$default-font-size: 15px;
|
||||
$small-font-size: 12px;
|
||||
|
||||
$status-primary-color: #fff;
|
||||
$status-secondary-color: #353e64;
|
||||
$status-secondary-color: #4e5572;
|
||||
$status-links-color: #d9e1e8;
|
||||
|
||||
|
||||
// Block dispositions
|
||||
$stream-selector-height: 30px;
|
||||
|
@ -28,16 +28,16 @@ html, body {
|
||||
background-color: $color-primary;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
// .invisible {
|
||||
// display: none;
|
||||
// }
|
||||
/* .ellipsis {
|
||||
} */
|
||||
|
||||
#toot-content p {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
// #toot-content p {
|
||||
// margin-bottom: 0 !important;
|
||||
// }
|
||||
|
||||
#toot-content a {
|
||||
color: #bec3d8;
|
||||
}
|
||||
// #toot-content a {
|
||||
// color: #bec3d8;
|
||||
// }
|
Loading…
x
Reference in New Issue
Block a user