Refactor image URL handling and enhance tutorial component layout
This commit is contained in:
+1
-1
@@ -34,7 +34,7 @@ export class DialogUploadImageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectUploadedImage() {
|
selectUploadedImage() {
|
||||||
this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo?.url);
|
this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo);
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-18
@@ -4,23 +4,42 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content >
|
<mat-card-content >
|
||||||
<mat-form-field class="example-full-width">
|
<mat-form-field class="example-full-width">
|
||||||
<mat-label>Title</mat-label>
|
<mat-label class="label-style">Title</mat-label>
|
||||||
|
|
||||||
<input matInput required
|
<input matInput required
|
||||||
[(ngModel)]="tutorial.title">
|
[(ngModel)]="tutorial.title">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="example-full-width">
|
|
||||||
<mat-label>Use title image</mat-label>
|
<mat-label class="label-style">Title image</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<table class="example-full-width">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 120px;">
|
||||||
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
||||||
<input matInput
|
</td>
|
||||||
|
<td>
|
||||||
|
<mat-form-field class="example-full-width">
|
||||||
|
<mat-label>Title image</mat-label>
|
||||||
|
<input matInput disabled
|
||||||
[(ngModel)]="tutorial.titleimage">
|
[(ngModel)]="tutorial.titleimage">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<div>
|
||||||
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
||||||
<button mat-raised-button color="primary" (click)="openUploadImageDialog('100ms', '5ms')">Upload image</button>
|
<button mat-raised-button color="primary" (click)="openUploadImageDialog('100ms', '5ms')">Upload image</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="example-full-width">
|
<div class="example-full-width">
|
||||||
<mat-label>Description</mat-label>
|
<mat-label class="label-style">Description</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab label="Markdown text">
|
<mat-tab label="Markdown text | Result">
|
||||||
|
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||||
|
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Editor">
|
||||||
<div class="markdown-editor-container">
|
<div class="markdown-editor-container">
|
||||||
<div class="markdown-editor">
|
<div class="markdown-editor">
|
||||||
<form novalidate>
|
<form novalidate>
|
||||||
@@ -33,9 +52,7 @@
|
|||||||
>
|
>
|
||||||
</angular-markdown-editor>
|
</angular-markdown-editor>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
|
||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="Result">
|
<mat-tab label="Result">
|
||||||
@@ -47,14 +64,18 @@
|
|||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="example-full-width">
|
<div class="example-full-width">
|
||||||
<mat-label>Body</mat-label>
|
<mat-label class="label-style">Body</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab label="Markdown text">
|
<mat-tab label="Markdown text | Result">
|
||||||
<div class="markdown-editor-container">
|
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||||
<div class="markdown-editor">
|
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Editor">
|
||||||
<form novalidate>
|
<form novalidate>
|
||||||
<angular-markdown-editor
|
<angular-markdown-editor style="color: #1a1a1a"
|
||||||
textareaId="editor2"
|
textareaId="editor2"
|
||||||
[options]="editorOptions"
|
[options]="editorOptions"
|
||||||
name="markdownText"
|
name="markdownText"
|
||||||
@@ -63,10 +84,6 @@
|
|||||||
>
|
>
|
||||||
</angular-markdown-editor>
|
</angular-markdown-editor>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="Result">
|
<mat-tab label="Result">
|
||||||
<markdown class="preview" [data]="tutorial.body"></markdown>
|
<markdown class="preview" [data]="tutorial.body"></markdown>
|
||||||
|
|||||||
+15
-3
@@ -9,9 +9,7 @@
|
|||||||
.markdown-editor-container{
|
.markdown-editor-container{
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
mat-card{
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
mat-card-title{
|
mat-card-title{
|
||||||
color: cyan;
|
color: cyan;
|
||||||
}
|
}
|
||||||
@@ -55,3 +53,17 @@ mat-card-title{
|
|||||||
/* display: block;
|
/* display: block;
|
||||||
float: right;*/
|
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));
|
||||||
|
}
|
||||||
|
|||||||
+6
-5
@@ -16,6 +16,7 @@ import {MatDialog} from '@angular/material/dialog';
|
|||||||
import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component';
|
import {DialogSelectImageComponent} from '../dialog-select-image.component/dialog-select-image.component';
|
||||||
import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component';
|
import {DialogUploadImageComponent} from '../dialog-upload-image.component/dialog-upload-image.component';
|
||||||
import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component';
|
import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component';
|
||||||
|
import {MatDivider} from '@angular/material/divider';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tutorial-add.component',
|
selector: 'app-tutorial-add.component',
|
||||||
@@ -33,7 +34,8 @@ import {FileUploadComponent} from '../../../components/file-upload.component/fil
|
|||||||
MatTabGroup,
|
MatTabGroup,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
AngularMarkdownEditorModule
|
AngularMarkdownEditorModule,
|
||||||
|
MatDivider
|
||||||
],
|
],
|
||||||
templateUrl: './tutorial-add.component.html',
|
templateUrl: './tutorial-add.component.html',
|
||||||
styleUrl: './tutorial-add.component.scss',
|
styleUrl: './tutorial-add.component.scss',
|
||||||
@@ -43,9 +45,9 @@ export class TutorialAddComponent implements OnInit{
|
|||||||
|
|
||||||
tutorial: Tutorial = new Tutorial();
|
tutorial: Tutorial = new Tutorial();
|
||||||
submitted = false;
|
submitted = false;
|
||||||
bsEditorInstance!: EditorInstance;
|
|
||||||
markdownText = '';
|
markdownText = '';
|
||||||
showEditor = true;
|
showEditor = true;
|
||||||
|
bsEditorInstance!: EditorInstance;
|
||||||
tutorialForm!: FormGroup;
|
tutorialForm!: FormGroup;
|
||||||
editorOptions!: EditorOption;
|
editorOptions!: EditorOption;
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ const language = 'typescript';
|
|||||||
this.editorOptions = {
|
this.editorOptions = {
|
||||||
autofocus: false,
|
autofocus: false,
|
||||||
iconlibrary: 'fa',
|
iconlibrary: 'fa',
|
||||||
|
height: 300,
|
||||||
savable: false,
|
savable: false,
|
||||||
onFullscreenExit: (e) => this.hidePreview(),
|
onFullscreenExit: (e) => this.hidePreview(),
|
||||||
onShow: (e) => this.bsEditorInstance = e,
|
onShow: (e) => this.bsEditorInstance = e,
|
||||||
@@ -99,9 +102,7 @@ const language = 'typescript';
|
|||||||
isPreview: [true]
|
isPreview: [true]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
selectImage() {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
/** highlight all code found, needs to be wrapped in timer to work properly */
|
/** highlight all code found, needs to be wrapped in timer to work properly */
|
||||||
highlight() {
|
highlight() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
+76
-2
@@ -4,15 +4,57 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content >
|
<mat-card-content >
|
||||||
<mat-form-field class="example-full-width">
|
<mat-form-field class="example-full-width">
|
||||||
<mat-label>Title</mat-label>
|
<mat-label class="label-style">Title</mat-label>
|
||||||
<input matInput required
|
<input matInput required
|
||||||
[(ngModel)]="tutorial.title">
|
[(ngModel)]="tutorial.title">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-label class="label-style">Title image</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<table class="example-full-width">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 120px;">
|
||||||
|
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<mat-form-field class="example-full-width">
|
||||||
|
<mat-label>Title image</mat-label>
|
||||||
|
<input matInput disabled
|
||||||
|
[(ngModel)]="tutorial.titleimage">
|
||||||
|
</mat-form-field>
|
||||||
|
<div>
|
||||||
|
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
||||||
|
<button mat-raised-button color="primary" (click)="openUploadImageDialog('100ms', '5ms')">Upload image</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="example-full-width">
|
||||||
|
<mat-label class="label-style">Description</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
<mat-tab-group>
|
<mat-tab-group>
|
||||||
<mat-tab label="Markdown text">
|
<mat-tab label="Markdown text | Result">
|
||||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
<mat-tab label="Editor">
|
||||||
|
<div class="markdown-editor-container">
|
||||||
|
<div class="markdown-editor">
|
||||||
|
<form novalidate>
|
||||||
|
<angular-markdown-editor
|
||||||
|
textareaId="editor1"
|
||||||
|
[options]="editorOptions"
|
||||||
|
name="markdownText"
|
||||||
|
[(ngModel)]="tutorial.description"
|
||||||
|
(onFullscreenExit)="hidePreview()"
|
||||||
|
>
|
||||||
|
</angular-markdown-editor>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
<mat-tab label="Result">
|
<mat-tab label="Result">
|
||||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
@@ -21,6 +63,38 @@
|
|||||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="example-full-width">
|
||||||
|
<mat-label class="label-style">Body</mat-label>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Markdown text | Result">
|
||||||
|
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||||
|
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Editor">
|
||||||
|
<form novalidate>
|
||||||
|
<angular-markdown-editor style="color: #1a1a1a"
|
||||||
|
textareaId="editor2"
|
||||||
|
[options]="editorOptions"
|
||||||
|
name="markdownText"
|
||||||
|
[(ngModel)]="tutorial.body"
|
||||||
|
(onFullscreenExit)="hidePreview()"
|
||||||
|
>
|
||||||
|
</angular-markdown-editor>
|
||||||
|
</form>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Result">
|
||||||
|
<markdown class="preview" [data]="tutorial.body"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Example">
|
||||||
|
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||||
|
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
|
|
||||||
|
|||||||
+133
-6
@@ -1,13 +1,18 @@
|
|||||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||||
import {MarkdownComponent} from 'ngx-markdown';
|
import {MarkdownComponent, MarkdownService} from 'ngx-markdown';
|
||||||
import {MatButton} from '@angular/material/button';
|
import {MatButton} from '@angular/material/button';
|
||||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||||
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
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 {Tutorial} from '../../../models/tutorial.model';
|
||||||
import {UserApiService} from '../user-api.service';
|
import {UserApiService} from '../user-api.service';
|
||||||
import {ActivatedRoute, RouterLink} from '@angular/router';
|
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({
|
@Component({
|
||||||
selector: 'app-tutorial-edit.component',
|
selector: 'app-tutorial-edit.component',
|
||||||
@@ -27,17 +32,28 @@ import {ActivatedRoute, RouterLink} from '@angular/router';
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
MatFormField,
|
MatFormField,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
MatError
|
MatError,
|
||||||
|
AngularMarkdownEditorModule,
|
||||||
|
MatDivider
|
||||||
],
|
],
|
||||||
templateUrl: './tutorial-edit.component.html',
|
templateUrl: './tutorial-edit.component.html',
|
||||||
styleUrl: './tutorial-edit.component.scss',
|
styleUrl: './tutorial-edit.component.scss',
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class TutorialEditComponent {
|
export class TutorialEditComponent implements OnInit{
|
||||||
|
|
||||||
tutorial: Tutorial = new Tutorial();
|
tutorial: Tutorial = new Tutorial();
|
||||||
submitted = false;
|
submitted = false;
|
||||||
hasError = false;
|
hasError = false;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
markdownText="";
|
||||||
|
bsEditorInstance!: EditorInstance;
|
||||||
|
tutorialForm!: FormGroup;
|
||||||
|
editorOptions!: EditorOption;
|
||||||
|
|
||||||
|
readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
|
|
||||||
markdown = `## Markdown __rulez__!
|
markdown = `## Markdown __rulez__!
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -56,7 +72,11 @@ const language = 'typescript';
|
|||||||
> Blockquote to the max`;
|
> Blockquote to the max`;
|
||||||
private id: string | null | undefined;
|
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
|
this.route.queryParams
|
||||||
.subscribe(params => {
|
.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 {
|
updateTutorial(): void {
|
||||||
this.userApiService.update(this.id, this.tutorial)
|
this.userApiService.update(this.id, this.tutorial)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@@ -87,4 +163,55 @@ const language = 'typescript';
|
|||||||
this.hasError = true;
|
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;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.jambotronGroup.jambotron.model.User;
|
|||||||
import com.jambotronGroup.jambotron.payload.request.SaveZhipuAiImageRequest;
|
import com.jambotronGroup.jambotron.payload.request.SaveZhipuAiImageRequest;
|
||||||
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
||||||
import com.jambotronGroup.jambotron.system.SystemServiceImpl;
|
import com.jambotronGroup.jambotron.system.SystemServiceImpl;
|
||||||
|
import com.jambotronGroup.jambotron.utils.FilesRoutingHelper;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -40,22 +41,6 @@ public class FilesController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
FilesStorageService storageService;
|
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")
|
@PostMapping("/api/user/saveZhipuAiImage")
|
||||||
public ResponseEntity<ResponseMessage> saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) {
|
public ResponseEntity<ResponseMessage> saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) {
|
||||||
@@ -117,7 +102,7 @@ public class FilesController {
|
|||||||
|
|
||||||
FileInfo fileInfo = new FileInfo(
|
FileInfo fileInfo = new FileInfo(
|
||||||
file.getOriginalFilename(),
|
file.getOriginalFilename(),
|
||||||
FilesController.getUserImageUrl(localFullFileName));
|
FilesRoutingHelper.getUserImageUrl(localFullFileName));
|
||||||
|
|
||||||
|
|
||||||
message = "Uploaded the file successfully: " + file.getOriginalFilename();
|
message = "Uploaded the file successfully: " + file.getOriginalFilename();
|
||||||
@@ -136,7 +121,7 @@ public class FilesController {
|
|||||||
List<FileInfo> fileInfos = storageService.loadUserImages(user.getId().toString()).map(path -> {
|
List<FileInfo> fileInfos = storageService.loadUserImages(user.getId().toString()).map(path -> {
|
||||||
String filename = path.getFileName().toString();
|
String filename = path.getFileName().toString();
|
||||||
|
|
||||||
String url = FilesController.getUserImageUrl(filename);
|
String url = FilesRoutingHelper.getUserImageUrl(filename);
|
||||||
|
|
||||||
return new FileInfo(filename, url);
|
return new FileInfo(filename, url);
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
@@ -149,7 +134,7 @@ public class FilesController {
|
|||||||
public ResponseEntity<List<FileInfo>> getListFiles() {
|
public ResponseEntity<List<FileInfo>> getListFiles() {
|
||||||
List<FileInfo> fileInfos = storageService.loadAll().map(path -> {
|
List<FileInfo> fileInfos = storageService.loadAll().map(path -> {
|
||||||
String filename = path.getFileName().toString();
|
String filename = path.getFileName().toString();
|
||||||
String url = FilesController.getPublicImageUrl(filename);
|
String url = FilesRoutingHelper.getPublicImageUrl(filename);
|
||||||
return new FileInfo(filename, url);
|
return new FileInfo(filename, url);
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.jambotronGroup.jambotron.repository.TutorialRepository;
|
|||||||
import com.jambotronGroup.jambotron.repository.UserRepository;
|
import com.jambotronGroup.jambotron.repository.UserRepository;
|
||||||
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
|
||||||
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
|
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
|
||||||
|
import com.jambotronGroup.jambotron.utils.FilesRoutingHelper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -118,8 +119,7 @@ public class TutorialController {
|
|||||||
String.format("Tutorial_%s",newFilename )
|
String.format("Tutorial_%s",newFilename )
|
||||||
);
|
);
|
||||||
|
|
||||||
String url = MvcUriComponentsBuilder
|
String url = FilesRoutingHelper.getPublicImageUrl(path.getFileName().toString());
|
||||||
.fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString();
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -160,7 +160,12 @@ public class TutorialController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("user/tutorial-update/{id}")
|
@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<Tutorial> tutorialData = tutorialRepository.findById(id);
|
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
|
||||||
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
Map<String, Object> map = new LinkedHashMap<String, Object>();
|
||||||
if (tutorialData.isPresent()) {
|
if (tutorialData.isPresent()) {
|
||||||
@@ -169,7 +174,22 @@ public class TutorialController {
|
|||||||
servTutorial.setDescription(tutorial.getDescription());
|
servTutorial.setDescription(tutorial.getDescription());
|
||||||
servTutorial.setPublished(tutorial.isPublished());
|
servTutorial.setPublished(tutorial.isPublished());
|
||||||
servTutorial.setTobepublished(tutorial.isTobepublished());
|
servTutorial.setTobepublished(tutorial.isTobepublished());
|
||||||
|
|
||||||
try {
|
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);
|
servTutorial = tutorialRepository.save(servTutorial);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,6 @@ public interface FilesStorageService {
|
|||||||
public Stream<Path> loadUserImages(String userID);
|
public Stream<Path> loadUserImages(String userID);
|
||||||
|
|
||||||
public Path moveFile(String userID, String url, String newFilename) throws Exception;
|
public Path moveFile(String userID, String url, String newFilename) throws Exception;
|
||||||
|
|
||||||
|
public void deletePublicFile(String filename);
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,16 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
|||||||
return path.substring(path.lastIndexOf('/') + 1); // Extract the file name
|
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
|
@Override
|
||||||
public Path moveFile(String userID, String url, String newFilename) throws Exception {
|
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);
|
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.
|
* Saves a file to a user's directory.
|
||||||
* uploads/user-images/{userID}/{filename}
|
* uploads/user-images/{userID}/{filename}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user