Merge pull request #62 from liosha84/57-fix-select-image-functionalities-in-add-tutorial-form-and-fix-edit-tutorial-functionalities
57 fix select image functionalities in add tutorial form and fix edit tutorial functionalities
This commit is contained in:
@@ -24,8 +24,6 @@
|
|||||||
"@angular/platform-server": "^20.0.0",
|
"@angular/platform-server": "^20.0.0",
|
||||||
"@angular/router": "^20.0.0",
|
"@angular/router": "^20.0.0",
|
||||||
"@angular/ssr": "^20.0.2",
|
"@angular/ssr": "^20.0.2",
|
||||||
"@ant-design/icons-angular": "19.0.0",
|
|
||||||
"@hreimer/angular-image-viewer": "^0.14.1",
|
|
||||||
"@ng-bootstrap/ng-bootstrap": "19.0.0",
|
"@ng-bootstrap/ng-bootstrap": "19.0.0",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@primeng/themes": "^19.1.3",
|
"@primeng/themes": "^19.1.3",
|
||||||
@@ -33,11 +31,7 @@
|
|||||||
"bootstrap": "5.3.6",
|
"bootstrap": "5.3.6",
|
||||||
"cropperjs": "^2.0.1",
|
"cropperjs": "^2.0.1",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"file-saver": "^2.0.5",
|
|
||||||
"ngx-filesaver": "^20.0.0",
|
|
||||||
"ngx-markdown": "^20.0.0",
|
"ngx-markdown": "^20.0.0",
|
||||||
"ngx-scrollbar": "18.0.0",
|
|
||||||
"primeng": "^19.1.3",
|
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {AppRoutingModule} from './app.routes';
|
|||||||
import {App} from './app';
|
import {App} from './app';
|
||||||
import {CustomHttpInterceptor} from './helpers/custom-http-interceptor';
|
import {CustomHttpInterceptor} from './helpers/custom-http-interceptor';
|
||||||
import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -33,7 +34,8 @@ import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
|||||||
AdminWelcomeComponent,
|
AdminWelcomeComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
SystemComponent,
|
SystemComponent,
|
||||||
AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' })
|
AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' }),
|
||||||
|
MatFormFieldModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{
|
authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<div>
|
||||||
|
<mat-toolbar>
|
||||||
|
<input matInput [value]="fileName" />
|
||||||
|
<button
|
||||||
|
mat-button
|
||||||
|
color="primary"
|
||||||
|
[disabled]="!currentFile"
|
||||||
|
(click)="upload()"
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</button>
|
||||||
|
</mat-toolbar>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="fileInput"
|
||||||
|
(change)="selectFile($event)"
|
||||||
|
name="fileInput"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (progress) {
|
||||||
|
<mat-toolbar class="progress-bar">
|
||||||
|
<mat-progress-bar color="accent" [value]="progress"></mat-progress-bar>
|
||||||
|
<span class="progress">{{ progress }}%</span>
|
||||||
|
</mat-toolbar>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (message) {
|
||||||
|
<div class="message">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (fileInfo){
|
||||||
|
<img src="{{fileInfo.url}}" alt=""/>
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
.progress-bar {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fileInput {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
opacity: 0;
|
||||||
|
height: 100%;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-toolbar-single-row {
|
||||||
|
height: auto !important;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-toolbar-single-row button {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
background-color: #ddd;
|
||||||
|
padding: 15px;
|
||||||
|
color: #333;
|
||||||
|
border: #aaa solid 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
img{
|
||||||
|
max-width: -webkit-fill-available;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FileUploadComponent } from './file-upload.component';
|
||||||
|
|
||||||
|
describe('FileUploadComponent', () => {
|
||||||
|
let component: FileUploadComponent;
|
||||||
|
let fixture: ComponentFixture<FileUploadComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [FileUploadComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FileUploadComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
|
||||||
|
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||||
|
import {MatList, MatListItem} from '@angular/material/list';
|
||||||
|
import {AsyncPipe} from '@angular/common';
|
||||||
|
import {MatToolbar} from '@angular/material/toolbar';
|
||||||
|
import {MatProgressBar} from '@angular/material/progress-bar';
|
||||||
|
import {MatFormField, MatInput} from '@angular/material/input';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {HttpEventType, HttpResponse} from '@angular/common/http';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {UserApiService} from '../../modules/user-module/user-api.service';
|
||||||
|
import {FileInfo} from '../../models/file-info';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-file-upload',
|
||||||
|
imports: [
|
||||||
|
MatToolbar,
|
||||||
|
MatProgressBar,
|
||||||
|
MatFormField,
|
||||||
|
MatButton,
|
||||||
|
MatInput
|
||||||
|
],
|
||||||
|
templateUrl: './file-upload.component.html',
|
||||||
|
styleUrl: './file-upload.component.scss'
|
||||||
|
})
|
||||||
|
export class FileUploadComponent {
|
||||||
|
currentFile?: File;
|
||||||
|
progress = 0;
|
||||||
|
message = '';
|
||||||
|
|
||||||
|
fileName = 'Select File';
|
||||||
|
|
||||||
|
@Output() fileInfo: FileInfo | undefined;
|
||||||
|
@Output() onImageUploaded = new EventEmitter<FileInfo>();
|
||||||
|
constructor(private userApiService: UserApiService) {}
|
||||||
|
|
||||||
|
selectFile(event: any): void {
|
||||||
|
this.progress = 0;
|
||||||
|
this.message = '';
|
||||||
|
|
||||||
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
const file: File = event.target.files[0];
|
||||||
|
this.currentFile = file;
|
||||||
|
this.fileName = this.currentFile.name;
|
||||||
|
} else {
|
||||||
|
this.fileName = 'Select File';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upload(): void {
|
||||||
|
if (this.currentFile) {
|
||||||
|
this.userApiService.upload(this.currentFile).subscribe({
|
||||||
|
next: (event: any) => {
|
||||||
|
if (event.type === HttpEventType.UploadProgress) {
|
||||||
|
this.progress = Math.round((100 * event.loaded) / event.total);
|
||||||
|
} else if (event instanceof HttpResponse) {
|
||||||
|
this.message = event.body.message;
|
||||||
|
this.fileInfo =event.body.fileInfo;
|
||||||
|
this.onImageUploaded.emit(this.fileInfo);
|
||||||
|
console.log(event.body);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
this.progress = 0;
|
||||||
|
|
||||||
|
if (err.error && err.error.message) {
|
||||||
|
this.message = err.error.message;
|
||||||
|
} else {
|
||||||
|
this.message = 'Could not upload the file!';
|
||||||
|
this.onImageUploaded.emit(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.currentFile = undefined;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,9 +19,9 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
|
|
||||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
req = req.clone({
|
// req = req.clone({
|
||||||
withCredentials: true,
|
// withCredentials: true,
|
||||||
});
|
// });
|
||||||
|
|
||||||
return next.handle(req).pipe(
|
return next.handle(req).pipe(
|
||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
|
import {HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
|
||||||
import {Observable, tap} from 'rxjs';
|
import {Observable, tap} from 'rxjs';
|
||||||
import {SpinnerService} from '../services/spinner.service';
|
import {SpinnerService} from '../services/spinner.service';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
@@ -19,6 +19,7 @@ export class CustomHttpInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
this.spinnerService.hide();
|
this.spinnerService.hide();
|
||||||
}));
|
}))
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-36
@@ -1,33 +1,23 @@
|
|||||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, inject, Renderer2} from '@angular/core';
|
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core';
|
||||||
import {AsyncPipe, DOCUMENT, NgOptimizedImage} from "@angular/common";
|
import {AsyncPipe} from "@angular/common";
|
||||||
import {FormsModule} from "@angular/forms";
|
import {FormsModule} from "@angular/forms";
|
||||||
import {MatButton, MatMiniFabButton} from "@angular/material/button";
|
import {MatButton, MatMiniFabButton} from "@angular/material/button";
|
||||||
import {
|
import {
|
||||||
MatCard,
|
MatCard,
|
||||||
MatCardActions,
|
MatCardActions,
|
||||||
MatCardContent,
|
MatCardContent,
|
||||||
MatCardHeader,
|
MatCardHeader
|
||||||
MatCardImage,
|
|
||||||
MatCardXlImage
|
|
||||||
} from "@angular/material/card";
|
} from "@angular/material/card";
|
||||||
import {MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
import {MatFormField, MatInput, MatLabel} from "@angular/material/input";
|
||||||
import {Image} from '../../../models/image';
|
import {Image} from '../../../models/image';
|
||||||
import {ZhipuaiImageService} from '../zhipuai-image.service';
|
import {ZhipuaiImageService} from '../zhipuai-image.service';
|
||||||
import {SpinnerService} from '../../../services/spinner.service';
|
import {SpinnerService} from '../../../services/spinner.service';
|
||||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
import {FileUploadService} from '../../../services/file-upload-api.service';
|
|
||||||
import {Observable, Subscription} from 'rxjs';
|
|
||||||
import {HttpClient, HttpResponse} from '@angular/common/http';
|
|
||||||
import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar';
|
import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar';
|
||||||
import {MatIcon} from '@angular/material/icon';
|
import {MatIcon} from '@angular/material/icon';
|
||||||
import {MatTooltip} from '@angular/material/tooltip';
|
import {MatTooltip} from '@angular/material/tooltip';
|
||||||
import {AuthService} from '../../../services/auth.service';
|
|
||||||
import {TokenStorageService} from '../../../services/token-storage.service';
|
import {TokenStorageService} from '../../../services/token-storage.service';
|
||||||
import {FileInfo} from '../../../models/file-info';
|
|
||||||
import {UserApiService} from '../../user-module/user-api.service';
|
import {UserApiService} from '../../user-module/user-api.service';
|
||||||
import {MatProgressBar} from '@angular/material/progress-bar';
|
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-generate-image.component',
|
selector: 'app-generate-image.component',
|
||||||
@@ -43,26 +33,17 @@ import {MatProgressBar} from '@angular/material/progress-bar';
|
|||||||
MatInput,
|
MatInput,
|
||||||
MatLabel,
|
MatLabel,
|
||||||
MatProgressSpinner,
|
MatProgressSpinner,
|
||||||
NgOptimizedImage,
|
|
||||||
MatCardXlImage,
|
|
||||||
MatCardImage,
|
|
||||||
MatToolbarRow,
|
MatToolbarRow,
|
||||||
MatToolbar,
|
MatToolbar,
|
||||||
MatIcon,
|
MatIcon,
|
||||||
MatMiniFabButton,
|
MatMiniFabButton,
|
||||||
MatTooltip,
|
MatTooltip,
|
||||||
MatProgressBar
|
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
templateUrl: './generate-image.component.html',
|
templateUrl: './generate-image.component.html',
|
||||||
styleUrl: './generate-image.component.scss',
|
styleUrl: './generate-image.component.scss',
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class GenerateImageComponent {
|
export class GenerateImageComponent {
|
||||||
|
|
||||||
isSpinnerVisible = false;
|
|
||||||
|
|
||||||
query : string = '';
|
query : string = '';
|
||||||
|
|
||||||
images: Image[] = [];
|
images: Image[] = [];
|
||||||
@@ -70,10 +51,6 @@ export class GenerateImageComponent {
|
|||||||
url: ''
|
url: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
currentFile?: File;
|
|
||||||
message = '';
|
|
||||||
|
|
||||||
|
|
||||||
isLoggedIn = false;
|
isLoggedIn = false;
|
||||||
|
|
||||||
private storageService: TokenStorageService = inject(TokenStorageService);
|
private storageService: TokenStorageService = inject(TokenStorageService);
|
||||||
@@ -81,13 +58,8 @@ export class GenerateImageComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private zhipuaiImageService: ZhipuaiImageService,
|
private zhipuaiImageService: ZhipuaiImageService,
|
||||||
public spinnerService: SpinnerService,
|
public spinnerService: SpinnerService,
|
||||||
private uploadService: FileUploadService,
|
private userApiService:UserApiService
|
||||||
private userApiService:UserApiService,
|
|
||||||
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
||||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +80,6 @@ export class GenerateImageComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
save(url: string | undefined) {
|
save(url: string | undefined) {
|
||||||
let requestUrl:string = url?url:"";
|
let requestUrl:string = url?url:"";
|
||||||
console.log(requestUrl);
|
console.log(requestUrl);
|
||||||
@@ -118,9 +89,6 @@ export class GenerateImageComponent {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { NgModule } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import {tutorialsRouting} from './tutorials.routing';
|
import {tutorialsRouting} from './tutorials.routing';
|
||||||
import {TutorialsComponent} from './tutorials.component/tutorials.component';
|
import {TutorialsComponent} from './tutorials.component/tutorials.component';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
imports: [
|
imports: [
|
||||||
tutorialsRouting,
|
tutorialsRouting,
|
||||||
TutorialsComponent,
|
TutorialsComponent,
|
||||||
CommonModule
|
CommonModule,
|
||||||
|
MatFormFieldModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class TutorialsModule { }
|
export class TutorialsModule { }
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
<h1 mat-dialog-title>Hi </h1>
|
<h1 mat-dialog-title>Hi </h1>
|
||||||
<div mat-dialog-content >
|
<!--<div mat-dialog-content >-->
|
||||||
<mat-dialog-content class="mat-typography">
|
<mat-dialog-content class="mat-typography">
|
||||||
<!-- <mat-nav-list>-->
|
<!-- <mat-nav-list>-->
|
||||||
@for (fileInfo of fileInfos; track fileInfo){
|
@for (fileInfo of fileInfos; track fileInfo){
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<!-- </mat-nav-list>-->
|
<!-- </mat-nav-list>-->
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
</div>
|
<!--</div>-->
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
<button mat-button (click)="upload()">Upload</button>
|
<button mat-button (click)="upload()">Upload</button>
|
||||||
<span class="menu-spacer"></span>
|
<span class="menu-spacer"></span>
|
||||||
|
|||||||
+20
-5
@@ -2,20 +2,30 @@
|
|||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-dialog-content{
|
.mat-mdc-list-item{
|
||||||
|
|
||||||
|
}
|
||||||
|
a.mdc-list-item
|
||||||
|
{
|
||||||
|
cursor: grab;
|
||||||
|
height: 120px;
|
||||||
|
padding-bottom: 66px;
|
||||||
|
background-color: rgba(24,255,255,0.04);
|
||||||
|
}
|
||||||
|
/*.mat-dialog-content{
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
|
||||||
height: 75%;
|
height: 75%;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
}
|
}*/
|
||||||
.mdc-dialog--open .mat-mdc-dialog-inner-container
|
/*.mdc-dialog--open .mat-mdc-dialog-inner-container
|
||||||
{
|
{
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
.mat-mdc-dialog-container {
|
/*.mat-mdc-dialog-container {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -25,4 +35,9 @@
|
|||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
max-width: inherit;
|
max-width: inherit;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
}*/
|
||||||
|
/*
|
||||||
|
.mat-mdc-dialog-content{
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
+5
-8
@@ -7,8 +7,7 @@ import {
|
|||||||
MatDialogTitle
|
MatDialogTitle
|
||||||
} from '@angular/material/dialog';
|
} from '@angular/material/dialog';
|
||||||
import {MatButton} from '@angular/material/button';
|
import {MatButton} from '@angular/material/button';
|
||||||
import {MatListItem, MatNavList} from '@angular/material/list';
|
import {MatListItem} from '@angular/material/list';
|
||||||
import {RouterLink} from '@angular/router';
|
|
||||||
import {FileInfo} from '../../../models/file-info';
|
import {FileInfo} from '../../../models/file-info';
|
||||||
import {UserApiService} from '../user-api.service';
|
import {UserApiService} from '../user-api.service';
|
||||||
|
|
||||||
@@ -20,10 +19,7 @@ import {UserApiService} from '../user-api.service';
|
|||||||
MatButton,
|
MatButton,
|
||||||
MatDialogActions,
|
MatDialogActions,
|
||||||
MatDialogClose,
|
MatDialogClose,
|
||||||
|
MatListItem
|
||||||
MatListItem,
|
|
||||||
MatNavList,
|
|
||||||
|
|
||||||
],
|
],
|
||||||
templateUrl: './dialog-select-image.component.html',
|
templateUrl: './dialog-select-image.component.html',
|
||||||
styleUrl: './dialog-select-image.component.scss'
|
styleUrl: './dialog-select-image.component.scss'
|
||||||
@@ -42,10 +38,10 @@ export class DialogSelectImageComponent {
|
|||||||
console.log(data);
|
console.log(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//open upload dialog
|
||||||
upload() {
|
upload() {
|
||||||
|
|
||||||
this.uploadClicked.emit();
|
this.uploadClicked.emit();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onNoClick() {
|
onNoClick() {
|
||||||
@@ -54,5 +50,6 @@ export class DialogSelectImageComponent {
|
|||||||
|
|
||||||
select(fileInfo:FileInfo) {
|
select(fileInfo:FileInfo) {
|
||||||
this.selectClicked.emit(fileInfo);
|
this.selectClicked.emit(fileInfo);
|
||||||
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<p>dialog-upload-image.component works!</p>
|
||||||
|
<mat-dialog-content class="mat-typography">
|
||||||
|
|
||||||
|
<app-file-upload #imageUpload (onImageUploaded)="imageUpload_ImageUploaded($event)"></app-file-upload>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
|
||||||
|
<button mat-button (click)="openSelectImageDialog()">Select image</button>
|
||||||
|
<span class="menu-spacer"></span>
|
||||||
|
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
|
||||||
|
<!-- <button mat-button cdkFocusInitial (click)="select()">Ok</button>-->
|
||||||
|
|
||||||
|
<button mat-button cdkFocusInitial [disabled]="!imageUploaded" (click)="selectUploadedImage()">Ok</button>
|
||||||
|
</div>
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
.menu-spacer {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DialogUploadImageComponent } from './dialog-upload-image.component';
|
||||||
|
|
||||||
|
describe('DialogUploadImageComponent', () => {
|
||||||
|
let component: DialogUploadImageComponent;
|
||||||
|
let fixture: ComponentFixture<DialogUploadImageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [DialogUploadImageComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DialogUploadImageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import {Component, EventEmitter, inject, Output, ViewChild} from '@angular/core';
|
||||||
|
import {FileUploadComponent} from '../../../components/file-upload.component/file-upload.component';
|
||||||
|
import {MatDialogActions, MatDialogClose, MatDialogContent, MatDialogRef} from '@angular/material/dialog';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {FileInfo} from '../../../models/file-info';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dialog-upload-image.component',
|
||||||
|
imports: [
|
||||||
|
FileUploadComponent,
|
||||||
|
MatDialogContent,
|
||||||
|
MatButton,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogClose
|
||||||
|
],
|
||||||
|
templateUrl: './dialog-upload-image.component.html',
|
||||||
|
styleUrl: './dialog-upload-image.component.scss'
|
||||||
|
})
|
||||||
|
export class DialogUploadImageComponent {
|
||||||
|
//select uploaded file
|
||||||
|
@Output() selectUploadedImageClicked = new EventEmitter<any>();
|
||||||
|
|
||||||
|
//open select dialog
|
||||||
|
@Output() openSelectDialogClicked = new EventEmitter<any>();
|
||||||
|
|
||||||
|
readonly dialogRef = inject(MatDialogRef<DialogUploadImageComponent>);
|
||||||
|
|
||||||
|
@ViewChild('imageUpload') imageUpload: FileUploadComponent | undefined;
|
||||||
|
|
||||||
|
imageUploaded:boolean = false;
|
||||||
|
|
||||||
|
onNoClick() {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUploadedImage() {
|
||||||
|
this.selectUploadedImageClicked.emit(this.imageUpload?.fileInfo);
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
openSelectImageDialog() {
|
||||||
|
this.openSelectDialogClicked.emit();
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
imageUpload_ImageUploaded($event: FileInfo | undefined) {
|
||||||
|
if($event){
|
||||||
|
this.imageUploaded = true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.imageUploaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
-45
@@ -4,38 +4,55 @@
|
|||||||
</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>
|
|
||||||
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
|
||||||
<input matInput
|
|
||||||
[(ngModel)]="tutorial.titleimage">
|
|
||||||
<button mat-raised-button color="primary" (click)="openSelectImageDialog('100ms', '5ms')">Select image</button>
|
|
||||||
|
|
||||||
</mat-form-field>
|
|
||||||
<div class="example-full-width">
|
|
||||||
<mat-label>Description</mat-label>
|
|
||||||
<mat-tab-group>
|
|
||||||
<mat-tab label="Markdown text">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
<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>
|
</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 label="Markdown text | Result">
|
||||||
|
<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 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>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="Result">
|
<mat-tab label="Result">
|
||||||
@@ -47,26 +64,26 @@
|
|||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="example-full-width">
|
|
||||||
<mat-label>Body</mat-label>
|
|
||||||
<mat-tab-group>
|
|
||||||
<mat-tab label="Markdown text">
|
|
||||||
<div class="markdown-editor-container">
|
|
||||||
<div class="markdown-editor">
|
|
||||||
<form novalidate>
|
|
||||||
<angular-markdown-editor
|
|
||||||
textareaId="editor2"
|
|
||||||
[options]="editorOptions"
|
|
||||||
name="markdownText"
|
|
||||||
[(ngModel)]="tutorial.body"
|
|
||||||
(onFullscreenExit)="hidePreview()"
|
|
||||||
>
|
|
||||||
</angular-markdown-editor>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<markdown class="variable-binding" [data]="tutorial.body"></markdown>
|
|
||||||
</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>
|
||||||
<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));
|
||||||
|
}
|
||||||
|
|||||||
+36
-12
@@ -12,10 +12,11 @@ import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
|||||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||||
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor';
|
import {AngularMarkdownEditorModule, EditorInstance, EditorOption} from 'angular-markdown-editor';
|
||||||
import {DialogLoginComponent} from '../../main-module/main.component/dialog-login.component/dialog-login.component';
|
|
||||||
import {GlobalConstants} from '../../../global-constants';
|
|
||||||
import {MatDialog} from '@angular/material/dialog';
|
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 {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 {DialogSelectImageComponent} from '../dialog-select-image.component/dialo
|
|||||||
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,14 +45,16 @@ 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;
|
||||||
|
|
||||||
readonly dialog = inject(MatDialog);
|
readonly dialog = inject(MatDialog);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
markdown = `## Markdown __rulez__!
|
markdown = `## Markdown __rulez__!
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -72,10 +76,6 @@ const language = 'typescript';
|
|||||||
constructor(private fb: FormBuilder,
|
constructor(private fb: FormBuilder,
|
||||||
private markdownService: MarkdownService,
|
private markdownService: MarkdownService,
|
||||||
private userApiService: UserApiService,
|
private userApiService: UserApiService,
|
||||||
private storageService: TokenStorageService,
|
|
||||||
private eventBusService: EventBusService,
|
|
||||||
private router: Router,
|
|
||||||
private authService: AuthService
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.tutorial.description = this.markdown;
|
this.tutorial.description = this.markdown;
|
||||||
@@ -86,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,
|
||||||
@@ -101,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(() => {
|
||||||
@@ -185,7 +184,7 @@ const language = 'typescript';
|
|||||||
});
|
});
|
||||||
dialogSelectRef.componentInstance.uploadClicked.subscribe(result => {
|
dialogSelectRef.componentInstance.uploadClicked.subscribe(result => {
|
||||||
dialogSelectRef.close();
|
dialogSelectRef.close();
|
||||||
//this.openSignupDialog(enterAnimationDuration, exitAnimationDuration);
|
this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration);
|
||||||
})
|
})
|
||||||
|
|
||||||
const dialogSelectSubscription = dialogSelectRef.componentInstance.selectClicked
|
const dialogSelectSubscription = dialogSelectRef.componentInstance.selectClicked
|
||||||
@@ -199,4 +198,29 @@ const language = 'typescript';
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+88
-14
@@ -4,23 +4,97 @@
|
|||||||
</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-tab-group>
|
|
||||||
<mat-tab label="Markdown text">
|
<mat-label class="label-style">Title image</mat-label>
|
||||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
<mat-divider></mat-divider>
|
||||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
<table class="example-full-width">
|
||||||
</mat-tab>
|
<tr>
|
||||||
<mat-tab label="Result">
|
<td style="width: 120px;">
|
||||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
<img style="width: 120px; height: 120px" src="{{tutorial.titleimage}}" alt="">
|
||||||
</mat-tab>
|
</td>
|
||||||
<mat-tab label="Example">
|
<td>
|
||||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
<mat-form-field class="example-full-width">
|
||||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
<mat-label>Title image</mat-label>
|
||||||
</mat-tab>
|
<input matInput disabled
|
||||||
</mat-tab-group>
|
[(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 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">
|
||||||
|
<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">
|
||||||
|
<markdown class="preview" [data]="tutorial.description"></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>
|
||||||
|
|
||||||
|
<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;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {forkJoin, Observable} from 'rxjs';
|
import {forkJoin, Observable} from 'rxjs';
|
||||||
import {Tutorial} from '../../models/tutorial.model';
|
import {Tutorial} from '../../models/tutorial.model';
|
||||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
import {HttpClient, HttpEvent, HttpHeaders, HttpRequest} from '@angular/common/http';
|
||||||
import {GlobalConstants} from '../../global-constants';
|
import {GlobalConstants} from '../../global-constants';
|
||||||
import {FileInfo} from '../../models/file-info';
|
import {FileInfo} from '../../models/file-info';
|
||||||
|
|
||||||
@@ -15,6 +15,19 @@ export class UserApiService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upload(file: File): Observable<HttpEvent<any>> {
|
||||||
|
const formData: FormData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, {
|
||||||
|
reportProgress: true,
|
||||||
|
responseType: 'json'
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.request(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
saveZhipuaiImage(url: string): Observable<any> {
|
saveZhipuaiImage(url: string): Observable<any> {
|
||||||
return this.http.post(`${this.baseUrl}/saveZhipuAiImage`, {imageUrl:url});
|
return this.http.post(`${this.baseUrl}/saveZhipuAiImage`, {imageUrl:url});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import {userRouting} from './user.routing';
|
import {userRouting} from './user.routing';
|
||||||
import {UserComponent} from './user.component/user.component';
|
import {UserComponent} from './user.component/user.component';
|
||||||
import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -12,7 +13,8 @@ import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
|||||||
userRouting,
|
userRouting,
|
||||||
UserComponent,
|
UserComponent,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
AngularMarkdownEditorModule
|
AngularMarkdownEditorModule,
|
||||||
|
MatFormFieldModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class UserModule { }
|
export class UserModule { }
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { FileUploadApiService } from './file-upload-api.service';
|
|
||||||
|
|
||||||
describe('FileUploadApiService', () => {
|
|
||||||
let service: FileUploadApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
service = TestBed.inject(FileUploadApiService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {GlobalConstants} from '../global-constants';
|
|
||||||
import {HttpClient, HttpEvent, HttpRequest} from '@angular/common/http';
|
|
||||||
import {Observable} from 'rxjs';
|
|
||||||
import {FileInfo} from '../models/file-info';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class FileUploadService {
|
|
||||||
private baseUrl = GlobalConstants.API_URL + '/file';
|
|
||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
|
||||||
|
|
||||||
upload(file: File): Observable<HttpEvent<any>> {
|
|
||||||
const formData: FormData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
|
|
||||||
const req = new HttpRequest('POST', `${this.baseUrl}/upload`, formData, {
|
|
||||||
responseType: 'json',
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.http.request(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFiles(): Observable<FileInfo[]> {
|
|
||||||
return this.http.get<FileInfo[]>(`${this.baseUrl}/files`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,15 +5,9 @@ import com.jambotronGroup.jambotron.fileUpload.FilesStorageService;
|
|||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.slf4j.Marker;
|
|
||||||
import org.slf4j.event.Level;
|
|
||||||
import org.slf4j.helpers.BasicMarker;
|
|
||||||
import org.springframework.boot.CommandLineRunner;
|
import org.springframework.boot.CommandLineRunner;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class JambotronApplication implements CommandLineRunner {
|
public class JambotronApplication implements CommandLineRunner {
|
||||||
@@ -35,4 +29,5 @@ public class JambotronApplication implements CommandLineRunner {
|
|||||||
// storageService.deleteAll();
|
// storageService.deleteAll();
|
||||||
storageService.init();
|
storageService.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package com.jambotronGroup.jambotron.configuretions;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
/*
|
|
||||||
@Configuration
|
|
||||||
|
|
||||||
@EnableWebMvc
|
|
||||||
public class MvcConfig implements WebMvcConfigurer {
|
|
||||||
@Value("${spring.resources.static-locations}")
|
|
||||||
String resourceLocations;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
|
||||||
//registry.addResourceHandler("/public/**").addResourceLocations(resourceLocations);
|
|
||||||
registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/");
|
|
||||||
|
|
||||||
//registry.
|
|
||||||
registry.addResourceHandler("/media/**").addResourceLocations("resources/main/public/media/");
|
|
||||||
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.jambotronGroup.jambotron.configuretions;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.util.ResourceUtils;
|
|
||||||
import org.springframework.util.StreamUtils;
|
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
/*
|
|
||||||
@ControllerAdvice
|
|
||||||
public class NotFoundHandler {
|
|
||||||
@Value("${spa.default-file}")
|
|
||||||
String defaultFile;
|
|
||||||
|
|
||||||
@ExceptionHandler(NoHandlerFoundException.class)
|
|
||||||
public ResponseEntity<String> renderDefaultPage() {
|
|
||||||
try {
|
|
||||||
File indexFile = ResourceUtils.getFile(defaultFile);
|
|
||||||
FileInputStream inputStream = new FileInputStream(indexFile);
|
|
||||||
String body = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
|
|
||||||
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(body);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("There was an error completing the action.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package com.jambotronGroup.jambotron.controllers;
|
package com.jambotronGroup.jambotron.controllers;
|
||||||
|
|
||||||
import com.jambotronGroup.jambotron.fileUpload.FilesStorageService;
|
import com.jambotronGroup.jambotron.fileUpload.FilesStorageService;
|
||||||
|
import com.jambotronGroup.jambotron.fileUpload.ResponseImageUploadResult;
|
||||||
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
|
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
|
||||||
import com.jambotronGroup.jambotron.model.FileInfo;
|
import com.jambotronGroup.jambotron.model.FileInfo;
|
||||||
import com.jambotronGroup.jambotron.model.User;
|
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;
|
||||||
@@ -30,17 +32,16 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
//@CrossOrigin("http://localhost:8081")
|
|
||||||
public class FilesController {
|
public class FilesController {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FilesController.class);
|
private static final Logger logger = LoggerFactory.getLogger(FilesController.class);
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
AuthenticationFacade authenticationFacade;
|
AuthenticationFacade authenticationFacade;
|
||||||
@Autowired
|
@Autowired
|
||||||
FilesStorageService storageService;
|
FilesStorageService storageService;
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/api/user/saveZhipuAiImage")
|
@PostMapping("/api/user/saveZhipuAiImage")
|
||||||
public ResponseEntity<ResponseMessage> saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) {
|
public ResponseEntity<ResponseMessage> saveZhipuAiImage(@Valid @RequestBody SaveZhipuAiImageRequest request) {
|
||||||
|
|
||||||
@@ -52,7 +53,6 @@ public class FilesController {
|
|||||||
|
|
||||||
String message = "";
|
String message = "";
|
||||||
|
|
||||||
|
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -92,14 +92,21 @@ public class FilesController {
|
|||||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
|
return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/api/file/upload")
|
@PostMapping("/api/user/upload")
|
||||||
public ResponseEntity<ResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
|
public ResponseEntity<ResponseMessage> uploadFile(@RequestParam("file") MultipartFile file) {
|
||||||
String message = "";
|
String message = "";
|
||||||
try {
|
try {
|
||||||
storageService.save(file);
|
User user = authenticationFacade.getUser();
|
||||||
|
|
||||||
|
String localFullFileName = storageService.save(user.getId().toString(), file);
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(
|
||||||
|
file.getOriginalFilename(),
|
||||||
|
FilesRoutingHelper.getUserImageUrl(localFullFileName));
|
||||||
|
|
||||||
|
|
||||||
message = "Uploaded the file successfully: " + file.getOriginalFilename();
|
message = "Uploaded the file successfully: " + file.getOriginalFilename();
|
||||||
return ResponseEntity.status(HttpStatus.OK).body(new ResponseMessage(message));
|
return ResponseEntity.status(HttpStatus.OK).body(new ResponseImageUploadResult(message,fileInfo));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
message = "Could not upload the file: " + file.getOriginalFilename() + ". Error: " + e.getMessage();
|
message = "Could not upload the file: " + file.getOriginalFilename() + ". Error: " + e.getMessage();
|
||||||
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
|
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
|
||||||
@@ -113,8 +120,8 @@ 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 = MvcUriComponentsBuilder
|
|
||||||
.fromMethodName(FilesController.class, "getUserImage", path.getFileName().toString()).build().toString();
|
String url = FilesRoutingHelper.getUserImageUrl(filename);
|
||||||
|
|
||||||
return new FileInfo(filename, url);
|
return new FileInfo(filename, url);
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
@@ -127,9 +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 = MvcUriComponentsBuilder
|
String url = FilesRoutingHelper.getPublicImageUrl(filename);
|
||||||
.fromMethodName(FilesController.class, "getFile", path.getFileName().toString()).build().toString();
|
|
||||||
|
|
||||||
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) {
|
||||||
|
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package com.jambotronGroup.jambotron.exceptionHandlers;
|
||||||
|
|
||||||
|
import com.jambotronGroup.jambotron.fileUpload.ResponseMessage;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
//@ControllerAdvice
|
||||||
|
//public class FileUploadExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
|
//
|
||||||
|
// @ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||||
|
// public ResponseEntity<ResponseMessage> handleMaxSizeException(MaxUploadSizeExceededException exc) {
|
||||||
|
// return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage("File too large!"));
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -10,7 +10,7 @@ import java.util.stream.Stream;
|
|||||||
public interface FilesStorageService {
|
public interface FilesStorageService {
|
||||||
public void init();
|
public void init();
|
||||||
|
|
||||||
public void save(String userID,MultipartFile file);
|
public String save(String userID,MultipartFile file);
|
||||||
|
|
||||||
public void save(MultipartFile file);
|
public void save(MultipartFile file);
|
||||||
|
|
||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -57,13 +67,34 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(String userID,MultipartFile file) {
|
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}
|
||||||
|
*
|
||||||
|
* @param userID The ID of the user.
|
||||||
|
* @param file The file to save.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String save(String userID,MultipartFile file) {
|
||||||
|
Path targetPath = null;
|
||||||
try {
|
try {
|
||||||
Path path = this.root.resolve(userID);
|
Path path = this.root.resolve(userID);
|
||||||
path.toFile().mkdirs(); // Ensure user directory exists
|
path.toFile().mkdirs(); // Ensure user directory exists
|
||||||
|
|
||||||
Files.copy(file.getInputStream(), path.resolve(file.getOriginalFilename()),
|
targetPath = path.resolve(file.getOriginalFilename());
|
||||||
|
|
||||||
|
Files.copy(file.getInputStream(), targetPath,
|
||||||
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e instanceof FileAlreadyExistsException) {
|
if (e instanceof FileAlreadyExistsException) {
|
||||||
throw new RuntimeException("A file of that name already exists.");
|
throw new RuntimeException("A file of that name already exists.");
|
||||||
@@ -71,6 +102,8 @@ public class FilesStorageServiceImpl implements FilesStorageService {
|
|||||||
|
|
||||||
throw new RuntimeException(e.getMessage());
|
throw new RuntimeException(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return targetPath.getFileName().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.jambotronGroup.jambotron.fileUpload;
|
||||||
|
|
||||||
|
import com.jambotronGroup.jambotron.model.FileInfo;
|
||||||
|
|
||||||
|
public class ResponseImageUploadResult extends ResponseMessage {
|
||||||
|
private FileInfo fileInfo;
|
||||||
|
|
||||||
|
|
||||||
|
public ResponseImageUploadResult(String message, FileInfo fileInfo) {
|
||||||
|
super(message);
|
||||||
|
this.fileInfo = fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo getFileInfo() {
|
||||||
|
return this.fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileInfo(FileInfo fileInfo) {
|
||||||
|
this.fileInfo = fileInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.jambotronGroup.jambotron.fileUpload;
|
|||||||
public class ResponseMessage {
|
public class ResponseMessage {
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ResponseMessage(String message) {
|
public ResponseMessage(String message) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
|
|||||||
|
|
||||||
.requestMatchers("/tutorials-images/**").permitAll()
|
.requestMatchers("/tutorials-images/**").permitAll()
|
||||||
|
|
||||||
|
.requestMatchers("/api/file/files").permitAll()
|
||||||
|
.requestMatchers("/api/file/upload").permitAll()
|
||||||
|
|
||||||
.requestMatchers("/files/**").permitAll()
|
.requestMatchers("/files/**").permitAll()
|
||||||
|
|
||||||
.requestMatchers("/api/auth/**").permitAll()
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
|||||||
@@ -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