Commit ac6bc2ca authored by liujiaxin's avatar liujiaxin

jm 12

parent 9f3a2900
......@@ -2,7 +2,7 @@
"name": "ng-template-generator",
"version": "0.0.1",
"scripts": {
"start": "ng serve",
"start": "ng serve --host 0.0.0.0",
"build": "ng build --build--optimizer --aot --base-href /JGT/v3/",
"publish": "node ./bin/publish.js",
"ng": "ng",
......@@ -24,15 +24,17 @@
"@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-regular-svg-icons": "^5.12.1",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@splidejs/splide": "^2.4.21",
"@tweenjs/tween.js": "~18.5.0",
"ali-oss": "^6.5.1",
"better-scroll": "^2.4.1",
"compressing": "^1.5.0",
"ng-zorro-antd": "^8.5.2",
"node-sass": "4.14.0",
"rxjs": "~6.5.4",
"spark-md5": "^3.0.0",
"tslib": "^1.10.0",
"zone.js": "~0.10.2",
"node-sass": "4.0.0"
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.3",
......
......@@ -4,26 +4,23 @@ import { NgModule, ErrorHandler } from '@angular/core';
import {MyErrorHandler} from './MyError';
import { AppComponent } from './app.component';
import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd';
import { FormsModule } from '@angular/forms';
import { NgZorroAntdModule, NZ_CAROUSEL_CUSTOM_STRATEGIES, NZ_I18N, zh_CN } from 'ng-zorro-antd';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { registerLocaleData } from '@angular/common';
import zh from '@angular/common/locales/zh';
import {FormComponent} from './form/form.component';
import {PlayComponent} from './play/play.component';
import {LessonTitleConfigComponent} from './common/lesson-title-config/lesson-title-config.component';
import {BackgroundImagePipe} from './pipes/background-image.pipe';
import {UploadImageWithPreviewComponent} from './common/upload-image-with-preview/upload-image-with-preview.component';
import {PlayerContentWrapperComponent} from './common/player-content-wrapper/player-content-wrapper.component';
import {CustomHotZoneComponent} from './common/custom-hot-zone/custom-hot-zone.component';
import {UploadVideoComponent} from './common/upload-video/upload-video.component';
import {TimePipe} from './pipes/time.pipe';
import {ResourcePipe} from './pipes/resource.pipe';
import {AudioRecorderComponent} from './common/audio-recorder/audio-recorder.component';
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { far } from '@fortawesome/free-regular-svg-icons';
import { ContentsPipe } from './pipes/contents.pipe';
import { TouchStrategy } from './play/touch-strategy';
import { CustomScrollBarDirective } from './play/scrollbar.directive';
registerLocaleData(zh);
......@@ -32,16 +29,11 @@ registerLocaleData(zh);
AppComponent,
FormComponent,
PlayComponent,
LessonTitleConfigComponent,
AudioRecorderComponent,
UploadImageWithPreviewComponent,
BackgroundImagePipe,
ResourcePipe,
ContentsPipe,
TimePipe,
UploadVideoComponent,
CustomHotZoneComponent,
PlayerContentWrapperComponent
CustomScrollBarDirective,
],
imports: [
......@@ -50,9 +42,19 @@ registerLocaleData(zh);
FormsModule,
HttpClientModule,
BrowserAnimationsModule,
FontAwesomeModule
FontAwesomeModule,
ReactiveFormsModule
],
providers: [
{
provide: NZ_CAROUSEL_CUSTOM_STRATEGIES,
useValue: [
{
name: 'touch',
strategy: TouchStrategy,
},
],
},
{provide: ErrorHandler, useClass: MyErrorHandler},
{ provide: NZ_I18N, useValue: zh_CN }
],
......
<div class="d-flex">
<div class="p-btn-record d-flex">
<div class="btn-clear" style="cursor: pointer" (click)="onBtnClearAudio()" *ngIf="withRmBtn && (audioUrl || audioBlob)">
<fa-icon icon="times"></fa-icon>
</div>
<div class="btn-record" *ngIf="type===Type.RECORD && !isUploading"
[class.p-recording]="isRecording"
(click)="onBtnRecord()">
<fa-icon icon="microphone"></fa-icon>
Record Audio
</div>
<nz-upload
[nzAccept] = "'.mp3'"
[nzShowUploadList]="false"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="handleChange($event)">
<div class="btn-upload" [ngClass]="{'has-clear': withRmBtn && (audioUrl || audioBlob)}" *ngIf="type===Type.UPLOAD && !isUploading">
<fa-icon icon="upload"></fa-icon>
Upload Audio
</div>
</nz-upload>
<div class="p-upload-progress-bg" *ngIf="isUploading">
<div class="i-bg" [style.width]="progress+'%'"></div>
<div class="i-text">
<fa-icon icon="cloud-upload-alt"></fa-icon>
Uploading...
</div>
</div>
<div *ngIf="audioUrl && needRemove; then truthyTemplate else falsyTemplate"></div>
<ng-template #truthyTemplate >
<div class="btn-delete" (click)="onBtnDeleteAudio()">
<fa-icon icon="close"></fa-icon>
</div>
</ng-template>
<ng-template #falsyTemplate>
<div class="btn-switch" (click)="onBtnSwitchType()">
<fa-icon icon="cog"></fa-icon>
</div>
</ng-template>
</div>
<div class="p-progress ml-2" (click)="onBtnPlay()" *ngIf="audioUrl || audioBlob">
<nz-progress [nzPercent]="percent" [nzWidth]="30" [nzFormat]="progressText"
nzType="circle"></nz-progress>
<div class="p-btn-play" [style.left]="isPlaying?'8px':''">
<fa-icon [icon]="playIcon"></fa-icon>
</div>
</div>
</div>
.d-flex{
display: flex;
}
.p-btn-record {
font-size: 0.9rem;
color: #555;
font-family: "Monospaced Number", "Chinese Quote", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
.btn-record, .btn-upload {
cursor: pointer;
text-align: center;
width: 130px;
height: 33px;
line-height: 33px;
border-radius: 0.5rem 0 0 0.5rem;
border: 1px solid #ddd;
border-right: 0.5px;
&:hover {
color: rgb(64, 169, 255);
}
}
.btn-record.has-clear, .btn-upload.has-clear{
border-radius:0;
border-left: 0;
}
.btn-clear{
text-align: center;
color: #aaa;
padding: 0 0.5rem;
border: 1px solid #ddd;
border-radius: 0.5rem 0 0 0.5rem;
height: 33px;
line-height: 33px;
}
.btn-switch {
text-align: center;
color: #aaa;
padding: 0 0.5rem;
border: 1px solid #ddd;
border-radius: 0 0.5rem 0.5rem 0;
height: 33px;
line-height: 33px;
&:hover {
color: rgb(64, 169, 255);
}
}
.btn-delete {
text-align: center;
color: #aaa;
padding: 0 0.5rem;
border: 1px solid #ddd;
border-radius: 0 0.5rem 0.5rem 0;
height: 33px;
line-height: 33px;
&:hover {
color: #ec5b56;
}
}
}
.p-recording {
background: orangered;
color: white !important;
}
.p-upload-progress-bg {
position: relative;
width: 130px;
height: 33px;
line-height: 33px;
& .i-text {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
border-radius: 0.5rem 0 0 0.5rem;
border: 1px solid #ddd;
border-right: 0.5px;
color: white;
text-shadow: 0 0 2px rgba(0, 0, 0, .85);
}
& .i-bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #27b43f;
border-radius: 0.5rem 0 0 0.5rem;
}
}
.p-progress {
margin-top: 2px;
position: relative;
line-height: 26px;
.p-btn-play {
position: absolute;
left: 10px;
top: 3px;
color: #555;
}
}
:host ::ng-deep nz-upload {
line-height: 33px;
}
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, NgZone, OnChanges} from '@angular/core';
import {NzMessageService, NzNotificationService, UploadFile} from 'ng-zorro-antd';
import {HttpClient, HttpEvent, HttpEventType, HttpRequest} from '@angular/common/http';
import {environment} from '../../../environments/environment';
declare var Recorder;
@Component({
selector: 'app-audio-recorder',
templateUrl: './audio-recorder.component.html',
styleUrls: ['./audio-recorder.component.scss']
})
export class AudioRecorderComponent implements OnInit, OnChanges, OnDestroy {
_audioUrl: string;
audio = new Audio();
playIcon = 'play';
isPlaying = false;
isRecording = false;
isUploading = false;
type = Type.UPLOAD; // record | upload
Type = Type;
@Input()
withRmBtn = false;
uploadUrl;
uploadData;
@Input()
needRemove = false;
@Input()
audioItem: any = null;
@Input()
set audioUrl(url) {
this._audioUrl = url;
if (url) {
this.audio.src = this._audioUrl;
this.audio.load();
}
this.init();
}
get audioUrl() {
return this._audioUrl;
}
@Output() audioUploaded = new EventEmitter();
@Output() audioUploadFailure = new EventEmitter();
@Output() audioRemoved = new EventEmitter();
percent = 0;
progress = 0;
recorder: any;
audioBlob: any;
constructor( private nzMessageService: NzMessageService ) {
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
}
init() {
this.playIcon = 'play';
this.isPlaying = false;
this.isRecording = false;
this.isUploading = false;
this.percent = 0;
this.progress = 0;
this.audioBlob = null;
}
ngOnChanges() {
// if (!this.audioItem || !this.audioItem.type) {
// return;
// }
// this.beforeUpload(this.audioItem);
}
ngOnInit() {
this.audio.onplay = () => {
this.onPlay();
};
this.audio.onpause = () => {
this.onPause();
};
this.audio.ontimeupdate = (event) => {
this.onTimeUpdate(event);
};
this.audio.onended = (event) => {
this.onEnded();
};
}
ngOnDestroy() {
this.audio.pause();
this.isPlaying = false;
this.audio.remove();
// if (this.recorder.worker) {
// this.recorder.worker.terminate();
// }
}
progressText(percent) {
return ``;
}
onPlay() {
console.log('play');
this.playIcon = 'pause';
this.isPlaying = true;
}
onPause() {
console.log('pause');
this.playIcon = 'play';
this.isPlaying = false;
}
onEnded() {
console.log('on end');
this.playIcon = 'play';
this.percent = 0;
this.isPlaying = false;
}
onTimeUpdate(event) {
this.percent = Math.floor((this.audio.currentTime / this.audio.duration) * 100);
}
onBtnPlay() {
if (this.isRecording) {
this.nzMessageService.warning('In Recording');
return;
}
if (this.isPlaying) {
this.audio.pause();
} else {
this.audio.play();
}
}
// 开始录音
onBtnRecord = () => {
}
// 切换模式
onBtnSwitchType() {
}
onBtnClearAudio() {
this.audioUrl = null;
this.audioRemoved.emit();
}
onBtnDeleteAudio() {
this.audioUrl = null;
this.audioRemoved.emit();
}
handleChange(info: { type: string, file: UploadFile, event: any }): void {
switch (info.type) {
case 'start':
this.isUploading = true;
this.progress = 0;
break;
case 'success':
this.isUploading = false;
this.uploadSuccess(info.file.response);
this.audioUploaded.emit(info.file.response);
break;
case 'progress':
this.progress = parseInt(info.event.percent, 10);
break;
}
}
checkSelectFile(file: any) {
if (!file) {
return;
}
const isAudio = ['audio/mp3', 'audio/wav', 'audio/ogg'].includes(file.type);
if (!isAudio) {
this.nzMessageService.error('You can only upload Audio file ( mp3 | wav |ogg)');
return;
}
const delta = 25;
const isOverSize = (file.size / 1024 / 1024) < delta;
if (!isOverSize) {
this.nzMessageService.error(`audio file must smaller than ${delta}MB!`);
return false;
}
return true;
}
beforeUpload = (file: File) => {
this.audioUrl = null;
if (!this.checkSelectFile(file)) {
return false;
}
this.isUploading = true;
this.progress = 0;
}
uploadSuccess = (url) => {
this.nzMessageService.info('Upload Success');
this.isUploading = false;
this.audioUrl = url;
}
uploadFailure = (err, file) => {
this.isUploading = false;
if (err.name && err.name === 'cancel') {
return;
}
console.log(err);
this.nzMessageService.error('Upload Error ' + err.message);
this.audioUploadFailure.emit(file);
}
doProgress = (p) => {
if (p > 1) {
p = 1;
}
if (p < 0) {
p = 0;
}
// console.log(Math.floor(p * 100));
this.progress = Math.floor(p * 100);
}
}
enum Type {
RECORD = 1, UPLOAD
}
import TWEEN from '@tweenjs/tween.js';
interface AirWindow extends Window {
air: any;
curCtx: any;
}
declare let window: AirWindow;
class Sprite {
x = 0;
y = 0;
color = '';
radius = 0;
alive = false;
margin = 0;
angle = 0;
ctx;
constructor(ctx = null) {
if (!ctx) {
this.ctx = window.curCtx;
} else {
this.ctx = ctx;
}
}
update($event) {
this.draw();
}
draw() {
}
}
export class MySprite extends Sprite {
_width = 0;
_height = 0;
_anchorX = 0;
_anchorY = 0;
_offX = 0;
_offY = 0;
scaleX = 1;
scaleY = 1;
_alpha = 1;
rotation = 0;
visible = true;
skewX = 0;
skewY = 0;
_shadowFlag = false;
_shadowColor;
_shadowOffsetX = 0;
_shadowOffsetY = 0;
_shadowBlur = 5;
_radius = 0;
children = [this];
childDepandVisible = true;
childDepandAlpha = false;
img;
_z = 0;
_showRect;
_bitmapFlag = false;
_offCanvas;
_offCtx;
init(imgObj = null, anchorX: number = 0.5, anchorY: number = 0.5) {
if (imgObj) {
this.img = imgObj;
this.width = this.img.width;
this.height = this.img.height;
}
this.anchorX = anchorX;
this.anchorY = anchorY;
}
setShowRect(rect) {
this._showRect = rect;
}
setShadow(offX, offY, blur, color = 'rgba(0, 0, 0, 0.3)') {
this._shadowFlag = true;
this._shadowColor = color;
this._shadowOffsetX = offX;
this._shadowOffsetY = offY;
this._shadowBlur = blur;
}
setRadius(r) {
this._radius = r;
}
update($event = null) {
if (!this.visible && this.childDepandVisible) {
return;
}
this.draw();
}
draw() {
this.ctx.save();
this.drawInit();
this.updateChildren();
this.ctx.restore();
}
drawInit() {
this.ctx.translate(this.x, this.y);
this.ctx.rotate(this.rotation * Math.PI / 180);
this.ctx.scale(this.scaleX, this.scaleY);
this.ctx.globalAlpha = this.alpha;
this.ctx.transform(1, this.skewX, this.skewY, 1, 0, 0);
//
// if (this._radius) {
//
// const r = this._radius;
// const w = this.width;
// const h = this.height;
//
// this.ctx.lineTo(-w / 2, h / 2); // 创建水平线
// this.ctx.arcTo(-w / 2, -h / 2, -w / 2 + r, -h / 2, r);
// this.ctx.arcTo(w / 2, -h / 2, w / 2, -h / 2 + r, r);
// this.ctx.arcTo(w / 2, h / 2, w / 2 - r, h / 2, r);
// this.ctx.arcTo(-w / 2, h / 2, -w / 2, h / 2 - r, r);
//
// this.ctx.clip();
// }
}
drawSelf() {
if (this._shadowFlag) {
this.ctx.shadowOffsetX = this._shadowOffsetX;
this.ctx.shadowOffsetY = this._shadowOffsetY;
this.ctx.shadowBlur = this._shadowBlur;
this.ctx.shadowColor = this._shadowColor;
} else {
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.shadowBlur = null;
this.ctx.shadowColor = null;
}
if (this.img) {
if (this._showRect) {
const rect = this._showRect;
this.ctx.drawImage(this.img, rect.x, rect.y, rect.width, rect.height, this._offX, this._offY + rect.y, this.width, rect.height);
} else {
this.ctx.drawImage(this.img, this._offX, this._offY);
}
}
}
updateChildren() {
if (this.children.length <= 0) { return; }
for (const child of this.children) {
if (child === this) {
if (this.visible) {
this.drawSelf();
}
} else {
child.update();
}
}
}
load(url, anchorX = 0.5, anchorY = 0.5) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
}).then(img => {
this.init(img, anchorX, anchorY);
return img;
});
}
addChild(child, z = 1) {
if (this.children.indexOf(child) === -1) {
this.children.push(child);
child._z = z;
child.parent = this;
}
this.children.sort((a, b) => {
return a._z - b._z;
});
if (this.childDepandAlpha) {
child.alpha = this.alpha;
}
}
removeChild(child) {
const index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
}
}
removeChildren() {
for (let i = 0; i < this.children.length; i++) {
if (this.children[i]) {
if (this.children[i] !== this) {
this.children.splice(i, 1);
i --;
}
}
}
}
_changeChildAlpha(alpha) {
for (const child of this.children) {
if (child !== this) {
child.alpha = alpha;
}
}
}
set btimapFlag(v) {
this._bitmapFlag = v;
}
get btimapFlag() {
return this._bitmapFlag;
}
set alpha(v) {
this._alpha = v;
if (this.childDepandAlpha) {
this._changeChildAlpha(v);
}
}
get alpha() {
return this._alpha;
}
set width(v) {
this._width = v;
this.refreshAnchorOff();
}
get width() {
return this._width;
}
set height(v) {
this._height = v;
this.refreshAnchorOff();
}
get height() {
return this._height;
}
set anchorX(value) {
this._anchorX = value;
this.refreshAnchorOff();
}
get anchorX() {
return this._anchorX;
}
set anchorY(value) {
this._anchorY = value;
this.refreshAnchorOff();
}
get anchorY() {
return this._anchorY;
}
refreshAnchorOff() {
this._offX = -this._width * this.anchorX;
this._offY = -this._height * this.anchorY;
}
setScaleXY(value) {
this.scaleX = this.scaleY = value;
}
getBoundingBox() {
const getParentData = (item) => {
let px = item.x;
let py = item.y;
let sx = item.scaleX;
let sy = item.scaleY;
const parent = item.parent;
if (parent) {
const obj = getParentData(parent);
const _x = obj.px;
const _y = obj.py;
const _sx = obj.sx;
const _sy = obj.sy;
px = _x + item.x * _sx;
py = _y + item.y * _sy;
sx *= _sx;
sy *= _sy;
}
return {px, py, sx, sy};
};
const data = getParentData(this);
const x = data.px + this._offX * Math.abs(data.sx);
const y = data.py + this._offY * Math.abs(data.sy);
const width = this.width * Math.abs(data.sx);
const height = this.height * Math.abs(data.sy);
return {x, y, width, height};
}
}
export class RoundSprite extends MySprite {
_newCtx;
init(imgObj = null, anchorX: number = 0.5, anchorY: number = 0.5) {
if (imgObj) {
this.img = imgObj;
this.width = this.img.width;
this.height = this.img.height;
}
this.anchorX = anchorX;
this.anchorY = anchorY;
const canvas = window['curCanvas'];
const w = canvas.nativeElement.width;
const h = canvas.nativeElement.height;
this._offCanvas = document.createElement('canvas');
this._offCanvas.width = w;
this._offCanvas.height = h;
this._offCtx = this._offCanvas.getContext('2d');
// this._newCtx = this.ctx;
// this.ctx = this._offCtx;
}
drawSelf() {
//
// if (this._shadowFlag) {
//
// this.ctx.shadowOffsetX = this._shadowOffsetX;
// this.ctx.shadowOffsetY = this._shadowOffsetY;
// this.ctx.shadowBlur = this._shadowBlur;
// this.ctx.shadowColor = this._shadowColor;
// } else {
// this.ctx.shadowOffsetX = 0;
// this.ctx.shadowOffsetY = 0;
// this.ctx.shadowBlur = null;
// this.ctx.shadowColor = null;
// }
if (this._radius) {
const r = this._radius;
const w = this.width;
const h = this.height;
const x = -this._offX;
const y = -this._offY;
this._offCtx.lineTo(x - w / 2, y + h / 2); // 创建水平线
this._offCtx.arcTo(x - w / 2, y - h / 2, x - w / 2 + r, y - h / 2, r);
this._offCtx.arcTo(x + w / 2, y - h / 2, x + w / 2, y - h / 2 + r, r);
this._offCtx.arcTo(x + w / 2, y + h / 2, x + w / 2 - r, y + h / 2, r);
this._offCtx.arcTo(x - w / 2, y + h / 2, x - w / 2, y + h / 2 - r, r);
this._offCtx.clip();
}
if (this.img) {
this._offCtx.drawImage(this.img, 0, 0);
this.ctx.drawImage(this._offCanvas,this._offX, this._offX);
}
}
}
export class ColorSpr extends MySprite {
r = 0;
g = 0;
b = 0;
createGSCanvas() {
if (!this.img) {
return;
}
const rect = this.getBoundingBox();
if (rect.width <= 1 || rect.height <= 1) {
return;
}
const c = this.ctx.getImageData(rect.x, rect.y, rect.width, rect.height);
for ( let i = 0; i < c.height; i++) {
for ( let j = 0; j < c.width; j++) {
const x = (i * 4) * c.width + ( j * 4 );
const r = c.data[x];
const g = c.data[x + 1];
const b = c.data[x + 2];
c.data[x] = this.r;
c.data[x + 1] = this.g;
c.data[x + 2] = this.b;
// c.data[x] = c.data[x + 1] = c.data[x + 2] = (r + g + b) / 3 ;
// // c.data[x + 3] = 255;
}
}
this.ctx.putImageData(c, rect.x, rect.y, 0, 0, rect.width, rect.height);
}
drawSelf() {
super.drawSelf();
this.createGSCanvas();
}
}
export class GrayscaleSpr extends MySprite {
grayScale = 120;
createGSCanvas() {
if (!this.img) {
return;
}
const rect = this.getBoundingBox();
const c = this.ctx.getImageData(rect.x, rect.y, rect.width, rect.height);
for ( let i = 0; i < c.height; i++) {
for ( let j = 0; j < c.width; j++) {
const x = (i * 4) * c.width + ( j * 4 );
const r = c.data[x];
const g = c.data[x + 1];
const b = c.data[x + 2];
// const a = c.data[x + 3];
c.data[x] = c.data[x + 1] = c.data[x + 2] = this.grayScale; // (r + g + b) / 3;
// c.data[x + 3] = 255;
}
}
this.ctx.putImageData(c, rect.x, rect.y, 0, 0, rect.width, rect.height);
}
drawSelf() {
super.drawSelf();
this.createGSCanvas();
}
}
export class BitMapLabel extends MySprite {
labelArr;
baseUrl;
setText(data, text) {
this.labelArr = [];
const labelArr = [];
const tmpArr = text.split('');
let totalW = 0;
let h = 0;
for (const tmp of tmpArr) {
const label = new MySprite(this.ctx);
label.init(data[tmp], 0);
this.addChild(label);
labelArr.push(label);
totalW += label.width;
h = label.height;
}
this.width = totalW;
this.height = h;
let offX = -totalW / 2;
for (const label of labelArr) {
label.x = offX;
offX += label.width;
}
this.labelArr = labelArr;
}
}
export class Label extends MySprite {
private _text: string;
// fontSize:String = '40px';
fontName = 'Verdana';
textAlign = 'left';
fontSize = 40;
fontColor = '#000000';
fontWeight = 900;
_maxWidth;
outline = 0;
outlineColor = '#ffffff';
// _shadowFlag = false;
// _shadowColor;
// _shadowOffsetX;
// _shadowOffsetY;
// _shadowBlur;
_outlineFlag = false;
_outLineWidth;
_outLineColor;
constructor(ctx = null) {
super(ctx);
this.init();
}
get text(): string {
return this._text;
}
set text(value: string) {
this._text = value;
this.refreshSize();
}
refreshSize() {
this.ctx.save();
this.ctx.font = `${this.fontSize * this.scaleX}px ${this.fontName}`;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
this.ctx.fontWeight = this.fontWeight;
this._width = this.ctx.measureText(this.text).width;
this._height = this.fontSize;
this.refreshAnchorOff();
this.ctx.restore();
}
setMaxSize(w) {
this._maxWidth = w;
this.refreshSize();
if (this.width >= w) {
this.scaleX *= w / this.width;
this.scaleY *= w / this.width;
}
}
show(callBack = null) {
this.visible = true;
if (this.alpha >= 1) {
return;
}
const tween = new TWEEN.Tween(this)
.to({ alpha: 1 }, 800)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
})
.start(); // Start the tween immediately.
}
setOutline(width = 5, color = '#ffffff') {
this._outlineFlag = true;
this._outLineWidth = width;
this._outLineColor = color;
}
drawText() {
if (!this.text) { return; }
this.ctx.font = `${this.fontSize}px ${this.fontName}`;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
this.ctx.fontWeight = this.fontWeight;
if (this._outlineFlag) {
this.ctx.lineWidth = this._outLineWidth;
this.ctx.strokeStyle = this._outLineColor;
this.ctx.strokeText(this.text, 0, 0);
}
this.ctx.fillStyle = this.fontColor;
if (this.outline > 0) {
this.ctx.lineWidth = this.outline;
this.ctx.strokeStyle = this.outlineColor;
this.ctx.strokeText(this.text, 0, 0);
}
this.ctx.fillText(this.text, 0, 0);
}
drawSelf() {
super.drawSelf();
this.drawText();
}
}
export class RichTextOld extends Label {
textArr = [];
fontSize = 40;
setText(text: string, words) {
let newText = text;
for (const word of words) {
const re = new RegExp(word, 'g');
newText = newText.replace( re, `#${word}#`);
// newText = newText.replace(word, `#${word}#`);
}
this.textArr = newText.split('#');
this.text = newText;
// this.setSize();
}
refreshSize() {
this.ctx.save();
this.ctx.font = `${this.fontSize}px ${this.fontName}`;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
this.ctx.fontWeight = this.fontWeight;
let curX = 0;
for (const text of this.textArr) {
const w = this.ctx.measureText(text).width;
curX += w;
}
this.width = curX;
this.height = this.fontSize;
this.refreshAnchorOff();
this.ctx.restore();
}
show(callBack = null) {
// console.log(' in show ');
this.visible = true;
// this.alpha = 0;
const tween = new TWEEN.Tween(this)
.to({ alpha: 1 }, 800)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
})
.start(); // Start the tween immediately.
}
drawText() {
// console.log('in drawText', this.text);
if (!this.text) { return; }
this.ctx.font = `${this.fontSize}px ${this.fontName}`;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
this.ctx.fontWeight = 900;
this.ctx.lineWidth = 5;
this.ctx.strokeStyle = '#ffffff';
// this.ctx.strokeText(this.text, 0, 0);
this.ctx.fillStyle = '#000000';
// this.ctx.fillText(this.text, 0, 0);
let curX = 0;
for (let i = 0; i < this.textArr.length; i++) {
const w = this.ctx.measureText(this.textArr[i]).width;
if ((i + 1) % 2 === 0) {
this.ctx.fillStyle = '#c8171e';
} else {
this.ctx.fillStyle = '#000000';
}
this.ctx.fillText(this.textArr[i], curX, 0);
curX += w;
}
}
}
export class RichText extends Label {
disH = 30;
constructor(ctx?: any) {
super(ctx);
// this.dataArr = dataArr;
}
drawText() {
if (!this.text) {
return;
}
this.ctx.font = `${this.fontSize}px ${this.fontName}`;
this.ctx.textAlign = this.textAlign;
this.ctx.textBaseline = 'middle';
this.ctx.fontWeight = this.fontWeight;
this.ctx.fillStyle = this.fontColor;
const selfW = this.width * this.scaleX;
const chr = this.text.split(' ');
let temp = '';
const row = [];
const w = selfW - 80;
const disH = (this.fontSize + this.disH) * this.scaleY;
for (const c of chr) {
if (this.ctx.measureText(temp).width < w && this.ctx.measureText(temp + (c)).width <= w) {
temp += ' ' + c;
} else {
row.push(temp);
temp = ' ' + c;
}
}
row.push(temp);
const x = 0;
const y = -row.length * disH / 2;
// for (let b = 0 ; b < row.length; b++) {
// this.ctx.strokeText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
// }
if (this._outlineFlag) {
this.ctx.lineWidth = this._outLineWidth;
this.ctx.strokeStyle = this._outLineColor;
for (let b = 0 ; b < row.length; b++) {
this.ctx.strokeText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
}
// this.ctx.strokeText(this.text, 0, 0);
}
// this.ctx.fillStyle = '#ff7600';
for (let b = 0 ; b < row.length; b++) {
this.ctx.fillText(row[b], x, y + ( b + 1 ) * disH ); // 每行字体y坐标间隔20
}
}
drawSelf() {
super.drawSelf();
this.drawText();
}
}
export class LineRect extends MySprite {
lineColor = '#ffffff';
lineWidth = 10;
setSize(w, h) {
this.width = w;
this.height = h;
}
drawLine() {
this.ctx.beginPath();
this.ctx.moveTo(this._offX, this._offY);
this.ctx.lineTo(this._offX + this.width, this._offY);
this.ctx.lineTo(this._offX + this.width, this._offY + this.height);
this.ctx.lineTo(this._offX, this._offY + this.height);
this.ctx.closePath();
this.ctx.lineWidth = this.lineWidth;
// this.ctx.fillStyle = "rgb(2,33,42)"; //指定填充颜色
// this.ctx.fill(); //对多边形进行填充
this.ctx.strokeStyle = this.lineColor; // "#ffffff";
this.ctx.stroke();
}
drawSelf() {
super.drawSelf();
this.drawLine();
}
}
export class ShapeRect extends MySprite {
fillColor = '#FF0000';
setSize(w, h) {
this.width = w;
this.height = h;
// console.log('w:', w);
// console.log('h:', h);
}
drawShape() {
this.ctx.fillStyle = this.fillColor;
this.ctx.fillRect(this._offX, this._offY, this.width, this.height);
}
drawSelf() {
super.drawSelf();
this.drawShape();
}
}
export class Line extends MySprite {
lineWidth = 5;
lineColor = '#000000';
_pointArr = [];
roundFlag = true;
_pointS = 1;
imgObj;
bitMap;
_offCtx;
_offCanvas;
lastPointIndex = 0;
init() {
const canvas = window['curCanvas'];
const w = canvas.nativeElement.width;
const h = canvas.nativeElement.height;
console.log('w: ', w);
console.log('h: ', h);
this._offCanvas = document.createElement('canvas');
this._offCanvas.width = w;
this._offCanvas.height = h;
// this._offCanvas = _offCanvas;
// this._offCtx = this._offCanvas.getContext('2d');
// this._offCanvas = new OffscreenCanvas(w, h);
this._offCtx = this._offCanvas.getContext('2d');
}
addPoint(x, y) {
this._pointArr.push([x, y]);
if (this._pointArr.length < 2) {
return;
}
//
// const lastP = this._pointArr[this._pointArr.length - 1];
//
//
// const context = this._offCtx;
// context.moveTo (lastP[0], lastP[1]); // 设置起点状态
// context.lineTo (x, y); // 设置末端状态
//
// context.lineWidth = this.lineWidth; //设置线宽状态
// context.strokeStyle = this.lineColor;
// context.stroke();
//
//
// this.bitMap = this._offCanvas.transferToImageBitmap();
// const tmpLine = new MySprite(this._offCtx);
// tmpLine.init(this.imgObj);
// tmpLine.anchorY = 1;
// tmpLine.anchorX = 0.5;
// tmpLine.x = lastP[0];
// tmpLine.y = lastP[1];
//
// const disH = getPosDistance(lastP[0], lastP[1], x, y);
// tmpLine.scaleX = this.lineWidth / tmpLine.width;
// tmpLine.scaleY = disH / tmpLine.height * 1.1;
//
// const angle = getAngleByPos(lastP[0], lastP[1], x, y);
// tmpLine.rotation = angle;
//
// this.addChild(tmpLine);
}
setPointArr(arr, imgObj) {
this.removeChildren();
if (arr.length < 2) {
return;
}
let p1 = arr[0];
let p2;
for (let i = 1; i < arr.length; i++) {
p2 = arr[i];
const tmpLine = new MySprite();
tmpLine.init(imgObj);
tmpLine.anchorY = 1;
tmpLine.anchorX = 0.5;
tmpLine.x = p1[0];
tmpLine.y = p1[1];
const disH = getPosDistance(p1[0], p1[1], p2[0], p2[1]);
tmpLine.scaleX = this.lineWidth / tmpLine.width;
tmpLine.scaleY = disH / tmpLine.height * 1.1;
const angle = getAngleByPos(p1[0], p1[1], p2[0], p2[1]);
tmpLine.rotation = angle;
this.addChild(tmpLine);
p1 = p2;
}
}
drawLine() {
if (this._pointArr.length < 2) {
return;
}
const curMaxPointIndex = this._pointArr.length - 1;
if (curMaxPointIndex > this.lastPointIndex) {
const arr = this._pointArr;
const context = this._offCtx;
context.moveTo (arr[this.lastPointIndex][0] * this._pointS, arr[this.lastPointIndex][1] * this._pointS); // 设置起点状态
for (let i = this.lastPointIndex + 1; i < arr.length; i++) {
context.lineTo (arr[i][0] * this._pointS, arr[i][1] * this._pointS); // 设置末端状态
}
if (this.roundFlag) {
context.lineCap = "round";
}
context.lineWidth = this.lineWidth; //设置线宽状态
context.strokeStyle = this.lineColor;
context.stroke();
this.lastPointIndex = curMaxPointIndex;
// this.bitMap = this._offCanvas.transferToImageBitmap();
}
// this.ctx.drawImage(this.bitMap, this._offX, this._offY);
this.ctx.drawImage(this._offCanvas, this._offX, this._offY);
}
drawSelf() {
super.drawSelf();
this.drawLine();
// if (this.img) {
// this.ctx.drawImage(this._offCanvas, 0, 0, this.width, this.height);
// }
// if (this.bitMap) {
// this.bitMap = this._offCanvas.transferToImageBitmap();
// this.ctx.drawImage(this.bitMap, 0, 0, this.width, this.height);
// }
}
}
export class ShapeCircle extends MySprite {
fillColor = '#FF0000';
radius = 0;
setRadius(r) {
this.anchorX = this.anchorY = 0.5;
this.radius = r;
this.width = r * 2;
this.height = r * 2;
}
drawShape() {
this.ctx.beginPath();
this.ctx.fillStyle = this.fillColor;
this.ctx.arc(0, 0, this.radius, 0, angleToRadian(360));
this.ctx.fill();
}
drawSelf() {
super.drawSelf();
this.drawShape();
}
}
export class ShapeRectNew extends MySprite {
radius = 0;
fillColor = '#ffffff';
strokeColor = '#000000';
fill = true;
stroke = false;
lineWidth = 1;
setSize(w, h, r) {
this.width = w;
this.height = h;
this.radius = r;
}
setOutLine(color, lineWidth) {
this.stroke = true;
this.strokeColor = color;
this.lineWidth = lineWidth;
}
drawShape() {
const ctx = this.ctx;
const width = this.width;
const height = this.height;
const radius = this.radius;
ctx.save();
ctx.beginPath(0);
// 从右下角顺时针绘制,弧度从0到1/2PI
ctx.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
// 矩形下边线
ctx.lineTo(radius, height);
// 左下角圆弧,弧度从1/2PI到PI
ctx.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
// 矩形左边线
ctx.lineTo(0, radius);
// 左上角圆弧,弧度从PI到3/2PI
ctx.arc(radius, radius, radius, Math.PI, Math.PI * 3 / 2);
// 上边线
ctx.lineTo(width - radius, 0);
// 右上角圆弧
ctx.arc(width - radius, radius, radius, Math.PI * 3 / 2, Math.PI * 2);
// 右边线
ctx.lineTo(width, height - radius);
ctx.closePath();
if (this.fill) {
ctx.fillStyle = this.fillColor;
ctx.fill();
}
if (this.stroke) {
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.strokeColor;
ctx.stroke();
}
ctx.restore();
}
drawSelf() {
super.drawSelf();
this.drawShape();
}
}
export class MyAnimation extends MySprite {
frameArr = [];
frameIndex = 0;
playFlag = false;
lastDateTime;
curDelay = 0;
loop = false;
playEndFunc;
delayPerUnit = 1;
restartFlag = false;
reverseFlag = false;
addFrameByImg(img) {
const spr = new MySprite(this.ctx);
spr.init(img);
this._refreshSize(img);
spr.visible = false;
this.addChild(spr);
this.frameArr.push(spr);
this.frameArr[this.frameIndex].visible = true;
}
addFrameByUrl(url) {
const spr = new MySprite(this.ctx);
spr.load(url).then(img => {
this._refreshSize(img);
});
spr.visible = false;
this.addChild(spr);
this.frameArr.push(spr);
this.frameArr[this.frameIndex].visible = true;
}
_refreshSize(img: any) {
if (this.width < img.width) {
this.width = img.width;
}
if (this.height < img.height) {
this.height = img.height;
}
}
play() {
this.playFlag = true;
this.lastDateTime = new Date().getTime();
}
stop() {
this.playFlag = false;
}
replay() {
this.restartFlag = true;
this.play();
}
reverse() {
this.reverseFlag = !this.reverseFlag;
this.frameArr.reverse();
this.frameIndex = 0;
}
showAllFrame() {
for (const frame of this.frameArr ) {
frame.alpha = 1;
}
}
hideAllFrame() {
for (const frame of this.frameArr) {
frame.alpha = 0;
}
}
playEnd() {
this.playFlag = false;
this.curDelay = 0;
this.frameArr[this.frameIndex].visible = true;
if (this.playEndFunc) {
this.playEndFunc();
this.playEndFunc = null;
}
}
updateFrame() {
if (this.frameArr[this.frameIndex]) {
this.frameArr[this.frameIndex].visible = false;
}
this.frameIndex ++;
if (this.frameIndex >= this.frameArr.length) {
if (this.loop) {
this.frameIndex = 0;
} else if (this.restartFlag) {
this.restartFlag = false;
this.frameIndex = 0;
} else {
this.frameIndex -- ;
this.playEnd();
return;
}
}
this.frameArr[this.frameIndex].visible = true;
}
_updateDelay(delay) {
this.curDelay += delay;
if (this.curDelay < this.delayPerUnit) {
return;
}
this.curDelay -= this.delayPerUnit;
this.updateFrame();
}
_updateLastDate() {
if (!this.playFlag) { return; }
let delay = 0;
if (this.lastDateTime) {
delay = (new Date().getTime() - this.lastDateTime) / 1000;
}
this.lastDateTime = new Date().getTime();
this._updateDelay(delay);
}
update($event: any = null) {
super.update($event);
this._updateLastDate();
}
}
// --------=========== util func =============-------------
export function tweenChange(item, obj, time = 0.8, callBack = null, easing = null, update = null) {
const tween = new TWEEN.Tween(item).to(obj, time * 1000);
if (callBack) {
tween.onComplete(() => {
callBack();
});
}
if (easing) {
tween.easing(easing);
}
if (update) {
tween.onUpdate( (a, b) => {
update(a, b);
});
}
tween.start();
return tween;
}
export function rotateItem(item, rotation, time = 0.8, callBack = null, easing = null) {
const tween = new TWEEN.Tween(item).to({ rotation }, time * 1000);
if (callBack) {
tween.onComplete(() => {
callBack();
});
}
if (easing) {
tween.easing(easing);
}
tween.start();
}
export function scaleItem(item, scale, time = 0.8, callBack = null, easing = null) {
const tween = new TWEEN.Tween(item).to({ scaleX: scale, scaleY: scale}, time * 1000);
if (callBack) {
tween.onComplete(() => {
callBack();
});
}
if (easing) {
tween.easing(easing);
}
tween.start();
return tween;
}
export function moveItem(item, x, y, time = 0.8, callBack = null, easing = null) {
const tween = new TWEEN.Tween(item).to({ x, y}, time * 1000);
if (callBack) {
tween.onComplete(() => {
callBack();
});
}
if (easing) {
tween.easing(easing);
}
tween.start();
return tween;
}
export function endShow(item, s = 1) {
item.scaleX = item.scaleY = 0;
item.alpha = 0;
const tween = new TWEEN.Tween(item)
.to({ alpha: 1, scaleX: s, scaleY: s }, 800)
.easing(TWEEN.Easing.Elastic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
})
.start();
}
export function hideItem(item, time = 0.8, callBack = null, easing = null) {
if (item.alpha === 0) {
return;
}
const tween = new TWEEN.Tween(item)
.to({alpha: 0}, time * 1000)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
});
if (easing) {
tween.easing(easing);
}
tween.start();
}
export function showItem(item, time = 0.8, callBack = null, easing = null) {
if (item.alpha === 1) {
if (callBack) {
callBack();
}
return;
}
item.visible = true;
const tween = new TWEEN.Tween(item)
.to({alpha: 1}, time * 1000)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
});
if (easing) {
tween.easing(easing);
}
tween.start();
}
export function alphaItem(item, alpha, time = 0.8, callBack = null, easing = null) {
const tween = new TWEEN.Tween(item)
.to({alpha}, time * 1000)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
});
if (easing) {
tween.easing(easing);
}
tween.start();
}
export function showStar(item, time = 0.8, callBack = null, easing = null) {
const tween = new TWEEN.Tween(item)
.to({alpha: 1, scale: 1}, time * 1000)
// .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
.onComplete(() => {
if (callBack) {
callBack();
}
});
if (easing) {
tween.easing(easing);
}
tween.start();
}
export function randomSortByArr(arr) {
if (!arr) {
return;
}
const newArr = [];
const tmpArr = arr.concat();
while (tmpArr.length > 0) {
const randomIndex = Math.floor( tmpArr.length * Math.random() );
newArr.push(tmpArr[randomIndex]);
tmpArr.splice(randomIndex, 1);
}
return newArr;
}
export function radianToAngle(radian) {
return radian * 180 / Math.PI;
// 角度 = 弧度 * 180 / Math.PI;
}
export function angleToRadian(angle) {
return angle * Math.PI / 180;
// 弧度= 角度 * Math.PI / 180;
}
export function getPosByAngle(angle, len) {
const radian = angle * Math.PI / 180;
const x = Math.sin(radian) * len;
const y = Math.cos(radian) * len;
return {x, y};
}
export function getAngleByPos(px, py, mx, my) {
const x = Math.abs(px - mx);
const y = Math.abs(py - my);
const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
const cos = y / z;
const radina = Math.acos(cos); // 用反三角函数求弧度
let angle = Math.floor(180 / (Math.PI / radina) * 100) / 100; // 将弧度转换成角度
if (mx > px && my > py) {// 鼠标在第四象限
angle = 180 - angle;
}
if (mx === px && my > py) {// 鼠标在y轴负方向上
angle = 180;
}
if (mx > px && my === py) {// 鼠标在x轴正方向上
angle = 90;
}
if (mx < px && my > py) {// 鼠标在第三象限
angle = 180 + angle;
}
if (mx < px && my === py) {// 鼠标在x轴负方向
angle = 270;
}
if (mx < px && my < py) {// 鼠标在第二象限
angle = 360 - angle;
}
// console.log('angle: ', angle);
return angle;
}
export function removeItemFromArr(arr, item) {
const index = arr.indexOf(item);
if (index !== -1) {
arr.splice(index, 1);
}
}
export function circleMove(item, x0, y0, time = 2, addR = 360, xPer = 1, yPer = 1, callBack = null, easing = null) {
const r = getPosDistance(item.x, item.y, x0, y0);
let a = getAngleByPos(item.x, item.y, x0, y0);
a += 90;
const obj = {r, a};
item._circleAngle = a;
const targetA = a + addR;
const tween = new TWEEN.Tween(item).to({_circleAngle: targetA}, time * 1000);
if (callBack) {
tween.onComplete(() => {
callBack();
});
}
if (easing) {
tween.easing(easing);
}
tween.onUpdate( (item, progress) => {
// console.log(item._circleAngle);
const r = obj.r;
const a = item._circleAngle;
const x = x0 + r * xPer * Math.cos(a * Math.PI / 180);
const y = y0 + r * yPer * Math.sin(a * Math.PI / 180);
item.x = x;
item.y = y;
// obj.a ++;
});
tween.start();
}
export function getPosDistance(sx, sy, ex, ey) {
const _x = ex - sx;
const _y = ey - sy;
const len = Math.sqrt( Math.pow(_x, 2) + Math.pow(_y, 2) );
return len;
}
export function delayCall(callback, second) {
const tween = new TWEEN.Tween(this)
.delay(second * 1000)
.onComplete(() => {
if (callback) {
callback();
}
})
.start();
}
export function formatTime(fmt, date) {
// "yyyy-MM-dd HH:mm:ss";
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds() // 毫秒
};
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); }
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1)
? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return fmt;
}
export function getMinScale(item, maxLen) {
const sx = maxLen / item.width;
const sy = maxLen / item.height;
const minS = Math.min(sx, sy);
return minS;
}
export function jelly(item, time = 0.7) {
if (item.jellyTween) {
TWEEN.remove(item.jellyTween);
}
const t = time / 9;
const baseSX = item.scaleX;
const baseSY = item.scaleY;
let index = 0;
const run = () => {
if (index >= arr.length) {
item.jellyTween = null;
return;
}
const data = arr[index];
const t = tweenChange(item, {scaleX: data[0], scaleY: data[1]}, data[2], () => {
index ++;
run();
}, TWEEN.Easing.Sinusoidal.InOut);
item.jellyTween = t;
};
const arr = [
[baseSX * 1.1, baseSY * 0.9, t],
[baseSX * 0.98, baseSY * 1.02, t * 2],
[baseSX * 1.02, baseSY * 0.98, t * 2],
[baseSX * 0.99, baseSY * 1.01, t * 2],
[baseSX * 1.0, baseSY * 1.0, t * 2],
];
run();
}
export function showPopParticle(img, pos, parent, num = 20, minLen = 40, maxLen = 80, showTime = 0.4) {
for (let i = 0; i < num; i ++) {
const particle = new MySprite();
particle.init(img);
particle.x = pos.x;
particle.y = pos.y;
parent.addChild(particle);
const randomR = 360 * Math.random();
particle.rotation = randomR;
const randomS = 0.3 + Math.random() * 0.7;
particle.setScaleXY(randomS * 0.3);
const randomX = Math.random() * 20 - 10;
particle.x += randomX;
const randomY = Math.random() * 20 - 10;
particle.y += randomY;
const randomL = minLen + Math.random() * (maxLen - minLen);
const randomA = 360 * Math.random();
const randomT = getPosByAngle(randomA, randomL);
moveItem(particle, particle.x + randomT.x, particle.y + randomT.y, showTime, () => {
}, TWEEN.Easing.Exponential.Out);
// scaleItem(particle, 0, 0.6, () => {
//
// });
scaleItem(particle, randomS, 0.6, () => {
}, TWEEN.Easing.Exponential.Out);
setTimeout(() => {
hideItem(particle, 0.4, () => {
}, TWEEN.Easing.Cubic.In);
}, showTime * 0.5 * 1000);
}
}
export function shake(item, time = 0.5, callback = null, rate = 1) {
if (item.shakeTween) {
return;
}
item.shakeTween = true;
const offX = 15 * item.scaleX * rate;
const offY = 15 * item.scaleX * rate;
const baseX = item.x;
const baseY = item.y;
const easing = TWEEN.Easing.Sinusoidal.InOut;
const move4 = () => {
moveItem(item, baseX, baseY, time / 4, () => {
item.shakeTween = false;
if (callback) {
callback();
}
}, easing);
};
const move3 = () => {
moveItem(item, baseX + offX / 4, baseY + offY / 4, time / 4, () => {
move4();
}, easing);
};
const move2 = () => {
moveItem(item, baseX - offX / 4 * 3, baseY - offY / 4 * 3, time / 4, () => {
move3();
}, easing);
};
const move1 = () => {
moveItem(item, baseX + offX, baseY + offY, time / 7.5, () => {
move2();
}, easing);
};
move1();
}
// --------------- custom class --------------------
export class HotZoneItem extends MySprite {
lineDashFlag = false;
arrow: MySprite;
label: Label;
title;
arrowTop;
arrowRight;
audio_url;
pic_url;
text;
private _itemType;
private shapeRect: ShapeRect;
get itemType() {
return this._itemType;
}
set itemType(value) {
this._itemType = value;
}
setSize(w, h) {
this.width = w;
this.height = h;
const rect = new ShapeRect(this.ctx);
rect.x = -w / 2;
rect.y = -h / 2;
rect.setSize(w, h);
rect.fillColor = '#ffffff';
rect.alpha = 0.2;
this.addChild(rect);
}
showLabel(text = null) {
if (!this.label) {
this.label = new Label(this.ctx);
this.label.anchorY = 0;
this.label.fontSize = 40;
this.label.textAlign = 'center';
this.addChild(this.label);
// this.label.scaleX = 1 / this.scaleX;
// this.label.scaleY = 1 / this.scaleY;
this.refreshLabelScale();
}
if (text) {
this.label.text = text;
} else if (this.title) {
this.label.text = this.title;
}
this.label.visible = true;
}
hideLabel() {
if (!this.label) { return; }
this.label.visible = false;
}
refreshLabelScale() {
if (this.scaleX == this.scaleY) {
this.label.setScaleXY(1);
}
if (this.scaleX > this.scaleY) {
this.label.scaleX = this.scaleY / this.scaleX;
} else {
this.label.scaleY = this.scaleX / this.scaleY;
}
}
showLineDash() {
this.lineDashFlag = true;
if (this.arrow) {
this.arrow.visible = true;
} else {
this.arrow = new MySprite(this.ctx);
this.arrow.load('assets/common/arrow.png', 1, 0);
this.arrow.setScaleXY(0.06);
this.arrowTop = new MySprite(this.ctx);
this.arrowTop.load('assets/common/arrow_top.png', 0.5, 0);
this.arrowTop.setScaleXY(0.06);
this.arrowRight = new MySprite(this.ctx);
this.arrowRight.load('assets/common/arrow_right.png', 1, 0.5);
this.arrowRight.setScaleXY(0.06);
}
this.showLabel();
}
hideLineDash() {
this.lineDashFlag = false;
if (this.arrow) {
this.arrow.visible = false;
}
this.hideLabel();
}
drawArrow() {
if (!this.arrow) { return; }
const rect = this.getBoundingBox();
this.arrow.x = rect.x + rect.width;
this.arrow.y = rect.y;
this.arrow.update();
this.arrowTop.x = rect.x + rect.width / 2;
this.arrowTop.y = rect.y;
this.arrowTop.update();
this.arrowRight.x = rect.x + rect.width;
this.arrowRight.y = rect.y + rect.height / 2;
this.arrowRight.update();
}
drawFrame() {
this.ctx.save();
const rect = this.getBoundingBox();
const w = rect.width;
const h = rect.height;
const x = rect.x + w / 2;
const y = rect.y + h / 2;
this.ctx.setLineDash([5, 5]);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#1bfff7';
// this.ctx.fillStyle = '#ffffff';
this.ctx.beginPath();
this.ctx.moveTo( x - w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y - h / 2);
// this.ctx.fill();
this.ctx.stroke();
this.ctx.restore();
}
draw() {
super.draw();
if (this.lineDashFlag) {
this.drawFrame();
this.drawArrow();
}
}
}
export class HotZoneImg extends MySprite {
drawFrame() {
this.ctx.save();
const rect = this.getBoundingBox();
const w = rect.width;
const h = rect.height;
const x = rect.x + w / 2;
const y = rect.y + h / 2;
this.ctx.setLineDash([5, 5]);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#1bfff7';
this.ctx.beginPath();
this.ctx.moveTo( x - w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y - h / 2);
// this.ctx.fill();
this.ctx.stroke();
this.ctx.restore();
}
draw() {
super.draw();
this.drawFrame();
}
}
export class HotZoneLabel extends Label {
drawFrame() {
this.ctx.save();
const rect = this.getBoundingBox();
const w = rect.width / this.scaleX;
const h = this.height * this.scaleY;
const x = this.x;
const y = this.y;
this.ctx.setLineDash([5, 5]);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#1bfff7';
this.ctx.beginPath();
this.ctx.moveTo( x - w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y - h / 2);
// this.ctx.fill();
this.ctx.stroke();
this.ctx.restore();
}
draw() {
super.draw();
this.drawFrame();
}
}
export class EditorItem extends MySprite {
lineDashFlag = false;
arrow: MySprite;
label: Label;
text;
showLabel(text = null) {
if (!this.label) {
this.label = new Label(this.ctx);
this.label.anchorY = 0;
this.label.fontSize = 50;
this.label.textAlign = 'center';
this.addChild(this.label);
this.label.setScaleXY(1 / this.scaleX);
}
if (text) {
this.label.text = text;
} else if (this.text) {
this.label.text = this.text;
}
this.label.visible = true;
}
hideLabel() {
if (!this.label) { return; }
this.label.visible = false;
}
showLineDash() {
this.lineDashFlag = true;
if (this.arrow) {
this.arrow.visible = true;
} else {
this.arrow = new MySprite(this.ctx);
this.arrow.load('assets/common/arrow.png', 1, 0);
this.arrow.setScaleXY(0.06);
}
this.showLabel();
}
hideLineDash() {
this.lineDashFlag = false;
if (this.arrow) {
this.arrow.visible = false;
}
this.hideLabel();
}
drawArrow() {
if (!this.arrow) { return; }
const rect = this.getBoundingBox();
this.arrow.x = rect.x + rect.width;
this.arrow.y = rect.y;
this.arrow.update();
}
drawFrame() {
this.ctx.save();
const rect = this.getBoundingBox();
const w = rect.width;
const h = rect.height;
const x = rect.x + w / 2;
const y = rect.y + h / 2;
this.ctx.setLineDash([5, 5]);
this.ctx.lineWidth = 2;
this.ctx.strokeStyle = '#1bfff7';
// this.ctx.fillStyle = '#ffffff';
this.ctx.beginPath();
this.ctx.moveTo( x - w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y - h / 2);
this.ctx.lineTo(x + w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y + h / 2);
this.ctx.lineTo(x - w / 2, y - h / 2);
// this.ctx.fill();
this.ctx.stroke();
this.ctx.restore();
}
draw() {
super.draw();
if (this.lineDashFlag) {
this.drawFrame();
this.drawArrow();
}
}
}
//
//
// import TWEEN from '@tweenjs/tween.js';
//
//
// class Sprite {
// x = 0;
// y = 0;
// color = '';
// radius = 0;
// alive = false;
// margin = 0;
// angle = 0;
// ctx;
//
// constructor(ctx) {
// this.ctx = ctx;
// }
// update($event) {
// this.draw();
// }
// draw() {
//
// }
//
// }
//
//
//
//
//
// export class MySprite extends Sprite {
//
// width = 0;
// height = 0;
// _anchorX = 0;
// _anchorY = 0;
// _offX = 0;
// _offY = 0;
// scaleX = 1;
// scaleY = 1;
// alpha = 1;
// rotation = 0;
// visible = true;
//
// children = [this];
//
// img;
// _z = 0;
//
//
// init(imgObj = null, anchorX:number = 0.5, anchorY:number = 0.5) {
//
// if (imgObj) {
//
// this.img = imgObj;
//
// this.width = this.img.width;
// this.height = this.img.height;
// }
//
// this.anchorX = anchorX;
// this.anchorY = anchorY;
// }
//
//
//
// update($event = null) {
// if (this.visible) {
// this.draw();
// }
// }
// draw() {
//
// this.ctx.save();
//
// this.drawInit();
//
// this.updateChildren();
//
// this.ctx.restore();
// }
//
// drawInit() {
//
// this.ctx.translate(this.x, this.y);
//
// this.ctx.rotate(this.rotation * Math.PI / 180);
//
// this.ctx.scale(this.scaleX, this.scaleY);
//
// this.ctx.globalAlpha = this.alpha;
//
// }
//
// drawSelf() {
// if (this.img) {
// this.ctx.drawImage(this.img, this._offX, this._offY);
// }
// }
//
// updateChildren() {
//
// if (this.children.length <= 0) { return; }
//
// for (let i = 0; i < this.children.length; i++) {
//
// if (this.children[i] === this) {
//
// this.drawSelf();
// } else {
//
// this.children[i].update();
// }
// }
// }
//
//
// load(url, anchorX = 0.5, anchorY = 0.5) {
//
// return new Promise((resolve, reject) => {
// const img = new Image();
// img.onload = () => resolve(img);
// img.onerror = reject;
// img.src = url;
// }).then(img => {
//
// this.init(img, anchorX, anchorY);
// return img;
// });
// }
//
// addChild(child, z = 1) {
// if (this.children.indexOf(child) === -1) {
// this.children.push(child);
// child._z = z;
// child.parent = this;
// }
//
// this.children.sort((a, b) => {
// return a._z - b._z;
// });
//
// }
// removeChild(child) {
// const index = this.children.indexOf(child);
// if (index !== -1) {
// this.children.splice(index, 1);
// }
// }
//
// set anchorX(value) {
// this._anchorX = value;
// this.refreshAnchorOff();
// }
// get anchorX() {
// return this._anchorX;
// }
// set anchorY(value) {
// this._anchorY = value;
// this.refreshAnchorOff();
// }
// get anchorY() {
// return this._anchorY;
// }
// refreshAnchorOff() {
// this._offX = -this.width * this.anchorX;
// this._offY = -this.height * this.anchorY;
// }
//
// setScaleXY(value) {
// this.scaleX = this.scaleY = value;
// }
//
// getBoundingBox() {
//
// const x = this.x + this._offX * this.scaleX;
// const y = this.y + this._offY * this.scaleY;
// const width = this.width * this.scaleX;
// const height = this.height * this.scaleY;
//
// return {x, y, width, height};
// }
//
// }
//
//
//
//
//
// export class Item extends MySprite {
//
// baseX;
//
// move(targetY, callBack) {
//
// const self = this;
//
// const tween = new TWEEN.Tween(this)
// .to({ y: targetY }, 2500)
// .easing(TWEEN.Easing.Quintic.Out)
// .onComplete(function() {
//
// self.hide(callBack);
// // if (callBack) {
// // callBack();
// // }
// })
// .start();
//
// }
//
// show(callBack = null) {
//
// const tween = new TWEEN.Tween(this)
// .to({ alpha: 1 }, 800)
// // .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
// .onComplete(function() {
// if (callBack) {
// callBack();
// }
// })
// .start(); // Start the tween immediately.
//
// }
//
// hide(callBack = null) {
//
// const tween = new TWEEN.Tween(this)
// .to({ alpha: 0 }, 800)
// // .easing(TWEEN.Easing.Quadratic.Out) // Use an easing function to make the animation smooth.
// .onComplete(function() {
// if (callBack) {
// callBack();
// }
// })
// .start(); // Start the tween immediately.
// }
//
//
// shake(id) {
//
//
// if (!this.baseX) {
// this.baseX = this.x;
// }
//
// const baseX = this.baseX;
// const baseTime = 50;
// const sequence = [
// {target: {x: baseX + 40 * id}, time: baseTime - 25},
// {target: {x: baseX - 20 * id}, time: baseTime},
// {target: {x: baseX + 10 * id}, time: baseTime},
// {target: {x: baseX - 5 * id}, time: baseTime},
// {target: {x: baseX + 2 * id}, time: baseTime},
// {target: {x: baseX - 1 * id}, time: baseTime},
// {target: {x: baseX}, time: baseTime},
//
// ];
//
//
// const self = this;
//
// function runSequence() {
//
// if (self['shakeTween']) {
// self['shakeTween'].stop();
// }
//
// const tween = new TWEEN.Tween(self);
//
// if (sequence.length > 0) {
// // console.log('sequence.length: ', sequence.length);
// const action = sequence.shift();
// tween.to(action['target'], action['time']);
// tween.onComplete( () => {
// runSequence();
// });
// tween.start();
//
// self['shakeTween'] = tween;
// }
// }
//
// runSequence();
//
// }
//
//
//
// drop(targetY, callBack = null) {
//
// const self = this;
//
// const time = Math.abs(targetY - this.y) * 2.4;
//
// this.alpha = 1;
//
// const tween = new TWEEN.Tween(this)
// .to({ y: targetY }, time)
// .easing(TWEEN.Easing.Cubic.In)
// .onComplete(function() {
//
// // self.hideItem(callBack);
// if (callBack) {
// callBack();
// }
// })
// .start();
//
//
// }
//
//
// }
//
//
// export class EndSpr extends MySprite {
//
// show(s) {
//
// this.scaleX = this.scaleY = 0;
// this.alpha = 0;
//
// const tween = new TWEEN.Tween(this)
// .to({ alpha: 1, scaleX: s, scaleY: s }, 800)
// .easing(TWEEN.Easing.Elastic.Out) // Use an easing function to make the animation smooth.
// .onComplete(function() {
//
// })
// .start(); // Start the tween immediately.
//
// }
// }
//
//
//
// export class ShapeRect extends MySprite {
//
// fillColor = '#FF0000';
//
// setSize(w, h) {
// this.width = w;
// this.height = h;
//
// console.log('w:', w);
// console.log('h:', h);
// }
//
// drawShape() {
//
// this.ctx.fillStyle = this.fillColor;
// this.ctx.fillRect(this._offX, this._offY, this.width, this.height);
//
// }
//
//
// drawSelf() {
// super.drawSelf();
// this.drawShape();
// }
// }
//
//
// export class HotZoneItem extends MySprite {
//
//
// lineDashFlag = false;
// arrow: MySprite;
// label: Label;
// title;
//
// arrowTop;
// arrowRight;
//
// audio_url;
// pic_url;
// text;
// private _itemType;
// private shapeRect: ShapeRect;
//
// get itemType() {
// return this._itemType;
// }
// set itemType(value) {
// this._itemType = value;
// }
//
// setSize(w, h) {
// this.width = w;
// this.height = h;
//
//
// const rect = new ShapeRect(this.ctx);
// rect.x = -w / 2;
// rect.y = -h / 2;
// rect.setSize(w, h);
// rect.fillColor = '#ffffff';
// rect.alpha = 0.2;
// this.addChild(rect);
// }
//
// showLabel(text = null) {
//
//
// if (!this.label) {
// this.label = new Label(this.ctx);
// this.label.anchorY = 0;
// this.label.fontSize = '40px';
// this.label.textAlign = 'center';
// this.addChild(this.label);
// // this.label.scaleX = 1 / this.scaleX;
// // this.label.scaleY = 1 / this.scaleY;
//
// this.refreshLabelScale();
//
// }
//
// if (text) {
// this.label.text = text;
// } else if (this.title) {
// this.label.text = this.title;
// }
// this.label.visible = true;
//
// }
//
// hideLabel() {
// if (!this.label) { return; }
//
// this.label.visible = false;
// }
//
// refreshLabelScale() {
// if (this.scaleX == this.scaleY) {
// this.label.setScaleXY(1);
// }
//
// if (this.scaleX > this.scaleY) {
// this.label.scaleX = this.scaleY / this.scaleX;
// } else {
// this.label.scaleY = this.scaleX / this.scaleY;
// }
// }
//
// showLineDash() {
// this.lineDashFlag = true;
//
// if (this.arrow) {
// this.arrow.visible = true;
// } else {
// this.arrow = new MySprite(this.ctx);
// this.arrow.load('assets/common/arrow.png', 1, 0);
// this.arrow.setScaleXY(0.06);
//
// this.arrowTop = new MySprite(this.ctx);
// this.arrowTop.load('assets/common/arrow_top.png', 0.5, 0);
// this.arrowTop.setScaleXY(0.06);
//
// this.arrowRight = new MySprite(this.ctx);
// this.arrowRight.load('assets/common/arrow_right.png', 1, 0.5);
// this.arrowRight.setScaleXY(0.06);
// }
//
// this.showLabel();
// }
//
// hideLineDash() {
//
// this.lineDashFlag = false;
//
// if (this.arrow) {
// this.arrow.visible = false;
// }
//
// this.hideLabel();
// }
//
//
//
// drawArrow() {
// if (!this.arrow) { return; }
//
// const rect = this.getBoundingBox();
// this.arrow.x = rect.x + rect.width;
// this.arrow.y = rect.y;
//
// this.arrow.update();
//
//
// this.arrowTop.x = rect.x + rect.width / 2;
// this.arrowTop.y = rect.y;
// this.arrowTop.update();
//
// this.arrowRight.x = rect.x + rect.width;
// this.arrowRight.y = rect.y + rect.height / 2;
// this.arrowRight.update();
// }
//
// drawFrame() {
//
//
// this.ctx.save();
//
//
// const rect = this.getBoundingBox();
//
// const w = rect.width;
// const h = rect.height;
// const x = rect.x + w / 2;
// const y = rect.y + h / 2;
//
// this.ctx.setLineDash([5, 5]);
// this.ctx.lineWidth = 2;
// this.ctx.strokeStyle = '#1bfff7';
// // this.ctx.fillStyle = '#ffffff';
//
// this.ctx.beginPath();
//
// this.ctx.moveTo( x - w / 2, y - h / 2);
//
// this.ctx.lineTo(x + w / 2, y - h / 2);
//
// this.ctx.lineTo(x + w / 2, y + h / 2);
//
// this.ctx.lineTo(x - w / 2, y + h / 2);
//
// this.ctx.lineTo(x - w / 2, y - h / 2);
//
// // this.ctx.fill();
// this.ctx.stroke();
//
//
//
//
// this.ctx.restore();
//
// }
//
// draw() {
// super.draw();
//
// if (this.lineDashFlag) {
// this.drawFrame();
// this.drawArrow();
// }
// }
// }
//
//
// export class EditorItem extends MySprite {
//
// lineDashFlag = false;
// arrow: MySprite;
// label:Label;
// text;
//
// showLabel(text = null) {
//
//
// if (!this.label) {
// this.label = new Label(this.ctx);
// this.label.anchorY = 0;
// this.label.fontSize = '50px';
// this.label.textAlign = 'center';
// this.addChild(this.label);
// this.label.setScaleXY(1 / this.scaleX);
// }
//
// if (text) {
// this.label.text = text;
// } else if (this.text) {
// this.label.text = this.text;
// }
// this.label.visible = true;
//
// }
//
// hideLabel() {
// if (!this.label) { return; }
//
// this.label.visible = false;
// }
//
// showLineDash() {
// this.lineDashFlag = true;
//
// if (this.arrow) {
// this.arrow.visible = true;
// } else {
// this.arrow = new MySprite(this.ctx);
// this.arrow.load('assets/common/arrow.png', 1, 0);
// this.arrow.setScaleXY(0.06);
//
// }
//
// this.showLabel();
// }
//
// hideLineDash() {
//
// this.lineDashFlag = false;
//
// if (this.arrow) {
// this.arrow.visible = false;
// }
//
// this.hideLabel();
// }
//
//
//
// drawArrow() {
// if (!this.arrow) { return; }
//
// const rect = this.getBoundingBox();
// this.arrow.x = rect.x + rect.width;
// this.arrow.y = rect.y;
//
// this.arrow.update();
// }
//
// drawFrame() {
//
//
// this.ctx.save();
//
//
// const rect = this.getBoundingBox();
//
// const w = rect.width;
// const h = rect.height;
// const x = rect.x + w / 2;
// const y = rect.y + h / 2;
//
// this.ctx.setLineDash([5, 5]);
// this.ctx.lineWidth = 2;
// this.ctx.strokeStyle = '#1bfff7';
// // this.ctx.fillStyle = '#ffffff';
//
// this.ctx.beginPath();
//
// this.ctx.moveTo( x - w / 2, y - h / 2);
//
// this.ctx.lineTo(x + w / 2, y - h / 2);
//
// this.ctx.lineTo(x + w / 2, y + h / 2);
//
// this.ctx.lineTo(x - w / 2, y + h / 2);
//
// this.ctx.lineTo(x - w / 2, y - h / 2);
//
// // this.ctx.fill();
// this.ctx.stroke();
//
//
//
//
// this.ctx.restore();
//
// }
//
// draw() {
// super.draw();
//
// if (this.lineDashFlag) {
// this.drawFrame();
// this.drawArrow();
// }
// }
// }
//
//
//
// export class Label extends MySprite {
//
// text:String;
// fontSize:String = '40px';
// fontName:String = 'Verdana';
// textAlign:String = 'left';
//
//
// constructor(ctx) {
// super(ctx);
// this.init();
// }
//
// drawText() {
//
// // console.log('in drawText', this.text);
//
// if (!this.text) { return; }
//
// this.ctx.font = `${this.fontSize} ${this.fontName}`;
// this.ctx.textAlign = this.textAlign;
// this.ctx.textBaseline = 'middle';
// this.ctx.fontWeight = 900;
//
// this.ctx.lineWidth = 5;
// this.ctx.strokeStyle = '#ffffff';
// this.ctx.strokeText(this.text, 0, 0);
//
// this.ctx.fillStyle = '#000000';
// this.ctx.fillText(this.text, 0, 0);
//
//
// }
//
//
// drawSelf() {
// super.drawSelf();
// this.drawText();
// }
//
// }
//
//
//
// export function getPosByAngle(angle, len) {
//
// const radian = angle * Math.PI / 180;
// const x = Math.sin(radian) * len;
// const y = Math.cos(radian) * len;
//
// return {x, y};
//
// }
//
// export function getAngleByPos(px, py, mx, my) {
//
// // const _x = p2x - p1x;
// // const _y = p2y - p1y;
// // const tan = _y / _x;
// //
// // const radina = Math.atan(tan); // 用反三角函数求弧度
// // const angle = Math.floor(180 / (Math.PI / radina)); //
// //
// // console.log('r: ' , angle);
// // return angle;
// //
//
//
//
// const x = Math.abs(px - mx);
// const y = Math.abs(py - my);
// // const x = Math.abs(mx - px);
// // const y = Math.abs(my - py);
// const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
// const cos = y / z;
// const radina = Math.acos(cos); // 用反三角函数求弧度
// let angle = Math.floor(180 / (Math.PI / radina) * 100) / 100; // 将弧度转换成角度
//
// if(mx > px && my > py) {// 鼠标在第四象限
// angle = 180 - angle;
// }
//
// if(mx === px && my > py) {// 鼠标在y轴负方向上
// angle = 180;
// }
//
// if(mx > px && my === py) {// 鼠标在x轴正方向上
// angle = 90;
// }
//
// if(mx < px && my > py) {// 鼠标在第三象限
// angle = 180 + angle;
// }
//
// if(mx < px && my === py) {// 鼠标在x轴负方向
// angle = 270;
// }
//
// if(mx < px && my < py) {// 鼠标在第二象限
// angle = 360 - angle;
// }
//
// // console.log('angle: ', angle);
// return angle;
//
// }
<div class="p-image-children-editor">
<h5 style="margin-left: 2.5%;"> preview: </h5>
<div class="preview-box" #wrap>
<canvas id="canvas" #canvas></canvas>
</div>
<div nz-row nzType="flex" nzAlign="middle">
<div nz-col nzSpan="5" nzOffset="1">
<h5> add background: </h5>
<div class="bg-box">
<app-upload-image-with-preview
[picUrl]="bgItem?.url"
(imageUploaded)="onBackgroundUploadSuccess($event)">
</app-upload-image-with-preview>
</div>
</div>
<div nz-col nzSpan="5" nzOffset="1" class="img-box"
*ngFor="let it of hotZoneArr; let i = index">
<div
style="margin: auto; padding: 5px; margin-top: 30px; width:90%; border: 2px dashed #ddd; border-radius: 10px">
<span style="margin-left: 40%;"> item-{{i + 1}}
</span>
<button style="float: right;" nz-button nzType="danger" nzSize="small" (click)="deleteBtnClick(i)">
X
</button>
<nz-divider style="margin-top: 10px;"></nz-divider>
<div style="margin-top: -20px; margin-bottom: 5px">
<nz-radio-group [ngModel]="it.itemType" (ngModelChange)="radioChange($event, it)">
<label *ngIf="isHasRect" nz-radio nzValue="rect">矩形</label>
<label *ngIf="isHasPic" nz-radio nzValue="pic">图片</label>
<label *ngIf="isHasText" nz-radio nzValue="text">文本</label>
</nz-radio-group>
</div>
<div *ngIf="it.itemType == 'pic'">
<app-upload-image-with-preview
[picUrl]="it?.pic_url"
(imageUploaded)="onItemImgUploadSuccess($event, it)">
</app-upload-image-with-preview>
</div>
<div *ngIf="it.itemType == 'text'">
<input type="text" nz-input [(ngModel)]="it.text" (blur)="saveText(it)">
</div>
<div style="width: 100%; margin-top: 5px;">
<app-audio-recorder
[audioUrl]="it.audio_url"
(audioUploaded)="onItemAudioUploadSuccess($event, it)"
></app-audio-recorder>
</div>
</div>
</div>
<div nz-col nzSpan="5" nzOffset="1">
<div class="bg-box">
<button nz-button nzType="dashed" (click)="addBtnClick()"
class="add-btn">
<i nz-icon nzType="plus-circle" nzTheme="outline"></i>
<!--Add Image-->
Add hot zone
</button>
</div>
</div>
</div>
<nz-divider></nz-divider>
<div class="save-box">
<button class="save-btn" nz-button nzType="primary" [nzSize]="'large'" nzShape="round"
(click)="saveClick()" >
<i nz-icon nzType="save"></i>
Save
</button>
</div>
</div>
<label style="opacity: 0; position: absolute; top: 0px; font-family: 'BRLNSR_1'">1</label>
.p-image-children-editor {
width: 100%;
height: 100%;
border-radius: 0.5rem;
border: 2px solid #ddd;
.preview-box {
margin: auto;
width: 95%;
height: 35vw;
border: 2px dashed #ddd;
border-radius: 0.5rem;
background-color: #fafafa;
text-align: center;
color: #aaa;
.preview-img {
height: 100%;
width: auto;
}
}
.bg-box{
//width: 100%;
margin-bottom: 1rem;
}
.img-box {
margin-bottom: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.img-box-upload{
width: 80%;
}
.add-btn {
margin-top: 1rem;
width: 200px;
height: 90px;
display: flex;
align-items: center;
justify-content: center;
}
}
.save-box {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.save-btn {
width: 160px;
height: 40px;
//margin-top: 20px;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
}
}
h5 {
margin-top: 1rem;
}
@font-face
{
font-family: 'BRLNSR_1';
src: url("/assets/font/BRLNSR_1.TTF") ;
}
//@import '../../../style/common_mixin';
//
//.p-image-uploader {
// position: relative;
// display: block;
// width: 100%;
// height: 0;
// padding-bottom: 56.25%;
// .p-box {
// position: absolute;
// left: 0;
// top: 0;
// right: 0;
// bottom: 0;
// border: 2px dashed #ddd;
// border-radius: 0.5rem;
// 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");
// @include k-img-bg();
// }
// }
//}
//
//.p-btn-delete {
// position: absolute;
// right: -0.5rem;
// top: -0.5rem;
// width: 2rem;
// height: 2rem;
// border: 0.2rem solid white;
// border-radius: 50%;
// font-size: 1.2rem;
// background-color: #fb781a;
// color: white;
// text-align: center;
//}
//
//.p-upload-progress-bg {
// position: absolute;
// width: 100%;
// height: 100%;
// display: flex;
// align-items: center;
// justify-content: center;
// & .i-text {
// position: absolute;
// text-align: center;
// color: white;
// text-shadow: 0 0 2px rgba(0, 0, 0, .85);
// }
// & .i-bg {
// position: absolute;
// left: 0;
// top: 0;
// height: 100%;
// background-color: #27b43f;
// border-radius: 0.5rem;
// }
//}
//
//
//:host ::ng-deep .ant-upload {
// display: block;
//}
import {
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
ViewChild
} from '@angular/core';
import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators';
import {EditorItem, HotZoneImg, HotZoneItem, HotZoneLabel, Label, MySprite, removeItemFromArr} from './Unit';
import TWEEN from '@tweenjs/tween.js';
import {getMinScale} from "../../play/Unit";
import {tar} from "compressing";
@Component({
selector: 'app-custom-hot-zone',
templateUrl: './custom-hot-zone.component.html',
styleUrls: ['./custom-hot-zone.component.scss']
})
export class CustomHotZoneComponent implements OnInit, OnDestroy, OnChanges {
@Input()
imgItemArr = null;
@Input()
hotZoneItemArr = null;
@Input()
hotZoneArr = null;
@Output()
save = new EventEmitter();
@ViewChild('canvas', {static: true}) canvas: ElementRef;
@ViewChild('wrap', {static: true}) wrap: ElementRef;
@Input()
isHasRect = true;
@Input()
isHasPic = true;
@Input()
isHasText = true;
@Input()
hotZoneFontObj = {
size: 50,
name: 'BRLNSR_1',
color: '#8f3758'
}
@Input()
defaultItemType = 'text';
@Input()
hotZoneImgSize = 190;
saveDisabled = true;
canvasWidth = 1280;
canvasHeight = 720;
canvasBaseW = 1280;
// @HostListener('window:resize', ['$event'])
canvasBaseH = 720;
mapScale = 1;
ctx;
mx;
my; // 点击坐标
// 声音
bgAudio = new Audio();
images = new Map();
animationId: any;
// 资源
// rawImages = new Map(res);
winResizeEventStream = new Subject();
canvasLeft;
canvasTop;
renderArr;
imgArr = [];
oldPos;
radioValue;
curItem;
bg: MySprite;
changeSizeFlag = false;
changeTopSizeFlag = false;
changeRightSizeFlag = false;
constructor() {
}
_bgItem = null;
get bgItem() {
return this._bgItem;
}
@Input()
set bgItem(v) {
this._bgItem = v;
this.init();
}
onResize(event) {
this.winResizeEventStream.next();
}
ngOnInit() {
this.initListener();
// this.init();
this.update();
}
ngOnDestroy() {
window.cancelAnimationFrame(this.animationId);
}
ngOnChanges() {
}
onBackgroundUploadSuccess(e) {
console.log('e: ', e);
this.bgItem.url = e.url;
this.refreshBackground();
}
onItemImgUploadSuccess(e, item) {
item.pic_url = e.url;
this.loadHotZonePic(item.pic, e.url);
}
onItemAudioUploadSuccess(e, item) {
item.audio_url = e.url;
}
refreshBackground(callBack = null) {
if (!this.bg) {
this.bg = new MySprite(this.ctx);
this.renderArr.push(this.bg);
}
const bg = this.bg;
if (this.bgItem.url) {
bg.load(this.bgItem.url).then(() => {
const rate1 = this.canvasWidth / bg.width;
const rate2 = this.canvasHeight / bg.height;
const rate = Math.min(rate1, rate2);
bg.setScaleXY(rate);
bg.x = this.canvasWidth / 2;
bg.y = this.canvasHeight / 2;
if (callBack) {
callBack();
}
});
}
}
addBtnClick() {
// this.imgArr.push({});
// this.hotZoneArr.push({});
const item = this.getHotZoneItem();
this.hotZoneArr.push(item);
this.refreshItem(item);
this.refreshHotZoneId();
}
deleteBtnClick(index) {
const item = this.hotZoneArr.splice(index, 1)[0];
removeItemFromArr(this.renderArr, item.pic);
removeItemFromArr(this.renderArr, item.textLabel);
this.refreshHotZoneId();
}
onImgUploadSuccessByImg(e, img) {
img.pic_url = e.url;
this.refreshImage(img);
}
refreshImage(img) {
this.hideAllLineDash();
img.picItem = this.getPicItem(img);
this.refreshImageId();
}
refreshHotZoneId() {
for (let i = 0; i < this.hotZoneArr.length; i++) {
this.hotZoneArr[i].index = i;
if (this.hotZoneArr[i]) {
this.hotZoneArr[i].title = 'item-' + (i + 1);
}
}
}
refreshImageId() {
for (let i = 0; i < this.imgArr.length; i++) {
this.imgArr[i].id = i;
if (this.imgArr[i].picItem) {
this.imgArr[i].picItem.text = 'Image-' + (i + 1);
}
}
}
getHotZoneItem(saveData = null) {
const itemW = 200;
const itemH = 200;
const item = new HotZoneItem(this.ctx);
item.setSize(itemW, itemH);
item.anchorX = 0.5;
item.anchorY = 0.5;
item.x = this.canvasWidth / 2;
item.y = this.canvasHeight / 2;
item.itemType = this.defaultItemType;
if (saveData) {
const saveRect = saveData.rect;
item.scaleX = saveRect.width / item.width;
item.scaleY = saveRect.height / item.height;
item.x = saveRect.x + saveRect.width / 2;
item.y = saveRect.y + saveRect.height / 2;
}
item.showLineDash();
const pic = new HotZoneImg(this.ctx);
pic.visible = false;
item['pic'] = pic;
if (saveData && saveData.pic_url) {
this.loadHotZonePic(pic, saveData.pic_url);
}
pic.x = item.x;
pic.y = item.y;
this.renderArr.push(pic);
const textLabel = new HotZoneLabel(this.ctx);
textLabel.fontSize = this.hotZoneFontObj.size;
textLabel.fontName = this.hotZoneFontObj.name;
textLabel.fontColor = this.hotZoneFontObj.color;
textLabel.textAlign = 'center';
// textLabel.setOutline();
// console.log('saveData:', saveData);
item['textLabel'] = textLabel;
textLabel.setScaleXY(this.mapScale);
if (saveData && saveData.text) {
textLabel.text = saveData.text;
textLabel.refreshSize();
}
textLabel.x = item.x;
textLabel.y = item.y;
this.renderArr.push(textLabel);
return item;
}
getPicItem(img, saveData = null) {
const item = new EditorItem(this.ctx);
item.load(img.pic_url).then(img => {
let maxW, maxH;
if (this.bg) {
maxW = this.bg.width * this.bg.scaleX;
maxH = this.bg.height * this.bg.scaleY;
} else {
maxW = this.canvasWidth;
maxH = this.canvasHeight;
}
let scaleX = maxW / 3 / item.width;
let scaleY = maxH / 3 / item.height;
if (item.height * scaleX < this.canvasHeight) {
item.setScaleXY(scaleX);
} else {
item.setScaleXY(scaleY);
}
item.x = this.canvasWidth / 2;
item.y = this.canvasHeight / 2;
if (saveData) {
const saveRect = saveData.rect;
item.setScaleXY(saveRect.width / item.width);
item.x = saveRect.x + saveRect.width / 2;
item.y = saveRect.y + saveRect.height / 2;
} else {
item.showLineDash();
}
});
return item;
}
onAudioUploadSuccessByImg(e, img) {
img.audio_url = e.url;
}
deleteItem(e, i) {
// this.imgArr.splice(i , 1);
// this.refreshImageId();
this.hotZoneArr.splice(i, 1);
this.refreshHotZoneId();
}
radioChange(e, item) {
item.itemType = e;
this.refreshItem(item);
// console.log(' in radioChange e: ', e);
}
refreshItem(item) {
switch (item.itemType) {
case 'rect':
this.setRectState(item);
break;
case 'pic':
this.setPicState(item);
break;
case 'text':
this.setTextState(item);
break;
default:
}
}
init() {
this.initData();
this.initCtx();
this.initItem();
}
initItem() {
if (!this.bgItem) {
this.bgItem = {};
} else {
this.refreshBackground(() => {
// if (!this.imgItemArr) {
// this.imgItemArr = [];
// } else {
// this.initImgArr();
// }
// console.log('aaaaa');
if (!this.hotZoneItemArr) {
this.hotZoneItemArr = [];
} else {
this.initHotZoneArr();
}
});
}
}
initHotZoneArr() {
// console.log('this.hotZoneArr: ', this.hotZoneArr);
let curBgRect;
if (this.bg) {
curBgRect = this.bg.getBoundingBox();
} else {
curBgRect = {x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight};
}
let oldBgRect = this.bgItem.rect;
if (!oldBgRect) {
oldBgRect = curBgRect;
}
const rate = curBgRect.width / oldBgRect.width;
console.log('rate: ', rate);
this.hotZoneArr = [];
const arr = this.hotZoneItemArr.concat();
for (let i = 0; i < arr.length; i++) {
const data = JSON.parse(JSON.stringify(arr[i]));
// const img = {pic_url: data.pic_url};
data.rect.x *= rate;
data.rect.y *= rate;
data.rect.width *= rate;
data.rect.height *= rate;
data.rect.x += curBgRect.x;
data.rect.y += curBgRect.y;
// img['picItem'] = this.getPicItem(img, data);
// img['audio_url'] = arr[i].audio_url;
// this.imgArr.push(img);
const item = this.getHotZoneItem(data);
item.audio_url = data.audio_url;
item.pic_url = data.pic_url;
item.text = data.text;
item.itemType = data.itemType;
this.refreshItem(item);
console.log('item: ', item);
this.hotZoneArr.push(item);
}
this.refreshHotZoneId();
// this.refreshImageId();
}
initImgArr() {
console.log('this.imgItemArr: ', this.imgItemArr);
let curBgRect;
if (this.bg) {
curBgRect = this.bg.getBoundingBox();
} else {
curBgRect = {x: 0, y: 0, width: this.canvasWidth, height: this.canvasHeight};
}
let oldBgRect = this.bgItem.rect;
if (!oldBgRect) {
oldBgRect = curBgRect;
}
const rate = curBgRect.width / oldBgRect.width;
console.log('rate: ', rate);
this.imgArr = [];
const arr = this.imgItemArr.concat();
for (let i = 0; i < arr.length; i++) {
const data = JSON.parse(JSON.stringify(arr[i]));
const img = {pic_url: data.pic_url};
data.rect.x *= rate;
data.rect.y *= rate;
data.rect.width *= rate;
data.rect.height *= rate;
data.rect.x += curBgRect.x;
data.rect.y += curBgRect.y;
img['picItem'] = this.getPicItem(img, data);
img['audio_url'] = arr[i].audio_url;
this.imgArr.push(img);
}
this.refreshImageId();
}
initData() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.mapScale = this.canvasWidth / this.canvasBaseW;
this.renderArr = [];
this.bg = null;
this.imgArr = [];
this.hotZoneArr = [];
}
initCtx() {
this.ctx = this.canvas.nativeElement.getContext('2d');
this.canvas.nativeElement.width = this.canvasWidth;
this.canvas.nativeElement.height = this.canvasHeight;
}
mapDown(event) {
this.oldPos = {x: this.mx, y: this.my};
for (let i = 0; i < this.hotZoneArr.length; i++) {
const item = this.hotZoneArr[i];
let callback;
let target;
switch (item.itemType) {
case 'rect':
target = item;
callback = this.clickedHotZoneRect.bind(this);
break;
case 'pic':
target = item.pic;
callback = this.clickedHotZonePic.bind(this);
break;
case 'text':
target = item.textLabel;
callback = this.clickedHotZoneText.bind(this);
break;
}
if (this.checkClickTarget(target)) {
callback(target);
return;
}
}
}
mapMove(event) {
if (!this.curItem) {
return;
}
if (this.changeSizeFlag) {
this.changeSize();
} else if (this.changeTopSizeFlag) {
this.changeTopSize();
} else if (this.changeRightSizeFlag) {
this.changeRightSize();
} else {
const addX = this.mx - this.oldPos.x;
const addY = this.my - this.oldPos.y;
this.curItem.x += addX;
this.curItem.y += addY;
}
this.oldPos = {x: this.mx, y: this.my};
this.saveDisabled = true;
}
mapUp(event) {
this.curItem = null;
this.changeSizeFlag = false;
this.changeTopSizeFlag = false;
this.changeRightSizeFlag = false;
}
changeSize() {
const rect = this.curItem.getBoundingBox();
let lenW = (this.mx - (rect.x + rect.width / 2)) * 2;
let lenH = ((rect.y + rect.height / 2) - this.my) * 2;
let minLen = 20;
let s;
if (lenW < lenH) {
if (lenW < minLen) {
lenW = minLen;
}
s = lenW / this.curItem.width;
} else {
if (lenH < minLen) {
lenH = minLen;
}
s = lenH / this.curItem.height;
}
// console.log('s: ', s);
this.curItem.setScaleXY(s);
this.curItem.refreshLabelScale();
}
changeTopSize() {
const rect = this.curItem.getBoundingBox();
// let lenW = ( this.mx - (rect.x + rect.width / 2) ) * 2;
let lenH = ((rect.y + rect.height / 2) - this.my) * 2;
let minLen = 20;
let s;
// if (lenW < lenH) {
// if (lenW < minLen) {
// lenW = minLen;
// }
// s = lenW / this.curItem.width;
//
// } else {
if (lenH < minLen) {
lenH = minLen;
}
s = lenH / this.curItem.height;
// }
// console.log('s: ', s);
this.curItem.scaleY = s;
this.curItem.refreshLabelScale();
}
changeRightSize() {
const rect = this.curItem.getBoundingBox();
let lenW = (this.mx - (rect.x + rect.width / 2)) * 2;
// let lenH = ( (rect.y + rect.height / 2) - this.my ) * 2;
let minLen = 20;
let s;
if (lenW < minLen) {
lenW = minLen;
}
s = lenW / this.curItem.width;
this.curItem.scaleX = s;
this.curItem.refreshLabelScale();
}
changeItemSize(item) {
this.curItem = item;
this.changeSizeFlag = true;
}
changeItemTopSize(item) {
this.curItem = item;
this.changeTopSizeFlag = true;
}
changeItemRightSize(item) {
this.curItem = item;
this.changeRightSizeFlag = true;
}
changeCurItem(item) {
this.hideAllLineDash();
this.curItem = item;
this.curItem.showLineDash();
}
hideAllLineDash() {
for (let i = 0; i < this.imgArr.length; i++) {
if (this.imgArr[i].picItem) {
this.imgArr[i].picItem.hideLineDash();
}
}
}
update() {
if (!this.ctx) {
return;
}
this.animationId = window.requestAnimationFrame(this.update.bind(this));
// 清除画布内容
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
for (let i = 0; i < this.renderArr.length; i++) {
this.renderArr[i].update(this);
}
// for (let i = 0; i < this.imgArr.length; i++) {
// const picItem = this.imgArr[i].picItem;
// if (picItem) {
// picItem.update(this);
// }
// }
this.updateArr(this.hotZoneArr);
this.updatePos()
TWEEN.update();
}
updateArr(arr) {
if (arr) {
for (let i = 0; i < arr.length; i++) {
arr[i].update();
}
}
}
renderAfterResize() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.init();
}
initListener() {
this.winResizeEventStream
.pipe(debounceTime(500))
.subscribe(data => {
this.renderAfterResize();
});
if (this.IsPC()) {
this.canvas.nativeElement.addEventListener('mousedown', (event) => {
setMxMyByMouse(event);
this.mapDown(event);
});
this.canvas.nativeElement.addEventListener('mousemove', (event) => {
setMxMyByMouse(event);
this.mapMove(event);
});
this.canvas.nativeElement.addEventListener('mouseup', (event) => {
setMxMyByMouse(event);
this.mapUp(event);
});
const setMxMyByMouse = (event) => {
this.mx = event.offsetX;
this.my = event.offsetY;
};
} else {
this.canvas.nativeElement.addEventListener('touchstart', (event) => {
setMxMyByTouch(event);
this.mapDown(event);
});
this.canvas.nativeElement.addEventListener('touchmove', (event) => {
setMxMyByTouch(event);
this.mapMove(event);
});
this.canvas.nativeElement.addEventListener('touchend', (event) => {
setMxMyByTouch(event);
this.mapUp(event);
});
this.canvas.nativeElement.addEventListener('touchcancel', (event) => {
setMxMyByTouch(event);
this.mapUp(event);
});
const setMxMyByTouch = (event) => {
if (event.touches.length <= 0) {
return;
}
if (this.canvasLeft == null) {
setParentOffset();
}
this.mx = event.touches[0].pageX - this.canvasLeft;
this.my = event.touches[0].pageY - this.canvasTop;
};
const setParentOffset = () => {
const rect = this.canvas.nativeElement.getBoundingClientRect();
this.canvasLeft = rect.left;
this.canvasTop = rect.top;
};
}
}
IsPC() {
if (window['ELECTRON']) {
return false; // 封装客户端标记
}
if (document.body.ontouchstart !== undefined) {
return false;
} else {
return true;
}
}
checkClickTarget(target) {
const rect = target.getBoundingBox();
if (this.checkPointInRect(this.mx, this.my, rect)) {
return true;
}
return false;
}
checkPointInRect(x, y, rect) {
if (x >= rect.x && x <= rect.x + rect.width) {
if (y >= rect.y && y <= rect.y + rect.height) {
return true;
}
}
return false;
}
saveClick() {
const bgItem = this.bgItem;
if (this.bg) {
bgItem['rect'] = this.bg.getBoundingBox();
} else {
bgItem['rect'] = {
x: 0,
y: 0,
width: Math.round(this.canvasWidth * 100) / 100,
height: Math.round(this.canvasHeight * 100) / 100
};
}
const hotZoneItemArr = [];
const hotZoneArr = this.hotZoneArr;
for (let i = 0; i < hotZoneArr.length; i++) {
const hotZoneItem = {
index: hotZoneArr[i].index,
pic_url: hotZoneArr[i].pic_url,
text: hotZoneArr[i].text,
audio_url: hotZoneArr[i].audio_url,
itemType: hotZoneArr[i].itemType,
fontSize: this.hotZoneFontObj.size,
fontName: this.hotZoneFontObj.name,
fontColor: this.hotZoneFontObj.color,
fontScale: hotZoneArr[i].textLabel ? hotZoneArr[i].textLabel.scaleX : 1,
imgScale: hotZoneArr[i].pic ? hotZoneArr[i].pic.scaleX : 1,
mapScale: this.mapScale
};
hotZoneItem['rect'] = hotZoneArr[i].getBoundingBox();
hotZoneItem['rect'].x = Math.round((hotZoneItem['rect'].x - bgItem['rect'].x) * 100) / 100;
hotZoneItem['rect'].y = Math.round((hotZoneItem['rect'].y - bgItem['rect'].y) * 100) / 100;
hotZoneItem['rect'].width = Math.round((hotZoneItem['rect'].width) * 100) / 100;
hotZoneItem['rect'].height = Math.round((hotZoneItem['rect'].height) * 100) / 100;
hotZoneItemArr.push(hotZoneItem);
}
console.log('hotZoneItemArr: ', hotZoneItemArr);
this.save.emit({bgItem, hotZoneItemArr});
}
private updatePos() {
this.hotZoneArr.forEach((item) => {
let x, y;
switch (item.itemType) {
case 'rect':
x = item.x;
y = item.y;
break;
case 'pic':
x = item.pic.x;
y = item.pic.y;
break;
case 'text':
x = item.textLabel.x;
y = item.textLabel.y;
break;
}
item.x = x;
item.y = y;
item.pic.x = x;
item.pic.y = y;
item.textLabel.x = x;
item.textLabel.y = y;
});
}
private setPicState(item: any) {
item.visible = false;
item.textLabel.visible = false;
item.pic.visible = true;
}
private setRectState(item: any) {
item.visible = true;
item.textLabel.visible = false;
item.pic.visible = false;
}
private setTextState(item: any) {
item.visible = false;
item.pic.visible = false;
item.textLabel.visible = true;
}
private clickedHotZoneRect(item: any) {
if (this.checkClickTarget(item)) {
if (item.lineDashFlag && this.checkClickTarget(item.arrow)) {
this.changeItemSize(item);
} else if (item.lineDashFlag && this.checkClickTarget(item.arrowTop)) {
this.changeItemTopSize(item);
} else if (item.lineDashFlag && this.checkClickTarget(item.arrowRight)) {
this.changeItemRightSize(item);
} else {
this.changeCurItem(item);
}
return;
}
}
private clickedHotZonePic(item: any) {
if (this.checkClickTarget(item)) {
this.curItem = item;
}
}
private clickedHotZoneText(item: any) {
if (this.checkClickTarget(item)) {
this.curItem = item;
}
}
saveText(item) {
item.textLabel.text = item.text;
}
private loadHotZonePic(pic: HotZoneImg, url) {
const baseLen = this.hotZoneImgSize * this.mapScale;
pic.load(url).then(() => {
const s = getMinScale(pic, baseLen);
pic.setScaleXY(s);
});
}
}
<div class="title-config">
<div class="title-wrap">
<div class="row" style="margin: 5px;">
<div class="p-content" style="width:100%">
<div class="p-tool-box d-flex" style="background: #fff;">
<nz-select class="ml-1" style="width: 120px;" [(ngModel)]="__fontFamily"
(ngModelChange)="onChangeFontFamily($event)"
nzPlaceHolder="Font Family"
[nzDropdownMatchSelectWidth]="false">
<nz-option [nzValue]="font" nzCustomContent [nzLabel]="font" *ngFor="let font of fontFamilyList">
<span [ngStyle]="{'font-family': font}" >{{font}}</span>
</nz-option>
</nz-select>
<nz-select class="ml-1" style="width: 110px;" [(ngModel)]="__fontSize"
(ngModelChange)="onChangeFontSize()"
nzPlaceHolder="Font Size">
<nz-option [nzValue]="i" [nzLabel]="'Size - ' + i" *ngFor="let i of fontSizeRange"></nz-option>
</nz-select>
<div class="p-divider"></div>
<div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeBold()">
<!-- <div class="fa fa-bold"></div>-->
<fa-icon icon="bold"></fa-icon>
</div>
</div>
<div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeItalic()">
<!-- <div class="fa fa-italic"></div>-->
<fa-icon icon="italic"></fa-icon>
</div>
</div>
<div class="i-tool-font-btn d-flex mr-2">
<div class="position-relative fa-icon" (click)="onChangeUnderline()">
<!-- <div class="fa fa-underline"></div>-->
<fa-icon icon="underline"></fa-icon>
</div>
</div>
<div class="i-tool-font-btn d-flex">
<div class="position-relative fa-icon" (click)="onChangeStrikethrough()">
<!-- <div class="fa fa-strikethrough"></div>-->
<fa-icon icon="strikethrough"></fa-icon>
</div>
</div>
<div class="p-divider"></div>
<div class="i-tool-font-color d-flex">
<div class="position-relative i-left flex-fill" (click)="onChangeFontColor($event)">
<!-- <div class="fa fa-font"></div>-->
<fa-icon icon="palette"></fa-icon>
<div class="i-color" [style.background-color]="__fontColor"></div>
</div>
<div class="i-dropdown-menu" nzPlacement="bottom"
nz-popover [(nzVisible)]="isShowFontColorPane" nzTrigger="click"
[nzContent]="colorPane">
<i nz-icon type="down" theme="outline"></i>
</div>
</div>
<div class="p-divider"></div>
<div style="background: #fff;display: block;">
<div class="position-relative">
<app-audio-recorder [audioUrl]="titleObj && titleObj.audio_url" (audioUploaded)="titleAudioUploaded($event)"></app-audio-recorder>
</div>
</div>
</div>
<div class="width-100 d-flex">
<iframe #titleEl id="titleContentEgret" frameborder="0" style="overflow: hidden;width: 100%; height:48px; margin: 0; padding: 0;"></iframe>
</div>
</div>
<ng-template #colorPane>
<div class="p-color-pane" nz-row nzGutter="16" nzType="flex">
<div nz-col class="p-color-item" *ngFor="let color of colorList"
[style.background-color]="color"
(click)="onSelectColor(color)"
>
</div>
</div>
</ng-template>
</div>
</div>
</div>
@import '../../style/common_mixin.css';
.title-config {
.letter-wrap{
width: 3rem;
flex: 0 0 3rem;
}
.str-wrap{
margin-left: 1rem;flex: 1 1;
}
.type-row{
margin: 0;padding-top: 1rem;
}
}
@font-face
{
font-family: 'BRLNSDB';
src: url("../../../assets/font/BRLNSDB.TTF") ;
}
@font-face
{
font-family: 'BRLNSB';
src: url("../../../assets/font/BRLNSB.TTF") ;
}
@font-face
{
font-family: 'BRLNSR';
src: url("../../../assets/font/BRLNSR.TTF") ;
}
@font-face
{
font-family: 'GOTHIC';
src: url("../../../assets/font/GOTHIC.TTF") ;
}
@font-face
{
font-family: 'GOTHICB';
src: url("../../../assets/font/GOTHICB.TTF") ;
}
@font-face
{
font-family: 'GOTHICBI';
src: url("../../../assets/font/GOTHICBI.TTF") ;
}
@font-face
{
font-family: 'GOTHICI';
src: url("../../../assets/font/GOTHICI.TTF") ;
}
@font-face
{
font-family: 'MMTextBook';
src: url("../../../assets/font/MMTextBook.otf") ;
}
@font-face
{
font-family: 'MMTextBook-Bold';
src: url("../../../assets/font/MMTextBook-Bold.otf") ;
}
@font-face
{
font-family: 'MMTextBook-BoldItalic';
src: url("../../../assets/font/MMTextBook-BoldItalic.otf") ;
}
@font-face
{
font-family: 'MMTextBook-Italic';
src: url("../../../assets/font/MMTextBook-Italic.otf") ;
}
@mixin tool-btn {
border: 1px solid #ddd;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
outline: none;
border-radius: 6px;
color: #555;
}
.d-flex{
display: flex;
}
.position-relative {
position: relative;
}
.flex-fill {
-webkit-box-flex: 1;
flex: 1 1 auto;
justify-content: center;
display: flex;
}
.i-dropdown-menu{
width: 15px;
font-size: 10px;
border-left: 1px solid #ddd;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
flex: 0
}
.p-title-box .p-title {
font-size: 20px;
}
.p-title-box input {
width: 300px;
margin-left: 10px;
}
.p-content {
border: 1px solid #ddd;
box-shadow: 0 0 8px #eee;
margin-top: 10px;
}
.p-divider {
border-left: 1px solid #ccc;
margin: 5px 8px;
height: 85%;
}
.p-tool-box {
background-color: #efefef;
padding: 2px;
height: 37px;
display: flex;
align-items: center;
border-bottom: 1px solid #ddd;
}
// save
.i-tool-save {
//@include tool-btn();
color: white;
}
.i-tool-save:disabled {
color: #555;
}
// font-size
.i-tool-font-size {
//@include tool-btn();
width: 37px;
}
.i-tool-font-size:hover {
color: black;
border-color: #bbb;
}
// font-color
.i-tool-font-color, .i-tool-font-btn {
border: 1px solid #ddd;
//padding: 3px 7px;
border-radius: 6px;
width: 45px;
height: 31px;
background-color: white;
color: #555;
::ng-deep > span {
display: flex;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
padding: 3px 7px;
}
.i-left {
.fa-font,.fa-bold,.fa-italic,.fa-strikethrough, .fa-underline {
font-size: 10px;
position: absolute;
color: #555;
left: 8px;
top: 7px;
}
.i-color {
width: 68%;
height: 5px;
background-color: black;
position: absolute;
top: 21px;
left: 5px;
}
}
.i-dropdown-menu {
width: 15px;
font-size: 10px;
border-left: 1px solid #ddd;
display: flex;
align-items: center;
.anticon-down {
transform: scale(0.6);
}
}
}
.i-tool-font-btn{
width: 31px;
}
.fa-icon{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
// bg-color
.i-tool-bg-color {
@include tool-btn();
padding: 0 9px;
::ng-deep > span {
display: flex;
align-items: center;
}
.i-color {
display: block;
width: 16px;
height: 16px;
background-color: white;
margin-left: 10px;
}
}
// horizontal-center
.i-tool-horizontal-center {
@include tool-btn();
width: 37px;
}
.p-box {
width: 1280px;
height: 720px;
left: 0;
top: 0;
transform-origin: top left;
overflow: hidden;
}
.p-animation-index-box {
.i-animation-index {
position: absolute;
font-size: 34px;
font-family: Arial;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 50px;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
background-color: darkorange;
color: white;
}
}
.p-edit {
background-color: white;
border: 1px solid black;
padding: 2px 15px;
}
.p-selected {
border: 1px solid darkorange;
box-shadow: 0 0 8px #ddd;
}
.p-tool-item-disable {
color: rgba(0, 0, 0, .25) !important;
background-color: transparent !important;
pointer-events: none;
}
// -----------
.p-color-pane {
width: 80px;
.p-color-item {
width: 17px;
height: 17px;
cursor: pointer;
margin: 4px;
border: 1px solid #bbb;
}
.p-color-item-active {
border: 1px solid white;
transform: scale(1.1);
}
}
.p-user-guide {
font-size: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.p-animation-box {
width: 400px;
background-color: #efefef;
border-left: 1px solid #ddd;
padding: 10px;
text-align: center;
.p-animation-label {
z-index: 1;
position: relative;
}
::ng-deep .ant-radio-button-wrapper {
padding: 0 10px;
}
.i-toolbox {
& > div {
font-size: 20px;
cursor: pointer;
color: #555;
padding: 0 3px;
margin: 0 5px;
}
& > div:hover {
color: #1585ff;
transform: scale(1.1);
}
}
.p-animation-list {
background-color: white;
border-radius: 10px;
height: calc(100% - 86px);
overflow-y: auto;
.p-animation-item {
padding: 0 20px;
background-color: aliceblue;
margin-top: 6px;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
cursor: pointer;
text-align: left;
display: flex;
}
.i-active {
background-color: antiquewhite;
}
}
}
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
ViewChild
} from '@angular/core';
const editorTpl = `<html lang="en"><head><meta charset="utf-8">
<meta name="viewport"
content="width=device-width,initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
<style>
@font-face{
font-family: 'BRLNSDB';
src: url("../../../assets/font/BRLNSDB.TTF") ;
}
@font-face{
font-family: 'BRLNSB';
src: url("../../../assets/font/BRLNSB.TTF") ;
}
@font-face{
font-family: 'BRLNSR';
src: url("../../../assets/font/BRLNSR.TTF") ;
}
@font-face{
font-family: 'GOTHIC';
src: url("../../../assets/font/GOTHIC.TTF") ;
}
@font-face{
font-family: 'GOTHICB';
src: url("../../../assets/font/GOTHICB.TTF") ;
}
@font-face{
font-family: 'GOTHICBI';
src: url("../../../assets/font/GOTHICBI.TTF") ;
}
@font-face{
font-family: 'GOTHICI';
src: url("../../../assets/font/GOTHICI.TTF") ;
}
@font-face{
font-family: 'MMTextBook';
src: url("../../../assets/font/MMTextBook.otf") ;
}
@font-face{
font-family: 'MMTextBook-Bold';
src: url("../../../assets/font/MMTextBook-Bold.otf") ;
}
@font-face{
font-family: 'MMTextBook-BoldItalic';
src: url("../../../assets/font/MMTextBook-BoldItalic.otf") ;
}
@font-face{
font-family: 'MMTextBook-Italic';
src: url("../../../assets/font/MMTextBook-Italic.otf") ;
}
html, body{
/*font-size: 30px;*/
}
body{
height:48px;
overflow: hidden;
margin: 0;
padding: 0 .5rem;
font-family: 'BRLNSB, BRLNSDB, BRLNSR, GOTHIC, GOTHICB, MMTextBook';
background: #FFF;
line-height: 48px;
}
</style>
</head>
<body>{{content}}</body>
</html>`;
@Component({
selector: 'app-lesson-title-config',
templateUrl: './lesson-title-config.component.html',
styleUrls: ['./lesson-title-config.component.scss']
})
export class LessonTitleConfigComponent implements OnInit, OnChanges, OnDestroy, OnChanges {
fontFamilyList = [
'Arial',
'BRLNSB',
'BRLNSDB',
'BRLNSR',
'GOTHIC',
'GOTHICB',
// "GOTHICBI",
// "GOTHICI",
'MMTextBook',
// "MMTextBook-Bold",
// "MMTextBook-Italic",
// "MMTextBook-BoldItalic",
];
colorList = [
'#000000',
'#ffffff',
'#595959',
'#0075c2',
'#c61c1e',
'#9cbc3a',
'#008000',
'#FF0000',
'#D2691E',
];
MIN_FONT_SIZE = 1;
MAX_FONT_SIZE = 7;
isShowFontColorPane = false;
isShowBGColorPane = false;
fontSizeRange = [
// {name: '1号', value: 9},
// {name: '2号', value: 13},
// {name: '3号', value: 16},
// {name: '4号', value: 18},
// {name: '5号', value: 24},
// {name: '6号', value: 32},
];
editorContent = '';
__fontFamily = 'Arial';
__fontColor = '';
__fontSize = 3;
loopCnt = 0;
maxLoops = 20;
@ViewChild('titleEl', {static: true}) titleEl: ElementRef;
titleEW = null;
@Input()
titleObj = {
content: '',
audio_url: ''
};
@Output()
titleUpdated = new EventEmitter();
constructor() {
this.fontSizeRange = [];
for (let i = this.MIN_FONT_SIZE; i <= this.MAX_FONT_SIZE; ++i) {
this.fontSizeRange.push(i);
}
this.__fontSize = 3;
this.__fontColor = this.colorList[0];
}
ngOnChanges(vars) {
if (!vars.titleObj.previousValue) {// 初始化,内容是空
return;
}
let defObj = this.titleObj;
if (!vars.titleObj.currentValue) {
defObj = {
content: '',
audio_url: ''
};
} else {
defObj = vars.titleObj.currentValue;
}
this.titleObj.content = defObj.content || '';
this.titleObj.audio_url = defObj.audio_url || '';
this.titleEW.document.body.innerHTML = this.titleObj.content;
}
ngOnInit() {
if (!this.titleObj) {
this.titleObj = {
content: '',
audio_url: ''
};
}
this.titleObj.content = this.titleObj.content || '';
this.titleObj.audio_url = this.titleObj.audio_url || '';
this.editorContent = editorTpl.replace('{{content}}', this.titleObj.content) ;
this.titleEW = this.titleEl.nativeElement.contentWindow;
console.log('this.titleEW', this.titleEW);
const tdoc = this.titleEW.document;
tdoc.designMode = 'on';
tdoc.open('text/html', 'replace');
tdoc.write(this.editorContent);
tdoc.close();
tdoc.addEventListener('keypress', this.keyPress, true);
tdoc.addEventListener('blur', () => {
if (this.titleObj.content === this.titleEW.document.body.innerHTML.trim()) {
return;
}
this.shouldSave();
}, true);
}
htmlEncode(text) {
if (!text) {
return '';
}
return text.replace(/\&/ig, '&amp;')
.replace(/\</ig, '&lt;')
.replace(/\>/ig, '&gt;')
.replace(/\"/ig, '&quot;');
}
htmlDecode(text) {
if (!text) {
return '';
}
return text.replace(/\&amp\;/ig, '&')
.replace(/\&lt\;/ig, '<')
.replace(/\&gt\;/ig, '>')
.replace(/\&quot\;/ig, '"');
}
ngOnDestroy(): void {
}
keyPress(evt) {
try {
if (evt.charCode === 13) {
evt.preventDefault();
evt.stopPropagation();
return;
}
if (evt.ctrlKey) {
const key = String.fromCharCode(evt.charCode).toLowerCase();
let cmd = '';
switch (key) {
case 'b': cmd = 'bold'; break;
case 'i': cmd = 'italic'; break;
case 'u': cmd = 'underline'; break;
}
if (cmd) {
this.execEditorCommand(cmd);
// stop the event bubble
evt.preventDefault();
evt.stopPropagation();
}
}
} catch (e) {
console.log(1, e);
alert(e);
}
}
execEditorCommand(command, option?: any) {
console.log('sssss');
try {
this.titleEW.focus();
const result = this.titleEW.document.execCommand(command, false, option);
console.log(result);
this.loopCnt = 0;
return false;
} catch (e) {
alert(e);
if (this.loopCnt < this.maxLoops) {
setTimeout(() => {
this.execEditorCommand(command, option);
}, 100);
this.loopCnt += 1;
} else {
alert('Error executing command.');
}
}
}
onSelectColor(color) {
this.execEditorCommand('forecolor', color);
this.__fontColor = color;
}
onChangeFontColor(val) {
this.execEditorCommand('forecolor', this.__fontColor);
}
onChangeFontFamily(font) {
this.execEditorCommand('fontName', font);
}
onChangeFontSize(size?: any) {
if (size) {
size += this.__fontSize;
} else {
size = this.__fontSize;
}
size = Math.max(this.MIN_FONT_SIZE, size);
size = Math.min(this.MAX_FONT_SIZE, size);
this.execEditorCommand('fontsize', size);
}
onChangeBold() {
this.execEditorCommand('bold');
}
onChangeItalic() {
this.execEditorCommand('italic');
}
onChangeUnderline() {
this.execEditorCommand('underline');
}
onChangeStrikethrough() {
this.execEditorCommand('strikethrough');
}
titleAudioUploaded(res) {
this.titleObj.audio_url = res.url;
this.titleUpdated.emit(this.titleObj);
}
shouldSave = () => {
console.log('title shouldSave', this.titleObj);
this.titleObj.content = this.titleEW.document.body.innerHTML.trim();
this.titleUpdated.emit(this.titleObj);
}
}
<div class="cmp-player-content-wrapper" #wrapperEl>
<div class="cmp-cnt-box">
<img [src]="'assets/'+ratio+'.png'" alt="">
<div class="cmp-cnt-main">
<ng-content></ng-content>
</div>
</div>
</div>
\ No newline at end of file
@import '../../style/common_mixin.css';
.cmp-player-content-wrapper{
max-height: 100%;
display: block;
position: relative;
height: 100%;
.cmp-cnt-box{
display: inline-block;
max-width: 100%;
max-height: 100%;
width: auto;
height: 100%;
position: relative;
img{
height: 100%;
width: auto;
}
}
.cmp-cnt-main{
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
}
}
[style*="--aspect-ratio"] > :first-child {
width: 100%;
}
[style*="--aspect-ratio"] > img {
height: auto;
}
@supports (--custom:property) {
[style*="--aspect-ratio"] {
position: relative;
}
[style*="--aspect-ratio"]::before {
content: "";
display: block;
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*="--aspect-ratio"] > :first-child {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}
import {
AfterViewInit,
Component,
ElementRef,
Input,
OnChanges,
OnDestroy,
OnInit,
ViewChild
} from '@angular/core';
@Component({
selector: 'app-player-content-wrapper',
templateUrl: './player-content-wrapper.component.html',
styleUrls: ['./player-content-wrapper.component.scss']
})
export class PlayerContentWrapperComponent implements OnInit, OnChanges, OnDestroy, AfterViewInit {
@ViewChild('wrapperEl', {static: true }) wrapperEl: ElementRef;
// // aspect ratio?
@Input() ratio;
_w: string;
_h: string;
constructor() {
if (window.innerHeight < window.innerWidth) {
this._h = '100%';
this._w = 'auto';
} else {
this._w = '100%';
this._h = 'auto';
}
}
ngOnInit() {
if (!this.ratio) {
this.ratio = '20-9';
}
}
ngOnChanges() {
}
ngOnDestroy(): void {
}
ngAfterViewInit() {
}
}
<div class="position-relative">
<nz-upload class="p-image-uploader" [nzDisabled]="disableUpload"
[nzShowUploadList]="false"
nzAccept = "image/*"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="handleChange($event)">
<!--[nzBeforeUpload]="customUpload">-->
<div class="p-box d-flex align-items-center">
<div class="p-upload-icon" *ngIf="!picUrl && !uploading">
<i nz-icon nzType="cloud-upload" nzTheme="outline" [style.font-size]="iconSize + 'em'"></i>
<div class="m-3"></div>
<span>{{TIP}}</span>
<!--<div class="mt-5 p-progress-bar" *ngIf="uploading">-->
<!--<div class="p-progress-bg" [style.width]="progress*0.2+'rem'"></div>-->
<!--<div class="p-progress-value">{{progress}}%</div>-->
<!--</div>-->
</div>
<div class="p-upload-progress-bg" *ngIf="uploading">
<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-preview" *ngIf="!uploading && picUrl "
[style.background-image]="picUrl | backgroundImage ">
</div>
</div>
</nz-upload>
<div class="p-btn-delete" *ngIf="canDelete"
nz-popconfirm nzTitle="Are you sure ?"
(nzOnConfirm)="onDelete()"
>
<i nz-icon nzType="close" nzTheme="outline"></i>
</div>
</div>
@import '../../style/common_mixin.css';
.p-image-uploader {
position: relative;
display: block;
width: 100%;
height: 0;
padding-bottom: 56.25%;
.p-box {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border: 2px dashed #ddd;
border-radius: 0.5rem;
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-size: contain;
background-repeat: no-repeat;
background-position: 50% 50%;
//background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
}
}
.d-flex{
display: flex;
}
}
.p-btn-delete {
position: absolute;
right: -0.5rem;
top: -0.5rem;
width: 2rem;
height: 2rem;
border: 0.2rem solid white;
border-radius: 50%;
font-size: 1.2rem;
background-color: #fb781a;
color: white;
text-align: center;
}
.p-upload-progress-bg {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
& .i-text {
position: absolute;
text-align: center;
color: white;
text-shadow: 0 0 2px rgba(0, 0, 0, .85);
}
& .i-bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #27b43f;
border-radius: 0.5rem;
}
}
:host ::ng-deep .ant-upload {
display: block;
}
import {Component, EventEmitter, Input, OnChanges, OnDestroy, Output} from '@angular/core';
import { NzMessageService, UploadXHRArgs, UploadFile } from 'ng-zorro-antd';
@Component({
selector: 'app-upload-image-with-preview',
templateUrl: './upload-image-with-preview.component.html',
styleUrls: ['./upload-image-with-preview.component.scss']
})
export class UploadImageWithPreviewComponent implements OnDestroy, OnChanges {
uploading = false;
progress = 0;
@Input()
picUrl;
@Input()
canDelete = false;
@Output()
imageUploaded = new EventEmitter();
@Output()
imageUploadFailure = new EventEmitter();
@Output()
delete = new EventEmitter();
@Input()
picItem = null;
@Input()
iconSize = 2;
@Input()
TIP = 'Click here to upload image';
@Input()
disableUpload = false;
uploadUrl;
uploadData;
constructor(private nzMessageService: NzMessageService) {
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
}
ngOnChanges() {
if (!this.picItem) {
return;
}
}
handleChange(info: { type: string, file: UploadFile, event: any }): void {
console.log('info:' , info);
switch (info.type) {
case 'start':
// this.isUploading = true;
// this.progress = 0;
this.picUrl = null;
// this.beforeUpload(item.file as any);
if (!this.checkSelectFile(info.file)) {
return;
}
this.uploading = true;
this.progress = 0;
break;
case 'success':
// this.isUploading = false;
// this.uploadSuccess(info.file.response);
// this.audioUploaded.emit(info.file.response);
this.uploadSuccess(info.file);
break;
case 'progress':
this.progress = parseInt(info.event.percent, 10);
this.doProgress(this.progress);
break;
}
}
onDelete() {
this.delete.emit();
}
checkSelectFile(file) {
const isImg = ['image/jpeg', 'image/png', 'image/jpeg', 'image/gif', 'image/bmp'].includes(file.type);
if (!isImg) {
this.nzMessageService.error('You can only upload Image file (jpg|gif|png|bmp)');
return false;
}
const isGif = !['image/jpeg', 'image/png', 'image/jpeg', 'image/bmp'].includes(file.type);
const delta = isGif ? 20 : 5;
const isOverSize = file.size / 1024 / 1024 < delta;
if (!isOverSize) {
this.nzMessageService.error(`${isGif ? 'Gif' : 'Image'} must smaller than ${delta}MB!`);
return false;
}
return true;
}
uploadSuccess = (file) => {
this.nzMessageService.info('Upload Success');
this.uploading = false;
this.picUrl = file.response.url ;
// this.uploadFinished(url);
// if (!inOSS) {
const img = new Image();
img.addEventListener('load', () => {
const height = img.naturalHeight;
const width = img.naturalWidth;
file['height'] = height;
file['width'] = width;
img.remove();
this.imageUploaded.emit(file.response);
// this.resService.updateImage(id, {width, height}).then( () => {
// this.imageUploaded.emit({res_id: id, id, name, hash, url});
// });
});
img.src = file.response.url;
// } else {
// this.imageUploaded.emit({res_id: id, id, name, hash, url});
// }
}
uploadFailure = (err, file) => {
this.uploading = false;
if (err.name && err.name === 'cancel') {
return;
}
console.log(err);
this.nzMessageService.error('Upload Error ' + err.message);
this.imageUploadFailure.emit(file);
}
doProgress = function(p) {
if (p > 1) {
p = 1;
}
if (p < 0) {
p = 0;
}
// console.log(Math.floor(p * 100));
this.progress = Math.floor(p * 100);
}
ngOnDestroy() {
}
}
<div class="p-video-box">
<div class="up-video" style="display: flex;">
<!--<nz-upload class="" [nzDisabled]="!showUploadBtn"-->
<!--[nzShowUploadList]="false"-->
<!--[nzBeforeUpload]="beforeUpload"-->
<!--nzAccept = ".mp4"-->
<!--style="margin-right: 1rem">-->
<nz-upload class="p-image-uploader" [nzDisabled]="uploading"
[nzShowUploadList]="false"
nzAccept = ".mp4"
[nzAction]="uploadUrl"
[nzData]="uploadData"
(nzChange)="handleChange($event)"
style="margin-right: 1rem">
<button type="button" nz-button nzType="default" *ngIf="showUploadBtn" [disabled]="uploading"
[nzLoading]="uploading" >
<i nz-icon nzType="plus" nzTheme="outline"></i>
<span>{{ uploading ? 'Uploading' : 'Select Video' }}</span>
<!--<span>Select Video</span>-->
</button>
</nz-upload>
<!--<button nz-button nzType="primary" *ngIf="showUploadBtn"-->
<!--style="margin-right: 1rem"-->
<!--type="button"-->
<!--[nzLoading]="uploading"-->
<!--(click)="handleUpload()"-->
<!--[disabled]="!videoFile || uploading">-->
<!--<i nz-icon type="upload" theme="outline"></i>-->
<!--<span>{{ uploading ? 'Uploading' : 'Start Upload' }}</span>-->
<!--</button>-->
<!--
<button type="button"
(click)="extraCover()"
nz-button nzType="default"
[disabled]=" !(videoUrl && videoUrl.constructor.name === 'String')">设置封面</button>
-->
<!--
<nz-divider></nz-divider>
<div [style.display]="!uploading?'none':''" style="position:relative">
<div style="width: calc( 100% - 20px);">
<nz-progress [nzPercent]="progress">
</nz-progress>
</div>
<i (click)="cancelUpload()" class="anticon anticon-close-circle" style="position: absolute;top: 50%;right: 0;transform: translateY(-50%);cursor: pointer"></i>
</div>
-->
</div>
<div class="p-box d-flex align-items-center p-video-uploader">
<div class="p-upload-icon" *ngIf="!showUploadBtn && !videoUrl && !uploading">
<i nz-icon nzType="upload" nzTheme="outline"></i>
<div class="m-3"></div>
<span>Click here to upload video</span>
<div class="mt-5 p-progress-bar" *ngIf="uploading">
<div class="p-progress-bg" [style.width]="progress*0.2+'rem'"></div>
<div class="p-progress-value">{{progress}}%</div>
</div>
</div>
<div class="p-upload-progress-bg dddd " *ngIf="uploading"
[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...
</div>
</div>
<div class="p-preview" *ngIf="!uploading && videoUrl " >
<!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>-->
<video [src]="safeVideoUrl(videoUrl)" controls #videoNode></video>
</div>
</div>
<div [style.display]="!checkVideoExists?'none':''">
<span><i nz-icon nzType="loading" nzTheme="outline"></i> checking file to upload</span>
</div>
</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%);
}
}*/
.p-video-uploader {
position: relative;
display: block;
width: 100%;
height: 0;
padding-bottom: 56.25%;
.p-box {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border: 2px dashed #ddd;
//border-radius: 0.5rem;
background-color: #fafafa;
text-align: center;
color: #aaa;
}
}
.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;
top: -0.5rem;
width: 2rem;
height: 2rem;
border: 0.2rem solid white;
border-radius: 50%;
font-size: 1.2rem;
background-color: #fb781a;
color: white;
text-align: center;
}
.p-upload-progress-bg, .p-upload-check-bg {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
& .i-text {
position: absolute;
text-align: center;
color: white;
text-shadow: 0 0 2px rgba(0, 0, 0, .85);
}
& .i-bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #1890ff;
//border-radius: 0.5rem 0 0 0.5rem;
}
}
.p-upload-progress-bg.smart-bar{
height: 50px!important;
margin-top: 20px!important;
}
.p-upload-check-bg {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
& .i-text {
position: absolute;
text-align: center;
color: white;
text-shadow: 0 0 2px rgba(0, 0, 0, .85);
}
& .i-bg {
position: absolute;
left: 0;
top: 0;
height: 100%;
background-color: #17a2b8;
//border-radius: 0.5rem 0 0 0.5rem;
}
}
//:host ::ng-deep .ant-upload {
// display: block;
//}
import {Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SecurityContext, ViewChild} from '@angular/core';
import {NzMessageService, UploadChangeParam, UploadFile, UploadFileStatus} from 'ng-zorro-antd';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
@Component({
selector: 'app-upload-video',
templateUrl: './upload-video.component.html',
styleUrls: ['./upload-video.component.scss']
})
export class UploadVideoComponent implements OnChanges, OnDestroy {
uploading = false;
checking = false;
checkVideoExists = false;
uploadClicked = false;
@Input()
videoFile = null;
uploaderInst = null;
progress = 0;
// @Input()
// setCovering = false;
@Input()
videoUrl = '';
@ViewChild('videoNode', {static: true })
videoNode: ElementRef;
// @Input()
// extractCoverFunc = null;
@Output()
videoUploaded = new EventEmitter();
@Output()
videoUploadFailure = new EventEmitter();
@Output()
extractVideoCover = new EventEmitter();
@Input()
showUploadBtn = true;
@Input()
item: any;
// videoItem = null;
uploadUrl;
uploadData;
constructor(private nzMessageService: NzMessageService,
private sanitization: DomSanitizer
// private cwService: _coursewareService,
// private resService: ResourceService
) {
this.uploading = false;
this.videoFile = null;
this.uploadUrl = (<any> window).courseware.uploadUrl();
this.uploadData = (<any> window).courseware.uploadData();
window['air'].getUploadCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
}
ngOnChanges() {
// if (!this.videoFile || this.showUploadBtn) {
// return;
// }
// this.beforeUpload(this.videoFile);
// this.handleUpload();
}
ngOnDestroy(): void {
URL.revokeObjectURL(this.videoUrl);
}
safeVideoUrl(url) {
console.log(url);
return this.sanitization.bypassSecurityTrustResourceUrl(url); // `${url}`;
}
videoLoadedMetaData() {
}
handleChange(info: UploadChangeParam/* { type: string, file: UploadFile, event: any }*/): void {
console.log('info:' , info);
switch (info.type) {
case 'start':
// this.beforeUpload(info.file);
if (!this.checkSelectFile(info.file)) {
return;
}
this.uploading = true;
this.progress = 0;
break;
case 'success':
this.uploadSuccess(info.file);
// this.beforeUpload(info.file);
// this.uploadSuccess(info.file);
break;
case 'progress':
this.progress = info.event.percent;
this.doProgress(this.progress);
break;
}
}
checkSelectFile(file) {
if (!file.lastModified) {
return false;
}
const delta = 500;
const isOverSize = (file.size / 1024 / 1024) < delta;
if (!isOverSize) {
this.nzMessageService.error(`video must smaller than ${delta}MB!`);
return false;
}
return true;
}
checkHashFinish = (hash) => {
this.checking = false;
this.uploading = true;
}
uploadSuccess = (file) => {
this.nzMessageService.info('Upload Success');
// this.updateStatus(false);
this.uploading = false;
this.videoFile = null;
// this.updateSource(url);
this.videoUrl = file.response.url;
// console.log(this.picUrl)
// this.uploadFinished(url);
// if (!inOSS) {
const vid = document.createElement('video');
vid.addEventListener('loadedmetadata', () => {
const height = vid.videoHeight;
const width = vid.videoWidth;
let duration = vid.duration;
if (duration) {
duration = duration * 1000;
}
file.height = height;
file.width = width;
file.duration = duration;
vid.preload = 'none';
vid.src = '';
vid.remove();
this.videoUploaded.emit(file.response);
// this.resService.updateVideo(id, {width, height, duration}).then( () => {
// this.videoUploaded.emit({res_id: id, id, name, hash, url});
// });
});
vid.src = file.response.url;
// } else {
// this.videoUploaded.emit(file.response);
// }
}
uploadFailure = (err, file) => {
this.uploading = false;
if (err.name && err.name === 'cancel') {
return;
}
console.log(err);
this.nzMessageService.error('Upload Error ' + err.message);
this.videoUploadFailure.emit(file);
}
doProgress = (p) => {
if (p > 1) {
p = 1;
}
if (p < 0) {
p = 0;
}
// console.log(Math.floor(p * 100));
this.progress = Math.floor(p * 100);
}
}
......@@ -4,5 +4,41 @@
.model-content {
width: 100%;
height: 100%;
padding: 2em;
overflow-x: hidden;
overflow-y: auto;
}
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 4px;
font-size: 24px;
color: #999;
transition: all 0.3s;
}
.dynamic-delete-button:hover {
color: #777;
}
.dynamic-input {
width: calc(100% - 36px);
margin-right: 8px;
}
.options{
display: block;
height: 32px;
line-height: 32px;
}
.options-input{
/*width: 100px;*/
margin-left: 10px;
flex: 1 auto;
}
.opt-row{
display: flex;
margin-bottom: 8px;
}
.opt-lbl{
flex: 0 auto;
}
......@@ -2,24 +2,113 @@
<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="body">
<form nz-form nzLayout="Horizontal" [formGroup]="validateForm" (ngSubmit)="submitForm()">
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired nzFor="title">标题</nz-form-label>
<nz-form-control nzSpan="20" nzErrorTip="请正确设置文章标题">
<input nz-input formControlName="title" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired nzFor="lens">蓝思值</nz-form-label>
<nz-form-control nzSpan="20" nzErrorTip="请正确设置文章蓝思值">
<nz-input-number nz-input formControlName="lens" id="lens" ></nz-input-number>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired nzFor="analysis">分析</nz-form-label>
<nz-form-control nzSpan="20" nzErrorTip="请正确设置文章分析">
<input nz-input formControlName="analysis" id="analysis" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzSpan="4" nzRequired nzFor="contents">文章内容</nz-form-label>
<nz-form-control nzSpan="20" nzErrorTip="请正确设置文章内容">
<textarea formControlName="contents" id="contents" nz-input rows="9"></textarea>
</nz-form-control>
</nz-form-item>
<ng-container *ngFor="let q of questionsOfControl; let i = index;">
<nz-divider [nzText]="'问题'+ (i + 1)"></nz-divider>
<nz-form-item nz-row>
<nz-form-label nz-col nzSpan="6" nzRequired>题目</nz-form-label>
<nz-form-control nz-col nzSpan="18" nzErrorTip="请正确设置问题">
<input class="dynamic-input" nz-input [formControlName]="q.questionTitle" />
<i nz-icon nzType="minus-circle-o" class="dynamic-delete-button" (click)="removeField(q, $event)"></i>
</nz-form-control>
</nz-form-item>
<!-- <nz-form-item nz-row>-->
<!-- <nz-form-label nz-col nzSpan="6" nzRequired>答案类型</nz-form-label>-->
<!-- <nz-form-control nz-col nzSpan="18" nzErrorTip="The input is not valid E-mail!">-->
<!-- <nz-radio-group (ngModelChange)="changeQuestionType(q.questionType, $event)" [formControlName]="q.questionType">-->
<!-- <label nz-radio [nzValue]="0">判断对错</label>-->
<!-- <label nz-radio [nzValue]="1">课文问与答</label>-->
<!-- </nz-radio-group>-->
<!-- </nz-form-control>-->
<!-- </nz-form-item>-->
<!-- *ngIf=" validateForm.controls[q.questionType].value === 0"-->
<nz-form-item nz-row>
<nz-form-label nz-col nzSpan="6" nzRequired>设置选项答案</nz-form-label>
<nz-form-control nz-col nzSpan="18" nzErrorTip="请正确设置答案">
<nz-radio-group [formControlName]="q.questionAnswer" style="width: 100%;">
<label class="options" class="opt-row">
<label nz-radio nzValue="A" class="opt-lbl">A:</label>
<input type="text" nz-input [formControlName]="q.questionOptionA" class="options-input" />
</label>
<label class="options" class="opt-row">
<label nz-radio nzValue="B" class="opt-lbl">B:</label>
<input type="text" nz-input [formControlName]="q.questionOptionB" class="options-input" />
</label>
<label class="options" class="opt-row">
<label nz-radio nzValue="C" class="opt-lbl">C:</label>
<input type="text" nz-input [formControlName]="q.questionOptionC" class="options-input" />
</label>
<label class="options" class="opt-row">
<label nz-radio nzValue="D" class="opt-lbl">D:</label>
<input type="text" nz-input [formControlName]="q.questionOptionD" class="options-input" />
</label>
</nz-radio-group>
<!-- <nz-radio-group [formControlName]="q.questionAnswer">-->
<!-- <label nz-radio [nzValue]="0">错误</label>-->
<!-- <label nz-radio [nzValue]="1">正确</label>-->
<!-- </nz-radio-group>-->
</nz-form-control>
</nz-form-item>
<!-- *ngIf=" validateForm.controls[q.questionType].value === 0"-->
<nz-form-item nz-row>
<nz-form-label nz-col nzSpan="6" nzRequired>错误解答</nz-form-label>
<nz-form-control nz-col nzSpan="18" nzErrorTip="请正确设置错误解答">
<input nz-input [formControlName]="q.questionExplain"/>
</nz-form-control>
</nz-form-item>
</ng-container>
<nz-form-item>
<nz-form-control [nzXs]="{ span: 24, offset: 0 }" [nzSm]="{ span: 20, offset: 4 }">
<button nz-button nzType="dashed" class="add-button" (click)="addField($event)">
<i nz-icon nzType="plus"></i>
添加问题
</button>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control [nzXs]="{ span: 24, offset: 0 }" [nzSm]="{ span: 20, offset: 4 }">
<button nz-button nzType="primary">Submit</button>
</nz-form-control>
</nz-form-item>
</form>
</div>
</div>
\ No newline at end of file
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { NzMessageService } from 'ng-zorro-antd';
interface QuestionType {
id: number;
questionIndex: string;
questionTitle: string;
// questionType: string;
questionAnswer: string;
questionExplain: string;
questionOptionA: string;
questionOptionB: string;
questionOptionC: string;
questionOptionD: string;
}
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit, OnChanges, OnDestroy {
export class FormComponent implements OnInit, OnDestroy {
// 储存数据用
saveKey = "test_0011";
saveKey = 'JM_12';
// 储存对象
item;
// title = '';
// lens = '';
// contents = '';
// questions = [];
validateForm: FormGroup;
questionsOfControl: Array<QuestionType> = [];
constructor(private appRef: ApplicationRef,
private fb: FormBuilder,
private nzMessageService: NzMessageService,
private changeDetectorRef: ChangeDetectorRef) {
// @ts-ignore
window.f = this;
}
addField(qItem?: QuestionType|Event): void {
// if (e) {
// e.preventDefault();
// }
const id = this.questionsOfControl.length > 0 ? this.questionsOfControl[this.questionsOfControl.length - 1].id + 1 : 0;
const control = {
id,
questionIndex: `${id}`,
questionTitle: `questionTitle_${id}`,
// questionType: `questionType_${id}`,
questionAnswer: `questionAnswer_${id}`,
questionExplain: `questionExplain_${id}`,
questionOptionA: `questionOptionA_${id}`,
questionOptionB: `questionOptionB_${id}`,
questionOptionC: `questionOptionC_${id}`,
questionOptionD: `questionOptionD_${id}`,
};
const index = this.questionsOfControl.push(control);
// console.log(this.questionsOfControl[this.questionsOfControl.length - 1]);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionTitle,
new FormControl(qItem ? qItem['questionTitle'] : null, Validators.required)
);
// this.validateForm.addControl(
// this.questionsOfControl[index - 1].questionType,
// new FormControl(0, Validators.required)
// );
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionAnswer,
new FormControl(qItem ? qItem['questionAnswer'] : 0, Validators.required)
);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionExplain,
new FormControl(qItem ? qItem['questionExplain'] : '', Validators.required)
);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionOptionA,
new FormControl(qItem ? qItem['questionOptionA'] : '', Validators.required)
);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionOptionB,
new FormControl(qItem ? qItem['questionOptionB'] : '', Validators.required)
);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionOptionC,
new FormControl(qItem ? qItem['questionOptionC'] : '', Validators.required)
);
this.validateForm.addControl(
this.questionsOfControl[index - 1].questionOptionD,
new FormControl(qItem ? qItem['questionOptionD'] : '', Validators.required)
);
}
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) {
removeField(i: QuestionType, e: MouseEvent): void {
e.preventDefault();
if (this.questionsOfControl.length > 1) {
const index = this.questionsOfControl.indexOf(i);
this.questionsOfControl.splice(index, 1);
console.log(this.questionsOfControl);
this.validateForm.removeControl(`questionTitle_${i.questionIndex}`);
this.validateForm.removeControl(`questionAnswer_${i.questionIndex}`);
this.validateForm.removeControl(`questionExplain_${i.questionIndex}`);
this.validateForm.removeControl(`questionOptionA_${i.questionIndex}`);
this.validateForm.removeControl(`questionOptionB_${i.questionIndex}`);
this.validateForm.removeControl(`questionOptionC_${i.questionIndex}`);
this.validateForm.removeControl(`questionOptionD_${i.questionIndex}`);
}
}
submitForm(): void {
console.log('submit');
for (const i in this.validateForm.controls) {
this.validateForm.controls[i].markAsDirty();
this.validateForm.controls[i].updateValueAndValidity();
}
if (this.validateForm.status !== 'VALID') {
return ;
}
const title = this.validateForm.controls['title'].value || 'Read and answer.';
const lens = this.validateForm.controls['lens'].value;
const contents = this.validateForm.controls['contents'].value;
const analysis = this.validateForm.controls['analysis'].value;
const questions = [];
const idxCache = [];
for (const name in this.validateForm.controls) {
const idxReg = /\d+/.exec(name);
if (idxReg === null) {
continue;
}
const idx = idxReg[0];
if (idxCache.includes(idx)) {
continue;
}
idxCache.push(idx);
const questionTitle = this.validateForm.controls[`questionTitle_${idx}`].value;
const questionAnswer = this.validateForm.controls[`questionAnswer_${idx}`].value;
const questionExplain = this.validateForm.controls[`questionExplain_${idx}`].value;
const questionOptionA = this.validateForm.controls[`questionOptionA_${idx}`].value;
const questionOptionB = this.validateForm.controls[`questionOptionB_${idx}`].value;
const questionOptionC = this.validateForm.controls[`questionOptionC_${idx}`].value;
const questionOptionD = this.validateForm.controls[`questionOptionD_${idx}`].value;
questions.push({
questionTitle,
questionAnswer,
questionExplain,
questionOptionA,
questionOptionB,
questionOptionC,
questionOptionD,
});
}
this.item = {
title,
lens ,
contents,
analysis,
questions
};
this.save();
this.nzMessageService.success('保存成功');
}
ngOnInit() {
this.validateForm = this.fb.group({
title: ['Read and answer.', [Validators.required]],
lens: ['', [Validators.required]],
analysis: ['', [Validators.required]],
contents: ['', [Validators.required]],
});
this.item = {};
// 获取存储的数据
(<any> window).courseware.getData((data) => {
(window as any).courseware.getData((data) => {
if (data) {
this.item = data;
}
this.init();
this.init(data);
this.changeDetectorRef.markForCheck();
this.changeDetectorRef.detectChanges();
this.refresh();
......@@ -39,24 +192,39 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}, this.saveKey);
}
changeQuestionType(typeName, val) {
console.log(typeName, val);
}
ngOnChanges() {
}
ngOnDestroy() {
}
init() {
init(data) {
console.log('init', data);
this.validateForm = this.fb.group({
title: [data && data.title ? data.title : 'Read and answer.' , [Validators.required]],
lens: [data?.lens, [Validators.required]],
analysis: [data?.analysis, [Validators.required]],
contents: [data?.contents, [Validators.required]],
});
if (data?.questions && Array.isArray(data?.questions)) {
data.questions.forEach((q, idx) => {
this.addField(q);
});
} else {
this.addField();
}
}
/**
* 储存图片数据
* @param e
*/
onImageUploadSuccess(e, key) {
......@@ -66,7 +234,6 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
/**
* 储存音频数据
* @param e
*/
onAudioUploadSuccess(e, key) {
......@@ -80,7 +247,8 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存数据
*/
save() {
(<any> window).courseware.setData(this.item, null, this.saveKey);
console.log(this.item);
(window as any).courseware.setData(this.item, null, this.saveKey);
this.refresh();
}
......
.game-container {
.main{
width: 100%;
height: 100%;
position: relative;
}
.body{
width: 100%;
height: 100%;
background: #599b43;
padding-top: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 0;
}
.pager{
text-align: center;
font-size: 14px;
color: #cacaca;
}
.lens{
background: #0dbafa;
color: #fff;
font-size: 28px;
padding: 2px .75em;
border-radius: 8px;
position: relative;
top: -4px;
font-family: 'ABlack';
}
.title{
margin-left: 80px;
margin-bottom: 16px;
flex: 0 auto;
color: #fff;
font-size: 36px;
font-family: ABlack;
}
.content{
/*background: url(../../assets/play/rectangle@2x.png), url(../../assets/play/rectangle@2x.png);*/
/*background-size: 100%, contain;*/
/*background-repeat: no-repeat, no-repeat;*/
/*background-position: center top, center bottom;*/
background: url(../../assets/play/bg2.jpg);
background-size: 100%;
background-repeat: repeat-y;
background-position: center;
border-radius: 72px 72px 0 0;
flex: 1 auto;
font-size: 22px;
color: #000;
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.lesson-text-container{
flex: 1 auto;
/*overflow-y: auto;*/
overflow: hidden;
padding: 50px 72px 0 72px;
position: relative;
}
.lesson-text{font-family: ASans; font-size: 22px;}
.questions{
flex: 0 auto;
height: 280px;
margin-bottom: 48px;
}
:host ::ng-deep .question-card-box{
width: 100%;
height: 100%;
background: #ffffff;
background-size: cover;
padding: 24px 6px 36px;
position: relative;
/*transform: scale(.9);*/
}
:host ::ng-deep .bscroll-indicator {
/*position: absolute;*/
/*right: 82px;*/
/*height: 100%;*/
/*width: 8px;*/
background: #3e9e33!important;
/*margin-top: 50px;*/
}
.questions-container{
height: 100%;
overflow: hidden;
}
:host ::ng-deep .question-card{
height: 100%;
background: #fff;
box-shadow: 0 5px 11px #eaddbe;
border-radius: 20px;
}
:host ::ng-deep .question-submit-card{
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.question-card-box.is-active > .question-card{
transform: scale(1);
}
.question-card-box > .question-card{
transform: scale(.85);
}
.submit-btn-shadow{
position: absolute;
z-index: 0;
height: 96px;
margin-top: 48px;
}
.submit-btn{
z-index: 1;
height: 72px;
}
#canvas {
.result{
width: 100%;
height: 100%;
background: rgba(255,255,255, .8);
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.result-body {
padding: 3.5em 5em 2em 5em;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.guagou{
background: url(../../assets/play/guagou.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center top;
width: 100%;
height: 75px;
position: absolute;
top: 25px;
left: 50%;
margin-left: -50%;
}
.body-board{
height: 100%;
width: 100%;
background: #3e9e33;
border-radius: 3rem;
padding-top: 66px;
padding-left: 2em;
padding-right: 2em;
flex: 1 auto;
margin-bottom: 2em;
box-shadow: 0px 14px 20px 0px #3e9e3399;
overflow: hidden;
display: flex;
flex-direction: column;
}
.body-board-container{
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.btn-text{
color: #fff;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
text-align: center;
line-height: 72px;
font-size: 30px;
font-weight: 700;
}
.btn-wen{
z-index: 999;
width: 56px;
height: 56px;
/*background: url(/assets/play/btn2.png);*/
/*background-size: contain;*/
/*background-repeat: no-repeat;*/
/*background-position: center;*/
position: absolute;
right: 8px;
bottom: 128px;
color: #fff;
font-size: 28px;
line-height: 53px;
text-align: center;
box-shadow: -1px 20px 8px -10px #dfac4866;
border-radius: 28px;
}
.btn-jie{
z-index: 999;
width: 48px;
height: 48px;
/*background: url(/assets/play/btn1.png);*/
/*background-size: contain;*/
/*background-repeat: no-repeat;*/
/*background-position: center;*/
position: absolute;
right: 12px;
bottom: 40px;
color: #fff;
font-size: 24px;
line-height: 48px;
text-align: center;
box-shadow: 0px 20px 8px -10px #3e9e3366;
border-radius: 26px;
}
.btn-help{
width: 48px;
height: 48px;
/*box-shadow: 0px 20px 8px -10px #3e9e3366;*/
border-radius: 26px;
z-index: 999;
position: absolute;
}
.btn-help.wen{
right: 12px;
bottom: 128px;
box-shadow: -1px 20px 8px -10px #dfac4866;
}
.btn-help.wen.big{
}
.btn-help.jie{
right: 12px;
bottom: 40px;
box-shadow: -1px 20px 8px -10px #3e9e3366;
}
.btn-help.big{
right: 8px;
width: 56px;
height: 56px;
text-align: center;
/*box-shadow: -1px 20px 8px -10px #dfac4866;*/
border-radius: 28px;
}
.btn-help.wen:active{
bottom: 124px;
box-shadow: 0px 12px 8px -10px #dfac4866;
}
.btn-help.jie:active{
bottom: 36px;
box-shadow: 0px 12px 8px -10px #3e9e3366;
}
.load-more{
flex: 0 auto;
height: 80px;
display: flex;
justify-content: center;
align-items: center;
}
.load-more-btn-box{
position: relative;
margin: 0 2em;
}
.load-more-btn-box .submit-btn-shadow{
position: absolute;
z-index: -1;
height: 96px;
margin-top: 18px;
width: 100%;
}
.load-more-btn-box .submit-btn {
width: 100%;
box-shadow: 0 23px 24px -20px #dfac48;
}
.load-more-btn-box .submit-btn:active {
box-shadow: 0 15px 24px -20px #dfac48;
position: relative;
top: 4px;
}
.question-row{
border-bottom: 4px solid #2c8520;
padding-bottom: 1.2em;
margin-bottom: 1.2em;
}
.q-wrapper{
}
.q-info{
/*display: flex;*/
color: #fff;
font-size: 24px;
font-family: 'ASans';
}
.q-answer{
padding: 0px 12px;
border-radius: 8px;
margin-right: 8px;
font-size: 22px;
line-height: 35px;
font-family: 'ABLACK';
}
.q-answer.r{
background: #007b3e;
}
.q-answer.w{
background: #d71818;
}
.q-title{
@font-face
{
font-family: 'BRLNSDB';
src: url("../../assets/font/BRLNSDB.TTF") ;
}
.explain-bubble{
width: 1.5em;
height: 1.5em;
margin-right: 4px;
position: absolute;
left: 1.5em;
}
.q-disabled{
pointer-events: none;
}
.q-explain{
background: #3b962e;
padding: .5em 1.5em .5em 3.5em;
border-radius: 16px;
color: #ffe9b1;
font-size: 16px;
margin-top: 13px;
font-weight: 500;
position: relative;
font-family: 'ABLACK';
}
@keyframes splide-loading{0%{transform:rotate(0)}to{transform:rotate(1turn)}}
.splide__container{position:relative;box-sizing:border-box}
.splide__list{margin:0!important;padding:0!important;width:-webkit-max-content;width:max-content;will-change:transform}
.splide.is-active .splide__list{display:flex}
.splide__pagination{display:inline-flex;align-items:center;width:95%;flex-wrap:wrap;justify-content:center;margin:0}
.splide__pagination li{list-style-type:none;display:inline-block;line-height:1;margin:0}
.splide{visibility:hidden}
.splide,.splide__slide{position:relative;outline:none}
.splide__slide{box-sizing:border-box;list-style-type:none!important;margin:0;flex-shrink:0}
.splide__slide img{vertical-align:bottom}
.splide__slider{position:relative}
.splide__spinner{position:absolute;top:0;left:0;right:0;bottom:0;margin:auto;display:inline-block;width:20px;height:20px;border-radius:50%;border:2px solid #999;border-left-color:transparent;animation:splide-loading 1s linear infinite}
.splide__track{position:relative;z-index:0;overflow:hidden}
.splide--draggable>.splide__track>.splide__list>.splide__slide{-webkit-user-select:none;user-select:none}
.splide--fade>.splide__track>.splide__list{display:block}
.splide--fade>.splide__track>.splide__list>.splide__slide{position:absolute;top:0;left:0;z-index:0;opacity:0}
.splide--fade>.splide__track>.splide__list>.splide__slide.is-active{position:relative;z-index:1;opacity:1}
.splide--rtl{direction:rtl}
.splide--ttb>.splide__track>.splide__list{display:block}
.splide--ttb>.splide__pagination{width:auto}
.splide{
margin: 0 auto;
height: 100%;
}
.splide__track{
height: 100%;
}
.splide__list{
height: 100%;
}
.q-index{
width: 40px;
height: 40px;
border-radius: 20px;
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
position: absolute;
left: 16px;
top: 16px;
font-family: 'ABlack';
font-size: 22px;
}
.q-index.even{
background: #f3dcdf;
color: #d7437c;
}
.q-index.odd{
background: #e3e9f0;
color: #3692be;
}
.question-card-body{
height: 100%;
margin: 16px 56px;
display: flex;
flex-direction: column;
padding-top: 16px;
}
.lesson-text-wrapper{
height: 100%;
overflow-x: hidden;
overflow-y: auto;
/*overflow: hidden;*/
position: relative;
}
.question-card-body .qt{
text-align: center;
font-size: 28px;
font-weight: 400;
flex: 1 auto;
font-family: 'ASans';
}
.question-answer{
height: 140px;
/*display: flex;*/
/*flex: 0 auto;*/
}
.btn-box{
/*flex: 1;*/
/*display: flex;*/
/*justify-content: center;*/
/*align-items: start;*/
width: 49%;
float: left;
width: calc(50% - 32px);
float: left;
margin: 5px 16px;
position: relative;
}
.btn-box .option{
background-image: url(../../assets/play/opt-bg.png);
background-repeat: no-repeat;
background-size: contain;
width: 100%;
height: 56px;
line-height: 44px;
font-size: 18px;
color: #fff;
text-align: center;
z-index: 1;
position: relative;
}
.btn-box .option .lbl{
background-image: url(../../assets/play/rectangle-56@3x.png);
background-repeat: no-repeat;
background-size: contain;
width: 30px;
height: 30px;
position: absolute;
top: 8px;
left: 8px;
font-size: 18px;
line-height: 32px;
font-weight: bolder;
color: #82d7ff;
text-align: center;
}
.btn-box.selected .option{
background-image: url(../../assets/play/opt-bg-sel.png);
top: 8px;
}
.btn-box.selected .option .lbl{
color: #fff;
}
.btn-box img.btn{
height: 56px;
z-index: 1;
}
.btn-box img.shadow{
position: absolute;
height: 104px;
z-index: 0;
position: absolute;
height: 72px;
z-index: 0;
left: 50%;
transform: translateX(-50%);
width: 80%;
}
.btn-box.pressed img.btn{
margin-top: 6px;
}
.btn-box.pressed img.shadow{
height: 84px;
}
.btn-true{
background: url(../../assets/play/rectangle-8-copy@3x.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 100%;
text-align: center;
line-height: 48px;
color: #fff;
font-size: 32px;
font-weight: 700;
}
.btn-false{
background: url(../../assets/play/rectangle-8@3x.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 100%;
text-align: center;
line-height: 48px;
color: #fff;
font-size: 32px;
font-weight: 700;
}
:host ::ng-deep nz-carousel{
/*position: absolute;*/
padding-left: 100px;
padding-right: 100px;
width: 100%;
position: static;
/*margin: 0 auto;*/
}
:host ::ng-deep .slick-slider{
width: 100%;
position: static;
}
:host ::ng-deep .ant-carousel .slick-list{
/*overflow-x: auto;*/
width: 800px;
}
:host ::ng-deep .ant-carousel .slick-initialized .slick-slide{
height: 200px!important;
}
:host ::ng-deep .ant-carousel .slick-list .slick-slide.slick-active{
height: 220px!important;
}
@font-face {
font-family: 'ABlack';
src: url("../../assets/font/Aileron-Black.ttf") ;
}
@font-face {
font-family: 'ABold';
src: url("../../assets/font/Aileron-Bold.ttf") ;
}
@font-face {
font-family: 'ASans';
src: url("../../assets/font/DroidSansFallback.ttf") ;
}
.analysis{
position: relative;
width: 100%;
}
.analysis-content{
background: #54c348;
margin: 8px;
padding: 12px;
color: #fff;
}
.analysis:before{
content: '';
position: absolute;
width: 40px;
height: 40px;
border-top: 1px solid #54c348;
border-left: 1px solid #54c348;
}
.analysis:after{
content: '';
position: absolute;
width: 40px;
height: 40px;
border-bottom: 1px solid #54c348;
border-right: 1px solid #54c348;
right: 0;
bottom: 0;
}
<div class="game-container" #wrap>
<canvas id="canvas" #canvas></canvas>
<div class="main">
<div class="body">
<div class="title">
{{data.title}} <span class="lens">{{data.lens}}L</span>
</div>
<div class="content">
<div class="lesson-text-container" >
<div class="lesson-text-wrapper" >
<div class="lesson-text" [innerHTML]="data.contents | contents: 2"></div>
</div>
</div>
<div class="questions" *ngIf="!isSubmitted" >
<div class="questions-container">
<div class="splide">
<div class="splide__track">
<ul class="splide__list">
<li class="splide__slide question-card-box" *ngFor="let q of data.questions; let i = index">
<div class="question-card" [ngClass]="isSubmitted ? 'q-disabled' : ''">
<div class="q-index" [ngClass]="i%2 ? 'odd' : 'even'">{{ formatIndex(i + 1) }}</div>
<div class="question-card-body">
<div class="qt" [innerText]="q.questionTitle"></div>
<div class="question-answer">
<ng-container *ngFor="let o of ['A', 'B', 'C', 'D']">
<div class="btn-box" [ngClass]="userAnswers[i]=== o ? 'selected' : ''"
(mousedown)="btnPressed($event, i , o)"
(touchstart)="btnPressed($event, i , o)">
<img class="shadow" src="assets/play/blue-shadow.png" alt="">
<div class="option">
<div class="lbl">{{o}}</div>
<span>{{q['questionOption'+o]}}</span>
</div>
</div>
</ng-container>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div class="pager">{{splide ? splide.index + 1: 0}}/{{splide ? splide.length: 0}}</div>
</div>
</div>
</div>
<template #SubmitTemplate>
<li class="splide__slide">
<div class="question-submit-card">
<img class="submit-btn-shadow" src="assets/play/submit-shadow.png" alt="">
<img class="submit-btn" src="assets/play/submit.png">
</div>
</li>
</template>
<div class="result" [@openClose]="isOpenResultPanel ? 'open' : 'closed'" >
<div class="result-body">
<div class="guagou"></div>
<div class="body-board">
<div class="analysis">
<div class="analysis-content">{{data.analysis}}</div>
</div>
<div class="body-board-container">
<div class="question-row" *ngFor="let q of data.questions; let i = index;">
<div class="q-wrapper">
<div class="q-info">
<div class="q-title">{{i + 1}}. {{q.questionTitle}}</div>
<div class="q-answer" [ngClass]="q.questionAnswer == userAnswers[i] ? 'r' : 'w'">
{{q.questionAnswer}}
</div>
</div>
<div class="q-explain">
<img class="explain-bubble" src="assets/play/explain-bubble.png" alt="">
{{q.questionExplain}}
</div>
</div>
</div>
</div>
</div>
<div class="load-more" *ngIf="isShowMore">
<div class="load-more-btn-box">
<!-- <img class="submit-btn-shadow" src="/assets/play/submit-shadow.png" alt="">-->
<!-- <img class="submit-btn" src="/assets/play/load-more-btn.png">-->
<input class="submit-btn" type="image" src="assets/play/more-btn.png" (click)="moreQuestion($event)"/>
<!-- <div class="btn-text">更多练习</div>-->
</div>
</div>
</div>
</div>
<input *ngIf="isSubmitted" [ngClass]="{'big': isOpenResultPanel}"
type="image" src="assets/play/btn-wen.png"
(click)="closeResultPanel()"
class="btn-help wen ">
<input *ngIf="isSubmitted" [ngClass]="{'big': !isOpenResultPanel}"
type="image" src="assets/play/btn-jie.png"
(click)="openResultPanel()"
class="btn-help jie">
<!-- <div style="display: none">-->
<!-- <audio src="assets/play/music/1.mp3" preload="auto" #submit></audio>-->
<!-- <audio src="assets/play/music/2.mp3" preload="auto" #more></audio>-->
<!-- <audio src="assets/play/music/3.mp3" preload="auto" #panel></audio>-->
<!-- <audio src="assets/play/music/4.mp3" preload="auto" #choose></audio>-->
<!-- </div>-->
</div>
......@@ -11,679 +11,294 @@ import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators';
import TWEEN from '@tweenjs/tween.js';
import { NzCarouselComponent } from 'ng-zorro-antd';
import Splide from '@splidejs/splide';
import { animate, state, style, transition, trigger } from '@angular/animations';
interface QuestionType {
questionAnswer: number;
questionExplain: string;
questionTitle: string;
}
interface DataType {
title?: string;
contents?: string;
lens?: number;
analysis?: string;
questions?: QuestionType[];
}
@Component({
selector: 'app-play',
templateUrl: './play.component.html',
styleUrls: ['./play.component.css']
styleUrls: ['./play.component.css'],
animations: [
trigger('openClose', [
// ...
state('open', style({
top: '0',
})),
state('closed', style({
top: '100%',
})),
transition('open => closed', [
animate('0.3s')
]),
transition('closed => open', [
animate('0.3s')
]),
]),
],
})
export class PlayComponent implements OnInit, OnDestroy {
@ViewChild('canvas', {static: true }) canvas: ElementRef;
@ViewChild('wrap', {static: true }) wrap: ElementRef;
// 数据
data;
ctx;
canvasWidth = 1280; // canvas实际宽度
canvasHeight = 720; // canvas实际高度
canvasBaseW = 1280; // canvas 资源预设宽度
canvasBaseH = 720; // canvas 资源预设高度
mx; // 点击x坐标
my; // 点击y坐标
isOpenResultPanel = false;
isSubmitted = false;
canSubmitted = true;
saveKey = 'JM_12';
data: DataType;
userAnswers = {};
isShowMore = false;
@ViewChild('SubmitTemplate', {static: true}) submitTemplate: ElementRef;
// 资源
rawImages = new Map(res);
rawAudios = new Map(resAudio);
@ViewChild('submit', {static: true}) submitAudio: ElementRef;
@ViewChild('more', {static: true}) moreAudio: ElementRef;
@ViewChild('panel', {static: true}) panelAudio: ElementRef;
@ViewChild('choose', {static: true}) chooseAudio: ElementRef;
images = new Map();
animationId: any;
winResizeEventStream = new Subject();
audioObj = {};
@ViewChild('carousel', {static: true}) carousel: NzCarouselComponent;
splide: Splide;
private AudioEffect: { [key: string]: HTMLAudioElement };
private ss: (el) => void;
constructor() {
window.addEventListener('message', (e) => {
const msgData = e.data;
renderArr;
mapScale = 1;
canvasLeft;
canvasTop;
saveKey = 'test_0011';
btnLeft;
btnRight;
pic1;
pic2;
canTouch = true;
curPic;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.winResizeEventStream.next();
if (msgData.action === 'is_show_more') {
const data = JSON.parse( msgData.data );
console.log('in is_show_more e: ', e);
console.log('in is_show_more data: ', data);
this.isShowMore = data.isShowMore;
}
});
// @ts-ignore
window.jm = this;
this.AudioEffect = {
submit: new Audio('assets/play/music/1.wav'),
more: new Audio('assets/play/music/2.wav'),
panel: new Audio('assets/play/music/3.wav'),
choose: new Audio('assets/play/music/4.wav'),
};
ngOnInit() {
this.data = {};
// 获取数据
const getData = (<any> window).courseware.getData;
getData((data) => {
if (data && typeof data == 'object') {
this.data = data;
Object.values(this.AudioEffect).forEach(a => {
a.preload = 'auto';
});
}
// console.log('data:' , data);
// 初始化 各事件监听
this.initListener();
// 若无数据 则为预览模式 需要填充一些默认数据用来显示
this.initDefaultData();
// 初始化 音频资源
this.initAudio();
// 初始化 图片资源
this.initImg();
// 开始预加载资源
this.load();
}, this.saveKey);
toggleResultPanel() {
this.isOpenResultPanel = !this.isOpenResultPanel;
}
ngOnDestroy() {
window['curCtx'] = null;
window.cancelAnimationFrame(this.animationId);
this.cleanAudio();
playSoundEffect(name) {
if (this.AudioEffect[name]) {
this.AudioEffect[name].pause();
this.AudioEffect[name].currentTime = 0;
this.AudioEffect[name].play();
}
cleanAudio() {
if (this.audioObj) {
for (const key in this.audioObj) {
this.audioObj[key].pause();
}
}
openResultPanel() {
this.playSoundEffect('panel');
this.isOpenResultPanel = true;
}
load() {
// 预加载资源
this.loadResources().then(() => {
window["air"].hideAirClassLoading(this.saveKey, this.data);
this.init();
this.update();
});
closeResultPanel() {
this.playSoundEffect('panel');
this.isOpenResultPanel = false;
}
init() {
this.initCtx();
this.initData();
this.initView();
}
initCtx() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.canvas.nativeElement.width = this.wrap.nativeElement.clientWidth;
this.canvas.nativeElement.height = this.wrap.nativeElement.clientHeight;
ngOnInit() {
// this.AudioEffect = {
// submit: this.submitAudio.nativeElement, // new Audio('assets/play/music/1.mp3'),
// more: this.moreAudio.nativeElement, // new Audio('assets/play/music/2.mp3'),
// panel: this.panelAudio.nativeElement, // new Audio('assets/play/music/3.mp3'),
// choose: this.chooseAudio.nativeElement, // new Audio('assets/play/music/4.mp3'),
// };
//
// Object.values(this.AudioEffect).forEach(a => {
// a.preload = 'auto';
// });
this.ctx = this.canvas.nativeElement.getContext('2d');
this.canvas.nativeElement.width = this.canvasWidth;
this.canvas.nativeElement.height = this.canvasHeight;
this.data = {};
window['curCtx'] = this.ctx;
window['curImages'] = this.images;
// 获取数据
const getData = (window as any).courseware.getData;
getData((data) => {
console.log(data);
if (data && typeof data === 'object') {
this.data = data;
}
setTimeout(() => {
this.splide = new Splide( '.splide', {
arrows: false,
pagination: false,
// width: "80%"
// type : 'loop',
updateOnMove: true,
padding: {
right: '11%',
left : '11%',
},
// fixedWidth: '80%',
} ).mount();
updateItem(item) {
if (item) {
item.update();
}
}
updateArr(arr) {
if (!arr) {
this.splide.on( 'click', (e) => {
if (this.splide.index === e.index) {
return;
}
for (let i = 0; i < arr.length; i++) {
arr[i].update(this);
}
}
// do something
console.log('click', e);
this.splide.go(`>${e.index}`);
});
window['air'].hideAirClassLoading(this.saveKey, this.data);
initListener() {
this.winResizeEventStream
.pipe(debounceTime(500))
.subscribe(data => {
this.renderAfterResize();
});
// ---------------------------------------------
const setParentOffset = () => {
const rect = this.canvas.nativeElement.getBoundingClientRect();
this.canvasLeft = rect.left;
this.canvasTop = rect.top;
};
const setMxMyByTouch = (event) => {
if (event.touches.length <= 0) {
return;
}
if (this.canvasLeft == null) {
setParentOffset();
}
this.mx = event.touches[0].pageX - this.canvasLeft;
this.my = event.touches[0].pageY - this.canvasTop;
};
const setMxMyByMouse = (event) => {
this.mx = event.offsetX;
this.my = event.offsetY;
};
// ---------------------------------------------
let firstTouch = true;
const touchDownFunc = (e) => {
if (firstTouch) {
firstTouch = false;
removeMouseListener();
}
setMxMyByTouch(e);
this.mapDown(e);
};
const touchMoveFunc = (e) => {
setMxMyByTouch(e);
this.mapMove(e);
};
const touchUpFunc = (e) => {
setMxMyByTouch(e);
this.mapUp(e);
};
}, this.saveKey);
const mouseDownFunc = (e) => {
if (firstTouch) {
firstTouch = false;
removeTouchListener();
}
setMxMyByMouse(e);
this.mapDown(e);
};
const mouseMoveFunc = (e) => {
setMxMyByMouse(e);
this.mapMove(e);
};
const mouseUpFunc = (e) => {
setMxMyByMouse(e);
this.mapUp(e);
};
const element = this.canvas.nativeElement;
const addTouchListener = () => {
element.addEventListener('touchstart', touchDownFunc);
element.addEventListener('touchmove', touchMoveFunc);
element.addEventListener('touchend', touchUpFunc);
element.addEventListener('touchcancel', touchUpFunc);
};
const removeTouchListener = () => {
element.removeEventListener('touchstart', touchDownFunc);
element.removeEventListener('touchmove', touchMoveFunc);
element.removeEventListener('touchend', touchUpFunc);
element.removeEventListener('touchcancel', touchUpFunc);
};
const addMouseListener = () => {
element.addEventListener('mousedown', mouseDownFunc);
element.addEventListener('mousemove', mouseMoveFunc);
element.addEventListener('mouseup', mouseUpFunc);
};
const removeMouseListener = () => {
element.removeEventListener('mousedown', mouseDownFunc);
element.removeEventListener('mousemove', mouseMoveFunc);
element.removeEventListener('mouseup', mouseUpFunc);
};
addMouseListener();
addTouchListener();
ngOnDestroy() {
}
playAudio(key, now = false, callback = null) {
const audio = this.audioObj[key];
if (audio) {
if (now) {
audio.pause();
audio.currentTime = 0;
formatIndex(num) {
return `${num}`.padStart(2, '0');
}
if (callback) {
audio.onended = () => {
callback();
};
}
audio.play();
isAnsweredQ(i) {
return typeof this.userAnswers !== 'undefined';
}
}
loadResources() {
const pr = [];
this.rawImages.forEach((value, key) => {// 预加载图片
const p = this.preload(value)
.then(img => {
this.images.set(key, img);
})
.catch(err => console.log(err));
pr.push(p);
});
this.rawAudios.forEach((value, key) => {// 预加载音频
const a = this.preloadAudio(value)
.then(() => {
// this.images.set(key, img);
})
.catch(err => console.log(err));
pr.push(a);
btnPressed(event, idx, val) {
event.stopPropagation();
event.preventDefault();
// event.currentTarget.classList.add('pressed');
this.playSoundEffect('choose');
this.userAnswers[idx] = val;
let nextIndex = this.data.questions.findIndex((q, qIdx) => {
return typeof this.userAnswers[qIdx] === 'undefined';
});
return Promise.all(pr);
if (nextIndex < 0) {
nextIndex = this.splide.length - 1;
}
preload(url) {
return new Promise((resolve, reject) => {
const img = new Image();
// img.crossOrigin = "anonymous";
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
if (this.canSubmitted && Object.keys(this.userAnswers).length === this.data.questions.length) {
this.canSubmitted = false;
const submitSlide = this.submitTemplate.nativeElement.children[0].cloneNode(true);
preloadAudio(url) {
return new Promise((resolve, reject) => {
const audio = new Audio();
audio.oncanplay = (a) => {
resolve();
};
audio.onerror = () => {
reject();
};
audio.src = url;
audio.load();
});
const b = submitSlide.querySelector('.submit-btn');
console.log(b);
if (b) {
b.addEventListener('mousedown', this.submitPress );
b.addEventListener('mouseup', this.submitRelease );
b.addEventListener('touchstart', this.submitPress );
b.addEventListener('touchcancel', this.submitRelease );
b.addEventListener('touchend', this.submitRelease );
}
renderAfterResize() {
this.canvasWidth = this.wrap.nativeElement.clientWidth;
this.canvasHeight = this.wrap.nativeElement.clientHeight;
this.init();
this.splide.add( submitSlide);
nextIndex = this.splide.length - 1;
}
this.splide.go( `>${nextIndex}` );
checkClickTarget(target) {
const rect = target.getBoundingBox();
if (this.checkPointInRect(this.mx, this.my, rect)) {
return true;
}
return false;
}
btnRelease(event, idx, val) {
event.stopPropagation();
event.preventDefault();
event.currentTarget.classList.remove('pressed');
this.userAnswers[idx] = val;
let nextIndex = this.data.questions.findIndex((q, qIdx) => {
return typeof this.userAnswers[qIdx] === 'undefined';
});
if (Object.keys(this.userAnswers).length === this.data.questions.length) {
const submitSlide = this.submitTemplate.nativeElement.children[0].cloneNode(true);
getWorlRect(target) {
let rect = target.getBoundingBox();
if (target.parent) {
const pRect = this.getWorlRect(target.parent);
rect.x += pRect.x;
rect.y += pRect.y;
const b = submitSlide.querySelector('.submit-btn');
console.log(b);
if (b) {
b.addEventListener('mousedown', this.submitPress );
b.addEventListener('mouseup', this.submitRelease );
b.addEventListener('touchstart', this.submitPress );
b.addEventListener('touchcancel', this.submitRelease );
b.addEventListener('touchend', this.submitRelease );
}
return rect;
this.splide.add( submitSlide);
nextIndex = this.splide.length - 1;
}
this.splide.go( `>${nextIndex}` );
checkPointInRect(x, y, rect) {
if (x >= rect.x && x <= rect.x + rect.width) {
if (y >= rect.y && y <= rect.y + rect.height) {
return true;
}
}
return false;
}
addUrlToAudioObj(key, url = null, vlomue = 1, loop = false, callback = null) {
const audioObj = this.audioObj;
if (url == null) {
url = key;
}
this.rawAudios.set(key, url);
const audio = new Audio();
audio.src = url;
audio.load();
audio.loop = loop;
audio.volume = vlomue;
audioObj[key] = audio;
if (callback) {
audio.onended = () => {
callback();
};
}
}
addUrlToImages(url) {
this.rawImages.set(url, url);
}
// ======================================================编写区域==========================================================================
/**
* 添加默认数据 便于无数据时的展示
*/
initDefaultData() {
if (!this.data.pic_url) {
this.data.pic_url = 'assets/play/default/pic.jpg';
this.data.pic_url_2 = 'assets/play/default/pic.jpg';
}
}
/**
* 添加预加载图片
*/
initImg() {
this.addUrlToImages(this.data.pic_url);
this.addUrlToImages(this.data.pic_url_2);
}
/**
* 添加预加载音频
*/
initAudio() {
// 音频资源
this.addUrlToAudioObj(this.data.audio_url);
this.addUrlToAudioObj(this.data.audio_url_2);
// 音效
this.addUrlToAudioObj('click', this.rawAudios.get('click'), 0.3);
}
/**
* 初始化数据
*/
initData() {
const sx = this.canvasWidth / this.canvasBaseW;
const sy = this.canvasHeight / this.canvasBaseH;
const s = Math.min(sx, sy);
this.mapScale = s;
// this.mapScale = sx;
// this.mapScale = sy;
this.renderArr = [];
}
/**
* 初始化试图
*/
initView() {
this.initPic();
this.initBottomPart();
}
initBottomPart() {
const btnLeft = new MySprite();
btnLeft.init(this.images.get('btn_left'));
btnLeft.x = this.canvasWidth - 150 * this.mapScale;
btnLeft.y = this.canvasHeight - 100 * this.mapScale;
btnLeft.setScaleXY(this.mapScale);
this.renderArr.push(btnLeft);
this.btnLeft = btnLeft;
const btnRight = new MySprite();
btnRight.init(this.images.get('btn_right'));
btnRight.x = this.canvasWidth - 50 * this.mapScale;
btnRight.y = this.canvasHeight - 100 * this.mapScale;
btnRight.setScaleXY(this.mapScale);
this.renderArr.push(btnRight);
this.btnRight = btnRight;
}
initPic() {
const maxW = this.canvasWidth * 0.7;
const pic1 = new MySprite();
pic1.init(this.images.get(this.data.pic_url));
pic1.x = this.canvasWidth / 2;
pic1.y = this.canvasHeight / 2;
pic1.setScaleXY(maxW / pic1.width);
this.renderArr.push(pic1);
this.pic1 = pic1;
const label1 = new Label();
label1.text = this.data.text;
label1.textAlign = 'center';
label1.fontSize = 50;
label1.fontName = 'BRLNSDB';
label1.fontColor = '#ffffff';
pic1.addChild(label1);
const pic2 = new MySprite();
pic2.init(this.images.get(this.data.pic_url_2));
pic2.x = this.canvasWidth / 2 + this.canvasWidth;
pic2.y = this.canvasHeight / 2;
pic2.setScaleXY(maxW / pic2.width);
this.renderArr.push(pic2);
this.pic2 = pic2;
this.curPic = pic1;
}
btnLeftClicked() {
this.lastPage();
}
btnRightClicked() {
this.nextPage();
}
lastPage() {
if (this.curPic == this.pic1) {
return;
reportUserJson(json: any) {
// @ts-ignore
// window.courseware.sendAnswer(json);
window.parent.postMessage({ action: 'temp_send_result', data: JSON.stringify(json), key: this.saveKey}, '*');
}
submitPress = (event) => {
event.stopPropagation();
event.preventDefault();
event.currentTarget.style.marginTop = '4px';
this.canTouch = false;
const moveLen = this.canvasWidth;
tweenChange(this.pic1, {x: this.pic1.x + moveLen}, 1);
tweenChange(this.pic2, {x: this.pic2.x + moveLen}, 1, () => {
this.canTouch = true;
this.curPic = this.pic1;
const baseData = JSON.parse(JSON.stringify(this.data));
baseData.questions.forEach((q, i) => {
q.userAnswer = this.userAnswers[i];
});
this.reportUserJson(baseData);
}
nextPage() {
if (this.curPic == this.pic2) {
return;
submitRelease = (event) => {
this.playSoundEffect('submit');
event.stopPropagation();
event.preventDefault();
event.currentTarget.style.marginTop = '0';
console.log(2, event);
this.isOpenResultPanel = true;
this.isSubmitted = true;
// this.splide.remove(this.splide.length - 1);
this.splide.destroy(true);
}
this.canTouch = false;
const moveLen = this.canvasWidth;
tweenChange(this.pic1, {x: this.pic1.x - moveLen}, 1);
tweenChange(this.pic2, {x: this.pic2.x - moveLen}, 1, () => {
this.canTouch = true;
this.curPic = this.pic2;
});
moreQuestion($event) {
this.playSoundEffect('more');
window.parent.postMessage({ action: 'temp_show_more', data: '' }, '*');
// this.AudioEffect.panel.play();
}
pic1Clicked() {
this.playAudio(this.data.audio_url);
}
pic2Clicked() {
this.playAudio(this.data.audio_url_2);
}
mapDown(event) {
if (!this.canTouch) {
return;
}
if ( this.checkClickTarget(this.btnLeft) ) {
this.btnLeftClicked();
return;
}
if ( this.checkClickTarget(this.btnRight) ) {
this.btnRightClicked();
return;
}
if ( this.checkClickTarget(this.pic1) ) {
this.pic1Clicked();
return;
}
if ( this.checkClickTarget(this.pic2) ) {
this.pic2Clicked();
return;
}
}
mapMove(event) {
}
init() {
mapUp(event) {
}
update() {
// ----------------------------------------------------------
this.animationId = window.requestAnimationFrame(this.update.bind(this));
// 清除画布内容
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// tween 更新动画
TWEEN.update();
// ----------------------------------------------------------
this.updateArr(this.renderArr);
}
......
......@@ -2,5 +2,6 @@
global css to mixin
*/
html, body{
overflow: hidden;
}
......@@ -8,7 +8,27 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script type="text/javascript" src="https://staging-teach.cdn.ireadabc.com/h5template/h5-static-lib/js/air.js"></script>
<style>
html, body {
overflow: hidden;
}
/*::-webkit-scrollbar-thumb{*/
/* background: red;*/
/*}*/
/*::-webkit-scrollbar-track{*/
/* background: green;*/
/*}*/
/*::-webkit-scrollbar-track {*/
/* background: blue;*/
/*}*/
/*::-webkit-scrollbar-thumb {*/
/* background-color: brown ;*/
/* border-radius: 6px;*/
/* border: 3px solid yellow;*/
/*}*/
</style>
</head>
<body>
<app-root></app-root>
......
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