1
0
mirror of https://github.com/NicolasConstant/sengi synced 2025-02-10 00:40:36 +01:00

added media edition #56

This commit is contained in:
Nicolas Constant 2019-03-10 14:36:22 -04:00
parent 17bdd7db42
commit ab38f8fc05
No known key found for this signature in database
GPG Key ID: 1E9F677FB01A5688
8 changed files with 91 additions and 44 deletions

View File

@ -110,7 +110,7 @@ export class AppComponent implements OnInit, OnDestroy {
let files = <File[]>event.dataTransfer.files; let files = <File[]>event.dataTransfer.files;
const selectedAccount = this.toolsService.getSelectedAccounts()[0]; const selectedAccount = this.toolsService.getSelectedAccounts()[0];
this.mediaService.uploadMedia(files, selectedAccount); this.mediaService.uploadMedia(selectedAccount, files);
return false; return false;
} }
} }

View File

@ -2,12 +2,14 @@
<div *ngIf="m.attachment === null" class="media__loading" title="{{m.file.name}}"> <div *ngIf="m.attachment === null" class="media__loading" title="{{m.file.name}}">
<app-waiting-animation class="waiting-icon status-form__sending--waiting"></app-waiting-animation> <app-waiting-animation class="waiting-icon status-form__sending--waiting"></app-waiting-animation>
</div> </div>
<div *ngIf="m.attachment !== null" class="media__loaded" title="{{m.file.name}}"> <div *ngIf="m.attachment !== null" class="media__loaded" title="{{m.file.name}}"
(mouseleave) ="updateMedia(m)">
<div class="media__loaded--hover"> <div class="media__loaded--hover">
<button class="media__loaded--button" title="remove" (click)="removeMedia(m)"> <button class="media__loaded--button" title="remove" (click)="removeMedia(m)">
<fa-icon [icon]="faTimes"></fa-icon> <fa-icon [icon]="faTimes"></fa-icon>
</button> </button>
<input class="media__loaded--description" autocomplete="off" placeholder="Describe for the visually impaired"/> <input class="media__loaded--description" [(ngModel)]="m.description"
autocomplete="off" placeholder="Describe for the visually impaired"/>
</div> </div>
<img class="media__loaded--preview" src="{{m.attachment.preview_url}}" /> <img class="media__loaded--preview" src="{{m.attachment.preview_url}}" />
</div> </div>

View File

@ -41,7 +41,7 @@
height: 10px; height: 10px;
position: absolute; position: absolute;
top:5px; top:5px;
right:7px; right:8px;
color: white; color: white;
} }

View File

