diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 53f78a19..c3227cbe 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -32,6 +32,7 @@ import { AddNewAccountComponent } from './components/floating-column/add-new-acc
import { SearchComponent } from './components/floating-column/search/search.component';
import { AddNewStatusComponent } from "./components/floating-column/add-new-status/add-new-status.component";
import { ManageAccountComponent } from "./components/floating-column/manage-account/manage-account.component";
+import { ActionBarComponent } from './components/stream/status/action-bar/action-bar.component';
import { WaitingAnimationComponent } from './components/waiting-animation/waiting-animation.component';
const routes: Routes = [
@@ -58,6 +59,7 @@ const routes: Routes = [
SettingsComponent,
AddNewAccountComponent,
SearchComponent,
+ ActionBarComponent,
WaitingAnimationComponent
],
imports: [
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.html b/src/app/components/stream/status/action-bar/action-bar.component.html
new file mode 100644
index 00000000..2f50011b
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/action-bar.component.html
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.scss b/src/app/components/stream/status/action-bar/action-bar.component.scss
new file mode 100644
index 00000000..ae753be3
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/action-bar.component.scss
@@ -0,0 +1,42 @@
+@import "variables";
+.action-bar {
+ // outline: 1px solid greenyellow; // height: 20px;
+ margin: 5px 10px 5px $avatar-column-space;
+ padding: 0;
+ font-size: 24px;
+ height: 30px;
+ &__link {
+ color: $status-secondary-color;
+ &:hover {
+ color: $status-links-color;
+ }
+ &:not(:last-child) {
+ margin-right: 15px;
+ }
+ }
+
+ &__lock {
+ color: $status-secondary-color;
+ width: 24px;
+
+ &:not(:last-child) {
+ margin-right: 15px;
+ }
+ }
+}
+
+.boosted {
+ color: $boost-color;
+
+ &:hover {
+ color: darken($boost-color, 10);
+ }
+}
+
+.favorited {
+ color: $favorite-color;
+
+ &:hover {
+ color: darken($favorite-color, 10);
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.spec.ts b/src/app/components/stream/status/action-bar/action-bar.component.spec.ts
new file mode 100644
index 00000000..be44980a
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/action-bar.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ActionBarComponent } from './action-bar.component';
+
+describe('ActionBarComponent', () => {
+ let component: ActionBarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ActionBarComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ActionBarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.ts b/src/app/components/stream/status/action-bar/action-bar.component.ts
new file mode 100644
index 00000000..f0fe0602
--- /dev/null
+++ b/src/app/components/stream/status/action-bar/action-bar.component.ts
@@ -0,0 +1,190 @@
+import { Component, OnInit, OnDestroy, Input } from '@angular/core';
+import { Store } from '@ngxs/store';
+
+import { StatusWrapper } from '../../stream.component';
+import { MastodonService } from '../../../../services/mastodon.service';
+import { AccountInfo } from '../../../../states/accounts.state';
+import { Observable, Subscription } from 'rxjs';
+import { Status, Results } from '../../../../services/models/mastodon.interfaces';
+// import { map } from "rxjs/operators";
+
+@Component({
+ selector: 'app-action-bar',
+ templateUrl: './action-bar.component.html',
+ styleUrls: ['./action-bar.component.scss']
+})
+export class ActionBarComponent implements OnInit, OnDestroy {
+
+ @Input() statusWrapper: StatusWrapper;
+
+ isFavorited: boolean;
+ isBoosted: boolean;
+
+ isBoostLocked: boolean;
+ isLocked: boolean;
+
+ private isProviderSelected: boolean;
+ private selectedAccounts: AccountInfo[];
+
+ private favoriteStatePerAccountId: { [id: string]: boolean; } = {};
+ private bootedStatePerAccountId: { [id: string]: boolean; } = {};
+
+ private accounts$: Observable;
+ private accountSub: Subscription;
+
+ constructor(
+ private readonly store: Store,
+ private readonly mastodonService: MastodonService) {
+
+ this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
+ }
+
+ ngOnInit() {
+ // const selectedAccounts = this.getSelectedAccounts();
+ // this.checkStatus(selectedAccounts);
+
+ const status = this.statusWrapper.status;
+ const account = this.statusWrapper.provider;
+ this.favoriteStatePerAccountId[account.id] = status.favourited;
+ this.bootedStatePerAccountId[account.id] = status.reblogged;
+
+ this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
+ this.checkStatus(accounts);
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.accountSub.unsubscribe();
+ }
+
+ private checkStatus(accounts: AccountInfo[]): void {
+ const status = this.statusWrapper.status;
+ const provider = this.statusWrapper.provider;
+ this.selectedAccounts = accounts.filter(x => x.isSelected);
+ this.isProviderSelected = this.selectedAccounts.filter(x => x.id === provider.id).length > 0;
+
+ if (status.visibility === 'direct' || status.visibility === 'private') {
+ this.isBoostLocked = true;
+ } else {
+ this.isBoostLocked = false;
+ }
+
+ if ((status.visibility === 'direct' || status.visibility === 'private') && !this.isProviderSelected) {
+ this.isLocked = true;
+ } else {
+ this.isLocked = false;
+ }
+
+ this.checkIfFavorited();
+ this.checkIfBoosted();
+ }
+
+ reply(): boolean {
+ console.warn('reply');
+ return false;
+ }
+
+ boost(): boolean {
+ this.selectedAccounts.forEach((account: AccountInfo) => {
+ const isProvider = this.statusWrapper.provider.id === account.id;
+
+ let pipeline: Promise = Promise.resolve(this.statusWrapper.status);
+
+ if (!isProvider) {
+ pipeline = pipeline.then((foreignStatus: Status) => {
+ const statusUrl = foreignStatus.url;
+ return this.mastodonService.search(account, statusUrl)
+ .then((results: Results) => {
+ //TODO check and type errors
+ return results.statuses[0];
+ });
+ });
+ }
+
+ pipeline
+ .then((status: Status) => {
+ if (this.isBoosted) {
+ return this.mastodonService.unreblog(account, status);
+ } else {
+ return this.mastodonService.reblog(account, status);
+ }
+ })
+ .then((boostedStatus: Status) => {
+ this.bootedStatePerAccountId[account.id] = boostedStatus.reblogged;
+ this.checkIfBoosted();
+ // this.isBoosted = !this.isBoosted;
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ });
+
+ return false;
+ }
+
+ favorite(): boolean {
+ this.selectedAccounts.forEach((account: AccountInfo) => {
+ const isProvider = this.statusWrapper.provider.id === account.id;
+
+ let pipeline: Promise = Promise.resolve(this.statusWrapper.status);
+
+ if (!isProvider) {
+ pipeline = pipeline.then((foreignStatus: Status) => {
+ const statusUrl = foreignStatus.url;
+ return this.mastodonService.search(account, statusUrl)
+ .then((results: Results) => {
+ //TODO check and type errors
+ return results.statuses[0];
+ });
+ });
+ }
+
+ pipeline
+ .then((status: Status) => {
+ if (this.isFavorited) {
+ return this.mastodonService.unfavorite(account, status);
+ } else {
+ return this.mastodonService.favorite(account, status);
+ }
+ })
+ .then((favoritedStatus: Status) => {
+ this.favoriteStatePerAccountId[account.id] = favoritedStatus.favourited;
+ this.checkIfFavorited();
+ // this.isFavorited = !this.isFavorited;
+ })
+ .catch(err => {
+ console.error(err);
+ });
+ });
+ return false;
+ }
+
+ private checkIfBoosted() {
+ const selectedAccount = this.selectedAccounts[0];
+ if (selectedAccount) {
+ this.isBoosted = this.bootedStatePerAccountId[selectedAccount.id];
+ } else {
+ this.isBoosted = false;
+ }
+ }
+
+ private checkIfFavorited() {
+ const selectedAccount = this.selectedAccounts[0];
+
+ if (selectedAccount) {
+ this.isFavorited = this.favoriteStatePerAccountId[selectedAccount.id];
+ } else {
+ this.isFavorited = false;
+ }
+ }
+
+ more(): boolean {
+ console.warn('more');
+ return false;
+ }
+
+ private getSelectedAccounts(): AccountInfo[] {
+ var regAccounts = this.store.snapshot().registeredaccounts.accounts;
+ return regAccounts;
+ }
+}
diff --git a/src/app/components/stream/status/status.component.html b/src/app/components/stream/status/status.component.html
index 13612b9b..ce7f40d0 100644
--- a/src/app/components/stream/status/status.component.html
+++ b/src/app/components/stream/status/status.component.html
@@ -1,5 +1,6 @@
@@ -15,21 +16,7 @@
getCompactRelativeTime(status.created_at) }}
-
+
-
-
-
+
\ No newline at end of file
diff --git a/src/app/components/stream/status/status.component.scss b/src/app/components/stream/status/status.component.scss
index cd7dd691..4db923be 100644
--- a/src/app/components/stream/status/status.component.scss
+++ b/src/app/components/stream/status/status.component.scss
@@ -1,5 +1,5 @@
@import "variables";
-$avatar-column-space: 70px;
+
.reblog {
position: relative;
margin: 5px 0 0 10px;
@@ -76,12 +76,12 @@ $avatar-column-space: 70px;
&__content {
/*width: calc(100% - 50px);*/
word-wrap: break-word;
- margin: 0px 10px 10px $avatar-column-space;
- }
- &__content p {
- margin: 0;
- font-size: 0.85em;
+ margin: 0 10px 0 $avatar-column-space;
}
+ // &__content p {
+ // margin: 0 !important;
+ // font-size: 0.85em;
+ // }
&__created-at {
color: $status-secondary-color;
position: absolute;
@@ -90,6 +90,10 @@ $avatar-column-space: 70px;
}
}
+// .attachments {
+
+// }
+
//Mastodon styling
:host ::ng-deep .status__content {
color: $status-primary-color;
@@ -101,24 +105,15 @@ $avatar-column-space: 70px;
& .invisible {
display: none;
}
+ & p {
+ margin: 0px;
+ //font-size: .9em;
+ // font-size: 14px;
+ }
}
.attachments {
- width: calc(100% - 80px);
- margin: 0px 10px 10px $avatar-column-space;
+ display: block;
+ // width: calc(100% - 80px);
+ margin: 10px 10px 0 $avatar-column-space;
}
-
-.action-bar {
- // outline: 1px solid greenyellow; // height: 20px;
- margin: 0px 10px 10px $avatar-column-space;
- font-size: 24px;
- &__link {
- color: $status-secondary-color;
- &:hover {
- color: $status-links-color;
- }
- &:not(:last-child) {
- margin-right: 15px;
- }
- }
-}
\ No newline at end of file
diff --git a/src/app/components/stream/status/status.component.ts b/src/app/components/stream/status/status.component.ts
index b0c36d3d..ca495536 100644
--- a/src/app/components/stream/status/status.component.ts
+++ b/src/app/components/stream/status/status.component.ts
@@ -2,6 +2,7 @@ import { Component, OnInit, Input, Inject, LOCALE_ID } from "@angular/core";
import { Status } from "../../../services/models/mastodon.interfaces";
import { formatDate } from '@angular/common';
import { stateNameErrorMessage } from "@ngxs/store/src/decorators/state";
+import { StatusWrapper } from "../stream.component";
@Component({
@@ -14,16 +15,18 @@ export class StatusComponent implements OnInit {
reblog: boolean;
hasAttachments: boolean;
- private _status: Status;
- @Input('status')
- set status(value: Status) {
- this._status = value;
+ private _statusWrapper: StatusWrapper;
+ status: Status;
+ @Input('statusWrapper')
+ set statusWrapper(value: StatusWrapper) {
+ this._statusWrapper = value;
+ this.status = value.status;
if(this.status.reblog){
this.reblog = true;
- this.displayedStatus = this._status.reblog;
+ this.displayedStatus = this.status.reblog;
} else {
- this.displayedStatus = this._status;
+ this.displayedStatus = this.status;
}
if(!this.displayedStatus.account.display_name){
@@ -36,8 +39,8 @@ export class StatusComponent implements OnInit {
}
- get status(): Status{
- return this._status;
+ get statusWrapper(): StatusWrapper{
+ return this._statusWrapper;
}
diff --git a/src/app/components/stream/stream.component.html b/src/app/components/stream/stream.component.html
index fbfd0357..8bb78cbf 100644
--- a/src/app/components/stream/stream.component.html
+++ b/src/app/components/stream/stream.component.html
@@ -3,8 +3,8 @@
{{ streamElement.name.toUpperCase() }}
diff --git a/src/app/components/stream/stream.component.ts b/src/app/components/stream/stream.component.ts
index 7cb6b257..10b94a81 100644
--- a/src/app/components/stream/stream.component.ts
+++ b/src/app/components/stream/stream.component.ts
@@ -17,7 +17,7 @@ export class StreamComponent implements OnInit {
private account: AccountInfo;
private websocketStreaming: StreamingWrapper;
- statuses: Status[] = [];
+ statuses: StatusWrapper[] = [];
private bufferStream: Status[] = [];
private bufferWasCleared: boolean;
@@ -90,7 +90,8 @@ export class StreamComponent implements OnInit {
}
for (const status of this.bufferStream) {
- this.statuses.unshift(status);
+ const wrapper = new StatusWrapper(status, this.account);
+ this.statuses.unshift(wrapper);
}
this.bufferStream.length = 0;
@@ -100,10 +101,11 @@ export class StreamComponent implements OnInit {
this.isProcessingInfiniteScroll = true;
const lastStatus = this.statuses[this.statuses.length - 1];
- this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.id)
+ this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id)
.then((status: Status[]) => {
for (const s of status) {
- this.statuses.push(s);
+ const wrapper = new StatusWrapper(s, this.account);
+ this.statuses.push(wrapper);
}
})
.catch(err => {
@@ -123,7 +125,8 @@ export class StreamComponent implements OnInit {
this.mastodonService.getTimeline(this.account, this._streamElement.type)
.then((results: Status[]) => {
for (const s of results) {
- this.statuses.push(s);
+ const wrapper = new StatusWrapper(s, this.account);
+ this.statuses.push(wrapper);
}
});
}
@@ -133,9 +136,10 @@ export class StreamComponent implements OnInit {
this.websocketStreaming.statusUpdateSubjet.subscribe((update: StatusUpdate) => {
if (update) {
if (update.type === EventEnum.update) {
- if (!this.statuses.find(x => x.id == update.status.id)) {
+ if (!this.statuses.find(x => x.status.id == update.status.id)) {
if (this.streamPositionnedAtTop) {
- this.statuses.unshift(update.status);
+ const wrapper = new StatusWrapper(update.status, this.account);
+ this.statuses.unshift(wrapper);
} else {
this.bufferStream.push(update.status);
}
@@ -159,4 +163,11 @@ export class StreamComponent implements OnInit {
}
}
+}
+
+export class StatusWrapper {
+ constructor(
+ public status: Status,
+ public provider: AccountInfo
+ ) {}
}
\ No newline at end of file
diff --git a/src/app/services/mastodon.service.ts b/src/app/services/mastodon.service.ts
index a189e679..39c82a3f 100644
--- a/src/app/services/mastodon.service.ts
+++ b/src/app/services/mastodon.service.ts
@@ -5,10 +5,11 @@ import { ApiRoutes } from './models/api.settings';
import { Account, Status, Results } from "./models/mastodon.interfaces";
import { AccountInfo } from '../states/accounts.state';
import { StreamTypeEnum } from '../states/streams.state';
+import { stat } from 'fs';
@Injectable()
export class MastodonService {
-
+
private apiRoutes = new ApiRoutes();
constructor(private readonly httpClient: HttpClient) { }
@@ -115,6 +116,30 @@ export class MastodonService {
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get(route, { headers: headers }).toPromise()
}
+
+ reblog(account: AccountInfo, status: Status): Promise {
+ const route = `https://${account.instance}${this.apiRoutes.reblogStatus}`.replace('{0}', status.id);
+ const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
+ return this.httpClient.post(route, null, { headers: headers }).toPromise()
+ }
+
+ unreblog(account: AccountInfo, status: Status): Promise {
+ const route = `https://${account.instance}${this.apiRoutes.unreblogStatus}`.replace('{0}', status.id);
+ const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
+ return this.httpClient.post(route, null, { headers: headers }).toPromise()
+ }
+
+ favorite(account: AccountInfo, status: Status): any {
+ const route = `https://${account.instance}${this.apiRoutes.favouritingStatus}`.replace('{0}', status.id);
+ const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
+ return this.httpClient.post(route, null, { headers: headers }).toPromise()
+ }
+
+ unfavorite(account: AccountInfo, status: Status): any {
+ const route = `https://${account.instance}${this.apiRoutes.unfavouritingStatus}`.replace('{0}', status.id);
+ const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
+ return this.httpClient.post(route, null, { headers: headers }).toPromise()
+ }
}
export enum VisibilityEnum {
diff --git a/src/app/services/models/mastodon.interfaces.ts b/src/app/services/models/mastodon.interfaces.ts
index 2d4aeb0a..e2b50740 100644
--- a/src/app/services/models/mastodon.interfaces.ts
+++ b/src/app/services/models/mastodon.interfaces.ts
@@ -116,15 +116,16 @@ export interface Status {
created_at: string;
reblogs_count: string;
favourites_count: string;
- reblogged: string;
- favourited: string;
- sensitive: string;
+ reblogged: boolean;
+ favourited: boolean;
+ sensitive: boolean;
spoiler_text: string;
visibility: string;
media_attachments: Attachment[];
mentions: string;
tags: string;
application: Application;
+ emojis: any[];
}
export interface Tag {
name: string;
diff --git a/src/sass/_variables.scss b/src/sass/_variables.scss
index ae8164e5..e8b45f14 100644
--- a/src/sass/_variables.scss
+++ b/src/sass/_variables.scss
@@ -6,13 +6,11 @@ $color-primary: #141824;
$color-secondary: #090b10;
$default-font-size: 15px;
$small-font-size: 12px;
-
$btn-primary-color: #515a62;
$btn-primary-color: #254d6f;
$btn-primary-color: #444f74;
$btn-primary-color-hover: darken($btn-primary-color, 10);
$btn-primary-font-color: white;
-
// TEST 1
$status-primary-color: #fff;
$status-secondary-color: #353e64;
@@ -21,11 +19,12 @@ $status-links-color: #d9e1e8;
// $status-primary-color : #8f93a2;
// $status-primary-color : lighten(#8f93a2, 30);
// $status-links-color : #b2ccd6;
-
+$boost-color : #5098eb;
+$favorite-color: #ffc16f;
// Block dispositions
$stream-selector-height: 30px;
$stream-column-separator: 7px;
$stream-column-width: 320px;
-
+$avatar-column-space: 70px;
//Bootstrap cuistomization
-$enable-rounded : false;
\ No newline at end of file
+$enable-rounded: false;
\ No newline at end of file