feat: add image page

This commit is contained in:
TopchetoEU 2023-06-30 23:52:08 +03:00
parent d080822781
commit cc3e1a6a9c
14 changed files with 210 additions and 27 deletions

View File

@ -106,7 +106,7 @@ export default class ImageRouter extends AppRouter {
// Clean up request
const req = await convert(rawReq, { name: 'string?', visibility: 'number?' });
req.name ??= new UUID().toString();
req.name ??= rawFile.name;
req.visibility ??= 0;
if (req.visibility < 0 || req.visibility > 2) throw new HttpError("body.visibility: Must be 0, 1, or 2");
const id = new UUID();
@ -150,6 +150,21 @@ export default class ImageRouter extends AppRouter {
await this.db.images.updateOne({ _id: id }, { $set: { name: body.name, visibility: body.visibility } });
return ImageRouter.deserialize(img);
}
@rest('POST', '/delete')
async delete(@schema('uuid') id: UUID, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {
const user = await this.db.users.findOne({ username: jwt.name });
if (!user) throw new HttpError("You don't exist.");
const img = await this.db.images.findOne({ _id: id });
if (!img) throw new HttpError("Image doesn't exist.");
if (user.username !== img.author) throw new HttpError("You don't own the image.");
await this.db.images.deleteOne({ _id: id });
await this.db.users.updateMany({}, {
$pull: { images: id, likes: id }
});
return ImageRouter.deserialize(img);
}
@rest('POST', '/like')
async like(@schema('uuid') id: UUID, @jwt(v => v.salt, true) @auth() jwt: JWTPayload) {

View File

@ -5,6 +5,7 @@ import { PageLoginComponent } from './page-login/page-login.component';
import { PageSignupComponent } from './page-signup/page-signup.component';
import { PageUploadComponent } from './page-upload/page-upload.component';
import { PageUserComponent } from './page-user/page-user.component';
import { PageImageComponent } from './page-image/page-image.component';
const routes: Routes = [
{ path: '', component: PageHomeComponent },
@ -12,6 +13,7 @@ const routes: Routes = [
{ path: 'signup', component: PageSignupComponent },
{ path: 'upload', component: PageUploadComponent },
{ path: 'user/:name', component: PageUserComponent },
{ path: 'image/:id', component: PageImageComponent },
];
@NgModule({

View File

@ -39,3 +39,24 @@
overflow: hidden;
overflow-y: auto;
}
.overlay {
pointer-events: none;
position: absolute;
top: 0;
right: 0;
z-index: 10000000;
width: 100vw;
.messages-container {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-end;
display: inline-block;
overflow: hidden;
max-height: 100vh;
position: absolute;
right: 0;
}
}

View File

@ -13,6 +13,7 @@ import { PageUserComponent } from './page-user/page-user.component';
import { ImagesComponent } from './images/images.component';
import { ImageComponent } from './image/image.component';
import { MessageComponent } from './message/message.component';
import { PageImageComponent } from './page-image/page-image.component';
@NgModule({
declarations: [
@ -24,7 +25,8 @@ import { MessageComponent } from './message/message.component';
PageUserComponent,
ImagesComponent,
ImageComponent,
MessageComponent
MessageComponent,
PageImageComponent
],
imports: [
BrowserModule,

View File

@ -1,10 +1,13 @@
<img class="image" [src]="environment.apiURL + '/images/img/' + image.file" *ngIf="image">
<h3 class="name" *ngIf="image">{{image.name}}</h3>
<div class="stats" *ngIf="image">
<span class="likes">
<img class="like" src="assets/like-active.svg" *ngIf="image.liked" (click)="unlike()">
<img class="like" src="assets/like-inactive.svg" *ngIf="!image.liked" (click)="like()">
{{image.likes}}
</span>
<span class="author">By <a [routerLink]="'/user/' + image.author">{{image.author}}</a></span>
<div class="card" *ngIf="image && !deleted">
<img class="image" [routerLink]="'/image/' + image.id" [src]="environment.apiURL + '/images/img/' + image.file">
<h3 class="name">{{image.name}}</h3>
<div class="stats">
<span class="likes">
<img class="button" src="assets/like-active.svg" *ngIf="image.liked" (click)="unlike()">
<img class="button" src="assets/like-inactive.svg" *ngIf="!image.liked" (click)="like()">
{{image.likes}}
</span>
<span class="author">By <a [routerLink]="'/user/' + image.author">{{image.author}}</a></span>
<img class="button" src="assets/trash.svg" (click)="delete()" *ngIf="image.author === users.$user()?.username">
</div>
</div>

View File

@ -1,12 +1,5 @@
:host {
.card {
user-select: none;
.image {
pointer-events: none;
height: 15rem;
display: block;
}
width: min-content;
border-radius: 1rem;
box-shadow: .25rem .25rem 1rem -.5rem #000;
@ -17,6 +10,12 @@
gap: 1rem;
margin: 1rem;
.image {
cursor: pointer;
height: 15rem;
display: block;
}
.name, .author {
text-align: left;
padding: 0;
@ -27,11 +26,11 @@
display: flex;
justify-content: space-between;
.button {
height: 1.5rem;
}
.likes {
// flex: 1 0 auto;
.like {
height: 1.5rem;
}
display: inline-flex;
align-items: center;
gap: .5rem;

View File

@ -1,6 +1,8 @@
import { Component, Input } from '@angular/core';
import { Component, Input, Output } from '@angular/core';
import { environment } from 'src/environments/environment';
import { Image, ImagesService } from '../services/images.service';
import { MessagesService } from '../services/messages.service';
import { UsersService } from '../services/users.service';
@Component({
selector: 'app-image',
@ -11,13 +13,31 @@ export class ImageComponent {
@Input()
public image?: Image | null;
public environment = environment;
public deleted = false;
public async like() {
this.image = await this.images.like(this.image!.id);
try {
this.image = await this.images.like(this.image!.id);
}
catch (e: any) { this.msgs.error(e); }
}
public async unlike() {
this.image = await this.images.unlike(this.image!.id);
try {
this.image = await this.images.unlike(this.image!.id);
}
catch (e: any) { this.msgs.error(e); }
}
public async delete() {
try {
await this.images.delete(this.image!.id);
this.deleted = true;
}
catch (e: any) { this.msgs.error(e); }
}
public constructor(public images: ImagesService) {}
public constructor(
public images: ImagesService,
public users: UsersService,
private msgs: MessagesService,
) {}
}

View File

@ -1,4 +1,4 @@
<div class="card" *ngIf="!hasDied" [ngClass]="['container', message.role]">
<div *ngIf="!hasDied" [ngClass]="['container', message.role]">
<div class="content">
{{message.content}}
</div>

View File

@ -1,5 +1,13 @@
.container {
width: min-content;
background-color: white;
box-shadow: .1rem .1rem .5rem -.1rem #000;
box-sizing: border-box;
display: inline-flex;
flex-direction: column;
gap: 1rem;
padding: .5rem 1rem;
margin: .5rem;
border-bottom-right-radius: 0;

View File

@ -0,0 +1,16 @@
<div class="container" *ngIf="image">
<h3 class="name">{{image.name}}</h3>
<img class="image" [src]="environment.apiURL + '/images/img/' + image.file">
<div class="stats">
<span class="actions">
<div class="likes">
<img class="button" src="assets/like-active.svg" *ngIf="image.liked" (click)="unlike()">
<img class="button" src="assets/like-inactive.svg" *ngIf="!image.liked" (click)="like()">
{{image.likes}}
</div>
<img class="button" src="assets/trash.svg" (click)="delete()" *ngIf="image.author === users.$user()?.username">
</span>
<span class="author">By <a [routerLink]="'/user/' + image.author">{{image.author}}</a></span>
</div>
</div>

View File

@ -0,0 +1,40 @@
.container {
user-select: none;
width: min(100%, 50rem);
padding: 1rem;
box-sizing: border-box;
flex-direction: column;
margin: auto;
.image {
margin: 1rem auto;
max-height: 30rem;
max-width: 100%;
display: block;
}
.name, .author {
text-align: left;
padding: 0;
margin: 0;
}
.stats {
.actions {
display: flex;
gap: 1rem;
.likes {
// flex: 1 0 auto;
display: inline-flex;
align-items: center;
gap: .5rem;
}
.button {
height: 1.5rem;
}
}
display: flex;
justify-content: space-between;
}
}

View File

@ -0,0 +1,53 @@
import { Component } from '@angular/core';
import { Image, ImagesService } from '../services/images.service';
import { ActivatedRoute, Router } from '@angular/router';
import { UsersService } from '../services/users.service';
import { ScrollService } from '../services/scroll.service';
import { MessagesService } from '../services/messages.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-page-image',
templateUrl: './page-image.component.html',
styleUrls: ['./page-image.component.scss']
})
export class PageImageComponent {
public image?: Image | null;
public environment = environment;
public async like() {
try {
this.image = await this.images.like(this.image!.id);
}
catch (e: any) { this.msgs.error(e); }
}
public async unlike() {
try {
this.image = await this.images.unlike(this.image!.id);
}
catch (e: any) { this.msgs.error(e); }
}
public async delete() {
try {
await this.images.delete(this.image!.id);
this.router.navigateByUrl('/');
}
catch (e: any) { this.msgs.error(e); }
}
public constructor(
route: ActivatedRoute,
public users: UsersService,
private images: ImagesService,
private msgs: MessagesService,
private router: Router,
) {
route.paramMap.subscribe(async v => {
try {
this.image = await images.get(v.get('id')!);
}
catch (e: any) { msgs.error(e); }
// this.imagesEl.next();
});
}
}

View File

@ -57,6 +57,9 @@ export class ImagesService {
public async change(id: string, proto: CreateImageBody) {
return await firstValueFrom(this.http.post<string>(`${this.url}/change?id=${id}`, proto, this.users.httpOptions({})));
}
public async delete(id: string) {
return await firstValueFrom(this.http.post<string>(`${this.url}/delete?id=${id}`, {}, this.users.httpOptions({})));
}
public async like(id: string) {
return await firstValueFrom(this.http.post<Image>(`${this.url}/like?id=${id}`, {}, this.users.httpOptions({})));

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="48px" height="48px"><path d="M 28 7 C 25.243 7 23 9.243 23 12 L 23 15 L 13 15 C 11.896 15 11 15.896 11 17 C 11 18.104 11.896 19 13 19 L 15.109375 19 L 16.792969 49.332031 C 16.970969 52.510031 19.600203 55 22.783203 55 L 41.216797 55 C 44.398797 55 47.029031 52.510031 47.207031 49.332031 L 48.890625 19 L 51 19 C 52.104 19 53 18.104 53 17 C 53 15.896 52.104 15 51 15 L 41 15 L 41 12 C 41 9.243 38.757 7 36 7 L 28 7 z M 28 11 L 36 11 C 36.552 11 37 11.449 37 12 L 37 15 L 27 15 L 27 12 C 27 11.449 27.448 11 28 11 z M 19.113281 19 L 44.886719 19 L 43.212891 49.109375 C 43.153891 50.169375 42.277797 51 41.216797 51 L 22.783203 51 C 21.723203 51 20.846109 50.170328 20.787109 49.111328 L 19.113281 19 z M 32 23.25 C 31.033 23.25 30.25 24.034 30.25 25 L 30.25 45 C 30.25 45.966 31.033 46.75 32 46.75 C 32.967 46.75 33.75 45.966 33.75 45 L 33.75 25 C 33.75 24.034 32.967 23.25 32 23.25 z M 24.642578 23.251953 C 23.677578 23.285953 22.922078 24.094547 22.955078 25.060547 L 23.652344 45.146484 C 23.685344 46.091484 24.462391 46.835938 25.400391 46.835938 C 25.421391 46.835938 25.441891 46.835938 25.462891 46.835938 C 26.427891 46.801938 27.183391 45.991391 27.150391 45.025391 L 26.453125 24.939453 C 26.419125 23.974453 25.606578 23.228953 24.642578 23.251953 z M 39.355469 23.251953 C 38.388469 23.224953 37.580875 23.974453 37.546875 24.939453 L 36.849609 45.025391 C 36.815609 45.991391 37.571109 46.801938 38.537109 46.835938 C 38.558109 46.836938 38.578609 46.835938 38.599609 46.835938 C 39.537609 46.835938 40.314656 46.091484 40.347656 45.146484 L 41.044922 25.060547 C 41.078922 24.094547 40.321469 23.285953 39.355469 23.251953 z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB