Commit a3367c96 authored by liujiaxin's avatar liujiaxin

1

parent 8a51c964
...@@ -30,7 +30,10 @@ ...@@ -30,7 +30,10 @@
], ],
"styles": [ "styles": [
"./node_modules/ng-zorro-antd/ng-zorro-antd.min.css", "./node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
"src/styles.css" "./node_modules/font-awesome/css/font-awesome.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"./node_modules/animate.css/animate.min.css",
"src/styles.scss"
], ],
"scripts": [] "scripts": []
}, },
......
...@@ -5,7 +5,7 @@ import {MyErrorHandler} from './MyError'; ...@@ -5,7 +5,7 @@ import {MyErrorHandler} from './MyError';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd'; import { NgZorroAntdModule, NZ_I18N, zh_CN } from 'ng-zorro-antd';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
...@@ -50,7 +50,8 @@ registerLocaleData(zh); ...@@ -50,7 +50,8 @@ registerLocaleData(zh);
FormsModule, FormsModule,
HttpClientModule, HttpClientModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FontAwesomeModule FontAwesomeModule,
ReactiveFormsModule
], ],
providers: [ providers: [
{provide: ErrorHandler, useClass: MyErrorHandler}, {provide: ErrorHandler, useClass: MyErrorHandler},
......
@import '../style/common_mixin.css';
.model-content {
width: 100%;
height: 100%;
}
<form nz-form [formGroup]="validateForm" style="width:100%">
<nz-form-item
style="margin-bottom: 0rem"
*ngFor="let control of controlArray;let i = index">
<nz-form-control [nzXs]="10" [nzSm]="10" [nzOffset]="0">
<div nz-col class="gutter-row" [nzSpan]="23">
<label style="width: 1.2rem">A:</label>
<nz-input-group
style="width: calc(100% - 1.2rem); padding-right:8px;">
<input type="text" nz-input
tabindex="{{control.odd.id+1}}"
[attr.id]="'s_'+control.odd.id"
(blur)="saveForm()"
[formControlName]="control.odd.textInst"
placeholder="input talk text" >
<!--
<input type="hidden"
[(ngModel)]="control.odd.audioId"
[formControlName]="control.odd.audioInst">
<div class="model-content"> <nz-upload class="p-audio-uploader"
nzAction="/user/resource"
nzAccept = "audio/*"
[nzShowUploadList]="false"
class="suf-upload-btn"
(nzChange)="handleAddAudio($event, control.odd)">
<button nz-button
style="border-radius: 0 .25rem .25rem 0;">
<i class="anticon anticon-upload"></i>
</button>
</nz-upload>
<nz-progress *ngIf="control.odd.uploading" [nzPercent]="control.odd.progress" [nzStatus]="control.odd.uploadStatus"></nz-progress>
<audio *ngIf="control.odd.audioId > 0 && !control.odd.uploading" [src]="control.odd.audioId | resource" controls></audio>
-->
</nz-input-group>
</div>
</nz-form-control>
<nz-form-control [nzXs]="10" [nzSm]="10" [nzOffset]="0">
<div nz-col class="gutter-row" [nzSpan]="23">
<label style="width: 1.2rem">B:</label>
<nz-input-group
style="width: calc(100% - 1.2rem); padding-right:8px;">
<input type="text" nz-input
tabindex="{{control.even.id+1}}"
[attr.id]="'s_'+control.even.id"
(blur)="saveForm()"
[formControlName]="control.even.textInst"
placeholder="input talk text" >
<!--
<input nz-input type="hidden"
[(ngModel)]="control.even.audioId"
[formControlName]="control.even.audioInst">
<nz-upload class="p-audio-uploader"
nzAction="/user/resource"
nzAccept = "audio/*"
[nzShowUploadList]="false"
class="suf-upload-btn"
(nzChange)="handleAddAudio($event, control.even)">
<button nz-button
style="border-radius: 0 .25rem .25rem 0;">
<i class="anticon anticon-upload"></i>
</button>
</nz-upload>
<div style="position: absolute; left: 200px; top: 100px; width: 800px;"> <nz-progress *ngIf="control.even.uploading" [nzPercent]="control.even.progress" [nzStatus]="control.even.uploadStatus"></nz-progress>
<audio *ngIf="control.even.audioId > 0 && !control.even.uploading" [src]="control.even.audioId | resource" controls></audio>
-->
</nz-input-group>
<input type="text" nz-input [(ngModel)]="item.text" (blur)="save()"> </div>
</nz-form-control>
<app-upload-image-with-preview <nz-form-control [nzXs]="4" [nzSm]="4" [nzOffset]="0">
[picUrl]="item.pic_url" <div nz-col class="gutter-row" [nzSpan]="23">
(imageUploaded)="onImageUploadSuccess($event, 'pic_url')" <i class="anticon anticon-minus-circle-o dynamic-delete-button" (click)="removeField(control,$event)"></i>
></app-upload-image-with-preview> </div>
</nz-form-control>
<app-audio-recorder </nz-form-item>
[audioUrl]="item.audio_url" <nz-form-item style="margin-top: 1rem;">
(audioUploaded)="onAudioUploadSuccess($event, 'audio_url')" <nz-form-control [nzXs]="{span:24,offset:0}" [nzSm]="{span:20,offset:4}">
></app-audio-recorder> <button nz-button nzType="dashed" style="width:60%" (click)="addField(null, $event)"><i class="anticon anticon-plus"></i> Add field</button>
<app-custom-hot-zone></app-custom-hot-zone> </nz-form-control>
<app-upload-video></app-upload-video> </nz-form-item>
<app-lesson-title-config></app-lesson-title-config> </form>
</div>
</div>
\ No newline at end of file
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 4px;
font-size: 24px;
color: #999;
transition: all .3s;
}
.dynamic-delete-button:hover {
color: #777;
}
[nz-form] {
max-width: 100%;
}
.suf-upload-btn{
position: absolute;
left: 100%;
transform: translateX(-100%);
border-radius: 0 .25rem .25rem 0;
z-index: 1;
}
import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core'; import {Component, EventEmitter, Input, OnDestroy, OnChanges, OnInit, Output, ApplicationRef, ChangeDetectorRef} from '@angular/core';
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { UploadFile } from 'ng-zorro-antd';
@Component({ @Component({
selector: 'app-form', selector: 'app-form',
templateUrl: './form.component.html', templateUrl: './form.component.html',
styleUrls: ['./form.component.css'] styleUrls: ['./form.component.scss']
}) })
export class FormComponent implements OnInit, OnChanges, OnDestroy { export class FormComponent implements OnInit, OnChanges, OnDestroy {
// 储存数据用 // 储存数据用
saveKey = "test_0011"; saveKey = 'ww_talk';
// 储存对象 // 储存对象
item; item;
validateForm: FormGroup;
controlArray: Array<{
constructor(private appRef: ApplicationRef,private changeDetectorRef: ChangeDetectorRef) { // id: number, controlInstance: string
// row_key: string,
odd: {
id: number,
text: string,
textInst: string,
audioInst: string,
progress: number,
uploadStatus: string,
uploading: boolean,
audioId: number
},
even: {
id: number,
text: string,
textInst: string,
audioInst: string,
progress: number,
uploadStatus: string,
uploading: boolean,
audioId: number
}
}> = [];
constructor(private appRef: ApplicationRef,
private fb: FormBuilder,
private changeDetectorRef: ChangeDetectorRef) {
} }
ngOnInit() { ngOnInit() {
this.item = {}; this.item = {
contentObj: {}
};
// 获取存储的数据 // 获取存储的数据
(<any> window).courseware.getData((data) => { (window as any).courseware.getData((data) => {
if (data) { if (data) {
this.item = data; this.item = data;
} }
this.controlArray.length = 0;
this.validateForm = this.fb.group({});
if (this.item.contentObj.talk && Array.isArray(this.item.contentObj.talk) ) {
for (let i = 0 ; i < this.item.contentObj.talk[0].length; i++ ) {
const control = {
// row_key: `row${i}`,
odd: {
id: i * 2,
text: this.item.contentObj.talk[0][i].text,
textInst: `text${i * 2 }`,
audioInst: `audio${i * 2 }`,
progress: 0,
uploadStatus: '',
uploading: false,
audioId: this.item.contentObj.talk[0][i].audio
},
even: {
id: i * 2 + 1,
text: this.item.contentObj.talk[1][i].text,
textInst: `text${i * 2 + 1}`,
audioInst: `audio${i * 2 + 1}`,
progress: 0,
uploadStatus: '',
uploading: false,
audioId: this.item.contentObj.talk[1][i].audio
}
};
this.addField(control);
}
} else {
this.addField();
}
this.init(); this.init();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
...@@ -53,26 +116,26 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -53,26 +116,26 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
} }
//
/** // /**
* 储存图片数据 // * 储存图片数据
* @param e // * @param e
*/ // */
onImageUploadSuccess(e, key) { // onImageUploadSuccess(e, key) {
//
this.item[key] = e.url; // this.item[key] = e.url;
this.save(); // this.save();
} // }
//
/** // /**
* 储存音频数据 // * 储存音频数据
* @param e // * @param e
*/ // */
onAudioUploadSuccess(e, key) { // onAudioUploadSuccess(e, key) {
//
this.item[key] = e.url; // this.item[key] = e.url;
this.save(); // this.save();
} // }
...@@ -80,7 +143,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -80,7 +143,7 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
* 储存数据 * 储存数据
*/ */
save() { save() {
(<any> window).courseware.setData(this.item, null, this.saveKey); (window as any).courseware.setData(this.item, null, this.saveKey);
this.refresh(); this.refresh();
} }
...@@ -93,5 +156,182 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy { ...@@ -93,5 +156,182 @@ export class FormComponent implements OnInit, OnChanges, OnDestroy {
}, 1); }, 1);
} }
addField(control?: any, e?: MouseEvent): void {
if (e) {
e.preventDefault();
}
const sts = this.checkForm();
if (!sts) {
return;
}
const id = (this.controlArray.length > 0) ? (this.controlArray[ this.controlArray.length - 1 ].odd.id + 2) : 0;
if (!control) {
control = {
// row_key: `row${id}`,
odd: {
id: id * 2,
text: '',
textInst: `text${id * 2 }`,
audioInst: `audio${id * 2 }`,
progress: 0,
uploadStatus: '',
uploading: false,
audioId: 0
},
even: {
id: id * 2 + 1,
text: '',
textInst: `text${id * 2 + 1}`,
audioInst: `audio${id * 2 + 1}`,
progress: 0,
uploadStatus: '',
uploading: false,
audioId: 0
}
};
}
const index = this.controlArray.push(control);
const row = this.controlArray[ index - 1 ];
this.validateForm.addControl(
row.odd.textInst,
new FormControl(row.odd.text, Validators.required));
this.validateForm.addControl(
row.even.textInst,
new FormControl(row.even.text, Validators.required));
this.validateForm.addControl(
row.odd.audioInst,
new FormControl(row.odd.audioId));
this.validateForm.addControl(
row.even.audioInst,
new FormControl(row.even.audioId));
}
removeField(i: any, e: MouseEvent): void {
e.preventDefault();
if (this.controlArray.length > 1) {
const index = this.controlArray.indexOf(i);
this.controlArray.splice(index, 1);
// console.log(this.controlArray);
this.validateForm.removeControl(i.odd.textInst);
this.validateForm.removeControl(i.even.textInst);
this.validateForm.removeControl(i.odd.audioInst);
this.validateForm.removeControl(i.even.audioInst);
this.saveForm(true);
}
}
getFormControl(name: string): AbstractControl {
console.log('getFormControl', name);
return this.validateForm.controls[ name ];
}
checkForm(): boolean {
const txts = Object.keys(this.validateForm.controls)
.filter(c => c.startsWith('text') )
.map(c => parseInt(c.replace('text', ''), 10));
const cl = txts.length; // Object.keys(this.validateForm.controls).length / 2;
if (cl === 0) {
return true;
}
for (let i = 0 ; i < cl ; i += 2) {
const k = txts[i];
const c1 = this.validateForm.controls[`text${k}`];
const c2 = this.validateForm.controls[`text${k + 1}`];
if (!c1.value.trim() && !c2.value.trim() ) {
c1.markAsDirty();
c1.updateValueAndValidity();
c1.setErrors({
required: true
});
c2.markAsDirty();
c2.updateValueAndValidity();
c2.setErrors({
required: true
});
return false;
} else {
c1.clearValidators();
c2.clearValidators();
c1.updateValueAndValidity();
c2.updateValueAndValidity();
}
}
return true;
// for (const i in this.validateForm.controls) {
// console.log(i)
// const c = this.validateForm.controls[ i ];
// if (c.invalid) {
// c.markAsDirty();
// c.updateValueAndValidity();
// c.setErrors(c.errors);
// return false;
// }
// }
// return true;
}
handleAddAudio(info: { type: string, file: UploadFile, event: any }, control): void {
switch (info.type) {
case 'start':
control.uploading = true;
control.progress = 0;
control.uploadStatus = 'active';
break;
case 'success':
this.validateForm.controls[control.audioInst].setValue(info.file.response.res_id);
console.log('info.file.response', info.file.response);
control.uploading = false;
control.audioId = info.file.response.res_id;
// control.audioUploaded.emit(info.file.response);
control.uploadStatus = '';
this.saveForm();
break;
case 'progress':
control.progress = parseInt(info.event.percent, 10);
break;
}
}
saveForm(forceUpdate?: boolean): void {
if (!forceUpdate) {
if (!this.validateForm.dirty) {
return;
}
const sts = this.checkForm();
if (!sts) {
return;
}
}
const ts = Object.keys(this.validateForm.value).filter(f => f.startsWith('text'));
const ks = [];
for (const t of ts) {
const vk = parseInt(t.replace('text', ''), 10);
ks.push(vk);
}
// const c = Object.keys(this.validateForm.value).length / 2;
const s1 = [];
const s2 = [];
for (const idx of ks) {
if (idx % 2 === 0) {
s1.push({
text: this.validateForm.value[`text${idx}`],
audio: this.validateForm.value[`audio${idx}`]
});
} else {
s2.push({
text: this.validateForm.value[`text${idx}`],
audio: this.validateForm.value[`audio${idx}`]
});
}
}
// console.log(this.validateForm.value, sts);
this.item.contentObj.talk = [s1, s2];
// console.log(this.item.contentObj);
this.save();
}
} }
.game-container {
width: 100%;
height: 100%;
background: #ffffff;
background-size: cover;
}
#canvas {
}
@font-face
{
font-family: 'BRLNSDB';
src: url("../../assets/font/BRLNSDB.TTF") ;
}
<div class="game-container" #wrap> <div class="player-preview-content"
<canvas id="canvas" #canvas></canvas> (click)="handleTapText();$event.preventDefault()"
style="background-image: url('../../assets/talk/bg.png');">
<div class="all-talk-wrap">
<div class="all-talk-box" #talkListBox>
<ul class="talk-list">
<!--<li *ngFor="let itm of talkListData index as i;" id="tklst_{{i%2===0?'A':'B'}}_{{i}}" >-->
<!--{{itm.text}}-->
<!--</li>-->
</ul>
</div>
</div>
<div class="monster-talk">
<div class="monster-talk-wrap">
<div class="A-role">
<div class="A-avatar" ></div>
<!--<div class="A-text-wrap" *ngIf="talkText.A" [style.zIndex]="zIdxA">-->
<!--<img class="talk-bubble" src="assets/talk/bubble_left.png" />-->
<!--<div class="talktext">-->
<!--<p>-->
<!--{{talkText.A}}-->
<!--</p>-->
<!--</div>-->
<!--</div>-->
</div>
<div class="talk-bubble-box">
<div class="p-dialog A-text-wrap" *ngIf="talkText.A" [@dialog]="animationA" [style.zIndex]="zIdxA">
<img class="talk-bubble" src="assets/talk/bubble_left.png" />
<div class="talktext">
<p>
{{talkText.A}}
</p>
</div>
</div>
<div class="p-dialog B-text-wrap" *ngIf="talkText.B" [@dialog]="animationB" [style.zIndex]="zIdxB">
<img class="talk-bubble" src="assets/talk/bubble_right.png" />
<div class="talktext">
<p>
{{talkText.B}}
</p>
</div>
</div>
</div>
<div class="B-role">
<div class="B-avatar" ></div>
<!--<div class="B-text-wrap" *ngIf="talkText.B" [style.zIndex]="zIdxB">-->
<!--<img class="talk-bubble" src="assets/talk/bubble_right.png" />-->
<!--<div class="talktext">-->
<!--<p>-->
<!--{{talkText.B}}-->
<!--</p>-->
<!--</div>-->
<!--</div>-->
</div>
</div>
</div>
</div> </div>
@font-face {
font-family: arialRound;
src: url('../../assets/talk/ArialRoundedBold.woff');
}
.player-preview-content{
width: 100%;
padding: 0 2em;
height: 100%;
position: relative;
font-family: arialRound;
background-size: 100%;
background-position-y: 18.4%;
background-image: url('../../assets/talk/bg.png');
font-size: 2.5vh;
.all-talk-wrap{
position: absolute;
right: 7.5%;
top: 23%;
text-align: left;
width: 18%;
word-wrap: break-word;
height: 59%;
overflow: hidden;
z-index: 99;
.all-talk-box{
position: relative;
height: 100%;
/*font-size: 0.8rem;*/
overflow-y: scroll;
margin-right: -20px;
.talk-list{
list-style: none;
padding: 0;
margin: 0;
padding: 0 1em 0 0;
}
.talk-list li{
display: inline-flex;
word-break: break-word;
line-height: 2rem;
}
}
}
.monster-talk{
width: 100%;
padding-right: 12em;
height: 100%;
padding-bottom: 12%;
position: relative;
display: flex;
flex-direction: row;
.monster-talk-wrap{
flex: 1;
position: relative;
display: flex;
.talk-bubble-box{
flex: 1;
position: relative;
}
.A-role, .B-role{
/*bottom: 0;
position: absolute;
left: 5%;
padding-bottom: inherit;
clear:both;*/
flex: 0 0 25%;
position: relative;
}
.B-role{
/*right: 0%;
left: auto;
padding-right: inherit;*/
}
.A-avatar, .B-avatar{
background-repeat: no-repeat;
background-size: contain;
background-position: center;
/*width: 8rem;
height: 10rem;*/
width: 100%;
padding-top: 125%;
font-weight: 900;
position: absolute;
bottom: 0;
}
.A-avatar{
background-image: url('../../assets/talk/A.png')
}
.B-avatar{
/*right: 0;
float: right;*/
background-image: url('../../assets/talk/B.png')
}
.A-text-wrap, .B-text-wrap{
position: relative;
}
.A-text-wrap {
/*float: right;*/
/*transform: translate(-15% ,-15%);*/
/*transform: translate(52%, 29%);*/
position: absolute;
max-width: 100%;
/*left: 26%;*/
top: 61%;
}
.B-text-wrap {
/*float: right;8/
/*transform: translate(6%, 130%);*/
/*transform: translate(-93%, -29%);*/
position: absolute;
max-width: 100%;
/*right: 45%;*/
top: 61%;
right: 0;
z-index: 2;
.talktext{
padding: 1em;
}
}
.A-text-wrap:before{
content: url('../../assets/talk/left_tri.png');
/*top: 34.5%;
position: absolute;
left: 88%;*/
top: 61.5%;
position: absolute;
left: 0;
z-index: 2;
transform: translateX(-20px);
z-index: 2;
}
.B-text-wrap:before{
content: url('../../assets/talk/right_tri.png');
top: 47.5%;
position: absolute;
right: 0;
transform: translateX(18px);
z-index: 2;
}
.talk-bubble{
width: 100%;
height: 100%;
text-align: left;
display: inline-block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.talktext{
padding: 1em;
text-align: left;
line-height: 1.5rem;
position: relative;
word-break: break-word;
z-index: 1;
}
.talktext p{
-webkit-margin-before: 0em;
-webkit-margin-after: 0em;
}
}
}
}
:host ::ng-deep {
@keyframes move-up {
0% {
top: 78%
}
100% {
top: 61%
}
}
}
import {Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener} from '@angular/core'; import { Component, ElementRef, ViewChild, OnInit, Input, OnDestroy, HostListener, AfterViewInit } from '@angular/core';
import { import {
Label, Label,
...@@ -11,6 +11,7 @@ import {Subject} from 'rxjs'; ...@@ -11,6 +11,7 @@ import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators'; import {debounceTime} from 'rxjs/operators';
import TWEEN from '@tweenjs/tween.js'; import TWEEN from '@tweenjs/tween.js';
import { animate, state, style, transition, trigger } from '@angular/animations';
...@@ -18,662 +19,188 @@ import TWEEN from '@tweenjs/tween.js'; ...@@ -18,662 +19,188 @@ import TWEEN from '@tweenjs/tween.js';
@Component({ @Component({
selector: 'app-play', selector: 'app-play',
templateUrl: './play.component.html', templateUrl: './play.component.html',
styleUrls: ['./play.component.css'] styleUrls: ['./play.component.scss'],
animations: [
trigger('dialog', [
state('fadeIn', style({opacity: '1', top: '61%'})),
state('moveUp', style({opacity: '1', top: '78%'})),
transition('* => fadeIn', [
style({opacity: '0', top: '61%'}),
animate('1s ease-in-out', style({opacity: '1', top: '61%'}))
]),
transition('* => moveUp', [
style({opacity: '1', top: '61%'}),
animate('1s ease-in-out', style({opacity: '1', top: '78%'}))
]),
])
],
}) })
export class PlayComponent implements OnInit, OnDestroy { export class PlayComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('canvas', {static: true }) canvas: ElementRef; talkText = {
@ViewChild('wrap', {static: true }) wrap: ElementRef; A: '',
B: ''
// 数据 };
data; _zIdx = 0;
zIdxB = 0;
ctx; zIdxA = 0;
// BText: string;
canvasWidth = 1280; // canvas实际宽度 talkIdx = {
canvasHeight = 720; // canvas实际高度 A: -1,
B: -1
canvasBaseW = 1280; // canvas 资源预设宽度 };
canvasBaseH = 720; // canvas 资源预设高度 talkListData = [];
audio = new Audio();
mx; // 点击x坐标 @Input() data ;
my; // 点击y坐标 @Input() sid ;
@ViewChild('talkListBox', {static: true})
// 资源 talkListBox: ElementRef;
rawImages = new Map(res);
rawAudios = new Map(resAudio); animationA = '';
animationB = '';
images = new Map(); saveKey = 'ww_talk';
animationId: any;
winResizeEventStream = new Subject();
audioObj = {};
renderArr;
mapScale = 1;
canvasLeft;
canvasTop;
saveKey = 'test_0011';
btnLeft;
btnRight;
pic1;
pic2;
canTouch = true;
curPic;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.winResizeEventStream.next();
}
ngOnInit() { ngOnInit() {
this.data = {}; this.data = {};
this.init();
// 获取数据 // 获取数据
const getData = (<any> window).courseware.getData; const getData = (window as any).courseware.getData;
getData((data) => { getData((data) => {
if (data && typeof data === 'object') {
if (data && typeof data == 'object') {
this.data = data; this.data = data;
} }
// console.log('data:' , data);
// 初始化 各事件监听
this.initListener();
// 若无数据 则为预览模式 需要填充一些默认数据用来显示
this.initDefaultData();
// 初始化 音频资源
this.initAudio();
// 初始化 图片资源
this.initImg();
// 开始预加载资源
this.load();
}, this.saveKey);
}
ngOnDestroy() {
window['curCtx'] = null;
window.cancelAnimationFrame(this.animationId);
}
load() {
// 预加载资源
this.loadResources().then(() => {
window["air"].hideAirClassLoading(this.saveKey, this.data);
this.init(); this.init();
this.update(); if (window['air']) {
}); window['air'].hideAirClassLoading(this.saveKey, this.data);
} }
}, this.saveKey);
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;
this.ctx = this.canvas.nativeElement.getContext('2d');
this.canvas.nativeElement.width = this.canvasWidth;
this.canvas.nativeElement.height = this.canvasHeight;
window['curCtx'] = this.ctx;
}
updateItem(item) {
if (item) {
item.update();
}
} }
init(){
updateArr(arr) { this.talkIdx = {
if (!arr) { A: -1,
B: -1
};
this.talkText = {
A: '',
B: ''
};
this._zIdx = 0;
this.zIdxB = 0;
this.zIdxA = 0;
this.talkListData = [];
// this.talkText.A = this.data.contentObj[0][this.talkIdx.A].text;
// this.talkText.B = this.data.contentObj[1][this.talkIdx.B].text;
this.talkListBox.nativeElement.querySelector('ul').innerHTML = '';
// this.addTextToList(this.talkText.A);
// this.addTextToList(this.talkText.B);
if (!this.data.contentObj || !this.data.contentObj.talk || !Array.isArray(this.data.contentObj.talk)) {
return; return;
} }
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < this.data.contentObj.talk[0].length; i++) {
arr[i].update(this); this.talkListData.push(this.data.contentObj.talk[0][i]);
this.talkListData.push(this.data.contentObj.talk[1][i]);
} }
} }
ngOnDestroy() {
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);
};
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();
} }
playAudio(key, now = false, callback = null) { ngAfterViewInit() {
// console.log('aaaaaaaaaaaa');
const audio = this.audioObj[key]; const el = this.talkListBox.nativeElement;
if (audio) { const ul = el.querySelector('ul');
if (now) { el.addEventListener('mousedown', (e) => {
audio.pause(); if (ul.offsetHeight > el.offsetHeight) {
audio.currentTime = 0; el._dragStart = true;
} el._dragStratY = e.pageY;
el._st = el.scrollTop;
if (callback) { } else {
audio.onended = () => { el._dragStart = false;
callback();
};
} }
audio.play();
}
}
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);
}); });
el.addEventListener('mousemove', (e) => {
this.rawAudios.forEach((value, key) => {// 预加载音频 if (el._dragStart) {
const delta = el._dragStratY - e.pageY;
const a = this.preloadAudio(value) el.scrollTop = el._st + delta;
.then(() => { }
// this.images.set(key, img);
})
.catch(err => console.log(err));
pr.push(a);
}); });
return Promise.all(pr); el.addEventListener('mouseup', (e) => {
} el._dragStart = false;
preload(url) {
return new Promise((resolve, reject) => {
const img = new Image();
// img.crossOrigin = "anonymous";
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
}); });
} el.addEventListener('mouseout', (e) => {
el._dragStart = false;
preloadAudio(url) {
return new Promise((resolve, reject) => {
const audio = new Audio();
audio.oncanplay = (a) => {
resolve();
};
audio.onerror = () => {
reject();
};
audio.src = url;
audio.load();
}); });
} }
renderAfterResize() { handleTapText() {
this.canvasWidth = this.wrap.nativeElement.clientWidth; let group;
this.canvasHeight = this.wrap.nativeElement.clientHeight; if (this._zIdx % 2 === 0) {
this.init(); group = 'A';
} } else {
group = 'B';
checkClickTarget(target) {
const rect = target.getBoundingBox();
if (this.checkPointInRect(this.mx, this.my, rect)) {
return true;
}
return false;
}
getWorlRect(target) {
let rect = target.getBoundingBox();
if (target.parent) {
const pRect = this.getWorlRect(target.parent);
rect.x += pRect.x;
rect.y += pRect.y;
}
return rect;
}
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';
} }
} if (!this.data.contentObj.talk || !Array.isArray(this.data.contentObj.talk)) {
/**
* 添加预加载图片
*/
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; return;
} }
if (this.talkIdx[group] !== -1 && this.talkIdx[group] !== 0 &&
this.canTouch = false; (this.talkIdx[group] % this.data.contentObj.talk[0].length === 0 ||
this.talkIdx[group] % this.data.contentObj.talk[1].length === 0)) {
const moveLen = this.canvasWidth; // this.talkIdx[group] = 0;
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;
});
}
nextPage() {
if (this.curPic == this.pic2) {
return; return;
} }
this.canTouch = false; this.talkIdx[group] += 1;
const g = group === 'A' ? 0 : 1;
const moveLen = this.canvasWidth; if (!this.data.contentObj.talk[g][this.talkIdx[group]]) {
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;
});
}
pic1Clicked() {
this.playAudio(this.data.audio_url);
}
pic2Clicked() {
this.playAudio(this.data.audio_url_2);
}
mapDown(event) {
if (!this.canTouch) {
return; return;
} }
this.talkText[group] = this.data.contentObj.talk[g][this.talkIdx[group]].text;
if ( this.checkClickTarget(this.btnLeft) ) { this._zIdx ++;
this.btnLeftClicked(); this['zIdx' + group] = this._zIdx;
return; this.addTextToList(this.talkText[group], group);
if (group === 'A') {
this.animationA = 'fadeIn';
this.animationB = 'moveUp';
} else if (group === 'B') {
this.animationB = 'fadeIn';
this.animationA = 'moveUp';
} }
}
if ( this.checkClickTarget(this.btnRight) ) { addTextToList(text: string, group: string) {
this.btnRightClicked(); if (!text) {
return; return;
} }
const li = document.createElement('li');
if ( this.checkClickTarget(this.pic1) ) { li.innerHTML = `<img src="/assets/talk/${group}.png"
this.pic1Clicked(); style="width: 2rem;height: 2rem;
return; position: absolute;left: 0px;
top: 50%;margin-top: -1rem">${text}`;
// li.style.display = 'inline-flex';
li.style.wordBreak = 'break-word';
li.style.lineHeight = '1.5rem';
li.style.padding = '.25rem .5rem .25rem 2rem';
li.style.position = 'relative';
li.style.borderBottom = '1px solid #eee';
li.style.fontSize = '80%';
li.style.marginTop = '0.5rem';
const ul = this.talkListBox.nativeElement.querySelector('ul');
ul.append(li);
const p_h = this.talkListBox.nativeElement.offsetHeight;
const l_h = ul.offsetHeight;
if ( l_h > p_h ) {
const ofst = l_h - p_h;
this.talkListBox.nativeElement.scrollTop = ofst;
} }
if ( this.checkClickTarget(this.pic2) ) {
this.pic2Clicked();
return;
}
}
mapMove(event) {
}
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);
}
} }
@mixin hide-overflow-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@mixin k-no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@mixin k-img-bg {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
/* You can add global styles to this file, and also import other style files */
html, body {
width: 100%;
height: 100%;
font-size: 16px;
}
body {
background-color: #f0f2f5 !important;
}
* {
outline: none;
}
@font-face {
font-family: ChalkboardSE-Regular;
src: url('/assets/font/ChalkboardSE-Regular.woff') format('WOFF');
}
.k-16-9 {
position: relative;
width: 100%;
height: fit-content;
&:before {
content: "";
display: block;
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
}
}
.k-full-fill {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.k-flex-center {
display: flex;
align-items: center;
justify-content: center;
}
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