feat: add image page
This commit is contained in:
parent
d080822781
commit
cc3e1a6a9c
@ -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) {
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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>
|
@ -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;
|
||||
|
@ -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,
|
||||
) {}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
16
frontend/src/app/page-image/page-image.component.html
Normal file
16
frontend/src/app/page-image/page-image.component.html
Normal 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>
|
40
frontend/src/app/page-image/page-image.component.scss
Normal file
40
frontend/src/app/page-image/page-image.component.scss
Normal 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;
|
||||
}
|
||||
}
|
53
frontend/src/app/page-image/page-image.component.ts
Normal file
53
frontend/src/app/page-image/page-image.component.ts
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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({})));
|
||||
|
1
frontend/src/assets/trash.svg
Normal file
1
frontend/src/assets/trash.svg
Normal 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 |
Loading…
Reference in New Issue
Block a user