@ -1,8 +1,9 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { faTimes } from "@fortawesome/free-solid-svg-icons"; import { faTimes } from "@fortawesome/free-solid-svg-icons";
import { Subscription } from 'rxjs';
import { MediaService, MediaWrapper } from '../../../services/media.service'; import { MediaService, MediaWrapper } from '../../../services/media.service';
import { Subscription } from 'rxjs'; import { ToolsService } from '../../../services/tools.service';
@Component({ @Component({
selector: 'app-media', selector: 'app-media',
@ -14,7 +15,9 @@ export class MediaComponent implements OnInit, OnDestroy {
media: MediaWrapper[] = []; media: MediaWrapper[] = [];
private mediaSub: Subscription; private mediaSub: Subscription;
constructor(private readonly mediaService: MediaService) { } constructor(
private readonly toolsService: ToolsService,
private readonly mediaService: MediaService) { }
ngOnInit() { ngOnInit() {
this.mediaSub = this.mediaService.mediaSubject.subscribe((media: MediaWrapper[]) => { this.mediaSub = this.mediaService.mediaSubject.subscribe((media: MediaWrapper[]) => {
@ -26,10 +29,14 @@ export class MediaComponent implements OnInit, OnDestroy {
this.mediaSub.unsubscribe(); this.mediaSub.unsubscribe();
} }
removeMedia(media: MediaWrapper): boolean{ removeMedia(media: MediaWrapper): boolean {
console.warn('delete'); this.mediaService.remove(media);
console.warn(media); return false;
}
updateMedia(media: MediaWrapper): boolean {
const account = this.toolsService.getSelectedAccounts()[0];
this.mediaService.update(account, media);
return false; return false;
} }
} }

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http'; import { HttpHeaders, HttpClient } from '@angular/common/http';
import { ApiRoutes } from './models/api.settings'; import { ApiRoutes } from './models/api.settings';
import { Account, Status, Results, Context, Relationship, Instance } from "./models/mastodon.interfaces"; import { Account, Status, Results, Context, Relationship, Instance, Attachment } from "./models/mastodon.interfaces";
import { AccountInfo } from '../states/accounts.state'; import { AccountInfo } from '../states/accounts.state';
import { StreamTypeEnum } from '../states/streams.state'; import { StreamTypeEnum } from '../states/streams.state';
@ -12,7 +12,7 @@ export class MastodonService {
constructor(private readonly httpClient: HttpClient) { } constructor(private readonly httpClient: HttpClient) { }
getInstance(instance: string): Promise<Instance>{ getInstance(instance: string): Promise<Instance> {
const route = `https://${instance}${this.apiRoutes.getInstance}`; const route = `https://${instance}${this.apiRoutes.getInstance}`;
return this.httpClient.get<Instance>(route).toPromise(); return this.httpClient.get<Instance>(route).toPromise();
} }
@ -114,25 +114,25 @@ export class MastodonService {
return this.httpClient.post<Status>(url, formData, { headers: headers }).toPromise(); return this.httpClient.post<Status>(url, formData, { headers: headers }).toPromise();
} }
search(account: AccountInfo, query: string, resolve: boolean = false): Promise<Results>{ search(account: AccountInfo, query: string, resolve: boolean = false): Promise<Results> {
if(query[0] === '#') query = query.substr(1); if (query[0] === '#') query = query.substr(1);
const route = `https://${account.instance}${this.apiRoutes.search}?q=${query}&resolve=${resolve}`; const route = `https://${account.instance}${this.apiRoutes.search}?q=${query}&resolve=${resolve}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get<Results>(route, { headers: headers }).toPromise() return this.httpClient.get<Results>(route, { headers: headers }).toPromise()
} }
getAccountStatuses(account: AccountInfo, targetAccountId: number, onlyMedia: boolean, onlyPinned: boolean, excludeReplies: boolean, maxId: string, sinceId: string, limit: number = 20): Promise<Status[]>{ getAccountStatuses(account: AccountInfo, targetAccountId: number, onlyMedia: boolean, onlyPinned: boolean, excludeReplies: boolean, maxId: string, sinceId: string, limit: number = 20): Promise<Status[]> {
const route = `https://${account.instance}${this.apiRoutes.getAccountStatuses}`.replace('{0}', targetAccountId.toString()); const route = `https://${account.instance}${this.apiRoutes.getAccountStatuses}`.replace('{0}', targetAccountId.toString());
let params = `?only_media=${onlyMedia}&pinned=${onlyPinned}&exclude_replies=${excludeReplies}&limit=${limit}`; let params = `?only_media=${onlyMedia}&pinned=${onlyPinned}&exclude_replies=${excludeReplies}&limit=${limit}`;
if(maxId) params += `&max_id=${maxId}`; if (maxId) params += `&max_id=${maxId}`;
if(sinceId) params += `&since_id=${sinceId}`; if (sinceId) params += `&since_id=${sinceId}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get<Status[]>(route+params, { headers: headers }).toPromise(); return this.httpClient.get<Status[]>(route + params, { headers: headers }).toPromise();
} }
getStatusContext(account: AccountInfo, targetStatusId: string): Promise<Context>{ getStatusContext(account: AccountInfo, targetStatusId: string): Promise<Context> {
const params = this.apiRoutes.getStatusContext.replace('{0}', targetStatusId); const params = this.apiRoutes.getStatusContext.replace('{0}', targetStatusId);
const route = `https://${account.instance}${params}`; const route = `https://${account.instance}${params}`;
@ -140,7 +140,7 @@ export class MastodonService {
return this.httpClient.get<Context>(route, { headers: headers }).toPromise(); return this.httpClient.get<Context>(route, { headers: headers }).toPromise();
} }
searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false): Promise<Account[]>{ searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false): Promise<Account[]> {
const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}`; const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get<Account[]>(route, { headers: headers }).toPromise() return this.httpClient.get<Account[]>(route, { headers: headers }).toPromise()
@ -173,7 +173,7 @@ export class MastodonService {
getRelationships(account: AccountInfo, accountsToRetrieve: Account[]): Promise<Relationship[]> { getRelationships(account: AccountInfo, accountsToRetrieve: Account[]): Promise<Relationship[]> {
let params = "?"; let params = "?";
accountsToRetrieve.forEach(x => { accountsToRetrieve.forEach(x => {
if(params.includes('id')) params += '&'; if (params.includes('id')) params += '&';
params += `id[]=${x.id}`; params += `id[]=${x.id}`;
}); });
@ -195,6 +195,22 @@ export class MastodonService {
} }
uploadMediaAttachment(account: AccountInfo, file: File): Promise<Attachment> {
let input = new FormData();
input.append('file', file);
const route = `https://${account.instance}${this.apiRoutes.uploadMediaAttachment}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.post<Attachment>(route, input, { headers: headers }).toPromise();
}
//TODO: add focus support
updateMediaAttachment(account: AccountInfo, mediaId: string, description: string): Promise<Attachment> {
let input = new FormData();
input.append('description', description);
const route = `https://${account.instance}${this.apiRoutes.updateMediaAttachment.replace('{0}', mediaId)}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.put<Attachment>(route, input, { headers: headers }).toPromise();
}
} }
export enum VisibilityEnum { export enum VisibilityEnum {

View File

@ -5,6 +5,8 @@ import { BehaviorSubject, Subject } from 'rxjs';
import { AccountInfo } from '../states/accounts.state'; import { AccountInfo } from '../states/accounts.state';
import { ApiRoutes } from './models/api.settings'; import { ApiRoutes } from './models/api.settings';
import { Attachment } from './models/mastodon.interfaces'; import { Attachment } from './models/mastodon.interfaces';
import { MastodonService } from './mastodon.service';
import { NotificationService } from './notification.service';
@Injectable({ @Injectable({
@ -15,27 +17,25 @@ export class MediaService {
mediaSubject: BehaviorSubject<MediaWrapper[]> = new BehaviorSubject<MediaWrapper[]>([]); mediaSubject: BehaviorSubject<MediaWrapper[]> = new BehaviorSubject<MediaWrapper[]>([]);
constructor(private readonly httpClient: HttpClient) { } constructor(
private readonly notificationService: NotificationService,
private readonly mastodonService: MastodonService) { }
uploadMedia(files: File[], account: AccountInfo){ uploadMedia(account: AccountInfo, files: File[]){
for (let file of files) { for (let file of files) {
this.postMedia(file, account); this.postMedia(account, file);
} }
} }
private postMedia(file: File, account: AccountInfo){ private postMedia(account: AccountInfo, file: File){
const uniqueId = `${file.name}${file.size}${Math.random()}`; const uniqueId = `${file.name}${file.size}${Math.random()}`;
const wrapper = new MediaWrapper(uniqueId, file, null); const wrapper = new MediaWrapper(uniqueId, file, null, null);
let medias = this.mediaSubject.value; let medias = this.mediaSubject.value;
medias.push(wrapper); medias.push(wrapper);
this.mediaSubject.next(medias); this.mediaSubject.next(medias);
let input = new FormData(); this.mastodonService.uploadMediaAttachment(account, file)
input.append('file', file);
const route = `https://${account.instance}${this.apiRoutes.uploadMediaAttachment}`;
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
this.httpClient.post(route, input, { headers: headers }).toPromise()
.then((attachment: Attachment) => { .then((attachment: Attachment) => {
let currentMedias = this.mediaSubject.value; let currentMedias = this.mediaSubject.value;
let currentMedia = currentMedias.filter(x => x.id === uniqueId)[0]; let currentMedia = currentMedias.filter(x => x.id === uniqueId)[0];
@ -45,18 +45,37 @@ export class MediaService {
} }
}) })
.catch((err)=>{ .catch((err)=>{
let currentMedias = this.mediaSubject.value; this.remove(wrapper);
let currentMedia = currentMedias.filter(x => x.id !== uniqueId); this.notificationService.notifyHttpError(err);
this.mediaSubject.next(currentMedia);
//TODO: notify
}); });
} }
update( account: AccountInfo, media: MediaWrapper): any {
if(media.attachment.description === media.description) return;
this.mastodonService.updateMediaAttachment(account, media.attachment.id, media.description)
.then((att: Attachment) => {
let medias = this.mediaSubject.value;
let updatedMedia = medias.filter(x => x.id === media.id)[0];
updatedMedia.attachment.description = att.description;
this.mediaSubject.next(medias);
})
.catch((err) => {
this.notificationService.notifyHttpError(err);
});
}
remove(media: MediaWrapper): any {
let medias = this.mediaSubject.value;
let filteredMedias = medias.filter(x => x.id !== media.id);
this.mediaSubject.next(filteredMedias);
}
} }
export class MediaWrapper { export class MediaWrapper {
constructor( constructor(
public id: string, public id: string,
public file: File, public file: File,
public attachment: Attachment) {} public attachment: Attachment,
public description: string) {}
} }

View File

@ -23,6 +23,7 @@ export class ApiRoutes {
followRemote = '/api/v1/follows'; followRemote = '/api/v1/follows';
getInstance = '/api/v1/instance'; getInstance = '/api/v1/instance';
uploadMediaAttachment = '/api/v1/media'; uploadMediaAttachment = '/api/v1/media';
updateMediaAttachment = '/api/v1/media/{0}';
getMutes = '/api/v1/mutes'; getMutes = '/api/v1/mutes';
getNotifications = '/api/v1/notifications'; getNotifications = '/api/v1/notifications';
getSingleNotifications = '/api/v1/notifications/{0}'; getSingleNotifications = '/api/v1/notifications/{0}';

View File

@ -61,6 +61,8 @@ export interface Attachment {
remote_url: string; remote_url: string;
preview_url: string; preview_url: string;
text_url: string; text_url: string;
meta: any;
description: string;
} }
export interface Card { export interface Card {