diff --git a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts index 45ba8a7..ae2879f 100644 --- a/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts +++ b/jambotron-ui/src/app/modules/user-module/dialog-upload-image.component/dialog-upload-image.component.ts @@ -34,7 +34,7 @@ export class DialogUploadImageComponent { } selectUploadedImage() { - this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo?.url); + this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo); this.dialogRef.close(); } diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html index b35dfde..571308f 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.html @@ -4,38 +4,55 @@ - Title - + Title - - Use title image - - - - - -
- Description - - -
-
-
- - -
+ Title image + + + + + + +
+ + + + Title image + + +
+ +
+ +
+ +
+ Description + + + + + + +
+
+
+ + +
+
@@ -47,26 +64,26 @@
-
- Body - - -
-
-
- - -
-
- -
+
+ Body + + + + + + + +
+ + +
diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss index b5cf161..1ee9028 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.scss @@ -9,9 +9,7 @@ .markdown-editor-container{ display: flex; } -mat-card{ - margin: 20px; -} + mat-card-title{ color: cyan; } @@ -55,3 +53,17 @@ mat-card-title{ /* display: block; float: right;*/ } + +.label-style{ + font-size: -webkit-xxx-large; + font-weight: bold; +} + + +.mat-mdc-card-outlined { + background-color: var(--mat-card-outlined-container-color, var(--mat-sys-surface)); + border-radius:0; + border-width: var(--mat-card-outlined-outline-width, 1px); + border-color: var(--mat-card-outlined-outline-color, var(--mat-sys-outline-variant)); + box-shadow: var(--mat-card-outlined-container-elevation, var(--mat-sys-level0)); +} diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts index bfa6464..adce58d 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts +++ b/jambotron-ui/src/app/modules/user-module/tutorial-add.component/tutorial-add.component.ts @@ -16,6 +16,7 @@ import {MatDialog} from '@angular/material/dialog'; import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component'; import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component'; import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component'; +import {MatDivider} from '@angular/material/divider'; @Component({ selector: 'app-tutorial-add.component', @@ -33,7 +34,8 @@ import {FileUploadComponent} from '../../../components/file-upload.component/fil MatTabGroup, ReactiveFormsModule, FormsModule, - AngularMarkdownEditorModule + AngularMarkdownEditorModule, + MatDivider ], templateUrl: './tutorial-add.component.html', styleUrl: './tutorial-add.component.scss', @@ -43,9 +45,9 @@ export class TutorialAddComponent implements OnInit{ tutorial: Tutorial = new Tutorial(); submitted = false; - bsEditorInstance!: EditorInstance; markdownText = ''; showEditor = true; + bsEditorInstance!: EditorInstance; tutorialForm!: FormGroup; editorOptions!: EditorOption; @@ -84,6 +86,7 @@ const language = 'typescript'; this.editorOptions = { autofocus: false, iconlibrary: 'fa', + height: 300, savable: false, onFullscreenExit: (e) => this.hidePreview(), onShow: (e) => this.bsEditorInstance = e, @@ -99,9 +102,7 @@ const language = 'typescript'; isPreview: [true] }); } - selectImage() { - throw new Error('Method not implemented.'); - } + /** highlight all code found, needs to be wrapped in timer to work properly */ highlight() { setTimeout(() => { diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html index 9a3eefd..fbd0be4 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html +++ b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.html @@ -4,23 +4,97 @@ - Title + Title - - - - - - - - - - - - - + + Title image + + + + + + +
+ + + + Title image + + +
+ + +
+ +
+ +
+ Description + + + + + + + +
+
+
+ + +
+
+
+
+ + + + + + + +
+
+ +
+ Body + + + + + + + +
+ + +
+
+ + + + + + + +
+
+
diff --git a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts index 9898445..0872c20 100644 --- a/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts +++ b/jambotron-ui/src/app/modules/user-module/tutorial-edit.component/tutorial-edit.component.ts @@ -1,13 +1,18 @@ -import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {MarkdownComponent} from 'ngx-markdown'; +import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core'; +import {MarkdownComponent, MarkdownService} from 'ngx-markdown'; import {MatButton} from '@angular/material/button'; import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input'; import {MatTab, MatTabGroup} from '@angular/material/tabs'; -import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {Tutorial} from '../../../models/tutorial.model'; import {UserApiService} from '../user-api.service'; import {ActivatedRoute, RouterLink} from '@angular/router'; +import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor'; +import {MatDivider} from '@angular/material/divider'; +import {MatDialog} from '@angular/material/dialog'; +import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component'; +import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component'; @Component({ selector: 'app-tutorial-edit.component', @@ -27,17 +32,28 @@ import {ActivatedRoute, RouterLink} from '@angular/router'; FormsModule, MatFormField, RouterLink, - MatError + MatError, + AngularMarkdownEditorModule, + MatDivider ], templateUrl: './tutorial-edit.component.html', styleUrl: './tutorial-edit.component.scss', schemas: [CUSTOM_ELEMENTS_SCHEMA] }) -export class TutorialEditComponent { +export class TutorialEditComponent implements OnInit{ + tutorial: Tutorial = new Tutorial(); submitted = false; hasError = false; errorMessage = ''; + markdownText=""; + bsEditorInstance!: EditorInstance; + tutorialForm!: FormGroup; + editorOptions!: EditorOption; + + readonly dialog = inject(MatDialog); + + markdown = `## Markdown __rulez__! --- @@ -56,7 +72,11 @@ const language = 'typescript'; > Blockquote to the max`; private id: string | null | undefined; - constructor(private userApiService: UserApiService, private route: ActivatedRoute) { + constructor(private userApiService: UserApiService, + private route: ActivatedRoute, + private fb: FormBuilder, + private markdownService: MarkdownService + ) { this.route.queryParams .subscribe(params => { @@ -71,6 +91,62 @@ const language = 'typescript'; } ); } + + ngOnInit(): void { + this.editorOptions = { + autofocus: false, + iconlibrary: 'fa', + height: 300, + savable: false, + onFullscreenExit: (e) => this.hidePreview(), + onShow: (e) => this.bsEditorInstance = e, + parser: (val) => this.parse(val) + }; + this.buildForm(this.tutorial.description); + + } + + buildForm(markdownText: string | undefined) { + this.tutorialForm = this.fb.group({ + body: [markdownText], + isPreview: [true] + }); + } + /** highlight all code found, needs to be wrapped in timer to work properly */ + highlight() { + setTimeout(() => { + this.markdownService.highlight(); + }); + } + + hidePreview() { + if (this.bsEditorInstance && this.bsEditorInstance.hidePreview) { + this.bsEditorInstance.hidePreview(); + } + } + + showFullScreen(isFullScreen: boolean) { + if (this.bsEditorInstance && this.bsEditorInstance.setFullscreen) { + this.bsEditorInstance.showPreview(); + this.bsEditorInstance.setFullscreen(isFullScreen); + } + } + + parse(inputValue: string) { + const markedOutput = this.markdownService.parse(inputValue.trim()); + this.highlight(); + + return markedOutput; + } + + onFormChanges(): void { + this.tutorialForm.valueChanges.subscribe(formData => { + if (formData) { + this.markdownText = formData.body; + } + }); + } + updateTutorial(): void { this.userApiService.update(this.id, this.tutorial) .subscribe( @@ -87,4 +163,55 @@ const language = 'typescript'; this.hasError = true; }); } + + openSelectImageDialog(enterAnimationDuration: string, exitAnimationDuration: string) { + + let dialogSelectRef = this.dialog.open(DialogSelectImageComponent, { + height: '500px', + width: '600px', + enterAnimationDuration, + exitAnimationDuration, + // data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password} + }); + dialogSelectRef.componentInstance.uploadClicked.subscribe(result => { + dialogSelectRef.close(); + this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration); + }) + + const dialogSelectSubscription = dialogSelectRef.componentInstance.selectClicked + .subscribe(result => { + console.log('Got the data!', result); + + if (result == null) { + return; + } + this.tutorial.titleimage = result.url; + + }); + } + + openUploadImageDialog(enterAnimationDuration: string, exitAnimationDuration: string) { + let dialogSelectRef = this.dialog.open(DialogUploadImageComponent, { + height: '500px', + width: '600px', + enterAnimationDuration, + exitAnimationDuration, + // data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password} + }); + dialogSelectRef.componentInstance.openSelectDialogClicked.subscribe(result => { + dialogSelectRef.close(); + this.openSelectImageDialog(enterAnimationDuration, exitAnimationDuration); + }) + + const dialogUploadSubscription = dialogSelectRef.componentInstance.selectUploadedImageClicked + .subscribe(result => { + console.log('Got the data!', result); + + if (result == null) { + return; + } + this.tutorial.titleimage = result.url; + + }); + } } diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java index 854ae34..1641451 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/FilesController.java @@ -8,6 +8,7 @@ import com.jambotronGroup.jambotron.model.User; import com.jambotronGroup.jambotron.payload.request.SaveZhipuAiImageRequest; import com.jambotronGroup.jambotron.security.AuthenticationFacade; import com.jambotronGroup.jambotron.system.SystemServiceImpl; +import com.jambotronGroup.jambotron.utils.FilesRoutingHelper; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,22 +41,6 @@ public class FilesController { @Autowired FilesStorageService storageService; - /** - * Generates a URL for accessing a user's image. - * format: /api/user/user-images/{filename:.+} - * - * @param filename The name of the file. - * @return The URL to access the file. - */ - private static String getUserImageUrl(String filename) { - return MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getUserImage", filename).build().toString(); - } - - private static String getPublicImageUrl(String filename) { - return MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getFile", filename).build().toString(); - } @PostMapping("/api/user/saveZhipuAiImage") public ResponseEntity saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) { @@ -117,8 +102,8 @@ public class FilesController { FileInfo fileInfo = new FileInfo( file.getOriginalFilename(), - FilesController.getUserImageUrl(localFullFileName)); - + FilesRoutingHelper.getUserImageUrl(localFullFileName)); + message = "Uploaded the file successfully: " + file.getOriginalFilename(); return ResponseEntity.status(HttpStatus.OK).body(new ResponseImageUploadResult(message,fileInfo)); @@ -136,7 +121,7 @@ public class FilesController { List fileInfos = storageService.loadUserImages(user.getId().toString()).map(path -> { String filename = path.getFileName().toString(); - String url = FilesController.getUserImageUrl(filename); + String url = FilesRoutingHelper.getUserImageUrl(filename); return new FileInfo(filename, url); }).collect(Collectors.toList()); @@ -149,7 +134,7 @@ public class FilesController { public ResponseEntity> getListFiles() { List fileInfos = storageService.loadAll().map(path -> { String filename = path.getFileName().toString(); - String url = FilesController.getPublicImageUrl(filename); + String url = FilesRoutingHelper.getPublicImageUrl(filename); return new FileInfo(filename, url); }).collect(Collectors.toList()); diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 99d9bf3..70f1434 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -9,6 +9,7 @@ import com.jambotronGroup.jambotron.repository.TutorialRepository; import com.jambotronGroup.jambotron.repository.UserRepository; import com.jambotronGroup.jambotron.security.AuthenticationFacade; import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; +import com.jambotronGroup.jambotron.utils.FilesRoutingHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -118,8 +119,7 @@ public class TutorialController { String.format("Tutorial_%s",newFilename ) ); - String url = MvcUriComponentsBuilder - .fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString(); + String url = FilesRoutingHelper.getPublicImageUrl(path.getFileName().toString()); try { @@ -160,7 +160,12 @@ public class TutorialController { } @PutMapping("user/tutorial-update/{id}") - public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) { + public ResponseEntity updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial){ + + User user = authenticationFacade.getUser(); + + + Optional tutorialData = tutorialRepository.findById(id); Map map = new LinkedHashMap(); if (tutorialData.isPresent()) { @@ -169,7 +174,22 @@ public class TutorialController { servTutorial.setDescription(tutorial.getDescription()); servTutorial.setPublished(tutorial.isPublished()); servTutorial.setTobepublished(tutorial.isTobepublished()); + try { + + String imageFileName = FilesStorageServiceImpl.getFileNameFromUrl(tutorial.getTitleimage()); + + String servImageFileName = FilesStorageServiceImpl.getFileNameFromUrl(servTutorial.getTitleimage()); + filesStorageService.deletePublicFile(servImageFileName); + + Path path= filesStorageService.moveFile( + user.getId().toString(), + tutorial.getTitleimage(), + String.format("Tutorial_%s",imageFileName ) + ); + + servTutorial.setTitleimage(FilesRoutingHelper.getPublicImageUrl(path.getFileName().toString())); + servTutorial = tutorialRepository.save(servTutorial); } catch (Exception e) { diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java index 4acd984..b56dd4e 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageService.java @@ -25,4 +25,6 @@ public interface FilesStorageService { public Stream loadUserImages(String userID); public Path moveFile(String userID, String url, String newFilename) throws Exception; + + public void deletePublicFile(String filename); } \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java index 404c847..4b4cee0 100644 --- a/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java +++ b/src/main/java/com/jambotronGroup/jambotron/fileUpload/FilesStorageServiceImpl.java @@ -39,6 +39,16 @@ public class FilesStorageServiceImpl implements FilesStorageService { return path.substring(path.lastIndexOf('/') + 1); // Extract the file name } + /** + * Moves a file from a user's directory to the public directory with a new name. + * StandardCopyOption.REPLACE_EXISTING + * + * @param userID The ID of the user owning the file. + * @param url The URL of the file to move. + * @param newFilename The new name for the file in the public directory. + * @return The path to the moved file in the public directory. + * @throws Exception If the file cannot be moved. + */ @Override public Path moveFile(String userID, String url, String newFilename) throws Exception { @@ -56,6 +66,16 @@ public class FilesStorageServiceImpl implements FilesStorageService { return this.rootPublic.resolve(newFilename); } + @Override + public void deletePublicFile(String filename) { + try { + Path filePath = this.rootPublic.resolve(filename); + Files.deleteIfExists(filePath); + } catch (IOException e) { + throw new RuntimeException("Could not delete the file: " + e.getMessage()); + } + } + /** * Saves a file to a user's directory. * uploads/user-images/{userID}/{filename} diff --git a/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java b/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java new file mode 100644 index 0000000..49e9a66 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/utils/FilesRoutingHelper.java @@ -0,0 +1,25 @@ +package com.jambotronGroup.jambotron.utils; + +import com.jambotronGroup.jambotron.controllers.FilesController; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; + +@Component +public class FilesRoutingHelper { + /** + * Generates a URL for accessing a user's image. + * format: /api/user/user-images/{filename:.+} + * + * @param filename The name of the file. + * @return The URL to access the file. + */ + public static String getUserImageUrl(String filename) { + return MvcUriComponentsBuilder + .fromMethodName(FilesController.class, "getUserImage", filename).build().toString(); + } + + public static String getPublicImageUrl(String filename) { + return MvcUriComponentsBuilder + .fromMethodName(FilesController.class, "getFile", filename).build().toString(); + } +}