mirror of
https://codeberg.org/beerbrawl/beerbrawl.git
synced 2024-09-23 01:30:52 +02:00
Merge branch 'feat/#37/public-picture-gallery' into 'development'
Feat/#37/public picture gallery See merge request 2024ss-se-pr-group/24ss-se-pr-qse-11!125
This commit is contained in:
commit
f381cd30d6
|
@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
|
|
|
@ -7,6 +7,7 @@ import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.SharedMediaCreateDto;
|
|||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.SharedMediaMetadataDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.SharedMediaUpdateStateDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.mapper.SharedMediaMapper;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.SharedMedia;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.NotFoundException;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.service.SharedMediaService;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
|
@ -60,7 +61,16 @@ public class SharedMediaEndpoint {
|
|||
@GetMapping(value = "/tournament/{tournamentId}", produces = "application/json")
|
||||
public ResponseEntity<List<SharedMediaMetadataDto>> getSharedMediaByTournament(@PathVariable(name = "tournamentId") Long tournamentId) {
|
||||
LOG.info("GET {}/tournament/{}", BASE_ENDPOINT, tournamentId);
|
||||
var sharedMediaMetadataDtos = sharedMediaService.findAllByTournamentIdWithoutImage(tournamentId);
|
||||
var sharedMediaMetadataDtos = sharedMediaService.findAllByTournamentIdWithoutImage(tournamentId, false);
|
||||
return ResponseEntity.ok(sharedMediaMetadataDtos);
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@GetMapping(value = "/tournament/public/{tournamentId}", produces = "application/json")
|
||||
public ResponseEntity<List<SharedMediaMetadataDto>> getPublicSharedMediaByTournament(@PathVariable(name = "tournamentId") Long tournamentId) {
|
||||
LOG.info("GET {}/tournament/public/{}", BASE_ENDPOINT, tournamentId);
|
||||
var sharedMediaMetadataDtos = sharedMediaService.findAllByTournamentIdWithoutImage(tournamentId, true);
|
||||
return ResponseEntity.ok(sharedMediaMetadataDtos);
|
||||
}
|
||||
|
||||
|
@ -80,6 +90,7 @@ public class SharedMediaEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@GetMapping(value = "/image/{sharedMediaId}", produces = MediaType.IMAGE_JPEG_VALUE)
|
||||
|
@ -96,6 +107,27 @@ public class SharedMediaEndpoint {
|
|||
}
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@GetMapping(value = "/image/public/{sharedMediaId}", produces = MediaType.IMAGE_JPEG_VALUE)
|
||||
public ResponseEntity<byte[]> getPublicSharedMediaImage(@PathVariable(name = "sharedMediaId") Long sharedMediaId) {
|
||||
LOG.info("GET {}/image/public/{}", BASE_ENDPOINT, sharedMediaId);
|
||||
try {
|
||||
var image = sharedMediaService.findOne(sharedMediaId);
|
||||
if (image.getState() != SharedMedia.MediaState.APPROVED) {
|
||||
throw new AccessDeniedException("Image is not public");
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + sharedMediaId + ".jpg\"")
|
||||
.body(image.getImage());
|
||||
} catch (NotFoundException e) {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
||||
} catch (AccessDeniedException e) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@PutMapping("/{sharedMediaId}")
|
||||
|
|
|
@ -22,6 +22,17 @@ public interface SharedMediaRepository extends JpaRepository<SharedMedia, Long>
|
|||
+ "FROM SharedMedia sm WHERE sm.tournament.id = :tournamentId")
|
||||
List<SharedMediaMetadataDto> findAllByTournamentIdWithoutImage(@Param("tournamentId") Long tournamentId);
|
||||
|
||||
/**
|
||||
* Find all shared media by a specific tournament id without the image field.
|
||||
*
|
||||
* @param tournamentId The tournament id
|
||||
* @return List of shared media entries for the given tournament without the image field
|
||||
*/
|
||||
@Query("SELECT new at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.SharedMediaMetadataDto(sm.id, sm.author, sm.title, sm.state, sm.tournament.id) "
|
||||
+ "FROM SharedMedia sm WHERE sm.tournament.id = :tournamentId AND sm.state = 'APPROVED'")
|
||||
List<SharedMediaMetadataDto> findAllPublicByTournamentIdWithoutImage(@Param("tournamentId") Long tournamentId);
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ public interface SharedMediaService {
|
|||
* @param tournamentId The ID of the tournament
|
||||
* @return List of shared media entries for the given tournament
|
||||
*/
|
||||
List<SharedMediaMetadataDto> findAllByTournamentIdWithoutImage(Long tournamentId);
|
||||
List<SharedMediaMetadataDto> findAllByTournamentIdWithoutImage(Long tournamentId, boolean onlyApproved);
|
||||
|
||||
/**
|
||||
* Create a shared media entry.
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.List;
|
|||
public class SharedMediaServiceImpl implements SharedMediaService {
|
||||
|
||||
|
||||
|
||||
private final SharedMediaRepository sharedMediaRepository;
|
||||
private final TournamentRepository tournamentRepository;
|
||||
|
||||
|
@ -33,8 +32,12 @@ public class SharedMediaServiceImpl implements SharedMediaService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<SharedMediaMetadataDto> findAllByTournamentIdWithoutImage(Long tournamentId) {
|
||||
return sharedMediaRepository.findAllByTournamentIdWithoutImage(tournamentId);
|
||||
public List<SharedMediaMetadataDto> findAllByTournamentIdWithoutImage(Long tournamentId, boolean onlyApproved) {
|
||||
if (onlyApproved) {
|
||||
return sharedMediaRepository.findAllPublicByTournamentIdWithoutImage(tournamentId);
|
||||
} else {
|
||||
return sharedMediaRepository.findAllByTournamentIdWithoutImage(tournamentId);
|
||||
}
|
||||
}
|
||||
|
||||
public SharedMedia create(SharedMediaCreateDto sharedMediaCreateDto, MultipartFile image) throws NotFoundException {
|
||||
|
|
341
e2e/package-lock.json
generated
341
e2e/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -263,6 +263,132 @@ export class SharedMediaEndpointService {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tournamentId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getPublicSharedMediaByTournament(tournamentId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<Array<SharedMediaMetadataDto>>;
|
||||
public getPublicSharedMediaByTournament(tournamentId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<Array<SharedMediaMetadataDto>>>;
|
||||
public getPublicSharedMediaByTournament(tournamentId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<Array<SharedMediaMetadataDto>>>;
|
||||
public getPublicSharedMediaByTournament(tournamentId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
|
||||
if (tournamentId === null || tournamentId === undefined) {
|
||||
throw new Error('Required parameter tournamentId was null or undefined when calling getPublicSharedMediaByTournament.');
|
||||
}
|
||||
|
||||
let localVarHeaders = this.defaultHeaders;
|
||||
|
||||
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (localVarHttpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (localVarHttpHeaderAcceptSelected !== undefined) {
|
||||
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
let localVarHttpContext: HttpContext | undefined = options && options.context;
|
||||
if (localVarHttpContext === undefined) {
|
||||
localVarHttpContext = new HttpContext();
|
||||
}
|
||||
|
||||
let localVarTransferCache: boolean | undefined = options && options.transferCache;
|
||||
if (localVarTransferCache === undefined) {
|
||||
localVarTransferCache = true;
|
||||
}
|
||||
|
||||
|
||||
let responseType_: 'text' | 'json' | 'blob' = 'json';
|
||||
if (localVarHttpHeaderAcceptSelected) {
|
||||
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType_ = 'text';
|
||||
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
|
||||
responseType_ = 'json';
|
||||
} else {
|
||||
responseType_ = 'blob';
|
||||
}
|
||||
}
|
||||
|
||||
let localVarPath = `/api/v1/shared-media/tournament/public/${this.configuration.encodeParam({name: "tournamentId", value: tournamentId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`;
|
||||
return this.httpClient.request<Array<SharedMediaMetadataDto>>('get', `${this.configuration.basePath}${localVarPath}`,
|
||||
{
|
||||
context: localVarHttpContext,
|
||||
responseType: <any>responseType_,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: localVarHeaders,
|
||||
observe: observe,
|
||||
transferCache: localVarTransferCache,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sharedMediaId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getPublicSharedMediaImage(sharedMediaId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'image/jpeg', context?: HttpContext, transferCache?: boolean}): Observable<Array<string>>;
|
||||
public getPublicSharedMediaImage(sharedMediaId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'image/jpeg', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<Array<string>>>;
|
||||
public getPublicSharedMediaImage(sharedMediaId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'image/jpeg', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<Array<string>>>;
|
||||
public getPublicSharedMediaImage(sharedMediaId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'image/jpeg', context?: HttpContext, transferCache?: boolean}): Observable<any> {
|
||||
if (sharedMediaId === null || sharedMediaId === undefined) {
|
||||
throw new Error('Required parameter sharedMediaId was null or undefined when calling getPublicSharedMediaImage.');
|
||||
}
|
||||
|
||||
let localVarHeaders = this.defaultHeaders;
|
||||
|
||||
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (localVarHttpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'image/jpeg'
|
||||
];
|
||||
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (localVarHttpHeaderAcceptSelected !== undefined) {
|
||||
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
let localVarHttpContext: HttpContext | undefined = options && options.context;
|
||||
if (localVarHttpContext === undefined) {
|
||||
localVarHttpContext = new HttpContext();
|
||||
}
|
||||
|
||||
let localVarTransferCache: boolean | undefined = options && options.transferCache;
|
||||
if (localVarTransferCache === undefined) {
|
||||
localVarTransferCache = true;
|
||||
}
|
||||
|
||||
|
||||
let responseType_: 'text' | 'json' | 'blob' = 'json';
|
||||
if (localVarHttpHeaderAcceptSelected) {
|
||||
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType_ = 'text';
|
||||
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
|
||||
responseType_ = 'json';
|
||||
} else {
|
||||
responseType_ = 'blob';
|
||||
}
|
||||
}
|
||||
|
||||
let localVarPath = `/api/v1/shared-media/image/public/${this.configuration.encodeParam({name: "sharedMediaId", value: sharedMediaId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`;
|
||||
return this.httpClient.request<Array<string>>('get', `${this.configuration.basePath}${localVarPath}`,
|
||||
{
|
||||
context: localVarHttpContext,
|
||||
responseType: <any>responseType_,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: localVarHeaders,
|
||||
observe: observe,
|
||||
transferCache: localVarTransferCache,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tournamentId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
126
frontend/package-lock.json
generated
126
frontend/package-lock.json
generated
|
@ -25,6 +25,7 @@
|
|||
"@popperjs/core": "2.11.8",
|
||||
"core-js": "3.36.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"ng-qrcode": "^18.0.0",
|
||||
"puppeteer": "^22.11.2",
|
||||
"replace-in-files-cli": "^2.2.0",
|
||||
"rxjs": "7.8.1",
|
||||
|
@ -7586,7 +7587,6 @@
|
|||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -8972,6 +8972,11 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
@ -9156,6 +9161,11 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/encode-utf8": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz",
|
||||
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
|
@ -10593,7 +10603,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
|
@ -13236,7 +13245,6 @@
|
|||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
|
@ -14063,6 +14071,19 @@
|
|||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ng-qrcode": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ng-qrcode/-/ng-qrcode-18.0.0.tgz",
|
||||
"integrity": "sha512-qmtU6n8lxyTGxrtRbzXYPR7JNGwQ6SZXqnEUQksjmFsPLssZRchal9zlw4eUMPm/bRxy+hSV6dWe/59mTNtc8A==",
|
||||
"dependencies": {
|
||||
"qrcode": "^1.5.3",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=18 <19",
|
||||
"@angular/core": ">=18 <19"
|
||||
}
|
||||
},
|
||||
"node_modules/nice-napi": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
|
||||
|
@ -15105,7 +15126,6 @@
|
|||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
|
@ -15120,7 +15140,6 @@
|
|||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
|
@ -15173,7 +15192,6 @@
|
|||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -15562,6 +15580,14 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||
|
@ -15954,6 +15980,79 @@
|
|||
"node": ">=0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz",
|
||||
"integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"encode-utf8": "^1.0.3",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
|
@ -16463,8 +16562,7 @@
|
|||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
|
@ -17034,8 +17132,7 @@
|
|||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
|
@ -19552,8 +19649,7 @@
|
|||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.15",
|
||||
|
@ -19584,7 +19680,6 @@
|
|||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
|
@ -19649,7 +19744,6 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
|
@ -19664,7 +19758,6 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
|
@ -19675,8 +19768,7 @@
|
|||
"node_modules/wrap-ansi/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"@popperjs/core": "2.11.8",
|
||||
"core-js": "3.36.1",
|
||||
"jwt-decode": "4.0.0",
|
||||
"ng-qrcode": "^18.0.0",
|
||||
"puppeteer": "^22.11.2",
|
||||
"replace-in-files-cli": "^2.2.0",
|
||||
"rxjs": "7.8.1",
|
||||
|
|
|
@ -21,6 +21,7 @@ import { TournamentPublicViewComponent } from './components/tournament/tournamen
|
|||
import { InfoscreenKnockoutPhaseTreeComponent } from './components/infoscreen-knockout-phase-tree/infoscreen-knockout-phase-tree.component';
|
||||
import { EditKnockoutPhaseTreeComponent } from './components/tournament/edit-knockout-phase-tree/edit-knockout-phase-tree.component';
|
||||
import { PartyPicsApproveComponent } from './components/party-pictures-approve/party-pics-approve.component';
|
||||
import { TournamentPublicGalleryComponent } from './components/tournament/tournament-public-gallery/tournament-public-gallery.component';
|
||||
import { ImageUploadComponent } from './components/image-upload/image-upload.component';
|
||||
|
||||
const routes: Routes = [
|
||||
|
@ -84,6 +85,7 @@ const routes: Routes = [
|
|||
{ path: 'tournaments/:tournamentId/signup', component: TeamSignupComponent },
|
||||
{ path: 'tournaments/:tournamentId/upload-image', component: ImageUploadComponent },
|
||||
{ path: 'tournaments/:tournamentId/live', component: TournamentPublicViewComponent },
|
||||
{ path: 'tournaments/:tournamentId/public-gallery', component: TournamentPublicGalleryComponent },
|
||||
{
|
||||
path: 'infoscreen/:tournamentId/ko-phase',
|
||||
component: InfoscreenKnockoutPhaseTreeComponent,
|
||||
|
|
|
@ -37,13 +37,13 @@
|
|||
|
||||
<mat-form-field class="full-width" [appearance]="'outline'">
|
||||
<mat-label>Author</mat-label>
|
||||
<input matInput [(ngModel)]="author" maxlength="50" />
|
||||
<mat-hint align="end">{{ author?.length || 0 }}/50</mat-hint>
|
||||
<input matInput [(ngModel)]="author" maxlength="30" />
|
||||
<mat-hint align="end">{{ author?.length || 0 }}/30</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="full-width" [appearance]="'outline'">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput [(ngModel)]="title" maxlength="100" />
|
||||
<mat-hint align="end">{{ title?.length || 0 }}/100</mat-hint>
|
||||
<input matInput [(ngModel)]="title" maxlength="50" />
|
||||
<mat-hint align="end">{{ title?.length || 0 }}/50</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="buttons">
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
<p>{{ picture?.title }}</p>
|
||||
</div>
|
||||
<div class="bottom-bar">
|
||||
<h5>11:00</h5>
|
||||
|
||||
<h5></h5>
|
||||
<div>
|
||||
@if (picture?.state === 'PENDING' || picture?.state === 'REJECTED') {
|
||||
<button
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<div class="cards-container">
|
||||
@for (card of cards; track card) {
|
||||
@if (card?.image !== 'src') {
|
||||
<mat-card
|
||||
class="image-card"
|
||||
[@cardAnimation]="card.animationState"
|
||||
(@cardAnimation.done)="onAnimationEnd($event, card)"
|
||||
>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ card.author }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<img [src]="card.image" [alt]="card.alt" class="behave" />
|
||||
<mat-card-footer class="centered">
|
||||
<div>{{ card.title }}</div>
|
||||
</mat-card-footer>
|
||||
</mat-card>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (showQrCode && checkUploadLinkString()) {
|
||||
<div class="qr-code-container">
|
||||
<div class="centered">
|
||||
<qr-code value="{{ linkToUpload }}" size="500" />
|
||||
<div>Scan and contribute!</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
.cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 25vw);
|
||||
grid-template-rows: repeat(2, 50vh);
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.image-card {
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.behave {
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
margin: 10px;
|
||||
height: calc(100% - 5rem);
|
||||
max-width: calc(100% - 2rem);
|
||||
}
|
||||
|
||||
.qr-code-container {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import { Component, OnInit, inject, input } from '@angular/core';
|
||||
import {
|
||||
CommonModule,
|
||||
Location,
|
||||
LocationStrategy,
|
||||
NgOptimizedImage,
|
||||
PathLocationStrategy,
|
||||
} from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { QrCodeModule } from 'ng-qrcode';
|
||||
import { SharedMediaEndpointService, SharedMediaMetadataDto } from '@api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tournament-public-gallery',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, QrCodeModule, NgOptimizedImage],
|
||||
templateUrl: './tournament-public-gallery.component.html',
|
||||
styleUrl: './tournament-public-gallery.component.scss',
|
||||
animations: [
|
||||
trigger('cardAnimation', [
|
||||
state(
|
||||
'expand',
|
||||
style({
|
||||
transform: 'scale(1)',
|
||||
}),
|
||||
),
|
||||
state(
|
||||
'shrink',
|
||||
style({
|
||||
transform: 'scale(0.1)',
|
||||
}),
|
||||
),
|
||||
transition('expand => shrink', [animate('0.8s')]),
|
||||
transition('shrink => expand', [animate('1s')]),
|
||||
]),
|
||||
],
|
||||
providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }],
|
||||
})
|
||||
export class TournamentPublicGalleryComponent implements OnInit {
|
||||
linkToUpload: string = '';
|
||||
cards: { image: string; alt: string; author: string; title: string; animationState: string }[] =
|
||||
[];
|
||||
showQrCode: boolean = false;
|
||||
pictures: SharedMediaMetadataDto[] = [];
|
||||
|
||||
tournamentId = input<number>();
|
||||
|
||||
location = inject(Location);
|
||||
sharedMediaService = inject(SharedMediaEndpointService);
|
||||
|
||||
ngOnInit() {
|
||||
this.sharedMediaService.getPublicSharedMediaByTournament(this.tournamentId()!).subscribe({
|
||||
next: (data: SharedMediaMetadataDto[]) => {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const card = {
|
||||
image: 'src',
|
||||
alt: 'Tournament Image',
|
||||
author: 'Author',
|
||||
title: 'Title',
|
||||
animationState: 'expand',
|
||||
};
|
||||
this.loadImage(data[Math.floor(Math.random() * data.length)], card);
|
||||
this.cards.push(card);
|
||||
this.setRandomAnimationInterval(card);
|
||||
}
|
||||
this.pictures = data;
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error fetching pictures:', error);
|
||||
},
|
||||
});
|
||||
this.setupQRCodeInterval();
|
||||
this.fetchImages();
|
||||
this.linkToUpload = this.location.prepareExternalUrl(
|
||||
`#/tournaments/${this.tournamentId()}/upload-image`,
|
||||
);
|
||||
}
|
||||
|
||||
setupQRCodeInterval() {
|
||||
setInterval(() => {
|
||||
this.showQrCode = true;
|
||||
setTimeout(() => {
|
||||
this.showQrCode = false;
|
||||
//this.fetchImages();
|
||||
}, 7000);
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
setRandomAnimationInterval(card: {
|
||||
image: string;
|
||||
alt: string;
|
||||
author: string;
|
||||
title: string;
|
||||
animationState: string;
|
||||
}) {
|
||||
const expandedDuration = 4000 + Math.random() * 10000;
|
||||
const shrinkDuration = 1500;
|
||||
setTimeout(() => {
|
||||
this.toggleAnimation(card, 'shrink');
|
||||
this.setRandomAnimationInterval(card);
|
||||
}, expandedDuration);
|
||||
}
|
||||
|
||||
toggleAnimation(
|
||||
card: { image: string; alt: string; author: string; title: string; animationState: string },
|
||||
state: string,
|
||||
) {
|
||||
card.animationState = state;
|
||||
}
|
||||
|
||||
onAnimationEnd(
|
||||
_: AnimationEvent,
|
||||
card: { image: string; alt: string; author: string; title: string; animationState: string },
|
||||
) {
|
||||
if (card.animationState === 'shrink') {
|
||||
this.loadImage(this.pictures[Math.floor(Math.random() * this.pictures.length)], card);
|
||||
card.animationState = 'expand';
|
||||
}
|
||||
}
|
||||
|
||||
fetchImages(): void {
|
||||
this.sharedMediaService.getPublicSharedMediaByTournament(this.tournamentId()!).subscribe({
|
||||
next: (data: SharedMediaMetadataDto[]) => {
|
||||
this.pictures = data;
|
||||
},
|
||||
error: error => {
|
||||
console.error('Error fetching pictures:', error);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
checkUploadLinkString(): boolean {
|
||||
return (
|
||||
this.linkToUpload === '' || this.linkToUpload.endsWith(`${this.tournamentId()}/upload-image`)
|
||||
);
|
||||
}
|
||||
|
||||
loadImage(
|
||||
imgEntry: SharedMediaMetadataDto,
|
||||
card: { image: string; alt: string; author: string; title: string; animationState: string },
|
||||
): void {
|
||||
console.log(`Inside LoadImage ${card.image}`);
|
||||
if (imgEntry?.id !== undefined) {
|
||||
this.sharedMediaService.getPublicSharedMediaImage(imgEntry.id!).subscribe(
|
||||
response => {
|
||||
card.image = URL.createObjectURL(response as any); // eslint-disable-line
|
||||
card.alt = imgEntry.title ?? 'Tournament Picture';
|
||||
card.author = imgEntry.author ?? 'Mysterious KnowOne';
|
||||
card.title = imgEntry.title ?? 'Tournament Picture';
|
||||
},
|
||||
error => {
|
||||
// Handle errors
|
||||
console.error('Error fetching shared media image:', error);
|
||||
card.image = '';
|
||||
card.alt = 'Error fetching specific image';
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue