Commit 4b441059 authored by liujiangnan's avatar liujiangnan

feat: 迁移完成

parent 8a51c964
......@@ -30,7 +30,10 @@
],
"styles": [
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
"src/styles.css"
"./node_modules/font-awesome/css/font-awesome.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"./node_modules/animate.css/animate.min.css",
"src/styles.scss"
],
"scripts": []
},
......@@ -128,5 +131,8 @@
}
}
},
"defaultProject": "ng-template-generator"
}
"defaultProject": "ng-template-generator",
"cli": {
"analytics": "19e287b4-6661-4439-96bc-15b9295bb59c"
}
}
\ No newline at end of file
......@@ -24,6 +24,7 @@ import {AudioRecorderComponent} from './common/audio-recorder/audio-recorder.com
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from '@fortawesome/free-regular-svg-icons';
import { BrLineBreakerPipe } from './pipes/br-line-breaker.pipe';
registerLocaleData(zh);
......@@ -36,6 +37,7 @@ registerLocaleData(zh);
AudioRecorderComponent,
UploadImageWithPreviewComponent,
BackgroundImagePipe,
BrLineBreakerPipe,
ResourcePipe,
TimePipe,
UploadVideoComponent,
......
<div class="p-video-box">
<div class="up-video" style="display: flex;">
<!--<nz-upload class="" [nzDisabled]="!showUploadBtn"-->
<!--[nzShowUploadList]="false"-->
......@@ -16,7 +16,8 @@
<button type="button" nz-button nzType="default" *ngIf="showUploadBtn" [disabled]="uploading"
[nzLoading]="uploading" >
<i nz-icon nzType="plus" nzTheme="outline"></i>
<i nz-icon type="plus" theme="outline"></i>
<span>{{ uploading ? 'Uploading' : 'Select Video' }}</span>
<!--<span>Select Video</span>-->
</button>
......@@ -55,9 +56,9 @@
</div>
<div class="p-box d-flex align-items-center p-video-uploader">
<div class="p-box d-flex align-items-center p-video-uploader" style="top: 20px;">
<div class="p-upload-icon" *ngIf="!showUploadBtn && !videoUrl && !uploading">
<i nz-icon nzType="upload" nzTheme="outline"></i>
<i nz-icon type="upload" theme="outline"></i>
<div class="m-3"></div>
<span>Click here to upload video</span>
<div class="mt-5 p-progress-bar" *ngIf="uploading">
......@@ -69,26 +70,24 @@
[ngClass]="{'smart-bar': showUploadBtn}" >
<div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon>
Uploading...
</div>
</div>
<div class="p-upload-check-bg" *ngIf="checking">
<div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon>
<i nz-icon nzType="loading" nzTheme="outline"></i>Checking...
<i nz-icon type="loading" theme="outline"></i>Checking...
</div>
</div>
<div class="p-preview" *ngIf="!uploading && videoUrl " >
<div class="p-preview" *ngIf="!showUploadBtn && !uploading && videoUrl " >
<!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>-->
<video [src]="safeVideoUrl(videoUrl)" controls #videoNode></video>
<video [src]="safeVideoUrl(videoUrl)" controls #videoNode (loadedmetadata)="videoLoadedMetaData()"></video>
</div>
</div>
<div [style.display]="!checkVideoExists?'none':''">
<span><i nz-icon nzType="loading" nzTheme="outline"></i> checking file to upload</span>
</div>
<span><i nz-icon type="loading" theme="outline"></i> checking file to upload</span>
</div>
@import '../../style/common_mixin.css';
/*.p-video-box{
bottom: 0;
border: 2px dashed #ddd;
border-radius: 0.5rem;
background-color: #fafafa;
text-align: center;
color: #aaa;
padding-top: 56.25%;
//font-size: 4rem;
position: relative;
.p-upload-icon{
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50% ,-50%);
}
}*/
@import '../../style/common_mixin';
.p-video-uploader {
position: relative;
display: block;
......@@ -33,56 +17,50 @@
background-color: #fafafa;
text-align: center;
color: #aaa;
.p-upload-icon {
text-align: center;
margin: auto;
.anticon-upload {
color: #888;
font-size: 5rem;
}
.p-progress-bar {
position: relative;
width: 20rem;
height: 1.5rem;
border: 1px solid #ccc;
border-radius: 1rem;
.p-progress-bg {
background-color: #1890ff;
border-radius: 1rem;
height: 100%;
}
.p-progress-value {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-shadow: 0 0 4px #000;
color: white;
text-align: center;
font-size: 0.9rem;
line-height: 1.5rem;
}
}
}
.p-preview {
width: 100%;
height: 100%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
video{
max-height: 100%;
max-width: 100%;
}
}
}
}
.p-upload-icon {
text-align: center;
margin: auto;
}
.p-upload-icon .anticon-upload {
color: #888;
font-size: 5rem;
}
p-progress-bar {
position: relative;
width: 20rem;
height: 1.5rem;
border: 1px solid #ccc;
border-radius: 1rem;
}
.p-progress-bar .p-progress-bg {
background-color: #1890ff;
border-radius: 1rem;
height: 100%;
}
.p-progress-bar .p-progress-value {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-shadow: 0 0 4px #000;
color: white;
text-align: center;
font-size: 0.9rem;
line-height: 1.5rem;
}
.p-preview {
width: 100%;
height: 100%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
}
.p-preview video{
max-height: 100%;
max-width: 100%;
position: absolute;
display: flex;
}
.p-btn-delete {
position: absolute;
right: -0.5rem;
......
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SecurityContext, ViewChild} from '@angular/core';
import {NzMessageService, UploadChangeParam, UploadFile, UploadFileStatus} from 'ng-zorro-antd';
import {NzMessageService, UploadFile} from 'ng-zorro-antd';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
......@@ -24,7 +24,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
@Input()
videoUrl = '';
@ViewChild('videoNode', {static: true })
@ViewChild('videoNode')
videoNode: ElementRef;
......@@ -47,8 +47,8 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
item: any;
// videoItem = null;
uploadUrl;
uploadData;
uploadUrl = (<any> window).courseware.uploadUrl();
uploadData = (<any> window).courseware.uploadData();
constructor(private nzMessageService: NzMessageService,
private sanitization: DomSanitizer
......@@ -81,7 +81,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
}
safeVideoUrl(url) {
console.log(url);
console.log(url)
return this.sanitization.bypassSecurityTrustResourceUrl(url); // `${url}`;
}
videoLoadedMetaData() {
......@@ -89,7 +89,7 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
}
handleChange(info: UploadChangeParam/* { type: string, file: UploadFile, event: any }*/): void {
handleChange(info: { type: string, file: UploadFile, event: any }): void {
console.log('info:' , info);
......@@ -119,8 +119,8 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
break;
case 'progress':
this.progress = info.event.percent;
this.doProgress(this.progress);
this.progress = parseInt(info.event.percent, 10);
// this.doProgress(this.progress);
break;
}
}
......@@ -162,9 +162,9 @@ export class UploadVideoComponent implements OnChanges, OnDestroy {
if (duration) {
duration = duration * 1000;
}
file.height = height;
file.width = width;
file.duration = duration;
file['height'] = height;
file['width'] = width;
file['duration'] = duration;
vid.preload = 'none';
vid.src = '';
vid.remove();
......
@import '../style/common_mixin.css';
.model-content {
width: 100%;
height: 100%;
}
<div class="model-content">
<div style="position: absolute; left: 200px; top: 100px; width: 800px;">
<input type="text" nz-input [(ngModel)]="item.text" (blur)="save()">
<app-upload-image-with-preview
[picUrl]="item.pic_url"
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')"
></app-upload-image-with-preview>
<app-audio-recorder
[audioUrl]="item.audio_url"
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')"
></app-audio-recorder>
<app-custom-hot-zone></app-custom-hot-zone>
<app-upload-video></app-upload-video>
<app-lesson-title-config></app-lesson-title-config>
<div class="courseware-container d-flex row" #videoContainer>
<div class="col-8">
<video class="courseware-video"
crossorigin="anonymous"
*ngIf="item.contentObj.video_url "
[src]="item.contentObj.video_url" controls
(loadeddata)="videoLoaded($event,item)"
(error)="videoError($event)"
(seeked)="videoSeeked($event)"
#videoNode ></video>
<!--&& item.contentObj.video_url.constructor.name === 'String'-->
<div *ngIf="!item.contentObj.video_url">
<div class="p-video-box d-flex align-items-center">
<div class="p-upload-icon">
<i class="anticon anticon-caret-right"></i>
<div class="m-3"></div>
</div>
</div>
</div>
</div>
<div class="col-4">
<app-upload-video
(videoUploaded)="onVideoUploadSuccess($event)"
[item]="item"
[videoUrl]="item.contentObj.video_url"></app-upload-video>
</div>
</div>
\ No newline at end of file
<div class="row" style="margin-top: 1rem"
*ngIf="item.contentObj.pic_id && item.contentObj.video_url">
<div class="col-8">
<button nz-button nzType="primary"
style="margin-bottom: 1rem"
(click)="setVideoCover()">
Set Video Cover
</button>
<img *ngIf="item.contentObj.pic_id"
[src]="item.contentObj.pic_id"
style="width:100%;overflow: hidden;box-shadow: 0 0 20px 0px #444;"/>
</div>
</div>
.p-video-box{
bottom: 0;
border: 2px dashed #ddd;
border-radius: 0.5rem;
background-color: #fafafa;
text-align: center;
color: #aaa;
padding-top: 56.25%;
font-size: 4rem;
.p-upload-icon{
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50% ,-50%);
}
}
.courseware-container{
.courseware-video{
//position: absolute;
width: 95%;
max-height: calc(100vh - 5rem);
}
}
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import {Component, ElementRef, ViewChild, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import {NzMessageService, NzMessageDataFilled} from 'ng-zorro-antd';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用
saveKey = "test_0011";
saveKey = 'ww_video';
// 储存对象
item;
@ViewChild('videoContainer')
videoContainer: ElementRef;
@ViewChild('videoNode')
videoNode: ElementRef;
newVideoCover = true;
private tooBigError: NzMessageDataFilled = null;
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) {
constructor(private appRef: ApplicationRef,
private changeDetectorRef: ChangeDetectorRef,
private nzMessageService: NzMessageService) {
}
ngOnInit() {
this.item = {};
this.item = {
contentObj: {
pic_id: null,
video_url: null
}
};
// 获取存储的数据
(<any> window).courseware.getData((data) => {
(window as any).courseware.getData((data) => {
if (data) {
this.item = data;
}
this.init();
this.changeDetectorRef.markForCheck();
this.changeDetectorRef.detectChanges();
this.refresh();
......@@ -47,35 +58,85 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
ngOnDestroy() {
}
videoLoaded(evt, item) {
const video = evt.target;
if (video.src.startsWith('blob:') && video.videoWidth > 1280 && video.videoHeight > 720) {
this.tooBigError = this.nzMessageService.error('视频分辨率过大,请调整分辨率后上传,建议 1280x720', {nzDuration: 10 * 1000});
// video.src = null;
return ;
} else {
init() {
if (this.tooBigError && this.tooBigError.messageId) {
this.nzMessageService.remove(this.tooBigError.messageId);
this.tooBigError = null;
}
}
if (video.src && video.src.startsWith('blob:')) {
return;
}
if ( item.contentObj.origin_video_url && item.contentObj.origin_video_url.startsWith('http') ) {
item.video_preview_thumb = null;
return;
}
// item.video_preview_thumb = null;
this.newVideoCover = true;
video.currentTime = .1;
}
videoError(evt) {
this.nzMessageService.error('video error');
}
videoSeeked(evt) {
const video = evt.target;
if (video.src && video.src.startsWith('blob:')) {
return;
}
if (!this.item.contentObj.pic_id) {
this.setVideoCover();
this.newVideoCover = false;
}
}
setVideoCover() {
if (!this.videoNode) {
return;
}
const video = this.videoNode.nativeElement;
if (!video.src) { return; }
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth ;
canvas.height = video.videoHeight ;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
/**
* 储存图片数据
* @param e
*/
onImageUploadSuccess(e, key) {
const video_preview_thumb = canvas.toDataURL();
this.item[key] = e.url;
this.save();
}
canvas.remove();
/**
* 储存音频数据
* @param e
*/
onAudioUploadSuccess(e, key) {
const img = this.convertImageToBlob(video_preview_thumb);
const formData = new FormData();
formData.append('file', img);
this.item[key] = e.url;
this.save();
}
convertImageToBlob(data) {
const imgType = data.substring(data.indexOf(':') + 1, data.indexOf(';'));
const binary = atob(data.split(',')[1]);
const array = [];
let i = 0;
while (i < binary.length) {
array.push (binary.charCodeAt(i));
i++;
}
return new Blob([ new Uint8Array(array) ], {type: imgType});
}
onVideoUploadSuccess(e) {
this.item.contentObj.video_url = e.url;
this.save();
}
/**
* 储存数据
*/
......
import {Pipe, PipeTransform} from '@angular/core';
// import {ElectronUtil} from '../util/ElectronUtil';
declare const APP_PATH;
// const isInElectron = localStorage.getItem('electron');
@Pipe({
name: 'backgroundImage'
})
export class BackgroundImagePipe implements PipeTransform {
transform(value: any, coursewareSid?: string) {
// const ret = value ? `url(${value})` : '';
const ret = value ? `url(${value})` : '';
return ret;
// let ret = value > 0 ? `url(/api/media-resource/${value}/file)` : '';
// if (isInElectron && ElectronUtil.hasCached(coursewareSid, value)) {
// ret = `url(file://${APP_PATH}/courseware_cache/${coursewareSid}/${value})`;
// }
// return ret;
transform(value: any) {
return `url(${value})`;
}
}
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'brLineBreaker'
})
export class BrLineBreakerPipe implements PipeTransform {
transform(value: any, args?: any): any {
return value ? value.replace(/\n/g, '<br />') : '';
}
}
......@@ -2,25 +2,17 @@ import {Pipe, PipeTransform} from '@angular/core';
// import {ElectronUtil} from '../util/ElectronUtil';
declare const APP_PATH;
// const isInElectron = localStorage.getItem('electron');
const isInElectron = localStorage.getItem('electron');
@Pipe({
name: 'resource'
})
export class ResourcePipe implements PipeTransform {
transform(pic_url: any, coursewareSid?: string): string {
// return pic_url;
if (pic_url && typeof pic_url === 'object') {
return pic_url;
}
// console.log('resource', pic_url)
return `${pic_url}?t=${Math.random()}`;
// let ret = value ? `/api/resource/${value}/file` : '';
// let ret = res_id ? `/resource/audio/${res_id}` : '';
// if (isInElectron && ElectronUtil.hasCached(coursewareSid, res_id)) {
// ret = `file://${APP_PATH}/courseware_cache/${coursewareSid}/${res_id}`;
transform(value: any, coursewareSid?: string): string {
return value ? `/api/media-resource/${value}/file` : '';
// if (isInElectron && ElectronUtil.hasCached(coursewareSid, value)) {
// ret = `file://${APP_PATH}/courseware_cache/${coursewareSid}/${value}`;
// }
// return ret;
}
......
.game-container {
width: 100%;
height: 100%;
background: #ffffff;
background-size: cover;
}
#canvas {
}
@font-face
{
font-family: 'BRLNSDB';
src: url("../../assets/font/BRLNSDB.TTF") ;
}
<div class="game-container" #wrap>
<canvas id="canvas" #canvas></canvas>
<div
class="p-courseware-video"
[style.background-image]="hasVideo ? '' : 'url(assets/player/video-not-upload.jpg)'"
>
<video *ngIf="hasVideo"
[src]="data.contentObj.video_url"
width="100%" height="100%"
#videoNode (click)="playVideo()">
</video>
<div class="video-cover" *ngIf="initCover && !isPlaying">
<img *ngIf="initCover" [src]="initCover"
style="width:100%;height:100%;"/>
</div>
<div class="k-play-btn" *ngIf="!isPlayStarted && hasVideo" (click)="playVideo()">
<i class="anticon anticon-play-circle-o"></i>
</div>
<div class="k-video-control" *ngIf="hasVideo">
<div class="k-video-control-bg"></div>
<div class="k-video-control-time-past">{{currentTime | time}}</div>
<div #videoProgress class="k-video-control-progress"
(mousedown)="onProgressMouseDown($event)"
(mousemove)="onProgressMouseMove($event)"
(mouseup)="onProgressMouseUp($event)">
<div class="i-bg"></div>
<div class="i-before-progress" [style.width]="progress + '%'"></div>
<div class="i-cursor" [style.left]="progress + '%'"></div>
</div>
<div class="k-video-control-time-total">{{duration | time}}</div>
<div class="k-video-control-tool">
<div class="i-tool-btn" (click)="onBtnQuickSeek(-10)">-10</div>
<div class="i-tool-btn" (click)="onBtnQuickSeek(-5)">-5</div>
<div class="i-tool-btn" (click)="onBtnQuickSeek(5)">+5</div>
<div class="i-tool-btn" (click)="onBtnQuickSeek(10)">+10</div>
</div>
</div>
</div>
@import '../style/common_mixin.scss';
.p-courseware-video {
height: 100%;
overflow: hidden;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
background-color: #333;
@include k-img-bg();
.video-cover{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
}
video {
width: 100%;
height: 93%;
}
}
.k-play-btn {
position: absolute;
width: 15%;
top: 30%;
left: 40%;
font-size: 12vh;
color: #444;
background-color: white;
border-radius: 50%;
opacity: 0.9;
}
.k-video-control {
position: absolute;
bottom: 1vh;
left: 0;
width: 100%;
height: 7.5%;
display: flex;
color: white;
padding: 0 8vh;
align-items: center;
&-bg {
position: absolute;
//width: 100%;
height: 100%;
background-color: #333;
opacity: 0.5;
}
&-time-past {
width: 3.5rem;
}
&-progress {
position: relative;
flex: 1;
height: 100%;
display: flex;
align-items: center;
margin: 0 3%;
& > * {
pointer-events: none; // Ken video seek
}
.i-bg {
position: absolute;
background-color: #aaa;
width: 100%;
height: 10%;
border-radius: 1rem;
}
.i-before-progress {
position: absolute;
height: 10%;
background-color: white;
border-radius: 1rem;
}
.i-cursor {
position: absolute;
width: 1.5vh;
height: 1.5vh;
border-radius: 50%;
background-color: white;
}
}
&-time-total {
margin-right: 3vh;
}
&-tool {
display: flex;
.i-tool-btn {
width: 4vh;
height: 4vh;
background-color: white;
border-radius: 50%;
margin: 0 1vh;
color: black;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.8vh;
font-weight: bold;
}
}
}
This diff is collapsed.
@mixin hide-overflow-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@mixin k-no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin k-img-bg {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
@mixin hide-overflow-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@mixin k-no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin k-img-bg {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
......@@ -3,6 +3,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
if (environment.production) {
enableProdMode();
......
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
@mixin hide-overflow-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@mixin k-no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin k-img-bg {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment