mirror of
https://github.com/Yourdaylight/CloudGame.git
synced 2026-07-28 01:25:17 +00:00
feat: support video and allow delete
This commit is contained in:
@@ -37,6 +37,9 @@
|
||||
"src/styles.scss",
|
||||
"node_modules/ng-zorro-antd/ng-zorro-antd.min.css"
|
||||
],
|
||||
"allowedCommonJsDependencies": [
|
||||
"events"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
|
||||
<h2>Upload Count({{images.length}})</h2>
|
||||
<nz-list [nzDataSource]="images" nzBordered>
|
||||
<nz-list-item *ngFor="let image of images; let i = index">
|
||||
<nz-list-item-meta
|
||||
[nzAvatar]="image.url"
|
||||
[nzTitle]="'Upload by ' + username"
|
||||
[nzDescription]="image.uploadTime">
|
||||
<p>{{ image.picTitle }}</p>
|
||||
<a [href]="image.url" target="_blank">{{ image.url }}</a>
|
||||
</nz-list-item-meta>
|
||||
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '10px' }" (click)="previewImage(image.blobUrl)">Preview</button>
|
||||
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '10px' }" (click)="previewImage(image)">Preview</button>
|
||||
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '10px' }" (click)="downloadImage(image.url)">Download</button>
|
||||
<button nz-button nzType="primary" nzDanger (click)="deleteImage(i)">Delete</button>
|
||||
</nz-list-item>
|
||||
|
||||
@@ -5,6 +5,9 @@ import { ApiService } from 'src/app/services/api.service';
|
||||
import { BlobServiceClient, ContainerClient } from '@azure/storage-blob';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorite',
|
||||
templateUrl: './favorite.component.html',
|
||||
@@ -14,7 +17,13 @@ export class FavoriteComponent implements OnInit {
|
||||
favoriteList: any = [];
|
||||
loading: boolean = true;
|
||||
username: any;
|
||||
images: { url: any, type: any, uploadTime:any, blobUrl:any }[] = [];
|
||||
images: {
|
||||
url: any,
|
||||
type: any,
|
||||
uploadTime:any,
|
||||
blobUrl:any ,
|
||||
picTitle:any
|
||||
}[] = [];
|
||||
// Azure Storage SAS URL
|
||||
sasUrl = 'https://medias.blob.core.windows.net/assets?si=asset&sv=2022-11-02&sr=c&sig=OA8DpIpkl1ak4ZG%2BA7BzMtIRbI6carOTxLtzAcHa9pg%3D';
|
||||
sasToken = 'si=asset&sv=2022-11-02&sr=c&sig=OA8DpIpkl1ak4ZG%2BA7BzMtIRbI6carOTxLtzAcHa9pg%3D'
|
||||
@@ -22,7 +31,8 @@ export class FavoriteComponent implements OnInit {
|
||||
private apiService: ApiService,
|
||||
private $message: NzMessageService,
|
||||
private navigateService: NavigateService,
|
||||
private http: HttpClient
|
||||
private http: HttpClient,
|
||||
private modalService: NzModalService
|
||||
|
||||
) {}
|
||||
|
||||
@@ -38,8 +48,23 @@ export class FavoriteComponent implements OnInit {
|
||||
return sasToken ? `?${sasToken}` : '';
|
||||
}
|
||||
|
||||
previewImage(url: string) {
|
||||
window.open(url,'_blank');
|
||||
previewImage(image: { url: string, type: string }) {
|
||||
let content = '';
|
||||
if (image.type.startsWith('image')) {
|
||||
content = `<img src="${image.url}" alt="Image" style="width: 100%; max-height: 600px;">`;
|
||||
} else if (image.type.startsWith('video')) {
|
||||
content = `<video src="${image.url}" controls style="width: 100%; max-height: 600px;"></video>`;
|
||||
}else if (image.type.startsWith('audio')) {
|
||||
content = `<audio src="${image.url}" controls style="width: 100%; max-height: 600px;"></video>`;
|
||||
}
|
||||
else {
|
||||
// Handle other types as needed
|
||||
}
|
||||
this.modalService.create({
|
||||
nzTitle: 'Preview',
|
||||
nzContent: content,
|
||||
nzFooter: null
|
||||
});
|
||||
}
|
||||
|
||||
downloadImage(url: string) {
|
||||
@@ -48,9 +73,20 @@ export class FavoriteComponent implements OnInit {
|
||||
|
||||
async deleteImage(index: number) {
|
||||
const image = this.images[index];
|
||||
// TODO: Delete the image from Azure
|
||||
// After the image is successfully deleted from Azure, remove it from the images array
|
||||
this.images.splice(index, 1);
|
||||
const blobUrl = image.blobUrl; // Assuming this is the URL of the blob
|
||||
|
||||
const headers = new HttpHeaders({
|
||||
'x-ms-date': new Date().toUTCString(),
|
||||
'x-ms-version': '2022-11-02' // Use the version of Azure Storage Service you are using
|
||||
});
|
||||
|
||||
try {
|
||||
await this.http.delete(blobUrl, { headers }).toPromise();
|
||||
// After the image is successfully deleted from Azure, remove it from the images array
|
||||
this.images.splice(index, 1);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete blob:', error);
|
||||
}
|
||||
}
|
||||
async loadImagesFromAzure() {
|
||||
const containerUrl = 'https://medias.blob.core.windows.net/assets';
|
||||
@@ -66,23 +102,21 @@ export class FavoriteComponent implements OnInit {
|
||||
this.images = [];
|
||||
for (let i = 0; i < blobs.length; i++) {
|
||||
const blob = blobs[i];
|
||||
console.log("###########")
|
||||
const name = blob.getElementsByTagName('Name')[0].textContent || '';
|
||||
const title = JSON.parse(localStorage.getItem('gameInfo') as any).Title;
|
||||
const lastModified = blob.getElementsByTagName('Last-Modified')[0].textContent || '';
|
||||
|
||||
console.log(name);
|
||||
console.log(title);
|
||||
if (!name || name.includes(this.username)) {
|
||||
// const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}}`;
|
||||
const url = blob.getElementsByTagName('Url')[0].textContent || '';
|
||||
const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}`;
|
||||
const picTitle = name.split('_')[0].replace('assets/', '');
|
||||
// Assuming you want to display images
|
||||
this.images.push({
|
||||
url: url,
|
||||
type: 'image/jpeg',
|
||||
uploadTime: lastModified ,
|
||||
blobUrl: blobUrl
|
||||
blobUrl: blobUrl,
|
||||
picTitle: picTitle
|
||||
}); // Modify 'type' as needed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,20 +59,26 @@
|
||||
</nz-upload>
|
||||
<button nz-button nzType="primary" (click)="handleUpload()" [disabled]="uploading">Upload</button>
|
||||
<nz-spin [nzSpinning]="uploading"></nz-spin>
|
||||
<!-- if there are files in the fileList and not uploading, show the input and button -->
|
||||
<div *ngIf="fileList.length > 0 && !uploading">
|
||||
<input nz-input [(ngModel)]="uploadTitle" placeholder="Please Enter a Title">
|
||||
<button nz-button nzType="primary" (click)="handleUpload()">Confirm</button>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let file of fileList">{{ file.name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 20px;">Wonderful moments</h3>
|
||||
<h2 style="margin-top: 15px;">Wonderful moments({{images.length}})</h2>
|
||||
<nz-row nzGutter="16">
|
||||
<nz-col nzSpan="8" *ngFor="let item of images">
|
||||
<nz-card [nzHoverable]="true">
|
||||
<img *ngIf="item.type.startsWith('image')" [src]="item.url" alt="Uploaded Image" />
|
||||
<video *ngIf="item.type.startsWith('video')" [src]="item.url" controls></video>
|
||||
<nz-card-meta [nzTitle]="'Upload by ' + username"></nz-card-meta>
|
||||
<nz-card-meta [title]="item.picTitle"></nz-card-meta>
|
||||
<img *ngIf="item.type.startsWith('image')" [src]="item.url" alt="Uploaded Image" class="media" />
|
||||
<video *ngIf="item.type.startsWith('video')" [src]="item.url" controls class="media"></video>
|
||||
<audio *ngIf="item.type.startsWith('audio')" [src]="item.url" controls class="media"></audio>
|
||||
<nz-card-meta [nzTitle]="'Upload by ' + username" [nzDescription]="item.lastModified | date:'medium'"></nz-card-meta>
|
||||
</nz-card>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
|
||||
|
||||
|
||||
@@ -37,3 +37,8 @@
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.media {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { BlobServiceClient, ContainerClient } from '@azure/storage-blob';
|
||||
import { NzUploadChangeParam, NzUploadFile } from 'ng-zorro-antd/upload';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { NzModalService } from 'ng-zorro-antd/modal';
|
||||
@Component({
|
||||
selector: 'app-game-details',
|
||||
templateUrl: './game-details.component.html',
|
||||
@@ -23,7 +24,14 @@ export class GameDetailsComponent implements OnInit {
|
||||
uploading = false;
|
||||
fileList: NzUploadFile[] = [];
|
||||
username: any;
|
||||
images: { url: any, type: any }[] = [];
|
||||
images: {
|
||||
url: any,
|
||||
type: any,
|
||||
lastModified:any,
|
||||
picTitle:any
|
||||
}[] = [];
|
||||
isModalOpen = false;
|
||||
uploadTitle = '';
|
||||
// Azure Storage SAS URL
|
||||
sasUrl = 'https://medias.blob.core.windows.net/assets?si=asset&sv=2022-11-02&sr=c&sig=OA8DpIpkl1ak4ZG%2BA7BzMtIRbI6carOTxLtzAcHa9pg%3D';
|
||||
sasToken = 'si=asset&sv=2022-11-02&sr=c&sig=OA8DpIpkl1ak4ZG%2BA7BzMtIRbI6carOTxLtzAcHa9pg%3D'
|
||||
@@ -37,7 +45,8 @@ export class GameDetailsComponent implements OnInit {
|
||||
private navigateService: NavigateService,
|
||||
public router: Router,
|
||||
private fb: FormBuilder,
|
||||
private http: HttpClient
|
||||
private http: HttpClient,
|
||||
private modalService: NzModalService
|
||||
) {
|
||||
this.blobServiceClient = new BlobServiceClient(this.sasUrl);
|
||||
this.containerClient = this.blobServiceClient.getContainerClient(this.containerName);
|
||||
@@ -64,20 +73,33 @@ export class GameDetailsComponent implements OnInit {
|
||||
this.fileList = this.fileList.concat(file);
|
||||
return false; // Prevent automatic upload
|
||||
};
|
||||
|
||||
// Perform the upload
|
||||
handleUpload(): void {
|
||||
|
||||
this.modalService.confirm({
|
||||
nzTitle: 'Make sure the title for the upload',
|
||||
nzContent: `
|
||||
${this.uploadTitle} will be the title for the upload.<br>
|
||||
${this.fileList.length} file(s) will be uploaded.
|
||||
`,
|
||||
nzOnOk: () => this.onConfirmUpload()
|
||||
});
|
||||
}
|
||||
|
||||
async onConfirmUpload() {
|
||||
const title = this.uploadTitle;
|
||||
this.uploadTitle = ''; // Reset the title for the next upload
|
||||
|
||||
this.uploading = true;
|
||||
this.fileList.forEach(file => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: any) => {
|
||||
const blob = new Blob([e.target.result], { type: file.type });
|
||||
this.uploadFileToAzure(file.name, blob);
|
||||
this.uploadFileToAzure(title, file.name, blob);
|
||||
};
|
||||
reader.readAsArrayBuffer(file as any);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Handle file change event for drag-and-drop
|
||||
handleChange({ file, fileList }: NzUploadChangeParam): void {
|
||||
const status = file.status;
|
||||
@@ -89,21 +111,27 @@ export class GameDetailsComponent implements OnInit {
|
||||
} else if (status === 'error') {
|
||||
this.$message.error(`${file.name} file upload failed.`);
|
||||
}
|
||||
this.fileList = fileList;
|
||||
}
|
||||
|
||||
|
||||
// Upload files to Azure Blob Storage
|
||||
async uploadFileToAzure(fileName: string, blob: Blob) {
|
||||
async uploadFileToAzure(uploadTitle: string, fileName: string, blob: Blob) {
|
||||
try {
|
||||
// Get username and title
|
||||
const username = localStorage.getItem('username');
|
||||
const title = JSON.parse(localStorage.getItem('gameInfo') as any).Title;
|
||||
// Add username and title to the file name
|
||||
const newFileName = `${title}_${username}_${fileName}`;
|
||||
const newFileName = `${uploadTitle}_${title}_${username}_${fileName}`;
|
||||
// Create a new FileReader object
|
||||
const blockBlobClient = this.containerClient.getBlockBlobClient(newFileName);
|
||||
// Upload the ArrayBuffer to Azure Blob Storage
|
||||
await blockBlobClient.uploadData(blob);
|
||||
//await blockBlobClient.uploadData(file.originFileObj as Blob);
|
||||
|
||||
// if needed, you can also upload the file to a user-specific container
|
||||
// const userContainer = this.blobServiceClient.getContainerClient(this.username);
|
||||
// const userBlockBlobClient = userContainer.getBlockBlobClient(newFileName);
|
||||
|
||||
this.$message.success(`${fileName} uploaded successfully.`);
|
||||
} catch (error) {
|
||||
this.$message.error(`Upload failed: ${error}`);
|
||||
@@ -129,6 +157,7 @@ export class GameDetailsComponent implements OnInit {
|
||||
const blob = blobs[i];
|
||||
const name = blob.getElementsByTagName('Name')[0].textContent || '';
|
||||
const title = JSON.parse(localStorage.getItem('gameInfo') as any).Title;
|
||||
const lastModified = blob.getElementsByTagName('Last-Modified')[0].textContent || '';
|
||||
if (!name || name.includes(title)) {
|
||||
const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}`;
|
||||
const extension = name.split('.').pop();
|
||||
@@ -144,11 +173,30 @@ export class GameDetailsComponent implements OnInit {
|
||||
case 'gif':
|
||||
type = 'image/gif';
|
||||
break;
|
||||
case 'mp3':
|
||||
type = 'audio/mp3';
|
||||
break;
|
||||
case 'wav':
|
||||
type = 'audio/wav';
|
||||
break;
|
||||
case 'mp4':
|
||||
type = 'video/mp4';
|
||||
break;
|
||||
case 'avi':
|
||||
type = 'video/avi';
|
||||
break;
|
||||
|
||||
// Add more cases as needed
|
||||
default:
|
||||
type = 'application/octet-stream'; // Default to binary data
|
||||
}
|
||||
this.images.push({ url: blobUrl, type: 'image/jpeg' }); // Modify 'type' as needed
|
||||
const picTitle = name.split('_')[0].replace('assets/', '');
|
||||
this.images.push({
|
||||
url: blobUrl,
|
||||
type: type,
|
||||
lastModified:lastModified ,
|
||||
picTitle: picTitle
|
||||
}); // Modify 'type' as needed
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -38,22 +38,24 @@
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</div>
|
||||
<div nz-col nzSpan="8" style="margin-top: 20px">
|
||||
<div nz-col nzSpan="12" style="margin-top: 20px">
|
||||
<button nz-button nzType="primary">Search</button>
|
||||
<!-- Button to open the modal for adding a new game -->
|
||||
<button
|
||||
*ngIf="isAdmin"
|
||||
nz-button
|
||||
nzType="default"
|
||||
(click)="isModalVisible = true"
|
||||
style="margin-left: 15px"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<i nz-icon nzType="plus" nzTheme="outline"></i>Add Game
|
||||
<i nz-icon nzType="upload" nzTheme="outline"></i>Upload Your Game Moment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<nz-modal [(nzVisible)]="isModalVisible" nzTitle="Upload your Game Moment" (nzOnCancel)="isModalVisible = false">
|
||||
<ng-container *nzModalContent>
|
||||
<p>{{modalContent}}</p>
|
||||
</ng-container>
|
||||
</nz-modal>
|
||||
<!-- Display of items with a loading spinner -->
|
||||
<div style="padding: 5px 30px">
|
||||
<nz-spin [nzSpinning]="isLoading">
|
||||
@@ -128,37 +130,3 @@
|
||||
style="text-align: right"
|
||||
></nz-pagination>
|
||||
</div>
|
||||
<nz-modal
|
||||
[(nzVisible)]="isModalVisible"
|
||||
nzTitle="Add Movie"
|
||||
nzOkText="Ok"
|
||||
nzCancelText="Cancel"
|
||||
(nzOnOk)="submitForm()"
|
||||
(nzOnCancel)="isModalVisible = false"
|
||||
>
|
||||
<ng-container *nzModalContent>
|
||||
<form nz-form [formGroup]="createForm">
|
||||
<div nz-row [nzGutter]="24">
|
||||
<div nz-col [nzSpan]="24" *ngFor="let field of createFormFields">
|
||||
<nz-form-item>
|
||||
<nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name"
|
||||
>{{ field.label }}
|
||||
</nz-form-label>
|
||||
<nz-form-control
|
||||
[nzSm]="14"
|
||||
[nzXs]="24"
|
||||
[nzErrorTip]="'Please input ' + field.label!"
|
||||
>
|
||||
<input
|
||||
nz-input
|
||||
[formControlName]="field.value"
|
||||
id="name"
|
||||
[placeholder]="field.label"
|
||||
/>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</nz-modal>
|
||||
|
||||
@@ -76,6 +76,10 @@ export class ListComponent implements OnInit {
|
||||
},
|
||||
];
|
||||
isAdmin: boolean = false; // Indicates if the user is an admin
|
||||
// ...
|
||||
|
||||
modalContent = 'Welcome to upload your game moment. If you need to upload, please select the game on the homepage first. Each game detail page has wonderful moments uploaded by other players. Welcome to upload your wonderful video.';
|
||||
|
||||
|
||||
// Constructor with necessary service injections
|
||||
constructor(
|
||||
@@ -118,30 +122,6 @@ export class ListComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
// Submit the create form
|
||||
submitForm(): void {
|
||||
if (this.createForm.valid) {
|
||||
let params = { ...this.createForm.value };
|
||||
this.apiService.post('/game/addGame', params).subscribe(
|
||||
(res: any) => {
|
||||
this.isModalVisible = false;
|
||||
this.getGameList();
|
||||
this.messageService.success(`add success!`);
|
||||
},
|
||||
() => {
|
||||
// Error handling
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Handling form validation
|
||||
Object.values(this.createForm.controls).forEach((control) => {
|
||||
if (control.invalid) {
|
||||
control.markAsDirty();
|
||||
control.updateValueAndValidity({ onlySelf: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger search
|
||||
onSearch() {
|
||||
|
||||
Reference in New Issue
Block a user